• No se han encontrado resultados

Prototipo de motor de realidad aumentada tridimensional para dispositivos móviles

N/A
N/A
Protected

Academic year: 2020

Share "Prototipo de motor de realidad aumentada tridimensional para dispositivos móviles"

Copied!
56
0
0

Texto completo

(1)FACULTAD DE I NFORM ÁTICA U NIVERSIDAD P OLIT ÉCNICA DE M ADRID TESIS DE MÁSTER MÁSTER UNIVERSITARIO EN INTELIGENCIA ARTIFICIAL. P ROTOTIPO DE MOTOR DE REALIDAD AUMENTADA TRIDIMENSIONAL PARA DISPOSITIVOS M ÓVILES. AUTOR: Xoan Iago Suárez Canosa TUTOR: Luis Baumela Molina Madrid, 19 de julio de 2016.

(2) II.

(3) Resumen La Realidad Aumentada es la tecnologı́a que nos permite superponer elementos gráficos sobre el mundo real. Para que ésta pueda funcionar adecuadamente es necesario conocer con precisión cuál es la posición de la cámara desde la que vemos el mundo y cómo se conforman las imágenes que nos muestra. En este trabajo mostramos cómo calcular la posición de la cámara a partir de diferentes marcas tanto naturales como artificiales, vemos cual es la mejor forma de proyectar sobre estas marcas mediante el estudio del error de calibración, diseñamos un modo de obtener la matriz de intrı́nsecos a partir de los parámetros fı́sicos de la cámara (distancia focal y ángulos de visión), elaboramos un modelo de proyección que permita emplear la matriz de intrı́nsecos para obtener la matriz de proyección de OpenGL y por último proyectamos una figura en tres dimensiones (3D) sobre el patrón. Este patrón será un tipo especial de letreros que detectamos empleando una técnica de seguimiento en el espacio de color.. III.

(4) IV.

(5) Agradecimientos. Gracias a todo el laboratorio Percepción Computacional y Robótica por su constante ayuda, sin ellos este trabajo no habrı́a sido posible. A mi familia y amigos por su cariño y apoyo.. “Podemos ver poco del futuro, pero lo suficiente para darnos cuenta de que todavı́a queda mucho que hacer”. Alan Turing. V.

(6) VI.

(7) Índice general. 1. Introducción. 1. 1.1. Objetivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 2. 1.2. Problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 2. 1.3. Contribuciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 3. 1.4. Organización del documento . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 3. 2. Trabajos previos. 5. 2.1. Calibración de la cámara . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 5. 2.2. Localización de planos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 6. 2.2.1. Métodos basados en texturas . . . . . . . . . . . . . . . . . . . . . . . . .. 6. 2.2.2. Métodos basados en caracterı́sticas . . . . . . . . . . . . . . . . . . . . .. 6. 2.2.3. Métodos basados en marcadores . . . . . . . . . . . . . . . . . . . . . . .. 8. 3. Estudio de Calibración. 9. 3.1. Calibración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.2. Calibración usando parámetros fı́sicos de la cámara del móvil . . . . . . . . . . . . 12 3.3. Algoritmo de calibración para Android . . . . . . . . . . . . . . . . . . . . . . . . 14 3.4. Obtención de la Matriz de Proyección en OpenGL . . . . . . . . . . . . . . . . . . 15 3.5. Experimentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.5.1. Error de Reproyección . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 VII.

(8) 3.5.2. Visualización de la proyección de OpenGL . . . . . . . . . . . . . . . . . 18 4. Búsqueda de Letreros. 23. 4.1. Paso a escala de grises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 4.2. Detección de contornos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.3. Selección de contornos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 4.4. Rectificar el letrero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 4.5. Experimentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 5. Conclusiones. 35. 5.1. Trabajos Futuros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 A. Consideraciones de Diseño y Arquitectura Software. 37. A.1. Selección de plataforma y lenguaje de implementación . . . . . . . . . . . . . . . 37 A.2. Arquitectura Software Global . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 A.2.1. Organización del proyecto . . . . . . . . . . . . . . . . . . . . . . . . . . 38 A.2.2. Aplicación Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 A.3. Diseño Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 A.4. Sobre la elaboración del proyecto . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Bibliografı́a. VIII. 45.

(9) Índice de figuras. 1.1. Ejemplos de aplicaciones de Realidad Aumentada . . . . . . . . . . . . . . . . . .. 1. 1.2. Esquema de una aplicación de Realidad Aumentada . . . . . . . . . . . . . . . . .. 2. 2.1. Flujo de trabajo en los métodos basados en caracterı́sticas . . . . . . . . . . . . . .. 8. 2.2. Ejemplos de marcadores de Realidad Aumentada . . . . . . . . . . . . . . . . . .. 8. 3.1. Modelo de cámara Pinhole . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.2. Parámetros proporcionados por la API de Android . . . . . . . . . . . . . . . . . . 13 3.3. Cálculos para la obtención del tamaño del sensor . . . . . . . . . . . . . . . . . . 13 3.4. Transformaciones aplicadas por OpenGL . . . . . . . . . . . . . . . . . . . . . . 15 3.5. Transformación del Frustrum de Opengl a “Clip Coordinates” . . . . . . . . . . . 16 3.6. Esquinas de un tablero de ajedrez reproyectadas con distintas distancias focales . . 19 3.7. Gráficas del error de reproyección frente a la desviación en la estimación de la focal 20 3.8. Proyección mediante la matriz P de visión (columna derecha) VS proyección de OpenGL (columna izquierda) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 4.1. Ejemplo de letrero estético a detectar . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.2. Proyección es el espacio RGB . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 4.3. Diferencia en la conversión a escala de grises entre un letrero con colores bien seleccionados y mal seleccionados . . . . . . . . . . . . . . . . . . . . . . . . . . 25 4.4. Diferentes binarizaciones empleando el filtro de Canny y distintos niveles de umbralización. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 IX.

(10) 4.5. Selección de contornos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 4.6. Ordenación de las esquinas según su ángulo con el centro de gravedad del cuadrilátero 30 4.7. Captura de pantalla que muestra el rectificado del letrero . . . . . . . . . . . . . . 31 4.8. Experimentos de la detección de letreros . . . . . . . . . . . . . . . . . . . . . . . 33 4.9. Secuencia de vı́deo grabada para los experimentos. Fila 1 y 3 imágenes originales, filas 2 y 4 imágenes en RA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 A.1. Estructura del proyecto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 A.2. Arquitectura de la aplicación Android . . . . . . . . . . . . . . . . . . . . . . . . 40 A.3. Diseño del paquete ar de la librerı́a nativa . . . . . . . . . . . . . . . . . . . . . . 41 A.4. Diseño del paquete calib de la librerı́a nativa . . . . . . . . . . . . . . . . . . . . . 42. X.

(11) Capı́tulo 1. Introducción La Realidad Aumentada (RA) es la tecnologı́a que permite ver imágenes o vı́deos del mundo real en los que se han insertado objetos gráficos generados por ordenador [3]. Al comienzo una de las ventajas de la RA frente a la realidad virtual, que recrea la totalidad del mundo alrededor del usuario, era el menor coste computacional ya que “habı́a que pintar menos pı́xeles” [3], pero con la experiencia se ha visto que este campo encierra importantes dificultades, siendo las principales aquellas relacionadas con la Visión por Computador (VC). La esencia de la RA consiste en pintar objetos gráficos sobre la imagen de tal manera que aparezcan integrados en la escena capturada por la cámara, el contenido de la capa virtual puede ser muy variado, desde las indicaciones para llegar a un lugar determinado hasta animaciones completas en tres dimensiones que se muevan y reaccionen como si estuviesen en el mundo real. Ejemplos de aplicaciones exitosas que empleen realidad aumentada pueden ser por ejemplo la aplicación de IKEA para ver cual serı́a el aspecto de un determinado mueble situado en una habitación, la aplicación de aero3Dpro para ver modelos 3D sobre las páginas de una revista o la reciente Pokémon Go que convierte el clásico juego en una actividad interactiva en el que el usuario debe salir a la calle para poder capturar Pokémons. El aspecto de estas aplicaciones es el que mostramos en la figura 1.1.. (a) App. Ikea. (b) App. aero3Dpro. (c) Pokémon GO. Figura 1.1: Ejemplos de aplicaciones de Realidad Aumentada 1.

(12) Capı́tulo 1. 1.1.. Objetivo. Nuestro objetivo es desarrollar una aplicación para realizar Realidad Aumentada en dispositivos móviles que detecte letreros sobre los que se mostrarán diversos modelos 3D y posiblemente alguna animación. Esta aplicación se basará en técnicas de visión por computador para reconocer la escena que rodea al dispositivo, en concreto, se detectará un patrón en el letrero y en base a él se estimará la posición del dispositivo. Una vez conocida la posición del dispositivo la aplicación debe mostrar los objetos gráficos virtuales de forma que se integren en la escena percibida por la cámara. Ilustramos este proceso en la imagen 1.2.. Figura 1.2: Esquema de una aplicación de Realidad Aumentada. 1.2.. Problemas. A la hora de desarrollar este tipo de aplicación nos encontramos con dos problemas importantes. El primero consiste en que para poder proyectar un objeto tridimensional sobre la imagen de una cámara es necesario conocer las propiedades de este sistema proyectivo. Esto suele resolverse mediante un procedimiento de calibración tedioso para un usuario móvil y que puede devolver valores erróneos si la calibración no se realiza adecuadamente. 2.

