lunes, 11 de julio de 2011

UN POCO DE ELECTRÓNICA

En primer lugar quiero pedir disculpas a todo el mundo que me sigue por haber tardado tanto tiempo en publicar un nuevo post.  Pero por diversos temas no he podido.  Bueno es que me he puesto por mi cuenta, y claro entre unas cosas y otras no he tenido mucho tiempo para escribir, y menos para estudiar temas de visión. Bueno aunque en este post vamos a tratar un tema electrónico, el blog seguirá dando un trato especial a la visión, ya que es un tema del que quiero aprender todo lo que pueda. 
Bueno sin más dilación voy a contar de lo que trata este tema.  En este caso he hecho el control de un teclado matricial, bueno el código del pic esta hecho para detectar la tecla cero, para el resto de los casos es igual.
Para realizar el control del teclado he utilizado un PIC, en mi caso el 24FJ64GA002.  El datasheet de este PIC lo podéis encontrar aquí.  En la siguiente figura se ve el esquema del PIC con el significado de cada una de las 24 patitas, lo he sacado del datasheet.
 El esquema electrónico que he realizado es el siguiente.
En este esquema podemos ver el circuito de reset que está formado por dos resistencias R1 y R2, de 10 k y 100 ohmios respectivamente, este circuito nos mantiene a uno la pata MCLR que es la que resetearía el PIC si se pone a cero.  También podemos ver que hemos puesto condensadores C1 y C2 en las patas de alimentación de 100nF respectivamente, estos condensadores sirven para enviar a tierra todas las corrientes parásitas, ruidos, ect.  En el datasheet también dice que hay que poner un condensador de 10 micro faradios entre las patas 19 y 20, exactamente no se que hace pero lo dice el datasheet.  También podemos ver nuestro circuito que hace de reloj con un cristal de cuarzo y dos condensadores de 30 pF.
Para controlar el teclado matricial hemos conectado 4 patas el pic a las columnas y 4 patas del pic a las filas del teclado matricial. Las filas las hemos conectado también a través de unas resistencias a uno (VDD) de esta forma cuando pongamos una fila columna cero y pulsemos una tecla conectada a esa fila la columna correspondiente pasará a ser cero y así detectaremos la pulsación.
El código del PIC es el siguiente, programado en C.

#include <p24FJ64GA002.h>
#define FCY 40000000UL
#include <libpic30.h>

_CONFIG2(FNOSC_PRI   & FCKSM_CSDCMD & POSCMOD_EC );
_CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & BKBUG_ON & COE_ON &ICS_PGx1 & FWDTEN_OFF & WINDIS_OFF & FWPSA_PR128 & WDTPS_PS32768);

int main(int argc, char *argv[])
{
    TRISB=0x83FF;

    LATBbits.LATB14=0;
    LATBbits.LATB13=0;
    LATBbits.LATB12=1;
    LATBbits.LATB11=1;
    LATBbits.LATB10=1;
    while(1)
    {
        if (PORTBbits.RB7==0)
        {
            LATBbits.LATB14=1;
        //    __delay_ms(500);
        }
        else
        {
            LATBbits.LATB14=0;
        }

//        LATBbits.LATB14=1;
//        __delay_ms(500);
//        LATBbits.LATB14=0;
//        __delay_ms(500);

    }
}
Como podemos ver ponemos a uno todas las filas menos una y evaluamos el valor de la columna que lleva la tecla cero si esa columna está a uno entonces no ha sido pulsado si está a cero ha sido pulsado, la tecla cero.
Para controlar todo el teclado será cuestión de hacer un barrido por todas las filas y columnas y así detectar que tecla se ha pulsado. Esto lo dejo para vosotros, más adelante publicaré el código completo para controlar todo el teclado.

domingo, 13 de marzo de 2011

RETO IX: OBTENER EL CENTROIDE DE UNA PELOTA

Muy buenas en el próximo reto vamos a volver con las pelotas de frontenis, y vamos a intentar hallar la posición del centroide de las pelotas.  En una primera idea, primero obtendremos como siempre el perímetro de la pelota, para posteriormente calcular el centroide. Bueno como siempre si a alguien se le ocurre formas de como hacerlo que escriba un comentario.

domingo, 6 de marzo de 2011

SOLUCIÓN RETO VIII 2:QUITAR EL FONDO

Bueno al final lo he conseguido.   El método que voy a contar ahora aparece en el libro learning openCV de Gary Bradski &Adrian kaehler, es el Averaging Background Method (Método de promedio de fondo).  Básicamente aprende la media y la desviación estándar de cada pixel como su modelo del fondo.

En primer lugar se crean punteros a varias imágenes de ceros y estadísticas de mantenimiento que se utilizaran a lo largo del proceso.

