sábado, 27 de noviembre de 2010

SOLUCIÓN RETO VII: CONTAR CARAS

Muy buenas, ya tenemos una solución al reto de contar caras.  Para esta solución me he modificado la estructura,y en vez de utilizar listas he utilizado un vector.  La estructura es la siguiente:

struct Rostro {
    bool HayCara; // Número total de elementos
    centro Pcentral;
    IplImage* Cara;  //Aqui guardamos la cara
    bool HayConincidencia;
    int ContadorCoincidencia;
    int ContadorNoCoincidencia;

};


Esta estructura contiene las siguientes variables hay cara, para saber si hay cara en esa posición del vector o no, Pcentral para conocer el punto central de la cara, Cara es la imagen de la cara, realmente esta para contar caras no lo necesitamos, pero por si en un futuro queremos visualizarla.  HayCoincidencia es una variable que me dice si anteriormente ha estado esta cara, en base a su posición.  Contador de NoCoincidencia es por si no está, y antes si.  Esto es por si nos pierde la imagen por lo que sea que no se olvide de esa cara.
Pcentral es una variable de tipo centro.  Este tipo lo he definido así:

struct centro{
    int x;
    int y;
};

Principalmente en el algoritmo lo que hacemos es lo siguiente. 
  1. Comparamos la distancia entre la imagen anterior y la imagen actual, si es menor a un determinado valor suponemos que es la misma.
  2. Si es la  misma contamos una coincidencia.
  3. Sin no es la misma contamos una no coincidencia
  4. Si hay un numero de veces coincidencia es una cara que nos interesa.
  5. Si ha pasado varias veces y no hay coincidencia suponemos que se ha ido la cara.

El algoritmo en lenguaje C es el siguiente:

           for( l = 0; l < 10; l++ )
            {
                //Comprobamos si hay coincidencia, si hay coincidencia y esta se ha repetido una serie de veces contamos cara

                for( m = 0; m < 10 ; m++ )
                {  
                        if ((ListaRostros[l].HayCara==true)&& (ListaRostrosAnterior[m].HayCara==true))
                        {
                            if(((((ListaRostros[l].Pcentral.x-ListaRostrosAnterior[m].Pcentral.x)^2+(ListaRostros[l].Pcentral.y-ListaRostrosAnterior[m].Pcentral.y)^2)^(1/2))<750)&& !ListaRostros[l].HayConincidencia&& !ListaRostrosAnterior[m].HayConincidencia)
                            {
                                ListaRostros[l].HayConincidencia=true;
                                ListaRostrosAnterior[m].HayConincidencia=true;
                                ListaRostros[l].ContadorCoincidencia=ListaRostrosAnterior[m].ContadorCoincidencia+1;
                                if (ListaRostros[l].ContadorCoincidencia==5)
                                {
                                    NumeroCaras++;
                                }
                            }
                        }
                       
                }
                //Si no hay coincidencia contamos
                if (!ListaRostros[l].HayConincidencia)
                {
                    ListaRostros[l].ContadorCoincidencia=0;
                    ListaRostros[l].ContadorNoCoincidencia++;

                }
               
            }
            //Si no hay coincidencia y esta es menor de 3 entonces guardamos la imagen
            for( m = 0; m < 10; m++ )
            {
                if (!ListaRostrosAnterior[m].HayConincidencia)
                {
                    ListaRostrosAnterior[m].ContadorNoCoincidencia++;
                    ListaRostrosAnterior[m].ContadorCoincidencia=0;
                    if (ListaRostrosAnterior[m].ContadorNoCoincidencia<3)
                    {
                        l=0;
                        while ((ListaRostros[l].HayCara)&& l<10) l++;
                        if (l<10) ListaRostros[l]=ListaRostrosAnterior[m];

                    }
                }
                   
            }
            //Copiamos la imagen en imagen anterior
            for( i = 0; i < 10; i++ )
            {
                ListaRostrosAnterior[i]=ListaRostros[i];
            }

Los resultados de este algoritmo no los he evaluado todavía, tasas de acierto y de fallo.  Parece que funciona bastante bien, aunque habría que mejorarlo, sobre todo en los falsos positivos, a veces me reconoce caras donde no las hay, para esto miraremos algoritmos que nos quitan el fondo con openCV.  También comentar que el algoritmo es parametrizable, podemos cambiar la distancia para comprobar si es la misma cara, y el numero de veces que cuenta coincidencia y el numero de veces que no.
Pongo un vídeo