(13) Introducción Otro problema es la detección de objetos sin textura, es decir cuyo color en su superficie es plano ya que los métodos tradicionales que explicaremos en el capı́tulo siguiente no son capaces de identificarlos eficientemente.. 1.3.. Contribuciones. Para solventar el primero de los problemas planteamos un método de calibración automática que hace uso de los parámetros fı́sicos de la cámara para modelar un sistema proyectivo, con ello seremos capaces de comprender cómo se muestran los objetos del mundo en las imágenes y por tanto podremos proyectar objetos virtuales que no se encuentran en la escena. Como solución al segundo problema proponemos un algoritmo de detección que es capaz de encontrar los letreros marcados con una lı́nea (ver figura 4.1) mediante una búsqueda en el espacio de color combinada con una selección basada en las propiedades geométricas del letrero.. 1.4.. Organización del documento. Este documento se estructura de la siguiente manera: En el capı́tulo 2 revisaremos el estado del arte y haremos un resumen de las técnicas más habituales a la hora de construir sistemas de Realidad Aumentada. En el capı́tulo 3 describiremos el procedimiento de calibración automática a partir de los parámetros extrı́nsecos y proporcionaremos una implementación para dispositivos Android en C++. En el capı́tulo 4 explicaremos cómo funciona el método de detección basada en color y por último en el capı́tulo 5 revisaremos las conclusiones más importantes de este trabajo ası́ como daremos unas indicaciones sobre las lı́neas futuras a seguir. En el apéndice encontraremos todas las consideraciones de diseño y arquitectura software que se han tenido en cuenta a la hora de implementar el sistema.. 3.

(14) 4.

(15) Capı́tulo 2. Trabajos previos Desde los años noventa el interés de la realidad aumentada ha ido incrementándose poco a poco, pero es a partir del año 2009, cuando la constante mejora en la capacidad de cómputo de los dispositivos móviles, la disponibilidad de sistemas de computación basados en las unidades de procesamiento gráfico y en la nube y, sobre todo, las expectativas comerciales que despierta esta tecnologı́a hacen que se produzca el gran salto en el mundo de la RA. En 2009 Qualcomm adquiere tecnologı́a de RA de la universidad de Gratz, que será el embrión del paquete QCAR lanzado en 2011, actualmente conocido como Vuforia. Coincidiendo en el tiempo Microsoft lanza el proyecto Natal, que más tarde dará lugar a la Kinect. En 2008 Mobilizy y Metaio lanzan también sendos productos comerciales, de los cuales el más destacado es Wikitude (www.wikitude.com), una aplicación que combina datos de la brújula y el GPS del móvil con información de la Wikipedia para construir un navegador basado en RA. Un año más tarde SPX Mobile lanza Layar (www.layar.com) otro producto basado en los mismos sensores que Wikitude, pero con más capas de información. En el campo de la investigación los aportes han sido constantes. En las siguientes secciones revisaremos los trabajos previos en los dos problemas que nos atañen: La calibración de la cámara y la detección de planos.. 2.1.. Calibración de la cámara. En Visión por Computador, el proceso de conocer cómo los objetos del mundo se proyectan sobre la imagen se conoce como calibración, donde los parámetros que dependen de las caracterı́sticas fı́sicas y geométricas de la cámara conforman una matriz llamada matriz de intrı́nsecos y la posición de la cámara expresada como una translación y rotación desde el origen, la matriz de extrı́nsecos[8]. La multiplicación de estas dos matrices se conoce como la Matriz de Proyección ya que transforma un punto homogéneo de la escena en un punto homogéneo de la imagen. El modelo puede afinarse todavı́a más incluyendo otros parámetros como pueden ser las distorsiones provocadas por la lente[22]. Mientras que los extrı́nsecos varı́an según la perspectiva desde donde se haya tomado la imagen, los intrı́nsecos que se mantienen constantes para todas las imágenes de una secuencia tomada con 5.

(16) Capı́tulo 2 la misma cámara, siempre y cuando la cámara no cambie los parámetros del zoom. El flujo de trabajo a la hora de hacer RA consiste normalmente en obtener en primer lugar los intrı́nsecos y posteriormente empleando la imagen obtenida se calculan los extrı́nsecos. La calibración se puede realizar partiendo de un patrón conocido como un tablero de ajedrez[25] [11], partiendo de un patrón natural conocido o bien partiendo de una suposición sobre la estructura de la escena que nos permite hacer una estimación[5]. Una última opción es crear la matriz de calibración a partir de otros datos fı́sicos conocidos de la cámara, en nuestro caso emplearemos la distancia focal y los ángulos de visión (horizontal y vertical) porque son datos muy comunes que se pueden obtener por ejemplo del API de Android.. 2.2.. Localización de planos. Para poder hacer RA sobre un patrón plano nuestro primer objetivo es el de encontrar ese plano en la imagen. Para esto podemos emplear dos tipos de métodos de detección. Para hacer RA es imprescindible estimar la posición de la cámara en el mundo desde la cual veremos los objetos virtuales, este proceso se llama localización. Siendo éste un problema abierto, en los últimos años se han desarrollado múltiples algoritmos que permiten reconocer y estimar la posición relativa de objetos en una imagen de una forma robusta a cambios de iluminación, orientación y en presencia de oclusiones parciales. Todos ellos parten de que conocemos un patrón que se encuentra en la escena y que servirá como punto de referencia de nuestro sistema de coordenadas. Para mayor simplicidad se asume que este patrón es plano y por tanto, el primero paso a realizar para localizar nuestra cámara es el de encontrar el patrón plano en la imagen proveniente de la cámara.. 2.2.1.. Métodos basados en texturas. Para encontrar en una nueva imagen el patrón de referencia que previamente hemos aprendido y en base al cual queremos situar nuestro objeto virtual, los sistemas más simples buscan la transformación que alinea ese patrón con la imagen percibida[10]. Un método ampliamente utilizado es el de Lucas–Kanade [15] que estima el flujo óptico suponiendo que entre dos imágenes consecutivas las vecindades de un pı́xel no varı́an. Con esta restricción el método resuelve las ecuaciones básicas del flujo óptico para todos los pı́xeles vecinos por el criterio de mı́nimos cuadrados. La ventaja de este enfoque es su bajo coste computacional, haciendo que corran a la perfección en tiempo real incluso en dispositivos móviles.. 2.2.2.. Métodos basados en caracterı́sticas. Dado que los métodos basados en texturas adolecen de problemas de robustez, veremos los métodos basados en caracterı́sticas que solventan buena parte de estos problemas. En los métodos basados en detección de caracterı́sticas (feature-based) procesamos la imagen para encontrar carac6.

(17) Trabajos previos terı́sticas (elementos invariantes de la imagen) en lugar de utilizar directamente la textura de la escena. El algoritmo clásico consiste en:. 1. Detección de Caracterı́sticas: La detección de caracterı́sticas consiste en hallar aquellas partes más identificables de la imagen, estas pueden ser regiones, aristas o puntos que suelen estar caracterizadas porque el cambio de color o de nivel de gris en ellos es pronunciado. Para detectarlos se pueden emplear métodos basados en el gradiente con un menor coste computacional como el detector de esquinas de Harris[7] que usa el operador de Sobel, la diferencia de gaussianas de SIFT[14] o la comparación de cada pı́xel de la imagen con un anillo de pı́xeles a su alrededor que vemos en FAST[19]. 2. Descripción de Caracterı́sticas: Una vez hallados aquellos puntos que caracterizan una imagen, debemos describirlos a ellos y a la zona que los rodea de modo que puedan compararse con otros puntos caracterı́sticos midiendo cuán parecido es su aspecto. Para ello podemos emplear un método basado en el histograma de la región [14]. Aunque estos métodos son robustos, en términos de memoria y tiempo de cálculo son muy costosos si no se posee una implementación en GPU[24][9] por lo que de cara a su cómputo en dispositivos móviles suscitan un mayor interés el otro tipo de descriptores, los descriptores binarios. BRIEF[23] propone una codificación binaria, que se genera comparando directamente pares de puntos en las vecindades del punto caracterı́stico. La versión original se ha mejorado en diversas ocasiones para hacerlo invariante ante la rotación, el escalado y en menor medida a la iluminación y la distorsión perspectiva siendo algunos ejemplo de ello descriptores como ORB[20] o BRISK[13] que consiguen una robustez razonable[9]. 3. Emparejamiento de Caracterı́sticas: Una vez que hemos detectado y descrito todos los puntos caracterı́sticos de la imagen que estamos recibiendo de la cámara lo que debemos hacer ahora para localizar nuestro patrón es comparar los descriptores obtenidos con aquellos que previamente hallamos calculado y almacenado en nuestra base de datos (BD) de patrones. Para esta tarea se suele emplear una medida de distancia que es caracterı́stica de cada descriptor. La forma más básica es comparar cada descriptor detectado con todos los aprendidos del patrón, esta es la aproximación basada en fuerza bruta, que es razonable si tan solo disponemos de uno o dos patrones-imagen pero en caso de tener una BD con múltiples patrones esto es completamente prohibitivo. Por este motivo surgen otras alternativas basadas en técnicas de Hashing [16] para los descriptores binarios y en técnicas de Clustering [17] para los descriptores basados números de coma flotante, incluso hay algunos trabajos que muestran cómo emplear técnicas de Clustering jerárquico también sobre descriptores binarios[18]. 4. Estimación robusta del Modelo Proyectivo: Sea cual sea el algoritmo de emparejamiento empleado, el resultado será una asociación entre cada caracterı́stica de la imagen y la caracterı́stica del patrón con menor distancia a la que llamaremos correspondencia, como entre las caracterı́sticas emparejadas están también aquellas de la imagen que no forman parte del patrón, tenemos una muestra altamente ruidosa por lo que debemos estimar nuestro modelo de forma robusta. El modelo normalmente utilizado es el de buscar una transformación que nos permita determinar la correspondencia entre el plano de la imagen y el plano de la escena que contiene a nuestro patrón. En función de los grados de libertad que deseemos emplear se puede utilizar 7.

