Program typu OCR w Javie

Liczba danych przetwarzanych obecnie przez maszyny jest ogromna. Szacuję się ze każdego dnia jest tworzone 2,5 kwintyliona bitów danych w tym m.in filmów, muzyki, książek jak i dokumentów. I choć wiele z tych danych powstaje bezpośrednio na komputerze nadal nie rzadko musimy pozyskać informacje z nośników analogowych. Nawet zaś wtedy edycja tych danych nie zawsze jest łatwa jak w przypadku wszelkich zeskanowanych dokumentów. W takim przypadku jednak mogą nam pomóc programy typu OCR. Przy odrobinie zaś wysiłku możemy samemu taki program stworzyć.

 
Niestety w Javie właściwie nie istnieje darmowa i dobra bibliotek do rozpoznawania tekstu. Jednak to nie powód by rezygnować z naszego przedsięwzięcia. Istnieją bowiem ciekawe rozwiązania w innych językach. Jednym z najlepszych może być biblioteka Tesseract napisana w C++.
 

Tesseract jest darmową biblioteką do rozpoznawania tekstu nad którą prace rozpoczęto już w 1985 roku i prace nad nim trwają do dziś. Od 2005 zaś jest udostępniany na licencji open source i jest możliwy do pobrania z githuba pod tym adresem https://github.com/tesseract-ocr. Obecnie domyślna wersja Tesseracta jest wytrenowana w oparciu o ponad 400000 lin tekstu i rozpoznaje około 4500 czcionek dając dużą skuteczność przy rozpoznawaniu języków łacińskich. Dodatkowo jeśli będziemy pracować z nietypowym tekstem jesteśmy w stanie go samemu przeszkolić. Pomimo zaś tego że jest to biblioteka napisana w C++ istnieje darmowy wrapaer Javowy korzystający z JNI. Wraper ten wykorzystamy w naszym programie (http://tess4j.sourceforge.net).
 

Żeby stworzyć prosty program typu OCR ściągamy bibliotek tesseract i instalujemy https://github.com/UB-Mannheim/tesseract/wiki (Windows). Następnie tworzymy nowy projekt i dodajemy te dwie zależności do mavena:
 

<dependency>
    <groupId>net.sourceforge.tess4j</groupId>
    <artifactId>tess4j</artifactId>
    <version>3.3.1</version>
</dependency>


<dependency>
    <groupId>org.im4java</groupId>
    <artifactId>im4java</artifactId>
    <version>1.4.0</version>
</dependency>  

Kod źródłowy wygląda następująco:

public class App 
{
    public static void main( String[] args ) throws IOException, InterruptedException, IM4JavaException
    {

        File imageFile = new File("C:\\Users\\Sariel\\Desktop\\moby.png");
        ITesseract instance = new Tesseract();  // JNA Interface Mapping
 	instance.setDatapath("C:\\Program Files (x86)\\Tesseract-OCR\\tessdata");      
  
         ConvertCmd cmd = new ConvertCmd();
         cmd.run(op);
         
         try {
             String result = instance.doOCR(imageFile);
             System.out.println(result);
         } catch (TesseractException e) {
             System.err.println(e.getMessage());
             System.out.println("reading failed");
         }
    }
    
}

Kod jest stosunkowo prosty otwieramy interesujący nas obraz. Tworzymy nową instancję tesseracta. Wskazujemy miejsce gdzie zainstalowaliśmy bibliotekę po czym dokonujemy rozpoznawania tekstu poprzez metode doOCR().
 
Dla celów testowych korzystałem z niniejszego fragmentu książki „Moby Dick”
moby dick

Powinniśmy otrzymać taki wynik:
 

