429 Stimmen

Einfache Ziffernerkennung OCR in OpenCV-Python

Ich versuche, eine "Ziffernerkennung OCR" in OpenCV-Python (cv2) zu implementieren. Es ist nur für Lernzwecke. Ich möchte sowohl KNearest- als auch SVM-Funktionen in OpenCV lernen.

Ich habe 100 Beispiele (d.h. Bilder) von jeder Ziffer. Ich möchte mit ihnen trainieren.

Es gibt eine Probe letter_recog.py die mit dem OpenCV-Beispiel geliefert wird. Aber ich konnte immer noch nicht herausfinden, wie man es verwendet. Ich verstehe nicht, was die Proben, Antworten usw. sind. Außerdem wird zunächst eine txt-Datei geladen, was ich zunächst nicht verstanden habe.

Später, als ich ein wenig suchte, konnte ich eine letter_recognition.data in cpp samples 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()

Ich habe ein Array der Größe 20000 erhalten, ich verstehe nicht, was das ist.

Fragen:

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

2) Was bedeutet results.reval() bezeichnen?

3) Wie können wir ein einfaches Ziffernerkennungsprogramm schreiben, das die Datei letter_recognition.data verwendet (entweder KNearest oder SVM)?

607voto

Abid Rahman K Punkte 50197

Nun, ich habe beschlossen, mich mit meiner Frage zu beschäftigen, um das obige Problem zu lösen. Ich wollte eine einfache OCR mit KNearest oder SVM Features in OpenCV implementieren. Und unten ist, was ich tat und wie. (es ist nur zum Lernen, wie man KNearest für einfache OCR-Zwecke verwendet).

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

Er enthält einen Buchstaben und 16 Merkmale dieses Buchstabens.

Und this SOF hat mir geholfen, sie zu finden. Diese 16 Merkmale werden in dem Papier erläutert Letter Recognition Using Holland-Style Adaptive Classifiers . (Obwohl ich einige der Funktionen am Ende nicht verstanden habe)

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

Also habe ich beschlossen, alle Pixelwerte als Merkmale zu verwenden. (Ich war nicht besorgt über die Genauigkeit oder Leistung, ich wollte nur, dass es funktioniert, zumindest mit der geringsten Genauigkeit)

Ich habe das unten stehende Bild für meine Trainingsdaten verwendet:

enter image description here

(Ich weiß, dass die Menge der Trainingsdaten geringer ist. Aber da alle Buchstaben die gleiche Schriftart und -größe haben, habe ich beschlossen, es damit zu versuchen).

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

  1. Es lädt das Bild.
  2. Wählt die Ziffern aus (natürlich durch Kontursuche und Anwendung von Beschränkungen auf Fläche und Höhe der Buchstaben, um falsche Erkennungen zu vermeiden).
  3. Zeichnet das begrenzende Rechteck um einen Buchstaben und wartet auf key press manually . Dieses Mal haben wir drücken Sie selbst die Zifferntaste entsprechend dem Buchstaben im Kästchen.
  4. Sobald die entsprechende Zifferntaste gedrückt wird, wird die Größe dieses Feldes auf 10x10 verkleinert und alle 100 Pixelwerte in einem Array (hier: Muster) und die entsprechende manuell eingegebene Ziffer in einem anderen Array (hier: Antworten) gespeichert.
  5. Dann speichern Sie die beiden Arrays in separaten .txt Dateien.

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

enter image description here

Nachfolgend finden Sie den Code, den ich für den oben genannten 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 kommen wir zum Teil der Ausbildung und Prüfung.

Für den Testteil habe ich das unten stehende Bild verwendet, das dieselbe Art von Buchstaben enthält, die ich in der Trainingsphase verwendet habe.

enter image description here

Für die Ausbildung gehen wir folgendermaßen vor :

  1. Laden Sie die .txt Dateien, die wir bereits zuvor gespeichert haben
  2. eine Instanz des von uns verwendeten Klassifikators erstellen (in diesem Fall KNearest)
  3. Dann verwenden wir die Funktion KNearest.train, um die Daten zu trainieren

