OCR Libary mit Textposition

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Tomek
User
Beiträge: 12
Registriert: Mittwoch 27. Februar 2008, 13:49

Ich programmiere zur Zeit in PyQt4 und möchte ein Bild analysieren. Ich möchte wissen wo sich der Text im Bild befindet, mir ist egal was dort steht (also ich brauch kein vollwertiges OCR). Ich möchte "nur" Pixelkoordinaten haben des 4-Ecks dass den text umschließt.

Ich habe mittlerweile vergebens versucht ocropus zu installieren. Ich arbeite unter Win7 + Eclipse.

Hat mir jemand vielleicht einen Tipp?
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Text (womöglich noch mit Perspektive) in einem beliebigen Bild zu finden, ist aktueller Stand der Forschung. Die primitiven OCR-Engines die man so frei findet, werden dir für diesen allgemeinen Fall nicht helfen. Wie sehen den deine Bilder aus? Vielleicht ist es ja gar nicht so schwer... Beschreib bitte den Problem genauer.

MFG HerrHagen
Tomek
User
Beiträge: 12
Registriert: Mittwoch 27. Februar 2008, 13:49

erstmal danke für die Antwort!

Ich arbeite ausschließlich mit Screenshots die eben Text und Symbole beinhalten, als .jpg oder als .bmp
Mein Ziel währe es eine vollautomatische Bildanalyse dieser Screenshots mit dem Ergebnis dass ein Overlay über den Screenshot erscheint mit z.B. rubberbands die den markieren. Der User kann dann selber nochmal nachkorrigieren.

Ob, oder in wie weit dies überhaupt möglich ist, will ich eben recherchieren. Ein kommerzielle Lizenz kommt leider nicht in Frage.
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Hallo,

ich hab mich nochmal dem Problem gewidmet und es folgendes dabei herausgekommen (Benötigt PIL, scipy, Matplotlib):

Code: Alles auswählen

import Image
import numpy
import scipy.ndimage as ndi
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle
import pylab

# Bild laden
im0 = Image.open("test.bmp")
im0 = numpy.asarray(im0)[:,:,0]  # nur einen Kanal verwenden

# Linien finden
median = ndi.median_filter(im0, size=3).astype(float)
im = abs(im0 - median)

# Binarisierung
im = im > 100

# Connectect Component Labeling
im = ndi.binary_dilation(im, structure=[[1,1,1,1,1]])
im = ndi.label(im)[0]
segments = ndi.find_objects(im)

patches = []
for s1, s2 in segments:
    y1, x1 = s1.start, s2.start
    y2, x2 = s1.stop, s2.stop

    height = y2 - y1
    width = x2 - x1

    # Segmente nach div. Kriterien filtern
    if (2*height) > width: continue
    if height * width < 50: continue
    if height > 16: continue
    if height * width > 20*1000: continue

    patches.append(Rectangle([x1-.5, y1-.5], width, height))

# Plotten
ax = plt.axes()
pylab.imshow(im0)
colors = 200*numpy.random.rand(len(patches))
collection = PatchCollection(patches, cmap=matplotlib.cm.jet, alpha=0.4)
collection.set_array(numpy.array(colors))
ax.add_collection(collection)
plt.show()
Bild

Es funktioniert bei weitem nicht perfekt, aber ich denke es ist gut genug für einen Einstig. Um es zu verfeinern müsste man die Kriterien verbessern, nach zusätzlichen Kriterien filtern (nicht nur Form der Bounding Box) und vor allem die Binarisierung verbessern.

MFG HerrHagen
Tomek
User
Beiträge: 12
Registriert: Mittwoch 27. Februar 2008, 13:49

wow! vielen dank für die Mühe! Ich hab es bisher nur kurz überflogen, werde mich damit morgen gleich intensiv beschäftigen aber es sieht SEHR vielversprechend aus und würde mich SEHR viel helfen. DANKE!
Tomek
User
Beiträge: 12
Registriert: Mittwoch 27. Februar 2008, 13:49

danke nochmal für die Mühe.

Ich habe mit dies etwas genauer angeschaut und bin eigentlich sehr positiv überrascht vom Ergebnis. Jedoch habe ich einige Verständnisschwierigkeiten. Meine Textpositionen kann ich daraus "gut" ermitteln jedoch verstehe ich die Logik dahinter nicht ganz.

Was genau passiert in binary_dilation() und find_objects() mit den Bilddaten? Wieso nur 1 Kanal? Was genau ist in der Variable im enthalten?

Alles sehr interessant für mich als "Grafik-Neuling" was man mit so wenig code hinbekommen kann...


grüße!
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Alles sehr interessant für mich als "Grafik-Neuling" was man mit so wenig code hinbekommen kann...
Bildverarbeitung heißt das Stichwort, nicht Grafik...

Ich machs mal etwas detailierter. Vielleicht interessiert es ja den ein oder anderen.

Fangen wir mal von vorn an.
Bild

