Einfache OCR zur Erkennung von Ziffern in OpenCV-Python

380

Ich versuche, eine "Digit Recognition OCR" in OpenCV-Python (cv2) zu implementieren. Es ist nur zu Lernzwecken. Ich möchte sowohl KNearest- als auch SVM-Funktionen in OpenCV lernen.

Ich habe 100 Proben (dh Bilder) von jeder Ziffer. Ich würde gerne mit ihnen trainieren.

Es gibt ein Beispiel letter_recog.py, das mit einem OpenCV-Beispiel geliefert wird. Aber ich konnte immer noch nicht herausfinden, wie man es benutzt. Ich verstehe nicht, was die Beispiele, Antworten usw. sind. Außerdem wird zuerst eine txt-Datei geladen, die ich zuerst nicht verstanden habe.

Später, als ich ein wenig suchte, konnte ich in cpp-Beispielen eine letter_recognition.data finden. Ich habe es verwendet und einen Code für cv2.KNearest im Modell von letter_recog.py erstellt (nur zum Testen):

import numpy as np
import cv2

fn = 'letter-recognition.data'
a = np.loadtxt(fn, np.float32, delimiter=',', converters={ 0 : lambda ch : ord(ch)-ord('A') })
samples, responses = a[:,1:], a[:,0]

model = cv2.KNearest()
retval = model.train(samples,responses)
retval, results, neigh_resp, dists = model.find_nearest(samples, k = 10)
print results.ravel()

Es gab mir eine Reihe von Größe 20000, ich verstehe nicht, was es ist.

Fragen:

1) Was ist die Datei letter_recognition.data? Wie erstelle ich diese Datei aus meinem eigenen Datensatz?

2) Was bedeutet results.reval()?

3) Wie können wir ein einfaches Ziffernerkennungswerkzeug mit der Datei letter_recognition.data (entweder KNearest oder SVM) schreiben?

Abid Rahman K.
quelle

Antworten:

527

Nun, ich habe mich entschlossen, meine Frage zu bearbeiten, um das obige Problem zu lösen. Ich wollte eine einfache OCR mit KNearest- oder SVM-Funktionen in OpenCV implementieren. Und unten ist, was ich getan habe und wie. (Es dient nur zum Erlernen der Verwendung von KNearest für einfache OCR-Zwecke).

1) Meine erste Frage betraf die Datei letter_recognition.data, die mit OpenCV-Beispielen geliefert wird. Ich wollte wissen, was sich in dieser Datei befindet.

Es enthält einen Brief sowie 16 Merkmale dieses Briefes.

Und this SOFhat mir geholfen, es zu finden. Diese 16 Funktionen werden in diesem Artikel erläutert Letter Recognition Using Holland-Style Adaptive Classifiers. (Obwohl ich einige der Funktionen am Ende nicht verstanden habe)

2) Da ich wusste, ohne all diese Funktionen zu verstehen, ist es schwierig, diese Methode anzuwenden. Ich habe einige andere Papiere ausprobiert, aber für Anfänger waren alle etwas schwierig.

So I just decided to take all the pixel values as my features. (Ich war nicht besorgt über Genauigkeit oder Leistung, ich wollte nur, dass es funktioniert, zumindest mit der geringsten Genauigkeit)

Ich habe das folgende Bild für meine Trainingsdaten aufgenommen:

Geben Sie hier die Bildbeschreibung ein

(Ich weiß, dass die Menge der Trainingsdaten geringer ist. Da jedoch alle Buchstaben dieselbe Schriftart und Größe haben, habe ich beschlossen, dies auszuprobieren.)

Um die Daten für das Training vorzubereiten, habe ich in OpenCV einen kleinen Code erstellt. Es macht folgende Dinge:

  1. Es lädt das Bild.
  2. Wählt die Ziffern aus (offensichtlich durch Kontursuche und Anwenden von Einschränkungen für Fläche und Höhe von Buchstaben, um falsche Erkennungen zu vermeiden).
  3. Zeichnet das Begrenzungsrechteck um einen Buchstaben und wartet key press manually. Diesmal drücken wir die Zifferntaste selbst , die dem Buchstaben im Feld entspricht.
  4. Sobald die entsprechende Zifferntaste gedrückt wird, ändert sich die Größe dieses Felds auf 10 x 10 und es werden 100 Pixelwerte in einem Array (hier Beispiele) und die entsprechende manuell eingegebene Ziffer in einem anderen Array (hier Antworten) gespeichert.
  5. Speichern Sie dann beide Arrays in separaten TXT-Dateien.