(18) Capı́tulo 2 una transformación proyectiva como la homografı́a que considera la distorsión provocada por el cambio de perspectiva, o si el plano de la escena esta suficientemente lejos de la cámara, podemos modelar su movimiento con una transformación afı́n o de similaridad que tiene menos grados de libertad y por tanto es más fácil de estimar. Para realizar la estimación modelo, puede emplearse RANSAC [6], un método iterativo que en cada iteración escoge aleatoriamente unas correspondencias con las que hacer la estimación. No obstante, como RANSAC es un método puramente aleatorio no aprovecha la información de correspondencia que proporcionan los descriptores. Otros algoritmos como MLSAC[21] o PROSAC[4] aprovechan esto ordenando la búsqueda de correspondencias comenzando por aquellas que tenı́an una menor distancia, esto les permite multiplicar hasta por 100 el rendimiento frente a RANSAC.. Figura 2.1: Flujo de trabajo en los métodos basados en caracterı́sticas. 2.2.3.. Métodos basados en marcadores. Otro método ampliamente utilizado y alternativo a los métodos basados en caracterı́sticos es la detección basada en marcas. En este caso el patrón tiene una forma muy especı́fica como la que podemos observar en la figura 2.2. Estas formas se detectan mediante el umbralizado de la imagen y el conocimiento que se tiene de su forma geométrica, ver [1, Capı́tulo 2].. (a) Marca de tipo QR (b) Marca del frame- (c) Marca empleada work ARUCO opr el framework ARToolKit. Figura 2.2: Ejemplos de marcadores de Realidad Aumentada. 8.

(19) Capı́tulo 3. Estudio de Calibración En este capı́tulo mostraremos cómo se pueden proyectar modelos tridimensionales superpuestos a la imagen que percibimos de la cámara de un dispositivo móvil. Esto no es posible simplemente con el uso de las transformaciones que citamos en el capı́tulo anterior (homografı́a y transformación afı́n), ya que solo definen la equivalencia entre los puntos del patrón detectado y de la imagen. Esto hace que con ellas solo sea posible pintar objetos virtuales que estén contenidos en el propio plano del patrón. El flujo de trabajo para poder llevar la RA a las tres dimensiones es, en primer lugar definir cuál será el sistema de referencia de nuestro mundo virtual 3D. Por ejemplo asignando un punto en tres dimensiones a cada una de las cuatro esquinas de nuestro patrón (suponiendo que sea un cuadrilátero) donde la coordenada Z de estos puntos sea 0, ya que ası́ el plano de nuestra etiqueta será el plano Z=0. En segundo lugar se ha de plantear un modelo de proyección como el que explicaremos en la primera sección de este capı́tulo. En concreto nuestro modelo consta de dos tipos de parámetros, los intrı́nsecos que determinan propiedades fı́sicas de la cámara y los extrı́nsecos que hacen referencia a la posición de la cámara en la escena. Una vez planteado el modelo proyectivo hay que estimar sus parámetros. Nosotros estimamos los intrı́nsecos gracias a la información fı́sica de la cámara que nos proveen APIs como la de Android, los extrı́nsecos los hallaremos a partir del patrón detectado en en la imagen percibida. El modo de calcular los extrı́nsecos a partir del patrón es planteando un problema Perspective-n-Point[12] que debe encontrar la posición de la cámara desde la cual los puntos del patrón se proyectarı́an sobre los puntos de la imagen que hemos detectado. La siguiente acción en el flujo de trabajo es la de definir la proyección del sistema gráfico virtual de manera equivalente al modelo de poyección que hemos estimado. Haciendo esto conseguimos que el modelo 3D virtual se proyecte sobre la imagen como lo harı́a si estuviese presente en la escena. En la segunda sección de este capı́tulo mostraremos como hacer esto empleando el sistema gráfico OpenGL.. 9.

(20) Capı́tulo 3. 3.1.. Calibración. La calibración es el proceso mediante el cual se estiman los parámetros internos de la cámara que dependen de sus propiedades fı́sicas, los parámetros intrı́nsecos. Este proceso es imprescindible para poder hacer realidad Aumentada, ya que sin ellos no podemos despejar los otros parámetros (extrı́nsecos) de nuestro sistema proyectivo. Este proceso puede ser tedioso y complejo para el usuario de un dispositivo móvil, además las “Application Programming Interface” (APIs) de los dispositivos no suelen proveer estos parámetros, en Android solo está disponible para algunos dispositivos a partir de la versión 6.0 (Octubre del 2015). Por lo que en esta sección mostraremos un modo automático de solucionar este problema basándonos en los parámetros fı́sicos que sı́ acostumbran a proporcionar estas APIs. Las cámaras observan el mundo tridimensional a su alrededor, proyectando este mundo sobre una imagen plana. En este trabajo contemplaremos un modelo Pinhole en el que asumimos que el sensor se encuentra situado delante del centro óptico de la cámara distando de él una distancia que llamaremos distancia focal (f). Para comprender el modelo de proyección comenzaremos por el caso más sencillo, supongamos que el centro de proyección es también el centro de coordenadas de un espacio euclı́deo. En el modelo Pinhole un punto del espacio X = (x, y, z)T se proyectará sobre el punto donde la lı́nea que va de X al centro corta el plano imagen. Por la similitud de triángulos de la figura 3.1, podemos ve que el punto (x, y, z)T de la escena se proyecta al punto (f x/z, f y/z, z)T que está contenido en el plano imagen.. Figura 3.1: Modelo de cámara Pinhole. Esta forma de proyectar hace que el punto (0,0) esté situado en el centro de la imagen, donde el eje Z atraviesa el plano imagen. Para generalizar esto a un caso en el que el centro de coordenadas de la imagen no esté centrado, se introduce un desplazamiento de modo que ahora el punto (x, y, z)T se proyecta a (f x/z +px , f y/z +py , z)T , donde (px , py )T son las coordenadas del punto principal (el centro de la imagen). Esta expresión también puede expresarse empleando coordenadas homogénea 10.

(21) Estudio de Calibración del siguiente modo:.     X f X + Zpx f  Y  →   f Y + Zpy  =  7  Z  Z 1 . f. px py 1.  X 0  Y   0 ·  Z . 0 1 . . (3.1). Este modelo proyecta un punto de la escena sobre el plano imagen, pero el sistema de referencia en las que se expresa el punto de la imagen son las mismas coordenadas euclı́deas de la escena cuando lo usual es que la unidades de la imagen sean los pı́xeles capturados por el sensor. Para que esto sea ası́ lo que hemos de hacer es expresar las medidas del sistema proyectivo en pı́xeles. Para ello definiremos la focal en pı́xeles α = f ku y β = f kv donde (1/ku , 1/kv ) son las dimensiones del pı́xel en unidades de la escena y el punto principal (i0 , j0 ) también en pı́xeles. Si obviamos la última columna de ceros, tenemos la matriz K también llamada matriz de intrı́nsecos[8]:. .    f ku s i0 α s i0 K =  0 f kv j0  =  0 β j0  0 0 1 0 0 1. (3.2). La s representará el sesgo de la cámara, que usualmente vale 0. En general los puntos del espacio se representarán en un sistema de coordenadas distinto que llamaremos sistema de coordenadas de la escena o del mundo. Los dos sistemas de coordenadas se relacionan entre sı́ a través de una rotación y una traslación. De modo que si Xmundo es un vector cartesiano que representa las coordenadas de un punto en el sistema de referencia del mundo, nosotros podemos calcular su punto equivalente en el sistema de referencia de la cámara como: Xcamara = R(Xmundo − C), donde C representa la posición de la cámara en coordenadas de la escena y R es una matriz de rotación de tamaño 3x3 que representa la orientación de la cámara respecto a los ejes de coordenadas del mundo. Si expresamos esto mediante de coordenadas homogéneas podemos construir un sistema proyectivo de la forma: P3x4 = K3x3 [R3x3 |t3x1]. (3.3). El parámetro t = −RC es decir el centro de la escena expresado en coordenadas de la imagen. A la matriz resultante P se le llama matriz de proyección mientras que a [R|t] se la conoce como matriz de extrı́nsecos.. 11.