Wir brauchen einen Kanal, d.h. einfach nur ein Graustufen-Bild weil die nachfolgenden Schritte es so brauchen. Ich habe einfach den roten Kanal genommen. Das ist natürlich schlecht weil so z.B. keine rote Schrift auf weißem Grund oder blaue Schrift auf schwarzem Grund gefunden werden kann. Besser wäre der Mittelwert über alle Kanäle gewesen. Oder etwas richtig ausgefuchstes... Den Aufwand hab ich mir erspart.

Nun kommt der wichtigste Schritt der ganzen Verarbeitung. Die grundsätzlich Frage ist ja: wie kann ich Text erkennen? Man könnte versuchen die Schriftzeichen direkt auswändig zu lernen und diese dann im Bild suchen. Das wird nicht gut funktionieren (jedenfalls nicht ohne großen Aufwand), da man sehr viele Zeichen für sehr viele Schriftarten lernen muss. Außerdem werden von PC zu PC unterschiedliche Kantenglättungen (insb. verschiedene Stufen Win7 Cleartype) das Ergebnis stören. Also braucht man was generisches. Dazu muss man sich fragen was die Regionen mit Schrift von dem Rest des Bildes? Ein entscheidendes Kriterium ist das Schrift (egal welche Sprache) aus Linien besteht. Der Ansatz ist also: Finde Regionen im Bild wo besonders viele Linien sind. Da ist vmtl. Text.
Linien finden geschieht im nächsten Schritt. Linien direkt im Bild zu finden ist sehr schwer. Deswegen benutze ich folgenden Trick: Ich versuche alle Linien verschwinden zu lassen und bilde das Differenzbild zum Original. Verschwinden lassen funktioniert mit dem Medianfilter.

Median:
Bild

Differenzbild:
Bild

Man sieht deutlich das die Regionen mit vielen Linien im Bild hervorgehoben sind.
Nun müssen die Regionen nur noch gefunden werden. Zuerst wird ein Schwellenwert t gesetzt. D.h. alle Pixel im Bild größer t werden 1 der Rest 0. Dadurch werden sehr kontrastarme Linien heraus gefiltert und es ist einfacher Regionen voneinander abzugrenzen.

Binarisierung:
Bild

Nun müssen die Regionen im Bild, die einzeln stehen aber zusammenwachsen sollen (Buchstaben), vereint werden. Das geschieht durch die Dilation. Such mal im Netz danach. Im Grunde wachsen die Vordergrund-Regionen im Bild. Allerdings nur horizontal da der Filterkern eine horizontale Linie ist.

Dilation:
Bild

Nun kommt das Labeling. Momentan sind zwar einige Regionen im Bild hervorgehoben, jedoch noch nicht einzeln identifiziert. Das Bild sieht also in etwa so aus:

Code: Alles auswählen

0 0 0 0 0
0 1 0 1 0
0 0 0 1 1
0 0 1 1 0
Wir brauchen aber so was:

Code: Alles auswählen

0 0 0 0 0
0 1 0 2 0
0 0 0 2 2
0 0 2 2 0
D.h. zusammenhängende Regionen im Bild sollen eine eindeutige ID bekommen damit man sie einzeln ansprechen kann.

Das Verfahren nennt sich Connected Component Labeling (eine Farbe = ein Segment):
Bild

Mit der Funktion find_objects werden die Regionen im Bild dann als slices zurückgegeben. Diese Segmente filtere ich dann nochmals und schmeiß die Regionen raus die garantiert keinen Text enthalten. Z.B. riesige Regionen oder Regionen die eine Höhe kleiner 16 haben (Text auf Screenshots ist meist größer).
Der Rest ist nur noch einzeichnen der Regionen ins Bild.

Und so kann man ganz einfach Text in einem Screenshot finden :D

MFG HerrHagen
sabram
User
Beiträge: 28
Registriert: Mittwoch 5. Januar 2011, 13:42

Sehr interessantes Thema, sowas plane ich für meine Bachelorarbeit .
Vielen Dank für deine Mühe =)
hellboy
User
Beiträge: 6
Registriert: Montag 10. Januar 2011, 20:33

es gibt auch

http://en.openocr.org/

und

auf OpenOCR basiert

http://pumanet.codeplex.com/
Tomek
User
Beiträge: 12
Registriert: Mittwoch 27. Februar 2008, 13:49

danke nochmal für die Mühe! Spätestens bei der ausführliche Erklärung hatte ichs verstanden ;)

Jetzt noch eine Frage. Nach weiterer Suche bin ich auf die Hough-Transformation: http://en.wikipedia.org/wiki/Hough_transform gestoßen. Die auch in OpenCV (http://opencv.willowgarage.com/wiki/) zu Verfügung gestellt wird. Nach ersten Tests findet mir diese Funktion auch wunderbar Linien ohne den Weg über den Median-Filter:

Code: Alles auswählen

import sys
from math import sin, cos, sqrt, pi
import cv
import urllib2

# toggle between CV_HOUGH_STANDARD and CV_HOUGH_PROBILISTIC
USE_STANDARD = False
	
