przetwarzanie obrazu w celu poprawy dokładności rozpoznawania OCR tesseract

145

Używam tesseract do konwersji dokumentów na tekst. Jakość dokumentów jest bardzo różna, a ja szukam wskazówek, jakie przetwarzanie obrazu może poprawić wyniki. Zauważyłem, że tekst, który jest silnie pikselizowany - na przykład ten generowany przez faksy - jest szczególnie trudny do przetworzenia przez tesserakt - prawdopodobnie wszystkie te postrzępione krawędzie znaków mylą algorytmy rozpoznawania kształtów.

Jakie techniki przetwarzania obrazu poprawiłyby dokładność? Używałem rozmycia Gaussa, aby wygładzić piksele obrazy i zauważyłem niewielką poprawę, ale mam nadzieję, że istnieje bardziej szczegółowa technika, która zapewni lepsze wyniki. Powiedz filtr dostrojony do czarno-białych obrazów, który wygładzi nieregularne krawędzie, a następnie filtr, który zwiększy kontrast, aby postacie były bardziej wyraźne.

Jakieś ogólne wskazówki dla kogoś, kto jest nowicjuszem w przetwarzaniu obrazu?

user364902
źródło

Odpowiedzi:

103
  1. napraw DPI (w razie potrzeby) 300 DPI to minimum
  2. popraw rozmiar tekstu (np. 12 punktów powinno być w porządku)
  3. spróbuj naprawić linie tekstu (tekst pochylony i wypaczony)
  4. spróbuj naprawić oświetlenie obrazu (np. brak ciemnej części obrazu)
  5. binaryzować i odszumiać obraz

Nie ma uniwersalnej linii poleceń, która pasowałaby do wszystkich przypadków (czasami trzeba rozmyć i wyostrzyć obraz). Ale możesz wypróbować TEXTCLEANER z programu Fred's ImageMagick Scripts .

Jeśli nie jesteś fanem wiersza poleceń, może możesz spróbować użyć opensource scantailor.sourceforge.net lub komercyjnego bookrestorer .

user898678
źródło
6
Jest też ilustrowany przewodnik, jak to zrobić: code.google.com/p/tesseract-ocr/wiki/ImproveQuality
iljau
2
Zauważ, że połączony skrypt wydaje się być tylko dla Linuksa.
Zoran Pavlovic
1
To nieprawda - to jest skrypt bash. Jeśli zainstalowałeś bash i ImageMagick, będą one działać również w systemie Windows. Bash można zainstalować jako część innego przydatnego oprogramowania, np. Git lub msys2 ...
user898678.
6
@iljau Od czasu przeniesienia do github. strona wiki jest pod adresem: github.com/tesseract-ocr/tesseract/wiki/ImproveQuality
hometoast
2
Dokumentacja Tesseract została ponownie przeniesiona na tesseract-ocr.github.io/tessdoc/ImproveQuality
Nobody
73

W żadnym wypadku nie jestem ekspertem od OCR. Ale w tym tygodniu musiałem przekonwertować tekst z jpg.

Zacząłem od pokolorowanego, RGB 445x747 pikseli jpg. Natychmiast wypróbowałem na tym tesseract i program prawie nic nie przekonwertował. Następnie wszedłem do GIMP i wykonałem następujące czynności. obraz> tryb> obraz w skali szarości> skalowanie obrazu> filtry 1191x2000 pikseli> ulepszanie> wyostrzająca maska ​​z wartościami promienia = 6,8, ilości = 2,69, próg = 0 Następnie zapisałem jako nowy plik jpg w jakości 100%.

Tesseract był wtedy w stanie wyodrębnić cały tekst do pliku .txt

Gimp to twój przyjaciel.