men swung in the howlines; still wordless Ahab stood up to the blast.
Even when Wearied nature seemed demanding repose he would not
seek that repose in his hammock. Never could Starbuck forget the
old man's aspect, when one night going down into the cabin to mark
how the barometer stood, he saw him with closed eyes sitting straight
in his floor‘screwed chair; the rain and hnlflmelted sleet of the storm
from which he had some time before emerged, still slowly dripping
from the unremoved hat and coat. On the table beside him lay un'
rolled one of those charts of tides and currents which have previously
been spoken of. His lantern swung from his tightly clenched hand
Though the body was erect, the head was thrown back so that the
closed eyes were pointed towards the needle of the tell'tale that
swung from a beam in the ceiling.’

Terrible old man! thought Starbuckwith a. shudder, sleepingin this
gale, still thou steadfastly eyest thy purposes
CHAPTER 52 THE ALBATROSS

OUTH'EASTWARD from the Cape, ed" the distant Crozetts,

a goodcruising ground for Right Whalemen,asail loomedahead,

S the Goney (Albatross) by name. As she slowly drew nigh, from

my lofty perch at the fore'mast—head, I had a good view of that sight

so remarkable to a tyre in the far ocean fisheries—awhaler at sea, and
long absent from home.

As if the waves had been fullers, this craft was bleached like the
skeleton of a stranded walrus. All down her sides, this spectral ap»
pearance was traced with long channels of reddened rust, While all
her spars and her rigging were like the thick branches of trees furred
over with hoar'frost. Only her lower sails were set. A wild sight it
was to see her longfbearded lookouts at those three mast—heads. They
seemed clad in the skins of beasts,so torn and hepatched the raiment
that had survived nearly four years of cruising Standing in iron

‘The cabin—compass is called the tell'ule, because without going to the campus at the
helm,the Captain, while below, can inform himselfof the course ofthe ship.' 

Jeśli chcemy możemy także skorzystać z linii komend umieszczamy w takim wypadku nasz dokument w folderze tesseracta, przechodzimy do tego folderu i korzystamy z niniejszej komendy:
 

tesseract moby.png resultText 

gdzie moby.png to adres sczytywanego pliku a resultText to wynik w formacie txt
 
W zasadzie to już wszystko co potrzebujemy aby mieć nasze proste oprogramowanie typu OCR. Niemniej wielokrotnie może się zdarzy ze nasze skany czy obrazy źródłowe nie będą tak łatwe do rozpoznania przez bibliotekę tesseract zmniejszając jego skuteczność. Jest jednak wiele rzeczy które możemy uczynić aby poprawić jego skuteczność.
 
Żeby zwiększyć skuteczność rozpoznawania tekstu przez bibliotekę teseract możemy
 
– dokonać binaryzacji obrazu
– zwiększyć rozdzielczość obrazu, wielkość tekstu
– wyrównać/obrócić obraz
– pozbyć się obramowania
– pozbyć się szumu
 
Żeby dokonać zmian w dokumencie możemy skorzystać z dowolnej biblioteki graficznej czy też nawet photoshopa lub gimpa choć to wymagało by zapewne ręcznego edytowania każdego skanu. Osobiście polecam image magick i na jego podstawie pokaże jak można dokonać drobnych poprawek obrazu.
 
Image magick jest dostępny do ściągnięcia pod adresem:
https://www.imagemagick.org/script/download.php
 
Instalujemy oprogramowanie wraz z wszystkimi dodatkami jak install legacy utilities, install development headers and liberies for c and c++ itd. Dodajemy image magick do zmiennych środowiskowych:
 

Nazwa zmiennej: MAGICK_HOME
Wartość zmiennej: C:\Program Files\ImageMagick-7.0.5-Q16

w linii komend poleceniem
 

convert -version 

możemy sprawdzić czy instalacja się udała, powinniśmy otrzymać informacje o wersji image magick.

 
Mając image magick zainstalowany możemy przygotować nasz obraz pod rozpoznawanie tekstu:
 
1) Binaryzacji obrazu czyli przekształcenie obrazu na czerń i biel. Możemy to uzyskać poprzez prostą komendę:
 

convert book.png -monochrome book2.png

