11 diciembre, 2016 / by fjmenendez / C++ / No Comments

Detección de pupila con visión artificial paso a paso.

Breve introducción a la librería OpenCV

Introducción

Tras haber aplicado uno de los múltiples algoritmos de detección facial y de detección de ojos que se pueden encontrar realizando una búsqueda en google, vamos a intentar detectar la pupila ocular con la foto de un ojo utilizando la librería de visión artificial OpenCV y Visual Studio.

OpenCV,  http://es.wikipedia.org/wiki/OpenCV  es probablemente la más potente librería de visión artificial. Originalmente desarrollada por Intel y liberada después como software libre bajo licencia GPL, ha evolucionado hasta convertirse en la base de la mayor parte de las aplicaciones de detección de movimiento, reconocimiento facial, detección de objectos, LPR (reconocimiento de matrículas), ..

Podéis descargar el código fuente de este ejemplo aquí o en github

Instalando Visual Studio C++ 2010 Express ® y OpenCV®

Pues bien, para comenzar esta sencilla aplicación vamos a utilizar Visual Studio.

Si no lo hemos hecho ya, lo primero que debemos hacer es instalar  “Visual Studio C++ 2010” ®, en este caso, y dado que es un proyecto de evaluación hemos instalado la versión gratuita o  Express desde el siguiente enlace  http://www.visualstudio.com/es-es/downloads/download-visual-studio-vs#DownloadFamilies_2

He incluido las dlls y librerías de enlazado necesarias para este proyecto en el código fuente, pero no dudéis  en instalar la versión completa.

Para  ello, necesitamos descargar la librería de visión artificial OpenCV http://docs.opencv.org/trunk/doc/tutorials/introduction/windows_visual_studio_Opencv/windows_visual_studio_Opencv.html#windows-visual-studio-how-to

Al ejecutar el archivo .exe de la descarga para Windows, se descomprimirán los archivos en la ruta deseada, hemos elegido la ruta “D:\Trabajo\OpenCV_Project\”

Podemos descargar el código fuente y los binarios, nosotros en concreto y dado que estamos trabajando con un Windows 7 de 64 bits, utilizaremos las dlls que se encuentran opencv\build\x64\vc10\bin.

Creando el proyecto

Tras abrir Visual Studio C++ 2010 Express, elegimos la opción “Crear nueva aplicación de consola de Win32”

Es importante, desmarcar la opción encabezado precompilado.

Debemos indicar donde se encuentran los archivos de encabezado .h, para ello, en las propiedades del proyecto  indicamos “D:\Trabajo\OpenCV_Project\opencv\build\include” como directorio de inclusión adicional.

 

Haremos lo mismo con las bibliotecas incluyendo la ruta “D:\Trabajo\OpenCV_Project\opencv\build\x86\vc10\lib” para resolver la dependencia de biblioteca.

Añadimos como entrada los archivos opencv_core247d.lib; opencv_imgproc247d.lib; opencv_highgui247d.lib;

Una vez echo esto, el siguiente código debería compilar

// TestOpenCV.cpp: define el punto de entrada de la aplicación de consola.
//

#include "stdafx.h"

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <string.h>

using namespace cv;
 

int main( int argc, char** argv )
{
    
    return 0;
   
}
 

El algoritmo

Nuestro objetivo será detectar el borde de la pupila, es decir obtener un conjunto de puntos que representen ese borde.

Buscaremos rellenar un vector de puntos con el resultado adecuado.

Partiremos de una imagen en disco llamada “imagen.jpg” (nombre original verdad?)

El tipo que almacena una imagen en opencv  es “Mat”, este tipo de objeto contiene varios campos y métodos públicos bastante obvios de deducir dada su intuitiva nomenclatura iremos detallándolos  según los vayamos utilizando

Para cargar la imagen utilizamos la función “imread(nombre_fichero_a_cargar, tipo_rgb_gray_…)”

Lo primero que debemos hacer es cargar la imagen en memoria para poder tratarla y “jugar” con ella.


 // imagen_color, contendrá la imagen original a tratar
    Mat imagen_color,copia_original;
    Mat imagen_temporal_color;       
    //Leemos el archivo 
    imagen_color = imread(nombre_fichero_imagen, IMREAD_COLOR); 
    copia_original= imagen_color.clone();

 

Las funciones namedWindow e imshow nos permiten mostrar una imagen en pantalla, la primera declara la ventana donde mostrar la imagen decidiendo si el  tamaño de la ventana se puede modificar, la segunda carga la imagen en esa ventana

    
    //Creamos una ventana para mostrar la imagen original
    namedWindow( "Imagen Original",cv::WINDOW_NORMAL );
    //Mostramos la imagen original
    imshow( "Imagen Original", imagen_color ); 
	

Aplicaremos un filtro Gaussiano con el objetivo de difuminar la imagen  y eliminar posible ruido en los pixeles http://en.wikipedia.org/wiki/Gaussian_blur


     //Difuminamos para quitar ruido
    GaussianBlur( imagen_color, imagen_color, Size( 3, 3 ), 0, 0 );

 

Transformamos a escala de image_grises

 


      cv::cvtColor(imagen_color, image_gris, CV_BGR2GRAY);

Aplicamos el umbral para transformar a blanco y negro. La imagen está en escala de grises, es decir, tiene un sólo canal