(22) Capı́tulo 3. 3.2.. Calibración usando parámetros fı́sicos de la cámara del móvil. Nosotros deseamos por tanto obtener todos estos parámetros para proyectar nuestros modelos 3D artificiales como si existiesen en el mundo real. Impondremos las siguientes restricciones: El valor del sesgo es 0. El punto principal está centrado en la imagen. No existe distorsión óptica. Imponiendo estas restricciones vemos que los únicos parámetros que quedan por resolver son f ku y f kv que se interpretan como la distancia focal expresada en pı́xeles. Para encontrar estos parámetros y obtener por tanto la matriz deseada vamos a echar mano de tres de las propiedades fı́sicas de la cámara más conocidas y que suelen estar disponibles en todas las APIs móviles. La Distancia Focal (f), que se mide en mm. Ángulo de visión horizontal (Θx ), medido en grados. Ángulo de visión vertical (Θy ), medido en grados. Además de estos parámetros fı́sicos emplearemos también el número de pı́xeles de la imagen, donde wpix será el ancho en pı́xeles y width en milı́metros, del mismo modo hpix será el alto en pı́xeles y height en milı́metros. La representación gráfica de esos valores puede verse en la figura: 3.2. Como la focal de la cámara en milı́metros podemos obtenerla de forma directa llamando a las funciones de las APIs, lo que necesitamos es el tamaño del sensor (el plano imagen) para poder expresar esta focal en función del tamaño de cada pı́xel. Estas operaciones se realizan en los cálculos de la figura 3.3. De forma análoga a como hemos calculado el ancho del sensor puede calcularse su alto, de modo que ya podemos calcular los parámetros de la matriz de intrı́nsecos como: f · ku =. 12. f 2f ·tan(Θx /2) wpix. =. wpix ; 2 tan(Θx /2). f · kv =. f 2f ·tan(Θy /2) hpix. =. hpix 2 tan(Θy /2).

(23) Estudio de Calibración. Figura 3.2: Parámetros proporcionados por la API de Android. Figura 3.3: Cálculos para la obtención del tamaño del sensor. 13.

(24) Capı́tulo 3. 3.3.. Algoritmo de calibración para Android. En el Sistema Operativo (SO) Android, los parámetro fı́sicos (Θx , Θy , f ) que hemos empleado anteriormente para calcular la matriz de proyección pueden obtenerse de modo directo a través de la clase Camera.Parameters que dispone de las siguientes funciones:. float getFocalLength () float getVerticalViewAngle () float getHorizontalViewAngle (). Como parte de nuestro sistema hemos creado una clase CameraCalibration que recubre a la matriz de intrı́nsecos y que puede construirse empleando los tres parámetros devueltos por el API de Android: Listado 3.1: Código C++ para el cálculo de la matriz de intrı́nsecos CameraCalibration : : CameraCalibration ( float f , float horizontalAngle , float verticalAngle , cv : : S i z e i m g S i z e ) : m imgSize ( i m g S i z e ) { / / m i n t r i n s i c i s a f i e l d o f t y p e cv : Matx33f m i n t r i n s i c = cv : : Matx33f : : z e r o s ( ) ; float 2 float 2. sensor size x = ∗ f ∗ tan ( toRadians ( horizontalAngle / 2 ) ) ; sensor size y = ∗ f ∗ tan ( toRadians ( verticalAngle / 2 ) ) ;. f l o a t p i x e l s i z e x = s e n s o r s i z e x / m imgSize . w i d t h ; f l o a t p i x e l s i z e y = s e n s o r s i z e y / m imgSize . h e i g h t ; m m m m m }. 14. intrinsic intrinsic intrinsic intrinsic intrinsic. (0 , (0 , (1 , (1 , (2 ,. 0) 2) 1) 2) 2). = = = = =. f m f m 1;. / pixel size x imgSize . w i d t h / pixel size y imgSize . h e i g h t. ; / 2; ; / 2;.

(25) Estudio de Calibración. 3.4.. Obtención de la Matriz de Proyección en OpenGL. OpenGL es el estándar más extendido para la renderización de gráficos, siendo soportado en la gran mayorı́a de dispositivos tanto móviles como de escritorio. En OpenGL están basados también gran parte de los motores de videojuegos que permiten el desarrollo de animaciones mucho más complejas como Unity y Unreal, es por ello que lo escogemos para implementar nuestros sistemas gráficos de modo que en el caso de que sean migrados en un futuro a alguno de estos motores, la adaptación sea lo más sencilla posible. En OpenGL, la proyección de las coordenadas del modelo también se lleva a cabo mediante el uso de una matriz de proyección, que, no obstante, tiene algunas diferencias con la matriz de intrı́nsecos de visión, en la presente sección veremos esas diferencias y mostraremos cómo expresar la matriz de intrı́nsecos en forma de una matriz de proyección de OpenGL. OpenGL emplea una serie de transformaciones geométricas para plasmar los distintos objetos de la escena en la imagen final, estos objetos están compuestos por una serie de primitivas como lı́neas, triángulos y vértices. Las transformaciones que se aplican sobre estos objetos pueden definirse como matrices que se acumulan multiplicando por el lado derecho de los vértices (postmultiplicando) a lo largo de proceso de transformación de OpenGL, que vemos en la figura 3.4. La primera matriz en post-multiplicar los puntos es la “ModelView Matrix” cuyo objetivo es transformar de las coordenadas de la escena a las coordenadas de la cámara.. Figura 3.4: Transformaciones aplicadas por OpenGL. OpenGL supone que el centro de coordenadas de la escena se encuentra situado en el centro de la cámara y por tanto la escena ha de transformarse con la matriz ModelView que podemos obtener a partir de los extrı́nsecos de visión, donde R era la matriz de rotación 3x3 que representa la orientación de la cámara en la escena y t el vector que se puede calcular como t = −RC, siendo C la posición de la cámara en la escena.  M odelV iew4x4 =. T R3x3 −t3x1 01x3 1. . El siguiente paso una vez que ya tenemos los objetos expresados en coordenadas de la cámara es proyectarlos a “Clip Coordinates”, que son coordenadas homogéneas cuyos valores están en el rango [-1, 1]. Para lo que se emplea la ProjectionMatrix, esta matriz debe seguir la definición que podemos observar en la ecuación 3.4, donde cada uno de sus parámetros es una delimitación geométrica que mostramos en 3.5: 15.

(26) Capı́tulo 3. Figura 3.5: Transformación del Frustrum de Opengl a “Clip Coordinates”.  2n. r−l.   0  P rojectionM atrix =   0  0. 2n t−b. r+l r−l t+b t−b. 0. −(f +n) f −n. 0. −1. 0. 0. .  0    −2f n  f −n . (3.4). 0. Si comparamos entre lo definido por OpenGL para el Frustrum y la matriz de proyección de visión, podemos ver que hay una serie de equivalencias: r-l: Representa el ancho de la imagen, en visión width. r+l: Esta suma simboliza es desplazamiento del punto principal en el eje X respecto del punto principal ideal (width/2), por lo que r + l ⇔ i0 − (width/2). t-b: Representa el alto de la imagen, en visión height. t+b: Esta suma simboliza es desplazamiento del punto principal en el eje Y respecto del punto principal ideal (height/2), por lo que t + b ⇔ −(j0 − (height/2)). Incluimos un signo negativo porque partimos de un eje de coordenadas en visión que comienza en la parte superior izquierda de la imagen. 2n n (p11 ): El elemento P rojectionM atrix11 = r−l tiene como objetivo proyectar un punto homogéneo de la escena a un punto homogéneo de la imagen expresado en coordenadas recortadas, en inglés “Clip Coordinates”, cuyos valores están en el intervalo [-1, 1]. 2 Dado que n es la distancia del centro al plano “near”, podemos interpretar el r−l como la función que hace la conversión de pı́xeles de la imagen a coordenadas recortadas.. En visión la proyección se apoya en la igualdad de triángulos que podemos ver en la figura 3.1, donde la focal de la cámara cumple un papel análogo al del plano “near” de OpenGL. Teniendo en cuenta que (r - l) es el ancho de la imagen que vendrá expresado en pı́xeles, la distancia focal ha de expresarse también en función del ancho de los pı́xeles, por lo que 2f ku 2n ⇔ width podemos establecer la equivalencia: r−l 16.

