Czyszczenie obrazu dla OCR

9

Próbowałem wyczyścić obrazy dla OCR: (linie)

wprowadź opis zdjęcia tutaj

Muszę usunąć te linie, aby czasami przetworzyć obraz i zbliżam się dość blisko, ale często próg zabiera zbyt wiele tekstu:

    copy = img.copy()
    blur = cv2.GaussianBlur(copy, (9,9), 0)
    thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,11,30)

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
    dilate = cv2.dilate(thresh, kernel, iterations=2)

    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 > 300:
            x,y,w,h = cv2.boundingRect(c)
            cv2.rectangle(copy, (x, y), (x + w, y + h), (36,255,12), 3)

Edycja: Ponadto użycie stałych liczb nie będzie działać w przypadku zmiany czcionki. Czy istnieje ogólny sposób, aby to zrobić?

K41F4r
źródło
2
Niektóre z tych wierszy lub ich fragmenty mają te same cechy, co tekst prawny i trudno będzie się ich pozbyć bez zepsucia prawidłowego tekstu. Jeśli tak się stanie, możesz skupić się na faktach, że są one dłuższe niż postacie i nieco odizolowane. Pierwszym krokiem może być więc oszacowanie wielkości i bliskości znaków.
Yves Daoust,
@YvesDaoust Jak poszedłbyś znaleźć bliskość postaci? (ponieważ filtrowanie wyłącznie według rozmiaru często miesza się z postaciami)
K41F4r
1
Możesz znaleźć dla każdej kropli odległość do jej najbliższego sąsiada. Następnie poprzez analizę histogramu odległości można znaleźć próg między „zamknięciem” a „rozłożeniem” (coś w rodzaju trybu rozkładu) lub między „otoczeniem” a „odizolowaniem”.
Yves Daoust,
W przypadku wielu małych linii w pobliżu siebie, czy ich najbliższy sąsiad nie byłby drugą małą linią? Czy obliczanie średniej odległości do wszystkich innych obiektów blob byłoby zbyt kosztowne?
K41F4r,
„czy ich najbliższy sąsiad nie byłby drugą małą linijką?”: dobry sprzeciw, Wysoki Sądzie. W rzeczywistości kilka bliskich krótkich segmentów nie różni się od legalnego tekstu, choć w zupełnie nieprawdopodobnym układzie. Może być konieczne przegrupowanie fragmentów linii przerywanych. Nie jestem pewien, czy uratuje cię średnia odległość do wszystkich.
Yves Daoust,

Odpowiedzi:

14

Oto pomysł. Ten problem dzielimy na kilka kroków:

  1. 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ę.

  2. Usuń duże kontury odstające. Powtarzamy ponownie kontury i usuwamy duże kontury, jeśli są one 5xwię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.

  3. 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.

  4. Usuń niewielki hałas . Teraz, gdy tekst do zachowania jest połączony, znajdujemy kontury i usuwamy wszelkie kontury mniejsze niż 4xśredni obszar konturu.

  5. 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

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

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.

wprowadź opis zdjęcia tutaj

Teraz znajdujemy kontury i filtrujemy za pomocą obszaru konturu, aby usunąć mały szum

wprowadź opis zdjęcia tutaj

Oto wszystkie usunięte cząsteczki hałasu podświetlone na zielono

wprowadź opis zdjęcia tutaj

Wynik

wprowadź opis zdjęcia tutaj

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.

nathancy
źródło
1
Czy w przypadku większej czcionki nie skasowałby też tekstu?
K41F4r,
Tak, może, więc musisz dostosować wartość obszaru progowego. Aby uzyskać bardziej dynamiczne podejście, pomysłem jest określenie średniego obszaru postaci, a następnie użycie go jako progu
nathancy
Wydaje się, że jest zbyt specyficzny dla przykładu, użycie średniego obszaru będzie nadal usuwać tekst, co znacznie pogarsza wynik dla OCR
K41F4r
Czy masz inny przykładowy obraz wejściowy, który możesz dodać do postu?
nathancy,
1
Znalezienie rozwiązania, które działa we wszystkich sytuacjach przy użyciu tradycyjnych technik przetwarzania obrazu, jest dość trudne. Być może warto przyjrzeć się szkoleniu własnego klasyfikatora za pomocą głębokiego uczenia się. Powodzenia!
nathancy