//Almacenamiento global
//
//Float,Imagen de tres canales
//
IplImage *IavgF,*IdiffF, *IprevF, *IhiF, *IlowF;
IplImage *Iscratch,*Iscratch2;
//Float, Imagen de un canal
//
IplImage *Igray1,*Igray2, *Igray3;
IplImage *Ilow1, *Ilow2, *Ilow3;
IplImage *Ihi1, *Ihi2, *Ihi3;
// Byte, Imagen de un canal
//
IplImage *Imaskt;
//Cuenta el numero de imágenes para la media posterior
//
float Icount;
Después creamos todas estas imagenes, esto lo hacemos con esta función a la cual le pasamos nuestra imagen.

void AllocateImages( IplImage* I )
{
    CvSize sz = cvGetSize( I );
    IavgF = cvCreateImage( sz, IPL_DEPTH_32F, 3 );
    IdiffF = cvCreateImage( sz, IPL_DEPTH_32F, 3 );
    IprevF = cvCreateImage( sz, IPL_DEPTH_32F, 3 );
    IhiF = cvCreateImage( sz, IPL_DEPTH_32F, 3 );
    IlowF = cvCreateImage( sz, IPL_DEPTH_32F, 3 );
    Ilow1 = cvCreateImage( sz, IPL_DEPTH_32F, 1 );
    Ilow2 = cvCreateImage( sz, IPL_DEPTH_32F, 1 );
    Ilow3 = cvCreateImage( sz, IPL_DEPTH_32F, 1 );
    Ihi1 = cvCreateImage( sz, IPL_DEPTH_32F, 1 );
    Ihi2 = cvCreateImage( sz, IPL_DEPTH_32F, 1 );
    Ihi3 = cvCreateImage( sz, IPL_DEPTH_32F, 1 );
    cvZero( IavgF );
    cvZero( IdiffF );
    cvZero( IprevF );
    cvZero( IhiF );
    cvZero( IlowF );
    Icount = 0.00001; //Protección contra la división por cero
    Iscratch = cvCreateImage( sz, IPL_DEPTH_32F, 3 );
    Iscratch2 = cvCreateImage( sz, IPL_DEPTH_32F, 3 );
    Igray1 = cvCreateImage( sz, IPL_DEPTH_32F, 1 );
    Igray2 = cvCreateImage( sz, IPL_DEPTH_32F, 1 );
    Igray3 = cvCreateImage( sz, IPL_DEPTH_32F, 1 );
    Imaskt = cvCreateImage( sz, IPL_DEPTH_8U, 1 );
    cvZero( Iscratch );
    cvZero( Iscratch2 );
}

Después aprendemos la estadística del fondo para más de una imagen. O sea llamaremos esta función unas cuantas veces.  Yo la he llamado unas 500.  Al principio del algoritmo, así aprendemos el fondo.  En este momento no debe aparecer ningún primer plano.
void accumulateBackground( IplImage *I )
{
    static int first = 1; // nb. Not thread safe
    cvCvtScale( I, Iscratch, 1, 0 ); // convert to float
    if( !first )
    {
        cvAcc( Iscratch, IavgF );
        cvAbsDiff( Iscratch, IprevF, Iscratch2 );
        cvAcc( Iscratch2, IdiffF );
        Icount += 1.0;
    }
    first = 0;
    cvCopy( Iscratch, IprevF );
}
Una vez que tenemos acumulados unos cuantos frames, creamos nuestro modelo estadístico del fondo de la imagen.
void createModelsfromStats()
{
    cvConvertScale( IavgF, IavgF,( double)(1.0/Icount) );
    cvConvertScale( IdiffF, IdiffF,(double)(1.0/Icount) );
    //Nos aseguramos de que la diferencia siempre es a alguna cosa
    cvAddS( IdiffF, cvScalar( 1.0, 1.0, 1.0), IdiffF );
    setHighThreshold( 7.0 );
    setLowThreshold( 6.0 );
}

A partir de aquí ya podemos ir evaluando frame a frame nuestras imagenes e ir separando el fondo del primer plano.  Para esto nos crearemos una máscara, negro fondo y blanco primer plano, y a partir de aquí lo podremos utilizar.
void setHighThreshold( float scale )
{
    cvConvertScale( IdiffF, Iscratch, scale );
    cvAdd( Iscratch, IavgF, IhiF );
    cvSplit( IhiF, Ihi1, Ihi2, Ihi3, 0 );
}
void setLowThreshold( float scale )
{
    cvConvertScale( IdiffF, Iscratch, scale );   
    cvSub( IavgF, Iscratch, IlowF );
    cvSplit( IlowF, Ilow1, Ilow2, Ilow3, 0 );
}

Con esta función es con la que creamos la máscara que discrimina el fondo del primer plano.

void backgroundDiff(IplImage *I, IplImage *Imask)
{
   
    cvCvtScale(I,Iscratch,1,0); // To float;
    cvSplit( Iscratch, Igray1,Igray2,Igray3, 0 );
    //Canal 1
    //
    cvInRange(Igray1,Ilow1,Ihi1,Imask);
    //Canal 2
    //
    cvInRange(Igray2,Ilow2,Ihi2,Imaskt);
    cvOr(Imask,Imaskt,Imask);
    //Canal 3
    //
    cvInRange(Igray3,Ilow3,Ihi3,Imaskt);
    cvOr(Imask,Imaskt,Imask);
    //Invertimos el resultado
    //
    cvSubRS( Imask, cvScalar(255,255,255), Imask);   
}
Al final del todo liberamos la memoria de todas las imágenes.

