Descarga ProyectoIntegrador_Moreno

PROYECTO INTEGRADOR DE
VISIÓN ARTIFICIAL-COMPLEMENTOS DE
INFORMÁTICA
SISTEMA DE VISIÓN PARA EL RECONOCIMINETO DE
MATRICULAS
ALUMNO: MORENO LUCAS FEDERICO
REG: 22589
AÑO:2013
Introducción
El presente trabajo usa las potencialidades del lenguaje de programación de alto nivel C++ y las
librerías de OpenCV (para el tratamiento de imágenes), para reconocer matriculas de vehículos a
partir de fotografías debidamente tomadas, seguido luego de un sistema de reconocimiento óptico
de caracteres (OCR) para reconocer el dominio de la matriucla.
Este programa es capaz de reconocer patentes de automóviles, mediante la búsqueda de contornos
del algoritmo de Canny, junto con otras herramientas presentes en las librerías de OpenCV. Por ende
puede ser implementado sobre cualquier tipo de patente, sin embargo aquí el desarrollo está
realizado sobre patentes argentinas.
Para el reconocimiento de caracteres se ha utilizado el software “tesseract”, desarrollado por
Hewlett Packard en 1995. Actualmente continúa su desarrollo Google y lo distribuye bajo licencia
Apache.
Organización del programa
El programa consta de dos clases, una de ellas realiza el pre-procesamiento de la imagen de entrada,
llamada “PreProcess” y la clase “Regions” tiene todos los métodos necesarios para trabajar con
contornos.
La interface de la clase “PreProcess” está organizada en:
El constructor de esta clase está declarado como:
PreProcess::PreProcess(const cv::Mat img_input, double canyThresh ,cv::Size blurSize, Size
resize)
,donde “Mat img_input” representa la imagen de entrada ingresada en línea de comandos, “double
canyThresh” es el valor de histéresis inferior utilizado por el algoritmo de Canny, “cv::Size
blurSize” indica el tamaño de la ventana, y por último “resize” indica el tamaño a la cual se
redimensionará la imagen de entrada.
Esta clase no tiene un constructor sin parámetros, dado que los atributos que son creados con la
llamada al constructor, son utilzados frozosamente en el resto del programa.
Los métodos Show(), y Write(), permiten mostrar y guardar las imágenes pre-procesadas.
La interface de la clase “Regions” está organizada en :
Donde se dispone solo de un constructor sin parámetros de la forma Regions::Regions()
Desarrollo del programa
El programa puede ser subdividido en tres etapas, las cuales permitirán obtener los caracteres de
una patente válida.
La primera etapa, se encarga de realizar un tratamiento de imagen, lo cual permite obtener un mejor
gradiente de bordes, necesario para potenciar la capacidad de búsqueda de bordes en la etapa
siguiente, tal como se muestra en figura 1.
La segunda etapa consiste en la búsqueda de contornos mediante el algoritmo de Canny, estos
contornos luego serán guardados en una clase vector, que serán tratados con posterioridad para su
clasificación según las dimensiones de una patente prototipo asignada en el programa.
La última etapa, se encarga de obtener la imagen a la cual pertenece dicho contorno o bien aquella
que está contenida en este, para luego ser clasificada en una imagen de patente valida o no, previo a
un entrenamiento realizado con anterioridad.
Lo anteriormente expuesto, se encuentra esquematizado de la siguiente manera.
Figura 1
1ra Etapa
Como se mencionó anteriormente, la primera etapa del programa es la encargada del procesamiento
de la imagen de entrada, la cual a su vez esta subdividida en:
Figura 2
A) Adquisición de imagen:
En este paso se ha utilizado la función cv::imread(argv[1]) ,donde argv[1] es una cadena de
caracteres e indica la ruta de archivo de la imagen del automóvil.
Para esto en el programa principal se ha usado:
//
//Open file
if (!argv[1])
{
return 1;
}
else
{
try
{
img=cv::imread(argv[1]);
//imshow("img",img);
}
catch(...)
{
cout<<"Existió un error durante la apertura del archivo"<<endl;
}
}
PreProcess imgPrep(img);
Se utiliza la sentencia Try en este caso para capturar y advertir cualquier error que pudiese ocurrir
durante la apertura de la imagen.
Las imágenes en este caso son fotografías de automóviles obtenidas de la red, algunos ejemplos de
ellas son:
Figura 3
B) Conversión de escala de grises:
En primer lugar la imagen es convertida a escala de grises dentro del constructor “PreProcess” de la
siguiente manera;
cv::cvtColor(img_resize,img_gray,CV_RGB2GRAY)
,donde el último parámetro indica el tipo de conversión a realizar, e ”img_gray” es la imagen de
entrada convertida en grises.
En el resto del programa se trabaja con la imagen tratada en escala de grises, dado que implica una
menor cantidad de cálculo, y además reduce la complejidad del programa.
C) Ecualización de la imagen:
Debido a los distintos niveles de intensidad presentes en una imagen, se ecualiza su histograma, en
este caso sobre “img_gray” (obtenida del paso anterior) para esto se utiliza la función;
cv::equalizeHist(img_gray,img_equ)
, donde “img_equ”, es la imagen de destino.
D) Filtro de Gauss:
En este último paso se ha realizado un “Gaussian Filter”, para reducir elevados gradientes de
intensidad, en otras palabras se reduce el ruido presente en la imagen.
Para esto utilizamos la función;
cv::GaussianBlur(img_equ,img_blur,blurSize,0,0,1)
, donde “img_blur” es la imagen filtrada, “blurSize” indica el tamaño de Kernel o ventana, donde se
utiliza una ventana de “3x3”, tamaño obtenido mediante prueba y error.
2da Etapa
Aquí se busca encontrar todos los contornos presentes en la imagen, que luego permitirá encontrar
la patente sobre la imagen.
Figura 4
A) Búsqueda de contornos.
En este apartado y como última función utilizada dentro del constructor “PreProcess”, se utiliza la
búsqueda de contornos con el algoritmo de Canny, utilizando la función;
cv::Canny(img_equ, img_canny, canyThresh, canyThresh*5, 3 )
, donde “img_canny” es la imagen de salida, canyThresh es el valor de histéresis inferior utilizado
para el cálculo del algoritmo, el cual es proporcionado por el constructor como parámetro, y el
tercero y último parámetro indican el límite superior de histéresis y el tamaño de la venta del
operador de Sobel respectivamente.
Estos parámetros se obtuvieron mediante prueba y error con distintos tipos de imágenes.
B) Clasificación
El arreglo de contornos es obtenido en el programa principal “main()”, utilizando la función
cv::findContours(imgPrep.img_canny, contours, hierarchy ,CV_RETR_LIST,
CV_CHAIN_APPROX_SIMPLE, Point(0, 0) ),
,donde “imgPrep.img_canny” es la imagen de bordes contenida en el objeto “imgPrep” creado con
anterioridad, “contours” es un arreglo de arrelgos de la clase vector que almacenan datos del tipo
cv::Point, donde quedarán almacenadas las coordenas “x” e “y” del los puntos que constituyen el
contorno, “hierarchy” es un arreglo de vectores de 4 dimensiones enteros, que en el presente
programa no se utiliza, “CV_RETR_LIST” es una constante entera e indica que todos los contornos
son retornados sin alguna relación con el arreglo hierarchy, “CV_CHAIN_APPROX_NONE” le
indica a la función que debe almacenar todos los puntos “cv::Points”, del contorno y por ultimo
“Point(0, 0)”, indica que no se ha considerado en este caso offset alguno para los puntos del
contorno.
Una vez obtenidos los contornos se procede a clasificarlos, para esto se realiza en el programa
principal lo siguiente;
for( int i=0; i<(int) contours.size(); i++ )
{
RotatedRect rotRect= minAreaRect(contours[i]);
Regions reg;
if(reg.Check(rotRect,i))
{
vrotRect.push_back(rotRect);
cRegions.push_back(reg);
}
}
Es decir, mediante este bucle intentamos aquí chequear, o bien verificar (con dimensiones preestablecidas en el programa) los tamaños de los contornos obtenidos en la etapa anterior. Aquí
“vrotRect” es un arreglo de objetos de la clase “RotatedRect” que almacena las dimensiones de los
contornos, y por ultimo “cRegions” es un arreglo de clase “Regions” que luego permitirá trabajar
con cada contorno por separado.
Para la clasificación en sí, se llama la función Check(rotRect,i) del objeto reg de la clase “Regions”,
el cual utiliza como parámetro el tamaño mínimo posible de un rectángulo, que encierra al contorno
dado, lo cual se realiza mediante la función “cv::minAreaRect” que devuelve un objeto de la clase
RotatedRect.
En esta función check(), la clasificación se realiza de la forma;
if ( rotRect.size.height < _teoricHeight*_factorPlate
>_teoricHeight/_factorPlate)
&& rotRect.size.height
if (rotRect.size.width < _teoricWidth*_factorPlate
>_teoricWidth/_factorPlate)
&&
rotRect.size.width
check=true;
donde “_teoricHeight”,” “_teoricWidth”,”_ factorPlate”, son atributos privados de la clase
“Regions”, definidos por defecto cuando se llama al constructor de la clase y son dimensiones
estimativas de una matrícula prototipo sobre la imagen del automóvil en un caso ideal, y
“_factorPlate”, es un factor de tamaño para considerar un amplio rango de posibles contornos.
Estos parámetros son inicializados en la llamada al constructor como:
Regions::Regions()
{
_teoricHeight=90;
_teoricWidth=60;
_factorPlate=4;
}
Recortes:
En este paso se realiza un corte sobre la imagen de entrada para cada uno de los contornos
verificados, en otras palabras, se obtiene la imagen contenida para este contorno a partir de la
imagen original.
Para esto se utiliza el método Cut de la clase “Regions” que utiliza la función
“cv::getRotationMatrix2D” para obtener el grado de rotación del rectángulo contenido en el
contorno, obteniendo como resultado una matriz de rotación homogénea, luego mediante
“cv::warpAffine”, se obtiene la imagen de entrada rotada que luego podrá ser cortada mediante la
función “cv::getRectSubPix” y luego redimensionada al tamaño indicado por el último parámetro
de la función “Cut”.
Esta función es llamada desde el programa principal como:
for(int i=0;i<(int)cRegions.size();i++)
{
Mat img_chkregion;
img_chkregion = cRegions[i].Cut(imgPrep.img_blur,vrotRect[i]);
img_chkregion = cRegions[i].Adjust(img_chkregion,3);
cRegions[i].Write(argv[1],i,img_chkregion);
}
Ajuste de intensidad
La función “Adjust” de la clase “Regions” hace un ajuste de contraste de la imagen entrada, donde
se incorpora más intensidad a los colores claros y se reducen los de menor intensidad.
Esta función está definida como:
cv::Mat Regions::Adjust(cv::Mat img_input, int itr )
{
Mat img_adj = Mat::zeros( img_input.size(), img_input.type() );
img_input.copyTo(img_adj);
for(int i=0;i<itr;i++)
{
for( int y = 0; y < img_input.rows; y++ )
for( int x = 0; x < img_input.cols; x++ )
{
img_adj.at<uchar>(y,x)= saturate_cast<uchar>( 1.5* img_adj.at<uchar>(y,
x));
img_adj.at<uchar>(y,x) =saturate_cast<uchar>( img_adj.at<uchar>(y, x) 50 );
}
}
return img_adj;
}
Donde 1.5 y 50, e itr, se han obtenido por prueba y error luego de probar con distintas imágenes.
3ra Etapa
Esta etapa es la encargada de clasificar las imagenes recortadas de la etapa anterior, y puede
subdividirse en:
Figura 5.
A) Clasificador SVM
Para seleccionar la imagen correcta dentro de todas las imágenes posibles contenidas en los
contornos, utilizamos un clasificador SVM (Support Vector Machines) el cual separa por medio de
un hiperplano a las dos clases, que en este caso son matriculas y no matriculas.
Las matriculas obtenidas tiene la forma;
Figura 6.
Aquellas que no lo son, pero son regiones válidas, es decir, cumplen con las dimensiones de bordes
anteriormente mencionadas son de la forma:
Figura 7.
Con imágenes de este tipo se construyó “TrainingData” y “SVM_Classes” que no son más que las
mismas imágenes organizadas en una “Mat” de [1,170x70x(N+n)] , donde “N” es la cantidad de
matriculas entrenadas y “n” corresponde a la cantidad de imágenes que no son matriculas,
“SVM_Classe” una “Mat” de [1,N+n] , donde se clasifica con uno “1” y con un “0” las imágenes
que no corresponden a la imagen de una matrícula válida.
Luego en el programa principal se construye el clasificador, de siguiente manera:
CvSVM svmClassifier(SVM_TrainingData, SVM_Classes, Mat(), Mat(), SVM_params);
,donde “TrainingData” y “SVM_Classes” son los parámetros anteriormente mencionados, y
“SVM_params” se encuentran definidas las características del clasificador, declaradas y definidas
en la cabecera del programa principal.
Posteriormente para cada una de las imágenes contenidas en los contornos válidos, se procede a
clasificarlas mediante el método “predict” de la clase “CvSVM”.
Luego de obtener la/las matriculas válidas (solo se considera una, esto se debe a que mas de un
contorno encierra a la misma matricula), se utiliza:
img_plate = cRegions[i].Adjust(img_plate);
img_plate= cRegions[i].AddBorder(img_plate,40);
Donde se llama al método “Adjust” el cual mediante una operación algebraica aplicada a cada pixel,
se mejora el contraste de la patente, que permitirá luego obtener mejores resultados cuando esta, es
procesada por el OCR. A modo de ejemplo se muestra la siguiente figura.
Figura 8.
Por otro lado el método “AddBorder” no es otra cosa que una función que permite agregar un borde
de color negro para que la imagen sea aceptada por el OCR.
Posteriormente ”img_plate” es guardada en el directorio raíz del programa para que luego sea
trabajada por el sistema de OCR.
Ejecución del OCR
En este apartado se utiliza el software tesseract en línea de comandos, tal como se muestra mas
abajo.
system("tesseract plate.jpg plate_ocr -l esp")
,donde se llama al método “system” para ejecutar en línea de comandos a “tesseract”, donde recibe
como parámetros la ubicación de la imagen de entrada, en este caso se encuentra en el directorio
raíz del programa, por otro lado “plate_ocr” es el archivo de texto de salida donde se guardan los
caracteres y por ultimo –l esp, indica que tesseract utilizará el idioma “esp”, creado durante la fase
de entrenamiento.
[Entrenamiento]
Durante la fase de entrenamiento, se utilizó una seria de programas adjuntos a “tesseract” mediante
el siguiente conjunto de imágenes (todo este proceso puede ser consultado en la bibliografía con
numero [3] de la ultima página)
Conjunto de caracteres (letras)
Figura 9.
Conjunto de caracteres (números)
Figura 10.
Para finalizar con el entrenamiento fue necesario editar y crear los siguiente archivos, mediante el
mismo software en línea de comandos y otras herramientas auxiliares como “jTessBoxEditor”.
Imprimir patente
La última tarea del programa es leer el archivo de texto donde se encuentra el código de la
matricula.
Para esto se utiliza el objeto “in” de la clase ifstream, donde en la llamada al constructor se pasa
como parámetro el archivo de lectura y el modo de operación.
Por último se llama al método getline para obtener la primera línea de texto del archivo.
ifstream in("plate_ocr.txt", ios::in);
in.getline(str_plate, sizeof(str_plate),'/n');
Estadísticas
Se analizó 30 imágenes con una resolución de 1200x900 px, y se obtuvieron 24 imágenes de
patentes válidas y el programa OCR ha reconocido de ellas solo 19. Por ende en definitiva son
reconocidas solo el 63% de las imágenes de entrada en caracteres imprimibles.
Conclusión:
En el último apartado concluimos que el programa reconoce para esta muestra solo el 63% de los
casos, este resultado está directamente afectado por tres factores importantes.
El primer factor que incide en este resultado es la calidad de la imagen entrada, la posición de la
patente respecto del frente de la cámara y por otro lado la capacidad del algoritmo de Canny para
detectar los bordes.
El segundo ítem a tener en cuenta que influye y en menor medida que el anterior, es la cantidad de
muestras de entrenamiento necesarias para clasificar correctamente.
Por último este factor, y de mayor importancia, la capacidad de detección del OCR no es optima, y
esto se debe principalmente a que en este caso se entrenó con una sola copia de cada carácter
posible en la matricula, esto es así dado que la tarea de conseguir caracteres de patentes de
matriculas es un trabajo absolutamente manual, además el entrenamiento es sumamente tedioso,
donde es necesario indicarle a “tesseract” donde se encuentra y con qué dimensiones cada carácter
dentro de una plantilla de caracteres, que en algunas situaciones según la calidad del carácter este
puede ser reconocido o no, lo cual hace aún más ardua la tarea. El software tesseract en su manual
de usuario indica al menos entre 6-10 muestras de cada carácter para obtener buenos resultados, es
decir que aquí los resultados obtenidos son bastante buenos considerando que se ha utilizado una
sola copia.
Anexo:
A continuación se detallan los archivos utilizados y desarrollados para este programa, donde además
del programa principal se desarrolláron otros programas para poder entrenar el clasificador, y otros
que permitiéron encontrar los parámetros adecuados a cada función, estos son:
“main.cpp”
-> Contiene el programa principal
“Regions.h” y “Regions.cpp”
->Contiene la clase “Regions”
“PreProcess.h” y “PreProcess.cpp” ->Contiene la clase “PreProcess”
“ToolContrast.cpp”
-> Permite comprobar distintos ajustes en la imagen
“ToolTrainning.cpp”
-> Contiene el programa necesario para entrenar
Bibliografía
[1] Mastering OpenCV with Practical Computer Vision Projects . Daniel Lélis Baggio .2012
[2] Learning OpenCV. Gary Bradski and Adrian Kaehler. O’Really.2008
[3] http://opencv.org/documentation.html
[4] http://iaci.unq.edu.ar/materias/vision/apuntes.htm
[5] http://hackingtelevision.blogspot.com.ar/2011/04/ocr-con-opencv-usando-canny.html
[6] https://code.google.com/p/tesseract-ocr/
[7] http://blog.damiles.com/
[8] https://code.google.com/p/lime-ocr/