Jan
źródło
11
+1 Podążyłem za Twoimi krokami i uzyskałem znaczną poprawę. Dzięki
dniu
1
Mam też wrażenie, że Tesseract działa lepiej, jeśli przekonwertujesz dane wejściowe do pliku TIFF i dasz Tesseractowi TIFF (zamiast prosić Tesseract o wykonanie konwersji za Ciebie). ImageMagick może wykonać konwersję za Ciebie. To moje anegdotyczne wrażenie, ale nie przetestowałem go dokładnie, więc może się mylić.
DW,
+1 Filtr „nieostra maska” naprawdę zrobił mój dzień. Kolejny krok, który mi pomógł: za pomocą narzędzia „rozmytego wyboru” wybierz tło, a następnie naciśnij Del, aby je rozjaśnić
Davide
Utknąłem na tym problemie z przetwarzaniem obrazu przed rozpoznaniem tesseract stackoverflow.com/questions/32473095/… Czy możesz mi tutaj pomóc?
Hussain
nie. Próbowałem zwiększyć rozmiar i ustawić go w skali szarości, wydaje mi się, że nic nie daje mi pozytywnego wyniku. Westchnienie :( Sprawdź ten cel: freesms4us.com/…
gumuruh
30

Trzy punkty poprawiające czytelność obrazu: 1) Zmień rozmiar obrazu ze zmienną wysokością i szerokością (pomnóż 0,5 i 1 i 2 z wysokością i szerokością obrazu). 2) Przekonwertuj obraz na format skali szarości (czarno-biały). 3) Usuń piksele szumu i wyraźniej (filtruj obraz).

Zobacz poniższy kod:

//Resize
  public Bitmap Resize(Bitmap bmp, int newWidth, int newHeight)
        {

                Bitmap temp = (Bitmap)bmp;

                Bitmap bmap = new Bitmap(newWidth, newHeight, temp.PixelFormat);

                double nWidthFactor = (double)temp.Width / (double)newWidth;
                double nHeightFactor = (double)temp.Height / (double)newHeight;

                double fx, fy, nx, ny;
                int cx, cy, fr_x, fr_y;
                Color color1 = new Color();
                Color color2 = new Color();
                Color color3 = new Color();
                Color color4 = new Color();
                byte nRed, nGreen, nBlue;

                byte bp1, bp2;

                for (int x = 0; x < bmap.Width; ++x)
                {
                    for (int y = 0; y < bmap.Height; ++y)
                    {

                        fr_x = (int)Math.Floor(x * nWidthFactor);
                        fr_y = (int)Math.Floor(y * nHeightFactor);
                        cx = fr_x + 1;
                        if (cx >= temp.Width) cx = fr_x;
                        cy = fr_y + 1;
                        if (cy >= temp.Height) cy = fr_y;
                        fx = x * nWidthFactor - fr_x;
                        fy = y * nHeightFactor - fr_y;
                        nx = 1.0 - fx;
                        ny = 1.0 - fy;

                        color1 = temp.GetPixel(fr_x, fr_y);
                        color2 = temp.GetPixel(cx, fr_y);
                        color3 = temp.GetPixel(fr_x, cy);
                        color4 = temp.GetPixel(cx, cy);

                        // Blue
                        bp1 = (byte)(nx * color1.B + fx * color2.B);

                        bp2 = (byte)(nx * color3.B + fx * color4.B);

                        nBlue = (byte)(ny * (double)(bp1) + fy * (double)(bp2));

                        // Green
                        bp1 = (byte)(nx * color1.G + fx * color2.G);

                        bp2 = (byte)(nx * color3.G + fx * color4.G);

                        nGreen = (byte)(ny * (double)(bp1) + fy * (double)(bp2));

                        // Red
                        bp1 = (byte)(nx * color1.R + fx * color2.R);

                        bp2 = (byte)(nx * color3.R + fx * color4.R);

                        nRed = (byte)(ny * (double)(bp1) + fy * (double)(bp2));

                        bmap.SetPixel(x, y, System.Drawing.Color.FromArgb
                (255, nRed, nGreen, nBlue));
                    }
                }



                bmap = SetGrayscale(bmap);
                bmap = RemoveNoise(bmap);

                return bmap;

        }