(27) Estudio de Calibración n (p22 ): De un modo análogo a lo explicado para el eje X, para el elemento 2f kv 2n 2n P rojectionM atrix22 = t−b , puede establecerse la equivalencia: t−b ⇔ height .. Por lo que podemos emplear nuestros intrı́nsecos para generar la matriz de proyección del siguiente modo:.      f ku 0 i0   0 f kv j0  =⇒    0 0 1 . −2f ku width. 0. i0 −(width/2) width. 0. 2f kv height. j0 −(height/2) height. 0. 0. −(f ar+near) f ar−near. 0. 0. −1. 0. .    0  −2f ar·near   f ar−near  0. (3.5). Una vez hemos definido estas dos matrices, OpenGL se encargará de completar automáticamente los pasos restantes: Transforma de coordenadas homogéneas a cartesianas en [-1, 1] denominadas Normalized Device Coordinates (NDC) y pasar las coordenadas en NDC a coordenadas de la ventana, que serán los pı́xeles resultantes. Resumiendo, en la primera sección hemos visto cómo obtener a partir del dispositivo los parámetros intrı́nsecos de la cámara mientras que en la presente sección se ha mostrado cómo pasar de estos intrı́nsecos a la matriz de proyección de OpenGL que nos permitirá pintar objetos en 3D, en esencia esto es todo lo necesario para saber cómo hemos de representar nuestra capa de realidad aumentada. La única cuestión que queda sin resolver es cómo obtener los parámetros extrı́nsecos, o lo que es lo mismo, en qué parte de la imagen debemos pintar nuestros modelos virtuales. Para resolver este punto veremos en el capı́tulo siguiente un método para localizar letreros sobre los que hacer RA, pero primero algunos experimentos que contrasten nuestras hipótesis.. 3.5.. Experimentos. Para poder verificar el correcto funcionamiento de las dos conversiones de parámetros anteriormente descritas se han diseñado dos experimentos, el primero es un estudio del error de reproyección producido al estimar erróneamente la focal de la cámara, el segundo de ellos es una comparación visual entre la proyección realizada con la matriz de proyección de OpenCV y la matriz de proyección de OpenGL de modo que se pueda comprobar su equivalencia.. 3.5.1.. Error de Reproyección. El error de reproyección es el error que se produce cuando aprendemos un modelo proyectivo como la matriz de proyección que podemos ver en la fórmula 3.6 y lo empleamos posteriormente para proyectar sobre la imagen esos mismos puntos con los que hemos aprendido el modelo. 17.

(28) Capı́tulo 3.   f ku 0 i0 P =  0 f kv j0  (R3x3 |t3x1 ) 0 0 1. (3.6). Como la estimación de la focal puede estar sujeta a errores, emplearemos el error de reproyección para medir cuánto influye sobre la proyección el emplear un tamaño de focal diferente al correcto (el de la cámara con la que fue tomada la imagen o secuencia de imágenes). Para esto hemos empleado una secuencia de imágenes en la que se ha grabado un patrón de calibración tipo tablero de ajedrez, con él se calibrará la cámara obteniendo los parámetros intrı́nsecos reales. Posteriormente se modificarán estos valores para ver cómo se comporta el error de reproyección cuando la focal toma valores desde un 10 por ciento de su tamaño real hasta un 200 por ciento. Los resultados visuales pueden observarse en la figura 3.6, donde observamos que para una focal pequeña el efecto visual que se produce es una pérdida de paralelismo entre las lı́neas que deberı́an ser paralelas, mientras que para focales más grandes de lo normal el efecto es un paralelismo cada vez mayor que redunda en una pérdida de perspectiva. Midiendo la diferencia entre cada esquina del tablero y la posición en la que se ha reproyectado (en pı́xeles) podemos calcular el error total de reproyección. Esto se ha hecho para las imágenes tomadas por un Huawey U8650 de tamaño (2048x1563px) y para las imágenes provenientes de un vı́deo tomado por un Motorola Moto X en resolución (1920x1080px). El resultado puede observarse en las gráficas 3.7 donde el eje de las abscisas representa la focal empleada en relación a la focal real, siendo 10 % una focal diez veces más pequeña que la real y un 200 % una focal con el doble de tamaño. En el eje de ordenadas tenemos el error de reproyección medio entre los puntos del tablero expresado en pı́xeles. En las gráficas se puede ver que una pequeña desviación al estimar la focal no tiene un efecto visualmente muy impactante, mientras que si la desviación crece este error aumenta, especialmente si la estimación de la focal es menor de su valor real. Otro efecto importante a destacar de las gráficas 3.7 es que el error nunca llega a valer 0px, ni tan siquiera cunado se emplea la focal real que se ha calculado mediante el método de calibración. Esto es ası́ porque el método de calibración también sufre de un pequeño error, que como vemos está en torno a los 0,25 pı́xeles por punto reproyectado. Esto se debe principalmente a que no modelamos la distorsión no lineal. Para los dos dispositivos citados anteriormente, se ha calculado su matriz de calibración empleando el algoritmo 3.1, y se ha visto que comparando estos valores valores con los obtenidos del proceso de calibración, existe una diferencia de un 2’5 % y un 3 % entre ambas medidas. Por lo que podemos concluir que el método aquı́ propuesto es válido para la estimación de los intrı́nsecos y además produce un error de reproyección de aproximadamente 0.30 px dada la curva de la figura 3.7.. 3.5.2.. Visualización de la proyección de OpenGL. En este segundo experimento comparamos la proyección que realiza la matriz proyectiva 3.6 empleando la librerı́a de visión OpenCV con la proyección de la escena realizada por OpenGL. El 18.

(29) Estudio de Calibración. (a) Reproyección con f: 10 %. (b) Reproyección con f: 30 %. (c) Reproyección con f: 50 %. (d) Reproyección con f: 80 %. (e) Reproyección con f: 100 %. (f) Reproyección con f: 200 %. Figura 3.6: Esquinas de un tablero de ajedrez reproyectadas con distintas distancias focales. 19.

(30) Capı́tulo 3. Figura 3.7: Gráficas del error de reproyección frente a la desviación en la estimación de la focal objetivo de este experimento es confirmar que las ecuaciones 3.4 y 3.5 son correctas. Para este experimento se emplea como patrón una imagen natural impresa, que se ha escaneado previamente. En la figura 3.8 podemos ver a la derecha la proyección de OpenCV en la que se han dibujado las esquinas detectadas del patrón (en rojo) junto con el eje de coordenadas de la escena. A la izquierda vemos la proyección de OpenGL donde hemos dibujado los bordes del patrón con una lı́nea azul y sobre el centro de la escena un cubo. Para que experimento sea exitoso lo que deberı́a ocurrir es que el contorno azul de OpenGL pase exactamente por encima de los puntos rojos de la imagen de OpenCV, y por otra parte que el cubo dibujado con OpenGL se encuentre centrado en el eje de coordenadas que nos indica OpenCV. Vemos que en los resultados esto efectivamente se cumple. Para la detección del patrón en este caso se ha implementado un sistema que emplea un método basado en puntos caracterı́sticos que explicamos en la sección 2.2.2, en concreto se ha utilizado el detector y descriptor de ORB[20], el emparejamiento se ha hecho empleando fuerza bruta, y la homografı́a que nos permite establecer la equivalencia entre el plato del patrón y el plano imagen se ha estimado de forma robusta gracias a PROSAC[4]. Una vez conocida la homografı́a se ha empleado ésta para calcular las cuatro esquinas del patrón y en base a ellas con una aproximación PnP[12] se ha estimado la translación y rotación de la cámara (parámetros extrı́nsecos).. 20.

(31) Estudio de Calibración. Realidad aumentada. Pose estimada. Figura 3.8: Proyección mediante la matriz P de visión (columna derecha) VS proyección de OpenGL (columna izquierda). 21.

(32) 22.

(33) Capı́tulo 4. Búsqueda de Letreros En este capı́tulo se tratará un problema más especı́fico que el abordado en el capı́tulo anterior ¿Como encontrar un letrero en una imagen, si sabemos de antemano que este letrero se encuentra rodeado por una lı́nea y que tiene unos determinados colores? En nuestro caso concreto deseamos hallar la respuesta a esta pregunta porque queremos crear un motor de Realidad Aumentada tridimensional que será la pieza clave de una aplicación más grande de reserva de salas. Las salas estarán marcadas con un letrero como el que vemos en la figura 4.1 y nuestro objetivo será detectarlo en la imagen proveniente de la cámara, rectificar la imagen para que sobre el letrero pueda aplicare un algoritmo de reconocimiento de texto y por último permitir que se puedan dibujar objetos 3D sobre él. Para esta tarea podrı́an emplearse marcadores QR, pero son muy poco estéticos y de imposible comprensión para un humano. En su lugar usaremos letreros como el de la figura 4.1, que tienen un color de fondo y además disponen de una lı́nea que rodea su contorno. El principal problema de estas marcas es que carecen de una textura bien diferenciada, por lo que las aproximaciones clásicas basadas en detección de caracterı́sticas no son válidas. En este capı́tulo propondremos un algoritmo para la detección de este tipo de letreros. Este algoritmo primero transforma la imagen en color a una imagen en escala de grises donde el contaste de la lı́nea que rodea el letrero es máximo, a continuación empleando métodos basados en un umbral dinámico o en la detección de bordes se detectan los posibles contornos candidatos. Después se emplean una serie de restricciones para poder detectar cuáles de estos contornos conforman la lı́nea del borde y por último se rectifica el letrero o se hace Realidad Aumentada sobre él. Figura 4.1: Ejemplo de letrero estético a detectar. 23.

