martes, 28 de septiembre de 2010

RETO V: SEGMENTACIÓN DE CARAS CON UNA CÁMARA

En este reto vamos a hacer lo mismo que en el reto IV pero esta vez empleando una cámara web.  Segmentaremos las caras para posteriormente marcarlas en la imagen con un cuadrado.
Para hacer esto vamos a utilizar la función Deteccion_y_dibujo, modificandola para que nos diga también el tiempo que ha tardado en reconocer la cara.  La función main la modificaremos para capturar las imágenes de la cámara web.
En principio parece un reto muy sencillo, ya que lo complicado está hecho.

domingo, 26 de septiembre de 2010

SOLUCIÓN RETO IV: SEGMENTACIÓN DE CARAS EN UNA IMAGEN

Muy buenas a todos, al final después de muchas búsquedas, he encontrado la solución a este reto.   La solución es más fácil de lo que yo pensaba, ya que ya estaba hecha en OpenCV.  Gracias al libro "Learnin OpenCV" de Gary Bradski y Adrian Kaehler, de la editorial O'reilly, lo he encontrado.

En OpenCV existe una función llamada cvHaarDetectObjects, que reconoce objetos utilizando el clasificador de Viola-Jones.  

  1. Usa las características de entrada Haar-Like:  un umbral aplicando sumas y restas de regiones rectangulares de la imagen.
  2. Es una técnica de integración de imagen que permite una rápida del valor de la región rectangular o regiones giradas 45º.  Esta estructura de datos es utilizada para acelerar los cálculos de las características de las entradas.
  3. Se utiliza para crear una clasificación de las características por alta detección y débil rechazo.
  4. Esta organizado en un débil clasificador de nodos en una cascada de rechazos. En otras palabras, en el primer clasificador seleccionamos la mejor región detectada que contiene el objeto, permitiendo errores en la detección, en el siguiente nodo la segundo mejor detección.  En un modo de test un objeto es detectado solo si ha superado toda la cascada de nodos.
Para utilizar esta función necesitamos entrenar la función cvHaarDetectObjects, en este reto no la hemos entrenado, lo dejamos para retos posteriores, ya que dentro del directorio data, de openCV encontramos el archivo haarcascade_frontalface_alt.xml  que es el que he utilizado.  Si es que al final OpenCV lo tiene todo resuelto, je je je.

A continuación dejo el código que he generado.

#include "cv.h"
#include "highgui.h"

#include <iostream>
#include <cstdio>



void Deteccion_y_dibujo( IplImage* imagen,CvHaarClassifierCascade* cascade,CvMemStorage* storage, double escala);





void main()
{
     CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad( "haarcascade_frontalface_alt.xml",0, 0, 0 );
     CvMemStorage* storage = cvCreateMemStorage(0);
     IplImage* imagen = cvLoadImage("HPIM1403.jpg");
     IplImage* imagenPequeña =cvCreateImage(cvSize((int)((imagen->width*25)/100) , (int)((imagen->height*25)/100) ),imagen->depth, imagen->nChannels );
     cvResize(imagen,imagenPequeña,CV_INTER_NN);
     Deteccion_y_dibujo(imagenPequeña, cascade, storage,1 );
     //Creamos la ventana
     cvNamedWindow( "Imagen", CV_WINDOW_AUTOSIZE );
     //Dibujamos la imagen
     cvShowImage( "Imagen", imagenPequeña);
     
     cvWaitKey(0);
     cvReleaseImage(&imagen);

  
}

void Deteccion_y_dibujo( IplImage* imagen,CvHaarClassifierCascade* cascade,CvMemStorage* storage, double escala  )
{
     static CvScalar colores[] = {
        {{0,0,255}}, {{0,128,255}},{{0,255,255}},{{0,255,0}},
        {{255,128,0}},{{255,255,0}},{{255,0,0}}, {{255,0,255}}};//Colores para dibujar
     
     //Preparación de la imagen
     IplImage* gris = cvCreateImage( cvSize(imagen->width,imagen->height), 8, 1 );
     IplImage* Imagen_Pequeña = cvCreateImage(
                cvSize( cvRound(imagen->width/escala), cvRound(imagen->height/escala)), 8, 1);
     cvCvtColor( imagen, gris, CV_BGR2GRAY );
     cvResize( gris, Imagen_Pequeña, CV_INTER_LINEAR );
     cvEqualizeHist( Imagen_Pequeña, Imagen_Pequeña );
     
     //Detección si las hay
     cvClearMemStorage( storage );
     CvSeq* objects = cvHaarDetectObjects(
            Imagen_Pequeña,
            cascade,
            storage,
            1.1,
            2,
            0 /*CV_HAAR_DO_CANNY_PRUNING*/,
            cvSize(30, 30));
     //Dibujamos cuadros al rededor de los objetos
     for(int i = 0; i < (objects ? objects->total : 0); i++ ) {
            CvRect* r = (CvRect*)cvGetSeqElem( objects, i );
            cvRectangle(
            imagen,
            cvPoint(r->x,r->y),
            cvPoint(r->x+r->width,r->y+r->height),
            colores[i%8]
            );
     }
     cvReleaseImage( &gris );
     cvReleaseImage( &Imagen_Pequeña );
}

