Oto pomysł. Ten problem dzielimy na kilka kroków:
Określ średni prostokątny obszar konturu. Wysuwamy próg, a następnie wyszukujemy kontury i filtrujemy, korzystając z obwiedniowego obszaru konturu. Powodem tego jest obserwacja, że każda typowa postać będzie tylko tak duża, podczas gdy duży hałas obejmie większy prostokątny obszar. Następnie określamy średnią powierzchnię.
Usuń duże kontury odstające. Powtarzamy ponownie kontury i usuwamy duże kontury, jeśli są one 5x
większe niż średnia powierzchnia konturu, wypełniając kontur. Zamiast korzystać ze stałego progu, używamy tego dynamicznego progu dla większej niezawodności.
Dylatuj za pomocą pionowego jądra, aby łączyć znaki . Chodzi o to, aby wykorzystać spostrzeżenie, że znaki są wyrównane w kolumnach. Po rozszerzeniu za pomocą pionowego jądra łączymy tekst razem, aby hałas nie był uwzględniany w tym połączonym konturze.
Usuń niewielki hałas . Teraz, gdy tekst do zachowania jest połączony, znajdujemy kontury i usuwamy wszelkie kontury mniejsze niż 4x
średni obszar konturu.
Bitowo i zrekonstruować obraz . Ponieważ chcieliśmy tylko, aby kontury pozostały na naszej masce, bitowo - i aby zachować tekst i uzyskać nasz wynik.
Oto wizualizacja procesu:
Mamy próg Otsu, aby uzyskać obraz binarny, a następnie znaleźć kontury, aby określić średni prostokątny obszar konturu. Stąd usuwamy duże kontury odstające podświetlone na zielono poprzez wypełnienie konturów
Następnie konstruujemy pionowe jądro i rozszerzamy, aby połączyć znaki. Ten krok łączy cały pożądany tekst, aby zachować i odizolować hałas na pojedyncze obiekty BLOB.
Teraz znajdujemy kontury i filtrujemy za pomocą obszaru konturu, aby usunąć mały szum
Oto wszystkie usunięte cząsteczki hałasu podświetlone na zielono
Wynik
Kod
import cv2
# Load image, grayscale, and Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Determine average contour area
average_area = []
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
area = w * h
average_area.append(area)
average = sum(average_area) / len(average_area)
# Remove large lines if contour area is 5x bigger then average contour area
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
area = w * h
if area > average * 5:
cv2.drawContours(thresh, [c], -1, (0,0,0), -1)
# Dilate with vertical kernel to connect characters
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,5))
dilate = cv2.dilate(thresh, kernel, iterations=3)
# Remove small noise if contour area is smaller than 4x average
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
area = cv2.contourArea(c)
if area < average * 4:
cv2.drawContours(dilate, [c], -1, (0,0,0), -1)
# Bitwise mask with input image
result = cv2.bitwise_and(image, image, mask=dilate)
result[dilate==0] = (255,255,255)
cv2.imshow('result', result)
cv2.imshow('dilate', dilate)
cv2.imshow('thresh', thresh)
cv2.waitKey()
Uwaga: Tradycyjne przetwarzanie obrazu ogranicza się do progowania, operacji morfologicznych i filtrowania konturów (aproksymacja konturu, powierzchnia, współczynnik kształtu lub wykrywanie plam). Ponieważ obrazy wejściowe mogą się różnić w zależności od rozmiaru tekstu znaków, znalezienie pojedynczego rozwiązania jest dość trudne. Być może warto przyjrzeć się szkoleniu własnego klasyfikatora z uczeniem maszynowym / głębokim w zakresie dynamicznego rozwiązania.