(34) Capı́tulo 4. 4.1.. Paso a escala de grises. Cada pı́xel de una imagen en color se compone normalmente de tres canales RGB(Red-GreenBlue) que se representan mediante valores entre 0 y 255. Una forma alternativa de ver esta representación del color es como un espacio vectorial de tres componentes en el cual cada color es un vector. Por otra parte, una imagen en escala de grises se compone de una única componente que toma valores entre 0 y 255, lo que visto desde la perspectiva del espacio vectorial RGB puede entenderse como proyectar cada color de la imagen sobre la recta que va desde el (0,0,0) que simboliza el color negro hasta el (255, 255, 255) que representa al color blanco. Ya que la transformación a escala de grises es necesaria para poder aplicar los siguientes pasos del algoritmo, podemos emplear lo explicado en los párrafos anteriores y nuestro conocimiento sobre los colores del letrero para encontrar la proyección de colores que maximiza el contraste entre el color de la lı́nea y el color del letrero. La proyección de colores que maximiza el contraste entre el color de la linea y el del fondo del letrero es aquella que se hace sobre la recta que pasa por estos dos puntos del espacio RGB, como se puede ver el la figura 4.2. Por lo que para calcular el gris correspondiente a cada color de la imagen, debemos en primer lugar calcular esta recta, luego proyectar todos los colores sobre ella, y por último normalizar estos valores para que se encuentren entre 0 y 255.. Figura 4.2: Proyección es el espacio RGB. La ecuación de esta recta puede calcularse a partir de uno de los dos puntos de color c1 o c2 y del vector dirección que será ~v = c1 − c2, no obstante como lo único que necesitamos es la proyección de los puntos de la imagen sobre esta recta, podemos calcularla como la proyección sobre su vector dirección ~v , para ello emplearemos una proyección escalar. Sea c el color(punto del espacio RGB) del que queremos calcular su proyección sobre ~v , esta 24.

(35) Búsqueda de Letreros. (a) Cartel 1 antes de la con- (b) Cartel 1 después de la (c) Cartel 2 antes de la con- (d) Cartel 2 después de la versión conversión versión conversión. Figura 4.3: Diferencia en la conversión a escala de grises entre un letrero con colores bien seleccionados y mal seleccionados. proyección viene dada por la proyección escalar de su vector sobre el vector ~v :. P roy~v ~c =. |~c · ~v | |~v |. No obstante esta proyección puede dar valores que no estarán necesariamente entre 0 y 255, por lo que emplearemos una transformación afı́n x 7→ Ax + b para colocarla todos los puntos(X) entre estos valores.. x 7→ Ax + b;. A=. 255 ; max(X) − min(X). b=−. 255 min(X) max(X) − min(X). De esta forma de calcular la imagen en escala de grises también se puede deducir una recomendación a la hora de seleccionar el color de los letreros, pues para una buena detección es favorable que la recta que pasa por los dos colores seleccionados sea ortogonal a la recta de luminosidad (si la recta es de esta forma es menos probable que halla colores que se proyecten sobre ella fuera del segmento delimitado por la proyección de los dos colores originales), y que estos colores sean entre si lo más distantes posible. Un ejemplo podemos encontrarlo en las figuras 4.3 donde a la derecha vemos un letrero cuyos colores del fondo del letrero y de la lı́nea a detectar son prácticamente el mismo solo que con el color de la lı́nea un poco más claro, como consecuencia, se ve que al pasarlo a escala de grises con el método propuesto anteriormente el contraste generado no es notorio. A la izquierda sin embargo, tenemos un letrero en el que el color del fondo es azul y la lı́nea amarilla (color antagónico del azul), por lo que la recta entre los dos colores es ortogonal a la recta luminosidad y vemos que en el paso a escala de grises esto redunda en un mayor contraste. Este procedimiento es costoso computacionalmente con lo que se ha implementado una solución paralela que emplea multi-threading para agilizar el cálculo especialmente en dispositivo móviles. 25.

(36) Capı́tulo 4. 4.2.. Detección de contornos. Ahora que ya tenemos la imagen en escala de grises nos interesa detectar en ella todos los posibles contornos que sean susceptibles de ser la lı́nea que rodea nuestro letrero. Los contornos son aquellos pı́xeles que en una imagen binaria (solo contiene unos y ceros) forman una frontera entre los unos y los ceros. Nuestro objetivo es el de extraer de una imagen todos los bordes en forma de contornos, para poder buscar entre ellos aquellos que conforman el borde del letrero. Para ello primero debemos binarizar la imagen haciendo que esta imagen binarizada refleje lo más claramente posible el contorno de la lı́nea de nuestro letrero. Se pueden emplear dos enfoques, el más sencillo de ellos consiste en establecer un umbral (Threshold) en el nivel de gris, de modo que todos los pı́xeles de la imagen con un nivel de gris superior pasan a valer 1 y todos aquellos con un nivel inferior 0. Esta alternativa es rápida de computar pero muy sensible a la iluminación y al ruido, puesto que no es fácil encontrar ese umbral y aún si lo encontrásemos podrı́a darse el caso de que una zona clara esté mal iluminada y pueda ser interpretada como oscura, si eso sucediese no serı́amos capaces de diferenciar en la imagen binarizada el borde del letrero. Otro posible enfoque es emplear un filtro de detección de bordes basado en el gradiente, como por ejemplo en filtro de Canny[2], este filtro emplea el operador de Sobel para calcular el gradiente de la imagen por lo que su evaluación no es excesivamente costosa siendo mucho más robusto ante cambios de iluminación y al ruido. El algoritmo de binarización que planteamos aquı́ emplea ambos enfoques, empleando el filtro de Canny en caso de que no se haya detectado el letrero previamente, y en caso contrario empleando Thresholding.. Data: image, color1, color2, last threshold, last frame contained a square Result: squares, last frame contained a square, used threshold blurred img = blur image(image); gray img = convert to grayscale using color projection (blurred img, color1, color2); if last frame contained a square then squares, used threshold = find squares by threshold levels (gray img, last threshold); last frame contained a square = not squares.empty(); else thresh img = Canny(gray img); squares = find squares in thesholded image(threshImg); if not squares.empty() then middle thres = get middle threshold(); thres squares, used threshold = find squares by threshold levels (gray img, middle thres); last frame contained a square = not thres squares.empty(); end end Algorithm 1: Umbralizado de la imagen en blanco y negro. 26.

(37) Búsqueda de Letreros. (a) Escala de grises. (b) Contornos Canny. (c) Umbral = 100 (Nivel 0). (d) Umbral = 101 (N. 1 hacia arriba). (e) Umbral = 99 (N. 1 hacia abajo). (f) Umbral = 103 (N. 2 hacia arriba). Figura 4.4: Diferentes binarizaciones empleando el filtro de Canny y distintos niveles de umbralización.. Este proceso de binarización va a ser ejecutado para cada fotograma que llegue procedente de la cámara, es por ello que si en el fotograma anterior hemos utilizado con éxito un determinado umbral para binarizar, es muy probable que el mismo umbral o uno similar nos sirvan a no ser que el letrero ya no esté visible. Este comportamiento se muestra en el algoritmo 1, que en caso de en el fotograma anterior se haya detectado un letrero llama a la función find squares by threshold levels pasándole la imagen actual y el umbral empleado en la iteración anterior. Find squares by threshold levels lo que hace es buscar un letrero en una serie de niveles de umbralizado, comenzando en el nivel que se le pasa como segundo parámetro y si en este no encuentra el letrero itera buscando en los niveles contiguos al anterior, separándose del nivel inicial una distancia que es exponencial en el ı́ndice de la iteración. En las imágenes 4.4 vemos en primer lugar (a) la imagen en escala de grises que obtenemos con el algoritmo 1, en segundo lugar (b) vemos la detección de bordes llevada a cabo por el filtro de Canny, que se aplica al comienzo para ver de modo robusto si hay algún letrero en la imagen. Si Canny tras binarizar la imagen encuentra el letrero mediante la llamada find squares in thesholded image(threshImg) que explicaremos en el siguiente sub-apartado, entonces probaremos también el umbralizado. Esto se hace ası́ porque si no tenemos conocimiento de que haya o no un letrero presente en la imagen, recorrer todos los posibles niveles de umbralizado en cada fotograma serı́a muy costoso, 27.