//SetGrayscale
  public Bitmap SetGrayscale(Bitmap img)
        {

            Bitmap temp = (Bitmap)img;
            Bitmap bmap = (Bitmap)temp.Clone();
            Color c;
            for (int i = 0; i < bmap.Width; i++)
            {
                for (int j = 0; j < bmap.Height; j++)
                {
                    c = bmap.GetPixel(i, j);
                    byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);

                    bmap.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
                }
            }
            return (Bitmap)bmap.Clone();

        }
//RemoveNoise
   public Bitmap RemoveNoise(Bitmap bmap)
        {

            for (var x = 0; x < bmap.Width; x++)
            {
                for (var y = 0; y < bmap.Height; y++)
                {
                    var pixel = bmap.GetPixel(x, y);
                    if (pixel.R < 162 && pixel.G < 162 && pixel.B < 162)
                        bmap.SetPixel(x, y, Color.Black);
                    else if (pixel.R > 162 && pixel.G > 162 && pixel.B > 162)
                        bmap.SetPixel(x, y, Color.White);
                }
            }

            return bmap;
        }

OBRAZ WEJŚCIOWY
OBRAZ WEJŚCIOWY

OBRAZ WYJŚCIOWY OBRAZ WYJŚCIOWY

Sathyaraj Palanisamy
źródło
Tak, musimy przekazać wymagany parametr do metody Resize, która wstępnie przetworzy operację resize, SetGrayscale i RemoveNoise, a następnie zwróci obraz wyjściowy z lepszą czytelnością.
Sathyaraj Palanisamy
Wypróbowałem to podejście na zestawie plików i porównano z początkowym wynikiem. W niektórych nielicznych przypadkach daje to lepsze rezultaty, najczęściej występował niewielki spadek jakości tekstu wyjściowego. Nie wygląda więc na uniwersalne rozwiązanie.
Bryn
To mi się naprawdę udało. Z pewnością stanowi punkt wyjścia do wstępnego przetwarzania obrazu, który usuwa bełkot, który dostajesz z Tesseract.
sesja
22

