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

2 comentarios:

  1. Muy bonito.
    Tengo algunas dudas.
    500 imágenes para extraer el fondo son muchas y eso lo hace lento, no?
    No se puede hacer dinámico? Me refiero a ir modificando el fondo conforme avanza el video.
    El ruido en la imagen no afecta? no lo reconoce como movimiento?

    P.D: Soy Rafa, hola ke tal!

    ResponderEliminar
  2. Tienes razón, se puede hacer más dinámico, así se salvaría el problema de cambios de iluminación. Lo que pasa es que lo he hecho así ya que en el momento en el que empiezo a utilizar el algoritmo yo se que solo es fondo. Si puedes garantizar que no hay primeros planos pues puedes hacerlo más dinámico. Hay otro método que lo hace más dinámico. Pero a mí me iba muy lento.

    ResponderEliminar