OCR de Reconocimiento de Dígitos Simple en OpenCV-Python


Estoy tratando de implementar un "OCR de Reconocimiento de dígitos" en OpenCV-Python (cv2). Es solo para propósitos de aprendizaje. Me gustaría aprender las funciones KNearest y SVM en OpenCV.

Tengo 100 muestras (es decir, imágenes) de cada dígito. Me gustaría entrenar con ellos.

Hay una muestra letter_recog.py que viene con la muestra OpenCV. Pero todavía no podía averiguar cómo usarlo. No entiendo cuáles son las muestras, respuestas, etc. Además, carga un archivo txt al principio, lo cual no hice entiende primero.

Más tarde buscando un poco, pude encontrar un letter_recognition.datos en muestras de cpp. Lo usé e hice un código para cv2.KNearest en el modelo de letter_recog.py (solo para probar):

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

Me dio una matriz de tamaño 20000, no entiendo lo que es.

Preguntas:

1) Qué es letter_recognition.archivo de datos? ¿Cómo construir ese archivo a partir de mi propio conjunto de datos?

2) ¿Qué denota results.reval()?

3) Cómo podemos escribir un herramienta simple de reconocimiento de dígitos usando letter_recognition.archivo de datos (ya sea KNearest o SVM)?

Author: Ultraviolet, 2012-02-23

3 answers

Bueno, decidí entrenarme en mi pregunta para resolver el problema anterior. Lo que quería era implementar un OCR simpl usando funciones KNearest o SVM en OpenCV. Y a continuación está lo que hice y cómo. ( es solo para aprender a usar KNearest para propósitos simples de OCR).

1) Mi primera pregunta fue sobre letter_recognition.archivo de datos que viene con muestras de OpenCV. Quería saber qué hay dentro de ese archivo.

Contiene una letra, junto con 16 características de esa carta.

Y this SOF me ayudó a encontrarlo. Estos 16 características se explican en el papelLetter Recognition Using Holland-Style Adaptive Classifiers. (Aunque no entendí algunas de las características al final)

2) Como sabía, sin entender todas esas características, es difícil hacer ese método. Probé algunos otros papeles, pero todos eran un poco difíciles para un principiante.

So I just decided to take all the pixel values as my features. (No estaba preocupado por la precisión o el rendimiento, solo quería que funcionara, al menos con el menor precisión)

Tomé la siguiente imagen para mis datos de entrenamiento:

introduzca la descripción de la imagen aquí

(Sé que la cantidad de datos de entrenamiento es menor. Pero, ya que todas las letras son de la misma fuente y tamaño, decidí probar esto).

Para preparar los datos para el entrenamiento, hice un pequeño código en OpenCV. Hace las siguientes cosas:

A) Carga la imagen.

B) Selecciona los dígitos (obviamente encontrando el contorno y aplicando restricciones en el área y la altura de las letras para evitar falsas detecciones).

C) Dibuja el rectángulo delimitador alrededor de una letra y espera a key press manually. Esta vez presionamos la tecla de dígito nosotros mismos correspondiente a la letra en el cuadro.

D) Una vez que se presiona la tecla de dígito correspondiente, cambia el tamaño de esta caja a 10x10 y guarda los valores de 100 píxeles en una matriz (aquí, muestras) y el dígito ingresado manualmente correspondiente en otra matriz(aquí, respuestas).

E) Luego guarde ambas matrices en archivos txt separados.

Al final de clasificación manual de dígitos, todos los dígitos en los datos del tren (train.png) son etiquetados manualmente por nosotros mismos, la imagen se verá como a continuación:

introduzca la descripción de la imagen aquí

A continuación está el código que utilicé para el propósito anterior (por supuesto, no tan limpio):

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)

Ahora entramos en la parte de entrenamiento y prueba.

Para probar la parte I se usa la siguiente imagen, que tiene el mismo tipo de letras que solía entrenar.

introduzca la descripción de la imagen aquí

Para la formación hacemos como sigue :

A) Cargar los archivos txt que ya guardamos anteriormente

B) crear una instancia de clasificador que estamos utilizando (aquí, es KNearest)

C) Luego usamos KNearest.función de tren para entrenar los datos

Para fines de prueba, hacemos lo siguiente:

A) Cargamos la imagen utilizada para probar

B) procesar la imagen como antes y extraer cada dígito usando métodos de contorno

C) Dibuje un cuadro delimitador para él, luego cambie el tamaño a 10x10 y almacene su valores de píxeles en una matriz como se hizo anteriormente.

D) Luego usamos KNearest.find_nearest () función para encontrar el elemento más cercano al que le dimos. (Si tiene suerte, reconoce el dígito correcto.)

Incluí los dos últimos pasos (entrenamiento y pruebas) en un solo código a continuación:

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)

Y funcionó, a continuación está el resultado que obtuve:

introduzca la descripción de la imagen aquí


Aquí funcionó con 100% de precisión. Asumo que esto es porque todos los dígitos son del mismo tipo y del mismo tamaño.

Pero cualquier camino, este es un buen comienzo para ir para los principiantes (eso espero).

 455
Author: Abid Rahman K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:55:11

Para aquellos que estén interesados en el código C++, pueden consultar el código a continuación. Gracias Abid Rahman por la agradable explicación.


El procedimiento es el mismo que el anterior, pero, la búsqueda de contorno utiliza solo el primer contorno de nivel de jerarquía, de modo que el algoritmo utiliza solo el contorno exterior para cada dígito.

Código para crear datos de muestras y etiquetas

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

Código para entrenamiento y pruebas

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

Resultado

En el resultado el punto en la primera línea se detecta como 8 y no hemos entrenado para dot. También estoy considerando cada contorno en el primer nivel de jerarquía como la entrada de muestra, el usuario puede evitarlo computando el área.

Resultado

 45
Author: Haris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-28 16:16:33

Si está interesado en el estado del arte en el Aprendizaje automático, debe buscar en el Aprendizaje profundo. Debe tener una GPU compatible con CUDA o, alternativamente, usar la GPU en Amazon Web Services.

Google Udacity tiene un buen tutorial sobre esto usando Tensor Flow. Este tutorial le enseñará cómo entrenar su propio clasificador en dígitos escritos a mano. Tengo una precisión de más del 97% en el conjunto de pruebas usando Redes Convolucionales.

 10
Author: Yonatan Simson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-13 11:03:56