(38) Capı́tulo 4 no obstante si ya sabemos que el letrero existe, solo tenemos que buscar en los distintos niveles la primera vez para encontrar el umbral correcto, en los siguientes fotogramas buscaremos partiendo de ese umbral lo cual es mucho más rápido. Este primer umbral de búsqueda es la proyección del punto medio entre los dos colores a escala de grises, en el ejemplo este valor es 100 que puede verse en la sub-figura (c). En esta imagen la claridad de una parte del letrero hace que no se detecte correctamente toda la lı́nea por lo que en una segunda iteración buscaremos en los niveles colindantes 101 (d) y 99 (e) donde tampoco encontraremos el cuadrado. Es en la tercera iteración donde al emplear un umbral de 103 podemos separar claramente la lı́nea del fondo del letrero (f), por lo que la detección es exitosa. En las próximos fotogramas el nivel del que partirá el algoritmo será 103 en vez de 100 por lo que la búsqueda será mucho más veloz. A partir de esta imagen binarizada extraer los contornos es tan sencillo como seleccionar aquellos pı́xeles negros que tienen algún vecino blanco o viceversa, agrupando en un único contorno aquellos contiguos entre sı́.. 4.3.. Selección de contornos. En la secciones anteriores pudimos ver cómo pasar de una imagen en color a una imagen en escala de grises y cómo obtener de esta imagen una serie de contornos, en la presente sección veremos como podemos tratar a éstos contornos para averiguar si estos son parte del la lı́nea que rodea nuestro letrero obteniendo como resultado final la posición de las cuatro esquinas (ver figura 4.5). El primer paso que debemos dar es reducir el número de puntos que definen nuestro contorno, esto puede hacerse empleando el algoritmo de Ramer–Douglas–Peucke, que dada una curva compuesta por segmentos, encuentra una curva similar aproximada con menos puntos. El algoritmo define una diferencia  basada en la máxima distancia entre la curva original y la curva simplificada. Esta diferencia tendrá un gran impacto en nuestro problema de detección, ya que un  muy grande hará que que otras figuras puedan ser tomadas como letreros y un  muy pequeño hará que debido al ruido los bordes de nuestro letrero puedan ser aproximados con más de 4 puntos principales (las cuatro esquinas). Para ello definiremos  en función de un parámetro del algoritmo epsilonRatio y de la longitud del contorno:  = epsilonRatio ∗ longitud Una vez que hemos simplificado adecuadamente todos los contornos, nos quedaremos con aquellos que tienen tan solo cuatro lados, ası́ descartaremos todos los bordes de cosas cuya forma no sea un cuadrilátero. También establecemos un área mı́nima de cara a descartar pequeños contornos como letras o paredes con azulejos. A continuación exigiremos que el contorno sea convexo, pues desde cualquier perspectiva un letrero nunca puede ser cóncavo. Tras todas estas comprobaciones ya hemos seleccionado los cuadriláteros convexos y de gran tamaño. Si los pasos previos se han sucedido adecuadamente dos de ellos deberı́an formar el borde interno y el borde externo de la lı́nea que rodea nuestro letrero, por lo que para encontrarlos, generaremos todos los posibles pares, y comprobaremos que los centros (para cada cuadrilatero, el 28.

(39) Búsqueda de Letreros. (a) Total de contornos detectados. (b) Contornos una vez aplicado el algoritmo de Ramer–Douglas–Peucke. (c) Contornos de cuatro lados. (d) Contornos cuyo tamaño superan el umbral. (e) Contornos convexos. (f) Contorno con menor área de entre los finales. Figura 4.5: Selección de contornos. punto donde se cruzan sus diagonales) de ambos cuadrilateros no disten entre sı́ más de maxCenterDistance pı́xeles. Si el par de contornos cumple esta condición entonces nos quedamos con el elemento dentro del par que tiene menor área, es decir el borde interno. Por si hubiese varios letreros en escena, el paso final consiste en ordenar los letreros detectados por su área, devolviendo una lista ordenada en la que los letreros más granes (los más próximos) estén al inicio, esto también es un modo de seleccionar el letrero frente a otros falsos positivos que hayan podido aparecer.. 4.4.. Rectificar el letrero. A partir de los pasos vistos en la sección anterior ya podemos obtener la posición de las cuatro esquinas, esto es muy importante ya que nos permitirá calcular la posición de la cámara en caso de que queramos hacer realidad aumentada. Un punto de interés en este caso es rectificar el letrero para poder utilizar el contenido que en él aparece, en el caso de la figura 4.1 podrı́amos emplear un reconocedor de textos para leer los dı́gitos que en él aparecen. 29.

(40) Capı́tulo 4 Esto puede hacerse mediante el uso de una transformación proyectiva como la homografı́a que nos defina como mapear la imagen original (la que nos llega a través de la cámara) a la imagen destino donde deseamos plasmar el letrero rectificado. Previamente al empleo de una homografı́a debemos definir las correspondencias entre los puntos de ambas imágenes origen-destino, para lo cual es imprescindible ordenar previamente las esquinas del letrero detectado en la imagen. Si suponemos que el letrero estará colgado de una pared, y por tanto en posición vertical, el problema se reduce a identificar correctamente esas cuatro esquinas y mapearlas a las cuatro esquinas de la imagen destino. Para ordenar las cuatro esquinas almacenadas en un vector es conveniente el empleo de la función std::sort (RandomAccessIterator first, RandomAccessIterator last, Compare comparator), donde el tercer parámetro es una función o clase comparadora, que dados dos elementos del vector determina cual de ellos es mayor. Para comparar dos puntos de un mismo cuadrilátero emplearemos como medida el ángulo que cada uno de estos puntos forma con el centro de gravedad de la figura. Esto nos dará de forma natural una ordenación como la que se puede observar en la figura 4.6. El ángulo entre los puntos debe obtenerse teniendo en cuenta que el eje Y de la imagen es el inverso del eje y geométrico convencional. El ángulo entre el punto p y el centro c puede calcularse como: tan(θ) =. θ = arctan. c.y − p.y p.x − c.x c.y − p.y p.x − c.x. !. Figura 4.6: Ordenación de las esquinas según su Una vez que hemos ordenado adecuadamente ángulo con el centro de gravedad del cuadrilátero las esquinas del letrero, el siguiente paso cosiste en definir un vector con las esquinas de la imagen destino donde queremos que se transforme el letrero rectificado. Hecho esto, sólo tenemos que construir la homografı́a correspondiente y emplearla para rectificar el letrero. Esto puede verse en el código 4.1.. 30.

(41) Búsqueda de Letreros Listado 4.1: Código C++ para el rectificado del letrero void S q u a r e D e t e c t o r : : wrapSquare ( s t d : : v e c t o r <cv : : P o i n t > & s q u a r e , c o n s t cv : : Mat& s r c , cv : : Mat& d s t , cv : : S i z e w r a p S i z e ) { i n t w = wrapSize . width ; i n t h = wrapSize . h e i g h t ; P o i n t 2 f p00 ( 0 , 0 ) , p01 ( 0 , w) , p10 ( h , 0 ) , p11 ( h , w ) ; P o i n t 2 f o u t P o i n t s [ 4 ] = { p00 , p10 , p11 , p01 } ; Point2f inPoints [4] = { square [0] , square [1] , square [2] , square [3] }; Mat homography = g e t P e r s p e c t i v e T r a n s f o r m ( i n P o i n t s , o u t P o i n t s ) ; w a r p P e r s p e c t i v e ( s r c , d s t , homography , w r a p S i z e ) ; }. Todo ello se ha implementado para correr en dispositivos móviles. El resultado puede observarse en la imagen 4.7:. Figura 4.7: Captura de pantalla que muestra el rectificado del letrero. 31.

(42) Capı́tulo 4. 4.5.. Experimentos. Para comprobar el correcto funcionamiento del algoritmo se han llevado a cabo experimentos con distintas secuencias de imágenes en las que se somete al algoritmo a diferentes perspectivas, condiciones de iluminación, ruido y oclusiones. También se han utilizado varios letreros para probar el comportamiento con diferentes colores de letrero y de lı́nea. En estas pruebas haremos Realidad Aumentada empleando OpenGL para proyectar un cubo sobre el centro de la escena. La correcta proyección de este objeto 3D está avalada por los experimentos realizados en el capı́tulo anterior. Los resultados obtenidos pueden verse en las imágenes de las figuras 4.8 y 4.9, donde las primeras cuatro imágenes muestran el comportamiento del algoritmo bajo luz artificial y con un patrón de colores contrastados. De izquierda a derecha y de arriba a abajo podemos ver el resultado final donde se proyecta un cubo virtual empleando OpenGL, a la derecha el sistema de coordenadas de la escena proyectado mediante las técnicas de visión con la librerı́a OpenCV. Abajo a la izquierda vemos el paso de la imagen a escala de grises empleando la proyección en el espacio de color y por último abajo a la derecha la imagen binaria que resulta del umbralizado. En el segundo grupo de imágenes podemos ver un patrón más claro sometido a luz natural y visto desde una distancia y perspectiva diferentes que también es detectado con éxito. A la vista de los resultados podemos concluir que el algoritmo se comporta adecuadamente ante cambios de perspectiva, iluminación e incluso el ruido provocado en la imagen por los focos fluorescentes. La única situación donde se ha observado que el algoritmo tiene problemas es cuando no consigue observar por completo el perı́metro del letrero. Éste hecho puede darse si dejamos fuera una parte del letrero o bien si existe alguna oclusión.. 32.

(43) Búsqueda de Letreros. Figura 4.8: Experimentos de la detección de letreros. 33.

(44) Capı́tulo 4. 34 Figura 4.9: Secuencia de vı́deo grabada para los experimentos. Fila 1 y 3 imágenes originales, filas 2 y 4 imágenes en RA.

(45) Capı́tulo 5. Conclusiones Los sistemas actuales no son válidos para hacer realidad aumentada 3D sobre letreros debido principalmente a lo difı́cil que es detectarlos, una complicación añadida es la tediosa calibración de los dispositivos móviles que hace inviable muchas aplicaciones prácticas. Hemos solucionado el problema implementando un sistema de RA para dispositivos móviles que se basa en dos contribuciones teóricas. La primera contribución es un método para realizar la calibración automática del dispositivo basándonos en parámetros fı́sicos de la cámara que son ampliamente conocidos y que podemos obtener de las APIs de la mayorı́a de los sistemas operativos móviles. Además proporcionamos una implementación C++ de este método para dispositivos Android. La segunda contribución es un algoritmo para detección de letreros, que emplea una técnica seguimiento en el espacio de color y un descarte de candidatos segundo sus propiedades geométricas para encontrar el letrero en un determinado fotograma. Mediante los experimentos realizados hemos estudiado el impacto de una calibración errónea y apoyándonos en este estudio hemos demostrado que el error cometido por nuestro método de calibración automática es mı́nimo. Por lo que esta calibración es idónea para ser empleada en sistemas de Realidad Aumentada. Respecto a los experimentos realizados para la detección de letreros, se ha probado que el algoritmo es invariante ante cambios en el tamaño y la rotación del letrero ası́ como robusto a variaciones de iluminación y perspectiva.. 5.1.. Trabajos Futuros. No obstante, queda todavı́a mucho por hacer, algunas tareas pendientes son: 1. La inclusión de técnicas de tracking que empleen la información de donde se encontraba el letrero en el instante anterior para hacer su búsqueda en el instante actual más eficiente. 2. El estudio de las distintas distorsiones ópticas, pues las cámaras de los dispositivos suelen 35.

