OCR de reconnaissance de chiffres simple dans OpenCV-Python

380

J'essaie d'implémenter un "OCR de reconnaissance de chiffres" dans OpenCV-Python (cv2). C'est juste à des fins d'apprentissage. Je voudrais apprendre les fonctionnalités KNearest et SVM dans OpenCV.

J'ai 100 échantillons (c'est-à-dire des images) de chaque chiffre. Je voudrais m'entraîner avec eux.

Un exemple letter_recog.pyest fourni avec l'exemple OpenCV. Mais je n'arrivais toujours pas à comprendre comment l'utiliser. Je ne comprends pas quels sont les échantillons, les réponses, etc. De plus, il charge un fichier txt au début, ce que je n'ai pas compris en premier.

Plus tard, en cherchant un peu, j'ai pu trouver un letter_recognition.data dans les échantillons cpp. Je l'ai utilisé et créé un code pour cv2.KNearest dans le modèle de letter_recog.py (juste pour les tests):

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

Il m'a donné un tableau de taille 20000, je ne comprends pas ce que c'est.

Des questions:

1) Qu'est-ce que le fichier letter_recognition.data? Comment créer ce fichier à partir de mon propre ensemble de données?

2) Qu'est - ce que results.reval()représentent chacun?

3) Comment pouvons-nous écrire un outil de reconnaissance de chiffres simple en utilisant le fichier letter_recognition.data (KNearest ou SVM)?

Abid Rahman K
la source

Réponses:

528

Eh bien, j'ai décidé de m'entraîner sur ma question pour résoudre le problème ci-dessus. Ce que je voulais, c'était implémenter une OCR simple en utilisant les fonctionnalités KNearest ou SVM dans OpenCV. Et voici ce que j'ai fait et comment. (c'est juste pour apprendre à utiliser KNearest à des fins d'OCR simples).

1) Ma première question concernait le fichier letter_recognition.data fourni avec les échantillons OpenCV. Je voulais savoir ce qu'il y a dans ce fichier.

Il contient une lettre, ainsi que 16 caractéristiques de cette lettre.

Et this SOFm'a aidé à le trouver. Ces 16 caractéristiques sont expliquées dans le document Letter Recognition Using Holland-Style Adaptive Classifiers. (Bien que je n'aie pas compris certaines fonctionnalités à la fin)

2) Comme je le savais, sans comprendre toutes ces fonctionnalités, il est difficile de faire cette méthode. J'ai essayé d'autres papiers, mais tous étaient un peu difficiles pour un débutant.

So I just decided to take all the pixel values as my features. (Je ne m'inquiétais pas de la précision ou des performances, je voulais juste que cela fonctionne, au moins avec le moins de précision)

J'ai pris l'image ci-dessous pour mes données d'entraînement:

entrez la description de l'image ici

(Je sais que la quantité de données d'entraînement est inférieure. Mais, comme toutes les lettres ont la même police et la même taille, j'ai décidé d'essayer).

Pour préparer les données pour la formation, j'ai fait un petit code dans OpenCV. Il fait les choses suivantes:

  1. Il charge l'image.
  2. Sélectionne les chiffres (évidemment en trouvant les contours et en appliquant des contraintes sur la surface et la hauteur des lettres pour éviter les fausses détections).
  3. Dessine le rectangle englobant autour d'une lettre et attend key press manually. Cette fois, nous appuyons nous-mêmes sur la touche numérique correspondant à la lettre en boîte.
  4. Une fois que la touche numérique correspondante est enfoncée, elle redimensionne cette case à 10x10 et enregistre les valeurs de 100 pixels dans un tableau (ici, les échantillons) et le chiffre correspondant saisi manuellement dans un autre tableau (ici, les réponses).
  5. Enregistrez ensuite les deux tableaux dans des fichiers txt séparés.

À la fin du classement manuel des chiffres, tous les chiffres dans les données du train (train.png) sont étiquetés manuellement par nous-mêmes, l'image ressemblera à ci-dessous:

entrez la description de l'image ici

Ci-dessous est le code que j'ai utilisé pour le but ci-dessus (bien sûr, pas si propre):

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)

Maintenant, nous entrons dans la partie formation et test.

Pour tester la partie, j'ai utilisé l'image ci-dessous, qui a le même type de lettres que j'ai utilisé pour former.

entrez la description de l'image ici

Pour la formation, nous procédons comme suit :

  1. Chargez les fichiers txt que nous avons déjà enregistrés plus tôt
  2. créer une instance de classificateur que nous utilisons (ici, c'est KNearest)
  3. Ensuite, nous utilisons la fonction KNearest.train pour former les données

À des fins de test, nous procédons comme suit:

  1. Nous chargeons l'image utilisée pour les tests
  2. traiter l'image comme précédemment et extraire chaque chiffre en utilisant des méthodes de contour
  3. Dessinez un cadre de délimitation pour lui, puis redimensionnez-le à 10 x 10 et stockez ses valeurs de pixels dans un tableau comme précédemment.
  4. Ensuite, nous utilisons la fonction KNearest.find_nearest () pour trouver l'élément le plus proche de celui que nous avons donné. (Si chanceux, il reconnaît le bon chiffre.)

J'ai inclus les deux dernières étapes (formation et tests) dans un seul code ci-dessous:

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)

Et cela a fonctionné, voici le résultat que j'ai obtenu:

entrez la description de l'image ici


Ici, cela a fonctionné avec une précision de 100%. Je suppose que c'est parce que tous les chiffres sont de même nature et de même taille.

Mais de toute façon, c'est un bon début pour les débutants (je l'espère).

