OCR per riconoscimento cifre semplici in OpenCV-Python


380

Sto cercando di implementare un "riconoscimento delle cifre OCR" in OpenCV-Python (cv2). È solo a scopo di apprendimento. Vorrei imparare sia le funzionalità KNearest che SVM in OpenCV.

Ho 100 campioni (cioè immagini) di ogni cifra. Vorrei allenarmi con loro.

C'è un esempio letter_recog.pyfornito con l'esempio OpenCV. Ma non riuscivo ancora a capire come usarlo. Non capisco quali sono i campioni, le risposte ecc. Inoltre, all'inizio carica un file txt, che non ho capito prima.

Più tardi cercando un po ', ho potuto trovare un letter_recognition.data negli esempi cpp. L'ho usato e ho creato un codice per cv2.KNearest nel modello di letter_recog.py (solo per i 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()

Mi ha dato un array di dimensioni 20000, non capisco di cosa si tratti.

Domande:

1) Cos'è il file letter_recognition.data? Come creare quel file dal mio set di dati?

2) Cosa results.reval()indica?

3) Come possiamo scrivere un semplice strumento di riconoscimento delle cifre usando il file letter_recognition.data (KNearest o SVM)?

Risposte:


528

Bene, ho deciso di allenarmi sulla mia domanda per risolvere il problema sopra. Quello che volevo era implementare un OCR semplice usando le funzioni KNearest o SVM in OpenCV. E di seguito è quello che ho fatto e come. (è solo per imparare a usare KNearest per semplici scopi OCR).

1) La mia prima domanda riguardava il file letter_recognition.data fornito con gli esempi OpenCV. Volevo sapere cosa c'è dentro quel file.

Contiene una lettera, insieme a 16 caratteristiche di quella lettera.

E this SOFmi ha aiutato a trovarlo. Queste 16 funzioni sono spiegate nel documento Letter Recognition Using Holland-Style Adaptive Classifiers. (Anche se alla fine non ho capito alcune delle funzionalità)

2) Da quando ho saputo, senza capire tutte quelle caratteristiche, è difficile fare quel metodo. Ho provato alcuni altri documenti, ma erano tutti un po 'difficili per un principiante.

So I just decided to take all the pixel values as my features. (Non ero preoccupato per l'accuratezza o le prestazioni, volevo solo che funzionasse, almeno con la minima precisione)

Ho preso l'immagine qui sotto per i miei dati di allenamento:

inserisci qui la descrizione dell'immagine

(So ​​che la quantità di dati di allenamento è inferiore. Ma, dal momento che tutte le lettere hanno lo stesso carattere e le stesse dimensioni, ho deciso di provarlo).

Per preparare i dati per la formazione, ho creato un piccolo codice in OpenCV. Fa le seguenti cose:

  1. Carica l'immagine.
  2. Seleziona le cifre (ovviamente individuando i contorni e applicando vincoli su area e altezza delle lettere per evitare falsi rilevamenti).
  3. Disegna il rettangolo di delimitazione attorno a una lettera e attendi key press manually. Questa volta premiamo noi stessi il tasto numerico corrispondente alla lettera nella casella.
  4. Una volta premuto il tasto numerico corrispondente, ridimensiona questa casella su 10x10 e salva i valori di 100 pixel in un array (qui, i campioni) e la cifra immessa manualmente corrispondente in un altro array (qui, le risposte).
  5. Quindi salvare entrambi gli array in file txt separati.

Alla fine della classificazione manuale delle cifre, tutte le cifre nei dati del treno (train.png) sono etichettate manualmente da noi stessi, l'immagine apparirà come di seguito:

inserisci qui la descrizione dell'immagine

Di seguito è riportato il codice che ho usato per lo scopo sopra (ovviamente, non così pulito):

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)

Ora entriamo nella parte relativa alla formazione e ai test.

Per la parte di test che ho usato sotto l'immagine, che ha lo stesso tipo di lettere che ho usato per addestrare.

inserisci qui la descrizione dell'immagine

Per la formazione facciamo come segue :

  1. Carica i file txt che abbiamo già salvato in precedenza
  2. crea un'istanza di classificatore che stiamo usando (qui, è KNearest)
  3. Quindi usiamo la funzione KNearest.train per addestrare i dati

A scopo di test, facciamo come segue:

  1. Carichiamo l'immagine utilizzata per i test
  2. elaborare l'immagine come prima ed estrarre ogni cifra usando i metodi di contorno
  3. Disegna il riquadro di delimitazione per esso, quindi ridimensionalo a 10x10 e memorizza i suoi valori di pixel in un array come fatto in precedenza.
  4. Quindi usiamo la funzione KNearest.find_nearest () per trovare l'oggetto più vicino a quello che abbiamo dato. (Se fortunato, riconosce la cifra corretta.)