Aquí pongo unas fotografías en las que muestro los resultados.


Bueno, en los próximos días publicare el siguiente reto.  Espero que no me cueste tanto como este je je je.

jueves, 16 de septiembre de 2010

RETO IV: SEGMENTACIÓN DE CARAS EN UNA IMAGEN

Hola a todos muy buenas, siento mucho haberme retrasado a la hora de escribir este nuevo reto.  Para este reto vamos a intentar segmentar las caras de una fotografía, tal como hacen las cámaras de fotos, que para enfocar reconocen el lugar donde hay una cara.  Bueno para esto buscaremos la posición de los ojos.  De momento no puedo decir mucho, ya que solo he investigado un poquito el tema.   Pero bueno, a ver si en una semana lo solucionamos entre todos.

domingo, 12 de septiembre de 2010

SOLUCIÓN RETO III: HACER MÁS FIABLE EL RECONOCIMIENTO DE LA PELOTA

Muy buenas a todos,  ya he conseguido hacer más robusto el algoritmo, para ello, reconocemos el color amarillo de la imagen y la segmentamos.

Para encontrar el color amarillo, nos hemos basado en el espacio de color HSV, del ingles Hue, Saturation, Value -Tonalidad, Saturación, Valor.  

La tonalidad nos representa el tipo del color, por ejemplo el rojo es 0, el verde 120.
La saturación representa la pureza del color, contra menos es su valor menos tonalidad de gris tendrá.
El valor del color es el brillo.

El algoritmo que nos queda es el siguiente:  Primero pasamos la imagen al espacio HSV, después buscamos los puntos donde tenemos el color de nuestra pelota y en una imagen creada en blanco y negro vamos poniendo valor blanco para los puntos de nuestra pelota y valor negro para el resto.  Después dibujamos nuestro circulo donde tenemos la pelota, para esto he utilizado el algoritmo de hough, aunque lo podríamos haber hecho de muchas maneras, esta ya la tenia hecha. 

El código es el siguiente
#include <cv.h>
#include <highgui.h>
#include <math.h>

