web-dev-qa-db-fra.com

OCR de reconnaissance de chiffres simple dans OpenCV-Python

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

J'ai 100 échantillons (images) de chaque chiffre. Je voudrais m'entraîner avec eux.

Il existe un exemple letter_recog.py fourni avec un exemple OpenCV. Mais je ne comprenais toujours pas comment l'utiliser. Je ne comprends pas quels sont les exemples, les réponses, etc. De plus, il charge un fichier txt au début, ce que je ne comprenais pas d'abord.

Plus tard, en cherchant un peu, je pourrais trouver un letter_recognition.data dans des exemples cpp. Je l'ai utilisé et fait un code pour cv2.KNearest dans le modèle de letter_recog.py (juste pour le test):

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

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

Questions:

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

2) Que signifie results.reval()?

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

365
Abid Rahman K

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

1) Ma première question portait sur le fichier letter_recognition.data fourni avec les exemples 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 SOF m'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 des fonctionnalités à la fin)

2) Depuis que je 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 l'exactitude ni des performances, je voulais juste que cela fonctionne, du moins avec le moins de précision possible)

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

enter image description here

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

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

  1. Il charge l'image.
  2. Sélectionne les chiffres (évidemment en recherchant 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 de délimitation autour d’une lettre et attendez _key press manually_. Cette fois, nous appuyons nous-mêmes sur la touche numérique correspondant à la lettre dans la case.
  4. Une fois que la touche de chiffre correspondante a été enfoncée, cette case est redimensionnée à 10x10 et enregistre 100 valeurs de pixels dans un tableau (ici, échantillons) et le chiffre correspondant entré manuellement dans un autre tableau (ici, les réponses).
  5. Enregistrez ensuite les deux tableaux dans des fichiers txt distincts.

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

enter image description here

Ci-dessous, 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)
_

Nous entrons maintenant dans la partie formation et test.

Pour tester la partie que j'ai utilisée ci-dessous image, qui a le même type de lettres que je formais auparavant.

enter image description here

Pour l'entraînement nous faisons comme suit :

  1. Chargez les fichiers txt que nous avons déjà enregistrés
  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 auparavant et extraire chaque chiffre à l'aide de méthodes de contour
  3. Dessinez un cadre, puis redimensionnez à 10x10 et stockez ses valeurs en pixels dans un tableau, comme précédemment.
  4. Ensuite, nous utilisons la fonction KNearest.find_nearest () pour trouver l’article le plus proche de celui que nous avons donné. (Si vous avez de la chance, il reconnaît le chiffre correct.)

J'ai inclus les deux dernières étapes (formation et test) dans un code unique 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:

enter image description here


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

Quoi qu’il en soit, c’est un bon début pour les débutants (je l’espère).

502
Abid Rahman K

Pour ceux qui s'intéressent au code C++, vous pouvez vous référer au code ci-dessous. Merci Abid Rahman pour l'explication de Nice.


La procédure est identique à celle décrite ci-dessus, mais la recherche de contour utilise uniquement le premier contour de niveau hiérarchique, de sorte que l'algorithme utilise uniquement le contour externe pour chaque chiffre.

Code de création des données d'échantillon et 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 ne sommes pas formés pour le point. De plus, je considère chaque contour du premier niveau de hiérarchie comme exemple d’entrée, l’utilisateur peut l’éviter en calculant la surface.

Results

48
Haris

Si vous êtes intéressé par l'état de l'art de l'apprentissage automatique, vous devriez vous pencher sur l'apprentissage en profondeur. Vous devez avoir un GPU prenant en charge CUDA ou utiliser le GPU sur Amazon Web Services.

Google Udacity propose un didacticiel de Nice sur l’utilisation de Tensor Flow . Ce tutoriel vous apprendra comment 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 utilisant des réseaux de convolution.

11
Yonatan Simson