Erstellen Sie mit Python und OpenCV ein einfaches OMR (Mark Sheet Reader)

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.

Rauer Fluss

Vorbereitungen

Markierungsblatt erstellen

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.

Hinweis 1: Die Markierung selbst ist so dünn wie möglich

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.

mark.png

Hinweis 2: Die Markierungen sollten gleichmäßig verteilt sein und einen ausreichenden Abstand zwischen ihnen haben.

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.

Hinweis 3: Bereiten Sie einen Feature-Punkt (Marker) vor.

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.

marksheet.png

Da die Markierungsbilddatei für die Erkennungsverarbeitung erforderlich ist, speichern Sie nur die Markierung als Bilddatei getrennt vom Markierungsblattpapier.

marker.jpg ← マーカーだけを画像として保存する

Damit ist die Vorbereitung des Markierungsblatts abgeschlossen.

Blatt-Scan markieren

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).

sample001_small.png

Erkennungsprozess

Von hier aus ist es die Bilderkennungsverarbeitung mit Python und OpenCV. Importieren Sie zunächst NumPy und OpenCV.

import numpy as np
import cv2

Zuschneiden und Ändern der Bildgröße

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)

clipped_small.png

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.

dicho_small.png

Markieren Sie die Erkennung

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)

Ergebnisausgabe

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**

Zusammenfassung

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.

Recommended Posts

Erstellen Sie mit Python und OpenCV ein einfaches OMR (Mark Sheet Reader)
Versuchen Sie, ein einfaches Spiel mit Python 3 und iPhone zu erstellen
So erstellen Sie eine Überwachungskamera (Überwachungskamera) mit Opencv und Python
Erstellen eines Markierungsblatts mit Python OpenCV (Tipps zum guten Lesen)
Erstellen Sie einen einfachen Slackbot mit einer interaktiven Schaltfläche in Python
Machen Sie eine Lotterie mit Python
Was ist Gott? Erstelle einen einfachen Chatbot mit Python
Erstellen Sie mit python wxpython + openCV ein einfaches Videoanalysetool
Leuchtendes Leben mit Python und OpenCV
Neuronales Netzwerk mit OpenCV 3 und Python 3
Lassen Sie uns eine GUI mit Python erstellen.
Erstellen Sie ein Empfehlungssystem mit Python
Lassen Sie uns ein Diagramm mit Python erstellen! !!
Ich habe eine einfache Schaltung mit Python gemacht (AND, OR, NOR, etc.)
Rubyist hat versucht, eine einfache API mit Python + Flasche + MySQL zu erstellen
Erstellen Sie eine gestreifte Illusion mit Gammakorrektur für Python3 und openCV3
Zeichnen Sie eine Aquarellillusion mit Kantenerkennung in Python3 und openCV3
Ich habe versucht, mit Selenium und Python einen regelmäßigen Ausführungsprozess durchzuführen
Erstellen Sie mit Python + Django + AWS eine Scraping-App und wechseln Sie Jobs
Mit Python und OpenCV lassen Sie Unschärfevideos wie Festkommakameras aussehen
Erstellen einer einfachen Power-Datei mit Python
Einfacher RSS-Reader mit Django
Erstellen Sie einen Videoplayer mit PySimpleGUI + OpenCV
Bilder mit Pupil, Python und OpenCV aufnehmen
Fraktal zum Erstellen und Spielen mit Python
Ein Memo mit Python2.7 und Python3 in CentOS
Lassen Sie uns mit Python langsam sprechen
Lassen Sie uns mit PLY 1 eine einfache Sprache erstellen
Erstellen Sie ein Webframework mit Python! (1)
Erstellen Sie eine Desktop-App mit Python mit Electron
Machen wir einen Twitter-Bot mit Python!
Erstellen Sie ein Webframework mit Python! (2)
Ich habe mit Python einen einfachen Blackjack gemacht
Erstellen Sie mit Raspberry Pi einen WLAN-Ethernet-Konverter und einen einfachen Router
Machen Sie mit Python einen Entscheidungsbaum von 0 und verstehen Sie ihn (4. Datenstruktur)
Hallo Welt- und Gesichtserkennung mit OpenCV 4.3 + Python
Machen Sie Twitter Trend Bot mit Heroku + Python
Erstellen einer Python-Umgebung mit virtualenv und direnv
Erstellen Sie mit Flask einen einfachen Punktbildgenerator
Ich möchte ein Spiel mit Python machen
Installieren Sie OpenCV 4.0 und Python 3.7 unter Windows 10 mit Anaconda
Starten Sie mit Docker einen einfachen Python-Webserver
Versuchen Sie, in Python einen "Entschlüsselungs" -Code zu erstellen
Stellen Sie OpenCV3 in Python3 zur Verfügung, das mit pyenv installiert wurde
Starten Sie einen Webserver mit Python und Flask
Ersetzen wir UWSC durch Python (5) Machen wir einen Roboter
Versuchen Sie, mit Python eine Diedergruppe zu bilden
Einfache Aufgabenliste, erstellt mit Python + Django
Feature Matching mit OpenCV 3 und Python 3 (A-KAZE, KNN)
[# 1] Mach Minecraft mit Python. ~ Vorforschung und Design ~
Stellen Sie Docker in Windows Home und führen Sie einen einfachen Webserver mit Python aus
Ich habe versucht, einen periodischen Prozess mit CentOS7, Selenium, Python und Chrome durchzuführen
Ich habe eine einfache Mail-Sendeanwendung mit tkinter von Python erstellt
Erstellen wir ein einfaches Empfangssystem mit dem serverlosen Python-Framework Chalice und Twilio
WEB Scraping mit Python und versuchen, aus Bewertungen eine Wortwolke zu machen
Holen Sie sich mit Python den Aktienkurs eines japanischen Unternehmens und erstellen Sie eine Grafik