void main()
{

    cvNamedWindow("Reco Pelota HSV",CV_WINDOW_AUTOSIZE);  //Creamos una ventana
    cvNamedWindow("Reco Pelota RGB",CV_WINDOW_AUTOSIZE);
    cvNamedWindow("Reco Pelota Binearizada",CV_WINDOW_AUTOSIZE);
    cvNamedWindow("Reco Pelota Segmentada",CV_WINDOW_AUTOSIZE);

    CvCapture* capture= cvCreateCameraCapture(0);    //Capturamos la cámara
    assert( capture != NULL ); 
    IplImage* frame;
    IplImage* frameBN;
    IplImage* frameHSV;
    IplImage* frameSeg;


    int height , width , step , channels , k = 1;
    int step_bn , channels_bn;
    int step_fra , channels_fra;
    int step_seg , channels_seg;
    uchar *data_hsv , *data_bn, *data, *data_seg;
    int i,j;

    int percent=200;
    CvSeq* results;
    bool Primera= true;

    int hlower =95;
    int hupper =105;
    int Saturation=0;
    int Brightness=0;


    int Dilate = 10;
    int Erode = 5;

   
    while(1)
    {
        frame=cvQueryFrame(capture);  //Capturamos la primera imagen
        if(!frame) break;  //Si no hay frame salimos
        if (Primera)
        {
            //Creamos la imagen
            frameHSV = cvCreateImage( cvGetSize(frame), IPL_DEPTH_8U, 3 );
            frameBN = cvCreateImage( cvGetSize(frame), IPL_DEPTH_8U, 1 );
            frameSeg=cvCreateImage( cvGetSize(frame), IPL_DEPTH_8U, 3 );

            // Obtenemos atributos de la imagen HSV
            height= frameHSV->height;
            width= frameHSV->width;
            step= frameHSV->widthStep/sizeof(uchar);
            channels   = frameHSV->nChannels;

            step_bn   = frameBN->widthStep/sizeof(uchar);
            channels_bn = frameBN->nChannels;

            step_fra   = frame->widthStep/sizeof(uchar);
            channels_fra = frame->nChannels;

            step_seg   = frameSeg->widthStep/sizeof(uchar);
            channels_seg = frameSeg->nChannels;
   
            //Creamos el lugar donde se almacenaran los circulos en el algoritmo de hough
            CvMemStorage* storage = cvCreateMemStorage(0);

            Primera=false;
        }
        cvCvtColor(frame,frameHSV,CV_RGB2HSV);
       
        // Obtenemos los valores RGB de la Imagen
        data_hsv = (uchar *)frameHSV->imageData;
        data_bn = (uchar *)frameBN->imageData;
        data = (uchar *)frame->imageData;
        data_seg=(uchar *)frameSeg->imageData;

        // Recorremos la imagen
        for(i = 0; i <height; i++ ) {
          for(j = 0; j <width; j++ ) {

                if (((data_hsv[i*step+j*channels])>= hlower)&& ((data_hsv[i*step+j*channels]) <= hupper)){
                    if (data_hsv[i*step+j*channels+1]>= Saturation) {
                        if (data_hsv[i*step+j*channels+2]>= Brightness) {
                            // Coloreamos el pixel en blanco
                            data_bn[i*step_bn+j*channels_bn] =255;

                            data_seg[i*step_seg+j*channels_seg] = data[i*step_fra+j*channels_fra] ;
                             data_seg[i*step_seg+j*channels_seg+1] = data[i*step_fra+j*channels_fra+1];
                            data_seg[i*step_seg+j*channels_seg+2] = data[i*step_fra+j*channels_fra+2];

                        }
                        else
                        {
                            // Coloreamos el pixel en negro
                             data_bn[i*step_bn+j*channels_bn] = 0;
                           
                             data_seg[i*step_seg+j*channels_seg] = 0;
                             data_seg[i*step_seg+j*channels_seg+1] = 0;
                             data_seg[i*step_seg+j*channels_seg+2] = 0;


                        }
                    }
                    else{
                        // Coloreamos el pixel en negro
                        data_bn[i*step_bn+j*channels_bn] = 0;

                          data_seg[i*step_seg+j*channels_seg] = 0;
                        data_seg[i*step_seg+j*channels_seg+1] = 0;
                        data_seg[i*step_seg+j*channels_seg+2] = 0;

                    }
                }
                else{
                    // Coloreamos el pixel en negro
                     data_bn[i*step_bn+j*channels_bn] = 0;

                     data_seg[i*step_seg+j*channels_seg] = 0;
                     data_seg[i*step_seg+j*channels_seg+1] = 0;
                     data_seg[i*step_seg+j*channels_seg+2] = 0;
                }
          }
        }
        // Erocionar y Dilatar , para la eliminacion de pixeles perdidos
        cvErode(frameBN,frameBN,0,Erode);
        cvDilate( frameBN,frameBN,0,Dilate);

        cvErode(frameSeg,frameSeg,0,Erode);
        cvDilate( frameSeg,frameSeg,0,Dilate);



       

        //Suavizamos la imagen
        //cvSmooth(frameBN, frameBN, CV_GAUSSIAN, 5, 5 );
        //Aplicamos el algoritmo de Hough para circulos
        results = cvHoughCircles(
            frameBN,        //Imagen en escala de grises, no hace falta aplicar ni canny ni sobel ya que lo invoca la función
            storage,            //El lugar donde almacena circulos
            CV_HOUGH_GRADIENT,    //El metodo
            1,                    //Resolución del acumulador
            frameBN->width/10,    //Mínima distancia entre dos circulos
            12,                //Umbral del algoritmo canny
            12,                //Acumulador del umbral
            1,                //Minimo radio
            300                    //Máximo radio
            );
        //Dibujamos las circunferencias de color rojo en la imagen en color
        for( int i = 0; i < results->total; i++ ) {
            float* p = (float*) cvGetSeqElem( results, i );
            CvPoint pt = cvPoint( cvRound( p[0] ), cvRound( p[1] ) );
            cvCircle(
                frame,
                pt,
                cvRound( p[2] ),
                CV_RGB(0xff,0x00,0x00),
                2);
            }
       

        cvShowImage("Reco Pelota HSV",frameHSV);
        cvShowImage("Reco Pelota RGB",frame);
        cvShowImage("Reco Pelota Binearizada",frameBN);
        cvShowImage("Reco Pelota Segmentada",frameSeg);

        char c = cvWaitKey(33);
        if(c==27) break;
    }
    cvReleaseCapture(&capture);
    cvDestroyWindow("Reco Pelota HSV");
    cvDestroyWindow("Reco Pelota RGB");
    cvDestroyWindow("Reco Pelota Binearizada");
    cvDestroyWindow("Reco Pelota Segmentada");


}