Z reguły stosuję następujące techniki wstępnego przetwarzania obrazu przy użyciu biblioteki OpenCV:

  1. Ponowne skalowanie obrazu (zalecane, jeśli pracujesz z obrazami o rozdzielczości mniejszej niż 300 dpi):

    img = cv2.resize(img, None, fx=1.2, fy=1.2, interpolation=cv2.INTER_CUBIC)
    
  2. Konwersja obrazu do skali szarości:

    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
  3. Stosowanie dylatacji i erozji w celu usunięcia szumu (możesz bawić się rozmiarem jądra w zależności od zestawu danych):

    kernel = np.ones((1, 1), np.uint8)
    img = cv2.dilate(img, kernel, iterations=1)
    img = cv2.erode(img, kernel, iterations=1)
    
  4. Stosowanie rozmycia, które można zrobić, używając jednej z następujących linii (z których każda ma swoje wady i zalety, jednak rozmycie mediany i filtr dwustronny zwykle działają lepiej niż rozmycie gaussowskie):

    cv2.threshold(cv2.GaussianBlur(img, (5, 5), 0), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
    
    cv2.threshold(cv2.bilateralFilter(img, 5, 75, 75), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
    
    cv2.threshold(cv2.medianBlur(img, 3), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
    
    cv2.adaptiveThreshold(cv2.GaussianBlur(img, (5, 5), 0), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 2)
    
    cv2.adaptiveThreshold(cv2.bilateralFilter(img, 9, 75, 75), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 2)
    
    cv2.adaptiveThreshold(cv2.medianBlur(img, 3), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 2)
    

Niedawno napisałem dość prosty przewodnik po Tesseraccie, ale powinien on umożliwić ci napisanie pierwszego skryptu OCR i wyjaśnienie niektórych przeszkód, które napotkałem, gdy sprawy były mniej jasne, niż chciałbym w dokumentacji.

Jeśli chcesz je sprawdzić, tutaj udostępniam Ci linki:

bkaankuguoglu
źródło
dlaczego konwertujemy obraz do skali szarości? Mówiąc dokładniej, widziałem w procesie wykrywania obrazu, najpierw obraz jest konwertowany do skali szarości, a następnie sobel-> MSER -> SWT. czy mógłbyś to rozwinąć? Jestem nowy w dziedzinie IP.
OnePunchMan
W moim rozumieniu zależy to od algorytmu, niektórzy mogą w ogóle nie potrzebować konwersji. Pomyśl o pikselach jako o kilku wartościach kolorów przechowywanych cyfrowo - w przypadku RGB, czerwonego, zielonego i niebieskiego -. Kiedy piksel jest konwertowany do skali czarno-białej, algorytm musi działać tylko na 2 wymiarach, zamiast na 3. Ma to oczywiste zalety w szybkości, gdy algorytm jest uruchamiany na pikselach jeden po drugim. Ponadto niektórzy mogą również powiedzieć, że łatwiej jest usunąć szum i wykryć krawędzie obrazu po przekonwertowaniu go na skalę szarości.
bkaankuguoglu
Dziękuję za odpowiedź. A jeśli chodzi o swojego bloga, czy mógłbyś napisać jeden na temat JAK BUDOWAĆ OCR Z PODKRESU PRZY UŻYCIU TESSERACT dla pisma innego niż rzymski. Szukałem wszędzie, wszystko, co jest dostępne, nie jest jasne.
OnePunchMan
16

To było trochę temu, ale nadal może być przydatne.

Z mojego doświadczenia wynika, że ​​czasami pomaga zmiana rozmiaru obrazu w pamięci przed przekazaniem go do tesseraktu.

Wypróbuj różne tryby interpolacji. Post https://stackoverflow.com/a/4756906/146003 bardzo mi pomógł.

Atokreacje
źródło
15

To, co było dla mnie BARDZO POMOCNE na tej drodze, to kody źródłowe projektu Capture2Text. http://sourceforge.net/projects/capture2text/files/Capture2Text/ .

BTW: Wyrazy uznania dla jego autora za udostępnienie tak żmudnego algorytmu.

Zwróć szczególną uwagę na plik Capture2Text \ SourceCode \ leptonica_util \ leptonica_util.c - na tym polega istota preprocesji obrazu dla tego narzędzia.

Jeśli uruchomisz pliki binarne, możesz sprawdzić transformację obrazu przed / po procesie w folderze Capture2Text \ Output \.

Wspomniane rozwiązanie PS wykorzystuje Tesseract do OCR i Leptonica do preprocesingu.

Mądry człowiek
źródło
1
Dziękujemy za narzędzie Capture2Text. Doskonale rozwiązuje wszystkie problemy z OCR w moim projekcie!
Lê Quang Duy
12

Wersja Java dla kodu Sathyaraja powyżej:

// Resize
public Bitmap resize(Bitmap img, int newWidth, int newHeight) {
    Bitmap bmap = img.copy(img.getConfig(), true);

    double nWidthFactor = (double) img.getWidth() / (double) newWidth;
    double nHeightFactor = (double) img.getHeight() / (double) newHeight;

    double fx, fy, nx, ny;
    int cx, cy, fr_x, fr_y;
    int color1;
    int color2;
    int color3;
    int color4;
    byte nRed, nGreen, nBlue;

    byte bp1, bp2;

    for (int x = 0; x < bmap.getWidth(); ++x) {
        for (int y = 0; y < bmap.getHeight(); ++y) {

            fr_x = (int) Math.floor(x * nWidthFactor);
            fr_y = (int) Math.floor(y * nHeightFactor);
            cx = fr_x + 1;
            if (cx >= img.getWidth())
                cx = fr_x;
            cy = fr_y + 1;
            if (cy >= img.getHeight())
                cy = fr_y;
            fx = x * nWidthFactor - fr_x;
            fy = y * nHeightFactor - fr_y;
            nx = 1.0 - fx;
            ny = 1.0 - fy;

            color1 = img.getPixel(fr_x, fr_y);
            color2 = img.getPixel(cx, fr_y);
            color3 = img.getPixel(fr_x, cy);
            color4 = img.getPixel(cx, cy);

            // Blue
            bp1 = (byte) (nx * Color.blue(color1) + fx * Color.blue(color2));
            bp2 = (byte) (nx * Color.blue(color3) + fx * Color.blue(color4));
            nBlue = (byte) (ny * (double) (bp1) + fy * (double) (bp2));

            // Green
            bp1 = (byte) (nx * Color.green(color1) + fx * Color.green(color2));
            bp2 = (byte) (nx * Color.green(color3) + fx * Color.green(color4));
            nGreen = (byte) (ny * (double) (bp1) + fy * (double) (bp2));

            // Red
            bp1 = (byte) (nx * Color.red(color1) + fx * Color.red(color2));
            bp2 = (byte) (nx * Color.red(color3) + fx * Color.red(color4));
            nRed = (byte) (ny * (double) (bp1) + fy * (double) (bp2));

            bmap.setPixel(x, y, Color.argb(255, nRed, nGreen, nBlue));
        }
    }

    bmap = setGrayscale(bmap);
    bmap = removeNoise(bmap);

    return bmap;
}

// SetGrayscale
private Bitmap setGrayscale(Bitmap img) {
    Bitmap bmap = img.copy(img.getConfig(), true);
    int c;
    for (int i = 0; i < bmap.getWidth(); i++) {
        for (int j = 0; j < bmap.getHeight(); j++) {
            c = bmap.getPixel(i, j);
            byte gray = (byte) (.299 * Color.red(c) + .587 * Color.green(c)
                    + .114 * Color.blue(c));

            bmap.setPixel(i, j, Color.argb(255, gray, gray, gray));
        }
    }
    return bmap;
}

// RemoveNoise
private Bitmap removeNoise(Bitmap bmap) {
    for (int x = 0; x < bmap.getWidth(); x++) {
        for (int y = 0; y < bmap.getHeight(); y++) {
            int pixel = bmap.getPixel(x, y);
            if (Color.red(pixel) < 162 && Color.green(pixel) < 162 && Color.blue(pixel) < 162) {
                bmap.setPixel(x, y, Color.BLACK);
            }
        }
    }
    for (int x = 0; x < bmap.getWidth(); x++) {
        for (int y = 0; y < bmap.getHeight(); y++) {
            int pixel = bmap.getPixel(x, y);
            if (Color.red(pixel) > 162 && Color.green(pixel) > 162 && Color.blue(pixel) > 162) {
                bmap.setPixel(x, y, Color.WHITE);
            }
        }
    }
    return bmap;
}
Fábio Silva
źródło
Jaka jest Twoja klasa dla Bitmap? Bitmapa nie występuje w Javie (jest natywnie w Androidzie).
Jesteśmy Borg
Ta metoda przechodzi przez wyjątek: Spowodowane przez: java.lang.IllegalArgumentException: y musi być <bitmap.height ()
Nativ
9

Dokumentacja Tesseract zawiera kilka dobrych szczegółów na temat poprawiania jakości OCR poprzez etapy przetwarzania obrazu.

Do pewnego stopnia Tesseract automatycznie je stosuje. Można również powiedzieć Tesseractowi, aby zapisał obraz pośredni do wglądu, tj. Sprawdzenia, jak dobrze działa wewnętrzne przetwarzanie obrazu (szukaj tessedit_write_imagesw powyższym odnośniku).

Co ważniejsze, nowy system sieci neuronowych w Tesseract 4 zapewnia znacznie lepsze wyniki OCR - ogólnie, a zwłaszcza w przypadku obrazów z pewnym szumem. Jest to możliwe --oem 1np. Jak w:

$ tesseract --oem 1 -l deu page.png result pdf

(ten przykład wybiera język niemiecki)

Dlatego warto najpierw przetestować, jak daleko można się posunąć dzięki nowemu trybowi Tesseract LSTM, zanim zastosuje się kilka niestandardowych kroków wstępnego przetwarzania obrazu.

maxschlepzig
źródło
6

Adaptacyjne progowanie jest ważne, jeśli oświetlenie jest nierówne na całym obrazie. Moje wstępne przetwarzanie przy użyciu GraphicsMagic jest wspomniane w tym poście: https://groups.google.com/forum/#!topic/tesseract-ocr/jONGSChLRv4

GraphicsMagic ma również płaską funkcję dla progu adaptacyjnego czasu liniowego, którą spróbuję wkrótce.

Inną metodę progowania przy użyciu OpenCV opisano tutaj: http://docs.opencv.org/trunk/doc/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html

rleir
źródło
2
Link do OpenCV został zmieniony. W dokumentacji OpenCV jest to OpenCV-Python Tutorials> Image Processing in OpenCV> Image Thresholding
richk
2

Zrobiłem to, aby uzyskać dobre wyniki z obrazu, który ma niezbyt mały tekst.

  1. Zastosuj rozmycie do oryginalnego obrazu.
  2. Zastosuj adaptacyjny próg.
  3. Zastosuj efekt wyostrzania.

A jeśli nadal nie daje dobrych wyników, przeskaluj obraz do 150% lub 200%.

Hamza Iqbal
źródło
2

Czytanie tekstu z dokumentów graficznych przy użyciu dowolnego silnika OCR wiąże się z wieloma problemami w celu uzyskania dobrej dokładności. Nie ma stałego rozwiązania dla wszystkich przypadków, ale oto kilka rzeczy, które należy wziąć pod uwagę, aby poprawić wyniki OCR.

1) Obecność szumu z powodu złej jakości obrazu / niepożądanych elementów / plam w obszarze tła. Wymaga to pewnych operacji przetwarzania wstępnego, takich jak usuwanie szumów, które można łatwo wykonać za pomocą filtru Gaussa lub normalnych metod filtru medianowego. Są one również dostępne w OpenCV.

2) Niewłaściwa orientacja obrazu: z powodu złej orientacji silnik OCR nie segmentuje poprawnie linii i słów na obrazie, co daje najmniejszą dokładność.

3) Obecność wierszy: Podczas wykonywania segmentacji słów lub wierszy silnik OCR czasami próbuje również łączyć słowa i wiersze ze sobą, przetwarzając w ten sposób niewłaściwą treść i dając błędne wyniki. Są też inne kwestie, ale te podstawowe.