if __name__ == "__main__":
    if len(sys.argv) > 1:
       filename = sys.argv[1]
       src = cv.LoadImage(filename, cv.CV_LOAD_IMAGE_GRAYSCALE)
    else:
        url = 'https://code.ros.org/svn/opencv/trunk/opencv/doc/pics/building.jpg'
        filedata = urllib2.urlopen(url).read()
        imagefiledata = cv.CreateMatHeader(1, len(filedata), cv.CV_8UC1)
        cv.SetData(imagefiledata, filedata, len(filedata))
        src = cv.DecodeImageM(imagefiledata, cv.CV_LOAD_IMAGE_GRAYSCALE)
	
	
    cv.NamedWindow("Source", 1)
    cv.NamedWindow("Hough", 1)
	
    while True:
        dst = cv.CreateImage(cv.GetSize(src), 8, 1)
        color_dst = cv.CreateImage(cv.GetSize(src), 8, 3)
        storage = cv.CreateMemStorage(0)
        lines = 0
        cv.Canny(src, dst, 50, 200, 3)
        cv.CvtColor(dst, color_dst, cv.CV_GRAY2BGR)
	
        if USE_STANDARD:
            lines = cv.HoughLines2(dst, storage, cv.CV_HOUGH_STANDARD, 1, pi / 180, 100, 0, 0)
            for (rho, theta) in lines[:100]:
                a = cos(theta)
                b = sin(theta)
                x0 = a * rho
                y0 = b * rho
                pt1 = (cv.Round(x0 + 1000*(-b)), cv.Round(y0 + 1000*(a)))
                pt2 = (cv.Round(x0 - 1000*(-b)), cv.Round(y0 - 1000*(a)))
                cv.Line(color_dst, pt1, pt2, cv.RGB(255, 0, 0), 3, 8)
        else:
            lines = cv.HoughLines2(dst, storage, cv.CV_HOUGH_PROBABILISTIC, 1, pi / 180, 50, 50, 10)
            for line in lines:
                cv.Line(color_dst, line[0], line[1], cv.CV_RGB(255, 0, 0), 1, 8)
	
        cv.ShowImage("Source", src)
        cv.ShowImage("Hough", color_dst)

        k = cv.WaitKey(0)
        if k == ord(' '):
            USE_STANDARD = not USE_STANDARD
        if k == 27:
            break
ist dies nicht "effizienter" bzw. liefert dies nicht ein besseres Ergebniss? Ich werde dies mal im Vergleich zum Median-Algo testen.
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Nein, vmtl. nicht. Die Hough-Transformation die du meinst, ist für Geraden (=unendlich lang, man beachte den Unterschied zu Linie...). D.h. wenn zwei Punkte ergeben schon eine potentielle Gerade. Eine Gerade ist zudem nicht lokal, sondern unendlich lang und geht somit immer durch gesamte Bild. Zum Lokalisieren von Text ist das Bild ist die Hough-Transformation für Geraden also nicht geeignet. OpenCV bietet nun noch eine zusätzliche Funktionalität mit der untersucht werden kann wo auf dieser Gerade eine Linie anfängt bzw. aufhört. Wie gut das funktioniert kann ich nicht beurteilen, da ich nicht weiß wie die Funktionalität implementiert wurde. Ich bezweifle allerdings das alle kurze Linien sauber gefunden werden, ohne sich einen Haufen Rauschen mit ins Bild zu holen.
Außerdem werden wenn dann nur gerade Linien gefunden. Runde Zeichen wie 8, 9, 6, O, C fallen da schon mal raus.
Zum Thema Effizienz. Die Hough Transformationen funktionieren im Grunde alle so: Um eine Form im Bild zu finden (d.h. die Translation & Rotation zu bestimmen wie diese Form am besten ins Bild zu legen ist) werden in einem Konturbild alle Konturpixel durchgegangen und es wird einfach mal so getan als ob dieses Pixel zum zu der gesuchten Form gehören würde, d.h. man markiert im sog. Hough-Bild alle Stellen an denen sich die Form theoretisch befinden könnte. Wenn man fertig ist schaut man welche Stelle die meisten Stimmen bekommen hat. An dieser Stelle befindet sich vmtl. die Form. Um bei Geraden nicht das Bild komplett voll zu zeichnen, zeichnet man in ein Parameterbild (Steigung, Versatz). D.h. für jeden Konturpunkt muss man recht viel (vmtl. >100Punkte) zeichnen und dann noch mal dieses Bild (am besten noch vorher glätten) durchgehen und die Maxima finden. Das ist wesentlich aufwendiger als mein Ansatz. Wenn du meinen Ansatz ebenfalls in OpenCV umsetzt, wird es sicherlich auch nochmal ein großes Stück schneller, da scipy.ndimage teilweise recht lahm ist.
Wenn du einen alternativen Ansatz suchst, dann probiers mal damit (muss natürlich für Screenshots angepasst werden): http://www.comp.nus.edu.sg/~cs4243/proj ... _scene.pdf

MFG HerrHagen
Antworten