python - opencv number recognition
Reconocimiento simple de dígitos OCR en OpenCV-Python (3)
Estoy intentando implementar un "Reconocimiento de dígitos OCR" en OpenCV-Python (cv2). Es sólo para fines de aprendizaje. Me gustaría aprender las funciones de KNearest y SVM en OpenCV.
Tengo 100 muestras (es decir, imágenes) de cada dígito. Me gustaría entrenar con ellos.
Hay un ejemplo de letter_recog.py
que viene con OpenCV. Pero todavía no pude averiguar cómo usarlo. No entiendo cuáles son las muestras, respuestas, etc. Además, al principio carga un archivo txt, que no entendí primero.
Más tarde, buscando un poco, pude encontrar un letter_recognition.data en muestras de cpp. Lo utilicé e hice un código para cv2.KNearest en el modelo de letter_recog.py (solo para pruebas):
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 variedad de tamaño 20000, no entiendo lo que es.
Preguntas:
1) ¿Qué es el archivo letter_recognition.data? ¿Cómo construir ese archivo desde mi propio conjunto de datos?
2) ¿Qué denota results.reval()
?
3) ¿Cómo podemos escribir una herramienta simple de reconocimiento de dígitos usando el archivo letter_recognition.data (ya sea KNearest o SVM)?
Bueno, decidí entrenarme en mi pregunta para resolver el problema anterior. Lo que quería era implementar un OCR simple con las funciones KNearest o SVM en OpenCV. Y abajo está lo que hice y cómo. (es solo para aprender a usar KNearest con propósitos simples de OCR).
1) Mi primera pregunta fue sobre el archivo letter_recognition.data que viene con las muestras de OpenCV. Quería saber qué hay dentro de ese archivo.
Contiene una carta, junto con 16 características de esa carta.
Y this SOF
me ayudó a encontrarlo. Estas 16 características se explican en el documento Letter 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. Intenté algunos otros papeles, pero todos fueron 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 la menor precisión)
Tomé la imagen de abajo para mis datos de entrenamiento:
(Sé que la cantidad de datos de entrenamiento es menor. Pero, dado 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, mediante la búsqueda de contornos y la aplicación de 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 que se key press manually
. Esta vez presionamos la tecla de dígitos correspondiente a la letra en el cuadro.
D) Una vez que se presiona la tecla de dígitos correspondiente, cambia el tamaño de este cuadro a 10x10 y guarda valores de 100 píxeles en una matriz (aquí, muestras) y el correspondiente dígito ingresado manualmente en otra matriz (aquí, respuestas).
E) Luego guarde ambas matrices en archivos txt separados.
Al final de la clasificación manual de dígitos, todos los dígitos en los datos del tren (train.png) están etiquetados manualmente por nosotros mismos, la imagen se verá a continuación:
A continuación se muestra el código que usé 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 pruebas.
Para la parte de prueba que usé debajo de la imagen, que tiene el mismo tipo de letras que entrené.
Para el entrenamiento hacemos lo siguiente :
A) Cargar los archivos txt que ya guardamos anteriormente.
B) crear una instancia de clasificador que estamos usando (aquí, es KNearest)
C) Luego usamos la función KNearest.train para entrenar los datos
Para propósitos de prueba, hacemos lo siguiente:
A) Cargamos la imagen utilizada para la prueba.
B) Procese la imagen como antes y extraiga cada dígito utilizando métodos de contorno.
C) Dibuje un cuadro delimitador para él, luego cambie su tamaño a 10x10 y almacene sus valores de píxeles en una matriz como se hizo anteriormente.
D) Luego usamos la función KNearest.find_nearest () 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 el siguiente código:
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ó, abajo está el resultado que obtuve:
Aquí funcionó con 100% de precisión. Supongo que esto se debe a que todos los dígitos son del mismo tipo y tamaño.
Pero de todos modos, este es un buen comienzo para los principiantes (espero que sí).
Para aquellos que estén interesados en el código C ++ pueden consultar el siguiente código. Gracias Abid Rahman por la bonita explicación.
El procedimiento es el mismo que el anterior, pero la búsqueda de contorno usa solo el primer nivel de jerarquía, de modo que el algoritmo usa solo el contorno exterior para cada dígito.
Código para crear muestras y datos de 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 de 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 el punto. También estoy considerando cada contorno en el primer nivel jerárquico como la entrada de muestra, el usuario puede evitarlo calculando el área.
Si está interesado en el estado del arte en Aprendizaje automático, debe buscar en Aprendizaje profundo. Debe tener una CUDA que admita GPU o, alternativamente, utilizar la GPU en los servicios web de Amazon.
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. Obtuve una precisión de más del 97% en el conjunto de pruebas mediante redes convolucionales.