Am Ende der manuellen Klassifizierung der Ziffern werden alle Ziffern in den Zugdaten (train.png) von uns manuell beschriftet. Das Bild sieht wie folgt aus:

Geben Sie hier die Bildbeschreibung ein

Unten ist der Code, den ich für den obigen Zweck verwendet habe (natürlich nicht so sauber):

import sys

import numpy as np
import cv2

im = cv2.imread('pitrain.png')
im3 = im.copy()

gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),0)
thresh = cv2.adaptiveThreshold(blur,255,1,1,11,2)

#################      Now finding Contours         ###################

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

samples =  np.empty((0,100))
responses = []
keys = [i for i in range(48,58)]

for cnt in contours:
    if cv2.contourArea(cnt)>50:
        [x,y,w,h] = cv2.boundingRect(cnt)

        if  h>28:
            cv2.rectangle(im,(x,y),(x+w,y+h),(0,0,255),2)
            roi = thresh[y:y+h,x:x+w]
            roismall = cv2.resize(roi,(10,10))
            cv2.imshow('norm',im)
            key = cv2.waitKey(0)

            if key == 27:  # (escape to quit)
                sys.exit()
            elif key in keys:
                responses.append(int(chr(key)))
                sample = roismall.reshape((1,100))
                samples = np.append(samples,sample,0)

responses = np.array(responses,np.float32)
responses = responses.reshape((responses.size,1))
print "training complete"

np.savetxt('generalsamples.data',samples)
np.savetxt('generalresponses.data',responses)

Jetzt treten wir in den Schulungs- und Testteil ein.

Zum Testen des Teils habe ich das folgende Bild verwendet, das die gleichen Buchstaben enthält, die ich zum Trainieren verwendet habe.

Geben Sie hier die Bildbeschreibung ein

Für das Training machen wir wie folgt :

  1. Laden Sie die bereits zuvor gespeicherten txt-Dateien
  2. Erstellen Sie eine Instanz des Klassifikators, den wir verwenden (hier ist es KNearest).
  3. Dann verwenden wir die Funktion KNearest.train, um die Daten zu trainieren

Zu Testzwecken gehen wir wie folgt vor:

  1. Wir laden das zum Testen verwendete Bild
  2. Verarbeiten Sie das Bild wie zuvor und extrahieren Sie jede Ziffer mit Konturmethoden
  3. Zeichnen Sie einen Begrenzungsrahmen dafür, ändern Sie die Größe auf 10 x 10 und speichern Sie die Pixelwerte wie zuvor in einem Array.
  4. Dann verwenden wir die Funktion KNearest.find_nearest (), um das Element zu finden, das dem von uns angegebenen am nächsten kommt. (Wenn Sie Glück haben, erkennt es die richtige Ziffer.)

Ich habe die letzten beiden Schritte (Training und Testen) in den folgenden Code aufgenommen:

import cv2
import numpy as np

#######   training part    ############### 
samples = np.loadtxt('generalsamples.data',np.float32)
responses = np.loadtxt('generalresponses.data',np.float32)
responses = responses.reshape((responses.size,1))

model = cv2.KNearest()
model.train(samples,responses)

############################# testing part  #########################

im = cv2.imread('pi.png')
out = np.zeros(im.shape,np.uint8)
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
thresh = cv2.adaptiveThreshold(gray,255,1,1,11,2)

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
    if cv2.contourArea(cnt)>50:
        [x,y,w,h] = cv2.boundingRect(cnt)
        if  h>28:
            cv2.rectangle(im,(x,y),(x+w,y+h),(0,255,0),2)
            roi = thresh[y:y+h,x:x+w]
            roismall = cv2.resize(roi,(10,10))
            roismall = roismall.reshape((1,100))
            roismall = np.float32(roismall)
            retval, results, neigh_resp, dists = model.find_nearest(roismall, k = 1)
            string = str(int((results[0][0])))
            cv2.putText(out,string,(x,y+h),0,1,(0,255,0))

cv2.imshow('im',im)
cv2.imshow('out',out)
cv2.waitKey(0)

Und es hat funktioniert, unten ist das Ergebnis, das ich bekommen habe:

Geben Sie hier die Bildbeschreibung ein