Abid Rahman K
la source
67
+1 Long message, mais très éducatif. Cela devrait aller à info balise
opencv
12
au cas où quelqu'un serait
goncalopp
10
Notez qu'il n'est pas nécessaire d'utiliser SVM et KNN lorsque vous avez une police parfaite bien définie. Par exemple, les chiffres 0, 4, 6, 9 forment un groupe, les chiffres 1, 2, 3, 5, 7 en forment un autre et 8 un autre. Ce groupe est donné par le numéro euler. "0" n'a pas de point final, "4" en a deux, et "6" et "9" se distinguent par la position du centroïde. "3" est le seul, dans l'autre groupe, à 3 points de terminaison. "1" et "7" se distinguent par la longueur du squelette. Lorsque l'on considère la coque convexe avec le chiffre, "5" et "2" ont deux trous et ils peuvent être distingués par le centre de gravité du plus grand trou.
mmgp
4
Vous avez le problème .. Merci. Ce fut un excellent tutoriel. Je faisais une petite erreur. Si quelqu'un d'autre est confronté au même problème, comme moi et @rash, c'est parce que vous appuyez sur une mauvaise touche. Pour chaque nombre dans la case, vous devez entrer ce non afin qu'il soit formé dessus. J'espère que cela pourra aider.
shalki
19
Un tutoriel stellaire. Je vous remercie! Quelques modifications sont nécessaires pour que cela fonctionne avec la dernière version (3.1) d'OpenCV: contours, hierarchy = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) => _, contours, hierarchy = cv2.findContours (thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE), model = cv2.KNearest () => model = cv2.ml.KNearest_create (), model.train (échantillons, réponses) => model.train (échantillons, cv2.ml .ROW_SAMPLE, réponses), retval, résultats, neigh_resp, dists = model.find_nearest (roismall, k = 1) => retval, results, neigh_resp, dists = model.find_nearest (roismall, k = 1)
Johannes Brodwall
53

Pour ceux qui s'intéressent au code C ++, reportez-vous au code ci-dessous. Merci Abid Rahman pour la belle explication.


La procédure est la même que ci-dessus, mais la recherche de contour utilise uniquement le contour de premier niveau de hiérarchie, de sorte que l'algorithme utilise uniquement le contour externe pour chaque chiffre.

Code pour créer des exemples et des données d'étiquette

//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 pour la formation et les tests

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();

Résultat

Dans le résultat, le point dans la première ligne est détecté comme 8 et nous n'avons pas été formés pour le point. Je considère également chaque contour du premier niveau de hiérarchie comme entrée d'échantillon, l'utilisateur peut l'éviter en calculant la zone.

Résultats

Haris
la source
1
Je suis fatigué d'exécuter ce code. J'ai pu créer des échantillons et des étiquettes de données. Mais lorsque j'exécute le fichier de test de formation, il s'exécute avec une erreur *** stack smashing detected ***:et, par conséquent, je n'obtiens pas une image correcte finale comme vous le faites ci-dessus (chiffres en couleur verte)
skm
1
j'ai changé char name[4];votre code en char name[7];et je n'ai pas eu l'erreur liée à la pile mais je n'obtiens toujours pas les résultats corrects. Je reçois une image comme ici < i.imgur.com/qRkV2B4.jpg >
skm
@skm Assurez-vous que le nombre de contours est identique au nombre de chiffres dans l'image, essayez également d'imprimer le résultat sur la console.
Haris
1
Bonjour, pourrions-nous charger un filet formé à utiliser?
il y a
14

Si vous êtes intéressé par l'état de l'art du Machine Learning, vous devriez vous pencher sur le Deep Learning. Vous devez disposer d'un GPU prenant en charge CUDA ou utiliser le GPU sur Amazon Web Services.

Google Udacity a un joli tutoriel à ce sujet en utilisant Tensor Flow . Ce tutoriel vous apprendra à former votre propre classificateur sur des chiffres écrits à la main. J'ai obtenu une précision de plus de 97% sur l'ensemble de test en utilisant les réseaux convolutionnels.

Yonatan Simson
la source