martes, 24 de agosto de 2010

SOLUCIÓN RETO I: DETECCIÓN DE MONEDAS

Hola muy buenas a todos, ya se que daba una semana para resolver el primer reto, pero bueno, al final ha sido más fácil de lo que pensaba.  Esto ha sido así gracias a que la librería OpenCV ya tiene implementados un montón de algoritmos, y entre ellos el de reconocer círculos en una imagen, el algoritmo de openCVHough para círculos. utilizado es la transformada de

La primitiva de esta función se la siguiente:
CvSeq* cvHoughCircles(
CvArr* image,
void* circle_storage,
int method,
double dp,
double min_dist,
double param1 = 100,
double param2 = 300,
int min_radius = 0,
int max_radius = 0
);
Esta función nos devuelve un puntero con los datos de la circunferencias encontradas, punto central y radio.
Los parámetros son en este orden, la imagen, esto indica si es un array de almacenamiento o una memoria (realmente esto no lo entiendo muy bien, continuaré estudiandolo je je je), el siguiente parámetro es el método, normalmente es CV_HOUGH_GRADIENT, el parámetro dp es la resolución del acumulador, y el siguiente parámetro es la distancia mínima entre dos círculos.  Los dos parámetros siguientes son el umbral y el acumulador del umbral del algoritmo de canny, y los dos últimos parámetros son el mínimo y el máximo radio de las circunferencias.
Bien vale, para resolver el problema he seguido esta estructura,
  1. Leer imagen, la imagen en escala de grises.
  2. Suavizar imagen
  3. Aplicar el algoritmo de hough para circulos
  4. Dibujar los círculos de la imagen.
A parte he leído también la imagen en color para así dibujar los círculos sobre esta, y he tenido que escalarla, ya que si no las imagenes se me hacían muy grandes.
El código en C que he creado es el siguiente:

#include <cv.h>
#include <highgui.h>
#include <math.h>
void main ()
{   
    int percent=25;
    // Leemos la imagen
    IplImage* imagen = cvLoadImage("DSCN1882.jpg", CV_LOAD_IMAGE_GRAYSCALE);
    //Leemos imagen en color
    IplImage* imagenColor = cvLoadImage("DSCN1882.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
        100,                //Umbral del algoritmo canny
        100,                //Acumulador del umbral
        100,                //Minimo radio
        200                    //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)
        );
    }
    //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 he obtenido es:
Imagen con las dos monedas detectadas



Bueno esta es mi solución, os invito a proponer otras soluciones, seguramente mucho mejores que esta.