con valores de 0 a 255, aplicar un umbral será decir que los valores por debajo de un valor pasen a 0 y los valores por encima de ese valor pasen a blanco. Por lo tanto obtendremos una imagen binaria.


   
   
    //Aplicamos el umbral para transformar a blanco y negro
    cv::threshold(image_gris,image_gris,0.4*sum(image_gris)[0]/(image_gris.cols*image_gris.rows),255,cv::THRESH_BINARY );
    
	

 

Con las siguientes funciones que obtendremos un  pseudo-histograma horizontal y vertical para detectar la posición del máximo absoluto.

Como hemos visto, tenemos un imagen binaria, los pixeles o bien son negros o bien son blancos.

El histograma horizontal que buscamos es la gráfica del sumatorio de pixeles por columna, realmente es una función f(x) cuyo dominio va de 0 al número de columnas y cuyo valor en cada punto del dominio f(x0), será la suma de los pixels para la columna x0.

Haremos lo mismo con el histograma vertical, suavizaremos la curva obtenida y obtendremos el valor máximo.


  
void SuavizaCurva(vector<int>&suma_negros_columnas);
vector<int>  DameHistogramaHorizontal(const Mat & gray);
vector<int>  DameHistogramaVertical(const Mat & gray);
int DamePosicionDelMaximo(vector<int> suma_negros_columnas);
int DamePuntoSuperiorMaximo(vector<int> suma_negros_columnas, int umbral);
int DamePuntoInferiorMaximo(vector<int> suma_negros_columnas, int umbral);

	

Podemos mostrar la proyección de pixels que hemos calculado.


	//Mostramos la proyección de pixels 
    for(int i=0;i<image_gris.cols;i++)
    { 
        cv::line(imagen_temporal_color,cv::Point(i,0),cv::Point(i,suma_negros_columnas[i]),Scalar(0,0,255), 3, 8, 0 );
    }
	  for(int i=0;i<image_gris.rows;i++)
    { 
        cv::line(imagen_temporal_color,cv::Point(0,i),cv::Point(suma_negros_filas[i],i),Scalar(255,0,0), 3, 8, 0 );
    }
	
	

Como podemos observar, hay un conjunto de puntos que acompañan al máximo, que destacan sobre el resto de la gráfica y que pertenecerán al conjunto de puntos buscado.

Una vez que hemos detectado la posible zona de ubicación de la pupila, nos queda recortar esta zona aplicar el agoritmo de detección de bordes de  Canny  http://es.wikipedia.org/wiki/Algoritmo_de_Canny   y mostraremos los resultados

 


 
 recorte_color=copia_original(cv::Rect(x_0 -dx   ,y_0-dy , (x_1-x_0 +2*dx) ,(y_1-y_0 +2*dy)  ) ).clone();
    namedWindow( "recorte_color",cv::WINDOW_NORMAL );
    imshow( "recorte_color", recorte_color ); 
    Mat recorte_gris;
            cv::imwrite("recorte_color.jpg",recorte_color);
    cv::cvtColor(recorte_color,recorte_gris, CV_BGR2GRAY);
     
    namedWindow( "recorte_gris",cv::WINDOW_NORMAL );
    imshow( "recorte_gris", recorte_gris );  
    cv::imwrite("recorte_gris.jpg",recorte_gris);
    //Difuminamos la imagen
    blur( recorte_gris, matriz_deteccion, Size(3,3) );
    //Aplicamos el algoritmo de detección de bordes de Canny
    //http://es.wikipedia.org/wiki/Algoritmo_de_Canny
	
	
	
   Canny( matriz_deteccion, matriz_deteccion, 30, 180, 3 );
    
	 //Invertimos los colores
    bitwise_not(matriz_deteccion,matriz_deteccion);
    //Aplicamos una erosión 
    cv::erode(matriz_deteccion,matriz_deteccion,Mat());

    namedWindow( "Canny", WINDOW_NORMAL );
    imshow( "Canny", matriz_deteccion );
    cv::imwrite("Canny.jpg",matriz_deteccion);
     // Mostramos los bordes detectados en el recorte
    Mat  dst =   Mat(recorte_color.size(),recorte_color.type(),Scalar::all(255));//.clone();
    recorte_color.copyTo( dst, matriz_deteccion);
    namedWindow( "detección", WINDOW_NORMAL );
    imshow( "detección", dst );
   cv::imwrite("dst.jpg",dst);
    //Llenamos el vector de puntos que estabamos buscando
    for(int i=0;i<matriz_deteccion.cols ;i++)
    {
        for(int j=0;j<matriz_deteccion.rows;j++)
        {
            //Hay que tener en cuenta que los puntos detectados están en el recorte
            //Por eso, habrá que sumarles los desplazamientos
            if(matriz_deteccion.at<uchar>(cv::Point(i,j))==0)
                vector_puntos.push_back(cv::Point(i+x_0 -dx ,j+y_0-dy ));
        }
    } 
    //Mostramos el resultado
    for(int i=0;i<vector_puntos.size() ;i++)
    {
        cv::circle(copia_original,vector_puntos[i],0.1,cv::Scalar(255,0,0),0.1);
    } 
    namedWindow( "resultado", WINDOW_NORMAL );
    imshow( "resultado", copia_original );
	

 

Por último mostramos el resultado

Publicado por Admin jueves, 20 de febrero de 2014 14:25:00

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *