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
Aplicando findSquares4:
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
En el peor de los casos, detecta un par de casillas:
Imagen real con perspectiva
Aplicando findSquares4:
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 imgIplImage* img = cvLoadImage(argv[1]);
//StorageCvMemStorage* storage = cvCreateMemStorage(0);
// Find SquaresCvSeq* seq = findSquares4( img, storage );
// Print SquaresdrawSquares( img, seq);
// Create an struct of squaretypedef struct
{CvPoint* points;
} CvSquare;
//Create an array of squaresCvSquare* 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 verticesCV_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
Tras aplicarle cvHoughLines2:
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
Tras aplicarle cvHoughLines2 :
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
Tras aplicarle cvHoughLines2:
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 imgIplImage* img = cvLoadImage(argv[1]);
//Create gray-scale img IplImage* _gray = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
//Convert RGB img to GRAYcvCvtColor(img, _gray, CV_BGR2GRAY);
//Threshold to delete noisecvThreshold(_gray, _gray, 190, 255, CV_THRESH_BINARY);
//SmoothcvSmooth(_gray, _gray, CV_GAUSSIAN, 5, 5);
//Canny Filter cvCanny(_gray, _gray, 120, 360, 3);
//Create storageCvMemStorage* 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
Tras aplicarle cvFindChessboardCorners
Uniendo las esquinas:
Con las imágenes reales, vemos que la función funciona perfectamente:
Imagen real con problemas de iluminación
Tras Tras aplicarle cvFindChessboardCorners
Uniendo las esquinas
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
Tras aplicarle Tras aplicarle cvFindChessboardCorners
Uniendo las esquinas:
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 imgIplImage* board= cvLoadImage(argv[1]);
//Create gray imgIplImage* _grayBoard = cvCreateImage(cvGetSize(board), IPL_DEPTH_8U, 1);
cvCvtColor(board, _grayBoard, CV_BGR2GRAY);
//Variables of cvFindChessboardCornersCvSize internalSize = cvSize(7, 7); //Number of horizontal x vertical internal cornersCvPoint2D32f* internalCorners = (CvPoint2D32f*)malloc(49 * sizof(CvPoint2D32f));
int internalCornersCount = 0, found; found = cvFindChessboardCorners(_grayBoard, internalSize, internalCorners, &internalCornersCount);
No hay comentarios:
Publicar un comentario