14 comentarios:

  1. Hola, me encanta que hayan personas como tu y publiques estas cosas.

    He intentado compilar este codigo y me tira esta clase de errores:
    too few arguments to function `cvLoadImage'
    stray '\241' in program

    Soy una novata en esto, me podrías ayudar con este pequeño problema?

    ResponderEliminar
  2. Hola muy buenas, ¿en cuál de los cvLoadImage te pasa eso? Igual es que estamos utilizando versiones distintas de OpenCV o puede ser el compilador, yo uso visual C++. Te paso todos los parámetros que tiene esta función a ver si se soluciona el problema. La directiva de la función es esta. IpImage* cvLoadImage(const char*filename, int iscolor=CV_LOAD_IMAGE_COLOR);
    Esta función tiene dos parámetros el primero es la propia imagen y el segundo es, si es color o blanco y negro. En visual C++ si no pongo nada lo coge como que es en color ya que la directiva asi lo tiene, igual otros compiladores no lo aceptan y tienes que poner CV_LOAD_IMAGE_COLOR en el segundo parámetro. Esto es para el segundo cvLoadImage que hay en el código, que es en el que he puesto solo un parámetro.
    Espero que te sirva, si no habrá que mirar a ver si hay más parámetros en las nuevas versiones de OpenCV. Ya me iras contando.

    ResponderEliminar
  3. Gracias por responder.
    Bueno, si tienes razón, yo ocupo el Devcpp y no acepta dejarlo en blanco, pero ya lo solucione con tu dato, el otro problema que tenía era por las "ñ" de ahi provenían los :stray '\241.

    ahora me tira error en el :
    cvSmooth(imagenPequeña, imagenPequeña, CV_GAUSSIAN, 5, 5 );
    y en el for tb tengo problema.

    Estos son los errores:
    In function `main':
    error: too few arguments to function `cvSmooth'
    error: 'for' loop initial declaration used outside C99 mode
    error: too few arguments to function `cvCircle'
    warning: return type of 'main' is not `int'

    espero que no te moleste que te pregunte.
    Ojala me puedas ayudar.

    gracias.

    ResponderEliminar
  4. solucione lo de C99, pero como utilizo el devcpp no sé que parametros debo poner en los espacios que tu dejaste en blanco para que me funcione correctamente, tanto en cvSmooth y en cvCircle.
    Me puedes ayudar con eso , por favor.

    Gracias :)

    ResponderEliminar
  5. Muy buenas, gracias a ti por leer mi blog, y por su puesto no me importa contestarte a todas tus preguntas lo más mínimo.
    Las directivas de las funciones son estas
    void cvSmooth( const CvArr* src, CvArr* dst,
    int smoothtype=CV_GAUSSIAN,
    int param1=3, int param2=0,
    double param3=0,
    double param4=0);
    void cvCircle(CvArr* array,CvPoint center,
    int radius, CvScalar color,
    int thickness =1,
    int connectivity = 8);
    El tema del warning lo miraré ya que en principio devolvemos nada. Pero lo miraré no vaya a ser que en devcpp se escriba la función main de otra manera.
    Bueno ya me contaras si lo haces funcionar.

    ResponderEliminar
  6. Hola, que bueno que no te molesten mis dudas y tu blog es buenísimo :D

    Te cuento, si me compila , pero lamentablemente no obtengo el reconocimiento de las monedas, solo me muestra la imagen a color... creo que el problema está los parámetros que pongo en las funciones que te mencione anteriormente ya que tu pones: cvSmooth(imagenPequeña, imagenPequeña, CV_GAUSSIAN, 5, 5 );
    omites 2 parametros, al = que la funcion cvCircle.
    yo con Devcpp estoy obliga a ponerlos.

    y tengo una duda, en cvSmoothporque pones en el primer y segundo parametro la imagenpequeña, en el segundo no deberia ser distinto o no?? me podrías explicar el porque y ayudar para obtener el reconocimiento, te lo agradeceria mucho.

    Bueno , espero tu respuesta, mientras me iré al reto || , que es el que mas me interesa implementar y entender.

    Estamos en contacto.

    ResponderEliminar
  7. Hola muy buenas:
    Bueno creo que para que te detecte las monedas vas a tener que jugar un poco con los parámetros de cvHoughCircles, te los escribo.
    CvSeq* cvHoughCircles(
    CvArr* image,
    void* circle_storage,
    int method,
    double dp,
    double min_dist,
    double param1 = 100,
    double param2 = 300,
    int min_radius = 0,
    int max_radius = 0
    );
    El primer parámetro es la imágen en escala de grises, yo la he llamado imagen pequeña, después pasas el lugar donde almacena, luego metes el método, y el acumulador. El siguiente parámetro es la distancia entre dos círculos, le estas diciendo cual es la mínima distancia en la que te pueden aparecer círculos, este es uno de los parámetros para jugar con el. Después metes dos parámetros mas que son el umbral del algoritmo de canny y el acumulador del umbra, esto es por que la función también nos hace un detecctor de bordes, en este caso canny, otros parámetros con los que jugar. Y los dos últimos parámetros son el mínimo rádio y el máximo rádio que pueden tener nunestros círculos, y por supuesto también jugaría con estos.
    Las funciones que me comentas yo dejo los parámetros sin poner, ya que lo que hago con eso es tomar ese parámetro como el que tiene la función por defecto, o sea si te fijas en las directivas te pone el nombre de la variable e = a lo que sea, ese lo sea es el parámetro que cojo.

    En el Cvsmooth paso la misma variable por ahorrar memoria, se puede poner otra variable y lugo pasar esta última al algoritmo de hough. Esta función la uso para reducir ruido, debería funcionar sin ella.

    En el cvCircle los parámetros que no pongo son el ancho de linea el tipo y el shift, ya que pongo los que vienen por defecto que son 1, 8 y 0.
    Para empezar a jugar y ver si me reconoce circulos, luego ya ajustaremos a las monedas, empezaria por lao dos últimos parámetros, o sea empezaría por un minímo muy pequeño y un máximo grande, me fijaría en las monedas las coje o no, si no las coje por que son muy pequeñas y el mínimo es mayor o por que son muy grandes y así sucesivamente. Luego iria acercando el máximo y el mínimo para solo tener las monedas.
    Espero que te sirva y animo.

    ResponderEliminar
  8. gracias por tu ayuda, intentaré seguir todos tus consejos :)

    estoy intrigada en cómo trabajas con los pixeles de una imagen, ya sea de una foto o de la que obtienes de la camara, me gustaria poder detectar circulos de una imagen cualquiera, lo mismo que de la pelota en la camara..
    pero para eso debo saber como trabajar con pixeles...
    te molestaria mucho que me hicieras una pequeña clase, mas bien una explicación de cómo lo haces y de como aplicas la transformada de hought.

    He estado investigando sobre Hought y encontre cosas como para coordenadas polares y la Transformada Circular de Hought que es la formula tipica de los circulos y que es la que aplicas y la que me interesa.

    yo veo tu algoritmo y no lo relaciono con la formula, como lo haces? me podrías explicar como accedes a los pixeles o los reconoces y como aplicas la fórmula?

    Son muchas preguntas, lo sé, probablemente no tengas el tiempo para responderme,entendería si no quisieras responder, ya que tb, quizas, estoy abusando de tu buena voluntad que has tenido hasta ahora, pero necesito aprender y estoy complicada ...

    De todas maneras estoy muy agradecida de que me respondas todas mis dudas :)
    Estamos en contacto .

    ResponderEliminar
  9. Bueno como hace dias que no publico nada, pues he decidido publicarlo en un nuevo post, ya que creo que es mucha información para un comentario. En breves tendre publicado ese post. Aunque ahora te adelanto un poquito, el trabajo sobre pixels lo hace la función cvHoughCircles(), es lo bueno que tiene OpenCV que ya te da resuelto eso. Pero por supuesto, si quieres también se puede trabajar con pixeles, en el reto de la pelota trabajo directamente sobre pixeles, y eso es lo que contaré en el post, ya que primero hay que saber como almacena una imagen y luego como leemos cada pixel.

    ResponderEliminar
  10. Gracias :D
    Estaré muy atenta a tu nuevo post.

    y disculpa por mis preguntas.

    ResponderEliminar
  11. Muy buenas he creado el post ya contestandote, un poco sobre esto. Ya me diras si te he contestado a todo o te gustaria que profundizara en alguna cosilla.

    ResponderEliminar
  12. Muchas gracias!!! es de mucha ayuda
    Cualquier duda te comento.

    ResponderEliminar
  13. podrias ayudarme con la instalacion de opencv en windows??
    seria una ayuda inmensa.
    gracias

    ResponderEliminar
  14. Aqui te dejo un enlace:

    http://opencv.willowgarage.com/wiki/InstallGuide

    ResponderEliminar