Ho incluso gli ultimi due passaggi (formazione e test) in un unico codice di seguito:

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)

E ha funzionato, di seguito è il risultato che ho ottenuto:

inserisci qui la descrizione dell'immagine


Qui ha funzionato con una precisione del 100%. Suppongo che ciò avvenga perché tutte le cifre sono dello stesso tipo e delle stesse dimensioni.

Ad ogni modo, questo è un buon inizio per i principianti (lo spero).


67
+1 post lungo, ma molto istruttivo. Questo dovrebbe andare alle informazioni sui tag
opencv

12
nel caso qualcuno fosse interessato, ho creato un vero motore OO da questo codice, insieme ad alcune campane e fischietti: github.com/goncalopp/simple-ocr-opencv
goncalopp

10
Si noti che non è necessario utilizzare SVM e KNN quando si dispone di un carattere perfetto ben definito. Ad esempio, le cifre 0, 4, 6, 9 formano un gruppo, le cifre 1, 2, 3, 5, 7 ne formano un'altra e 8 un'altra. Questo gruppo è dato dal numero di eulero. Quindi "0" non ha endpoint, "4" ne ha due e "6" e "9" si distinguono per la posizione del centroide. "3" è l'unico, nell'altro gruppo, con 3 endpoint. "1" e "7" si distinguono per la lunghezza dello scheletro. Quando si considera lo scafo convesso insieme alla cifra, "5" e "2" hanno due fori e possono essere distinti dal centroide del foro più grande.
mmgp,

4
Ho il problema .. Grazie. È stato un ottimo tutorial. Stavo commettendo un piccolo errore. Se qualcun altro deve affrontare lo stesso problema in questo modo come me e @rash, è perché stai premendo il tasto sbagliato. Per ogni numero nella casella, devi inserire quel numero in modo che venga addestrato su di esso. Spero che aiuti.
Shalki,

19
Un tutorial stellare. Grazie! Ci sono alcune modifiche necessarie per farlo funzionare con l'ultimo (3.1) versjon di OpenCV: contorni, gerarchia = cv2.findContours (trebbia, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) => _, contorni, gerarchia = cv2.findContours (trebbia, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE), modello = cv2.KNearest () => modello = cv2.ml.KNearest_create (), model.train (samples, risposte) => model.train (samples, cv2.ml .ROW_SAMPLE, risposte), retval, risultati, neigh_resp, dists = model.find_nearest (roismall, k = 1) => retval, risultati, neigh_resp, dists = model.find_nearest (roismall, k = 1)
Johannes Brodwall

53

Per coloro che sono interessati al codice C ++ possono fare riferimento al codice seguente. Grazie Abid Rahman per la bella spiegazione.


La procedura è la stessa di cui sopra, ma il rilevamento del profilo utilizza solo il contorno del livello della prima gerarchia, quindi l'algoritmo utilizza solo il contorno esterno per ogni cifra.

Codice per la creazione di dati campione ed etichetta

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

Codice per formazione e 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();

Risultato

Nel risultato il punto nella prima riga viene rilevato come 8 e non ci siamo allenati per il punto. Inoltre sto considerando ogni profilo nel primo livello gerarchico come input di esempio, l'utente può evitarlo calcolando l'area.

risultati


1
Sono stanco di eseguire questo codice. Sono stato in grado di creare dati campione ed etichetta. Ma quando eseguo il file di test-training, viene eseguito con un errore *** stack smashing detected ***:e quindi non ottengo un'immagine corretta finale mentre ti trovi sopra (cifre in colore verde)
skm

1
cambio il char name[4];tuo codice in char name[7];e non ho riscontrato l'errore relativo allo stack ma non ottengo ancora i risultati corretti. Ricevo un'immagine come qui < i.imgur.com/qRkV2B4.jpg >
skm

@skm Assicurati di ottenere il numero di contorni uguale al numero di cifre nell'immagine, prova anche a stampare il risultato sulla console.
Haris,

1
Ciao, potremmo caricare una rete addestrata da usare?
yode

14

Se sei interessato allo stato dell'arte dell'apprendimento automatico, dovresti esaminare Deep Learning. È necessario disporre di una CUDA che supporti GPU o in alternativa utilizzare la GPU su Amazon Web Services.

Google Udacity ha un bel tutorial su questo usando Tensor Flow . Questo tutorial ti insegnerà come addestrare il tuo classificatore su cifre scritte a mano. Ho ottenuto un'accuratezza di oltre il 97% sul set di test utilizzando reti convoluzionali.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.