Los vídeos que dejo son: Pelota binearizada
Pelota en el espacio HSV


Pelota segmentada


Pelota localizada

martes, 7 de septiembre de 2010

RETO III: HACER MÁS FIABLE EL RECONOCIMIENTO DE LA PELOTA

Bueno, como se puede ver en el vídeo de la solución del reto II, el reconocimiento de la pelota no es muy fiable, en cuanto nos movemos un poco empieza a reconocer otras cosas que no son la pelota, o no reconoce la pelota.  En este reto vamos a intentar decirle al ordenador, que es una pelota y que no es:  En nuestro caso, vemos que la pelota es de color amarillo, con lo cual, vamos a intentar reconocer todo lo que es amarillo dentro de los círculos que encontremos en el algoritmo de hough, de esta forma descartaremos todo lo que no es una pelota.

Parece fácil, pero lo de hacer más robusto un algoritmo, a mi juicio es lo más complicado.  Pero no por esto vamos a dejar de intentarlo.  
Como siempre en este blog se aceptan sugerencias.


domingo, 5 de septiembre de 2010

SOLUCIÓN RETO II: RECONOCER UNA PELOTO Y SEGUIRLA

Este reto me está costando más de lo que me había planteado en un principio, pero bueno voy a publicar las soluciones que tengo hasta ahora.  De momento no sigo a la pelota pero la reconozco tanto en una foto como en un con la cámara.  Esto lo hago de la misma manera que el reto uno, con el algoritmo de hough.  
Para la fotografía este el  código que he hecho, como siempre he utilizado las librerías de OpenCV:

#include <cv.h>
#include <highgui.h>
#include <math.h>
void main ()
{   
    int percent=25;
    // Leemos la imagen
    IplImage* imagen = cvLoadImage("DSCN1886.jpg", CV_LOAD_IMAGE_GRAYSCALE);
    //Leemos imagen en color
    IplImage* imagenColor = cvLoadImage("DSCN1886.jpg");
    //Tenemos que reducir el tamaño de la imagen
    IplImage* imagenPequeña =cvCreateImage(cvSize((int)((imagen->width*percent)/100) , (int)((imagen->height*percent)/100) ),imagen->depth, imagen->nChannels );
    cvResize(imagen,imagenPequeña,CV_INTER_NN);
    //Lo mismo con la imagen en color   
    IplImage* imagenPequeñaColor =cvCreateImage(cvSize((int)((imagenColor->width*percent)/100) , (int)((imagenColor->height*percent)/100) ),imagenColor->depth, imagenColor->nChannels );
    cvResize(imagenColor,imagenPequeñaColor,CV_INTER_NN);

    //Creamos el lugar donde se almacenaran los circulos en el algoritmo de hough
    CvMemStorage* storage = cvCreateMemStorage(0);
    //Suavizamos la imagen
    cvSmooth(imagenPequeña, imagenPequeña, CV_GAUSSIAN, 5, 5 );
    //Aplicamos el algoritmo de Hough para circulos
    CvSeq* results = cvHoughCircles(
        imagenPequeña,        //Imagen en escala de grises, no hace falta aplicar ni canny ni sobel ya que lo invoca la función
        storage,            //El lugar donde almacena circulos
        CV_HOUGH_GRADIENT,    //El metodo
        2,                    //Resolución del acumulador
        imagenPequeña->width/10,    //Mínima distancia entre dos circulos
        150,                //Umbral del algoritmo canny
        150,                //Acumulador del umbral
        80,                //Minimo radio
        140                    //Máximo radio
        );
    //Dibujamos las circunferencias de color rojo en la imagen en color
    for( int i = 0; i < results->total; i++ ) {
        float* p = (float*) cvGetSeqElem( results, i );
        CvPoint pt = cvPoint( cvRound( p[0] ), cvRound( p[1] ) );
        cvCircle(
            imagenPequeñaColor,
            pt,
            cvRound( p[2] ),
        CV_RGB(0xff,0x00,0x00),
        2

        );
    }
    //Creamos la ventana
    cvNamedWindow( "Imagen", CV_WINDOW_AUTOSIZE );
    //Dibujamos la imagen
    cvShowImage( "Imagen", imagenPequeñaColor);

    //Esperamos indefinidamente hasta que se cierre la ventana
    cvWaitKey(0);
    //Borramos las imagen
    cvReleaseImage(&imagen);
    cvReleaseImage(&imagenColor);
    cvReleaseImage(&imagenPequeñaColor);
    cvReleaseImage(&imagenPequeña);
    //Destruimos la ventana
    cvDestroyWindow("Imagen");
}

