scieee AI-readable full text Open interactive document viewer

Registro automático de una partida de go mediante visión por computador

Siles Bonilla, Guillermo

Abstract

El objetivo de este trabajo fin de grado es construir una aplicación Android de forma que con un dispositivo móvil se analice el transcurso de una partida de Go1 y genere un archivo que registre todos los movimientos realizados durante la partida. Posteriormente puede revisarse la partida en otra sección de la aplicación, que debe ser capaz de reproducir ésta sin ningún error. La detección de las jugadas se realizará mediante técnicas de visión por computador, usando la librería OpenCV y procurando la mínima interacción posible entre el usuario y la aplicación. Sería necesario preparar el sistema antes de empezar la partida, colocando el dispositivo móvil a una altura apropiada y calculando manualmente la posición de las esquinas para detectar el tablero.

Full text

Registro autom´atico de una partida de go mediante visi´on por computador Automatic recording of a game of Go through computer vision Guillermo Siles Bonilla Titulaci´on: Grado en Ingenier´ıa Inform´atica. Centro: Escuela T´ecnica Superior de Ingenier´ıa Inform´atica, Universidad de M´alaga. Tutor: D. Javier Gonz´alez Jim´enez. Departamento: Ingenier´ıa de Sistemas y Autom´atica. 6 de febrero de 2017 Fecha de defensa: El secretario del tribunal 1 2 Resumen El objetivo de este trabajo fin de grado es construir una aplicaci´on Android de forma que con un dispositivo m´ovil se analice el transcurso de una partida de Go1y genere un archivo que registre todos los movimientos realizados durante la partida. Posteriormente puede revisarse la partida en otra secci´on de la aplicaci´on, que debe ser capaz de reproducir ´esta sin ning´un error. La detecci´on de las jugadas se realizar´a mediante t´ecnicas de visi´on por computador, usando la librer´ıa OpenCV y procurando la m´ınima interacci´on posible entre el usuario y la aplicaci´on. Ser´a necesario preparar el sistema antes de empezar la partida, colocando el dispositivo m´ovil a una altura apropiada y calculando manualmente la posici´on de las esquinas para detectar el tablero. Palabras clave Visi´on por computador, procesamiento de im´agenes, android, java, sistemas, autom´atica, go, baduk, weiqi, AEGO, AMAGO, OpenCV, erosi´on, dilataci´on, Hough, circunferencias de Hough, grabaci´on, suavizado gaussiano, correlaci´on cruzada, homograf´ıa, perspectiva, l´ogica, ruido, detecci´on, falso positivo, falso negativo, media, c´amara, tr´ıpode, sgf, Lee Hajin. 1Juego oriental, originado en China aproximadamente 3000 a˜nos antes de Cristo. Actualmente el juego es muy popular en China, Corea y Jap´on. 3 4 Abstract The objective of this work is to build an Android application that can analize a game of go with a mobile phone and generate an archive with the moves the players played during the game. Later on the game can be reviewed in other section of the application, that should be able to reproduce the game without errors. The detection of moves will be achieved by computer vision using the OpenCV library, with the minimum interaction between the player and the application. It will be necessary to prepare the system before the game starts, setting the mobile at a proper height and manually calculating all the corners in order to detect the board. Keywords Computer vision, image processing, android, java, systems, automatics, go, baduk, weiqi, AEGO, AMAGO, OpenCV, erode, dilate, Hough, Hough circunferences, recording, gaussian blur, cross correlation, homography, perspective, logic, noise, detection, false positive, false negative, mean, camera, tripod, sgf, Lee Hajin. 5 6 ´ Indice 1. Introducci´on 11 1.1. Reglasdejuego .......................... 11 1.1.1. Puntuaci´on de una partida de Go . . . . . . . . . . . . 12 1.1.2. Captura de piedras . . . . . . . . . . . . . . . . . . . . 12 1.1.3. El Go en occidente . . . . . . . . . . . . . . . . . . . . 14 2. Descripci´on global del sistema 15 2.1. Objetivos ............................. 15 2.2. Descripci´on de la aplicaci´on . . . . . . . . . . . . . . . . . . . 16 2.3. Descripci´on de la parte f´ısica . . . . . . . . . . . . . . . . . . . 16 2.4. Funcionamiento.......................... 17 3. Interfaz de usuario 18 3.1. Reproducir partida . . . . . . . . . . . . . . . . . . . . . . . . 18 3.1.1. Lista de partidas . . . . . . . . . . . . . . . . . . . . . 19 3.1.2. Reproducci´on de la partida . . . . . . . . . . . . . . . 19 3.2. Grabarpartida .......................... 20 3.2.1. Detecci´on de esquinas . . . . . . . . . . . . . . . . . . 21 3.2.2. Homograf´ıa ........................ 22 3.2.3. Detecci´on de movimientos . . . . . . . . . . . . . . . . 22 4. Codificaci´on 24 4.1. Basededatos........................... 24 4.2. Clasesdeapoyo.......................... 25 4.2.1. Game ........................... 25 4.2.2. CompetitorPoint . . . . . . . . . . . . . . . . . . . . . 29 4.3. Reproducci´on de partida . . . . . . . . . . . . . . . . . . . . . 30 4.4. Visi´on por computador . . . . . . . . . . . . . . . . . . . . . . 31 4.4.1. Instalaci´on de OpenCV . . . . . . . . . . . . . . . . . . 31 4.4.2. Detecci´on de esquinas . . . . . . . . . . . . . . . . . . 32 4.4.3. Homograf´ıa ........................ 34 4.4.4. C´alculo de homograf´ıa y competitorPoints . . . . . . . 36 4.4.5. Detecci´on de jugadas . . . . . . . . . . . . . . . . . . . 36 4.4.6. Obtenci´on de votos . . . . . . . . . . . . . . . . . . . . 38 4.4.7. Ganador.......................... 38 5. Pruebas y resultados 41 5.1. PrimeraPrueba.......................... 41 5.1.1. Descripci´on actual del sistema . . . . . . . . . . . . . . 41 7 5.1.2. Descripci´on de los resultados . . . . . . . . . . . . . . . 44 5.2. Segundaprueba.......................... 45 5.2.1. Cambios aplicados . . . . . . . . . . . . . . . . . . . . 46 5.2.2. Descripci´on de los resultados . . . . . . . . . . . . . . . 46 5.2.3. Experimento........................ 48 5.3. ´ Ultimaspruebas.......................... 52 5.3.1. Cambios aplicados . . . . . . . . . . . . . . . . . . . . 52 5.3.2. Descripci´on de los resultados . . . . . . . . . . . . . . . 53 6. Conclusiones 56 6.1. Resultadofinal .......................... 56 6.2. Limitaciones............................ 56 6.3. Aprendizaje............................ 57 6.4. Continuaci´on ........................... 58 A. Algoritmo de detecci´on de capturas 60 B. M´etodo onCameraFrame 66 C. C´odigo de la aplicaci´on 73 Bibliograf´ıa 74 8 ´ Indice de figuras 1. Tablero de go con una partida en curso. . . . . . . . . . . . . . 11 2. Capturadepiedras......................... 13 3. Sistema en funcionamiento grabando una partida. . . . . . . . 17 4. Pantalla de presentaci´on con dos botones. . . . . . . . . . . . 18 5. Listadepartidas.......................... 19 6. Partida en proceso de reproducci´on. . . . . . . . . . . . . . . 20 7. A la derecha el men´u desplegable. . . . . . . . . . . . . . . . 20 8. Vista del men´u principal. . . . . . . . . . . . . . . . . . . . . 21 9. Vista del men´u principal cuando se est´a grabando la partida. 23 10. Diagrama representante de los calculos que se hacen dentro de laclaseGame............................ 26 11. M´etodo calculaSiguienteMatriz. . . . . . . . . . . . . . . . . . 26 12. Algoritmo del m´etodo calculaMuertas. . . . . . . . . . . . . . 27 13. Grid antes y despu´es de una jugada. . . . . . . . . . . . . . . 31 14. Diagrama de procesamiento de im´agenes. . . . . . . . . . . . 32 15. Circunferencias de Hough en el espacio de par´ametros. . . . . 33 16. Distintas transformaciones proyectivas. . . . . . . . . . . . . . 35 17. Procesado de la imagen durante la detecci´on de jugadas. . . . 37 18. Efectos de dilataci´on y erosi´on. . . . . . . . . . . . . . . . . . 37 19. Algoritmo de obtenci´on de votos. . . . . . . . . . . . . . . . . 38 20. Secuencia que detecta falsos positivos. . . . . . . . . . . . . . 39 21. Pasos a seguir con el ganador. . . . . . . . . . . . . . . . . . . 40 22. Detecci´on de las cuatro esquinas del tablero (puntos rosas). . . 41 23. Detecci´on de todas las intersecciones del tablero. . . . . . . . . 42 24. Primer movimiento de una partida (marcado en rojo). . . . . . 42 25. Antes de capturar. . . . . . . . . . . . . . . . . . . . . . . . . 43 26. Captura de la piedra (n´otese la desaparici´on del c´ıruclo rojo). 43 27. La piedra negra no es detectada hasta pasados 10 segundos. . 44 28. Falso positivo (C´ırculo en rojo marcando una piedra inexistente). 45 29. Partida de 142 movimientos detectada sin ning´un fallo. . . . . 47 30. Partida de m´as de 200 movimientos a punto de terminar. . . . 48 31. Jugadas interesantes. . . . . . . . . . . . . . . . . . . . . . . . 49 32. Ko en la esquina superior derecha. . . . . . . . . . . . . . . . . 50 33. Movimiento dif´ıcil de detectar. . . . . . . . . . . . . . . . . . . 51 34. C´odigo corregido para evitar el IndexOutOfBoundsException. 52 35. The 12th Jeongkwanjang Cup. Main Round, played March 26, 2011................................. 54 36. The 14th Women’s Kuksu. Semi-finals, played January 22, 2009. 55 9 ·Resolver, en la medida de lo posible, todos los problemas originados por factores externos incontrolables en muchas situaciones, como la iluminaci´on o el ruido. ·Desarrollar una aplicaci´on responsable, robusta y sencilla, que requiera el m´ınimo de interacci´on con el usuario10. 2.2. Descripci´on de la aplicaci´on La aplicaci´on constar´a de dos secciones principales. Una se encarga de grabar la partida y la otra de mostrarla. Al entrar en la secci´on para grabar la partida, el usuario deber´a hacer los preparativos para ´esta11. Una vez la aplicaci´on est´a lista para grabar, el usuario puede olvidarse de ´esta. Al terminar la partida el usuario debe pulsar el bot´on de guardar, que le dar´a la opci´on de escribir los nombres de los jugadores antes de guardarla en la base de datos. Para reproducir una partida, el usuario primero acceder´a a una lista con todas las partidas almacenadas en la base de datos. Elegir´a la que desee y se le mostrar´a en una nueva ventana, con botones para avanzar y retroceder seg´un desee en las jugadas. 2.3. Descripci´on de la parte f´ısica Ser´a necesario por supuesto un dispositivo Android y un tr´ıpode para elevar el dispositivo a una altura considerable. Esto cumple doble funci´on, mejorar la calidad de las im´agenes y estorbar lo menos posible a los usuarios. Como dije, se asume que el dispositivo y el tablero estar´an inm´oviles durante el transcurso de la partida, pues en otro caso habr´ıa que tratar con infinidad de errores por diversas situaciones que puedan pasar y es mucho m´as interesante dedicar el tiempo a mejorar todo lo posible la detecci´on de jugadas en el tablero. Adem´as, no es una situaci´on que ocurra a menudo. En la figura 3 tenemos una imagen de todo el sistema al terminar de grabar una partida de Go. 10Para la parte de visi´on por computador. 11Concretamente, detectar las esquinas. 16 Figura 3: Sistema en funcionamiento grabando una partida. 2.4. Funcionamiento En resumen, los pasos a seguir ser´ıan los siguientes: ·Montamos el tr´ıpode y colocamos la c´amara apuntando al tablero, a una altura considerable12 ·Seleccionamos grabar partida13. ·Detectamos las esquinas. ·Comenzamos a grabar, jugamos la partida sin interactuar con el dispositivo. ·Cuando terminamos la partida, seleccionamos guardar en el dispositivo. ·La partida ahora aparecer´a en la lista de partidas para reproducirla cuando deseemos. 12Medio metro, m´as o menos. 13Probablemente lo m´as c´omodo sea tener el dispositivo en modo landscape para todo esto. 17 3. Interfaz de usuario En este apartado se va a describir todo lo que puede experimentar el usuario respecto a la aplicaci´on. Como navegar por ella, de que opciones se dispone y cual debe ser su uso. Al comenzar se dispone de dos opciones: Reproducir o grabar una partida. En funci´on del bot´on que pulsemos navegaremos a la secci´on correspondiente. Figura 4: Pantalla de presentaci´on con dos botones. Tambi´en tenemos un men´u con las distintas opciones de las que dispone el usuario, que cambiar´an seg´un la secci´on en la que se encuentre. A continuaci´on describimos que podemos encontrar en cada una de ellas. 3.1. Reproducir partida Si pulsamos el bot´on ’Reproducir partida’ cargaremos la actividad que nos muestra todas las partidas de la base de datos en una lista. 18 3.1.1. Lista de partidas Podemos borrar o editar partidas accediendo al men´u individual de cada una. Cada elemento de esta lista est´a representado por los jugadores blanco y negro m´as la fecha en la que se jug´o. Al editar una partida podemos cambiar los nombres de los jugadores. Una vez encontramos la partida que queremos visualizar, hacemos click sobre ella, cargando la pantalla de reproducci´on de partidas. Figura 5: Lista de partidas. 3.1.2. Reproducci´on de la partida Se dispone de cuatro botones, que permiten retroceder o avanzar los movimientos de uno en uno o de cinco en cinco. El tablero, que realmente es un grid de 19x19 im´agenes, cambiar´a las im´agenes correspondientes para reflejar el estado actual de la partida. 19 Figura 6: Partida en proceso de reproducci´on. 3.2. Grabar partida Al pulsar el bot´on de grabar partida se carga la pantalla que se encarga de grabar la partida. La pantalla refleja las capturas de la c´amara, modificadas seg´un las operaciones que estemos haciendo en cada momento. Tambi´en disponemos de un men´u con multitud de botones, que hay que entender como usar para el correcto funcionamiento de la aplicaci´on. Figura 7: A la derecha el men´u desplegable. 20 En la figura 7 vemos el men´u desplegable, que nos permite identificar las esquinas del tablero. Inicialmente la aplicaci´on solo carga las im´agenes que captura en la pantalla. 3.2.1. Detecci´on de esquinas Al pulsar la opci´on ’Detect Corners’ se activa la parte de la actividad que busca c´ırculos en la imagen. Si queremos parar de detectar las esquinas14 solo tenemos que pulsar el bot´on ’stop detection’, ahorrando as´ı bater´ıa. Para detectar las esquinas, se coloca una piedra15 en la esquina correspondiente y se pulsa su bot´on. El algoritmo hace una media ponderada de los c´ırculos detectados en la imagen, que adem´as puede corregirse con las flechas del men´u principal que podemos ver en la figura 8. Si por cualquier raz´on esto saliese mal16, se puede repetir el c´alculo volviendo a pulsar el bot´on correspondiente a la esquina. Figura 8: Vista del men´u principal. 14Bien porque ya se han calculado todas y la partida todav´ıa no va a empezar o por cualquier otra raz´on. 15Preferiblemente blanca, que son m´as f´aciles de detectar. 16En mi experiencia casi nunca hay problemas. 21 3.2.2. Homograf´ıa Una vez calculadas las esquinas, podemos comenzar a grabar pulsando el bot´on17 de la c´amara en color verde. Ahora se mostrar´a en pantalla la homograf´ıa de la imagen, de forma que el cuadril´atero que es el tablero se muestre como un cuadrado perfecto. Si queremos ver si las intersecciones se han calculado bien, podemos pulsar el bot´on ’SH’18 que pintar´a un punto en cada intersecci´on. 3.2.3. Detecci´on de movimientos A partir de este momento la aplicaci´on est´a grabando. Cuando se detecta un nuevo movimiento, pintar´a un c´ırculo alrededor de ´el, por si hay alguna duda para ver cual fue el ´ultimo movimiento que se detect´o. Ahora tambi´en est´a visible el bot´on de grabar la partida en la base de datos19. Si se pulsa, se abre una ventana en la que el usuario puede escribir el nombre de los jugadores y con la opci´on de cancelar o guardar. Si m´as adelante se guarda la partida de nuevo, se crear´a un nuevo registro en la tabla, por lo que el usuario deber´a borrar manualmente el registro anterior (puede hacerlo en la lista de partidas). Tambi´en podemos observar que el bot´on de la c´amara aparece de color naranja. Esto significa que est´a grabando. Si queremos pausar la grabaci´on (para ahorrar bater´ıa o por cualquier otro motivo) solo tenemos que pulsar el bot´on. Veremos que se pone de color verde, por lo que para reanudar el an´alisis de las im´agenes debemos pulsarlo de nuevo. 17En caso de que las esquinas no estuviesen calculadas la aplicaci´on nos avisar´a de ello al pulsar el bot´on. 18Show Homography. 19Y han desaparecido las flechas, que ya no sirven para nada. 22 Figura 9: Vista del men´u principal cuando se est´a grabando la partida. Por ´ultimo, disponemos de dos botones para que la aplicaci´on nos comunique el n´umero de piedras capturadas por el jugador negro y blanco. 23 4. Codificaci´on A continuaci´on vamos a revisar parte del c´odigo de la aplicaci´on, para entender que est´a haciendo realmente en cada momento. As´ı veremos c´omo funciona y porque se ha hecho as´ı. Para simplificar la lectura del documento no se muestra c´odigo alguno. Si el lector desea inspeccionar alguna parte del c´odigo en concreto en el ap´endice C puede ver como hacerlo. 4.1. Base de datos Comenzamos describiendo la base de datos, ya que es importante saber como se guarda una partida para saber porqu´e estamos haciendo diversas operaciones m´as adelante. La base de datos de la aplicaci´on es muy sencilla. Consta de una sola tabla, que guarda los datos relativos a una partida: ·ID de la partida en la base de datos. ·Nombre del jugador negro. ·Nombre del jugador blanco. ·Fecha del d´ıa de la partida. ·Una cadena de caracteres que representa los movimientos que se hicieron durante la partida. En la base de datos se guarda lo necesario para identificar y reproducir m´as tarde la partida. La cadena de caracteres que la representa est´a formada por el patr´on ’C[fc];’, uno por cada jugada. Su significado es el siguiente: ·C representa el color mediante la letra W o B, que indica quien ha jugado (White/Black). ·f representa la fila donde se ha jugado, puede ser una letra desde ’a’ hasta ’s’. ·c representa la columna donde se ha jugado, puede ser una letra desde ’a’ hasta ’s’. ·Los caracteres ’[’, ’]’ y ’;’ son delimitadores. 24 Por ejemplo, una partida que conste de cinco movimientos ser´ıa algo parecido a: B[dd];W[pd];B[dp];W[pp];B[cn]; 4.2. Clases de apoyo Vamos a describir dos clases de las que es necesario entender su funcionamiento, ya que se han creado para lidiar con diversas situaciones que veremos m´as adelante. 4.2.1. Game En primer lugar, vamos a hablar de la clase Game. Esta clase representa una partida completa, por lo que, como es de esperar, al leer una partida de la base de datos, se crear´a un nuevo objeto Game con los datos suficientes para reproducirla m´as tarde. Adem´as, cuenta con una serie de m´etodos de ayuda para hacer todos los c´alculos necesarios durante la partida. Vamos a ver los m´as interesantes. Un objeto game cuenta con una matriz tridimensional, que se crea al crearse el objeto. Cada matriz bidimensional20 contenida en esta matriz representa un estado de la partida. Cada vez que se a˜nade una jugada, cambia el estado. Los valores de estas matrices bidimensionales pueden ser: ·0: Representa una intersecci´on vac´ıa. ·1: Representa una piedra negra. ·2: Representa una piedra blanca. Inicialmente esta matriz solo contiene ceros (el tablero est´a vac´ıo). 20De 19 filas y 19 columnas, representando todas las intersecciones del tablero. 25 ·onCameraViewStarted. Se encarga de las operaciones iniciales al iniciar la grabaci´on. ·onCameraViewStopped. Se encarga de las operaciones finales al finalizar la grabaci´on. ·onCameraFrame. Se encarga de realizar operaciones sobre cada imagen. En los dos primeros solo se hacen operaciones rutinarias (inicializaci´on de objetos y dem´as). El interesante es ’onCameraFrame’, que se ver´a a continuaci´on. Mediante varias variables booleanas se controla que parte de onCameraFrame se ejecuta en cada momento de las siguientes. El m´etodo completo se puede ver en el ap´endice B. 4.4.2. Detecci´on de esquinas Para comenzar a detectar las esquinas se debe activar en el men´u la opci´on detect corners. Esto activar´a el proceso de im´agenes: Figura 14: Diagrama de procesamiento de im´agenes. Primero se pasa la imagen a tonos de gris, nunca se hace ninguna operaci´on sobre la imagen en color. A continuaci´on se dilata26 la imagen, para que sea m´as f´acil detectar los c´ırculos. Se aplica un filtro gaussiano para suavizar la imagen y se calculan c´ırculos de Hough sobre ella. 4.4.2.1. Hough La transformada de Hough es una t´ecnica usada para detectar formas geom´etricas que puedan ser representadas por una expresi´on matem´atica (en este caso, circunferencias). Consiste en detectar ciertos p´ıxeles de la imagen que otorgar´an votos a cada posible circunferencia que los contenga. 26M´as adelante se explicar´a porque dilatamos y erosionamos la imagen. 32 La expresi´on matem´atica que define una circunferencia es: (x−cx)2+ (y−cy)2=r2 En la figura 15 se observa el espacio donde se detectan las circunferencias y el espacio de par´ametros, donde cada punto representa a una circunferencia en el espacio imagen. Figura 15: Circunferencias de Hough en el espacio de par´ametros. Valores diferentes de (cx, cy, r) proporcionan distintas circunferencias. Para cada p´ıxel de contorno que aparece en la posici´on (x0, y0) existe una familia de circunferencias que pasan por este punto dadas por: cx=x0+cos(θ)·r cy=y0+sin(θ)·r Cada p´ıxel de contorno vota por todas las posibles circunferencias que lo contienen. Si aparece un punto en el espacio de par´ametros que tenga muchos votos es que posiblemente hay una circunferencia en el espacio imagen. 33 4.4.2.2. Detecci´on de una esquina Hasta ahora lo ´unico que se hace es detectar c´ırculos en la imagen. Para detectar una esquina, se debe primero colocar una piedra blanca en la esquina que se quiere detectar y a continuaci´on pulsar el bot´on correspondiente a esa esquina en el men´u. A partir de este momento las circunferencias detectadas con Hough se usar´an para calcular la esquina correspondiente. Para cada circunferencia se obtiene su centro y se hace la media ponderada con el centro calculado hasta ese momento. Las circunferencias cuyo centro est´an muy alejadas del calculado hasta ahora se descartan. Pulsando los botones del men´u de opciones se puede corregir manualmente las coordenadas de la esquina en cuesti´on. Si el primer c´ırculo detectado fuese un falso positivo27 habr´ıa que repetir el proceso pulsando de nuevo el bot´on en el men´u. Este proceso hay que repetirlo para las cuatro esquinas del tablero. Una vez ´estas est´an calculadas, podemos comenzar a grabar la partida, pulsando el bot´on de grabar en el men´u. Adicionalmente, podemos ver las intersecciones si pulsamos el bot´on SH28 aunque esto consume bastante memoria, as´ı que es recomendable desactivarlo al comprobar que todo funciona correctamente. 4.4.3. Homograf´ıa Al pulsar el bot´on ’grabar’ en el men´u ya se puede dejar sola la aplicaci´on. ´ Esta calcular´a primero la homograf´ıa respecto a los bordes del tablero y los competitorPoints. La homograf´ıa es una t´ecnica para hacer transformaciones proyectivas entre dos planos. En la figura 16 se pueden ver los distintos tipos de transformaciones proyectivas. 27En mi experiencia no ocurre casi nunca, pero puede llegar a ocurrir por ruido respectivo a la iluminaci´on y otras fuentes. 28Este bot´on muestra todos los CompetitorPoints en la imagen, as´ı podemos ver si coinciden con las intersecciones del tablero. 34 Figura 16: Distintas transformaciones proyectivas. En este caso hay que lidiar con una transformaci´on de tipo proyectivo (la ’peor’ de todas). Con la matriz de homograf´ıa se podr´a transformar el cuadril´atero que representa al tablero en la imagen original a un cuadrado en la imagen rectificada. Para hacer esto hacen falta las coordenadas de las cuatro esquinas29 en la imagen y los cuatro puntos equivalentes en la imagen rectificada. Para n puntos tenemos el sistema de ecuaciones siguiente:        x1y11 0 0 0 −x0 1x1−x0 1y1−x0 1 0 0 0 x1y11−y0 1x1−y0 1y1−y0 1 . . . xnyn1 0 0 0 −x0 nxn−x0 nyn−x0 n 0 0 0 xnyn1−y0 nxn−y0 nyn−y0 n                      h00 h01 h02 h10 h11 h12 h20 h21 h22               =        0 0 . . . 0 0        Dado un punto en la imagen inicial podemos calcular sus coordenadas en la imagen corregida con la matriz de homograf´ıa de la siguiente forma: λ  x0 i y0 i 1  =  h00 h01 h02 h10 h11 h12 h20 h21 h22     xi yi 1   29Realmente solo hacen falta cuatro puntos, no hace falta que sean las esquinas, pero ´estas nos dar´an la m´axima precisi´on posible. 35 4.4.4. C´alculo de homograf´ıa y competitorPoints Como se ha visto, para calcular la homograf´ıa hacen falta dos sets de puntos. El primero son los puntos calculados anteriormente (las cuatro esquinas). Los segundos ser´an los equivalentes en la nueva imagen. Como interesa crear un cuadrado perfecto, se les asigna los valores que m´as convienen30. La matriz de homograf´ıa se calcula usando la librer´ıa openCV. Tambi´en se calculan las coordenadas de los puntos de la imagen para poder dibujarlos si el usuario pulsa el bot´on ’show homography’. Por ´ultimo, se crea y a˜nade a la lista de competitorPoints todas las intersecciones, cuya ´area (que viene a ser un cuadrado) est´a delimitada por las variables minX, maxX, minY, maxY. La idea detr´as de esto es que m´as adelante cuando se detecte un c´ırculo, si su centro se encuentra dentro de este ´area este competitorPoint ganar´a puntos de cara a sus compa˜neros. Despu´es de todos estos c´alculos, las variables booleanas correspondientes se activan para comenzar la detecci´on de jugadas. 4.4.5. Detecci´on de jugadas En la figura 17 puede verse el proceso al que es sometida la imagen desde su captura inicial hasta que se le aplica Hough. En primer lugar se aplica la homograf´ıa calculada anteriormente sobre la imagen, para transformar el tablero a un cuadrado perfecto. A continuaci´on se transforma la imagen a tonos de gris y se suaviza para eliminar ruido. En funci´on de si se est´an buscando piedras negras o blancas se aplica erosi´on o dilataci´on y transformadas de Hough para reconocer circunferencias en la imagen. La transformada de Hough que se aplica cuando se est´an buscando piedras negras es m´as d´ebil que la de las blancas, ya que es m´as dif´ıcil en general detectar estas circunferencias. A cambio se aplica m´as adelante restricciones extra para lidiar con los falsos positivos, que podr´ıan detectarse por culpa de esto. 30El valor de height de las im´agenes es 480 y el de width es 800, por lo que asignamos un cuadrado de aproximadamente 450 p´ıxeles de lado. 36 Figura 17: Procesado de la imagen durante la detecci´on de jugadas. 4.4.5.1. Erosi´on y dilataci´on La imagen de la izquierda en la figura 18 es la imagen sin procesar. Al aplicarle un filtro de dilataci´on, obtenemos la imagen central, y al aplicarle el filtro de erosi´on obtenemos la imagen de la derecha. Figura 18: Efectos de dilataci´on y erosi´on. 37 Lo interesante es que si se quiere detectar piedras negras, conviene aplicar un filtro de dilataci´on. Al hacerlo, haremos m´as grandes las peque˜nas zonas entre piedras, marcando m´as el c´ırculo (al mismo tiempo que se hace m´as peque˜no, pero esto no importa). Lo mismo ocurre con las piedras blancas. El efecto de esto fue espectacular, inicialmente el algoritmo ten´ıa problemas para detectar las piedras adyacentes entre si. Despu´es de a˜nadir esto, el problema pr´acticamente desapareci´o, sin contar algunas excepciones con las que hay que lidiar de otra forma. Tambi´en, si es la primera imagen que toma el algoritmo, se guarda una lista con subim´agenes de todas las intersecciones, para hacer correlaci´on cruzada m´as adelante. Una vez se tienen los c´ırculos, para cada uno se aplica un algoritmo para dar votos a los competitorPoints. 4.4.6. Obtenci´on de votos Para cada c´ırculo, comprobamos que efectivamente corresponde a una intersecci´on. Si esta intersecci´on est´a libre entonces gana un voto. Adem´as, en caso de que sea la intersecci´on con m´as votos hasta ahora, se convierte en la ganadora actual de la ronda. Figura 19: Algoritmo de obtenci´on de votos. Obviamente, si la circunferencia est´a fuera del tablero, ning´un competitorPoint se llevar´a su voto, por lo que el algoritmo no se ejecuta. De la misma forma, si el competitorPoint no es el que m´as votos tiene no se hace nada con el, simplemente almacena su voto. 4.4.7. Ganador Una vez todas las circunferencias han sido clasificadas, se analiza el ganador en este momento. Si el ganador ha sobrepasado un n´umero determinado 38 de votos, deber´a pasar unos tests para detectar si es un falso positivo. Figura 20: Secuencia que detecta falsos positivos. B´asicamente hay dos tests. El primero se encarga de comprobar si es un falso positivo, haciendo correlaci´on entre la imagen inicial de la intersecci´on y la actual. Si el valor de la correlaci´on es muy alto significa que la imagen no ha cambiado apenas, por lo que es probable que se detectara un c´ırculo debido a efectos de luces, sombras y otros ruidos31. Si pasa la prueba de correlaci´on, entonces se comprueba que la piedra que se ha detectado corresponde al turno del jugador. De esta forma se asegura de guardar en el orden correcto las jugadas en la base de datos32. En caso de que falle alguno de los dos tests, el ganador pierde todos sus votos y se sigue analizando im´agenes. En caso contrario, se ejecuta un algoritmo para registrar la nueva jugada y hacer los preparativos para seguir detectando nuevas jugadas. 31El algoritmo de correlaci´on que se usa en OpenCV es capaz de amortiguar el efecto de luces y sombras entre dos im´agenes. 32Si uno de los dos jugadores juega muy r´apido, podr´ıa detectarse su jugada antes que la del jugador correspondiente y alterar el orden de jugadas. 39 Figura 21: Pasos a seguir con el ganador. Se ejecutan una serie de alertas que indican que se ha encontrado un nuevo movimiento. El flag del competidor sirve para saber si en esa posici´on ya se ha detectado una piedra anteriormente, de esa forma no competir´a cuando se detecte su circunferencia correspondiente. Se guarda la jugada con el formato que vimos en la secci´on 4.1 y se calcula si esa jugada ha capturado otras piedras. En ese caso, los competidores correspondientes a dichas piedras desactivan su flag para volver a competir (ya que ahora pueden jugarse de nuevo piedras en los huecos vac´ıos). Por ´ultimo, el flag que indica de quien es el turno cambia su valor y se resetean todos los stack33. Despu´es de esto se continua analizando im´agenes. 33Esto ayuda a eliminar falsos positivos que van acumulando votos con el tiempo. 40 5. Pruebas y resultados 5.1. Primera Prueba A continuaci´on se ver´an las primeras tomas de contacto y los resultados obtenidos. 5.1.1. Descripci´on actual del sistema Se tiene la c´amara elevada a una altura considerablemente alta, con el soporte algo inclinado para favorecer la captura de im´agenes de forma que la deformaci´on del tablero sea la m´ınima posible. Esto ser´a muy importante para que las piedras no est´en demasiado distorsionadas al hacer la homograf´ıa y no aparezcan como elipses. Figura 22: Detecci´on de las cuatro esquinas del tablero (puntos rosas). Se calculan las cuatro esquinas, una a una, mediante el sistema ’place a stone’, que consiste en colocar una piedra (preferiblemente blanca) en la esquina que se va a detectar. Esta piedra la detecta el sistema mediante detecci´on de c´ırculos con Hough. Se obtiene el centro del c´ırculo y mediante un algoritmo se va corrigiendo ese centro con los datos entrantes de cada imagen. Tambi´en se puede corregir manualmente este punto con unos botones en el men´u. 41 informar al sistema de que la aplicaci´on necesita mucha memoria, ya que sigui´o d´andole exactamente la misma de antes. Lo que parece que ha funcionado es la limpieza del c´odigo. Se ha intentado no crear nuevos objetos en cada iteraci´on, cre´andose fuera del bucle y reutilizando objetos cuando es posible. Antes, la aplicaci´on sol´ıa dejar de funcionar al cabo de pocos minutos, si bien a veces no daba problemas. En las pruebas actuales se han grabado partidas durante media hora sin ning´un problema, aunque desconozco si con el paso del tiempo fallar´a y cual es la probabilidad de que esto suceda. Conforme se hagan m´as pruebas habr´a m´as datos para analizar el problema. 5.2.3. Experimento Vamos a ver un experimento en concreto que nos aporta datos interesantes. En la figura 30 podemos observar las condiciones y el estado de la partida al terminar de grabar. Figura 30: Partida de m´as de 200 movimientos a punto de terminar. 48 Lamentablemente, en este punto fall´o la gesti´on de la memoria de la aplicaci´on, haci´endola reiniciarse a falta de unos diez movimientos para el final de la partida. Durante esta partida ocurrieron cosas muy desconcertantes, vamos a verlas a continuaci´on. Figura 31: Jugadas interesantes. En la figura 31 vemos dos jugadas que el algoritmo detect´o sin problemas. De hecho, me sorprendi´o much´ısimo, esperaba problemas en ambos casos. En el caso de la izquierda, aunque las jugadas blancas se detectan bien, no esperaba que en este caso con tantas piedras alrededor fuese capaz de detectar el c´ırculo y menos con tanta seguridad como para detectar la jugada en el tiempo medio que suele tardar en detectar movimientos menos complicados. Lo mismo pas´o con la jugada negra en la imagen de la derecha. Al estar entre tres piedras amigas, pens´e que tardar´ıa al menos 3 o 4 segundos en 49 detectarla, pero al igual que con la blanca no tuvo problema alguno. Esto creo que es un gran avance de cara al problema que ten´ıamos en la figura 27 , que al haber algo cerca ya dificultaba bastante la detecci´on de jugadas negras. Figura 32: Ko en la esquina superior derecha. En la figura 25 vemos una de las situaciones m´as famosas del juego, un ko42. Es interesante porque ocurre en casi todas las partidas, normalmente es un recurso para el jugador que va perdiendo, una forma de hacer ’all in’, y no hubo ning´un problema durante las aproximadamente 40 jugadas que dur´o el ko. 42Esto es una situaci´on especial, en la que negro y blanco pueden comerse piedras mutuamente y la partida no terminar´ıa nunca. Para ello se invent´o la regla de ko, que dice que al hacer una jugada un jugador, la posici´on o estado de la partida no puede ser la misma que la que era cuando hizo su ´ultimo movimiento. 50 La mala noticia de este experimento (aparte del fallo de memoria, pero ese era esperado) es que el movimiento de la figura 33, por razones que desconozco, no lo detect´o hasta pasados unos 20 segundos (una cantidad de tiempo que no se puede permitir el algoritmo). Figura 33: Movimiento dif´ıcil de detectar. El balance general me parece muy bueno. Hemos detectado una partida casi completa, sin tener que estar pendiente del dispositivo en ning´un momento, con un consumo de bater´ıa del 30 %43 y sin fallos de memoria hasta el final de la partida, que realmente no es interesante grabar pues la parte m´as importante de analizar es el opening y a veces el mid-game. 43Un dato muy bueno ya que la bater´ıa del dispositivo no est´a en sus mejores condiciones. 51 5.3. ´ Ultimas pruebas Parece que despu´es de estas ultimas pruebas se tendr´a la versi´on definitiva de la aplicaci´on, a falta de algunos cambios menores. 5.3.1. Cambios aplicados Hemos corregido el supuesto44 ’fallo de memoria’. Realmente era un fallo del algoritmo, concretamente un acceso incorrecto a la lista de CompetitorPoints. double circleX = circles.get(0,i)[0]; double circleY = circles.get(0,i)[1]; if(circleX < 24*19 + 12 && circleY < 24*19 + 12) { int index = (int) ((circleX - 12) / 24) *19 + (int) ((circleY - 12) / 24); CompetitorPoint cp = competitorPoints.get(index); . . . Figura 34: C´odigo corregido para evitar el IndexOutOfBoundsException. Inicialmente no se comprobaba que los c´ırculos detectados estuviesen dentro del tablero. Por eso, al no existir el if de la figura 34 , al detectar un c´ırculo en una posici´on muy concreta debido a ruido de luces y otros factores, el valor de la variable index tomaba un valor inadecuado que podr´ıa resultar en un falso positivo m´as un posible falso negativo o en el peor caso, en un IndexOutOfBoundsException, haciendo reiniciar la aplicaci´on y perdi´endose la posibilidad de guardar la partida. Respecto al otro tema importante, la detecci´on de piedras negras, se han combinado dos m´etodos: Por un lado, se ha bajado un 25 % el valor que marca el umbral a la hora de detectar piedras negras. Esto tiene dos consecuencias: las piedras negras ser´an m´as f´aciles de detectar, pero habr´a much´ısimos m´as falsos positivos. 44Index out of bounds exception. No ten´ıa nada que ver con la memoria. 52 Para corregir estos falsos positivos, lo que se hace es tomar la media de los p´ıxeles del c´ırculo, aplicando antes un filtro de erosi´on45. Si esta media no pasa un umbral determinado, se considera que es un falso positivo y reiniciamos los votos de este. Esta medida es muy efectiva puesto que la media entre los valores de las piedras negras y de los falsos positivos despu´es de la erosi´on son muy distintos. El valor de la media de las piedras negras est´a en torno a 10 - 20 y los falsos positivos en torno a 125 - 225. 5.3.2. Descripci´on de los resultados La primera impresi´on fue espectacular, todo parec´ıa funcionar perfectamente. Sin embargo, al cabo de unas pruebas se detectaron algunos falsos positivos en el turno blanco46. Por tanto, hay que encontrar una soluci´on para reforzar m´as la detecci´on de piedras blancas. La soluci´on que se aplica para los falsos positivos de las piedras negras no es muy efectiva en este caso ya que es dif´ıcil encontrar un l´ımite entre los falsos positivos47 y las piedras blancas para la media. En cuanto a la detecci´on de piedras negras, todo parece funcionar perfectamente, los falsos positivos se descartan en todos los experimentos, independientemente de la cantidad y foco de luz. En las pr´oximas pruebas se pondr´a atenci´on a si todo funciona seg´un lo previsto. En cuanto a la aplicaci´on en general, funciona sin ning´un problema. Sin embargo, en una de las pruebas hubo alg´un error que no pude capturar y la aplicaci´on se reinici´o. Actualmente me es dif´ıcil arreglar este tipo de errores, en la secci´on 6.2 se explica porqu´e. En otras pruebas sin embargo, he tenido la aplicaci´on funcionando durante m´as de una hora sin ning´un problema, y desde entonces no ha vuelto a ocurrir. 45Sirve para eliminar el posible brillo sobre las piedras negras. 46En el turno negro al menos en los experimentos que he hecho no se ha dado ninguno. 47El valor de su media es bastante alto, algunos incluso alcanzando valores de piedras blancas. 53 Figura 35: The 12th Jeongkwanjang Cup. Main Round, played March 26, 2011. La figura 35 muestra una de las partidas grabadas con ´exito. Es una partida corta (165 movimientos) en la que blanco se rinde despu´es de perder la batalla en la esquina inferior izquierda. Para mejorar el tema de los falsos positivos se podr´ıa intentar dos cosas nuevas. Por un lado, hacer correlaci´on cruzada entre un modelo y la submatriz que contiene la nueva jugada detectada. Si la correlaci´on no pasa un cierto umbral, se deduce que es un falso positivo. Para los falsos positivos que se van acumulando lentamente, se podr´ıa crear un contador en la clase CompetitorPoint que guarda el n´umero de serie de la ´ultima imagen en la que se le dio un voto. Si hay mucha diferencia entre el n´umero de serie actual y el anterior, se descarta el voto48. Al cabo de unas pruebas la correlaci´on cruzada ha sido un ´exito. El valor que da una piedra negra est´a en torno a 0.75, el de una piedra blanca en torno a 0.95 y el falso positivo que se quiere descartar en torno a 0.999. De esta forma se consigue eliminar los falsos positivos de blanco (al menos en los entornos en los que he probado hasta ahora la aplicaci´on). 48Aunque esto podr´ıa perjudicar en algunos casos la detecci´on de algunas jugadas dif´ıciles para el algoritmo, habr´ıa que elegir cuidadosamente el par´ametro de distancia entre n´umeros de serie. 54 Figura 36: The 14th Women’s Kuksu. Semi-finals, played January 22, 2009. En la figura 36 vemos una partida49 bastante larga, de m´as de 250 movimientos, que se ha conseguido grabar sin ning´un problema50. 49La misma partida que vimos en el experimento anterior. Ahora no ha habido un solo problema. 50Gracias a la correlaci´on cruzada no hay falsos positivos. Sin embargo, alguna vez alguna jugada de blanco se ha clasificado como falso positivo por lo que ha tardado m´as de la cuenta en detectarse (alrededor de 5 segundos cuando la media suele ser de 2 segundos). 55 6. Conclusiones En esta secci´on se va a hablar aqu´ı del resultado obtenido en general, tanto respecto a la aplicaci´on como personal, como sobre que pienso se puede hacer para continuar con este trabajo. 6.1. Resultado final En la secci´on 5 hemos visto el avance paso a paso seg´un a˜nad´ıamos contenido a la aplicaci´on. El resultado final es bastante bueno. Es una base sobre la que se puede construir una aplicaci´on robusta que pueda ser usada por cualquier usuario en cualquier dispositivo. Actualmente hay que saber que hacer para que la aplicaci´on funcione correctamente y, a´un as´ı, hay cosas que mejorar. Esto es normal, al fin y al cabo, los recursos de que disponemos son limitados, incluido el tiempo el cual sin duda ya he excedido51, pero creo que con m´as recursos se puede obtener un producto muy bueno, libre en la medida de lo posible de errores. 6.2. Limitaciones Mi equipo en concreto no me ha permitido desarrollar la aplicaci´on de forma suficientemente eficiente. En concreto, al desarrollar toda la parte de visi´on no puedo usar el emulador (ya que no captura ninguna imagen, es un emulador al fin y al cabo) y mi dispositivo m´ovil en concreto no muestra las excepciones, reinicia la aplicaci´on autom´aticamente. Por esta raz´on por ejemplo, durante un tiempo pens´e que la aplicaci´on ten´ıa problemas de memoria cuando realmente el fallo no ten´ıa nada que ver con ella. La soluci´on de que dispon´ıa era usar otro dispositivo m´ovil52, pero muy pocas veces lo ten´ıa disponible y los errores tampoco ocurren siempre, dependen del entorno, la iluminaci´on, etc. Obviamente, tambi´en hay unas limitaciones f´ısicas en este proyecto. Estamos realizando una aplicaci´on orientada a ejecutarse en un dispositivo m´ovil, que tiene una velocidad de procesamiento y bater´ıa limitadas. Estos algoritmos funcionar´ıan mejor en dispositivos con una mayor velocidad de procesamiento, ya que necesitamos obtener una respuesta en el menor tiempo posible (normalmente alrededor de dos segundos es suficiente) 51Y no me arrepiento de ello en absoluto. 52Concretamente el de mi hermano. 56 y si podemos analizar im´agenes m´as r´apido podr´ıamos aumentar las restricciones. Adem´as, para partidas de larga duraci´on tenemos un problema con la bater´ıa del dispositivo, ya que no siempre puede estar conectado a una red el´ectrica. A˜nadido a todo esto est´an los factores que no podemos controlar, la iluminaci´on o el ruido53 entre otros. Por ´ultimo, recordar que el objetivo de este proyecto era grabar una partida de Go usando t´ecnicas de visi´on por computador, concretamente usando la librer´ıa OpenCV. Esto es una limitaci´on de por s´ı, ya que por ejemplo, para detectar jugadas en el tablero tambi´en podr´ıamos hacer uso de redes neuronales. 6.3. Aprendizaje Por supuesto he aprendido much´ısimas cosas. Las podr´ıa clasificar en dos partes, una relativa a inform´atica y otra relativa al trabajo personal. Respecto a inform´atica, algoritmos y protocolos he aprendido much´ısimas cosas pero de todas ellas, creo que debo destacar tres: ·Android. Cuando empec´e este proyecto no ten´ıa ni idea de como hacer una aplicaci´on para Android54 y no solo he aprendido, si no que me ha encantado hasta el punto de querer enfocar mi carrera profesional en esa direcci´on. ·StackOverflow y Github. Son herramientas que conoc´ıa desde hace tiempo pero apenas hab´ıa hecho uso de ellas anteriormente. No solo he aprendido a usarlas para este proyecto si no que he visto la verdadera utilidad que tienen. En el caso de Github me permiti´o volver atr´as sobre mis pasos en multitud de ocasiones cuando hab´ıa perdido ya la cuenta de los cambios que hab´ıa hecho y teniendo el c´odigo totalmente ilegible. En el caso de StackOverflow, me ha servido para resolver multitud de problemas en cuesti´on de minutos, cuando por mi cuenta podr´ıa haber echado horas en resolverlos. Concretamente en el caso de 53Por ejemplo la manga de una camiseta negra con c´ırculos blancos casualmente del mismo tama˜no que las piedras... 54Por eso en parte quer´ıa hacer este proyecto, no solo iba a aprender sobre visi´on por computador. 57 } if(!listaProvisional.isEmpty()){ for(Position pos : listaProvisional){ listaMuertas.add(pos); } } listaProvisional = new HashSet<>(); // Caso right // Obtenemos la fila y columna de la piedra de arriba. i = p.getFila(); j = p.getColumna()+1; // Aniadimos esa posicion a la lista de visitas. if( j <= 19 ) listaVisitas.add(new Position(i,j)); while(!listaVisitas.isEmpty()){ // Cojo la siguiente posicion de la lista de visitas. Position pos = listaVisitas.iterator().next(); // Si en esa posicion en la matriz no hay nada o hay una piedra a un grupo vivo.. if(matriz[pos.getFila()-1][pos.getColumna()-1] == 0 || listaIntocable.contains(pos)){ // Limpiamos listaProvisional.add(pos); listaVisitas = new HashSet<>(); for(Position position : listaProvisional){ listaIntocable.add(position); } listaProvisional = new HashSet<>(); // Si por el contrario, hay una piedra amiga, aniadimos los cuatro vecinos a visitar y demas. }else if(matriz[pos.getFila()-1][pos.getColumna()-1] != turno + 1){ listaProvisional.add(pos); Position up = new Position(pos.getFila() -1, pos.getColumna()); Position down = new Position(pos.getFila() +1, pos.getColumna()); Position left = new Position(pos.getFila(), pos.getColumna() -1); Position right = new Position(pos.getFila(), pos.getColumna() +1); 64 if(!listaProvisional.contains(up) && pos.getFila() > 1) listaVisitas.add(up); if(!listaProvisional.contains(down) && pos.getFila() < 19) listaVisitas.add(down); if(!listaProvisional.contains(left) && pos.getColumna() > 1) listaVisitas.add(left); if(!listaProvisional.contains(right) && pos.getColumna() < 19) listaVisitas.add(right); } listaVisitas.remove(pos); } if(!listaProvisional.isEmpty()){ for(Position pos : listaProvisional){ listaMuertas.add(pos); } } return listaMuertas; 65 B. M´etodo onCameraFrame A continuaci´on el c´odigo completo del m´etodo onCameraFrame, que se ejecuta una vez por cada captura de pantalla. mRgba = inputFrame.rgba(); if(detectingCircles) { gray = mRgba.clone(); Imgproc.cvtColor(mRgba, gray, Imgproc.COLOR_BGR2GRAY, 0); Imgproc.dilate(gray, gray, Imgproc.getStructuringElement(Imgproc.MORPH_CROSS, new Size(3, 3))); Imgproc.GaussianBlur(gray, gray, new Size(13, 13), 2, 2); HoughCircles(gray, circles, CV_HOUGH_GRADIENT, 1, 45, 30, 20, 5, 15); for (int i = 0; i < circles.cols(); i++) { circle(mRgba, new Point(circles.get(0, i)[0], circles.get(0, i)[1]), (int) circles.get(0, i)[2], new Scalar(89, 255, 202), 5); // Para cada uno de los casos. if(leftDownCorner){ // Si se da el caso de ser la primera vez que se detecta: if(leftDownCornerCount == 0){ leftDown = new Point(circles.get(0, i)[0], circles.get(0, i)[1]); leftDownCornerCount++; }else{ double cond1 = circles.get(0,i)[0] - leftDown.x; double cond2 = circles.get(0,i)[1] - leftDown.y; if (cond1 < 10 && cond1 > -10 && cond2 < 10 && cond2 > -10){ double xCoord = leftDown.x; 66 double yCoord = leftDown.y; xCoord = (xCoord*leftDownCornerCount + circles.get(0,i)[0]) / (leftDownCornerCount + 1); yCoord = (yCoord*leftDownCornerCount + circles.get(0,i)[1]) / (leftDownCornerCount + 1); leftDown = new Point(xCoord,yCoord); leftDownCornerCount++; } } } if(leftUpCorner){ if(leftUpCornerCount == 0){ leftUp = new Point(circles.get(0, i)[0], circles.get(0, i)[1]); leftUpCornerCount++; }else{ double cond1 = circles.get(0,i)[0] - leftUp.x; double cond2 = circles.get(0,i)[1] - leftUp.y; if (cond1 < 10 && cond1 > -10 && cond2 < 10 && cond2 > -10){ double xCoord = leftUp.x; double yCoord = leftUp.y; xCoord = (xCoord*leftUpCornerCount + circles.get(0,i)[0]) / (leftUpCornerCount + 1); yCoord = (yCoord*leftUpCornerCount + circles.get(0,i)[1]) / (leftUpCornerCount + 1); leftUp = new Point(xCoord,yCoord); leftUpCornerCount++; } } } if(rightDownCorner){ if(rightDownCornerCount == 0){ rightDown = new Point(circles.get(0, i)[0], circles.get(0, i)[1]); rightDownCornerCount++; }else{ double cond1 = circles.get(0,i)[0] - rightDown.x; double cond2 = circles.get(0,i)[1] - rightDown.y; if (cond1 < 10 && cond1 > -10 && cond2 < 10 && cond2 > -10){ 67 double xCoord = rightDown.x; double yCoord = rightDown.y; xCoord = (xCoord*rightDownCornerCount + circles.get(0,i)[0]) / (rightDownCornerCount + 1); yCoord = (yCoord*rightDownCornerCount + circles.get(0,i)[1]) / (rightDownCornerCount + 1); rightDown = new Point(xCoord,yCoord); rightDownCornerCount++; } } } if(rightUpCorner){ if(rightUpCornerCount == 0){ rightUp = new Point(circles.get(0, i)[0], circles.get(0, i)[1]); rightUpCornerCount++; }else{ double cond1 = circles.get(0,i)[0] - rightUp.x; double cond2 = circles.get(0,i)[1] - rightUp.y; if (cond1 < 10 && cond1 > -10 && cond2 < 10 && cond2 > -10){ double xCoord = rightUp.x; double yCoord = rightUp.y; xCoord = (xCoord*rightUpCornerCount + circles.get(0,i)[0]) / (rightUpCornerCount + 1); yCoord = (yCoord*rightUpCornerCount + circles.get(0,i)[1]) / (rightUpCornerCount + 1); rightUp = new Point(xCoord,yCoord); rightUpCornerCount++; } } } } } if(!startRecording && !calculateHomography) { if (leftDownCornerCount > 0) { circle(mRgba, leftDown, 1, new Scalar(200, 80, 202), 2); } if (leftUpCornerCount > 0) { 68 circle(mRgba, leftUp, 1, new Scalar(200, 80, 202), 2); } if (rightDownCornerCount > 0) { circle(mRgba, rightDown, 1, new Scalar(200, 80, 202), 2); } if (rightUpCornerCount > 0) { circle(mRgba, rightUp, 1, new Scalar(200, 80, 202), 2); } }else if (startRecording && !pausedRecording){ // RECORDING //DETECT CIRCLES Imgproc.warpPerspective(mRgba,mRgba,homographyMatrix,tamMats); Imgproc.cvtColor(mRgba, gray, Imgproc.COLOR_BGR2GRAY,0); // CORRELACION // Si es la primera vez que estamos grabando, creamos la lista de mats. if(firstRecording){ for(CompetitorPoint cp : competitorPoints){ correlation.add(gray.submat((int) cp.getMinY() + 3,(int)cp.getMaxY() - 3,(int) cp.getMinX() + 3,(int) cp.getMaxX() - 3)); } firstRecording = false; } Imgproc.GaussianBlur(gray, gray, size3x3, 2); // En funcion del turno erosionamos o dilatamos la imagen. if(!turno){ // Estamos buscando piedras negras. dilate(gray,gray,Imgproc.getStructuringElement (Imgproc.MORPH_RECT, size2x2)); }else{// Estamos buscando piedras blancas. erode(gray,gray,Imgproc.getStructuringElement (Imgproc.MORPH_RECT, size2x2)); } // HEMOS BAJADO DE 20 A 15 PARA NEGRAS 69 if(!turno){ HoughCircles(gray, circles, CV_HOUGH_GRADIENT, 1, 5, 30, 15, 9, 13); }else{ HoughCircles(gray, circles, CV_HOUGH_GRADIENT, 1, 5, 30, 20, 9, 13); } // Dibuja la homografia if(showHomography){ for(int row = 0; row < 19; row++){ for(int column = 0; column < 19; column++){ circle(mRgba, homography.get(row*19 + column), 1, new Scalar(89, 255, 202), 2); } } } //FOR EVERY POINT MATCH CIRCLES for(int i = 0; i < circles.cols(); i++){ double circleX = circles.get(0,i)[0]; double circleY = circles.get(0,i)[1]; // Con este if evitamos mirar un ciruclo donde no hay intersecciones (debido a fallos de luces). if(circleX < 24*19 + 12 && circleX >= 12 && circleY < 24*19 + 12 && circleY >= 12) { int index = (int) ((circleX - 12) / 24) *19 + (int) ((circleY - 12) / 24); CompetitorPoint cp = competitorPoints.get(index); if (!cp.isFound()) { int stack = cp.getStack(); cp.setStack(stack + 1); if (ganadorDeRonda.getStack() < cp.getStack()) { ganadorDeRonda = cp; } } } } if(lastPlayed != null){ int indexLastPlay = lastPlayed.getId(); 70 circle(gray, new Point(24 + (indexLastPlay / 19)*24,24 + (indexLastPlay %19)*24), 13, red , 5); } // SI SE DETECTA UN MOVIMIENTO if (ganadorDeRonda.getStack() >= 10) { Mat pruebaMat = gray.submat((int) ganadorDeRonda.getMinY() + 3,(int)ganadorDeRonda.getMaxY() - 3,(int) ganadorDeRonda.getMinX() + 3,(int) ganadorDeRonda.getMaxX() - 3); erode(pruebaMat,pruebaMat,Imgproc.getStructuringElement (Imgproc.MORPH_RECT, size3x3)); Scalar mean = Core.mean(pruebaMat); //CORRELACION Mat plantilla = correlation.get(ganadorDeRonda.getId()).clone(); // / Create the result matrix int result_cols = 1; int result_rows = 1; Mat result = new Mat(result_rows, result_cols, CvType.CV_32FC1); matchTemplate(plantilla,pruebaMat,result, Imgproc.TM_CCORR_NORMED); Scalar meanResult = Core.mean(result); plantilla.release(); result.release(); //FIN CORRELACION pruebaMat.release(); // Este if sirve para descartar falsos positivos y para que se guarden en el orden correcto. if(((!turno && mean.val[0] < 100) || (turno && mean.val[0] > 100)) && (meanResult.val[0] <= 0.995)) { mensajeDeApoyo = "Valor de la correlacion: " + meanResult.val[0]; mp.start(); // TENEMOS GANADOR lastPlayed = ganadorDeRonda; ganadorDeRonda.setFound(true); String turnoColor; 71 if (!turno) { turnoColor = "B"; }else { turnoColor = "W"; } partida = partida + ";" + turnoColor + "[" + ganadorDeRonda.getPosition() + "]"; // CALCULO DE PIEDRAS MUERTAS. eliminarMuertas(ganadorDeRonda); turno = !turno; for (int i = 0; i < competitorPoints.size(); i++) { cpAuxiliar = competitorPoints.get(i); cpAuxiliar.setStack(0); } }else{ ganadorDeRonda.setStack(0); if(turno && mean.val[0] > 100){ mensajeDeApoyo = "Valor de la correlacion: " + meanResult.val[0]; countWhiteFalsePositive++; } } } mRgba = gray.clone(); }else{ // PAUSED Toast.makeText(this,"Grabacion en pausa",Toast.LENGTH_LONG).show(); } circles.release(); gray.release(); return mRgba; 72 C. C´odigo de la aplicaci´on El c´odigo de la aplicaci´on est´a disponible en: https://github.com/Trethtzer/Proyecto Todo el material inform´atico usado para el desarrollo de este proyecto se encuentra ah´ı. 73