void DeallocateImages()
{
    cvReleaseImage( &IavgF);
    cvReleaseImage( &IdiffF );
    cvReleaseImage( &IprevF );
    cvReleaseImage( &IhiF );
    cvReleaseImage( &IlowF );
    cvReleaseImage( &Ilow1 );
    cvReleaseImage( &Ilow2 );
    cvReleaseImage( &Ilow3 );
    cvReleaseImage( &Ihi1 );
    cvReleaseImage( &Ihi2 );
    cvReleaseImage( &Ihi3 );
    cvReleaseImage( &Iscratch );
    cvReleaseImage( &Iscratch2 );
    cvReleaseImage( &Igray1 );
    cvReleaseImage( &Igray2 );
    cvReleaseImage( &Igray3 );
    cvReleaseImage( &Imaskt);
}

Si queréis saber más sobre este método está descrito en el libro Learning OpenCV de Gary Bradski y Adrian Kaehler

lunes, 24 de enero de 2011

Trabajo sobre Pixeles

Muy buenas, hace unos días una lectora me ha preguntado como trabajo con pixeles, como aplico la transformada de hough, y estas cosas, pues para esto he decidido crear un nuevo post, en el que explico todo esto.
Cuando aplico la transformada de hough, yo realmente no trabajo sobre pixeles directamente, si no que lo hace OpenCV por mi.  La función que aplico es esta, 
CvSeq* cvHoughCircles(
CvArr* image,    //Imagen en escala de grises
void* circle_storage, //Almacenamiento
int method,   //método
double dp,  //Resolución del acumulador
double min_dist, //Minima distancia entre circulos
double param1 = 100,  //Umbral del algoritmo de Cany
double param2 = 300,  //Acumulador del umbral del algoritmo de canny
int min_radius = 0,  //Minimo radio de un circulo
int max_radius = 0  //Máximo radio de un circulo
);
Esta función me devuelve una secuencia, que son los círculos que tengo en la imagen, para esto introducimos una imagen en blanco y negro (image), a esta imagen el propio algoritmo de la función aplica un detector de bordes y posteriormente la ecuación de la circunferencia par detectar los círculos, los cuales los guarda en el cirgle_storage y yo los capturo con    float* p = (float*) cvGetSeqElem( results, i ); a la cual le paso el CvSeq que me ha devuelta la función cvHoughCircles. Es lo bueno que tiene OpenCV que el trabajo arduo con pixeles viene resuelto.
Bien pero si quiero trabajar directamente con pixeles tenemos que conocer como guarda openCV una imagen, este la guarda como si fuera un vector y para capturar cada pixel necesitamos una serie de datos que estan en la imagen.
Entonces en la imagen tenemos el ancho, el alto, el paso y los canales, esta información la necesitaremos y los propios datos.  Par explicarlo pongo un ejemplo:
IplImage* frame;
height= frame->height;
width= frame->width;
step= frame->widthStep/sizeof(uchar);
channels   = frame->nChannels;
data = (uchar *)frame->imageData;
for(i = 0; i <height; i++ ) {
          for(j = 0; j <width; j++ ) {
                        data_hsv[i*step+j*channels+1]
                        data_hsv[i*step+j*channels+2]
                        data_hsv[i*step+j*channels+3]

}
}
donde 1 2 y 3 es el RGB de color.

     
 

jueves, 6 de enero de 2011

SOLUCIÓN RETO VIII:QUITAR EL FONDO

Después de haber trabajado mucho en esto me doy por rendido, he intentado aplicar muchas soluciones pero ninguna me daba buenos resultados, la que mejor me ha parecido aunque iba muy lento en mi ordenador de forma que era inviable, me imagino que por el poder de cálculo de mi ordenador, es un método que utiliza codebook, página 278 del libro learning OpenCV, si ha alguno se le ocurre otras mejores formas de utilizar éste o cualquier otro método que me lo diga.  Yo sigo trabajando en esto paralelamente a otros retos que iremos resolviendo, cuando tenga algo mejor lo publicaré.

jueves, 2 de diciembre de 2010

RETO VIII:QUITAR EL FONDO

En este reto vamos a quitar el fondo a nuestras imágenes de la cámara, ayudados por las funciones de OpenCV que realizan esto mismo.  Esto lo haremos buscando las similitudes entre píxeles.  De esta forma quitaremos de nuestra imagen todos los elementos que no nos importan, en el caso que llevamos en estos días son todos los elementos que no son caras, y que pertenecen al fondo de la imagen.  En mi caso a veces tenemos falsos positivos con partes del fondo, en concreto unos cuadros que hay en la pared de mi cuarto, si antes de intentar detectar caras los suprimo de la imagen pues ya no me darán un falso positivo, bueno o eso espero je je je.

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.