Ta aplikacja post OCR jest przykładem przypadku, w którym można zastosować wstępne przetwarzanie obrazu i przetwarzanie końcowe w wyniku OCR, aby uzyskać lepszą dokładność OCR.

flamelit
źródło
1

Rozpoznawanie tekstu zależy od wielu czynników, aby uzyskać dobrą jakość wydruku. Wyjście OCR w dużym stopniu zależy od jakości obrazu wejściowego. Dlatego każdy silnik OCR zawiera wytyczne dotyczące jakości obrazu wejściowego i jego rozmiaru. Te wskazówki pomagają silnikowi OCR w uzyskiwaniu dokładnych wyników.

Napisałem szczegółowy artykuł na temat przetwarzania obrazu w Pythonie. Prosimy kliknąć poniższy link, aby uzyskać więcej wyjaśnień. Dodano również kod źródłowy Pythona, aby zaimplementować ten proces.

Napisz komentarz, jeśli masz sugestię lub lepszy pomysł na ten temat, aby go ulepszyć.

https://medium.com/cashify-engineering/improve-accuracy-of-ocr-using-image-preprocessing-8df29ec3a033

Brijesh Gupta
źródło
2
Dodaj odpowiedź tutaj jako podsumowanie swojego bloga. Więc nawet jeśli łącze jest martwe, odpowiedź nie będzie bezużyteczna.
Nithin
0

możesz zrobić redukcję szumów, a następnie zastosować progowanie, ale możesz bawić się konfiguracją OCR, zmieniając wartości --psm i --oem

spróbuj: --psm 5 --oem 2

Możesz również spojrzeć na poniższy link, aby uzyskać więcej informacji tutaj

sameer maurya
źródło