Hier werden die Unterschiede zwischen zwei Versionen gezeigt.
Beide Seiten der vorigen Revision Vorhergehende Überarbeitung Nächste Überarbeitung | Vorhergehende Überarbeitung | ||
ss17:erkennung_gedruckter_geschrift_einen_kassenbon_lesen [2017/09/20 17:12] Jan_Philipps [Symbol-Finder] |
ss17:erkennung_gedruckter_geschrift_einen_kassenbon_lesen [2017/10/18 09:13] (aktuell) Jan_Philipps [Symbol-Finder] |
||
---|---|---|---|
Zeile 41: | Zeile 41: | ||
=="Main"-Methode== | =="Main"-Methode== | ||
- | __Name im Code: SymbolScanner.__ Diese Methode wird vom Primärteil des Programms (neuronales Netz usw.) aufgerufen und nutzt verschiedene Hilfsmethoden um möglichst alle Symbole auf dem Kassenbon zu finden und als kleine Bilder zu speichern. | + | __Name im Code: SymbolScanner.__ Diese Methode wird vom Primärteil des Programms (neuronales Netz usw.) aufgerufen. Sie nutzt verschiedene Hilfsmethoden um das Bild Zeile für Zeile zu durchsuchen und möglichst alle Symbole auf dem Kassenbon zu finden und als kleine Bilder zu speichern. |
Die gefundenen Symbole werden in Format //Zeile//_//xKoordinate//_//yKoordinate//.png gespeichert. | Die gefundenen Symbole werden in Format //Zeile//_//xKoordinate//_//yKoordinate//.png gespeichert. | ||
Zeile 53: | Zeile 53: | ||
img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #Konvertiert das Bild zu schwarz-weiss/Graustufen | img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #Konvertiert das Bild zu schwarz-weiss/Graustufen | ||
- | h,b=img.shape #h ist die Hoehe des Bildes, b ist die Breite des Bildes | + | h,b=img.shape #h ist die Hoehe des Bildes, b ist die Breite des Bildes |
threshold=Plotton(img,h,b) #Ruft Plotton auf, um Grenzwert zu erhalten | threshold=Plotton(img,h,b) #Ruft Plotton auf, um Grenzwert zu erhalten | ||
Zeile 65: | Zeile 65: | ||
yMax=0 #Obere Grenze fuer das Programm, damit nichts doppelt gescannt wird. | yMax=0 #Obere Grenze fuer das Programm, damit nichts doppelt gescannt wird. | ||
- | Zeit1=time() #Startet Zeitmessung | + | #Zeit1=time() #Startet Zeitmessung. Auskommentiert weil meist sinnlos, aber manchmal doch ganz nett zu wissen. |
while EndPage==False: #Diese Schleife wird ausgefuehrt, solange die Seite nicht bis zum Ende ueberprueft wurde | while EndPage==False: #Diese Schleife wird ausgefuehrt, solange die Seite nicht bis zum Ende ueberprueft wurde | ||
Zeile 72: | Zeile 72: | ||
zO,zU=ZeilenFindo(h,b,yMax,threshold,img) #Ruft ZeilenFindo auf, um Zeilenober- und Untergrenze zu bestimmen | zO,zU=ZeilenFindo(h,b,yMax,threshold,img) #Ruft ZeilenFindo auf, um Zeilenober- und Untergrenze zu bestimmen | ||
- | yMax=zU #Setzt Obergrenze fuer naechsten Aufruf von ZeilenFindo, damit nur der Bereich unterhalb der vorherigen Zeile durchsucht wird | + | yMax=zU #Setzt Obergrenze fuer naechsten Aufruf von ZeilenFindo, damit nur der Bereich unterhalb der vorherigen Zeile durchsucht wird |
- | CurrentLine=CurrentLine+1 #Merkt sich die aktuelle Zeile | + | CurrentLine=CurrentLine+1 #Merkt sich die aktuelle Zeile |
xS, yS=Skannomaton(zU,b,xMax,zO,threshold,img) #Ruft Skannomatin auf, um x- und y-Koordinate des ersten Symbols zu finden | xS, yS=Skannomaton(zU,b,xMax,zO,threshold,img) #Ruft Skannomatin auf, um x- und y-Koordinate des ersten Symbols zu finden | ||
Zeile 100: | Zeile 100: | ||
try: | try: | ||
- | os.mkdir(pPath) #Erstellt Speicherverzeichniss falls noch nicht vorhanden | + | os.mkdir(pPath) #Erstellt Speicherverzeichnis falls noch nicht vorhanden |
except: pass | except: pass | ||
Zeile 115: | Zeile 115: | ||
EndLine=True | EndLine=True | ||
- | Zeit2=time() #Beendet Zeitmessung. | + | #Zeit2=time() #Beendet Zeitmessung. Auskommentiert weil meist sinnlos, aber manchmal doch ganz nett zu wissen. |
- | print "SymbolScanner-Time=", (Zeit2-Zeit1) #Gibt an, wie lange die ganze Suche gedauert hat | + | #print "SymbolScanner-Time=", (Zeit2-Zeit1) #Gibt an, wie lange die ganze Suche gedauert hat. Auskommentiert weil meist sinnlos, aber manchmal doch ganz nett zu wissen. |
Zeile 130: | Zeile 130: | ||
def Plotton(img,h,b): | def Plotton(img,h,b): | ||
+ | #img=BildMatrix; h=Bildhoehe in Pixeln; b=BildBreite in Pixeln; | ||
pixelCounter=[0]*256 #Erstellt Liste mit 256 Slots, fuer jeden Farbwert einen | pixelCounter=[0]*256 #Erstellt Liste mit 256 Slots, fuer jeden Farbwert einen | ||
Zeile 139: | Zeile 140: | ||
pixelCounter[img[y,x]]=pixelCounter[img[y,x]]+1 | pixelCounter[img[y,x]]=pixelCounter[img[y,x]]+1 | ||
- | for n in range(len(pixelCounter)): #Diese Schelife Teilt jedes Listenelement durch die Anzahl der Pixel, (Absolute zur relativen Haeufigkeit) | + | for n in range(len(pixelCounter)): #Diese Schleife Teilt jedes Listenelement durch die Anzahl der Pixel, (Absolute zur relativen Haeufigkeit) |
pixelCounter[n]=100.*(pixelCounter[n]/nOFpixels) | pixelCounter[n]=100.*(pixelCounter[n]/nOFpixels) | ||
Zeile 165: | Zeile 166: | ||
def ZeilenFindo(h,b,yMax,threshold,img): | def ZeilenFindo(h,b,yMax,threshold,img): | ||
+ | #h=Bildhoehe in Pixeln; b=BildBreite in Pixeln; yMax=OberGrenze fuer Suche; threshold=Grenzwert; img=BildMatrix; | ||
jmp=2 #Legt die Anzahl der Reihen fest, die uebersprungen werden (mehr=schneller=ungenauer) | jmp=2 #Legt die Anzahl der Reihen fest, die uebersprungen werden (mehr=schneller=ungenauer) | ||
Zeile 215: | Zeile 217: | ||
def Skannomaton(h,b,xMax,yMax,threshold,img): | def Skannomaton(h,b,xMax,yMax,threshold,img): | ||
- | + | #h=Untergrenze fuer Suche; b=BildBreite in Pixeln (rechte Grenze fuer Suche); | |
+ | #xMax=LinkeGrenze fuer Suche; yMax=OberGrenze fuer Suche; threshold=Grenzwert; img=BildMatrix; | ||
y=yMax #Obergrenze | y=yMax #Obergrenze | ||
Zeile 226: | Zeile 229: | ||
while (cy>yMax) and (cx<b): #Schleife laeuft bis der Algorithmus an der oberen oder rechten Kante vom Bereich ankommt. | while (cy>yMax) and (cx<b): #Schleife laeuft bis der Algorithmus an der oberen oder rechten Kante vom Bereich ankommt. | ||
- | if img[cy,cx]<threshold: #Wenn der aktuell Pixel dunkel ist, werden die Koordinaten zurueckgegeben | + | if img[cy,cx]<threshold: #Wenn der aktuelle Pixel dunkel ist, werden die Koordinaten zurueckgegeben |
return cx,cy | return cx,cy | ||
cx=cx+1 #Geht eins nach rechts | cx=cx+1 #Geht eins nach rechts | ||
Zeile 238: | Zeile 241: | ||
while (cy>yMax) and (cx<b): #Schleife laeuft bis der Algorithmus an der oberen oder rechten Kante vom Bereich ankommt. | while (cy>yMax) and (cx<b): #Schleife laeuft bis der Algorithmus an der oberen oder rechten Kante vom Bereich ankommt. | ||
- | if img[cy,cx]<threshold: #Wenn der aktuell Pixel dunkel ist, werden die Koordinaten zurueckgegeben | + | if img[cy,cx]<threshold: #Wenn der aktuelle Pixel dunkel ist, werden die Koordinaten zurueckgegeben |
return cx,cy | return cx,cy | ||
cx=cx+1 #Geht eins nach rechts | cx=cx+1 #Geht eins nach rechts | ||
Zeile 249: | Zeile 252: | ||
return 0,0 #Das steht nur hier, weil das Programm sonst nicht mal starten will. Diese Zeile wird nie aufgerufen. | return 0,0 #Das steht nur hier, weil das Programm sonst nicht mal starten will. Diese Zeile wird nie aufgerufen. | ||
+ | |||
+ | </code> | ||
+ | |||
+ | == Koordinaten-Anpasser Oberkante== | ||
+ | __Name im Code: Korrektoid.__ Diese Methode wurde zu dem Zweck geschrieben, den "Halbe Zeichen"-Fehler zu beheben. Sie nimmt den zuvor von //Skannomaton// gefundenen Punkt und bringt ihn auf die Höhe des höchsten Punktes des Buchstaben. Die Funktionsweise ähnelt der von //Findomat_UR// und //Findomat_OL//. Der Helligkeitswert des Pixels über dem Startpunkt wird getestet. Ist der Pixel dunkel, "bewegt" sich Algorithmus nach oben, ist der Pixel hell, wird der Pixel auf der rechten Seit überprüft. Falls dieser Pixel dunkel ist, wird er zu neuen Startpunkt. Dies wird so lange wiederholt, bis beide Pixel hell sind, der Algorithmus also am lokalen Maximum des Zeichens angelangt ist. Der y-Wert des von //Scannomaton// übergeben Punktes wird nun durch den des lokalen Maximums ersetzt. | ||
+ | |||
+ | <code python> | ||
+ | |||
+ | def Korrektoid(xS,yS,threshold,img): | ||
+ | #xS=x-Koordinate Startpunkt; yS=y-Koordinate Startpunkt; treshold=Grenzwert; img=BildMatrix | ||
+ | |||
+ | cx=xS | ||
+ | cy=yS | ||
+ | localMax=False | ||
+ | #Legt Startkoordinaten fest und schaltet Abbruchbedingung aus | ||
+ | |||
+ | while localMax==False and (cy>(yS-20)): | ||
+ | #Solange das Maximum nicht gefunden wurde und das Programm sich nicht mehr | ||
+ | #als 19 von der Starthoehe entfernt hat, wird nach dem Maximum gesucht. | ||
+ | |||
+ | if img[(cy-1),cx]<threshold: | ||
+ | cy=cy-1 | ||
+ | #Wenn der Pixel ueber dem aktuellen dunkel ist, wird dieser der neue Ausgagngspixel | ||
+ | |||
+ | else: | ||
+ | if img[cy,(cx+1)]<threshold: | ||
+ | cx=cx+1 | ||
+ | #Wenn der Pixel rechts von dem aktuellen dunkel ist, wird dieser der neue Ausgagngspixel | ||
+ | |||
+ | else: | ||
+ | localMax=True | ||
+ | #Gibt es keine dunklen Pixel direkt ueber oder direkt rechts vom aktuelle, ist das Maximum gefunden | ||
+ | |||
+ | return cy #Hoehe des Maximums wird returnt | ||
+ | |||
</code> | </code> | ||
Zeile 259: | Zeile 297: | ||
def Findomat_UR(x,y,yH,threshold,img): | def Findomat_UR(x,y,yH,threshold,img): | ||
+ | #x=x-Koordinate Startpunkt; y=y-Koordinate Startpunkt; yH=Von Korrektoid verbesserte y-Koordinate; | ||
+ | #treshold=Grenzwert; img=BildMatrix | ||
cx=x+1 | cx=x+1 | ||
Zeile 295: | Zeile 335: | ||
== Schwarz/Weiß-Tester Unten Rechts== | == Schwarz/Weiß-Tester Unten Rechts== | ||
- | __Name im Code: swTester_UR.__ Die Methode bekommt einen Startpunkt, eine Zielkoordinate und eine Variable, die klar macht ob die Zielkoordinate ein x-Wert oder y-Wert ist. Dann versucht sie eine Line vom Start- zum Zielpunkt zu ziehen ohne einen dunklen Pixel zu treffen. Die "Linien" können nur von unten nach oben und von rechts nach links gezogen weren. Ist die Aktion erfolgreich, wurd "True" zurückgegeben, schlägt der versuch fehl, ist der Rückgabewert "False". | + | __Name im Code: swTester_UR.__ Die Methode bekommt einen Startpunkt, eine Zielkoordinate und eine Variable, die klar macht ob die Zielkoordinate ein x-Wert oder y-Wert ist. Dann versucht sie eine Line vom Start- zum Zielpunkt zu ziehen ohne einen dunklen Pixel zu treffen. Die "Linien" können nur von unten nach oben und von rechts nach links gezogen weren. Ist die Aktion erfolgreich, wurde "True" zurückgegeben, schlägt der versuch fehl, ist der Rückgabewert "False". |
+ | |||
+ | <code python> | ||
+ | |||
+ | def swTester_UR(x,y,w,zk,threshold,img): | ||
+ | #x=Startbreite; y=Starthoehe; w=horizontal oder vertikal; zk=Zielkoordinate; | ||
+ | |||
+ | h,b=img.shape #h ist die Hoehe des Bildes, b ist die Breite des Bildes | ||
+ | |||
+ | cy=y | ||
+ | cx=x | ||
+ | #Setzt Startparameter | ||
+ | |||
+ | if w==1: | ||
+ | #Diese Schleife wird ausgefuehrt, wenn in x-Richtung getestet werden soll | ||
+ | while (img[cy,cx]>=threshold): | ||
+ | #Diese Schleife bewegt sich so lange nach links, bis entweder ein | ||
+ | #dunkler Pixel gefunden wird (False) oder die Zielkoordinate erreicht wurde (True) | ||
+ | if cx==zk: | ||
+ | return True | ||
+ | cx=cx-1 | ||
+ | return False | ||
+ | |||
+ | else: | ||
+ | #Diese Schleife wird ausgefuehrt, wenn in y-Richtung getestet werden soll | ||
+ | while (img[cy,cx]>=threshold): | ||
+ | #Diese Schleife bewegt sich so lange nach oben, bis entweder ein | ||
+ | #dunkler Pixel gefunden wird (False) oder die Zielkoordinate erreicht wurde (True) | ||
+ | if cy==zk: | ||
+ | return True | ||
+ | cy=cy-1 | ||
+ | return False | ||
+ | |||
+ | </code> | ||
== Koordinaten-Anpasser Unterkante== | == Koordinaten-Anpasser Unterkante== | ||
- | __Name im Code: Korrektomat.__ Um korrekt zu funktionieren, benötigt //Findomat_OL// einen dunkler Pixel, welcher teil des Symbols ist. Diese Methode wurde geschrieben, um diesen Pixel zu finden. Die Startkoordinaten für die Suche sind die zuvor von //Findomat_UR// gefundenen Koordinaten der unteren rechten Ecke. Von dort aus bewegt sich die Methode nach links oben, bis sie auf einen dunklen Pixel trifft. | + | __Name im Code: Korrektomat.__ Um korrekt zu funktionieren, benötigt //Findomat_OL// einen dunklen Pixel, welcher teil des Symbols ist. Diese Methode wurde geschrieben, um diesen Pixel zu finden. Die Startkoordinaten für die Suche sind die zuvor von //Findomat_UR// gefundenen Koordinaten der unteren rechten Ecke. Von dort aus bewegt sich die Methode nach links oben, bis sie auf einen dunklen Pixel trifft. |
+ | |||
+ | <code python> | ||
+ | |||
+ | def Korrektomat(x,y,threshold,img): | ||
+ | #x=x-Koordinate Startpunkt; y=y-Koordinate Startpunkt; treshold=Grenzwert; img=BildMatrix | ||
+ | |||
+ | cx=x-1 | ||
+ | cy=y-1 | ||
+ | #Verschiebt die Koordinten des Punktes um eins nach links bzw oben | ||
+ | |||
+ | while (img[cy,cx]>=threshold): | ||
+ | cx=cx-1 | ||
+ | cy=cy-1 | ||
+ | #Verscheibt den Punkt solange nach oben links, bis der auf einem dunklen Pixel liegt | ||
+ | |||
+ | return cx,cy #Gibt verbesserten Punkt zurueck | ||
+ | |||
+ | </code> | ||
== Linke-Kante-Finder == | == Linke-Kante-Finder == | ||
Zeile 306: | Zeile 397: | ||
def Findomat_OL(x,y,threshold,img): | def Findomat_OL(x,y,threshold,img): | ||
+ | #x=x-Koordinate Startpunkt; y=y-Koordinate Startpunkt; treshold=Grenzwert; img=BildMatrix | ||
cx=x-1 | cx=x-1 | ||
Zeile 343: | Zeile 435: | ||
__Name im Code: swTester_OL.__ Die Funktionsweise dieser Methode ist fast identisch mit der von //swTester_UR//. Der einzige Unterschied besteht darin, dass die der Algorithmus sich von oben nach unten Bzw. von links nach rechts über das Bild bewegt. | __Name im Code: swTester_OL.__ Die Funktionsweise dieser Methode ist fast identisch mit der von //swTester_UR//. Der einzige Unterschied besteht darin, dass die der Algorithmus sich von oben nach unten Bzw. von links nach rechts über das Bild bewegt. | ||
- | == Koordinaten-Anpasser Oberkante== | + | <code python> |
- | __Name im Code: Korrektoid.__ Diese Methode wurde zu dem Zweck geschrieben, den "Halbe Zeichen"-Fehler zu beheben. Sie nimmt den zuvor von //Scannomaton// gefundenen Punkt und bringt ihn auf die Höhe des höchsten Punktes des Buchstaben. Die Funktionsweise ähnelt der von //Findomat_UR// und //Findomat_OL//. Der Helligkeitswert des Pixels über dem Startpunkt wird getestet. Ist der Pixel dunkel, "bewegt" sich Algorithmus nach oben, ist der Pixel hell, wird der Pixel auf der rechten Seit überprüft. Falls dieser Pixel dunkel ist, wird er zu neuen Startpunkt. Dies wird so lange wiederholt, bis beide Pixel hell sind, der Algorithmus also am lokalen Maximum des Zeichens angelangt ist. Der y-Wert des von //Scannomaton// übergeben Punktes wird nun durch den des lokalen Maximums ersetzt. | + | |
+ | def swTester_OL(x,y,w,zk,threshold,img): | ||
+ | #x=Startbreite; y=Starthoehe; w=horizontal oder vertikal; zk=Zielkoordinate; | ||
+ | |||
+ | h,b=img.shape #h ist die Hoehe des Bildes, b ist die Breite des Bildes | ||
+ | |||
+ | cy=y | ||
+ | cx=x | ||
+ | #Setzt Startparameter | ||
+ | |||
+ | |||
+ | if w==1: | ||
+ | #Diese Schleife wird ausgefuehrt, wenn in x-Richtung getestet werden soll | ||
+ | while (img[cy,cx]>=threshold) and (cx<b): | ||
+ | #Diese Schleife bewegt sich so lange nach rechts, bis entweder ein | ||
+ | #dunkler Pixel gefunden wird (False) oder die Zielkoordinate erreicht wurde (True) | ||
+ | if cx==zk: | ||
+ | return True | ||
+ | cx=cx+1 | ||
+ | return False | ||
+ | |||
+ | else: | ||
+ | #Diese Schleife wird ausgefuehrt, wenn in y-Richtung getestet werden soll | ||
+ | while (img[cy,cx]>=threshold) and (cy<h): | ||
+ | #Diese Schleife bewegt sich so lange nach unten, bis entweder ein | ||
+ | #dunkler Pixel gefunden wird (False) oder die Zielkoordinate erreicht wurde (True) | ||
+ | if cy==zk: | ||
+ | return True | ||
+ | cy=cy+1 | ||
+ | return False | ||
+ | |||
+ | </code> | ||
Zeile 369: | Zeile 492: | ||
== Artefakte == | == Artefakte == | ||
- | Flecken, Schatten, Druckfehler, Stempeln oder ähnliche Unreinheiten auf dem Papier sind teilweise dunkel genug um vom Programm als Symbol erkannt zu werden. Das kann dazu führen, dass Zeilen nicht richtig erkannt werden, in manchen Fällen hält das Programm dann zwei Zeilen für eine einzige. In solchen Fällen werden oft Buchstaben aus den beiden Zeilen nicht gefunden. Dieser Fall tritt aber nur bei relativ großen Artefakten ein, welche ziemlich selten sind, wenn man sich mit dem Foto etwas Mühe gibt. | + | Flecken, Schatten, Druckfehler, Stempel oder ähnliche Unreinheiten auf dem Papier sind teilweise dunkel genug um vom Programm als Symbol erkannt zu werden. Das kann dazu führen, dass Zeilen nicht richtig erkannt werden, in manchen Fällen hält das Programm dann zwei Zeilen für eine einzige. In solchen Fällen werden oft Buchstaben aus den beiden Zeilen nicht gefunden. Dieser Fall tritt aber nur bei relativ großen Artefakten ein, welche ziemlich selten sind, wenn man sich mit dem Foto etwas Mühe gibt. |
Kleinere Artefakte werden einfach als Symbole erkannt und an das neuronale Netzwerk übergeben, welches diese dann aussortieren soll. In diesem Teil des Programmes eine Lösung zu finden wäre zu aufwendig, vor allem da man mit einem neuronales Netzwerk ziemlich einfach erkennen kann, ob es sich um eine richtiges Symbol handelt oder nicht. | Kleinere Artefakte werden einfach als Symbole erkannt und an das neuronale Netzwerk übergeben, welches diese dann aussortieren soll. In diesem Teil des Programmes eine Lösung zu finden wäre zu aufwendig, vor allem da man mit einem neuronales Netzwerk ziemlich einfach erkennen kann, ob es sich um eine richtiges Symbol handelt oder nicht. | ||
==== Machine Learning ==== | ==== Machine Learning ==== | ||
Zeile 709: | Zeile 832: | ||
== Predictions und Verarbeitung == | == Predictions und Verarbeitung == | ||
- | Ein weiteres Problem was wir hatten war die Modelle zu Bewerten. Dazu haben wir das Modell einfach über unsere Testbilder laufen lassen und geguckt wie viele es richtig bewertet. | + | Ein weiteres Problem was wir hatten war die Modelle zu Bewerten. Dazu haben wir das Modell einfach über unsere Testbilder laufen lassen und geguckt wie viele es richtig bewertet. Dabei ging es aber erst mal darum einen Eindruck über die ungefähre Genauigkeit zu bekommen. Den richtigen Test haben wir dann mit Bildern von ausgeschnittenen Text gemacht welche das Programm dann als String ausgeben sollte. Dabei sind jedoch die meisten Modelle gescheitert und man konnte oft nicht wiedererkennen was der eigentliche Text sein sollte. Ein weiteres Problem was wir hatten war die Leerzeichen richtig zu setzen. Dies haben wir dann dadurch gelöst, dass wir den Anfangs und Endpixel in Y- Richtung uns merken und dann bei einem zu großen Unterschied ein Leerzeichen in den Text einfügen. Dieses Verfahren ist aber sehr anfällig, weil man einen festen Wert braucht der ungefähr die Größe eines Leerzeichens darstellt. |
- | + | ||
- | + | ||
- | Unsere Gruppe besteht aus Jan, Tessa, Lucas, Lea und Benni | ||
- | Unser Ziel ist es einen Kassenbon einlesen zu können und diese Daten dann darzustellen. | ||
- | Dazu müssen wir zwei Funktionen implementieren und zwar brauchen wir eine Funktion, die ein Bild in kleine Bilder aufteilt so, dass in jedem kleinem Bild nur ein Buchstabe ist. | ||
- | Die zweite Funktion muss in dem kleinen Bild einen Buchstaben erkennen. | + | Unsere Gruppe bestand aus Jan, Tessa, Lucas, Lea und Benni |
[[ss17:protokolle_kassenzettel |Protokolle ]] | [[ss17:protokolle_kassenzettel |Protokolle ]] | ||