Ich habe nach OMR (Optical Mark Reader) gesucht, weil ich die Dateneingabe bei der Durchführung einer Fragebogenumfrage so effizient wie möglich gestalten wollte, aber ich konnte nicht finden, wonach ich suchte, und habe mich daher für eine einfache entschieden. Ich habe beschlossen, es zu machen. Es mag einen klügeren Weg geben, aber ich habe etwas geschaffen, das so funktioniert, wie es sollte.
OpenCV wird für die Bilderkennungsverarbeitung und NumPy für die numerische Verarbeitung verwendet. Beachten Sie, dass Sie bei der Verwendung von OpenCV 3 aus Python 3 mit Homebrew auf dem Mac etwas vorsichtig sein müssen. Es kann nicht von Python 3 aus verwendet werden, es sei denn, die Option "--with-python3" wird hinzugefügt, wenn "Brew Install" ausgeführt wird. Da es sich um ein "Nur-Fass" -Paket handelt, müssen Sie den Bibliothekspfad für Python 2 und 3 selbst festlegen. Wenn Sie im Internet suchen, werden verschiedene Seiten angezeigt, auf denen das Verfahren vorgestellt wird. Die leicht verständliche Seite ist jedoch diese Seite. Ich denke.
Erstellen Sie zunächst ein Markierungsblatt, da es ohne dieses Blatt nicht gestartet werden kann. Wenn Sie den Lesevorgang für Marken so einfach wie möglich gestalten möchten, sollten Sie beim Erstellen des Markenblatts einige Punkte beachten.
Wenn die Markierung selbst dunkel gezeichnet ist, ist es schwierig festzustellen, ob sie gefüllt ist. Die Markierung selbst sollte hell sein, damit sie nicht schwer zu erkennen ist, oder sie sollte mit ziemlich dünnen Linien versehen sein.
Markierungen für Markenblätter können mit Illustrator usw. erstellt werden, oder sie können mit grundlegenden Abbildungen von Textverarbeitungssoftware wie MS Word und Pages erstellt werden. Die folgende Abbildung wurde mit den abgerundeten Rechtecken der Seiten erstellt. Die Farbe ist 40% grau, die Zahlen für Hiragino Kakugo W1 sind 5pt und die umgebenden Linien sind 0,3pt. Auf dem Markenblatt ist die Markengröße 3 mm breit und 5 mm hoch.
Um den Markierungserkennungsprozess zu vereinfachen, trennen Sie die Markierungen ein wenig und ordnen Sie sie so an, dass sie in vertikaler und horizontaler Richtung ** gleichmäßig verteilt ** sind. Wenn der Markierungsabstand groß genug ist, gibt es kein Problem, auch wenn die Markierungen geringfügig vom regulären Abstand abweichen.
Feature-Punkte (** Markierungen **) sind erforderlich, um den Markierungsblattbereich zu extrahieren. Erstellen Sie eine Figur mit einer relativ einfachen Form, die sich nicht mit anderen Zeichen oder Symbolen überschneidet, und platzieren Sie 2 bis 4 an den vier Ecken des Bereichs, der für die Erkennungsverarbeitung verwendet wird.
Stellen Sie zu diesem Zeitpunkt sicher, dass ** oben links ** der Markierung mit der Ecke des Erkennungsbereichs übereinstimmt. In dem Beispielmarkierungsblatt wird der Bereich mit einem Rand von 3 Linien für die Höhe der Markierungslinie über dem Bereich, in dem die Markierung platziert ist, und 1 Zeile darunter als Erkennungsbereich verwendet, und der obere linke, obere rechte und untere rechte Rand dieses Bereichs werden verwendet. An den drei Stellen sind Markierungen angebracht. Ich denke, es ist in Ordnung, nur zwei Markierungen zu haben, die obere linke und die untere rechte, aber ich dachte, dass die Neigung der Seite falsch eingeschätzt werden könnte, also habe ich sie für alle Fälle oben rechts platziert. Das Bild unten ist ein Beispiel für das erstellte Markierungsblattpapier.
Da die Markierungsbilddatei für die Erkennungsverarbeitung erforderlich ist, speichern Sie nur die Markierung als Bilddatei getrennt vom Markierungsblattpapier.
← マーカーだけを画像として保存する
Damit ist die Vorbereitung des Markierungsblatts abgeschlossen.
Das ausgefüllte Markierungsblatt wird mit einem Dokumentenscanner wie ScanSnap in ** Graustufen ** gelesen. Wenn das gescannte Bild geneigt ist, kann es nicht gut erkannt werden. Es ist möglich, eine Neigungskorrektur mit OpenCV durchzuführen, die Verarbeitung wird jedoch entsprechend kompliziert. Aktivieren Sie daher hier die ** Neigungskorrektur ** mit der Scan-Software des Scanners und lesen Sie. Wenn die Ausrichtung des Markierungsblattpapiers nicht ausgerichtet ist, aktivieren Sie auch die automatische Drehung.
Das folgende Bild ist das Ergebnis des Scannens, indem das Mustermarkierungsblattpapier entsprechend markiert wurde (die Größe wurde verringert).
Von hier aus ist es die Bilderkennungsverarbeitung mit Python und OpenCV. Importieren Sie zunächst NumPy und OpenCV.
import numpy as np
import cv2
Lesen Sie das gescannte Bild und schneiden Sie mit dem Marker nur den gewünschten Bereich aus.
Lesen Sie zuerst die Markierungsbilddatei (in diesem Fall marker.jpg
), um den Bereich des Markierungsblatts auszuschneiden, und nehmen Sie die erforderlichen Einstellungen vor. Der Vorlagenabgleich wird verwendet, um die Position des Markers mit OpenCV zu identifizieren. Die Größe des Vorlagenbilds (Markierungsbildes) muss jedoch mit der Größe des Markers im gescannten Bild übereinstimmen. Ändern Sie die Größe des Markers entsprechend der Scanauflösung, sodass die Größe des Markerbilds in etwa der Größe des gescannten Bildes entspricht. Sie können den Marker vergrößern und im Voraus speichern.
###Markereinstellungen
marker_dpi = 72 #Bildschirmauflösung(Markergröße)
scan_dpi = 300 #Auflösung des gescannten Bildes
#Graustufen(mode = 0)Lesen Sie die Datei mit
marker=cv2.imread('marker.jpg',0)
#Holen Sie sich die Größe des Markers
w, h = marker.shape[::-1]
#Ändern Sie die Größe des Markers
marker = cv2.resize(marker, (int(h*scan_dpi/marker_dpi), int(w*scan_dpi/marker_dpi)))
Laden Sie als Nächstes das gescannte Markierungsblattbild. Es wird angenommen, dass das gescannte Bild als "sample001.jpg " gespeichert wird.
###Gescanntes Bild laden
img = cv2.imread('sample001.jpg',0)
Aus diesem gescannten Bild werden mit der Template-Matching-Funktion matchTemplate ()
Marker extrahiert.
res = cv2.matchTemplate(img, marker, cv2.TM_CCOEFF_NORMED)
Dieser Teil "cv2.TM_CCOEFF_NORMED" gibt die Funktion zur Beurteilung der Ähnlichkeit an. Wenn es kein besonderes Problem gibt, können Sie es so lassen, wie es ist.
Das Übereinstimmungsergebnis von "matchTemplate ()" enthält einen Wert, der den Ähnlichkeitsgrad (Maximum = 1,0) mit dem Vorlagenbild für jede Koordinate im gescannten Bild angibt. Von hier werden nur die Teile mit einem hohen Grad an Ähnlichkeit extrahiert.
threshold = 0.7
loc = np.where( res >= threshold)
Hier werden nur die Koordinaten extrahiert, deren Ähnlichkeit "0,7" oder mehr beträgt. Passen Sie diesen Wert nach Bedarf an. Je größer der Wert, desto strenger das Urteil und je kleiner der Wert, desto lockerer das Urteil. Wenn sich beispielsweise die Größe des als Vorlage verwendeten Markers stark von der Größe des Markers im gescannten Bild unterscheidet, ist die Ähnlichkeit gering und wird nur dann richtig erkannt, wenn dieser Wert verringert wird. Wenn die Kriterien zu locker sind, werden Nichtmarker fälschlicherweise erkannt. Wenn die Größe des Markers angemessen ist, sollte er meiner Meinung nach bei 0,7 liegen.
Suchen Sie aus den extrahierten Koordinaten die oberen linken und unteren rechten Koordinaten des Erkennungsbereichs. Von den Ergebnissen mit hoher Ähnlichkeit ist der obere linke Koordinatenwert der minimale Koordinatenwert sowohl für "x" als auch für "y", und der untere rechte Koordinatenwert ist der größte Koordinatenwert für "x" und "y". Beachten Sie, dass die extrahierten Koordinatenwerte im Array in der Reihenfolge "y" und "x" gespeichert werden.
mark_area={}
mark_area['top_x']= min(loc[1])
mark_area['top_y']= min(loc[0])
mark_area['bottom_x']= max(loc[1])
mark_area['bottom_y']= max(loc[0])
Basierend auf diesen Koordinaten wird ein gescanntes Bild ausgeschnitten. Um ein Bild zuzuschneiden, geben Sie einfach die Koordinaten des gewünschten Bereichs für das Originalbild an. Es ist jedoch zu beachten, dass die Reihenfolge Y-Koordinate und X-Koordinate ist.
img = img[mark_area['top_y']:mark_area['bottom_y'],mark_area['top_x']:mark_area['bottom_x']]
Notieren Sie den Ausschnittbereich und prüfen Sie, ob er richtig ausgeschnitten ist.
cv2.imwrite('res.png',img)
Auf der linken Seite befindet sich ein leichtes Leerzeichen, dies ist jedoch kein Problem.
Um die nachfolgende Verarbeitung zu erleichtern, ändern Sie die Größe des ausgeschnittenen Bilds so, dass es ein ganzzahliges Vielfaches der Anzahl der Spalten und Zeilen der Marke ist. Hier beträgt die Anzahl der Spalten und Zeilen das 100-fache. Berücksichtigen Sie beim Zählen der Anzahl der Zeilen den Rand vom Markierungsbereich bis zur Markierung.
n_col = 7 #Anzahl der Markierungen pro Zeile
n_row = 7 #Anzahl der Markierungslinien
margin_top = 3 #Anzahl der oberen Randlinien
margin_bottom = 1 #Anzahl der unteren Randlinien
n_row = n_row + margin_top + margin_bottom #Anzahl der Zeilen(Markieren Sie Zeile 7 Zeilen+Oberer Rand 3 Zeilen+Unterer Rand 1 Zeile)
img = cv2.resize(img, (n_col*100, n_row*100))
Nachdem das ausgeschnittene Bild leicht verwischt wurde, wird das Bild in Schwarzweiß binärisiert und Schwarzweiß wird invertiert. Im folgenden Beispiel wird die Gaußsche Unschärfe angewendet und dann basierend auf einer Helligkeit von 50 binärisiert. Die Schwarz-Weiß-Inversion subtrahiert nur den Bildwert von 255.
###Verwischen
img = cv2.GaussianBlur(img,(5,5),0)
###Binar mit 50 als Schwelle
res, img = cv2.threshold(img, 50, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
###Schwarz-Weiß-Inversion
img = 255 - img
Das Ergebnis dieser Operationen ist wie folgt.
Markierungen werden erkannt, indem das Bild ausgeschnitten, in der Größe geändert und weiter in Linien unterteilt wird.
Der für jede Zeile auszuführende Vorgang besteht darin, das Bild zuerst horizontal in die Anzahl der Markierungen zu teilen und die Summe jedes Bildwerts zu berechnen. Da das Bild schwarz und weiß invertiert ist, ist der farbige Teil weiß (255
) und der leere Teil ist schwarz ( 0
). Mit anderen Worten bedeutet die Summe der Bildwerte die Fläche des farbigen Teils (markierter Teil).
Dann wird der ** Median ** der Fläche des markierten Teils berechnet und als ** Schwellenwert ** verwendet, wenn bestimmt wird, ob dieser Median markiert ist oder nicht. Da einige Teile des Markierungsblatts ursprünglich farbig sind, wie z. B. gedruckte Linien und Zahlen, sind die nicht markierten Teile in gewissem Umfang auch farbig. Wenn daher im folgenden Beispiel die Fläche des farbigen Teils das Dreifache oder mehr des berechneten Medianwerts beträgt, wird sie als "wahr" beurteilt, und wenn dies nicht der Fall ist, wird sie als "falsch" beurteilt. Passen Sie dieses Vielfache nach Bedarf an. Je größer das Vielfache, desto strenger das Urteil und je kleiner das Vielfache, desto süßer das Urteil.
Diese Methode basiert auf der Annahme, dass jede Zeile mit einer oder zwei Markierungen gefüllt wird. Da es auf dem Medianwert basiert, funktioniert es nicht gut, wenn alle Markierungen gefüllt sind.
###Bereiten Sie ein Array vor, um das Ergebnis zu platzieren
result = []
###Zeilenweise Verarbeitung(Prozess durch Ausschließen von Randlinien)
for row in range(margin_top, n_row - margin_bottom):
###Schneiden Sie nur die zu verarbeitende Zeile aus
tmp_img = img [row*100:(row+1)*100,]
area_sum = [] #Ein Array, um den Gesamtwert zu setzen
###Bearbeitung jeder Marke
for col in range(n_col):
###Berechnen Sie mit NumPy den Gesamtwert der Bilder in jedem Markierungsbereich
area_sum.append(np.sum(tmp_img[:,col*100:(col+1)*100]))
###Beurteilung, ob der Gesamtwert des Bildbereichs das Dreifache oder mehr des Medianwerts beträgt
result.append(area_sum > np.median(area_sum) * 3)
Lassen Sie uns das Erkennungsergebnis ausgeben. Stellen Sie zur Sicherheit sicher, dass sich nicht mehrere Markierungen in derselben Zeile befinden.
for x in range(len(result)):
res = np.where(result[x]==True)[0]+1
if len(res)>1:
print('Q%d: ' % (x+1) +str(res)+ ' ##Mehrere Antworten##')
elif len(res)==1:
print('Q%d: ' % (x+1) +str(res))
else:
print('Q%d: **Unbeantwortet**' % (x+1))
Q1: [1]
Q2: [2]
Q3: [3]
Q4: [4]
Q5: [5]
Q6: [6]
Q7: [7]
Es wurde richtig erkannt. Hier gibt es keine Warnung mit mehreren Antworten. Wenn Sie jedoch den Erkennungsschwellenwert ein wenig senken, erhöht sich die falsche Erkennung und Sie können Warnungen mit mehreren Antworten sehen.
Wenn der Erkennungsschwellenwert auf das Doppelte des Medianwerts eingestellt und ausgeführt wurde, war das Ergebnis wie folgt.
Q1: [1]
Q2: [2]
Q3: [3]
Q4: [4 7] ##Mehrere Antworten
Q5: [5]
Q6: [6]
Q7: [7]
Wenn der Erkennungsschwellenwert auf das 50-fache des Medianwerts eingestellt und ausgeführt wurde, war das Ergebnis wie folgt.
Q1: [1]
Q2: **Unbeantwortet**
Q3: **Unbeantwortet**
Q4: [4]
Q5: **Unbeantwortet**
Q6: [6]
Q7: **Unbeantwortet**
Wenn Sie das Markierungsblattpapier richtig herstellen, können Sie einen Markierungsblattleser mit einer anständigen Erkennungsfähigkeit relativ einfach herstellen, indem Sie ihn zusammen mit einem Dokumentenscanner verwenden. Da es sich bei dem hier vorgestellten Beispiel nur um ein Beispiel handelt, wird es nicht so verarbeitet, dass mehrere gescannte Bilder erkannt werden können. Ich denke jedoch, dass es nicht so schwierig ist, alle Bilddateien im Ordner zu erkennen.
Wenn sich das Layout des Markenblatts ändert, muss das Skript geändert werden. Da sich das Layout des Markenblatts in meiner Umgebung jedoch wahrscheinlich nicht ändert, ist es fast erforderlich, es zu ändern, nachdem Sie es zuerst mehrmals versucht und den Schwellenwert angepasst haben. da ist nicht. Eigentlich verarbeite ich seit mehr als einem Jahr zehn bis hundert Blätter pro Woche mit diesem OMR, wobei einige Funktionen hinzugefügt wurden, aber ich habe den Einstellwert in dieser Zeit mehrmals geändert. Da ist nur.