{"id":231,"date":"2016-08-06T19:13:31","date_gmt":"2016-08-06T17:13:31","guid":{"rendered":"http:\/\/www.rtbasics.com\/WP_2\/?p=231"},"modified":"2016-08-06T19:13:31","modified_gmt":"2016-08-06T17:13:31","slug":"sensor-de-profundidad-composicion-del-escenario","status":"publish","type":"post","link":"https:\/\/www.rtbasics.com\/WP_2\/archives\/231","title":{"rendered":"Sensor de profundidad. Composici\u00f3n del escenario"},"content":{"rendered":"<body><p><\/p>Estaba yo pensando\u2026 \u00bfC\u00f3mo se compone el escenario completo?\n<p>Nuestro problema, ahora, es componer la imagen de un escenario completo de 180\u00ba contenida en un \u00fanico fichero .pcd<\/p>\n<p>Como ya vimos en su momento, los puntos de una nube de puntos representada por un fichero en formato pcd no necesitan mantener un orden establecido ni est\u00e1n sujetos a muchas restricciones. Yo dir\u00eda que s\u00f3lo a una: todos est\u00e1n referenciados al mismo sistema de coordenadas. Bueno, pues a esto se reduce nuestro problema: debemos cambiar el sistema de coordenadas referencia de los puntos de un fichero por el del otro. Es decir, los puntos del fichero source, que tienen como referencia sus propias coordenadas, debemos referenciarlos a las coordenadas del fichero target. Una vez hecho esto ya podremos a\u00f1adir los puntos al fichero target.<\/p>\n<p><!--more--><\/p>\n<p>Creo que es conveniente recordar que las coordenadas de los puntos en cada fichero tienen su origen en el propio sensor. Pod\u00e9is revisarlo en este <a href=\"http:\/\/www.rtbasics.com\/WP_2\/archives\/180\" target=\"_blank\">post<\/a>. Por lo tanto, el problema se reduce a determinar el cambio de posici\u00f3n del objetivo del sensor de la foto source a la foto target.<\/p>\n<p>En este momento, tuve que repasar mis olvidados conocimientos de geometr\u00eda espacial. Me ayud\u00f3 mucho un libro llamado \u201cFundamentos de rob\u00f3tica\u201d de Antonio Barrientos, Lu\u00eds Felipe Pe\u00f1\u00edn, Carlos Balaguer y Rafael Aracil (ISBN: 84-481-0815-9). De estos cuatro autores, Barrientos, Balaguer y Aracil son viejos conocidos porque me dieron alguna clase en los cursos de la especialidad de Electr\u00f3nica y Autom\u00e1tica en la ETSII de la UPM. Adem\u00e1s, tuve cierto contacto con ellos porque hice el proyecto fin de carrera en el Departamento de Autom\u00e1tica, Ingenier\u00eda Electr\u00f3nica e Inform\u00e1tica Industrial (DISAM) de la UPM.<\/p>\n<p>En el Cap\u00edtulo 3 de este libro \u201cHerramientas matem\u00e1ticas para la localizaci\u00f3n espacial\u201d se describen con claridad los recursos matem\u00e1ticos necesarios para plantear y resolver el problema del cambio de coordenadas de los puntos.<\/p>\n<p>El cambio de posici\u00f3n del sensor se caracteriza con una matriz de transformaci\u00f3n que indica un giro alrededor de cada uno de sus ejes (X, Y, Z) y una traslaci\u00f3n del punto origen de coordenadas. Una vez conocida esta matriz, podremos trasladar los puntos de un sistema de coordenadas a otro.<\/p>\n<p>Para obtener esta matriz de traslaci\u00f3n podr\u00edamos usar dos m\u00e9todos:<\/p>\n<p>\u2013 Si tuvi\u00e9ramos caracterizada con precisi\u00f3n la geometr\u00eda de la plataforma\u00a0 de pan &amp; tilt y conoci\u00e9ramos con precisi\u00f3n la posici\u00f3n del objetivo del sensor sobre la plataforma y pudi\u00e9ramos medir con precisi\u00f3n los giros de los servos podr\u00edamos obtener te\u00f3ricamente la matriz de transformaci\u00f3n de cada uno de los cambios de posici\u00f3n. Esto siempre y cuando la plataforma tuviera unos ajustes y tolerancias de reloj suizo.<\/p>\n<p>\u2013 La librer\u00eda PCL (de nuevo) y la clase <strong>pcl::IterativeClosestPoint<\/strong>. Mediante esta clase se implementa un algoritmo que permite minimizar la diferencia entre dos nubes de puntos de manera iterativa. Es decir, va aproximando de manera iterativa una nube source a una nube target y cuando el algoritmo finaliza se obtiene la matriz de transformaci\u00f3n que ha llevado la nube source original a la nube m\u00e1s pr\u00f3xima a la target.<\/p>\n<p>Como ninguno de los cuatro supuestos que har\u00edan factible el primer m\u00e9todo se cumple, s\u00f3lo nos queda la alternativa de la librer\u00eda PCL. \u00c9ste es su valor: podemos resolver por software un problema que, de otra manera, hubiera necesitado una soluci\u00f3n mec\u00e1nica m\u00e1s compleja y, por supuesto, mucho m\u00e1s cara.<\/p>\n<p>El uso de esta clase es muy sencillo:<\/p>\n<ul>\n<li>Tras generar una instancia, se configura la nube de puntos source con el m\u00e9todo <em>setInputSource()<\/em>.<\/li>\n<li>Se configura la nube de puntos target con el m\u00e9todo <em>setInputTarget()<\/em>.<\/li>\n<li>Se configura la manera de considerar finalizada la aproximaci\u00f3n. Hay distintas maneras de hacerlo. En este caso uso el m\u00e9todo <em>setMaximumIterations()<\/em> para configurar el n\u00famero de iteraciones que se deben hacer.<\/li>\n<li>El algoritmo se lanza con el m\u00e9todo <em>align()<\/em>.<\/li>\n<li>Se verifica que el algoritmo converge con el m\u00e9todo <em>hasConverged()<\/em>.<\/li>\n<li>Se obtiene la matriz de transformaci\u00f3n con el m\u00e9todo <em>getFinalTransformation()<\/em>.<\/li>\n<\/ul>\n<p>Este recurso de la librer\u00eda PCL es fundamental, pero no lo resuelve todo. Hay que trabajar en la ingenier\u00eda y generar un procedimiento de integraci\u00f3n de las distintas instant\u00e1neas en un \u00fanico escenario.<\/p>\n<p>En alg\u00fan momento ya he comentado que el campo de visi\u00f3n en horizontal del sensor es de 58\u00ba. He obtenido una toma cada 15\u00ba, lo que supone siete tomas por cuadrante tal y como se muestran a continuaci\u00f3n. En realidad, as\u00ed se cubre un \u00e1ngulo de visi\u00f3n de 150\u00ba.<\/p>\n<p>Para cubrir otro cuadrante necesitamos otras siete tomas. Una de ellas, la toma central numerada como la 6, es com\u00fan a los dos cuadrantes.<\/p>\n<p>Considerar\u00e9 las coordenadas de la toma n\u00famero 6 como la referencia a la que quiero trasladar todos los puntos de las dem\u00e1s tomas, convirti\u00e9ndose, de esta manera, en la instant\u00e1nea central de la composici\u00f3n de las trece tomas que abarcar\u00e1n una amplitud de 180\u00ba. Bueno, realmente, cubren 240\u00ba de visi\u00f3n.<\/p>\n<p>He mantenido desvinculado el proceso de adquisici\u00f3n de las trece instant\u00e1neas y el de su integraci\u00f3n sobre el sistema de coordenadas de la toma n\u00famero 6.\u00a0Por lo tanto, primero se obtienen todas las tomas grabando cada una a un fichero .pcd y despu\u00e9s se comienza el proceso de integraci\u00f3n. Esto permite depurar con comodidad el proceso de integraci\u00f3n ya que las tomas est\u00e1n grabadas en ficheros y se puede repetir este proceso partiendo siempre de la misma informaci\u00f3n.<\/p>\n<p>En este proceso de integraci\u00f3n se pueden distinguir tres pasos, primero se acondicionan las nubes de puntos con:<\/p>\n<ul>\n<li>Filtro para descartar los puntos que quedan fuera del rango del sensor.<\/li>\n<li>Submuestreo con un voxel de 1 cm para ajustar la informaci\u00f3n a la necesaria y aligerar los tratamientos posteriores.<\/li>\n<li>Filtro de outliers para descartar puntos aislados fruto de reflejos, errores o imprecisiones del sensor.<\/li>\n<\/ul>\n<p>Despu\u00e9s de esto, en un segundo paso, se obtienen \u00a0las matrices de transformaci\u00f3n de una instant\u00e1nea a otra de la siguiente manera:<a href=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/12_a_6.bmp\"><img decoding=\"async\" class=\"alignright size-medium wp-image-235\" src=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/12_a_6-300x170.bmp\" alt=\"12_a_6\" width=\"300\" height=\"170\" loading=\"lazy\" srcset=\"https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/12_a_6-300x170.bmp 300w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/12_a_6-768x435.bmp 768w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/12_a_6-1024x580.bmp 1024w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/12_a_6.bmp 1196w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a> <a href=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/0_a_6.bmp\"><img decoding=\"async\" class=\"alignright size-medium wp-image-237\" src=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/0_a_6-300x170.bmp\" alt=\"0_a_6\" width=\"300\" height=\"170\" loading=\"lazy\" srcset=\"https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/0_a_6-300x170.bmp 300w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/0_a_6-768x435.bmp 768w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/0_a_6-1024x580.bmp 1024w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/0_a_6.bmp 1196w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Se obtiene la matriz de transformaci\u00f3n entre la foto 12 (source) y la foto 11 (target), despu\u00e9s la matriz entre la foto 11 (source) y la 10 (target) y as\u00ed hasta obtener la matriz de transformaci\u00f3n entre la foto 7 (source) y la 6 (target).<\/p>\n<p>En el otro cuadrante, se obtiene la matriz entre la foto 0 (source) y la 1 (target), despu\u00e9s la matriz entre la foto 1 (source) y la 2 (target) y as\u00ed hasta obtener la matriz de transformaci\u00f3n entre la foto 5 (source) y la 6 (target).<\/p>\n<p>\u00bfC\u00f3mo se obtienen estas matrices de transformaci\u00f3n de una instant\u00e1nea a otra?<\/p>\n<p>Como se puede ver en la siguiente figura dos tomas consecutivas tienen en com\u00fan \u00be partes de los puntos, es decir 45\u00ba. Se usan estos puntos comunes para obtener la matriz de transformaci\u00f3n que pasa los puntos de la foto 0 a las coordenadas de la foto 1. Para ello, le quito un slice de 15\u00ba con los puntos no comunes a cada una de las fotos. No hay una funci\u00f3n que haga esto directamente en la librer\u00eda PCL, he tenido que implementarla.<\/p>\n<p><a href=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/0_a_1.bmp\"><img decoding=\"async\" class=\"size-medium wp-image-236 alignleft\" src=\"http:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/0_a_1-300x104.bmp\" alt=\"0_a_1\" width=\"300\" height=\"104\" loading=\"lazy\" srcset=\"https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/0_a_1-300x104.bmp 300w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/0_a_1-768x267.bmp 768w, https:\/\/www.rtbasics.com\/WP_2\/wp-content\/uploads\/2016\/08\/0_a_1.bmp 896w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Ya tengo dos nubes de puntos que contienen solamente los puntos comunes pero en sus propias coordenadas.<\/p>\n<p>Con la intenci\u00f3n de mejorar las prestaciones del algoritmo iterativo, en lugar de partir de la foto 0 como source, parto de la foto 0 rotada 15\u00ba. As\u00ed, al estar m\u00e1s cercanas las nubes de puntos, he comprobado que el algoritmo necesita menos iteraciones para obtener resultados similares.<\/p>\n<p>En un\u00a0tercer paso, una vez que he calculado todas las matrices de transformaci\u00f3n con las instant\u00e1neas originales, puedo ir aplicando estas transformaciones a cada foto para ir cambiando las coordenadas de sus puntos a las de la siguiente.<\/p>\n<p>En este \u00faltimo paso\u00a0se va haciendo:<\/p>\n<ul>\n<li>Giro de 15\u00ba para colocar los puntos en la posici\u00f3n desde donde se calcul\u00f3 la matriz de transformaci\u00f3n.<\/li>\n<li>Se aplica la matriz de transformaci\u00f3n mediante la funci\u00f3n <em>pcl::TransformPointCloud()<\/em>.<\/li>\n<li>Se a\u00f1aden los puntos de esta nube transformada a la nube de puntos target. Esto se hace con el operador + (suma).<\/li>\n<li>Se aplica a la nube resultado un filtro de voxelizaci\u00f3n para volver a quedarnos con un punto por cada cm3 (cent\u00edmetro c\u00fabico).<\/li>\n<\/ul>\n<p>El resultado de estos cuatro pasos se vuelve a tratar de manera iterativa girando, transformando, sumando al siguiente target y voxelizando hasta que se obtienen todos los puntos de un cuadrante en un fichero que tiene el origen de coordenadas de la instant\u00e1nea 6.\u00a0Se hace lo mismo con el otro cuadrante y finalmente obtenemos dos ficheros .pcl, uno por cuadrante, con las mismas coordenadas de la instant\u00e1nea 6.<\/p>\n<p>Como estos dos ficheros tienen el mismo sistema de coordenadas para obtener un \u00fanico fichero de toda la escena solamente hay que sumarlos y pasarle un filtro de voxel de 1cm3.<\/p>\n<p>Listo!! We did it!! \u00c9ste es el objetivo que me plantee hace unos cuantos posts. Con \u00e9sto me doy por satisfecho.<\/p>\n<p>Pod\u00e9is ver el resultado\u00a0desde el visualizador online de PCL. Si quer\u00e9is descargar el fichero para visualizarlo localmente, pinchad <a href=\"http:\/\/www.rtbasics.com\/Downloads\/3D_pics\/shot_semi_000_regsemi.pcd\" target=\"_blank\">aqui<\/a>.<\/p>\n<table border=\"0\" align=\"center\">\n<tbody>\n<tr>\n<td width=\"500\"><iframe loading=\"lazy\" style=\"max-width: 100%;\" src=\"http:\/\/pointclouds.org\/assets\/viewer\/pcl_viewer.html?load=http:\/\/www.rtbasics.com\/Downloads\/3D_pics\/shot_semi_000_regsemi.pcd&amp;zoom=5\" width=\"500\" height=\"500\" frameborder=\"no\" marginwidth=\"0\" marginheight=\"0\" align=\"center\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Quiero hacer un comentario al proceso de integraci\u00f3n que acabo de describir: Este que he descrito es el proceso \u201cingenuo\u201d, he hecho todos los pasos y los he hecho uno a uno. Me parec\u00eda importante ir viendo cuales son los resultados y el rendimiento de cada paso para poder estudiar cada uno de ellos y determinar c\u00f3mo se podr\u00edan mejorar. Alguna de las mejoras es evidente, como, por ejemplo, componer el giro de 15\u00ba y la transformaci\u00f3n obtenida entre una instant\u00e1nea y la siguiente en una \u00fanica matriz de transformaci\u00f3n en lugar de aplicarlas por separado a todos los puntos.<\/p>\n<p>Tambi\u00e9n, queda por hacer un trabajo de ajustar el procedimiento y tratar de mejorar los resultados y el consumo de CPU. De hecho, si os fij\u00e1is con cuidado en la composici\u00f3n de la escena se nota un poquito que no es completamente ortogonal, parece que las paredes de los lados no quedan completamente paralelas. Tengo dos hip\u00f3tesis para tratar de explicar esto con las que habr\u00eda que trabajar:<\/p>\n<ul>\n<li>Que el propio sensor tenga un poquito de curvatura que s\u00f3lo se notar\u00eda si se van componiendo fotos para ampliar la escena. Ser\u00eda el mismo efecto que se observa en las fotos tomadas con un gran angular.<\/li>\n<li>Que al haber registrado todas las fotos de un cuadrante en la misma direcci\u00f3n se hayan acumulado errores que provocan este efecto. Para descartar esta hip\u00f3tesis se podr\u00eda probar registrando las fotos al tresbolillo de manera que los efectos de los errores se vayan contrarrestando en lugar se sumarse.<\/li>\n<\/ul>\n<p>En el siguiente post, y \u00faltimo de esta serie, me gustar\u00eda hacer alg\u00fan comentario sobre qu\u00e9 se podr\u00eda hacer a partir de aqu\u00ed con este trabajo o alguna reflexi\u00f3n sobre c\u00f3mo adaptar el hardware a un producto.<\/p>\n<\/body>","protected":false},"excerpt":{"rendered":"<p>Estaba yo pensando\u2026 \u00bfC\u00f3mo se compone el escenario completo? Nuestro problema, ahora, es componer la imagen de un escenario completo de 180\u00ba contenida en un \u00fanico fichero .pcd Como ya vimos en su momento, los puntos de una nube de &hellip; <a href=\"https:\/\/www.rtbasics.com\/WP_2\/archives\/231\">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,30,22,25],"class_list":["post-231","post","type-post","status-publish","format-standard","hentry","category-broadcasting","category-depth-sensors","category-sensors","tag-asus-xtion","tag-cloudcompare","tag-kinect","tag-pcl"],"jetpack_featured_media_url":"","jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p4vaHY-3J","_links":{"self":[{"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/posts\/231","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=231"}],"version-history":[{"count":6,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/posts\/231\/revisions"}],"predecessor-version":[{"id":240,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/posts\/231\/revisions\/240"}],"wp:attachment":[{"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/media?parent=231"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/categories?post=231"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rtbasics.com\/WP_2\/wp-json\/wp\/v2\/tags?post=231"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}