miércoles, 24 de marzo de 2010

Primer Paso: Conseguir las coordenadas (cartesianas) de las casillas

Una vez instalado OpenCV, el primer paso es pasarle una foto y conseguir las coordenadas 2D de las casillas del tablero.
Para obtener las coordenadas, hemos encontrado varios métodos. Comentaremos cada uno de ellos con sus "pros" y sus "contras":

1) Detección de Casillas mediante la función findSquares4 (encuentra cuadrados en una imagen)

Al pasarle una tablero pintado por ordenador (tablero perfecto), el método funciona perfectamente. Detecta todas las casillas.

Imagen de tablero perfecto

board 

Aplicando findSquares4:

findsquare3


Sin embargo, cuando le pasamos una imagen real (efectos de distorsión, perspectiva, problemas de iluminación...),  en el mejor de los casos (problemas de iluminación), detecta todas las casillas menos un par de ellas. La mayoría de las casillas no están definidas perfectamente, sino que hay un cierto margen de error apreciable.

Imagen real con problemas de iluminación

board

Aplicando findSquares4:

findsquare1

En el peor de los casos, detecta un par de casillas:

Imagen real con perspectiva

board

Aplicando findSquares4:

findsquare1 


    Es evidente que esta función no nos sirve para nuestros propósitos. Podríamos aplicar un algoritmo de reconstrucción si obtuviéramos bastantes casillas bien definidas, pero esto solo ocurre cuando la vista del tablero es desde arriba, no de frente.

"Pros" -> Detecta todos los cuadrados de una región con una simple función
"Contras" -> Los cuadrados en la imagen deben de ser perfectos en la imagen.

 

//Load img
IplImage* img = cvLoadImage(argv[1]);
//Storage
CvMemStorage* storage = cvCreateMemStorage(0);
 
// Find Squares
CvSeq* seq = findSquares4( img, storage );
 
// Print Squares
drawSquares( img, seq);
 
// Create an struct of square
typedef struct
{
    CvPoint* points;
} CvSquare;
 
//Create an array of squares
CvSquare* squares = (CvSquare*)malloc(NUM_SQUARES * sizeof(CvSquare));
 
// Get squares from sequence reading 4 sequence elements at a time (all vertices of a square)
CvSeqReader reader;
cvStartReadSeq( seq, &reader, 0 );
int i; // Counter for loops
 
for( i = 0; i < NUM_SQUARES; i ++ )
{
    CvPoint pt[4];
    
    // read 4 vertices
    CV_READ_SEQ_ELEM( pt[0], reader );
    CV_READ_SEQ_ELEM( pt[1], reader );
    CV_READ_SEQ_ELEM( pt[2], reader );
    CV_READ_SEQ_ELEM( pt[3], reader ); 
    
    CvSquare sq;
    sq.points = pt;
    squares[i] = sq;
 
    printf("Vertices: (%d, %d), (%d, %d), (%d, %d), (%d, %d) \n", pt[0].x, pt[0].y, pt[1].x, pt[1].y, 
pt[2].x, pt[2].y, pt[3].x, pt[3].y);
}




2) Detección de Casillas encontrando Líneas mediante cvHoughLines2


    Esta función encuentra líneas en una imagen, donde, dependiendo del algoritmo utilizado, puedes especificar la longitud mínima, resolución de las líneas, tamaño del acumulador para distinguir una línea de ruido, …).

    Para usar la función cvHoughLines2, que aplica la transformada de Hough para encontrar líneas, es necesario aplicarle la función cvCanny para encontrar bordes. La imagen ha de tener 1 solo canal (escala de grises). Es recomendable aplicarle antes un filtro smooth, ya que se obtienen mejores resultado*.

* Si alguien está interesado en la detección de líneas, puede ver el desarrollo del PFC de Nacho Rodriguez

    Al pasarle una tablero pintado por ordenador (tablero perfecto), el método funciona bastante bien. Detecta casi todas las líneas sin duplicar ninguna.


Imagen de tablero perfecto


board


Tras aplicarle cvHoughLines2:


cvHoughLines3



Cuando le pasamos una imagen real, en el mejor de los casos, detecta casi todo el tablero, aunque deja zonas vacías:


Imagen real con perspectiva


board


Tras aplicarle cvHoughLines2 :


cvHoughLines3


En el peor de los casos (en este caso, problemas de iluminación), no detecta ninguna zona donde la iluminación es escasa:


Imagen real con problemas de iluminación


board


Tras aplicarle cvHoughLines2:


cvHoughLines1


    Es evidente que esta función no nos sirve para nuestros propositos. Ni siquiera con un algoritmo de reconstrucción podriamos reconstruir las casillas.