En le vídeo podemos ver, que reconoce una vez y me cuenta una sola vez.  También se ve la aparición de falsos positivos, esto nos puede fastidiar un poco la cuenta, hay que ver como los eliminamos, probaremos, eliminando el fondo.  Además luego desaparezco de la imagen y vuelvo, como me he ido y el vuelto pues me cuenta otra vez.

sábado, 13 de noviembre de 2010

RETO VII: CONTAR CARAS

Para este reto vamos a ir probando distintas soluciones hasta dar con la más adecuada, lo que vamos a intentar hacer es contar caras, o sea cada vez que alguien pasa contarlo.  Esto a priori parece fácil, pero si decimos cada vez que detectes cara, nos va a contar una por cada frame en la que aparece, con lo cual no tendría mucho sentido. Por lo tanto como primera aproximación vamos a intentar comparar imágenes si es la misma no la contamos si es una nueva la contamos.  Si esto no funciona bien tendremos que intentar otras cosas, como intentar obtener los puntos biométricos de la cara y comparar, o seguir una imagen a lo largo de su trayectoria.  De momento haremos esto para ver los resultados.

domingo, 7 de noviembre de 2010

SOLUCIÓN RETO VI: SEPARACIÓN DE LAS CARAS EN OTRAS IMAGENES

Para la resolución de este reto, me he creado una pequeña estructura, en la cual almaceno la imagen de la cara.  La estructura es la siguiente:

typedef struct _Rostro {
    struct _Rostro* Anterior; // secuencia previa
    struct _Rostro* Siguiente; // secuencia siguiente
    struct _Rostro* Primero; //Primer elemento de la lista
    int Nodo;
    int total; // Número total de elementos
    IplImage* Cara;  //Aqui guardamos la cara

}Rostro;
typedef Rostro *pRostro;
typedef Rostro *Rostros;

Podemos ver que nuestra intención es realizar una lista con las caras, para he definido una serie de funciones para tratar esta lista.

int ListaVacia(Rostros R);
void Insertar(Rostros *R, IplImage* Cara);
IplImage* MostrarRostro(Rostros *R,int nodo);

Con estas funciones controlaremos la lista.
El código fuente de estas funciones será el siguiente.

int ListaVacia(Rostros R) {
   return (R == NULL);
}
void Insertar(Rostros *R, IplImage* Cara){
    pRostro nuevo, anterior;
    /* Crear un nodo nuevo */
    nuevo = (pRostro)malloc(sizeof(Rostro));
    nuevo->Cara= Cara;
    if (ListaVacia(*R)){
        *R=nuevo;
        (*R)->Siguiente=NULL;
        (*R)->Anterior=NULL;
        (*R)->Cara=Cara;
        (*R)->Nodo=0;
        (*R)->total=1;
        (*R)->Primero=*R;
    }
    else
    {   
        anterior=(*R)->Primero;
        while (anterior->Siguiente!=NULL){
            anterior->total++;
            anterior=anterior->Siguiente;
        }
        anterior->total++;
        anterior->Siguiente=nuevo;
        nuevo->Anterior=anterior;
        nuevo->Siguiente=NULL;
        nuevo->Nodo=anterior->Nodo+1;
        nuevo->total=anterior->total;
        nuevo->Cara=Cara;
       
    }

}
IplImage* MostrarRostro(Rostros *R,int nodo){
   pRostro Caras = *R;

   if(ListaVacia(*R))  {
       printf("ERROR:  Lista Vacia\n");
       return NULL;
   }
   else {
      
      while(Caras->Nodo<nodo) {
         Caras = Caras->Siguiente;
     }
      return Caras->Cara;
   }
}

Bueno pero, como separamos nuestra cara de la imagen.  Para esto utilizaremos las siguientes funciones de OpenCV:

cvSetImageROI(IplImage*,CvSeq); 
cvCopy(IplImage* src,IplImage* des,NULL);
cvResetImageROI(IplImage*);

La primera, cvSetImageROI nos delimita la zona a copiar, la segunda función nos copia ese fragmento de imagen en la imagen des, y la tercera nos elimina la delimitación en la imagen, así la volvemos a tener completa otra vez.
Todo lo demás es exactamente igual al reto anterior.

El código fuente lo dejo aquí.

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

#include <iostream>
#include <cstdio>
#include <string>

#include "Funciones.h"
#include "Estructuras.h"
using namespace std;

