{"id":167,"date":"2016-04-16T13:29:20","date_gmt":"2016-04-16T11:29:20","guid":{"rendered":"http:\/\/www.rtbasics.com\/WP_2\/?p=167"},"modified":"2016-05-05T12:26:57","modified_gmt":"2016-05-05T10:26:57","slug":"sensor-de-profundidad-adquisicion","status":"publish","type":"post","link":"https:\/\/www.rtbasics.com\/WP_2\/archives\/167","title":{"rendered":"Sensor de profundidad. Adquisici\u00f3n."},"content":{"rendered":"<body><p><\/p>Estaba yo pensando\u2026 \u00bfSer\u00e1 un juguete o ser\u00e1 realmente \u00fatil?\n<p>Cuando compr\u00e9 el ASUS Xtion Pro Live en Amazon por 143,71\u20ac ya imaginaba que no ser\u00eda lo mismo que los sensores de Prometheus, que escaneaban la cueva con una agilidad y precisi\u00f3n propias de una pel\u00edcula de ciencia ficci\u00f3n, pero no sab\u00eda hasta qu\u00e9 punto el sensor iba a ser realmente \u00fatil o ser\u00eda simplemente un juguete de dudosa utilidad asociado a una consola de videojuegos. La verdad era que los videojuegos con interface \u201cnatural\u201d no hab\u00edan tenido el \u00e9xito que se esperaba de ellos.<\/p>\n<p>\u00c9ste era el primer paso: averiguar si el sensor es suficientemente estable y tiene suficiente precisi\u00f3n como para que los resultados tengan cierto valor. Tambi\u00e9n me preocupaba si el software asociado al sensor tendr\u00eda la suficiente calidad como para usar el sensor con tranquilidad y poder construir sobre \u00e9l alguna aplicaci\u00f3n b\u00e1sica que me permitiera hacer lo que estaba buscando.<\/p>\n<p><!--more--><\/p>\n<p>Bien, pues voy a tratar de obtener la informaci\u00f3n que genera el sensor de profundidad.<\/p>\n<p>El sensor dispone de una conexi\u00f3n USB para comunicarse con un host. Adem\u00e1s, se alimenta por esta conexi\u00f3n USB por lo que no necesita una alimentaci\u00f3n separada.<\/p>\n<p>La informaci\u00f3n del sensor se obtiene a trav\u00e9s de la librer\u00eda OpenNI (OpenNI2 en su versi\u00f3n actual). Esta librer\u00eda facilita la adquisici\u00f3n de la informaci\u00f3n que proporciona el sensor a trav\u00e9s de la conexi\u00f3n USB. Se distribuye en c\u00f3digo fuente bajo licencia Apache Versi\u00f3n 2.0, que confieso no haber le\u00eddo aunque, de momento, no me preocupa mucho ya que la he usado \u201cjust for fun\u201d y no he ganado nada.<\/p>\n<p>Como ya dije en mi post anterior, era mantenida por PrimeSense hasta que fue comprada por Apple. Desde entonces es mantenida por Occipital, que ha generado un repositorio en github.com y ha publicado un sitio (<a href=\"http:\/\/structure.io\/openni\" target=\"_blank\">http:\/\/structure.io\/openni<\/a>) desde el que se puede obtener informaci\u00f3n y descargar los fuentes de la \u00faltima versi\u00f3n. Est\u00e1 disponible para desarrollar en Windows, Linux, OS X y Android.<\/p>\n<p>Voy a hacer la primera prueba. Con el sensor viene un CD etiquetado como \u201cPrimeSense Software Package 20.4.2.20\u201d pero sorprendentemente no se puede leer. \u00a1Qu\u00e9 desastre! Rebuscando por el sitio de ASUS encuentro una imagen iso identificada como \u201cV1164_1202.iso\u201d, me la bajo, me hago un disco y con este tengo m\u00e1s suerte. Encuentro un ejecutable Windows para instalar un SDK. Lo ejecuto y me instala el driver USB del sensor y la librer\u00eda OpenNi. Veo que con la librer\u00eda vienen una serie de aplicaciones ejemplo de c\u00f3mo usarla. Est\u00e1n compiladas. Enchufo el conector USB del sensor en mi port\u00e1til. Veo c\u00f3mo aparecen dos nuevos dispositivos USB en Windows. Empieza a subirme la adrenalina. Pincho compulsivamente dos veces en el fichero NIViewer.exe y \u2026 \u00a1vaya, parece que funciona!<\/p>\n<p>Estoy viendo la representaci\u00f3n de dos streams de video en tiempo real. A la derecha de la pantalla de mi port\u00e1til se ve mi habitaci\u00f3n, en color, como si se estuviera viendo con una c\u00e1mara USB. A la izquierda de la pantalla se ve una imagen de video coloreada con distintos tonos de amarillo. Al cabo de un rato mir\u00e1ndola me doy cuenta de que es la misma escena que se ve en color a la derecha pero coloreada con distintos tonos de amarillo en funci\u00f3n de la distancia de los objetos de la escena al sensor. Bueeeno, la imagen en amarillo es bastante fea pero es una manera de representar la informaci\u00f3n de profundidad que adquiere el sensor.<\/p>\n<p><a href=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/04\/Adquisicion_depth.bmp\" rel=\"attachment wp-att-166\"><img decoding=\"async\" class=\"alignnone wp-image-166\" src=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/04\/Adquisicion_depth-300x177.bmp\" alt=\"Imagen de profundidad\" width=\"326\" height=\"193\" loading=\"lazy\" srcset=\"https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/04\/Adquisicion_depth-300x177.bmp 300w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/04\/Adquisicion_depth-768x453.bmp 768w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/04\/Adquisicion_depth.bmp 827w\" sizes=\"auto, (max-width: 326px) 100vw, 326px\" \/><\/a><a href=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/04\/Adquisicion_color.bmp\" rel=\"attachment wp-att-165\"><img decoding=\"async\" class=\"alignnone wp-image-165\" src=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/04\/Adquisicion_color-300x186.bmp\" alt=\"Imagen de color\" width=\"310\" height=\"192\" loading=\"lazy\" srcset=\"https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/04\/Adquisicion_color-300x186.bmp 300w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/04\/Adquisicion_color-768x477.bmp 768w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/04\/Adquisicion_color.bmp 786w\" sizes=\"auto, (max-width: 310px) 100vw, 310px\" \/><\/a><\/p>\n<p>Es un buen comienzo. Merece la pena seguir. Pero no me gusta la imagen en amarillo, quiero ver una nube de puntos.<\/p>\n<p>A partir de aqu\u00ed voy a trabajar con Linux. Windows me ha servido para ver algo deprisa, es muy c\u00f3modo, uno est\u00e1 acostumbrado al entorno y me manejo con soltura con carpetas, ficheros, editores, etc. En Windows instalas el paquete mediante un ejecutable, se instalan los recursos necesarios, el ejemplo viene compilado, se ejecuta y, generalmente, funciona. Pero para ir m\u00e1s lejos me parece mejor usar Linux. Mi intenci\u00f3n es generar una aplicaci\u00f3n sobre OpenNI que permita manipular las im\u00e1genes obtenidas. Si hay que compilar, depurar o bucear por el c\u00f3digo fuente, me parece mejor hacerlo en Linux. Adem\u00e1s, de momento, voy a desarrollar en mi ordenador port\u00e1til pero considero que es un requerimiento clave que este trabajo se pueda trasladar a un equipo embebido.<\/p>\n<p>Uso la \u00faltima versi\u00f3n LTS de Ubuntu. Me descargo la \u00faltima versi\u00f3n de OpenNI2 para x86. Es un bz2 que se descomprime en home. Tiene un script de instalaci\u00f3n que ejecuto y se instala. Tambi\u00e9n vienen los ejemplos compilados pero antes de probar a ejecutarlos hay que instalar un par de paquetes: freeglut3-dev y libusb-1.0-0-dev. Los instalo, enchufo el sensor, ejecuto NiViewer y \u00a1funciona!<\/p>\n<p>Mi objetivo es poder compilar y depurar de una manera c\u00f3moda. Voy a usar Eclipse, pero la compilaci\u00f3n del paquete (librer\u00edas y aplicaciones de ejemplo) la hago con make en la l\u00ednea de comandos. Van saliendo errores: el compilador no encuentra alg\u00fan .h. Corresponden a paquetes que usa la librer\u00eda y que tengo que ir instalando para que vaya compilando. He instalado, adem\u00e1s de los que ya hab\u00eda instalado antes, doxigen, graphviz, libudev-dev y openjdk-6-jdk. Finalmente compila, pruebo NiViewer y funciona. Voy avanzando.<\/p>\n<p>Ahora voy a compilar con Eclipse el ejemplo NiViewer. Ya se ha compilado con make pero quiero usar Eclipse para compilar en Release y Debug y poder depurar c\u00f3modamente. Me genero un Workspace y un proyecto del tipo \u201cMakefile Project with existing code\u201d con objetivo de clean, release y debug. Tengo que compilar el paquete completo en modo debug para que est\u00e9n disponibles tambi\u00e9n las librer\u00edas en modo debug. Finalmente, tras ajustar la configuraci\u00f3n t\u00edpica de Eclipse de paths y dem\u00e1s, consigo que compile y funcione el ejecutable, pero no puedo depurar: gbd no encuentra las librer\u00edas din\u00e1micas. Esto se arregla configurando la variable de entorno LD_LIBRARY_PATH con el valor apropiado.<\/p>\n<p>Bueno, ya estoy en condiciones de generar aplicaciones c\u00f3modamente sobre OpenNI. Puedo compilar, linkar y depurar. Voy a rebuscar en qu\u00e9 formato vienen los datos en esos streams de video que he visto con la aplicaci\u00f3n de ejemplo NiViewer.<\/p>\n<p>Para familiarizarse con la librer\u00eda OpenNI uno puede: bajarse del sitio de Occipital y leerse el \u201cOpenNI Programers Guide\u201d, s\u00f3lo te da una idea del alcance de la librer\u00eda\u00a0y\u00a0qu\u00e9 es lo que se puede hacer con cada clase; comprar y leerse por encima par\u00e1ndose en alg\u00fan ejemplo el libro \u201cOpenNI Cookbook\u201d de Soroush Falahati; y bucear por el c\u00f3digo fuente de la librer\u00eda y de los ejemplos de aplicaciones que incluye. Sin duda esta \u00faltima es la m\u00e1s efectiva pero las dos primeras tambi\u00e9n son muy \u00fatiles.<\/p>\n<p>La librer\u00eda est\u00e1 desarrollada en C++.<\/p>\n<p>El sensor se gestiona mediante una instancia de la clase Device. Las dos im\u00e1genes de video que se representan con el ejemplo NiViewer se corresponden con dos streams que genera el sensor. Uno de ellos env\u00eda la informaci\u00f3n de profundidad y el otro la informaci\u00f3n de color. Estos dos streams se gestionan mediante instancias de la clase VideoStream.<\/p>\n<p>Para ponerlo en marcha, el sensor se \u201cabre\u201d con el m\u00e9todo open de la clase Device y se arranca con el m\u00e9todo start. Tras \u00e9sto, se pueden arrancar los streams. Se generan con el m\u00e9todo create. Con este m\u00e9todo se identifica el sensor del que procede y su tipo, es decir si se trata del stream de profundidad o del stream de color. Se ponen en marcha con el m\u00e9todo start.<\/p>\n<p>Una vez arrancados los streams, si queremos informaci\u00f3n coherente de profundidad y de color, es necesario sincronizarlos. La sincronizaci\u00f3n se habilita con el m\u00e9todo setDepthColorSyncEnabled de la clase Device y se elige el modo de la sincronizaci\u00f3n con el m\u00e9todo setImageRegistrationMode.<\/p>\n<p>Los streams van llegando frame a frame. Los frames se gestionan con una instancia de la clase VideoFrameRef.<\/p>\n<p>Mediante la funci\u00f3n OpenNI::waitForAnyStream() se espera con un timeout a que llegue un nuevo frame de alguno de los dos streams. Como los streams est\u00e1n sincronizados, con esta funcion recibiremos secuencialmente los streams de profundidad y de color que van marcados con el mismo indice. Este \u00edndice numera cada frame desde un origen com\u00fan a los dos streams. Ser\u00e1 necesario verificar que el \u00edndice coincide en los dos frames, de profundidad y de color, que van a tratarse juntos.<\/p>\n<p>\u00bfEn qu\u00e9 formato tenemos la informaci\u00f3n en los frames?<\/p>\n<p>Un frame de profundidad est\u00e1 compuesto por un mapa de pixels representado por un array de 320\u00d7240 estructuras de tipo DepthPixel que no es m\u00e1s que un unsigned int de 16 bits. De esta manera, a cada pixel se le asigna un valor de profundidad que representa la distancia desde un plano que pasa por el objetivo de la c\u00e1mara hasta el objeto de la escena representado por ese pixel. Las coordenados X e Y son simplemente las posiciones en el mapa de pixels donde el origen es la esquina superior izquierda de la imagen.<\/p>\n<p>Un frame de color est\u00e1 compuesto por un mapa de pixels representado por un array de 320\u00d7240 estructuras de tipo RGB888Pixel. Esta estructura tiene tres componentes, cada uno de ellos es un unsigned int de 8 bits y representa cada una de las componentes RGB de color del pixel. Esto es lo que habitualmente se conoce por un pixel RGB de 24 bits.<\/p>\n<p>Las coordenadas X Y del frame de color coinciden con las del frame de profundidad por lo que en el frame de color esta la informaci\u00f3n del color con el que la camara RGB ve el punto representado en el frame de profundidad por las mismas coordenadas XY.<\/p>\n<p>Ahora se entiende mejor qu\u00e9 es lo que representa el video pintado de amarillo. Son los mismos pixels del frame de video RGB pero pintados en distintos tonos de amarillo en funci\u00f3n de su distancia a la c\u00e1mara. Los pixels m\u00e1s brillantes est\u00e1n m\u00e1s cerca de la c\u00e1mara y los m\u00e1s oscuros est\u00e1n m\u00e1s lejos.<\/p>\n<\/body>","protected":false},"excerpt":{"rendered":"<p>Estaba yo pensando\u2026 \u00bfSer\u00e1 un juguete o ser\u00e1 realmente \u00fatil? Cuando compr\u00e9 el ASUS Xtion Pro Live en Amazon por 143,71\u20ac ya imaginaba que no ser\u00eda lo mismo que los sensores de Prometheus, que escaneaban la cueva con una agilidad &hellip; <a href=\"https:\/\/www.rtbasics.com\/WP_2\/archives\/167\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[5,21,20],"tags":[23,13,7,22,28,24,27,29],"class_list":["post-167","post","type-post","status-publish","format-standard","hentry","category-broadcasting","category-depth-sensors","category-sensors","tag-asus-xtion","tag-comm","tag-hard","tag-kinect","tag-occipital","tag-openni","tag-primesense","tag-structure-sensor"],"jetpack_featured_media_url":"","jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p4vaHY-2H","_links":{"self":[{"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/posts\/167","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/comments?post=167"}],"version-history":[{"count":7,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/posts\/167\/revisions"}],"predecessor-version":[{"id":190,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/posts\/167\/revisions\/190"}],"wp:attachment":[{"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/media?parent=167"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/categories?post=167"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/tags?post=167"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}