(46) Capı́tulo 5 sufrir fuertes distorsiones que de ser consideradas en un modelo de Realidad Aumentada le darı́an todavı́a más realismo. 3. El uso de los sensores del dispositivo que proveen de información sobre la posición y orientación, ya que si estos datos se combinan darán lugar a una estimación más robusta de la posición de la cámara, haciendo la proyección más precisa. 4. El tratamiento de oclusiones ya que con el algoritmo de detección actual si alguna de las partes del letrero se encuentra ocluida por otro objeto su detección no será posible. 5. El empleo de patrones tridimensionales es también una vı́a de investigación muy atractiva que dotarı́a al sistema de muchas más aplicaciones. 6. La implementación en otros sistemas operativos como iOS o Windows phone que permitirı́a que este motor llegase a un público mucho mayor.. 36.

(47) Apéndice A. Consideraciones de Diseño y Arquitectura Software Para construir el sistema que permita dar soporte al trabajo de investigación desarrollado en el transcurso de este Trabajo Fin de Máster se ha desarrollado un sistema de Realidad Aumentada que se describirá en el presente apéndice.. A.1.. Selección de plataforma y lenguaje de implementación. Existen múltiples plataformas y librerı́as sobre las que se puede desarrollar un sistema de RA, las principales decisiones a tomar consisten en el motor gráfico a emplear y la librerı́a de visión a seleccionar. Respecto al lenguaje de implementación además del requisito de interoperabilidad tenemos también el de eficiencia, ya que estos algoritmos son tı́picamente muy costosos y las plataformas móviles muy limitadas en recursos. Teniendo en cuenta esta consideraciones se escoge C++ por su eficiencia y su amplio soporte multiplataforma, siendo el Native Development Kit (NDK) de Android el entorno donde se construirá la aplicación de prueba. Teniendo en cuenta esta decisión a continuación veremos los motores 3D que se han valorado. Por una lado se han valorado los motores libres y sin copyleft para gráficos simples y por otro lado a modo de posibilidades de futuro se han valorado motores privativos de gran potencia. No se contemplan conocidos motores libres como OpenSceneGraph ya que sus licencias con copyleft podrı́an ser un impedimento a la hora de su uso comercial.. Cocos2d: Cocos2d es un motor libre y de licencia MIT, construido en C++ y integrable con JavaScript, Java, Lua y Objetive-C. Es compatible con Windows, Linux, OS X, iOS, Android, BlackBerry y Tizen. LibGDX: LibGDX es otro motor libre de licencia Apache 2.0 construido como una API Java 37.

(48) Apéndice A para videojuegos por lo que es ampliamente empleado por la comunidad Android. Compatible con GNU/Linux, Windows, Mac OS X, iOS(en dudosas condiciones) y Android. OGRE3D: Ogre es otro motor libre de licencia MIT, escrito y pensado para C++ soporta compatibilidad con Linux, Windows, OS X, iOS y Android. Unity 3D: Unity es un conocido motor 3D privativo que destaca por su alta compatibilidad multi-plataforma, por su usabilidad y por su rendimiento. Funciona como un entorno de desarrollo propio basado en C#, lo dificulta su uso como librerı́a. Unreal: Unreal es uno de los mejores motores del mercado, escrito en C++ y pensado para ser usado desde C++ genera unos gráficos de una altı́sima calidad y dispone de un sin fin de herramientas de desarrollo gráfico. En nuestro caso, utilizaremos OpenGL en la versión de escritorio para correr los experimentos, ya que no necesitamos una gran potencia, tan solo corroborar el buen funcionamiento de la librerı́a nativa C++. Para la versión Android, hemos escogido libGDX al que nuestra aplicación Android pasará la posición de la cámara calculada por la librerı́a nativa. En cuanto a la librerı́a de visión por computador para hacer RA, existe una amplia gama como MXRToolkit, FLARToolKit, ALVAR, ArUCO... pero la mayorı́a de ellas no soportan el tracking basado en carácterı́sticas naturales y solo sirven para reconocer targets. La única librerı́a opensource que hemos encontrado capaz de tratar con estas marcas es ARToolKit, no obstante su código C está muy poco documentado y dado su tamaño hemos considerado que es más factible emplear la conocida librerı́a de visión OpenCV partiendo de algunos ejemplos como los que se ofrecen en el libro “Mastering OpenCV with Practical Computer Vision Projects” [1].. A.2.. Arquitectura Software Global. La arquitectura global de la aplicación se plantea como una librerı́a C++ compilada estáticamente de modo que englobe aquellos módulos de OpenCV que sean necesarios. Esta librerı́a será llamada por un programa C++ en caso de que se esté ejecutando en un PC, o bien a través de la parte nativa de una aplicación android en caso de que se utilice este sistema operativo. La librerı́a recibirá en un primer instante como entrada los datos fı́sicos del dispositivo con los que calculará y devolverá la matriz de proyección de OpenGL. Durante la ejecución, la librerı́a recibirá los fotogramas provenientes de la cámara y devolverá la matriz ModelView de OpenGL con los parámetros extrı́nsecos que indican la posición de la cámara si se usa OpenGL, o si se emplea un motor como libGDX la posición de la cámara y su rotación en formato de su vector dirección y vector superior (up).. A.2.1.. Organización del proyecto. El proyecto se estructura desde un directorio raı́z que tiene la estructura de la imagen A.1. En el directorio principal podemos encontrar lo relativo a la versión de escritorio, esta versión de 38.

(49) Consideraciones de Diseño y Arquitectura Software escritorio es un proyecto Cmake, que generará los ejecutables tanto para la aplicación de prueba como para cada uno de los tests y experimentos.. Figura A.1: Estructura del proyecto. A.2.2.. Aplicación Android. Debido a las limitaciones de tiempo y de recursos en este TFM, sólo se contemplará como plataforma móvil Android. La arquitectura de la aplicación en Android, que puede verse en la figura A.2, será la de una aplicación java & C++. En la parte java se importará la librerı́a de OpenCV Android para usarla en el manejo de la cámara, la gestión de threads y la visualización de fotogramas. En java también se importará el motor gráfico liGDX para el renderizado de la capa virtual. Nuestra Activity principal estará compuesta por dos capas principales, la primera de ellas será una clase que extiende de CameraGLSurfaceView y que enviará mediante OpenGL ES 2.0 la imagen proveniente de la cámara directamente a la GPU del dispositivo si es que dispone de ella. Encima de esta capa que muestra las imágenes de la cámara se situará la capa virtual que estará formada por una clase que extiende de com.badlogic.gdx.ApplicationAdapter y que contiene toda la lógica necesaria para mostrar nuestros modelos 3D. El flujo de trabajo en la aplicación comienza cuando un nuevo fotograma llega de la cámara, en ese momento OpenCV Android llama a nuestra aplicación a través de un callback pasándole la dirección de memoria de esta imagen. A continuación pasaremos esta imagen a la librerı́a de visión nativa a través de JNI. Nuestra aplicación nativa escrita en C++ para maximizar la eficiencia aplicará el algoritmo para detección de letreros visto en el capı́tulo 4 y en base a ese patrón calculará la posición del dispositivo en la escena. La posición calculada es devuelta a la máquina virtual modificando los miembros de la clase 39.

Referencias

Documento similar

Fuente de emisión secundaria que afecta a la estación: Combustión en sector residencial y comercial Distancia a la primera vía de tráfico: 3 metros (15 m de ancho)..

De la Salud de la Universidad de Málaga y comienza el primer curso de Grado en Podología, el cual ofrece una formación generalista y profesionalizadora que contempla

d) que haya «identidad de órgano» (con identidad de Sala y Sección); e) que haya alteridad, es decir, que las sentencias aportadas sean de persona distinta a la recurrente, e) que

De hecho, este sometimiento periódico al voto, esta decisión periódica de los electores sobre la gestión ha sido uno de los componentes teóricos más interesantes de la

La campaña ha consistido en la revisión del etiquetado e instrucciones de uso de todos los ter- mómetros digitales comunicados, así como de la documentación técnica adicional de

dente: algunas decían que doña Leonor, &#34;con muy grand rescelo e miedo que avía del rey don Pedro que nueva- mente regnaba, e de la reyna doña María, su madre del dicho rey,

Y tendiendo ellos la vista vieron cuanto en el mundo había y dieron las gracias al Criador diciendo: Repetidas gracias os damos porque nos habéis criado hombres, nos

Entre nosotros anda un escritor de cosas de filología, paisano de Costa, que no deja de tener ingenio y garbo; pero cuyas obras tienen de todo menos de ciencia, y aun