void main()
{
     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

   
     double t;
     bool Primera= true;
     CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad( "haarcascade_frontalface_alt.xml",0, 0, 0 );
     CvMemStorage* storage = cvCreateMemStorage(0);
     //Creamos la ventana
     cvNamedWindow( "Imagen", CV_WINDOW_AUTOSIZE );
     //Capturamos la cámara
     CvCapture* capture= cvCreateCameraCapture(0);   
     assert( capture != NULL ); 
     IplImage* Frame;
     IplImage* gris;
     IplImage* RostroDetectado;
     Rostros ListaRostros=NULL;
     CvSeq* objects;
   

   
   
     int i,j,k,l;
     string nombreVentana;
     cvSetCaptureProperty(capture,CV_CAP_PROP_FRAME_WIDTH,300);
     cvSetCaptureProperty(capture,CV_CAP_PROP_FRAME_HEIGHT,300);

     Frame=cvQueryFrame(capture);  //Capturamos la primera imagen
     

     if(Frame)  //Si hay frame
     {
         gris = cvCreateImage( cvSize(Frame->width,Frame->height), 8, 1 );
        
         while(1)
        {
            //Preparación de la imagen
            cvCvtColor( Frame, gris, CV_BGR2GRAY );
           
            //Capturamos el tiempo inicial
            t = (double)cvGetTickCount();
            objects= Deteccion_y_dibujo(gris, cascade, storage);  //Aqui ya no me rellena el objects la segunda vez
            //Capturamos el tiempo final y le restamos el inicial
            t = (double)cvGetTickCount() - t;

             //Dibujamos cuadros al rededor de los objetos
            //Con lo cual este for no me lo hace
           

            for( i = 0; i < (objects ? objects->total : 0); i++ ) {
                CvRect* r = (CvRect*)cvGetSeqElem( objects, i );
                cvRectangle(
                Frame,
                cvPoint(r->x,r->y),
                cvPoint(r->x+r->width,r->y+r->height),
                colores[i%8],
                2
                 );
               RostroDetectado = cvCreateImage(cvSize(r->width, r->height), gris->depth, gris->nChannels);
               cvSetImageROI(gris,*r);
               cvCopy(gris,RostroDetectado,NULL);
               cvResetImageROI(gris);
               Insertar(&ListaRostros, RostroDetectado);
             


            }   
           
            //Visualizamos el tiempo
           printf( "Tiempo deteccion = %g ms\n",t/((double) cvGetTickFrequency() *1000.) );
            //Dibujamos la imagen
            cvShowImage( "Imagen", Frame);
            for( l = 0; l < (ListaRostros ? ListaRostros->total : 0); l++ )
                {  
                    nombreVentana="Imagen";
                    j=l;
                    do
                    {                   
                         k=j%10;
                         nombreVentana+=(char)(k+48);
                         j=j/10;
                    }
                    while (j/10!=0);
                    cvNamedWindow(nombreVentana.data(), CV_WINDOW_AUTOSIZE );
                    cvShowImage( nombreVentana.data(), MostrarRostro(&ListaRostros,l));
                }
               
           
            char c = cvWaitKey
            if(c==27) break;
            BorrarLista(&ListaRostros);
            Frame=cvQueryFrame(capture);  //Capturamos el resto de las imagenes
           
           
        }
        cvReleaseImage( &gris );
     }
     cvReleaseImage(&Frame);


CvSeq*  Deteccion_y_dibujo( IplImage* gris,CvHaarClassifierCascade* cascade,CvMemStorage* storage )
{
     CvSeq*  objects;
     cvEqualizeHist( gris, gris );
   
     //Detección si las hay
     cvClearMemStorage( storage );
     objects = cvHaarDetectObjects(
            gris,
            cascade,
            storage,
            1.1,
            2,
            0 /*CV_HAAR_DO_CANNY_PRUNING*/,
            cvSize(30, 30));
           
    return objects;   
   
}
 

En este código se puede ver el empleo de la función 

cvSetCaptureProperty(capture,CV_CAP_PROP_FRAME_WIDTH,300); la cual uso para cambiar el ancho del frame, y la función.
cvSetCaptureProperty(capture,CV_CAP_PROP_FRAME_HEIGHT,300); la cual utilizo para cambiar el alto, del frame.  

Así de esta forma puedo llegar a un compromiso entre reconocimiento y velocidad de procesado.

Bueno como siempre si alguien tiene una solución mejor que la proponga, así aprendemos todos.

lunes, 1 de noviembre de 2010

RETO VI: SEPARACIÓN DE LAS CARAS EN OTRAS IMÁGENES

Muy buenas a todos, después de casi un mes desaparecido, vuelvo a escribir.  Para este reto, propongo, coger el algoritmo de segmentación de caras y separar las caras en otras imágenes para posteriormente visualizarlas en la pantalla.  De esta forma, ya podremos hacer lo que queramos con las caras.  Reconocerlas, contarlas, lo que sea.