Hier hat es mit 100% Genauigkeit funktioniert. Ich gehe davon aus, dass alle Ziffern von gleicher Art und Größe sind.

Aber auf jeden Fall ist dies ein guter Anfang für Anfänger (ich hoffe es).

Abid Rahman K.
quelle
67
+1 Langer Beitrag, aber sehr lehrreich. Dies sollte zu opencv Tag Info gehen
Karlphillip
12
Falls jemand interessiert ist, habe ich aus diesem Code eine richtige OO-Engine gemacht, zusammen mit ein paar
Schnickschnack
10
Beachten Sie, dass Sie SVM und KNN nicht verwenden müssen, wenn Sie eine genau definierte perfekte Schriftart haben. Beispielsweise bilden die Ziffern 0, 4, 6, 9 eine Gruppe, die Ziffern 1, 2, 3, 5, 7 eine andere und 8 eine andere. Diese Gruppe wird durch die Eulernummer angegeben. Dann hat "0" keine Endpunkte, "4" hat zwei und "6" und "9" werden durch die Schwerpunktposition unterschieden. "3" ist der einzige in der anderen Gruppe mit 3 Endpunkten. "1" und "7" unterscheiden sich durch die Skelettlänge. Wenn man die konvexe Hülle zusammen mit der Ziffer betrachtet, haben "5" und "2" zwei Löcher und können durch den Schwerpunkt des größten Lochs unterschieden werden.
mmgp
4
Habe das Problem .. Danke. Es war ein großartiges Tutorial. Ich habe einen kleinen Fehler gemacht. Wenn jemand anderes das gleiche Problem hat wie ich und @rash, dann liegt das daran, dass Sie die falsche Taste drücken. Für jede Nummer im Feld müssen Sie das Nein eingeben, damit es darauf trainiert wird. Ich hoffe, das hilft.
Shalchi
19
Ein hervorragendes Tutorial. Vielen Dank! Es sind einige Änderungen erforderlich, damit dies mit der neuesten (3.1) Version von OpenCV funktioniert: Konturen, Hierarchie = cv2.findContours (Schwellenwert, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) => _, Konturen, Hierarchie = cv2.findContours (Thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE), model = cv2.KNearest () => model = cv2.ml.KNearest_create (), model.train (Beispiele, Antworten) => model.train (Beispiele, cv2.ml) .ROW_SAMPLE, Antworten), Retval, Ergebnisse, neigh_resp, dists = model.find_nearest (roismall, k = 1) => retval, Ergebnisse, neigh_resp, dists = model.find_nearest (roismall, k = 1)
Johannes Brodwall
53

Für diejenigen, die an C ++ - Code interessiert sind, kann der folgende Code verwendet werden. Danke Abid Rahman für die nette Erklärung.


Das Verfahren ist das gleiche wie oben, jedoch verwendet die Konturfindung nur die Kontur der ersten Hierarchieebene, sodass der Algorithmus nur die äußere Kontur für jede Ziffer verwendet.

Code zum Erstellen von Muster- und Etikettendaten

//Process image to extract contour
Mat thr,gray,con;
Mat src=imread("digit.png",1);
cvtColor(src,gray,CV_BGR2GRAY);
threshold(gray,thr,200,255,THRESH_BINARY_INV); //Threshold to find contour
thr.copyTo(con);

// Create sample and label data
vector< vector <Point> > contours; // Vector for storing contour
vector< Vec4i > hierarchy;
Mat sample;
Mat response_array;  
findContours( con, contours, hierarchy,CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ); //Find contour

for( int i = 0; i< contours.size(); i=hierarchy[i][0] ) // iterate through first hierarchy level contours
{
    Rect r= boundingRect(contours[i]); //Find bounding rect for each contour
    rectangle(src,Point(r.x,r.y), Point(r.x+r.width,r.y+r.height), Scalar(0,0,255),2,8,0);
    Mat ROI = thr(r); //Crop the image
    Mat tmp1, tmp2;
    resize(ROI,tmp1, Size(10,10), 0,0,INTER_LINEAR ); //resize to 10X10
    tmp1.convertTo(tmp2,CV_32FC1); //convert to float
    sample.push_back(tmp2.reshape(1,1)); // Store  sample data
    imshow("src",src);
    int c=waitKey(0); // Read corresponding label for contour from keyoard
    c-=0x30;     // Convert ascii to intiger value
    response_array.push_back(c); // Store label to a mat
    rectangle(src,Point(r.x,r.y), Point(r.x+r.width,r.y+r.height), Scalar(0,255,0),2,8,0);    
}