book.png – plik źródłowy
-monochrome – komenda przekształcająca obraz na czerń oraz biel
book2.png – obraz wynikowy


alice color


alice black and white

Najlepszym wyjściem jest gdy otrzymamy czarny czytelny tekst na białym tle. Jednak nierzadko uzyskany obraz wymaga dalszej obróbki.
 
2) Zalecane jest aby obrazy w tesseracu miały przynajmniej rozdzielczość 300 dpi dlatego możemy skorzystać z następującej komendy:
 

convert -units PixelsPerInch dpi.png -density 300 dpiresult.png

Warto także pamiętać iż jeśli wielkość naszej czcionki jest niższa niż 10pt efektywność rozpoznawania obrazu poprzez tesseract może znacznie spaść.
 
3) Wyrównanie/obrócenie obrazu, wielokrotnie zeskanowany przez nasz obraz może być odrobinę odwrócony co utrudnia rozpoznanie poszczególnych liter. Obrócenie obrazu możemy uzyskać poprzez poniższa komendę:
 

convert rotate.png -rotate -3 rotate2.png

4)Pozbycie się obramowania. Częstokrotnie podczas skanowania możemy posiadać czarną obwódkę wokół obrazu która może utrudniać czytanie tekstu. Możemy się jej pozbyć na kilka sposobów:
 
Korzystając z metody trim która służy właśnie do usuwania czarnego obramowania
 

convert black_border.png -trim tes_out.png

Niestety częstokrotnie poziom czerni może być nie równy na całym arkuszu, domyślnie zaś program wyszukuje jedynie dokładnie jeden wybrany kolor z tego powodu możemy skorzystać z metody -fuzz która pozwala nam znaleźć pobliski kolor.
 

convert black_border2.png -fuzz 40% -trim tes_out.png

Jeśli obwódka jest stałego rozmiaru możemy ją po prostu przyciąc korzystając z metody crop lub shave
 

convert black_border2.png -crop 300x500+15+12 tes_out.png
convert black_border2.png -crop 300x500-15-12 tes_out.png

convert black_border2.png -shave 15x15 tes_out.png

Nierzadko obwódka znajduję się tylko po jednej stronie, jest nie jednolita i nie uda nam się jej usunąć metodą trim w takim wypadku możemy skorzystać z niniejszej komendy:
 

convert myborder.png -bordercolor black -border 1 -fuzz 95% -fill white -draw "color 
0,0 floodfill" noborder.png

5) Ostatnia rzecz jaka pozostała to pozbywanie się szumu z obrazu. Niestety jest to najcięższy element i zależy w dużym stopniu od rodzaju szumu jaki mamy na obrazie. W celu jego nieusunięcia możemy wykorzystać np. rozmycie, podjaśnić obraz, dodać kontrast, czy skorzystać z innych filtrów. Przykładowo jeśli posiadamy szum w postaci białych i czarnych kropek dobrym wyjsciem może być skorzystanie z filtru medianowego:
 

convert ship.png -median 5 ship2.png

Inne przydatne komendy:
 

convert image.png -adaptive-blur 5 image2.png // dodanie rozmycia do obrazu
convert image.png -negate image2.png // zamiana/negacja kolorów
convert image.png -noise image2.png // dodanie szumu
convert image.png -white-threshold 20000 image2.png // zamienia wszystkie piksele powyżej podanej wartości na białe, reszta pikseli pozostaje bez zmian

Lista wszystkich komend wraz z ich opisami znajduję się pod adresem https://www.imagemagick.org/script/command-line-options.php

 
Te same funkcje możemy wykorzystać w naszym javovym programie musimy jedynie dodać kolejny wraper poprzez mavena tym razem dla Image Magick:
 

<dependency>
    <groupId>org.im4java</groupId>
    <artifactId>im4java</artifactId>
    <version>1.4.0</version>
</dependency>

Przykładowy kod zaś mógłby wyglądać następująco, wszystkie metody działają tak samo jak zostało to opisane powyżej przy wykorzystaniu linii komend:
 

        