"Pros" -> Detecta líneas para delimitar zonas grandes muy bien.
"Contras" -> La precisión no es muy buena. Suele asignar muchas líneas a una línea de la imagen.



//Load img
IplImage* img = cvLoadImage(argv[1]);
 
//Create gray-scale img 
IplImage* _gray = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
 
//Convert RGB img to GRAY
cvCvtColor(img, _gray, CV_BGR2GRAY);
 
//Threshold to delete noise
cvThreshold(_gray, _gray, 190, 255, CV_THRESH_BINARY);
 
//Smooth
cvSmooth(_gray, _gray, CV_GAUSSIAN, 5, 5);
//Canny Filter 
cvCanny(_gray, _gray, 120, 360, 3); 
 
//Create storage
CvMemStorage* storage = cvCreateMemStorage(0);
 
CvSeq* overheadLines = cvHoughLines2(_gray, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/360, 30, 10, 10); 



3) Detección de Casillas encontrando Esquinas mediante cvFindChessboardCorners

    Esta función encuentra las esquinas interiores de un tablero de ajedrez/damas, donde el tablero puede tener cualquier formato (5x6, 8x8,..).

Los argumentos que se le pasan son:
- const void* image -> Imagen donde está el tablero de ajedrez
- CvSize pattern_size -> Formato del tablero (en el caso normal, 8x8)
- CvPoint2D32f* corners > Puntero donde va a guardar el array de las esquinas encontradas
- int* corner_count -> Puntero done va a guardar el número de esquinas encontradas

El valor que devuelve la función indica si se han encontrado y ordenado por filas y columnas las esquinas encontradas.

    cvFindChessboardCorners es muy utilizada en el área de calibración de imágenes con OpenCV. Los tableros de ajedrez/damas, al tener un patrón regular (lo más importante es la alternancia entre color claro y oscuro que se mantiene en las 4 direcciones), es un caso particular ótpimo para aplicar el método de "Harris Corner Detection", que es en lo que se sustenta la función cvFindChessboardCorners.

    Haciendo pruebas con este método, vimos el porqué de su gran popularidad. Esta fución detecta las esquinas con gran precisión y sin necesidad de condiciones óptimas (el tablero no necesita ocupar toda la imagen, sino que puede estar alejado de la cámara y con cualquier inclinación y con cualquier alineación respecto de la cámara).


Para las pruebas hemos aplicado un algoritmo de reconsrucción báscio (estimamos las posiciones de las esquinas suponiendo que se mantiene el mismo ancho y alto de las casillas).


Imagen de tablero perfecto


board


Tras aplicarle  cvFindChessboardCorners


cvFindChessboardCorners3


Uniendo las esquinas:


cvFindChessboardCorners4


Con las imágenes reales, vemos que la función funciona perfectamente:


Imagen real con problemas de iluminación


board


Tras Tras aplicarle  cvFindChessboardCorners


cvFindChessboardCorners2


Uniendo las esquinas


cvFindChessboardCorners 


Cuando hay problemas de perspectivas, vemos que funciona también muy bien, aunque la precisión en las zonas alejadas no es muy buena*


Imagen real con perspectiva


board


Tras aplicarle Tras aplicarle  cvFindChessboardCorners


cvFindChessboardCorners4 


Uniendo las esquinas:


cvFindChessboardCorners3



Como era de esperar, esta función no hace exactamente lo que queremos, ya que nosotros queremos encontrar todas las esquinas (no solo las interiores), y detectarlas en el orden correcto desde cualquier ángulo**.

*) En el siguiente post explicaremos con detalle estos problemas y como los solucionamos.

"Pros" -> Detecta las esquinas con gran precisión y sin necesidad de condiciones óptimas
"Contras" -> Sólo encentra las esquinas interiores. El orden de las esquinas varía (aunque siempre va por filas o por columnas)



//Load img
IplImage* board= cvLoadImage(argv[1]); 
 
//Create gray img
IplImage* _grayBoard = cvCreateImage(cvGetSize(board), IPL_DEPTH_8U, 1); 
cvCvtColor(board, _grayBoard, CV_BGR2GRAY); 
 
//Variables of cvFindChessboardCorners
CvSize internalSize = cvSize(7, 7); //Number of horizontal x vertical internal corners
CvPoint2D32f* internalCorners = (CvPoint2D32f*)malloc(49 * sizof(CvPoint2D32f)); 
int internalCornersCount = 0, found; 
 
found = cvFindChessboardCorners(_grayBoard, internalSize, internalCorners, &internalCornersCount); 

No hay comentarios:

Publicar un comentario