Zu Testzwecken gehen wir folgendermaßen vor:

  1. Wir laden das für den Test verwendete Bild
  2. Verarbeitung des Bildes wie zuvor und Extraktion der einzelnen Ziffern mit Hilfe von Konturverfahren
  3. Zeichnen Sie einen Begrenzungsrahmen, ändern Sie die Größe auf 10x10 und speichern Sie die Pixelwerte in einem Array, wie zuvor geschehen.
  4. Dann verwenden wir die Funktion KNearest.find_nearest(), um das Element zu finden, das dem von uns angegebenen am nächsten liegt. (Wenn wir Glück haben, erkennt sie die richtige Ziffer.)

Ich habe die letzten beiden Schritte (Training und Test) in einem einzigen Code unten zusammengefasst:

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 geklappt, unten ist das Ergebnis zu sehen:

enter image description here


Hier funktionierte es mit 100%iger Genauigkeit. Ich nehme an, dass dies daran liegt, dass alle Ziffern von der gleichen Art und Größe sind.

Aber wie auch immer, dies ist ein guter Anfang für Anfänger (hoffe ich).

61voto

Haris Punkte 12882

Diejenigen, die an C++-Code interessiert sind, können sich auf den unten stehenden Code beziehen. Danke Abid Rahman für die nette Erklärung.


Das Verfahren ist dasselbe wie oben, aber bei der Kontursuche wird nur die Kontur der ersten Hierarchieebene verwendet, so dass der Algorithmus nur die äußere Kontur für jede Ziffer verwendet.

Code zur Erstellung 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 Schulung und Prüfung

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 auf den Punkt trainiert. Außerdem betrachte ich jede Kontur in der ersten Hierarchieebene als Mustereingabe, die der Benutzer durch Berechnung der Fläche vermeiden kann.

Results

4voto

Nick_F Punkte 1023

Ich hatte einige Probleme, die Trainingsdaten zu generieren, da es manchmal schwierig war, den letzten ausgewählten Buchstaben zu identifizieren, also habe ich das Bild um 1,5 Grad gedreht. Jetzt wird jedes Zeichen in der richtigen Reihenfolge ausgewählt und der Test zeigt immer noch eine 100%ige Trefferquote nach dem Training. Hier ist der Code:

import numpy as np
import cv2

def rotate_image(image, angle):
  image_center = tuple(np.array(image.shape[1::-1]) / 2)
  rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
  result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
  return result

img = cv2.imread('training_image.png')
cv2.imshow('orig image', img)
whiteBorder = [255,255,255]
# extend the image border
image1 = cv2.copyMakeBorder(img, 80, 80, 80, 80, cv2.BORDER_CONSTANT, None, whiteBorder)
# rotate the image 1.5 degrees clockwise for ease of data entry
image_rot = rotate_image(image1, -1.5)
#crop_img = image_rot[y:y+h, x:x+w]
cropped = image_rot[70:350, 70:710]
cv2.imwrite('rotated.png', cropped)
cv2.imshow('rotated image', cropped)
cv2.waitKey(0)

Für die Beispieldaten habe ich einige Änderungen am Skript vorgenommen, etwa so:

import sys
import numpy as np
import cv2

def sort_contours(contours, x_axis_sort='LEFT_TO_RIGHT', y_axis_sort='TOP_TO_BOTTOM'):
    # initialize the reverse flag
    x_reverse = False
    y_reverse = False
    if x_axis_sort == 'RIGHT_TO_LEFT':
        x_reverse = True
    if y_axis_sort == 'BOTTOM_TO_TOP':
        y_reverse = True

    boundingBoxes = [cv2.boundingRect(c) for c in contours]

    # sorting on x-axis 
    sortedByX = zip(*sorted(zip(contours, boundingBoxes),
    key=lambda b:b[1][0], reverse=x_reverse))

    # sorting on y-axis 
    (contours, boundingBoxes) = zip(*sorted(zip(*sortedByX),
    key=lambda b:b[1][1], reverse=y_reverse))
    # return the list of sorted contours and bounding boxes
    return (contours, boundingBoxes)

im = cv2.imread('rotated.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)

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
contours, boundingBoxes = sort_contours(contours, x_axis_sort='LEFT_TO_RIGHT', y_axis_sort='TOP_TO_BOTTOM')

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 and h < 40:
            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.ubyte)
responses = responses.reshape((responses.size,1))
print("training complete")

np.savetxt('generalsamples.data',samples,fmt='%i')
np.savetxt('generalresponses.data',responses,fmt='%i')

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X