Análisis de imágenes de profundidad para aplicaciones

Anuncio
Análisis de imágenes de profundidad para aplicaciones
de realidad aumentada mediante el uso de la Kinect
Visión Artificial
Escuela Técnica Superior de Ingenieros Industriales
Universitat Politècnica de València
Primer cuatrimestre curso 2013-2014
Por Alfonso Arbona Gimeno
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
Índice
1.- Introducción y objetivos..................................................................................................................2
2.- Obtención de imágenes...................................................................................................................2
2.1.- Hardware.................................................................................................................................2
2.2.- Software...................................................................................................................................2
2.3.- Preprocesado............................................................................................................................4
2.3.1.- Ruido................................................................................................................................4
2.3.2.- Detección de bordes.........................................................................................................4
2.4.- Imágenes de color....................................................................................................................8
3.- Análisis............................................................................................................................................9
3.1.- Datos de profundidad..............................................................................................................9
3.2.- Transformada de Hough..........................................................................................................9
3.2.1.- Primer método...............................................................................................................10
3.2.2.- Segundo método.............................................................................................................11
3.2.3.- Procesado de la imagen de Hough.................................................................................11
3.3.- Obtención de coordenadas del mundo real............................................................................15
3.3.1.- Cálculo de distancias.....................................................................................................15
3.3.2.- Puntos de interés............................................................................................................16
3.3.3.- Rectas u,v a x,y,z...........................................................................................................16
4.- Renderizado...................................................................................................................................19
5.- Optimizaciones aplicadas..............................................................................................................20
6.- Conclusiones.................................................................................................................................21
7.- Bibliografía y enlaces de interés...................................................................................................21
1
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
1.- Introducción y objetivos
El análisis de imágenes mediante ordenadores ha sido un tema muy estudiado y analizado en los
últimos años. El objetivo último siempre ha sido el obtener información del mundo físico mediante
el análisis de imágenes capturadas por una cámara.
Dentro del gran rango de aplicaciones de estas técnicas, las que aquí nos conciernen son las que
versan sobre la obtención de la posición y orientación de elementos concretos de una cierta escena.
Esta información nos permite hacer una reconstrucción interna de la escena real, que podemos
utilizar por ejemplo, para dibujar sobre la imagen obtenida por la cámara objetos y elementos que
no existen en la realidad, aumentando así la escena real original.
El mayor problema es la obtención de dichos parámetros de la escena para poder establecer dónde y
cómo dibujar los objetos, y hacer esto en tiempo real.
Por su simplicidad, el método más utilizado ha sido la introducción en la escena de un elemento
cuyas propiedades son conocidas a priori, pero obviamente tiene la desventaja de que es necesario
añadir y detectar un objeto concreto a la escena.
La solución aquí presentada consiste en el uso de una cámara no-convencional que ofrece datos de
profundidades en vez de color. De esta forma, es más sencillo detectar objetos físicos y su posterior
análisis de la escena ya que tenemos información sobre el mundo 3D captado, y no solo sobre una
proyección bidimensional.
Como ejemplo de aplicación se adjunta un código en C capaz de detectar esquinas y sobre ellas
dibujar un pequeño robot. Concretamente está diseñado para detectar las lámparas del techo del
laboratorio en el que ha sido desarrollado.
2.- Obtención de imágenes
2.1.- Hardware
Una cámara fácil de conseguir y de reducido coste que permite la obtención de profundidades de la
escena es la Kinect producida por Microsoft. Además, existen librerías de fácil uso opensource
disponibles en internet para la creación de programas en C/C++ multiplataforma.
2.2.- Software
La librería opensource utilizada es libfreenect, cuyo código puede ser descargado de:
https://github.com/OpenKinect/libfreenect
O de la página web principal del proyecto:
http://openkinect.org
Su uso es relativamente sencillo, aunque la documentación es bastante pobre y hay que basarse en
pruebas y análisis de los datos obtenidos para entender qué obtenemos con la cámara, y con ello y
2
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
los ejemplos existentes, adaptarlo todo a nuestro propio proyecto.
El primer problema ha sido entender qué datos se guardan en la matriz de profundidades. Para ello
ha sido necesario guardar una imagen en un archivo y analizarla mediante un editor hexadecimal.
Imagen 1
De esta forma se observa que los valores obtenidos son de 2bytes por “pixel”, y en la
documentación se indica el rango de valores que la cámara puede enviar.
Cabe destacar que estos valores no son directamente la profundidad de la escena, si no que para
obtener la distancia en milímetros es
necesario aplicar la siguiente
ecuación:
1000
d mm =
−0.00307⋅d raw +3.33
Sabiendo esto podemos representar
ya los valores obtenidos por la
cámara en la pantalla. Lo más lógico
sería utilizar el valor en milímetros y
representarlo por pantalla, pero
puesto que la ecuación que
transforma el valor obtenido en
distancia real es costosa de calcular
cada frame para cada píxel, es mejor
representar únicamente el valor
obtenido sin ningún tipo de
preprocesado, como se observa en la Imagen 2: Obtención de datos en bruto
imagen de la derecha.
3
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
2.3.- Preprocesado
2.3.1.- Ruido
Como queda evidente en la imagen 2, hay una serie de puntos negros que aparecen. Estas zonas
indican que la cámara no es capaz de obtener información sobre la profundidad de la escena, y por
tanto están representados ahí de color negro.
Esta falta de información puede ser debida a muchas causas, las más comunes son una superficie
reflectante (el suelo a partir de cierto ángulo), superficies que no reflejen los infrarrojos (las
pantallas de ordenador), superficies pequeñas o bordes en las que el mallado de infrarrojos no sea
detectado por la cámara (las patas de las mesas), sombras producidas sobre el haz de luz infrarroja
de la cámara, o elementos que generen su propia luz infrarroja (lámparas, luz solar, …).
Todos estos elementos nos pueden dar problemas en un futuro, especialmente el ruido que cambia
con el tiempo que se observa en la
animación temblor_sin.gif.
La primera solución adoptada es la de
mostrar
una
imagen
filtrada
temporalmente. Para ello en vez de
mostrar la última imagen obtenida, lo
que hacemos es rellenar la zona con
datos que falten con una imagen
obtenida anteriormente.
El resultado se puede ver en el archivo
temblor_filtrado.gif, pero como se
aprecia el ruido no desaparece lo
suficiente como para que sea útil, y se
producen distorsiones en la imagen que
tampoco nos interesan.
Por ello optamos finalmente por no
realizar este filtrado.
Imagen 3: Filtro temporal
2.3.2.- Detección de bordes
Puesto que nuestro objetivo es encontrar las esquinas de objetos, deberemos antes realizar un
preprocesado de la imagen con un filtro paso alto para detectar los cambios bruscos. Normalmente,
en una imagen tradicional esto quiere decir detectar cambios de color, pero puesto que aquí estamos
trabajando con profundidades, lo que obtenemos son puntos en los que la distancia de la cámara al
objeto varía de forma brusca. Es decir, obtenemos puntos en los que el objeto termina.
Tras probar varios tipos de filtros, el que mejores resultados ha generado es el filtro basado en el
laplaciano:
0 -1 0
-1 4 -1
0 -1 0
4
Alfonso Arbona Gimeno
Imagen 4: Original
Imagen 5: Tras detección de bordes
5
Visión Artificial - ETSII - UPV
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
Por otra parte, antes de utilizar el laplaciano como filtro definitivo, también probamos varios filtros.
Entre ellos el de Sobel daba buenos resultados, pero requería dos pasadas por la imagen, y
multiplicaba el tiempo de procesado significativamente.
Además, el resultado de Sobel debe de calcularse mediante la distancia geométrica de sus dos
componentes direccionales, y por tanto introducimos cálculos muy costosos en la CPU por cada
píxel de la imagen, y en definitiva, no haciendo rentable el uso de dicho filtro.
Otro elemento probado era el realizar pasadas de suavizado (filtro paso bajo) antes y tras el filtro
paso alto.
El filtrado inicial es relativamente útil en entornos con mucho ruido, y por tanto se ha dejado en el
código como una opción cambiable en tiempo real (variable do_presmooth) aunque no tiene
asignada ninguna tecla actualmente ya que su uso no era especialmente útil en las situaciones en las
que ha sido ensayado el programa.
Por otra parte, el filtrado tras la detección de bordes tiene sentido únicamente en situaciones en las
que el filtro paso alto deja líneas de borde no continuas.
Inicialmente el código realizaba dicho post-procesado a la imagen, pero tras comenzar con el
análisis de rectas se hizo completamente innecesario, y por tanto borrado del código ya que
introducía una pasada más y eliminaba la binariedad de la imagen obtenida por el filtro paso alto
aumentando el tiempo de cómputo significativamente.
En las imágenes 6 y 7 se puede apreciar como el filtrado paso bajo permitía detectar rectas con una
mayor resolución, pero en nuestro caso no era realmente útil ya que buscábamos detectar
únicamente cambios fuertes en la profundidad de la imagen y no la cantidad de detalles.
Cabe destacar que el tiempo de procesado es realmente muy elevado en la primera imagen en
comparación con la segunda.
6
Alfonso Arbona Gimeno
Imagen 6: Filtrado paso bajo antes y después del paso alto
Imagen 7: Sin filtro paso bajo
7
Visión Artificial - ETSII - UPV
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
2.4.- Imágenes de color
La Kinect dispone de una cámara tradicional a color
junto con la cámara de profundidad, y será la que
utilizaremos para ser superpuesta con los elementos
ficticios que queramos una vez hayamos procesado
la imagen de profundidad.
Uno de los mayores problemas que hay cuando se
utilizan varias cámaras es que no están en la misma
posición, y que los datos obtenidos no se
corresponden a nivel de píxel. Es decir, que una
coordenada en píxeles (u1, v1) de la cámara de color
no coincide con el mismo punto físico para la
cámara de profundidad.
Tampoco coinciden los tamaños de las matrices
obtenidos por ambas cámaras. La de color es de Imagen 8: Cámara de color
640x480 mientras que la de profundidad es de
640x488.
Estos efectos se observan en las imágenes 8 y 9,
ambas tomadas sin mover la cámara de posición. Es
especialmente visible en la parte superior de la puerta
como ambas imágenes no se corresponden
directamente tanto en posición como en escala.
La librería se puede configurar para que realice de
manera transparente el escalado y desplazado de la
imagen de profundidad de tal forma que se ajuste
pixel a pixel con la imagen a color (imagen 101).
Obviamente se trata de una transformación lenta y Imagen 9: Comparación color - profundidad
por tanto la omitimos en este punto del programa,
pero para ser coherentes con la imagen a color
deberíamos de realizar la transformación una vez
hayamos analizado la imagen y tan solo nos interesen
unos poco puntos.
Por simplicidad y porque el cambio de posición es
pequeño en los puntos centrales de la imagen, en el
código no está aplicada esta transformación a la hora
de situar el modelo 3D sobre la cámara de color.
Imagen 10: Mapeado automático por la librería
1
8
En este caso los puntos sin información de profundidad se representan con una cuadrícula blanca y negra. Por otra
parte, la librería realiza también la transformación a milímetros y por ello se notan mayores cambios en el color.
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
3.- Análisis
3.1.- Datos de profundidad
Como hemos anticipado en el punto 2.2, los datos obtenidos no son directamente profundidades, si
no que hay que aplicar una ecuación de cómputo relativamente costoso para obtener la distancia en
milímetros reales.
1000
Recordemos que la relación es: d mm =
−0.00307⋅d raw +3.33
Si calculamos la diferencia en unidades obtenidas para un incremento de 1cm a una distancia de 1m
y a una distancia de 3m obtenemos:
d mm =1000 ⇒d raw =758.9577
d mm =1010 ⇒d raw =762.1827
Δ d raw =3.2250
d mm=3000 ⇒ d raw=976.1129
d mm =3010 ⇒d raw =976.4736
Δ d raw=0.3607
Como observamos, el sistema es muy poco lineal, y por tanto, el filtrado debería de haberse
realizado tras haber calculado la distancia real (aunque fuese de forma aproximada). Pero puesto
que como dicho cómputo es costoso y que los elementos que queremos analizar están a una
distancia más o menos constante siempre, podemos realizar la aproximación y filtrar respecto a los
datos raw obtenidos por la cámara.
Obviamente, a la hora de calcular el valor final de profundidad nos vemos obligados a recurrir a
esta expresión, pero intentaremos evitarlo en la medida de lo posible para reducir el tiempo de
cómputo.
3.2.- Transformada de Hough
Una vez obtenida una matriz binaria de los bordes de los objetos pasamos a buscar las rectas que los
definen.
Este paso es importante ya que nos permite diferenciar puntos no interesantes en la imagen
preprocesada de los puntos que definen aristas y bordes físicos de los objetos que componen la
escena.
El método es sencillo. Para cada punto se guarda en una matriz los elementos que definen todas las
posibles rectas que lo contienen. Obviamente, puesto que hay infinitas rectas que pasan por un
plano se tiende a generar un número concreto de rectas cuyo ángulo varía de forma constante.
9
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
3.2.1.- Primer método
Inicialmente se optó por utilizar la ecuación de la recta y=m⋅x +n , utilizando como coordenadas
de la matriz las variables m y n. El problema surge cuando las rectas se acercan a la vertical, ya que
m tiende a valer ±∞ , valor que no puede ser una coordenada de una matriz.
La solución a este problema fue diferenciar rectas “normales” (cuya pendiente es un valor real), de
las rectas con pendiente infinita.
De esta forma la matriz quedaba de la siguiente manera:
Imagen 11: Matriz de Hough para m y n
El segundo problema que se observó es evidente: las dimensiones de la matriz.
La memoria RAM no es infinita, y por tanto hace falta establecer unos límites en cómo de grande es
la matriz generada por Hough. En este caso concreto forzamos la n a estar en el rango
[-width*height, width*height], es decir 2⋅640⋅480=614400 posibles n.
Por su parte, la m no puede almacenarse directamente ya que los valores se dispersan muy
rápidamente a infinito. Para solucionarlo se creó una matriz que trasladaba un valor concreto de
coordenada de la matriz en una pendiente. Si por ejemplo quisiésemos representar rectas cada grado
necesitaríamos 180 posiciones (de -89 a +89 y la vertical). La matriz de transformación nos daría la
m en función de dicho ángulo.
Pero siguen habiendo problemas. La ordenada en el origen puede salirse del rango, la pendiente
puede dar errores de cálculo en valores cercanos a la vertical, la recta vertical está separada del resto
de rectas y la necesidad de memoria es elevada.
Por tanto se optó por cambiar por completo el sistema de Hough por uno mucho más lógico:
guardar como coordenadas el ángulo y la distancia de la recta al origen.
10
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
3.2.2.- Segundo método
Otra forma de representar una recta es como d =x⋅cos (θ)+ y⋅sin (θ) , así pues, dado un punto
(x,y) concreto podemos ir haciendo pasar todas los ángulos que queramos y marcando el punto (d,θ)
en la matriz. Siguiendo el ejemplo
anterior: necesitaríamos igual que antes
180 posiciones para un eje, pero por
otra
parte
tan
solo
2
2
√ 640 + 480 =800 posiciones para el
otro eje ya que es la máxima distancia
que hay al origen. Esto es una
reducción de más del 780000 % en
memoria, a parte de ser más rápido de
calcular si se realizan ciertos trucos que
se explicarán más adelante.
El objetivo de estos cálculos es obtener
una nube de puntos en dicha matriz que
puede verse como una nueva imagen a
analizar (imagen 12).
Imagen 12: Zoom en una nube de puntos generados por
Hough tras un primer filtrado.
En este caso el eje vertical representa el
ángulo de las rectas y el eje horizontal
la distancia de dicha recta al origen de
coordenadas (situado en la esquina
inferior izquierda de la imagen de
profundidades).
3.2.3.- Procesado de la imagen de Hough
La imagen generada por Hough debe de ser también analizada para extraer información importante
sobre las rectas que realmente forman la imagen.
Se trata de una imagen no binaria cuyo valor representa cuántos puntos comparten una recta
concreta. Puesto que tan solo nos importan las rectas que contienen muchos puntos de nuestra
imagen original, un primer filtro podría ser por valor mínimo, eliminando así todas las rectas que
solo han sido generadas por unos pocos puntos (Imagen 12).
El problema, como se puede apreciar en la imagen, es que ahora tenemos gran cantidad de puntos
inconexos en esta imagen, y por tanto al separarlos en objetos de interés, lo que debería de ser una
nube termina siendo un conjunto de pequeños elementos.
Para solucionarlo, inicialmente optamos por realizar dos inflados seguido de dos erosiones para
intentar generar nubes más grandes, aunque el resultado no es del todo el esperado, y cada inflado o
erosión implica una pasada por la imagen ralentizando por tanto el proceso. Como observamos en la
imagen 13, el resultado es muy similar a la imagen 12 de la que parte, aunque por otra parte sí que
es cierto que muchas de las islas que originalmente estaban sueltas han terminado unidas a la zona
principal.
11
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
Por otra parte, hay gran cantidad de puntos que se han vuelto a separar en elementos más pequeños
tras las erosiones.
Por todo ello terminamos ejecutando
únicamente tres inflados y ninguna
erosión. Esto provoca que se generen
rectas que realmente no existen, pero
a efectos prácticos no tienen
relevancia ya que al realizar los
inflados los valores introducidos son
equivalentes a que un único punto
generase
esa
recta,
que
en
comparación con la cantidad de
puntos que generan el resto de puntos
es despreciable. Es decir, el valor
guardado en la matriz es de 1,
mientras que el resto tienen que
superar
un
cierto
umbral
relativamente elevado.
El siguiente paso a aplicar es la
separación en distintos objetos de
interés.
Imagen 13: Hough tras 2 inflados y 2 erosiones
Para ello nos valemos de la técnica
flood-fill, similar a la usada en
programas de dibujo estilo Paint para rellenar bloques continuos, pero modificada para que funcione
en nuestra nube de puntos.
El algoritmo está bien documentado y no vamos a entrar en detalle en su funcionamiento interno,
pero cabe destacar que ha sido modificado para optimizar su ejecución en esta situación concreta y
para obtener también distintas características de los objetos en cuestión.
En resumen, dada la imagen 13, al final obtenemos una lista de objetos de interés con sus
propiedades básicas: masa, área y coordenadas del centro de masas.
Al contrario que en los programas de dibujo, en nuestro caso únicamente necesitamos estas
propiedades y no la imagen “pintada”, pero para poder ver cómo se está comportando el sistema
también está implementado en el código la parte que dibuja con colores los distintos objetos como
se puede ver en las imágenes 14 y 15.
12
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
Imagen 14: Objetos de interés sin inflado
Imagen 15: Objetos de interés tras 3 inflados
Como se puede observar el área ha variado mucho, pero internamente la masa de los objetos de
interés sigue constante y el centro de masas sigue bien situado ya que la masa de los elementos
añadidos por los inflados es muy pequeña.
Una vez tenemos esta lista de elementos hay que volver a filtrarlos, ahora utilizando como variables
datos mucho más elaborados como la masa y la densidad, de tal forma que solo nos queden los
elementos con una masa mínima y una densidad relativamente elevada.
Estas rectas son las únicas que utilizaremos para calcular las propiedades de la escena
tridimensional que estamos analizando.
Podemos antes representarlas para poder ver que todo es correcto hasta ahora, como se muestra en
la imagen 16.
13
Alfonso Arbona Gimeno
Imagen 16: Rectas obtenidas sobre imagen de bordes
14
Visión Artificial - ETSII - UPV
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
3.3.- Obtención de coordenadas del mundo real
3.3.1.- Cálculo de distancias
El primer paso para pasar de coordenadas locales de la imagen (u,v) a coordenadas reales (x,y,z) es
obtener un modelo de la cámara que nos permita aplicar operaciones matemáticas trigonométricas
necesarias.
Recordemos que para cada coordenada (u,v) podemos sacar su distancia a la cámara d. Esta
distancia nos ahorra muchísimo trabajo que de otra forma debería de hacerse mediante elementos
cuya medida sea conocida para poder realizar la homografía y así saber la orientación y posición del
objeto.
Para calcular de forma aproximada el tamaño de un objeto primero necesitamos saber cómo
proyecta la cámara los objetos.
En una cámara convencional la
proyección se realiza mediante
esta operación:
[][
][ ]
αu 0 u 0 x
u
=
v
0 αv v 0 ⋅ y
w
0 0 1 z
Aunque en nuestro caso no
perdemos una dimensión ya que
guardamos la distancia, y por
tanto podríamos hacer una
reconstrucción 3D del entorno
sin necesidad de elementos
externos.
Mediante
varios
ensayos
(imagen 17) con objetos de
medidas conocidas a distintas
distancias calculamos que:
αu≈α v ≈574.9822
Imagen 17: Obtención de los parámetros de la cámara
Y con la siguiente ecuación podemos calcular aproximadamente la distancia entre dos puntos en un
plano paralelo a la cámara:
d mm =
d px⋅profundidad
574.9822
Esto es especialmente útil para sacar rápidamente (el coste computacional es una simple
multiplicación) la distancia entre dos puntos a una profundidad similar, aunque finalmente no ha
sido necesario utilizarlo en el programa principal.
15
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
3.3.2.- Puntos de interés
Con toda esta información únicamente falta encontrar el punto que queremos analizar y del que
queremos sacar las coordenadas en las que situaremos el objeto virtual.
Como ejemplo, el código busca la intersección de dos rectas obtenidas por el método de Hough,
concretamente de las lámparas del laboratorio. De todas las rectas posibles busca dos cuyo ángulo
no sea similar y el punto de intersección esté cerca del centro de la cámara. Esto es especialmente
importante ya que en la cámara podrían producirse muchas intersecciones de rectas (por ejemplo si
se ven varias lámparas), y no queremos que se intente dibujar el objeto en un punto casi fuera de la
pantalla si tenemos otra intersección mejor en el centro de la pantalla.
El código comprueba una a una todas las rectas que han pasado los filtros anteriores y calcula las
coordenadas (u,v) del punto de intersección. Y de todos estos puntos retorna las rectas que producen
aquella intersección más cercana al centro de la pantalla.
Con más tiempo se podrían haber desarrollado algoritmos más complejos para detectar objetos
concretos, pero estos algoritmos dejan de tratar ya temas de visión artificial para pasar más a
sistemas de inteligencia artificial, ya que la obtención de información del entorno ya ha sido
realizado y únicamente queda la selección de puntos de interés.
3.3.3.- Rectas u,v a x,y,z
Una vez hemos decidido qué dos rectas
analizar pasamos al cálculo del plano que
las contiene.
Si la selección de las rectas es correcto,
ambas comparten un punto concreto (la
intersección) y por tanto definen un plano
en el mundo real. El problema es que
únicamente tenemos su ángulo y distancia
en coordenadas (u,v) de la pantalla, y el
paso a coordenadas 3D no es trivial.
El algoritmo desarrollado realiza los
siguientes pasos:
Para cada recta, empezando en el punto de
intersección nos desplazamos una cierta
distancia por ella en una dirección.
Si la información de profundidad está
disponible en dicho punto, la guardamos y
terminamos de buscar un punto.
Si no está disponible, volvemos a
desplazarnos por la recta en la misma
dirección hasta que lo encontremos o nos
salgamos fuera de la pantalla (en cuyo caso
retornamos error).
Realizamos la misma operación con un
segundo punto siguiendo la otra dirección
Imagen 18: Cambio de coordenadas
de la recta.
16
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
De esta forma tendremos por cada recta 3 puntos distintos con sus coordenadas (u,v) y la distancia
real a la cámara. Uno de esos puntos (el central) será compartido entre las rectas por ser el punto de
intersección.
De la documentación sacamos esta información importante sobre la cámara de profundidades:
Distancia focal: 580px.
Field of View: 57.8º.
Y analizando la imagen 18 para el eje x, y aplicándolo de forma similar para el eje y obtenemos las
siguientes expresiones:
α=asin
(
u px −640/ 2
fd
Siendo d la profundidad en milímetros obtenida por la cámara, fd la
distancia focal y upx y vpx las coordenadas en píxeles.
)
d
640 Como podemos observar, no es necesario realizar operaciones
x=d⋅sin (α)= ⋅ u px −
costosas como senos y cosenos. De hecho, se trata únicamente de
fd
2
un escalado lineal tras cambiar el origen de coordenadas de la
d
480
cámara a uno centrado en el centro de la pantalla en vez de en la
y=d⋅sin (ρ)= ⋅ v px−
fd
2
esquina inferior izquierda.
z≈d
La única aproximación relativamente incorrecta es asumir que z es directamente la profundidad,
pero recordemos que estamos tratando puntos cercanos al centro de la pantalla, y que por tanto los
ángulos son muy pequeños, y con ello la coordenada z es prácticamente la profundidad obtenida por
la cámara.
(
(
)
)
Con estas ecuaciones podemos pasar los puntos (u,v) obtenidos anteriormente con de las rectas, a
coordenadas (x,yz) situadas en el espacio real de la escena.
Utilizando 3 de esos puntos podemos calcular el plano que contiene a ambas rectas, concretamente
nos interesa el vector normal al plano:
⃗
n =( p⃗2− p⃗1 )∧( p⃗3 − p⃗1)
Con el punto de intersección y el vector normal tenemos ya todo lo necesario para dibujar nuestro
modelo.
17
Alfonso Arbona Gimeno
18
Visión Artificial - ETSII - UPV
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
4.- Renderizado
Puesto que el software ha sido desarrollado en un entorno linux, el software utilizado es opensource
libre y gratuíto, pero además las librerías utilizadas son multi-plataforma para permitir su uso en
otros entornos.
Para crear la ventana, dibujar sobre texturas y controlar las acciones y eventos del usuario y el
sistema operativo se ha utilizado la librería SDL sin ningún extra.
Inicialmente el proyecto se creó utilizando SDL también para el dibujado en la pantalla, pero al
llegar al punto en el que se necesitaba dibujar ya el modelo 3D sobre la imagen obtenida por la
cámara se requería utilizar OpenGL, así que se tuvo que migrar la totalidad del código de dibujo a
OpenGL, cambiando gran cantidad de elementos (entre ellos texto informativo y elementos
similares), que no fueron reintroducidos en el programa ya que ya no eran necesarios en ese punto
tan avanzado del trabajo.
Las funciones que cargan el modelo 3ds fueron desarrolladas por mí con anterioridad para un
proyecto personal y están disponibles bajo la licencia MIT. Su único objetivo es cargar un archivo
3ds y transformarlo en listas de vectores, normales, caras y texturas que OpenGL es capaz de
dibujar en la pantalla.
Para cargar texturas en formatos más complejos que un simple bmp se ha utilizado la librería
DevIL, y se utiliza tanto en el código para cargar los modelos como para cargar alguna textura extra
que se utiliza en el programa.
El resumen de la línea de dibujo es el siguiente:
Se borra la pantalla, se crean las superficies SDL con los datos obtenidos de la cámara y más tarde
se actualizan las texturas OpenGL con los datos de dichas superficies SDL.
En función del modo de dibujo se representa en la pantalla una de las texturas (color, profundidad o
Hough), y se indica que no se desea controlar la coordenada z de la textura, de tal forma que para la
GPU la textura estará siempre detrás de todo.
Una vez dibujada esta textura se dibuja el modelo en las coordenadas y orientación especificadas.
Cabe destacar que estas coordenadas van dadas en milímetros reales, y alineadas con las
coordenadas reales que se detectan con la cámara gracias a los cálculos de los puntos anteriores, y
por tanto no hay que hacer ningún cálculo en este punto.
19
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
5.- Optimizaciones aplicadas
Puesto que este proyecto busca realizar los cálculos en tiempo real, se ha optado por realizar
muchas aproximaciones y optimizaciones para mejorar la velocidad, en gran parte a costa de
aumentar la cantidad de memoria requerida.
Esta es una pequeña lista de las optimizaciones más importantes del código:
20
•
Multithreading en la captura de de la cámara. Puesto que la cámara va a su propia frecuencia
de muestreo, puede que se de el caso en el que recibamos demasiadas imágenes o muy
pocas, o que la cámara bloquee la ejecución del código, por tanto, todo el código de control
y de obtención de imágenes se realiza en un hilo independiente sincronizado con el hilo
principal de ejecución.
•
Caché de valores y cálculos. Muchos de los cálculos, especialmente los relacionados con
funciones trigonométricas utilizadas en la matriz de Hough son extremadamente lentos
computacionalmente. Por ello, para agilizar el proceso, al arrancar el programa se calculan
todos los senos y cosenos posibles y se guardan en una matriz. Esto es posible porque
conocemos a priori todos los ángulos posibles que van a tener las rectas en hough (por
ejemplo, todos los ángulos con 1º de diferencia entre 0º y 179º). En otros trozos de código
que se requieren varias veces el seno y el coseno de otros ángulos se precalculan una vez y
se guardan para futuros usos (por ejemplo en el código de desplazamiento por las rectas para
encontrar puntos con los que generar el plano)
•
Uso de operaciones de copia rápidos para la CPU. Por ejemplo memcpy o memset para las
matrices e imágenes. Estas funciones están optimizadas a nivel de ensamblador para ser
realmente rápidas.
•
Aceleración por GPU para dibujar. Al utilizar la librería OpenGL estamos aprovechando el
poder del procesador gráfico paralelamente con la CPU para todos los cálculos de dibujo de
modelos y texturas en el mundo virtual tridimensional que se proyecta sobre la pantalla para
dar la sensación de que los modelos son 3D.
•
Evitar pasadas por las imágenes innecesarias. Como se ha visto en el punto de preprocesado,
se ha evitado a toda costa realizar pasadas por las matrices de manera innecesaria a costa de
un filtrado menos correcto siempre que ha sido posible y los resultados eran aceptables.
•
Uso de filtros rápidos antes que los filtros más costosos. De esta forma quitamos primero los
elementos sencillos (por ejemplo, filtrado por nivel mínimo antes de filtrar por área o masa
del objeto de interés) dejando menos elementos a analizar con los algoritmos más costosos.
•
Programación de bajo nivel en C. Al tratarse de un lenguaje tan lineal y de bajo nivel, el
código compilado es mucho menor que si se usasen otros lenguajes como java o Matlab.
•
Utilización inteligente de las variables. No se utilizan por ejemplo variables de coma
flotante cuando pueden ser utilizadas las enteras. Por ejemplo, al calcular los senos y
cosenos de los ángulos, en vez de guardar el valor en coma flotante (entre 0.0 y 1.0) se
guarda el valor multiplicado por 1000 y se elimina la parte decimal. A la hora de operar se
divide al final por 1000 como un valor entero siempre realizando las operaciones muy
rápidamente. El único momento en el que se realizan de manera lenta es al crear la lista de
senos y cosenos precalculados, pero esto se realiza únicamente al arrancar el programa.
Alfonso Arbona Gimeno
Visión Artificial - ETSII - UPV
6.- Conclusiones
Hemos visto una forma de aplicar los algoritmos que comúnmente se utilizan en imágenes de
colores en los mapas de profundidad capturados por una cámara Kinect.
Dichos algoritmos han sido cambiados y ajustados para esta nueva situación, y utilizados para
obtener varios puntos de interés sobre los que hemos realizado una des-proyección para calcular las
coordenadas en la escena real en las que dichos puntos estaban situados.
Con esta información hemos sido capaz de introducir un elemento de manera virtual que no existía
a priori en la escena real, de tal forma que parece que el objeto estaba realmente situado allí.
Todo esto, además, se hace sin tener que introducir elementos conocidos en la escena como
marcadores, y se ejecuta en tiempo real a una velocidad más que aceptable en un ordenador de
gama media con poco más de 3600 líneas de código y el uso únicamente de software libre
multiplataforma.
7.- Bibliografía y enlaces de interés
http://openkinect.org/wiki/Main_Page – Código fuente y página web principal de la librería
libfreenect, contiene información interesante sobre el uso de la librería.
http://openkinect.org/wiki/Getting_Started#Ubuntu.2FDebian – Instalación de la librería en
entornos linux y solución al problema de permisos de root.
https://groups.google.com/forum/#!topic/openkinect/gTyZjqqGay4 – Discusión en la lista de correo
oficial de libfreenect sobre el formato de los datos capturados por la cámara.
http://wiki.ros.org/kinect_node – Información por parte de la librería ROS (para proyectos de
robótica) sobre el uso de la kinect mediante libfreenect. Contiene mucha información importante
sobre el funcionamiento interno de la cámara y cálculos útiles.
http://docs.opencv.org/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html?
highlight=findhomography – Cálculos matemáticos para la reconstrucción del espacio 3D y la
calibración de la cámara (matrices de proyección y otras propiedades interesantes).
https://en.wikipedia.org/wiki/Flood_fill – Pseudocódigo utilizado para la detección de elementos
independientes en las nubes generadas por la transformada de Hough.
http://lists.libsdl.org/pipermail/sdl-libsdl.org/1999-November/003589.html – Código utilizado como
base para dibujar rectas dados 2 puntos en una superficie SDL.
https://en.wikipedia.org/wiki/Hough_transform – Información y pseudocódigo de la transformada
de Hough.
http://www.gamedev.net/topic/184477-sdl_surface-to-opengl-texture/ - Código de ejemplo utilizado
como base para pasar de una superficie SDL a una de OpenGL. Aunque no funcionaba
correctamente, gran parte del código de esta función ha sido extraído de ese hilo.
http://www.nakerium.com/wiki/tiki-read_article.php?articleId=1 – Artículo escrito por mí en el
2010 del que he sacado gran parte del código base del proyecto.
https://libsdl.org/ y https://www.opengl.org/ - Descargas y documentación de ambas librerías.
21
Descargar