{"id":180,"date":"2016-05-05T12:24:32","date_gmt":"2016-05-05T10:24:32","guid":{"rendered":"http:\/\/www.rtbasics.com\/WP_2\/?p=180"},"modified":"2016-05-05T12:35:20","modified_gmt":"2016-05-05T10:35:20","slug":"sensor-de-profundidad-procesamiento","status":"publish","type":"post","link":"https:\/\/www.rtbasics.com\/WP_2\/archives\/180","title":{"rendered":"Sensor de profundidad. Procesamiento."},"content":{"rendered":"<body><p><\/p>Estaba yo pensando\u2026 \u00bfCu\u00e1l ser\u00e1 la mejor manera de representar esta informaci\u00f3n?\n<p>Una vez que hemos adquirido la informaci\u00f3n de la escena que ve el sensor ser\u00e1 necesario procesarla para extraer la parte que nos interesa o nos resulta \u00fatil. Se podr\u00eda, por ejemplo, obtener medidas de las paredes que envuelven la escena; se podr\u00eda extraer de la imagen todo lo que no fueran paredes, suelo o techo; se podr\u00edan extraer objetos de la escena para tratarlos de manera separada y otras muchas cosas. En fin, \u00e9sta es la parte m\u00e1s imaginativa.<\/p>\n<p>Yo me he propuesto, simplemente, obtener una imagen de 360\u00ba de la escena. Para esto habr\u00e1 que contar con varias \u201cinstant\u00e1neas\u201d tomadas desde distintas posiciones del sensor alrededor de un eje. C\u00f3mo integrar estas distintas im\u00e1genes en una \u00fanica representaci\u00f3n de la escena lo dejo para otro post m\u00e1s adelante. Ahora contar\u00e9 algo sobre la herramienta que he usado para procesar la informaci\u00f3n.<\/p>\n<p><!--more--><\/p>\n<p>La informaci\u00f3n de profundidad organizada en los frames que acabamos de adquirir del sensor no parece la m\u00e1s conveniente para hacer este procesado. Esta representaci\u00f3n es buena para hacer la trasferencia desde el sensor, porque es compacta, pero es mala para procesar la informaci\u00f3n que contiene ya que, incluso, es dependiente de la resoluci\u00f3n del propio sensor.<\/p>\n<p><a href=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/05\/PCL.bmp\" rel=\"attachment wp-att-182\"><img decoding=\"async\" class=\"alignleft size-medium wp-image-182\" src=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/05\/PCL-223x300.bmp\" alt=\"Point Cloud Library\" width=\"223\" height=\"300\" loading=\"lazy\" srcset=\"https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/05\/PCL-223x300.bmp 223w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/05\/PCL.bmp 466w\" sizes=\"auto, (max-width: 223px) 100vw, 223px\" \/><\/a><\/p>\n<p>Cuando uno busca algo sobre el tratamiento de escenas escaneadas en 3D siempre acaba en el mismo sitio: la librer\u00eda PCL (Point Cloud Library).<\/p>\n<p>Como ellos mismos aclaran en su sitio web (<a href=\"http:\/\/pointclouds.org\" target=\"_blank\">http:\/\/pointclouds.org<\/a>), PCL es un gran proyecto de c\u00f3digo abierto para el procesamiento de im\u00e1genes 2D\/3D y nubes de puntos. Realmente, es un conjunto de librer\u00edas que incluyen algoritmos para filtrar, identificar caracter\u00edsticas, reconstruir superficies, combinar im\u00e1genes, extraer objetos y otros. Estos algoritmos pueden ser usados, por ejemplo, para filtrar datos con ruido, combinar varias nubes de puntos, extraer partes relevantes de una escena, extraer puntos clave, calcular caracter\u00edsticas que nos permitan reconocer objetos por su forma geom\u00e9trica o reconstruir superficies por nombrar alguno.<\/p>\n<p>La librer\u00eda se distribuye en c\u00f3digo fuente bajo licencia \u201c3-clause BSD\u201d. Su uso comercial y para investigaci\u00f3n es gratuito.<\/p>\n<p>Una nube de puntos es una estructura de datos usada para representar una colecci\u00f3n de puntos que representa informaci\u00f3n en tres dimensiones. Los puntos de la nube se representan por sus tres coordenadas cartesianas X, Y y Z. Cuando la nube de puntos contiene informaci\u00f3n de color de cada uno de los puntos se puede hablar de una nube de puntos 4D.<\/p>\n<p>Un aspecto muy importante es la representaci\u00f3n de una nube de puntos en un fichero. Hay distintos formatos, el m\u00e1s com\u00fan y el usado por las librer\u00edas PCL es el formato PCD (Point Cloud Data). Un fichero de este tipo acaba su nombre con la extensi\u00f3n .pcd, que lo identifica como tal.<\/p>\n<p>Haciendo memoria de lo publicado en el post anterior, la informaci\u00f3n obtenida del sensor en un frame de profundidad estaba ordenada por pixels y para cada uno de ellos se inclu\u00eda la distancia al plano de la c\u00e1mara. Este es un formato muy c\u00f3modo para representar el streming en niveles de amarillo en funci\u00f3n de la profundidad pero no son coordenadas cartesianas. Para poder procesar la informaci\u00f3n de los frames adquiridos con la librer\u00eda PCL primero ser\u00e1 necesario traducir este formato a una nube de puntos.<\/p>\n<p>La primera tarea, por tanto, ser\u00e1 generar un fichero PCD con la informaci\u00f3n capturada en un frame de profundidad.<\/p>\n<p>Tambi\u00e9n hay otra manera de pasar datos del sensor a las librer\u00edas PCL. Es haciendo uso del OpenNI grabber que permite el control de los streams de color y de profundidad del sensor desde la librer\u00eda PCL pero no me pareci\u00f3 buena idea usarlo por:<\/p>\n<ul>\n<li>El grabber de la versi\u00f3n de PCL que us\u00e9 no soportaba la versi\u00f3n 2 de OpenNI, que es la que yo hab\u00eda usado con \u00e9xito para obtener los datos del sensor.<\/li>\n<li>Me pareci\u00f3 buena idea mantener un interface claro entre OpneNI y PCL que me permitiera determinar con facilidad en qu\u00e9 lado estaban los problemas en caso de que los hubiera (que siempre los hay).<\/li>\n<\/ul>\n<p>Para obtener la disposici\u00f3n de los puntos en coordenadas cartesianas se usa el m\u00e9todo convertDepthToWorld de la clase CoordinateConverter. Este m\u00e9todo toma como argumentos la direcci\u00f3n de la instancia de la clase VideoStream que representa el stream de profundidad, la posici\u00f3n X del pixel, la posici\u00f3n Y del pixel, su valor de profundidad y tres punteros a float en los que se devuelven los valores de X, Y y Z medidos en mil\u00edmetros de la posici\u00f3n del pixel en sus coordenadas cartesianas.<\/p>\n<p>El eje X de estas coordenadas coincide con una l\u00ednea que pasa por el proyector infrarrojo y la c\u00e1mara, es decir, ser\u00eda el eje horizontal si el sensor estuviera de pie apoyado en el suelo. El eje Y estar\u00eda contenido en un plano paralelo al frontal de la c\u00e1mara siendo perpendicular al eje X y el eje Z se proyectar\u00eda hacia la escena siendo perpendicular a los ejes X e Y. El origen de las coordenadas estar\u00eda en la c\u00e1mara del sensor y, en cuanto a los signos, desde la perspectiva de la c\u00e1mara, un objeto movi\u00e9ndose desde la izquierda a la derecha estar\u00eda incrementando su valor en el eje X; movi\u00e9ndose desde abajo hacia arriba estar\u00eda incrementando su valor en el eje Y; y movi\u00e9ndose desde la c\u00e1mara hacia la escena estar\u00eda incrementando su valor en el eje Z.<\/p>\n<p>Como ya he comentado antes, los ficheros pcd contienen informaci\u00f3n de una nube de puntos. Est\u00e1n compuestos por dos partes. La primera parte del fichero es una cabecera codificada en ASCII en la que se indican distintas caracter\u00edsticas del fichero. La segunda parte consiste en la informaci\u00f3n de los puntos, que puede estar codificada en ASCII o en binario.<\/p>\n<p>La cabecera est\u00e1 formada por varias entradas, cada una de ellas en una l\u00ednea separada por el car\u00e1cter new line (\\n). Se pueden poner comentarios precedidos por el car\u00e1cter \u2018#\u2019. Cada entrada est\u00e1 definida por una palabra clave y describe una caracter\u00edstica del fichero o de los puntos representados. Son las siguientes:<\/p>\n<ul>\n<li>VERSION. Se indica la versi\u00f3n del formato del fichero. La \u00faltima es la v.7<\/li>\n<li>FIELDS. Se indican en esta entrada los campos de informaci\u00f3n que incluye cada punto. En el ejemplo a continuaci\u00f3n se incluye informaci\u00f3n de las coordenadas (x y z) y del color del punto. Tambi\u00e9n se pueden incluir otras caracter\u00edsticas como las normales a la superficie en el punto u otros campos m\u00e1s complejos que no voy a comentar.<\/li>\n<li>SIZE. Especifica el tama\u00f1o en bytes de cada campo de informaci\u00f3n del punto.<\/li>\n<li>TYPE. Especifica el tipo de cada campo de informaci\u00f3n. Las opciones son I para enteros con signo, U para enteros sin signo y F para floats.<\/li>\n<li>COUNT. Especifica cuantos componentes tiene cada campo de informaci\u00f3n del punto. Siempre ser\u00e1 1 excepto para algunos campos complejos.<\/li>\n<li>WIDTH. Si la nube de puntos es organizada se indica el n\u00famero de puntos en una fila de \u00e9sta. Si no lo es, indica el n\u00famero total de puntos.<\/li>\n<li>HEIGHT. Indica el n\u00famero de filas de la nube de puntos si es organizada. En caso de que no lo sea, tomar\u00e1 el valor 1.<\/li>\n<li>VIEWPOINT. Especifica un punto de vista para los puntos de la nube. Se define como una posici\u00f3n m\u00e1s un cuaternio, que define un giro. Son, por tanto, siete valores. En el ejemplo se indica su valor por defecto.<\/li>\n<li>POINTS. Indica el n\u00famero de puntos de la nube. En la versi\u00f3n v.7 es un campo redundante.<\/li>\n<li>DATA. Indica el formato en el que se representan los datos. Puede tomar los valores \u201cascii\u201d, como en el ejemplo, o \u201cbinary\u201d. El formato ascii es m\u00e1s costoso de leer y escribir, pero es portable y el formato binary es una representaci\u00f3n en memoria del array <em>pcl::PointCloud.points<\/em> escrito o le\u00eddo del fichero con <em>mmap\/munmap<\/em> en Linux. Es m\u00e1s r\u00e1pido pero la portabilidad no est\u00e1 garantizada.<\/li>\n<\/ul>\n<p>Se puede ver a continuaci\u00f3n un ejemplo. Los valores de las coordenadas se representan en metros.<\/p>\n<p><a href=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/05\/Fichero_pcd.bmp\" rel=\"attachment wp-att-185\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-185\" src=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/05\/Fichero_pcd.bmp\" alt=\"Fichero_pcd\" width=\"586\" height=\"274\" loading=\"lazy\" srcset=\"https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/05\/Fichero_pcd.bmp 586w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/05\/Fichero_pcd-300x140.bmp 300w\" sizes=\"auto, (max-width: 586px) 100vw, 586px\" \/><\/a><\/p>\n<p>Ya tengo un fichero pcd con la informaci\u00f3n que me llega en dos frames sincronizados de los streams de color y de profundidad. Ya puedo procesar la nube de puntos. Lo primero es instalar las librer\u00edas PCL, compilarlas y poner en marcha un entorno que me permita editar, compilar y depurar c\u00f3modamente. De nuevo mi candidato es Eclipse que, adem\u00e1s, ya estoy usando con OpenNI.<\/p>\n<p>En la web de la librer\u00eda PCL se facilita la descarga de los fuentes y de binarios precompilados para Linux, Windows y Mac OS X. La instalaci\u00f3n de las liber\u00edas PCL s\u00f3lo la he hecho en Linux, ya estaba metido en faena con Linux y no me aportaba nada hacerla en Windows.<\/p>\n<p>Para la instalaci\u00f3n del paquete en Linux y la compilaci\u00f3n de los fuentes he seguido las instrucciones del tutorial <a href=\"http:\/\/pointclouds.org\/documentation\/tutorials\/compiling_pcl_posix.php\" target=\"_blank\">http:\/\/pointclouds.org\/documentation\/tutorials\/compiling_pcl_posix.php<\/a>.<\/p>\n<p>Tambi\u00e9n hay un tutorial que indica c\u00f3mo usar Eclipse para la compilaci\u00f3n de las librer\u00edas y ejemplos de aplicaciones que las usan. Se puede encontrar en <a href=\"http:\/\/pointclouds.org\/documentation\/tutorials\/using_pcl_with_eclipse.php\" target=\"_blank\">http:\/\/pointclouds.org\/documentation\/tutorials\/using_pcl_with_eclipse.php<\/a>. Esto me sirve para poner en marcha Eclipse.<\/p>\n<p>Ahora ya puedo procesar la nube de puntos.<\/p>\n<p>En primer lugar, es conveniente hacerle una \u201climpieza\u201d a la informaci\u00f3n que nos llega directamente del sensor y que est\u00e1 reflejada en este fichero. Se trata de eliminar la informaci\u00f3n que, por redundante o por err\u00f3nea, no aporta nada y complica el procesamiento de la informaci\u00f3n.<\/p>\n<p>Se aplica un filtro para despreciar los puntos que quedan fuera del rango \u00fatil del sensor. Seg\u00fan las especificaciones del fabricante del sensor, el ASUS Xtion Pro Live funciona en el rango de distancias de 0,8 a 6,0 metros medidos desde el sensor. Se genera una instancia de la clase PassThrough y se configura para filtrar todos los puntos cuyos valores en la coordenada Z (con el m\u00e9todo setFilterFieldName) queden fuera del rango mencionado (con el m\u00e9todo setFilterLimits).<\/p>\n<p>En segundo lugar, se aplica un submuestreo para un voxel de 1cm. Un voxel es una unidad c\u00fabica. Si es de 1 cm, representa un espacio c\u00fabico de 1cm de lado. Para nuestra aplicaci\u00f3n es m\u00e1s que suficiente una resoluci\u00f3n de la informaci\u00f3n de 1 cm3 (cent\u00edmetro c\u00fabico). De esta manera se elimina informaci\u00f3n innecesaria que hace m\u00e1s costosos los tratamientos posteriores. Por la tecnolog\u00eda del propio sensor habr\u00e1 m\u00e1s densidad de puntos en las partes de la escena m\u00e1s cercanas al sensor, mediante este filtro se ajusta esta densidad a la necesaria en las partes m\u00e1s pobladas.<\/p>\n<p>Se genera una instancia de la clase VoxelGrid, se configura el tama\u00f1o del voxel con el m\u00e9todo setLeafSize y se aplica el filtro generando una nueva nube de punto filtrada.<\/p>\n<p>Por \u00faltimo, otro tratamiento com\u00fan es el filtro estad\u00edstico de \u201coutliers\u201d. Mediante este filtro se eliminan los puntos que est\u00e1n aislados en la nube de puntos. Probablemente, estos puntos aislados son debidos a reflejos del haz de leds sobre la escena u otros errores y no corresponden a puntos reales. Estos puntos at\u00edpicos se filtran en base a la media de las distancias a sus n vecinos m\u00e1s cercanos. Se genera una instancia de la clase StatisticalOutlierRemoval y la configuro para rechazar aquellos puntos cuya distancia media a sus 50 vecinos m\u00e1s cercanos sea superior a 3 veces la desviaci\u00f3n t\u00edpica.<\/p>\n<p>Con esto ya tenemos una nube de puntos limpia y reluciente, preparada para su representaci\u00f3n con un visualizador y su integraci\u00f3n con otras instant\u00e1neas para generar una representaci\u00f3n panor\u00e1mica de la escena.<\/p>\n<\/body>","protected":false},"excerpt":{"rendered":"<p>Estaba yo pensando\u2026 \u00bfCu\u00e1l ser\u00e1 la mejor manera de representar esta informaci\u00f3n? Una vez que hemos adquirido la informaci\u00f3n de la escena que ve el sensor ser\u00e1 necesario procesarla para extraer la parte que nos interesa o nos resulta \u00fatil. &hellip; <a href=\"https:\/\/www.rtbasics.com\/WP_2\/archives\/180\">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,22,24,25],"class_list":["post-180","post","type-post","status-publish","format-standard","hentry","category-broadcasting","category-depth-sensors","category-sensors","tag-asus-xtion","tag-kinect","tag-openni","tag-pcl"],"jetpack_featured_media_url":"","jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p4vaHY-2U","_links":{"self":[{"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/posts\/180","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=180"}],"version-history":[{"count":7,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/posts\/180\/revisions"}],"predecessor-version":[{"id":191,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/posts\/180\/revisions\/191"}],"wp:attachment":[{"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/media?parent=180"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/categories?post=180"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/tags?post=180"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}