La imagen que me dio fue la siguiente:
Solución, encontrar las pelotas en una foto.





Lo que más trabajo me está dando es la parte de la cámara, como no tengo nada para dejar la cámara fija, ya que la distancia de la cámara al plano de la pelota es muy importante, ya veis que en el la función que llama al algoritmo de hough le digo el mínimo y el máximo radio de la pelota, pues bien este cambia si me acerco con la cámara o me alejo.  En siguientes retos intentaremos solucionar este problemilla.  
#include <cv.h>
#include <highgui.h>
#include <math.h>

void main()
{
    int width;
    int height;
    cvNamedWindow("Reco Pelota",CV_WINDOW_AUTOSIZE);  //Creamos una ventana
    CvCapture* capture= cvCreateCameraCapture(0);    //Capturamos la cámara
    assert( capture != NULL ); 
    IplImage* frame;
    IplImage* frameBN;
    IplImage* frameGrande;
    int percent=200;
    while(1)
    {
        frame=cvQueryFrame(capture);  //Capturamos la primera imagen
        if(!frame) break;  //Si no hay frame salimos
        //Tenemos que ampliar el tamaño de la imagen
        frameGrande =cvCreateImage(cvSize((int)((frame->width*percent)/100) , (int)((frame->height*percent)/100) ),frame->depth, frame->nChannels );
        cvResize(frame,frameGrande,CV_INTER_NN);
        //Transformamos la imagen en BN
        width = frameGrande->width; 
        height = frameGrande->height;
        frameBN = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U,1); 
        cvCvtColor(frameGrande,frameBN,CV_RGB2GRAY);//Transformamos la imagen en blanco y negro
        //Creamos el lugar donde se almacenaran los circulos en el algoritmo de hough
        CvMemStorage* storage = cvCreateMemStorage(0);
        //Suavizamos la imagen
        cvSmooth(frameBN, frameBN, CV_GAUSSIAN, 5, 5 );
        //Aplicamos el algoritmo de Hough para circulos
        CvSeq* results = cvHoughCircles(
            frameBN,        //Imagen en escala de grises, no hace falta aplicar ni canny ni sobel ya que lo invoca la función
            storage,            //El lugar donde almacena circulos
            CV_HOUGH_GRADIENT,    //El metodo
            2,                    //Resolución del acumulador
            frameBN->width/10,    //Mínima distancia entre dos circulos
            50,                //Umbral del algoritmo canny
                                50,                //Acumulador del umbral
            150,                //Minimo radio
            200                    //Máximo radio
            );

        for( int i = 0; i < results->total; i++ ) {
            float* p = (float*) cvGetSeqElem( results, i );
            CvPoint pt = cvPoint( cvRound( p[0] ), cvRound( p[1] ) );
            cvCircle(
                frameGrande,
                pt,
                cvRound( p[2] ),
                CV_RGB(0xff,0x00,0x00),
                2);
            }

        cvShowImage("Reco Pelota",frameGrande);
        char c = cvWaitKey(33);
        if(c==27) break;
    }
    cvReleaseCapture(&capture);
    cvDestroyWindow("Reco Pelota");
}

Más adelante iré mejorando esta solución .  Lo que hay que mejorar es que siempre detecte la pelota, o sea hacer algo para que mi cámara este quieta, je je je.  Hay que tener en cuenta que la iluminación también es muy importante, otra cosa que no controlo desde mi despacho.  También tendríamos que implementar algoritmos para decirle que hay círculos que no son la pelota, falsos positivos.   Dejo el vídeo a continuación.
En retos posteriores, intentaremos mejorar este algoritmos con otras técnicas.