ProcessStarter.setGlobalSearchPath("C:\\Program Files\\ImageMagick-7.0.5-Q16");
ConvertCmd cmd = new ConvertCmd();

IMOperation op = new IMOperation();
op.addImage("C:\\Users\\Sariel\\Desktop\\image.png");
op.density(300);
op.whiteThreshold(90d*256);
op.monochrome();
op.rotate(-1d);
op.trim().fuzz(40d);
op.blur(6d);
op.addImage("C:\\Users\\Sariel\\Desktop\\testResult");
cmd.run(op);

 
Istnieje także dobry gotowy skrypt pod image magick do przygotowywania obrazów dla OCR o nazwie textcleaner, działający jednak jedynie pod Linuxem bądź Windowsem z zainstalowanym cygwinem:
http://www.fmwconcepts.com/imagemagick/textcleaner/
 
Dzięki tylko jednej komendy możemy zrobić wszystko to co zostało opisane powyżej jak i więcej dając nam bardzo dobre rezultaty.

 
Nic nie stoi też na przeszkodzie byśmy samemu zaimplementowali niektóre rozwiązania. Samemu napisałem prostą funkcje która szczytuję wszystkie piksele z obrazu i zamienia je według naszych potrzeb:
 

public static void readPixels(String in, String out) throws IOException {

		File img = new File(in);
		BufferedImage image = ImageIO.read(img);

		int w = image.getWidth();
		int h = image.getHeight();

		int[] dataBuffInt = image.getRGB(0, 0, w, h, null, 0, w);
		int[] pixels = new int[dataBuffInt.length];

		for (int a = 0; a < dataBuffInt.length; a++) {

			Color c = new Color(dataBuffInt[a]);

			if (c.getRed() > 0 && c.getRed() <= 50 && c.getGreen() > 0 && c.getGreen() <= 50 && c.getBlue() > 0
					&& c.getBlue() <= 100) {
				pixels[a] = -1;
			} else {
				pixels[a] = 65536 * 1 + 256 * 1 + 1;
			}
		}

		BufferedImage img2 = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		img2.getRaster().setDataElements(0, 0, w, h, pixels);

		File outputfile = new File(out);
		ImageIO.write(img2, "png", outputfile);
	}

 
Na koniec jako ciekawostka jest alternatywne wykorzystanie oprogramowania Tesseract oraz Image Magick w celu łamania prostych captch:

Obraz przed obróbką
Obraz po obróbce
Użyte funkcje

 

captcha1
captcha1
captcha2

captcha2 rendered

op.density(300);
op.crop(278,70,31,67);
op.monochrome();
op.median(3.5d);
captcha3
captcha3 rendered
op.density(300);
op.noise(2d);
op.whiteThreshold(100d*256);
op.adaptiveBlur(1d);
captcha4
captcha4 rendered
op.density(300);
op.median(2d);
op.negate();
op.whiteThreshold(90d*256);
op.monochrome();
op.adaptiveBlur(6d);
op.noise(4d);

Wykorzystując nasz program bez problemu możemy rozszyfrować pierwszą captche nie dokonując jakichkolwiek modyfikacji graficznych. Kolejne trzy wymagają już pewnych prac graficznych lecz po tym tesseract i z nimi sobie dobrze radzi. Zautomatyzowanie tego procesu zaś powoduję iż takie captche nie spełnia swojego zadania. Dlatego gdy tworzymy serwis internetowy korzystajmy z lepszych generatorów captchy. Niestety mimo nawet najlepszych poprawek graficznych czasami nie będziemy w stanie odczytać zeskanowane dokumentu lub też będą w nim błędy i konieczne będzie zrobienie lub pozyskanie lepszego skanu.
 

Poniżej można ściągnąć kod źródłowy gotowego programu stworzonego w Eclipse.
Kod zródłowy

Dodaj komentarz

WordPress Video Lightbox Plugin