// Store the data to file
Mat response,tmp;
tmp=response_array.reshape(1,1); //make continuous
tmp.convertTo(response,CV_32FC1); // Convert  to float

FileStorage Data("TrainingData.yml",FileStorage::WRITE); // Store the sample data in a file
Data << "data" << sample;
Data.release();

FileStorage Label("LabelData.yml",FileStorage::WRITE); // Store the label data in a file
Label << "label" << response;
Label.release();
cout<<"Training and Label data created successfully....!! "<<endl;

imshow("src",src);
waitKey();

Code für Training und Test

Mat thr,gray,con;
Mat src=imread("dig.png",1);
cvtColor(src,gray,CV_BGR2GRAY);
threshold(gray,thr,200,255,THRESH_BINARY_INV); // Threshold to create input
thr.copyTo(con);


// Read stored sample and label for training
Mat sample;
Mat response,tmp;
FileStorage Data("TrainingData.yml",FileStorage::READ); // Read traing data to a Mat
Data["data"] >> sample;
Data.release();

FileStorage Label("LabelData.yml",FileStorage::READ); // Read label data to a Mat
Label["label"] >> response;
Label.release();


KNearest knn;
knn.train(sample,response); // Train with sample and responses
cout<<"Training compleated.....!!"<<endl;

vector< vector <Point> > contours; // Vector for storing contour
vector< Vec4i > hierarchy;

//Create input sample by contour finding and cropping
findContours( con, contours, hierarchy,CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
Mat dst(src.rows,src.cols,CV_8UC3,Scalar::all(0));

for( int i = 0; i< contours.size(); i=hierarchy[i][0] ) // iterate through each contour for first hierarchy level .
{
    Rect r= boundingRect(contours[i]);
    Mat ROI = thr(r);
    Mat tmp1, tmp2;
    resize(ROI,tmp1, Size(10,10), 0,0,INTER_LINEAR );
    tmp1.convertTo(tmp2,CV_32FC1);
    float p=knn.find_nearest(tmp2.reshape(1,1), 1);
    char name[4];
    sprintf(name,"%d",(int)p);
    putText( dst,name,Point(r.x,r.y+r.height) ,0,1, Scalar(0, 255, 0), 2, 8 );
}

imshow("src",src);
imshow("dst",dst);
imwrite("dest.jpg",dst);
waitKey();

Ergebnis

Im Ergebnis wird der Punkt in der ersten Zeile als 8 erkannt und wir haben nicht für den Punkt trainiert. Außerdem betrachte ich jede Kontur in der ersten Hierarchieebene als Beispieleingabe. Der Benutzer kann dies vermeiden, indem er den Bereich berechnet.

Ergebnisse

Haris
quelle
1
Ich war es leid, diesen Code auszuführen. Ich konnte Muster- und Etikettendaten erstellen. Aber wenn ich die Test-Trainings-Datei ausführe, läuft sie mit einem Fehler *** stack smashing detected ***:und daher erhalte ich kein endgültiges Bild, wie Sie oben sehen (Ziffern in grüner Farbe)
skm
1
Ich ändere char name[4];Ihren Code in char name[7];und habe den stapelbezogenen Fehler nicht erhalten, aber ich erhalte immer noch nicht die richtigen Ergebnisse. Ich bekomme ein Bild wie hier < i.imgur.com/qRkV2B4.jpg >
skm
@skm Stellen Sie sicher, dass die Anzahl der Konturen mit der Anzahl der Stellen im Bild übereinstimmt. Drucken Sie das Ergebnis auch auf der Konsole aus.
Haris
1
Hallo, könnten wir ein trainiertes Netz laden, um es zu benutzen?
Yode
14

Wenn Sie sich für den Stand der Technik im maschinellen Lernen interessieren, sollten Sie sich mit Deep Learning befassen. Sie sollten eine CUDA haben, die die GPU unterstützt, oder alternativ die GPU in Amazon Web Services verwenden.

Google Udacity hat ein nettes Tutorial dazu mit Tensor Flow . In diesem Tutorial lernen Sie, wie Sie Ihren eigenen Klassifikator mit handgeschriebenen Ziffern trainieren. Ich habe eine Genauigkeit von über 97% für den Testsatz mit Convolutional Networks erhalten.

Yonatan Simson
quelle