lunes, 7 de febrero de 2011

Filtro de detección de bordes (Canny) usando OpenCV

La detección de bordes, en procesamiento digital de imágenes, nos permite averiguar los contornos de la imagen o, mejor dicho, los cambios bruscos de intensidad lumínica. Es por esto último por lo que no se recomienda usar un filtro de detección de bordes tras haber suavizado con una amplitud grande una imagen a no ser que se use una apertura superior en el filtro Canny.

La función que realiza el filtro de suavizado de imagen en OpenCV es cvCanny.

El código fuente del ejemplo es:


El resultado obtenido tras ejecutar el código de ejemplo es:
Filtro Canny usando OpenCV

38 comentarios:

  1. Holaa.. tenemos que hacer un proyecto como el que propones, con la diferencia de que no usamos imágenes en tiempo real sino imágenes guardadas en la memoria de una FPGA..
    Debemos usar el algoritmo de canny para detectar los bordes de la imagen, no hemos usado nunca opencv y no se si se pueda usar directamente la función cvcanny.
    también queremos saber como puedo cargar la imagen usando alguna función de opencv

    ResponderEliminar
  2. Hola Politapazvil.

    Para obtener fotogramas desde un archivo de vídeo guardado en disco (que sea compatible con ffmpeg en linux, quicktime en macos o con video for windows bajo windows) basta sustituir la función "cvCaptureFromCAM" por "cvCaptureFromFile" y listo:
    http://opencv.willowgarage.com/documentation/reading_and_writing_images_and_video.html#capturefromfile

    Si lo que quieres es leer un único archivo (y no vídeo) deberás usar la función "cvLoadImage":
    http://opencv.willowgarage.com/documentation/reading_and_writing_images_and_video.html#loadimage

    Espero que te sea de utilidad.

    ResponderEliminar
  3. muchas gracias =)

    otra pregunta..
    estamos usando una FPGA para hacer el proyecto con procesador DE2, utilizamos el programa nios II for eclipse y no sabemos como agregrar las librerias de opencv a este programa..!!

    http://sourceforge.net/projects/opencvlibrary/
    de este link me baje y lo ejecute en la máquina pero no sabemos como incluir las librerías en el programa (eclipse)

    estas librerías que nos bajamos son las correctas?? o en tu caso cual usaste y que proceso hiciste para la instalación??

    te agradecemos mucho tu ayuda :)

    ResponderEliminar
  4. Hola que tal, quiero saber si me puedes ayudar con algo, tengo una imagen cargada en la memoria ram, de la cual se la posicion inicial de la memoria, quiero saber como puedo hacer para cargar la imagen desde memoria, osea usando solo la direccion conocida, para luego poder detectarle el borde con la ayuda de CvCanny

    ResponderEliminar
  5. Paola, tal y como puedes ver en los comentarios del código fuente yo he usado Linux para compilar la aplicación. En Linux basta con ejecutar `pkg-config --cflags opencv` `pkg-config --libs opencv` para que te devuelva los parámetros de línea de comandos que deben pasarse a gcc para que se incluyan tanto las librerías como parámetros de compilación.

    Uso eclipse para programar aplicaciones para Android, pero no lo he usado nunca para compilar aplicaciones C ni C++. Debe haber algún menú en el que incluir los parámetros necesarios de compilación al compilador (el famoso -lxxx en el caso de usar gcc).

    Yo he compilado aplicaciones OpenCV para Windows usando cygwin + gcc por lo que, de nuevo, lo hago sin usar eclipse.

    Siento no poder ayudarte en ese aspecto.

    ResponderEliminar
  6. DIGITAL_69, tu caso es algo más complejo pq depende principalmente del formato en el que tengas los datos de la imagen. (PD: Repito el mensaje de nuevo pq el comentario estaba corrupto por usar > y < para algunas cosas).

    La mayoría de las webcam suelen devolver fotogramas en formatos que deben decodificarse o adaptarse antes de usar: compresión JPEG, cambio en el orden de los valores RGB, formatos de 8bits como RGB332, etc.

    De esta tarea se ocupa OpenCV de manera transparente al usuario, pero si vas a usar datos que están ya en memoria entonces debes crearte la estructura mediante:

    IplImage* miImagen = cvCreateImageHeader(cvSize(ancho, alto), IPL_DEPTH_8U, <bytes por pixel>);
    cvSetData(miImagen, puntero, ancho * <bytes por pixel>);

    Posteriormente deberás liberar la imagen creada con cvCreateImageHeader con la función cvReleaseImageHeader. No olvides liberar también la memoria del fotograma de manera independiente.

    En <bytes por pixel> deberás poner 1 para imágenes en escala de gris y 3 para imágenes RGB.

    En particular, para el filtro canny, la imagen debe estar en escala de grises. Para eso hago el cvConvertImage en mi aplicación.

    Espero que tengas suerte, ya me contarás si te funciona.

    ResponderEliminar
  7. Gracias Oscar.. me es de mucha ayuda.. pondre manos a la obra..!!

    ResponderEliminar
  8. No puedo correr el programa porque me marca un error:
    fatal error C1083: No se puede abrir el archivo incluir: 'unistd.h': No such file or directory

    podrias ayudarme con eso?

    ResponderEliminar
  9. Hola Pao, ¿podrías darme algo más de información? Como mínimo la plataforma sobre la que estás compilando: Linux, (Free)BSD, Windows, MacOS, AIX, HP-UX, Solaris, etc (en mi caso uso Ubuntu Linux y Windows XP). Otra cosa que me será de utilidad es conocer qué compilador estás usando (en mi caso uso GNU Compiler Collection - gcc tanto para Linux como para Windows). Por lo que me cuentas (no encontrar el archivo unistd.h) parece que no estás usando gcc bajo linux o bajo cygwin. En ese caso creo que puedes quitar unistd.h y compilar sin problemas. Esa librería suelo usarla para levantar procesos (fork), esperas (usleep, pause, etc) que creo que no he usado en este programa en particular.

    ResponderEliminar
  10. Hola somos unos estudiantes que ahora mismo estamos probando a ejecutar la función de canny que la necesitamos para un proyecto que estamos realizando, al ejecutar el proyecto que pones por consola hay un parámetro que no sabemos que hay que poner: dispositivo, podrías decirnos que habría que poner? es para probar la función en sí que estamos un poco perdidos. Muchas Gracias.

    otra duda la librería unistd es de linux hay alguna que sea así para windows?
    Un saludo.

    ResponderEliminar
    Respuestas
    1. Hola de nuevo Cristina. Releyendo el blog me he dado cuenta que te refieres al parámetro opcional llamado "[#dispositivo]". Al llevar el # delante se refiere al número (o índice) de dispositivo de captura. Por norma general en Linux el dispositivo número 0 es /dev/video0, el 1 es /dev/video1, y así sucesivamente. En Windows es el número de dispositivo de captura en el orden tal y como se enumeran en DirectShow.

      Si no pones nada en ese parámetro se usará el dispositivo de captura por defecto. Si sólo tienes una webcam conectada te funcionará a la primera, si tienes una capturadora de vídeo u otras webcams entonces deberás probar diferentes números hasta que des con la que deseas.

      Eliminar
  11. Cristina, el tema del unistd.h del que me hablas lo respondo justo encima de tu comentario: "En ese caso creo que puedes quitar unistd.h y compilar sin problemas. Esa librería suelo usarla para levantar procesos (fork), esperas (usleep, pause, etc) que creo que no he usado en este programa en particular."

    El tema de la variable dispositivo tenemos que tratarla en dos partes:

    Primera: La variable está definida como "CvCapture *dispositivo = NULL;". Es decir, es un puntero a un dispositivo de captura de OpenCV.

    Segunda: El contenido de dicha variable se obtiene llamando las funciones de OpenCV cvCaptureFrom* (en este caso "dispositivo = cvCaptureFromCAM(ndispositivo);"). Donde ndispositivo es el número del dispositivo de captura de vídeo que queramos usar (si tenemos varias webcams o capturadoras de vídeo podríamos seleccionar cualquiera de ellas).

    Espero haberte respondido la pregunta.

    ResponderEliminar
  12. Hola he visto tu programa y he intentado compilarlo en windows usando visual estudio 2012 y opencv, sin modificar nada no me compila y modificando algunas cosas tampoco, como puedo hacer para compilarlo? necesito algun fichero extra? El programa lo quiero usar para una parte de una practica de la uni, muchas gracias!!

    ResponderEliminar
    Respuestas
    1. Hola Arturo. Necesito que me copies/pegues el/los mensaje/s de error. Lo que te está ocurriendo está en ese texto (aunque parezca un texto incompresible podrá arrojar luz al problema).

      ¿Otros ejemplos de OpenCV te funcionan? ¿Empezando de cero (copiando/pegando el código en un .c) o usando la estructura de archivos que te ofrece un archivo comprimido?

      Yo trabajo siempre con Eclipse/Linux, pero en el trabajo tengo un Windows 7 en el que podría probar para reproducir el error (usando la versión express).

      Eliminar
    2. Hola Oscar gracias por responder y perdona por la tardanza, esto es lo que me aparece:
      1>c:\users\arturo\desktop\filtro_canny.cpp(21): warning C4627: '#include ': skipped when looking for precompiled header use
      1> Add directive to 'StdAfx.h' or rebuild precompiled header
      1>c:\users\arturo\desktop\filtro_canny.cpp(23): warning C4627: '#include ': skipped when looking for precompiled header use
      1> Add directive to 'StdAfx.h' or rebuild precompiled header
      1>c:\users\arturo\desktop\filtro_canny.cpp(24): warning C4627: '#include ': skipped when looking for precompiled header use
      1> Add directive to 'StdAfx.h' or rebuild precompiled header
      1>c:\users\arturo\desktop\filtro_canny.cpp(25): warning C4627: '#include ': skipped when looking for precompiled header use
      1> Add directive to 'StdAfx.h' or rebuild precompiled header
      1>c:\users\arturo\desktop\filtro_canny.cpp(111): fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "StdAfx.h"' to your source?
      ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
      El .c lo paso a .cpp puesto que cuando creo el programa uso win32consoleaplication.
      puede que tenga que añadir esos .h no?
      muchas gracias!

      Eliminar
    3. Por un lado es un error de precompilación. Parece que hay que desactivar la opción "Usar cabeceras precompiladas" dentro de las opciones del proyecto.

      De todas formas no sé qué hace esa librería (stdafx.h). Quizá deberías cambiar la extensión de .cpp a .c para que trate el archivo por lo que es, C, y no C++.

      Eliminar
  13. hola, que tal me aparece un error con el basename que no esta declarado y la vdd estoy muy perdido, podrias ayudarme o mandarme alguna ayuda, muchas gracias

    ResponderEliminar
    Respuestas
    1. Por supuesto, ¿qué te ocurre? Copia y pega el error para que pueda intentar reproducirlo o, al menos, sepa exactamente qué mensaje de error te aparece.

      Así a bote pronto parece que ninguna cabecera cubre la definición de "basename" que suele estar en unistd.h (al menos en ubuntu 10.04 estaba así). Debes hacer un "man 3 basename" para que la ayuda de tu distribución de linux te indique qué cabecera debes usar.

      Ahora mismo estoy en ubuntu 12.04 y me sale esto:

      BASENAME(3) Linux Programmer's Manual BASENAME(3)
      NAME
      basename, dirname - parse pathname components

      SYNOPSIS
      #include <libgen.h>

      char *dirname(char *path);
      char *basename(char *path);


      Lo que significa que debes incluir la cabecera "libgen.h" en su lugar (o además).

      Prueba incluyendo esa cabecera y me cuentas.

      Eliminar
    2. Confirmado, debes agregar dicha cabecera al resto:

      #include <stdlib.h>
      #include <stdio.h>
      #include <unistd.h>
      #include <libgen.h>
      #include <cv.h>
      #include <highgui.h>

      En caso contrario me he encontrado con este mensaje de advertencia (debido a la declaración automática de función que, por defecto, todas devuelven un entero):

      redstar@greystar:~/Descargas$ gcc filtro_canny.c -o filtro_canny `pkg-config --cflags --libs opencv`
      filtro_canny.c: En la función ‘main’:
      filtro_canny.c:36:69: aviso: conversión a puntero desde un entero de tamaño diferente [-Wint-to-pointer-cast]
      redstar@greystar:~/Descargas$ ./filtro_canny
      Violación de segmento (`core' generado)

      Sin embargo una vez incluida la cabecera la compilación y funcionamiento es el esperado:

      redstar@greystar:~/Descargas$ gcc filtro_canny.c -o filtro_canny `pkg-config --cflags --libs opencv`
      redstar@greystar:~/Descargas$ ./filtro_canny 0
      Uso: filtro_canny <umbral1> <umbral2> <apertura> [#dispositivo]

      Un saludo.

      Eliminar
    3. llego a este resultado y??? quiero que me aparezca la cam y aplicando la detección de bordes, como hago??

      Eliminar
    4. Entonces todo está bien y funcionando, esa es la ayuda de uso.

      El filtro Canny necesita tres parámetros: dos umbrales y la apertura.

      Por decir algo, puedes probar con: ./filtro_canny 40 120 5

      Dependiendo del brillo de la imagen, calidad de la cámara, etc deberás ir ajustando esos umbrales para que den el resultado que deseas.

      Para más información sobre esos umbrales y el algoritmo, aquí te dejo el enlace a la nueva documentación de OpenCV (el enlace del mensaje ha dejado de funcionar, lo corregiré en cuanto tenga un hueco):
      http://docs.opencv.org/modules/imgproc/doc/feature_detection.html#canny

      Eliminar
  14. Muchas Gracias :D Me funciono correctacmente, un saludo.

    ResponderEliminar
    Respuestas
    1. De nada, un placer :D

      Me alegra saber que aún sigue siendo de utilidad este blog, me anima a seguir subiendo y redactando cosas (aunque últimamente el proyecto fin de máster me absorbe todo el tiempo libre).

      Un saludo.

      Eliminar
  15. La verdad estoy muy agradecida contigo :D Muchas gracias por tomarte el tiempo para responderme :) Se ejecuto correctamente :3 Muchos Saludos.

    ResponderEliminar
  16. Hola amigo, me sirvio de mucho tu codigo, de antemano gracias, una molestia. crees que me puedas pasar el diagrama de flujo de como funciona tu programa? gracias

    ResponderEliminar
    Respuestas
    1. ¡Claro! Yo te pongo los puntos y tú lo dibujas en el programa de dibujo que más te guste. Si quieres detalle línea a línea de código házmelo saber, si te vale a grandes rasgos esto es lo que hace el programa:

      1.- Carga desde los parámetros todos los datos del filtro (umbrales y apertura) y cámara deseada.
      2.- Configura y prepara la cámara deseada.
      3.- Prepara una ventana donde mostrar el resultado.
      4.- (bucle) Obtenemos un fotograma de la cámara.
      5.- Si no hemos preparado previamente las superficies del resultado del filtro y la imagen en escala de grises, lo hacemos (sólo se hará, entonces, una única vez).
      6.- Convierte el fotograma a escala de grises.
      7.- Calcula el resultado del filtro Canny sobre la imagen en escala de grises.
      8.- Creamos una región de interés (ROI) en la parte inferior derecha de la imagen del resultado del filtro Canny.
      9.- Metemos la imagen en escala de grises en el interior de esa región de interés usando un proceso de reescalado (resize). Si hiciéramos una copia entonces sólo saldría la parte superior izquierda de la imagen en escala de grises, el resto quedaría recortado fuera del área de interés.
      10.- Eliminamos la región de interés (que no significa que también borremos su contenido).
      11.- Mostramos el resultado del montaje "Resultado del filtro Canny + Imagen en escala de grises en pequeño".
      12.- Durante un periodo de 100 ms esperamos la pulsación de una tecla (si pasa ese intervalo de tiempo y no se ha pulsado ninguna tecla devolverá un valor -1).
      13.- Si la tecla pulsada no ha sido "ESC" volvemos a ejecutar todo desde el punto (4).
      14.- Liberamos las superficies donde metíamos los resultados del filtro y la imagen en escala de grises.
      15.- Destruimos (cerramos) la ventana donde dibujábamos el resultado.
      16.- Liberamos el dispositivo de captura (la cámara).

      Espero que te sea de ayuda. Verás que el diagrama es muy sencillo, sinceramente no entiendo qué parte del código no entiendes sin un diagrama de flujo.

      Eliminar
    2. PD: Al final te he hecho un diagrama con una relación línea de código a punto que es casi de 1 a 1.

      Eliminar
  17. Oscar muy bueno el bolg.
    Toy trabajando en un proyecto donde básicamente se quiere identificar de una imagen las formas geometricas en con creto circulos, logre jalar la imagen colocarla en escala de grises ejecutar el algoritmo de gauss para los borde pero de ahi no puedo identificar el siguiente paso toy trabajando en python te muestro el código ojala me puedas ayudar.
    import cv2
    image=cv2.imread('c:\image\circulo.jpg')
    image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gauss = cv2.GaussianBlur( image_gray,(7, 7), 2, 2 )
    cv2.imwrite('c:\image\circulo1.jpg',image_gray)

    Bruno

    ResponderEliminar
    Respuestas
    1. Hola Bruno, muchas gracias por tus palabras, son muy bien recibidas.

      No entiendo cómo quieres usar un filtro de difuminado para encontrar círculos. Quizá te confundiste y querías usar el filtro de detección de bordes de Canny (el que usé en mi código):
      http://docs.opencv.org/trunk/modules/imgproc/doc/feature_detection.html?highlight=Canny#canny

      Ese sería el primer paso para detectar formas o perfiles, y no un difuminado de imagen, que lo que hace es precisamente todo lo contrario, hace que se pierdan los detalles de los bordes en las imágenes.

      Una vez que has conseguido detectar los bordes (deberás ajustar los umbrales hasta que te funcione con el menor ruido posible) debes usar la transformada de Hough, en particular OpenCV ofrece una variante de la transformada que encuentra círculos:
      http://docs.opencv.org/trunk/modules/imgproc/doc/feature_detection.html?highlight=HoughCircles#houghcircles

      Si estás buscando círculos de un color determinado entonces te aconsejaría que antes de aplicar el filtro Canny a tu imagen hicieras segmentación de color usando un filtro de color.

      Esta semana santa estaré completamente offline (sin cobertura de móvil ni portátil), pero me has animado a que el siguiente artículo que haga sea precisamente eso :) escribiré un ejemplo de cómo se hace (en C++, pero no creo que te cueste pasarlo a python).

      Un saludo.

      Eliminar
    2. Aquí tienes tu ejemplo:
      https://github.com/ojgarciab/opencv/blob/master/blog/ddc.cpp

      He visto por ahí que la gente usa un filtro gausiano para eliminar algo del ruido que tiene una imagen... pero si se hace un filtro de color el contraste se eleva debido a que lo que encaja en el filtro sale en blanco y lo que no en negro... eso significa eliminar prácticamente todo el ruido.

      He tenido que jugar un poco con los valores hasta encontrar uno que funcionara con mi cámara, luego subiré un vídeo (¿en una semana quizá?).

      Un saludo y que lo disfrutes :)

      Eliminar
  18. Muchas gracias por este tipo de información, es bastante util cuando uno se está iniciando en este tipo de programación.

    Este tipo de detector de bordes es muy util, pero quisiera saber -a modo de solicitud- qué tipo de detector podría usar para la detección de elementos tales como una circunferencia, quiero decir, ya no es todos los bordes de la imagen sino aquellos circulares.

    Muchas gracias y un saludo!

    ResponderEliminar
    Respuestas
    1. Hola Milton, ante todo gracias por leer mi blog.

      Justo un comentario antes del tuyo otra persona me preguntó algo similar y le expliqué cómo hacerlo e incluso puse código de ejemplo en mi GIT. Espero que te sirva.

      Hay que hacer un filtro gausiano para eliminar ruido y facilitar la transformada de Hough del círculo que usa OpenCV, que es una variante optimizada (de ahí el parámetro CV_HOUGH_GRADIENT) y, por lo tanto, parece que mejora cuando la imagen está borrosa (descubrí este detalle experimentando con el código estas últimas semanas). En un principio pensé que era porque la cámara era de gama baja, pero tengo que investigar más sobre el tema.

      En cuanto pueda escribiré un artículo contando mi experiencia y compartiendo más código.

      Eliminar
  19. Hola.... tengo k hacer un proyecto en el cual tengo k capturar una imagen, luego sacar su contorno...una vez obtenido el contorno debo introducir una imagen varias veces por dentro del mismo de manera k el espacio usado sea el mas optimo y no se desperdicie, porfavor me podrias ayudar con esto.....gracias

    ResponderEliminar
    Respuestas
    1. Disculpa, no me ha quedado claro lo que quieres hacer. ¿Tienes algún enlace a una imagen o documento que explique gráficamente lo que quieres hacer?

      Eliminar
    2. Hola primero muchas gracias por responder y la verdad no tengo imagen pero tratare de explicartelo de una mejor manera.......la idea es capturar una imagen mediante una camara y obtener su contorno es decir si por ejemplo captamos la imagen de una hoja A4 obtendriamos el contorno de un rectangulo de ciertas medidas ...... una vez obtenido este contorno debo de introducir una figura en su interior varias veces de tal forma k el espacio del rectangulo sea totalmente utilizado..... suponiendo que la figura k deseamos introducir es un cuadrado de lado 3mm por ejemplo , deberemos introducir varios cuadrados hasta poder llenar la forma del contorno de la hoja A4 .....que en otras palabras seria introducir todos los cuadrados posibles hasta ocupar en su totalidad el espacio del rectangulo.....claro los cudrados deberan contar con una cierta sepacion.....pero la idea es optimizar el espacio....muchas gracias por tu atencion y ojala me hayas podido entender....

      Eliminar
    3. Debes descomponer tu problema en problemas más pequeños. Algunos de ellos se pueden resolver con OpenCV, pero otros (obviamente) no.

      Por ejemplo, detectar el contorno de un folio se puede hacer de dos formas usando OpenCV.

      1.- Usando el filtro Canny http://docs.opencv.org/doc/tutorials/imgproc/imgtrans/canny_detector/canny_detector.html
      2.- Usando segmentación de color con inRange https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/in_range.md

      Una vez que tienes el contorno puedes usar un algoritmo de búsqueda de esquinas con (por ejemplo):
      * http://docs.opencv.org/doc/tutorials/features2d/trackingmotion/harris_detector/harris_detector.html
      * http://docs.opencv.org/doc/tutorials/features2d/trackingmotion/corner_subpixeles/corner_subpixeles.html

      A partir de ahí puedes hacer muchas cosas como corrección de perspectiva, convertir escala de imagen a escala en mm, etc... pero si no consigues empezar resolviendo la segmentación y detección de esquinas te va a ser difícil continuar con el resto de tu problema.

      Saludos.

      Eliminar
  20. cordial saludo!

    quien sabe manejar opn cv con eclipse?

    ResponderEliminar
    Respuestas
    1. Es el entorno de desarrollo que suelo usar (aunque he migrado a Android Studio para proyectos Android).

      ¿Qué duda tienes exactamente?

      Eliminar