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); 

Instalación de OpenCV 2.0 con Visual Studio 2008

Para empezar a trabajar con OpenCV, lo primero es instalarlo y conseguir que funcione con nuestro entorno de programación. En nuestro caso será Microsoft Visual Studio 2008. Damos las gracias a Jorge y Alejandro por la información compartida, que fue de gran ayuda.
Básicamente voy a repetir la información de ese artículo, asi que podéis seguir ese tutorial si quereis.
Se divide en 3 partes: OpenCV, CMake y Visual Studio.

#### OpenCV ####

1) Nos bajamos OpenCV 2.0

2) Instalamos OpenCV 2.0 en C:\OpenCV2.0

Seguro que más de uno ya lo ha instalado en C:\Archivos de Programa\OpenCV2.0 o en
C:\Program Files\OpenCV2.0.
Pues ¡¡NO!! Por una limitación (¿bug?) en los scripts, no coge las rutas con espacios...


#### CMake ####

3) Nos bajamos CMake (los binarios)

4) Instalamos CMake (donde queramos).

5) Ejecutamos CMake GUI.

6) En CMake GUI, en "Where is the source code", seleccionamos C:\OpenCV2.0 (donde tengais instalado OpenCV, pero recordad el tema de los espacios...)

7) Nos creamos un directorio llamado vs2008 en C:\OpenCV2.0 (mkdir C:\OpenCV2.0\vs2008)

8) Volvemos a CMake GUI, y en "Where to build the binaries", seleccionamos el directorio creado antes (C:\OpenCV2.0\vs2008)

9) Le damos a Configurar y seleccionamos "Visual Studio 9 2008".

10) Una vez terminado, seleccionamos las opciones que queramos (yo he seleccionado los ejemplos en C)

11) De nuevo le damos a configurar

12) Terminamos dando a Generar


#### Microsoft Visual Studio 2008 ####

13) Abrimos un proyecto, y seleccionamos "C:\OpenCV2.0\vs2008\OpenCV.sln"

14) Construimos el proyecto en modo Debug (le damos a la flecha verde mirando que a la izquierda ponga Debug)

15) Construimos el proyecto en modo Release (le damos a la flecha verde mirando que a la izquierda ponga Release)

16) Añadimos al System Path "C:\OpenCV2.0\vs2008\bin\Debug" y "C:\OpenCV2.0\vs2008\bin\Release"

Para el que no sepa ahcer esto: En MiPC, botón derecho -> Propiedades -> Opciones Avanzadas -> Variables de Entorno -> Variables del Sistema -> Path -> Modificar | Copiamos y pegamos ;C:\OpenCV2.0\vs2008\bin\Debug;C:\OpenCV2.0\vs2008\bin\Release

17) En Tools–>Options–>Projects–>VC++ Directories–>Library files
Añadimos:
C:\OpenCV2.0\vs2008\lib\Release
C:\OpenCV2.0\vs2008\lib\Debug

18) En Tools–>Options–>Projects–>VC++ Directories–>Include files
Añadimos:
C:\OpenCV2.0\include\opencv

19) Para CADA proyecto que hagamos en el que usemos OpenCV, tenemos que añadir en:
Project->Properties->Linker->Input->Additional Dependencies

cv200.lib cvaux200.lib cxcore200.lib highgui200.lib (separadas por espacios)


Eso es todo lo que hay que hacer. Puede que me haya pasado con instrucciones demasiado obvias, pero siempre es mejor que sobren que no falten :)

jueves, 18 de marzo de 2010

Descripción detallada de las pretensiones iniciales de la práctica

Hemos subido un documento Word y una presentación Power Point (ambos en formato PDF) explicando detalladamente nuestros objetivos iniciales. Podéis descargároslos siguiendo los siguientes enlaces:

Documento
Presentación de diapositivas

Para los que no queráis leer ambos documentos (tampoco son muy largos) podemos exponer los puntos más importantes:

Objetivos principales:
- Reconocer las jugadas realizadas en un tablero con una webcam
- Implementar un software de inteligencia artifical de las damas que controle el transcurso de la partida
- Implementar un brazo robótico que mueva las piezas

Objetivos opcionales/mejoras:
- Enviar las jugadas realizadas a un server de Internet en tiempo real
- Detectar estados de ánimo en el humano para regular el nivel de dificultad

Estas dos semanas hemos avanzado sobre todo en el tema relativo al software. Hemos instalado la plataforma Visual Studio, las librerías del OpenCV y hemos trasteado un poco con ellas. No teníamos al principio mucha noción del lenguaje C++ (sí de Java y ensamblador), con lo que durante este tiempo nos hemos preocupado de familiarizarnos con el material de trabajo.

Como nota positiva hemos también encontrado códigos capaces de detectar con OpenCV cuadrados, esquinas y piezas en un tablero de ajedrez. Pero lo más importante, hemos encontrado también un exquisito código en C++ de una IA de las damas. Ahora mismo estamos estudiando todo este código e intuir cómo adaptarlo a nuestras necesidades.

La próxima semana tenemos una presentación en el B-043 sobre el progreso de nuestra práctica. Tras la misma podremos adjuntar más material ilustrativo al blog.

lunes, 8 de marzo de 2010

Bienvenid@s

La función de este blog es ofrecer información detallada y actualizada sobre el desarrollo de nuestra práctica especial del Laboratorio de Sistemas Electrónicos Digitales (LSED), asignatura de la ETSIT-UPM

El objetivo es construir un sistema electrónico capaz de jugar a las damas contra un oponente humano. No será únicamente una aplicación software, sino que se pretende usar un brazo robótico para mover las piezas en un tablero físico y real. Una ilustración orientativa podría ser la siguiente:



Próximamente publicaremos más detalles sobre nuestras pretensiones iniciales, y desde allí en adelante procuraremos actualizar el blog al menos una vez cada semana. Esperamos no defraudar a los seguidores del blog, ya que si han entrado aquí es porque tienen un interés inicial que debería perdurar y aumentar a medida que avance el proyecto. =)

Sin más, nos despedimos hasta la siguiente entrada.

Los autores,

Alberto González de Dios
Miguel Cristian Greciano Raiskila
Juan Manuel Montero Martínez (profesor tutor)

Este blog también está en inglés: checkersbotenglish.blogspot.com