UNIVERSIDAD POLITÉCNICA DE CARTAGENA
Escuela Técnica Superior de Ingeniería Industrial
Programación de un sistema
empotrado para capturar y analizar datos de un láser de alta densidad
TRABAJO FIN DE ESTUDIO
GRADO EN INGENIERÍA EN ELECTRÓNICA INDUSTRIAL Y AUTOMÁTICA
Autor: Alejandro Egea Caballero Director: Diego Alonso Cáceres
Codirector: Pedro Javier Navarro Lorente
Cartagena, febrero de 2018
Autor Alejandro Egea Caballero E-mail del Autor [email protected] Director(es) Diego Alonso Cáceres E-mail del Director [email protected] Codirector(es) Pedro Javier Navarro Lorente E-mail del Codirector [email protected].
Título del TFE Programación de un sistema empotrado para capturar y analizar datos de un láser de alta densidad
Descriptores
Resumen
El presente TFE trata del diseño de un sistema de captura y posterior análisis de los datos procedentes del sensor VLP-16, de la empresa Velodyne Inc.
Para la realización de este TFE se cuenta con, además del sensor VLP-16, una placa Odroid C1. Los datos procedentes del sensor VLP-16 serán enviados mediante el protocolo de transmisión UDP. Por ello, en primer lugar, será necesario la realización del programa de captura e interpretación de los datos recibidos.
Una vez interpretados los datos, se realizará mediante la librería “PCL” (Point Cloud Library), la interpretación, visualización y reconstrucción de las nubes de puntos obtenidas.
Titulación Grado en Ingeniería en Electrónica Industrial y Automática Intensificación
Departamento Tecnologías de la Información y las Comunicaciones Fecha de Presentación Febrero - 2018
AGRADECIMIENTOS
Quiero dar las gracias a mis directores Diego Alonso y Pedro Javier Navarro por haberme dado la oportunidad de utilizar, para mi último trabajo del grado, una tecnología tan en auge como la empleada.
De estos cuatro años de grado, además de conocimiento y empleo, me llevo personas, que empezaron siendo compañeros, y han acabado siendo grandes amigos.
Dar las gracias a mis padres y a mi hermano por el ánimo en estos 4 largos años de carrera.
Índice
1. Introducción... 3
1.1 Introducción ... 3
1.2 Objetivos del trabajo fin de estudio. ... 6
1.3 Organización del proyecto ... 7
2. Estado de la técnica ... 8
2.1. Aplicaciones de las nubes de puntos ... 10
2.1.1. Industria del automóvil. ... 10
2.1.2. Industria. ... 13
2.1.3. Topografía. ... 16
2.1.4. Tecnología actual. ... 17
2.1.5 Programas para el tratamiento de nubes de puntos ... 22
2.1.6. Herramientas para la programación de nubes de puntos. ... 23
3. Descripción del código ... 27
3.1. Comunicación con VLP – 16. ... 27
3.1.1. POCO Libraries. ... 27
3.1.2. Instalación de POCO Libraries. ... 28
3.1.3. Uso de la librería “POCO” con VLP-16. ... 32
3.2. Tratamiento del paquete UDP ... 33
3.3. Librería PCL ... 37
3.3.1. Instalar librería PCL. ... 37
3.3.2. Uso librería PCL. ... 41
3.4. Interfaz de usuario ... 45
3.4.1. Instalación librería GTK. ... 45
3.4.2. Programación de la interfaz. ... 46
4. Tratamiento de nubes de puntos. ... 56
4.1. Reconstrucción de superficies. ... 56
4.2 Detección de movimiento. ... 61
4.3. Captura de grandes espacios. ... 65
4.4. Uso de Gprof. ... 66
5. Trabajos futuros ... 69
5.1. Modelado de grandes entornos. ... 69
5.2. Mejora de nubes de puntos. ... 71 6. Conclusiones ... 74 7. Bibliografía ... 75
Anexo I - Manual VLP - 16
Anexo II - Programa e interfaz gráfica Anexo III - Reconstrucción de superficies
1
CAPÍTULO 1
INTRODUCCIÓN
2
Capítulo 1 Objetivos del trabajo fin de estudio
3
1. Introducción.
1.1 Introducción
La revolución tecnológica en el mundo de la automoción está cada vez más en auge. Hoy en día, gran cantidad de empresas, como por ejemplo Google, están trabajando por hacer realidad la existencia de un coche completamente autónomo (ver figura 1.1). Para conseguir este reto, es necesario que el automóvil sea capaz de capturar y analizar datos en tiempo real del entorno por el que se desplaza.
Una de las posibles soluciones que ofrece actualmente el mercado; es el uso de la tecnología LIDAR (Laser Imaging Detection and Ranging). La tecnología LIDAR, se utiliza para medir la distancia y el ángulo entre un emisor láser y un objeto. Mediante esta distancia y ángulo es posible obtener una nube de puntos (con las tres coordenadas del sistema cartesiano) que permitan reconstruir todo el entorno.
Esta tecnología dota al vehículo de la información requerida para una circulación completamente autónoma.
Otra aspecto fundamental en estos vehículos, es la reducción del peso total. Una reducción de peso, implica una menor contaminación y una mayor autonomía. Para ello, este TFE pretende, mediante el uso de un computador de placa reducida, establecer la comunicación necesaria con un láser LIDAR, recibiendo e interpretando toda su información.
Figura 1.1. Coche autónomo de Google, Inc.
Capítulo 1 Objetivos del trabajo fin de estudio
4 Para el desarrollo del presente TFE, se ha utilizado un láser LIDAR VLP-16 (ver figura 1.2), de la empresa Velodyne, Inc. y un computador de placa simple, Odroid C1 (ver figura 1.3).
En términos generales, el sistema a desarrollar funcionará de la siguiente forma:
El láser VLP-16 enviará paquetes de datos a través de un cable Ethernet, hacía la placa Odroid C1.
El programa desarrollado recibirá los paquetes, enviados mediante el UDP, por el láser VLP-16.
Tratamiento y posterior visualización de los datos tomados.
Debido a la poca capacidad de computo de la placa que se va a utilizar, una interpretación de los datos más avanzada, reconocimiento de objetos o personas, requerirá el empleo de una placa de mayor potencia.
Figura 1.2. VLP – 16, láser LIDAR.
Capítulo 1 Objetivos del trabajo fin de estudio
5 Posteriormente, se utilizará un ordenador más potente para realizar el tratamiento y visualización de las nubes de puntos capturadas. Se utilizará la librería de C++ “Point Cloud Library” [1]. El empleo de esta librería permitirá, entre otras cosas:
Visualización de nubes de puntos.
Almacenamiento de nubes de puntos en formato .pcd.
Reconocimiento de objetos.
El presente trabajo se realizará utilizando la distribución de Ubuntu [2] del sistema operativo Linux.
Figura 1.3. Odroid C1.
Figura 1.4. Point Cloud Library.
Capítulo 1 Objetivos del trabajo fin de estudio
6 1.2 Objetivos del trabajo fin de estudio.
El presente trabajo fin de estudios pretende ser el punto de partida en el tratamiento de nubes de puntos en sistemas de visión artificial.
Los objetivos principales consisten en el desarrollo de un programa de comunicación que permita la recepción de los datos procedentes de VLP-16, así como un programa para el procesado e interpretación de los datos recibidos.
El resultado final esperado es un programa que permita realizar, mediante una interfaz gráfica, cada una de las funciones programadas.
Además de los resultados obtenidos, a nivel de programación, este proyecto permitirá la obtención de numerosa información relativa al empleo de librerías de comunicación y de tratamiento de nubes de puntos en sistemas operativos Linux, desde la instalación de la librería al empleo de las funciones más destacadas.
El primer objetivo a conseguir es establecer la comunicación con el láser VLP-16. La emisión de estos paquetes se realiza mediante el protocolo UDP, por ello, para conseguir este objetivo se tendrán que utilizar las bibliotecas de comunicación necesarias.
Una vez establecida la comunicación, se procederá a obtener los puntos enviados por el láser.
Estos puntos se obtendrán siguiendo el procedimiento explicado en el manual del VLP-16 (ver Anexo I).
Como parte de la verificación de la obtención de los puntos tomados, será necesario el uso de la libraría PCL para visualizar los puntos. Está verificación permitirá iniciarse en el uso de la librería (instalación y uso en Linux).
Una vez obtenidos los primeros objetivos, será necesario la programación de una interfaz gráfica para facilitar el uso de las funciones programadas.
La librería PCL se usará para la detección de movimiento de los objetos capturados en la nube de puntos, así como para la mejora de las nubes capturadas.
Al tratarse de algoritmos que manejan grandes cantidades de datos (en este caso puntos), como parte final del proyecto habrá que conseguir la optimización de los programas realizados. Se analizará el tiempo consumido por cada función y se intentará optimizarla.
Capítulo 1 Organización del proyecto
7 1.3 Organización del proyecto
La memoria del presente TFE está dividida en cuatro partes:
La parte inicial corresponderá a una documentación inicial de todo lo que se quiere utilizar para el desarrollo del proyecto. Habrá que aprender a utilizar el sistema operativo que se va a emplear, librerías de C++ de comunicación, de tratamiento de nubes de puntos, de creación de interfaces gráficas, así como a compilar programas que requieran del uso de librerías externas al lenguaje empleado.
Cuando se ha conseguido obtener el conocimiento necesario para poder llevar a cabo los objetivos del proyecto habrá que intentar desarrollar los objetivos planteados. A pesar de la documentación previa, durante el desarrollo del proyecto, surgirán problemas que tendrán que ser subsanados con nuevas búsquedas de documentación, o mediante tutorías con los directores del TFE.
Todo lo desarrollado, tendrá que ser paralelamente probado. Al tratarse de un proyecto a ser desarrollado para utilizarlo con un sensor en concreto, habrá que verificar, si los programas desarrollados funcionan correctamente.
Finalmente, al tratarse de un programa que requiere de gran cantidad de recursos para poder procesar toda la información recibida del láser (es capaz de enviar unos 300000 puntos por segundo), será necesario dedicar gran cantidad de tiempo para optimizar las distintas funciones empleadas. Habrá que utilizar procesos de ejecución paralela, así como funciones optimizadas disponibles en el lenguaje.
8
CAPÍTULO 2
ESTADO DE LA TÉCNICA
9
Capítulo 2 Industria
10
2. Estado de la técnica
Las nubes de puntos son conjuntos de coordenadas tridimensionales (x, y, z) tomadas, generalmente, desde un láser escáner LIDAR tridimensional. El láser mide la distancia a distintos objetos emitiendo luz láser pulsada y midiendo los pulsos reflejados con un sensor.
Se trata de un sensor activo, el cuál emite su propia energía para tomar la señal, a diferencia de otro tipo de sensores utilizados en la industria. El sensor consta de un receptor con detectores sensibles para medir la luz retrodispersada o reflejada.
Los láser LIDAR, utilizan luz ultravioleta, visible o infrarroja. Normalmente, la luz se refleja a través de la retrodispersión. Se usan diferentes tipos de dispersión para diferentes aplicaciones LIDAR, dispersión Rayleigh, dispersión Mie, dispersión Raman y fluorescencia.
2.1. Aplicaciones de las nubes de puntos
En este apartado se describen las principales actividades y sectores que están utilizando la tecnología láser LIDAR y qué tipo de aplicaciones desarrollan con ella.
2.1.1. Industria del automóvil.
Empresas como Tesla, Google o Ford están aportando fuertemente por el sector de la conducción autónoma. Tesla dota a sus vehículos de gran cantidad de sensores (en los Model S y Model X, un radar frontal, ocho cámaras de vídeo y doce sensores de ultrasonidos) para garantizar una visión completa del entorno. Además de sensores, se necesita una gran potencia de cómputo para procesar rápidamente la gran cantidad de información tomada.
Google, al contrario que Tesla, sólo utiliza sensores LIDAR (ver figura 2.1). El problema de los sensores utilizados por Tesla es que las cámaras no funcionan bien en condiciones de poca iluminación o muchos reflejos.
Organismos internacionales realizaron una clasificación de los distintos niveles de conducción autónoma, aunque inicialmente fueron varios los organismos que propusieron distintas clasificaciones, finalmente la que más repercusión tiene es la propuesta por SAE (Sociedad de Ingenieros de Automoción). La clasificación consta de 5 niveles [3]:
Nivel 1. El vehículo proporciona al conductor alguna característica que facilite la conducción, por ejemplo, mantener la velocidad. El conductor es siempre el que toma las decisiones en la conducción.
Nivel 2. Automatización parcial de la conducción. El vehículo puede realizar acciones de complejidad reducida como seguir a otro vehículo durante un periodo corto de tiempo, aparcar, etc.
Capítulo 2 Industria
11 Nivel 3. Automatización condicionada. El vehículo puede tomar ciertas decisiones para mantener la seguridad, pero el conductor debe de estar siempre atento.
Nivel 4. Alta automatización. Este nivel actualmente ha sido alcanzado por algunos vehículos como los de Tesla o Google. El conductor puede permanecer bastante tiempo sin tomar ninguna decisión, sobre todo cuando se circule por carreteras en buenas condiciones y con buena climatología. El conductor tiene que permanecer siempre alerta.
Nivel 5. Automatización total. El automóvil tiene el control total de la circulación.
Actualmente, los coches autónomos tienen pendiente resolver los siguientes problemas (que son los principales motivos de los accidentes que están sucediendo):
Los sistemas de conducción autónoma utilizan como referencia en la conducción los edificios, arboles, etc., que se encuentran en los alrededores, el problema surge en los puentes, ya que el vehículo pierde las referencias y tiende a desorientarse.
Las marcas de los carriles en la carretera son usadas por los vehículos para seguir una correcta trayectoria. En carreteras donde estas marcas se encuentran en mal estado y apenas se ven hacen que el vehículo no siga correctamente por el carril adecuado.
Las condiciones climatológicas adversas hacen que los sensores no sean capaces de detectar con precisión los obstáculos cercanos.
Las obras en la vía pública, ya que pueden provocar desviaciones en el tráfico (ocupar parte del arcén para superar el obstáculo) o simplemente cortes.
Conducción con vehículos conducidos por humanos. Los humanos pueden realizar trayectorias impredecibles, cambios bruscos del sentido de circulación, acelerar cuando se va a poner el semáforo en rojo, etc., que dificulten la circulación de los vehículos autónomos.
Pérdida de señal GPS al atravesar las ciudades. Las ciudades están llenas de altos edificios, emisores de red Wifi, etc., que ocasionan pérdidas o errores de la señal GPS.
Estas pérdidas ocasionan que el automóvil no sea capaz de seguir la trayectoria correcta.
En 2016, un Tesla Model S chocó contra un camión en Florida, el sistema de conducción autónoma no detectó al camión y el conductor estaba distraído viendo una película. Se llegó a la conclusión de que el sistema de conducción autónoma no falló, si no que no estaba programado para evitar un obstáculo que invada el carril lateralmente, tan solo recurre al frenado si detecta un obstáculo delante del coche.
En Enero de 2018, un Tesla chocó contra un camión de bomberos en Estados Unidos cuando el vehículo circulaba en modo de conducción autónoma [4].
Capítulo 2 Industria
12 Últimamente, están apareciendo cada vez más vehículos que disponen de láseres LIDAR para realizar trayectorias de forma autónoma. Compañías como GE están realizando fuertes inversiones en su desarrollo tecnológico, recientemente con la compra de Strobe.
La tecnología LIDAR empezó a utilizarse en coches autónomos debido a las posibilidades que tenían, capaces de generar enormes mapas en 3D y detectar señales u objetos a más de 500 metros de distancia. Con el paso de los años, la tecnología LIDAR tiene cada vez más resolución y permite trabajar en rangos cada vez más amplios. Permitiendo no solo mapear espacios tridimensionales, sino permitiendo la detección de objetos u obstáculos, como personas y otros vehículos.
Uno de los principales problemas de esta tecnología es su precio y su escasez, ya que actualmente no se fabrican a gran escala, y empresas automovilísticas como Tesla han tenido que recurrir a otras alternativas para implantarlas en sus grandes producciones de vehículos.
Actualmente, directores de Ingeniería de Google o Uber afirman que solo se podrá llegar al nivel 5 de conducción autónoma utilizando la tecnología LIDAR.
Debido a las grandes inversiones que se están haciendo sobre la tecnología LIDAR (Ford y Baidu invirtieron 140 millones de € a la empresa Velodyne LiDar [5]), se espera conseguir reducir el coste de los LIDAR a 50€ (según informaciones publicadas por la empresa Velodyne en Diciembre de 2017). En 2018, Velodyne empezará a comercializar LIDAR de aproximadamente 230€ fabricados en su nueva megafactoría en EEUU.
Figura 2.1 Vehículo de Google.
Capítulo 2 Industria
13 2.1.2. Industria.
La tecnología LIDAR, actualmente está empezando a ser usada, para el modelado tridimensional de plantas industriales.
Tradicionalmente, las plantas industriales eran modelizadas mediante herramientas como PDS (Plant Design System) [6]. Esta herramienta, aunque inicialmente supuso un gran avance, con el paso de los años fue perdiendo su inteligencia y eficiencia con respecto a los nuevos avances del mercado.
Las empresas industriales modelizaban sus plantas con la idea de disminuir los costes de los nuevos proyectos realizados. Estos costes estaban derivados de las numerosas visitas que tenía que hacer el ingeniero a planta para tomar todos los datos necesarios. Con la planta modelizada en 3D esto no pasaba, el ingeniero podía disponer de cualquier distancia, longitud de tuberías, diámetros de válvulas, etc., sin tener que desplazarse de su oficina.
Aprovechando la mejora de la tecnología actual, en concreto la tecnología LIDAR, actualmente son cada vez más las plantas industriales que están empezando a modelizar sus plantas con este tecnología. Se consigue así un aumento de la velocidad de finalización del proyecto, y consecuentemente una reducción de costes.
Un ejemplo de los buenos resultados que se obtienen con el uso de las actuales centrales de escaneo, lo podemos ver en las siguientes imágenes:
Figura 2.2 Escaneo de instalación industrial con Leica.
Capítulo 2 Industria
14 El uso de estas estaciones de escaneo se resume en los siguientes puntos:
Colocación de las estación de escaneo en diferentes puntos.
Colocación de dianas guía (2 o 3) visibles desde cualquier punto donde se coloque la estación de escaneo.
Fotografiado de la instalación a escanear, para superponer la imagen a la nube de puntos tomada.
Estos datos, utilizados en herramientas potentes de la empresa Autodesk, Inc. permiten obtener resultados muy buenos (ver figura 2.3). Esto se aprecia en la siguiente imagen, la cual corresponde a la apertura de la nube de puntos tomada con una estación Leica P40, después de haber guardado la nube de puntos generados con el programa de Leica “Cyclone” [7].
En la imagen anterior, se puede apreciar la calidad de los resultados obtenidos, los cuales demuestran la importancia que tomará en el futuro los láser LIDAR.
Otra aplicación de las nubes de puntos, es en el estudio de instalaciones o equipos industriales.
La empresa SI3 Ingeniería realiza estudios API 653 y 579 mediante el mapeo del interior de tanques y depósitos, ofreciendo datos como los siguientes:
Coordenadas simples de los objetos.
Figura 2.3 Visualización de nube de puntos en Plant3D.
Capítulo 2 Industria
15 Información de asentamiento.
Redondez del tanque o depósito.
Medición del volumen real.
Calibración volumétrica.
Proporcionan, además, planos tridimensionales donde se muestran las grietas o posibles deformaciones que hayan podido surgir durante el funcionamiento.
Otras empresas, realizan estudios de verticalidad de chimeneas industriales u otras instalaciones de grandes alturas. Estos estudios son realizados con drones y escáneres LIDAR.
Rodeando el objeto en cuestión desde su base hasta su cima, se consigue la modelización exacta del objeto. Pudiendo obtener características del objeto fundamentales, para realizar estudios sobre, por ejemplo, la peligrosidad de ruptura ante fuertes rachas de viento.
Capítulo 2 Topografía
16 2.1.3. Topografía.
Mediante estaciones de trabajo LIDAR se pueden hacer con facilidad levantamientos topográficos del lugar.
Algunas de las aplicaciones topográficas a destacar son:
- Escaneo de parcelas forestales para hacer inventario del número de árboles por parcela, calcular la altura de los árboles, ancho de la corona y diámetro de la copa.
- Generación de informes del estado de las vías ferroviarias.
- Optimización de los sistemas solares fotovoltaicos en ciudades, mediante la selección de los tejados apropiados.
Capítulo 2 Tecnología actual
17 2.1.4. Tecnología actual.
Velodyne LiDAR es una compañía especializada en el desarrollo de la tecnología LIDAR. Está centrada actualmente en el desarrollo de aplicaciones en vehículos autónomos, sistemas de seguridad de vehículos, mapeo terrestre y aéreo 3D, y seguridad.
A finales del año 2016, Velodyne informó sobre el desarrollo de sensores LIDAR de estado sólido, más compactos y económicos, así como menor probabilidad de fallo al disponer de menos piezas móviles.
Los diferentes modelos con los que cuenta actualmente la compañía son:
VLP-32C. El más reciente de los láser fabricados por Velodyne, sus características principales son:
32 canales
20 metros de alcance
Amplitud vertical de 40° (-15° a 25°)
Amplitud horizontal de 360°
600000 puntos por segundo a través de conectores RJ45/M12
VLP-16. Láser utilizado en el presente TFE, sus características principales son:
± 15° de rango vertical
100 m de rango
Figura 2.4 VLP-32C.
Capítulo 2 Tecnología actual
18
300000 puntos por segundo
16 canales
HDL-32E. Es el segundo láser que mayor cantidad de puntos ofrece por segundo:
700000 puntos por segundo
De 80 a 100 metros de distancia
+10° a -30° de amplitud vertical
± 2 cm de precisión
Figura 2.5 VLP-16.
Figura 2.6 VLP-32E.
Capítulo 2 Tecnología actual
19 HDL-64E. EL láser más potente de Velodyne, destaca por:
64 canales
120 m de distancia
26,9° de amplitud vertical
0,08° de resolución angular
Menos de 2 cm de precisión
2,2 millones de puntos por segundo
Resolución de aproximadamente 0,4° en la vertical
Otro de los fabricantes destacados es Leica Geosystems. Posee numerosas estaciones de trabajo de gran precisión enfocadas principalmente para aplicaciones de topografía y mapeados 3D en plantas industriales. Uno de los mejores ejemplos para ver cómo funciona esta tecnología es el mostrado en el apartado 2.2. Sus principales estaciones de escaneo 3D son:
Leica ScanStation P50. Es la estación más potente y de mayor alcance de Leica.
Algunas de sus principales características son:
Compensador de doble eje de nivel topográfico.
Alcance de hasta 1 kilómetro.
Velocidad de escaneo de hasta 1 millón de puntos por segundo.
Datos 3D e imágenes HDR (alto rango dinámico) de gran calidad.
Figura 2.7 VLP-64E.
Capítulo 2 Tecnología actual
20 Leica ScanStation P40. La diferencia principal con el modelo P50 es que tiene menor alcance, aunque mantiene la calidad y el número de puntos por segundo.
Características prinpales:
- Alcance de 120 metros.
- Resolución ajustable desde 0,8 mm a 50 mm a 10 metros.
Leica ScanStation P16. Es la estación más barata de Leica, utilizada principalmente por las empresas que se inician en este sector. Características principales:
- Alcance de 80 metros.
- Resolución ajustable desde 1,6 mm a 50 mm.
Figura 2.8. Leica ScanStation P50.
Figura 2.9. Leica ScanStation P40.
Capítulo 2 Tecnología actual
21 Figura 2.10. Leica ScanStation P16.
Capítulo 2 Programas para el tratamiento de nubes de puntos
22 2.1.5 Programas para el tratamiento de nubes de puntos
Una vez obtenida la nube de puntos mediante alguno de los escáner láser 3D anteriores u otras tecnologías para crear representaciones tridimensionales de objetos o estructuras, existen multitud de programas para tratarlos.
El programa Autocad, de la empresa Autodesk, Inc. permite realizar, sobre una nube de puntos ya tomada, las siguientes acciones:
Segmentación de la nube de puntos, identificando los grupos de puntos que pueden representar algún tipo de superficie, plana o cilíndrica (ver figura 2.11).
Enlace de una nube de puntos a un dibujo.
Visualización de la nube de puntos, modificando características como la densidad de los puntos y el color.
Recortes de nubes de puntos.
Extraer geometría 2D a partir la nube de puntos seleccionada.
Cualquier tipo de desplazamiento en la nube de puntos seleccionada.
En general, la gran mayoría de programas de Autodesk, Inc. tienen un gran soporte en el tratamiento de nubes de puntos, además de Autocad otros programas como Revit, Plant3D también ofrecen muy buenos resultados. Otra alternativa a Autodesk, Inc. la ofrece Bently, Inc. con los programas Microstation y Descartes. Ambos ofrecen características similares a las ofrecidas por los programas de Autodesk.
Figura 2.11 Creación de geometrías a partir de nubes de puntos
Capítulo 2 Programas para el tratamiento de nubes de puntos
23 2.1.6. Herramientas para la programación de nubes de puntos.
PCL, es un proyecto a gran escala para el procesado de nubes de puntos 2D y 3D. La librería contiene numerosos algoritmos (ver figura 2.9) de filtrado, estimación de características, reconstrucción de superficies, registro, ajuste de modelo y segmentación. Estos algoritmos son usados para el filtrado de valores atípicos, unión de nubes de puntos, segmentar partes relevantes de una escena o extraer puntos clave.
Se trata de una multiplataforma que ha sido compilada e implementada en Linux, Windows, MacOS, iOS y Android. PCL se divide en una serie de bibliotecas de códigos más pequeñas, que se pueden compilar por separado. Esto ofrece la ventaja de poder utilizar la librería en plataformas o hardware con limitaciones computacionales.
Multitud de empresas están desarrollando proyectos con PCL. Cabe destacar:
Figura 2.9 Conjunto de librerías que componen PCL.
Capítulo 2 Programas para el tratamiento de nubes de puntos
24 Velodyne
Toyota
Leica Geosystems Nvidia
Intel Etc.
En España, tan solo tres universidades aparecen listadas entre las que desarrollan proyectos en esta librería:
Universidad Carlos III de Madrid Universidad de Málaga
Universidad de Extremadura
Otro potente programa para la programación de nubes de puntos es Matlab. Las utilidades del toolbox de Matlab para las nubes de puntos, son las siguientes:
Operaciones de lectura y escritura de nubes de puntos.
Transformar y registrar nubes de puntos (ver figura 2.10).
Adaptación de nubes de puntos a figuras geométricas.
Muestreo, eliminación de ruido, fusión y estimación normal de nubes de puntos 3D.
Además de PCL, la librería libLAS de C/C++ también se puede utilizar para leer y escribir nubes de puntos [8]. El desarrollo de esta librería empezó en 2007, respaldado por la Universidad de Iowa. Se utiliza, especialmente, para almacenar e intercambiar datos procedentes de LIDAR. Todas las funciones que recoge la librería se pueden encontrar explicadas en el siguiente enlace:
https://www.liblas.org/doxygen/annotated.html
Actualmente la librería no cuenta con mucho desarrollo, siendo su última versión estable (a fecha de la escritura del presente TFE) la publicada en agosto del 2016.
Figura 2.10 Rotación de nube de puntos.
25
CAPÍTULO 3
DESCRIPCIÓN DEL CÓDIGO
26
Capítulo 3 Comunicación con VLP-16
27
3. Descripción del código
En este apartado se describen las librerías utilizadas y el software desarrollado durante el TFE. Está estructurado en 4 apartados.
3.1. Comunicación con VLP – 16.
VLP-16 envía la información capturada mediante el protocolo UDP (User Datagram Protocol) [9]. Este protocolo permite el envío de paquetes de datos a red sin haber establecido previamente una conexión, ya que en la cabecera del paquete incorpora toda la información necesaria. No hay confirmación de la entrega de los paquetes, pero su gran ventaja es la poca carga a la que somete la red (debido a la simplicidad de su cabecera).
Para la recepción y procesado de los paquetes enviados por VLP-16, en primer lugar, hay que establecer la comunicación. Para ello, se han realizado pruebas con la librería POCO [10] y con las librerías nativas del sistema. Finalmente se ha usado “POCO Libraries” por su mayor simplicidad.
Con la idea de ofrecer al lector de este trabajo fin de estudios la mayor cantidad de herramientas, se explicaran los dos caminos estudiados.
3.1.1. POCO Libraries.
POCO Library [11] (ver figura 3.1) es un conjunto de librarías en C++ de código abierto útiles para la creación de programas de comunicación ejecutados desde servidores, dispositivos móviles, ordenadores de escritorio y sistemas integrados.
Figura 3.1. Logo POCO Libraries.
Capítulo 3 Comunicación con VLP-16
28 3.1.2. Instalación de POCO Libraries.
Para poder instalar las librerías de “POCO”, necesarias para establecer la comunicación con el láser VLP-16, hay que seguir los pasos que se detallan a continuación.
En primer lugar, accediendo a la web de la librería (https://pocoproject.org) se descargan todos los archivos necesarios para su uso. El archivo a descargar será en formato comprimido, y se ubicará en, por ejemplo, el directorio de descargas del ordenador. Este archivo comprimido ha sido descargado en el área de descargas de la web de POCO Libraries (“https://pocoproject.org/download/).
Los pasos siguientes serán realizados mediante la consola de Ubuntu. Situándose dentro de la carpeta en la que se ha descomprimido el archivo descargado (ver figura 3.2), hay que ejecutar los siguientes comandos para compilar la librería:
./configure make -s
Posteriormente, hay que crear una carpeta donde se alojaran todos los ficheros de la librería.
Para ello se escribe en la consola lo siguiente:
sudo mkdir /usr/include/Poco
Finalmente se copian los paquetes de librería que se necesiten:
sudo cp -r Foundation/include/Poco/. /usr/include/Poco/
sudo cp -r Util/include/Poco/. /usr/include/Poco/
sudo cp -r JSON/include/Poco/. /usr/include/Poco/
sudo cp -r Net/include/Poco/. /usr/include/Poco/
Este último, es la librería necesaria para establecer la comunicación mediante UDP.
Figura 3.2. Ubicación del archivo descomprimido (POCO).
Capítulo 3 Comunicación con VLP-16
29 Para verificar que la librería se ha instalado correctamente, se ha desarrollado un programa que envía paquetes de un ordenador a otro, conectados mediante un cable Ethernet. Se utilizará un portátil y una placa Odroid en la que está instalado un sistema operativo Linux, concretamente Ubuntu.
En el portátil se utilizará la librería de “Poco” para la recepción de los paquetes, sin embargo, en la placa Odroid se utilizarán funciones nativas de C++.
Aquí se muestra una prueba de lo obtenido:
La placa Odroid envía un mensaje con la dirección IP 192.168.1.199 y el puerto 2368:
Y el portátil lo recibe:
Ahora se explicará el código utilizado en ambos casos.
Utilizando la librería “POCO” es necesario incluir “DatagramSocket.h” y “SocketAddress.h”
(ver figura 3.5). La primera permite utilizar las funciones de envío y recepción de paquetes, y la segunda elegir la dirección IP y el puerto utilizados en el envío y recepción de paquetes.
Figura 3.3. Envío de paquetes UDP en Odroid.
Figura 3.4. Recepción de paquetes UDP en portátil.
Capítulo 3 Comunicación con VLP-16
30 Mediante la función “receiveFrom()” se recibe el paquete que nos han enviado, pasando como parámetros un vector, su tamaño y un objeto “SocketAddress”. Un ejemplo del uso de esta función se puede ver en la línea 12 de la figura anterior.
Para el envío de los datos, se han utilizado librerías propias de C++ (como ya se ha comentado).
Los socket se crean mediante la función “socket()”. Esta función devuelve el identificador del socket de tipo entero. En caso de que se haya producido algún error durante la creación del socket, la función devuelve el valor de -1.
El formato de la función es el siguiente:
comunicación = socket (int dominio, int tipo, int protocolo);
comunicación: es el identificador del socket, se utilizará en las funciones de recepción y envío de datos.
dominio: según el tipo de conexión. Para el ejemplo realizado será “AF_INET”.
tipo: según el socket a utilizar. Como se va a hacer una comunicación UDP, se utilizará
“SOCK_DGRAM”.
protocolo: con el valor 0, la función utiliza el protocolo más apropiado.
Para el uso de la función anterior, es necesario incluir las librerías “types.h” y “socket.h”.
Se utiliza un objeto tipo “sockaddr_in” como estructura del socket. Este objeto presenta los siguientes atributos:
Figura 3.5. Ejemplo de uso de POCO Libraries.
Capítulo 3 Comunicación con VLP-16
31 sin_family: tomará siempre el valor AF_INET.
sin_port: representa el número de puerto. Será necesario utilizar una función para convertirlo en la ordenación de bytes de la red.
sin_addr: representa la dirección IP. También será necesario el uso de una función para indicarla.
Para poder convertir una dirección IP en formato texto a un “unsigned long” con la ordenación de bytes adecuado, se utiliza la función “inet_addr()”. También será necesario utilizar la función “htons()” para convertir un “short int” de formato máquina al formato de red. Esto es necesario para declarar el atributo “sin_port” como el número de puerto a utilizar en la comunicación.
Para el uso de las funciones de conversión a formato de red, será necesario incluir la librería
“netinet/in.h”.
El tratarse de una comunicación UDP, sólo será necesario el uso de dos funciones para realizar la transmisión. Estas funciones son, “sendto” (para el envío de datos) y “recvfrom” (para la recepción de los datos).
Para el ejemplo, como solo se requería hacer un envío de datos, solo se utilizó la función
“sendto()”. El formato es el siguiente:
int sendto (int comunicación, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
comunicacion: se trata, como ya se ha visto, del identificador del socket para el envío de datos.
msg: puntero a los datos a enviar.
len: longitud en bytes de los datos a enviar.
flags: por defecto, 0.
to: puntero a una estructura “sockaddr_in” que contiene la dirección IP y número de puerto destino.
tolen: tamaño de la estructura “sockaddr” definida en el punto anterior, mediante la función “sizeof()”.
La estructura de la función “recvfrom()” es similar a la de la función “sendto()”, simplemente hay que tener en cuenta que el lugar que ocupa, en la función “sendto()”, “msg” será el puntero al vector donde se almacenarán los datos recibidos.
Finalmente, al terminar la comunicación, hay que utilizar una de las dos funciones de cierre,
“close()” o “shutdown()”.
Capítulo 3 Comunicación con VLP-16
32 La estructura de la función “close ()” es la siguiente:
close (comunicacion);
Después de utilizar esta función, quedan deshabilitadas el envío y la recepción de datos.
La estructura de la función “shutdown ()” es la siguiente:
shutdown(comunicacion, int how);
Esta función es un poco más completa que la anterior, ya que permite deshabilitar o el envío, o la recepción o ambas, en función del valor que tome el parámetro “how”:
0: deshabilita la recepción.
1: deshabilita el envío.
2: deshabilita la recepción y el envío.
3.1.3. Uso de la librería “POCO” con VLP-16.
Para establecer la comunicación será necesario crear dos objetos, un objeto “SocketAddress“
que contenga la dirección IP y el puerto del VLP-16, y un objeto “DatagramSocket” para recibir los datos del VLP-16.
Poco::Net::SocketAddress Velodyne(Poco::Net::IPAddress(),NumeroPuerto);
Poco::Net::DatagramSocket GetData(Velodyne);
Cada paquete recibido de VLP-16 consta de 1206 bytes, divididos en 12 bloques de datos, los cuales comienzan después de dos bytes consecutivos que indiquen “FF” y “EE”.
Por ello, se usa un vector de tamaño 1206 y del tipo uint8_t para almacenar los datos recibidos.
uint8_t buffer[1206];
La función utilizada para recibir los datos es “receiveFrom()”, con los siguientes parámetros:
n = GetData.receiveFrom(buffer, sizeof(buffer)-1,sender);
Para indicar el final del vector se escribe, posteriormente, buffer[n] = ‘\0’.
Capítulo 3 Tratamiento del paquete UDP
33 3.2. Tratamiento del paquete UDP
Como ya se ha comentado, VLP-16 emite paquetes de 1206 bytes. En cada paquete encontramos datos de distancia, tiempo, ángulo, etc., de los puntos tomados por el láser. La estructura del paquete se puede observar en la siguiente imagen:
Cada bloque de datos comienza con los bytes “FF” y “EE”, esto permite distinguir todos los bloques de un paquete. Seguido de estos dos bytes de inicio, se encuentra el valor azimut en los siguientes dos bytes (ángulo de rotación). Este ángulo corresponde con el ángulo del primer disparo del láser en cada colección de 16 disparos de láser.
Como en cada bloque de datos hay dos conjuntos de 16 disparos, y el ángulo disponible corresponde al disparo del primer conjunto, es necesario interpolar entre el ángulo del bloque actual y del bloque posterior, para obtener una mejor aproximación del ángulo que había en el disparo del segundo conjunto de disparos.
Para calcular en ángulo interpolado, se utiliza la siguiente función:
Figura 3.6. Estructura paquete VLP-16.
Capítulo 3 Tratamiento del paquete UDP
34
float AnguloInterpolado(float anterior, float posterior) {
float AngInterpolado;
if(anterior>posterior) {
posterior = posterior + 360.0;
}
AngInterpolado = anterior + ((posterior-anterior)/2);
if(AngInterpolado>360) {
AngInterpolado = AngInterpolado - 360.0;
}
return AngInterpolado;
}
Con esta función se asegura el obtener una resta positiva y un ángulo interpolado comprendido entre 0 y 360 grados.
Para obtener el ángulo de los dos bytes recibidos hay que seguir unos determinados pasos, por ejemplo, si recibe el ‘0x12’ y el ‘0x33’ tendremos que:
Dar la vuelta a los bytes: ‘0x33’ y ‘0x12’.
Convertir los dos bytes en un entero sin signo: ‘0x3312’.
Convertir a decimal: 13074.
Dividir por 100: 130,74º.
Los pasos anteriores se realizan con las siguientes funciones:
uint16_t u8int_a_Ruint16(uint8_t primero, uint8_t segundo) {
valor = (segundo << 8) + primero;
}
float ObtenerAnguloG(uint16_t) {
return angulo/100;
}
Para el obtener las distancias, el procedimiento es similar, cambiando la última función anterior por:
Capítulo 3 Tratamiento del paquete UDP
35
float ObtenerMetros(uint16_t resultado)
{
float ModMetros;
ModMetros = (resultado*2.00)/(1000.0);
return ModMetros;
}
Una vez recibidos los datos, será necesario obtener las coordenadas de todos los puntos tomados. Para guardar cada uno de los puntos (con las 3 coordenadas cartesianas) se utilizará una variable pcl::PointXYZI.
Cada uno de los 16 láser tiene una inclinación distinta, identificada en la figura 3.7:
Figura 3.7. Ángulo por Laser ID.
Capítulo 3 Tratamiento del paquete UDP
36 Por ello, habrá que tener en cuenta en qué posición está el punto en cada uno de los paquetes.
Las ecuaciones necesarias para obtener las coordenadas, sacadas del manual de VLP-16, son:
x = r · cos(ω) · sin(θ) y = r · cos(ω) · cos(θ) z = r · sin(ω)
Siendo r la distancia obtenida del paquete de datos (para cada punto), ω la inclinación de cada láser (según la tabla anterior) y θ ángulo de rotación (disponible al inicio de cada bloque de datos).
Para saber la orientación de los puntos, hay que tener en cuenta que el ángulo 0º corresponde con la parte contraria al cable de conexión:
Figura 3.8. Origen de coordenadas VLP-16.
Capítulo 3 Librería PCL
37 3.3. Librería PCL
Biblioteca de código abierto empleada para el desarrollo de algoritmos de tratamiento de nubes de puntos. Está formada por librerías modulares que pueden utilizarse independientemente. Su desarrollo comenzó en marzo de 2010, y en el momento de realizar este TFE, la última versión estable es la publicada en agosto de 2017.
3.3.1. Instalar librería PCL.
Toda la documentación relacionada con “Point Clouds Library” se puede encontrar en su página web http://pointclouds.org/.
En el apartado de descargas de la web, aparecen diferentes tutoriales para facilitar a los usuarios la instalación de la librería. Como para este caso, la instalación es para el sistema operativo Ubuntu, hay que seguir los pasos que se observan en la siguiente imagen:
Con los pasos anteriores no fue posible la instalación de la librería, por ello se recurrió a buscar información sobre como instalar la librería en Internet [12].
En base a la información buscada, finalmente la instalación realizada fue la siguiente:
En primer lugar, y debido a que “Point Clouds Library” (en adelante PCL) requiere la instalación previa de diferentes librerías de las que hace uso, hay que instalar por consola cada uno de los prerrequisitos de PCL:
sudo apt-get update
sudo apt-get install git build-essential linux-libc-dev sudo apt-get install cmake cmake-gui
sudo apt-get install libusb-1.0-0 dev libusb-dev libudev-dev Figura 3.9. Instalación PCL Library.
Capítulo 3 Librería PCL
38 sudo apt-get install mpi-default-dev openmpi-bin openmpi-common
sudo apt-get install libflann1.8 libflann-dev sudo apt-get install libeigen3-dev
sudo apt-get install libboost-all-dev
sudo apt-get install libvtk5.10-qt4 libvtk5.10 libvtk5-dev sudo apt-get install libqhull* libgtest-dev
sudo apt-get install freeglut3-dev pkg-config sudo apt-get install libxmu-dev libxi-dev sudo apt-get install mono-complete
sudo apt-get install qt-sdk openjdk-8-jdk openjdk-8-jre
Una vez instalados los prerrequisitos, es el momento de instalar PCL, siguiendo las instrucciones de la imagen anterior (Figura 3.9.).
Para verificar que la instalación de la librería PCL ha sido correcta, se puede utilizar uno de los tutoriales de visualización que aparecen en la web de la librería. El ejemplo utilizado es el siguiente:
http://www.pointclouds.org/documentation/tutorials/pcl_visualizer.php#pcl-visualizer.
Para poder ejecutar el programa los pasos a seguir son:
Copiar el programa escrito en la web en un documento vacío.
Ponerle de extensión .cpp al documento anterior.
Escribir el CMakeLists.txt con el siguiente contenido (sacado de la web):
Figura 3.10. Uso de CMake para usar PCL.
Capítulo 3 Librería PCL
39 Para que funcione correctamente, la carpeta donde esté alojado el “fichero.cpp” se deberá llamar pcl_visualizer_viewports (tal y como se ve en la tercera línea de la Figura 3.10), y el fichero se tendrá que llamar plc_visualizer_demo.cpp (véase línea 11).
Utilizando el programa CMake (instalado previamente, ya que era uno de los prerrequisitos de la librería PCL) ejecutamos el fichero “CMakeLists.txt”.
Esto genera en el directorio el archivo “Makefile”. Realizando un make a dicho archivo se genera el ejecutable del programa:
Figura 3.11. Ubicación CMakeLists.
Figura 3.12. Empleo de CMake con PCL.
Figura 3.13. Creación del ejecutable con CMake.
Capítulo 3 Librería PCL
40 Obtenido el ejecutable, ya se puede hacer uso del programa (ver figura 3.14 y 3.15).
La opción seleccionada provoca la ejecución del siguiente programa:
Figura 3.14. Ejecutable del ejemplo de PCL.
Figura 3.15. Ejemplo PCL.
Capítulo 3 Librería PCL
41 3.3.2. Uso librería PCL.
Los puntos son guardados, una vez ha sido tratado el paquete recibido, en un nube de puntos tipo pcl::PointXYZRGB.
Una de las funcionalidades que ofrece la interfaz creada (se mostrará en un apartado posterior), es la posibilidad de visualizar archivos de nubes de puntos en formado .pcd.
Para ello, es necesario, en primer lugar, crear una variable “PointCloud” donde almacenar posteriormente la nube de puntos leída:
pcl::PointCloud<pcl::PointXYZRGB>::Ptr nube (new pcl::PointCloud<pcl::PointXYZRGB>);
Para leer el archivo “.pcd” y guardarlo en la variable creada, es necesario usar la siguiente función:
pcl::io::loadPCDFile<pcl::PointXYZRGB> (filename,*nube);
filename. Variable tipo string que contiene la ruta del archivo .pcd.
nube. Variable tipo “PointCloud” donde almacenar el archivo leído.
Figura 3.16. 3D Viewer de PCL.
Capítulo 3 Librería PCL
42 Previamente a la lectura del archivo, se comprueba que la extensión del archivo a leer sea .pcd.
Además, la función anterior devuelve el valor de -1 cuando se produce un error en la lectura, por ello se ha usado de la siguiente forma:
if(pcl::io::loadPCDFile<pcl::PointXYZRGB> (filename, *nube) == -1) {
PCL_ERROR("No se ha podido leer el archivo .pcd");
}
Una vez leído el archivo, se crea un objeto visualizador, asignándole las características que se consideren, y se le añade la nube de puntos leída:
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer;
viewer = simpleVis(nube);
viewer->setBackgroundColor(0,0,0);
viewer->addPointCloud<pcl::PointXYZRGB> (nube, "Nombre");
Finalmente, para frenar el consumo de recursos por parte del programa, se hace dormir al hilo de ejecución del visualizador un determinado tiempo:
while(!viewer.wasStopped()) {
viewer.spinOnce(100);
boost::this_thread::sleep(boost::posix_time::microseconds(5000));
}
Para poder visualizar la nube en tiempo real, visualizando las variaciones de puntos que sufre la nube debidas a las variaciones ocurridas en el entorno, es necesario actualizar la nube de puntos cada un cierto tiempo. Por ello, las nubes visualizadas serán correspondientes a los puntos capturados en los últimos 0,1 segundos.
En los paquetes recibidos de VLP-16, en las posiciones 1200 a 1203, encontramos el tiempo en el que se han capturado los puntos. Se trata de un tiempo relativo, tomado desde el momento en el que el láser es arrancado. Por ello, se utiliza una función que obtiene el tiempo de todos los paquetes que recibe:
Capítulo 3 Librería PCL
43
void RecibirPaquete(uint32_t *tiempoPaquete) {
int n;
n = GetData.receiveFrom(buffer, sizeof(buffer)-1, sender);
buffer[n] = '\0';
*tiempoPaquete = ObtenerTiempo(u8int_a_Ruint16(buffer[1200], buffer[1201]), u8int_a_Ruint16(buffer[1202], buffer[1203]));
}
El tiempo se calcula siguiente las indicaciones recogidas en el manual del láser VLP-16. Dicho manual está recogido en el anexo I de este TFE.
Para la captura de puntos, necesaria para su posterior visualización, se ha creado la función NubeIntervalo. La función NubeIntervalo nos proporciona una nube de puntos capturada entre el tiempo de llamada de la función y el intervalo de tiempo indicado posterior. Está función podrá ser llamada por cualquier otra función del programa, ya sea para visualizar, guardar o comparar ciertos conjuntos de nubes de puntos.
void NubeIntervalo(pcl::PointCloud<pcl::PointXYZRGB> &Nube, uint32_t intervalo)
{
uint32_t TiempoIni, TiempoAct;
RecibirPaquete(&TiempoIni);
TiempoAct = TiempoIni;
while((TiempoAct-TiempoIni)<intervalo) {
TratamientoPaquete(buffer, Nube);
RecibirPaquete(&TiempoAct);
} }
En las siguientes imágenes, se mostrará tanto la interfaz desarrollada, como una prueba del uso de ella para la visualización de una nube de puntos.
Capítulo 3 Librería PCL
44 Presionando, por ejemplo, el botón de Abrir visualizador podemos observar, en tiempo real, los puntos tomados por el láser.
Figura 3.17. Interfaz del programa.
Figura 3.18. Visualización.
Capítulo 3 Interfaz de usuario
45 3.4. Interfaz de usuario
Para utilizar el programa desarrollado con mayor facilidad, se ha hecho uso de la librería GTK [13]. La librería GTK ha permitido realizar una interfaz gráfica de usuario donde poder ejecutar las funciones programadas. Así el programa dispondrá de una interfaz (ver figura 3.17) donde poder seleccionar y rellenar las opciones necesarias.
3.4.1. Instalación librería GTK.
En primer lugar, será necesario instalar la librería en el ordenador (ver figura 3.20). Para ello, escribimos lo siguiente en la consola de Ubuntu:
sudo apt-get install libgtk-3-dev
Para hacer uso de las librería su usará el programa CMake [14].
CMake es una herramienta desarrollada para construir y empaquetar software. La construcción empieza con la creación de un fichero “CMakeLists.txt”, siguiendo la sintaxis establecida. En dicho fichero, se indican las características necesarias para la construcción del programa final (ficheros a compilar, librerías y ejecutables a generar, librerías de las que
Figura 3.19. Logo Gnome (GTK Library).
Figura 3.20. Instalar librería GTK.
Capítulo 3 Interfaz de usuario
46 depende, versiones mínimas a emplear, etc.). Con este fichero, CMake genera el archivo Makefile que genera el programa compilado. Para proyectos grandes, el uso de archivos Makefile puede reducir considerablemente los tiempos de compilación cuando solo hayan cambiado pocas líneas en el código.
El archivo Makefile generado contiene las directivas necesarias por Make para construir el ejecutable. Además de para la construcción de programas ejecutables y librerías, Make se puede utilizar en cualquier proceso que implique la ejecución de comandos para transformar un archivo fuente a un determinado resultado. En un archivo Makefile se distinguen 5 tipos de declaraciones:
Comentarios. Facilitan el entendimiento del código escrito. Los comentarios comienzan con #.
Variables. Al igual que en otros lenguajes, sirven para almacenar valores.
Reglas explícitas. Comandos requeridos para compilar un archivo particular.
Reglas implícitas. Dicen cómo y cuándo rehacer una clase de archivos en función de su nombre.
Directiva. Instrucción para realizar otra acción mientras se lee un Makefile. Un ejemplo de directiva puede ser la lectura de otro archivo Makefile.
Para indicar en el archivo CMakeLists.txt el uso de la librería GTK, hay que indicar estos 6 puntos:
1. FIND_PACKAGE(PkgConfig REQUIRED).
2.
PKG_CHECK_MODULES(GTK3 REQUIRED gtk+-3.0).3. INCLUDE_DIRECTORIES(${GTK3_LIBRARY_DIRS}).
4. LINK_DIRECTORIES(${GTK3_LIBRARY_DIRS}).
5. ADD_DEFINITIONS(${GTK3_CFLAGS_OTHER}).
6. TARGET_LINK_LIBRARIES (NombreEjecutable ${GTK3_LIBRARIES}).
Una vez indicados, se podrá usar cualquier fichero “.h” de la librería.
3.4.2. Programación de la interfaz.
Es de destacar la gran documentación existente en la web de GTK [15], lo cual ha ayudado a agilizar el aprendizaje para poder utilizar las funciones necesarias. El código utilizado para la programación de la interfaz gráfica es el siguiente:
Capítulo 3 Interfaz de usuario
47
33 GtkWidget *ventana, *ventanaSec, *apariencia, *aparienciaSec, *texto,
*entrada, *imagen, *imagen1, *boton, *boton1, *boton2, *boton3, *botonSec,
*etiquetaSec, *entradaSec;
467 int main(int argc, char **argv) 468 {
469 gtk_init(&argc,&argv);
470
471 ventana = gtk_window_new(GTK_WINDOW_TOPLEVEL);
472 g_signal_connect(ventana,"destroy", G_CALLBACK(gtk_main_quit),NULL);
473
474 gtk_window_set_title(GTK_WINDOW(ventana),"Conexión VLP-16");
475 gtk_container_set_border_width(GTK_CONTAINER(ventana),0);
476 gtk_window_set_default_size(GTK_WINDOW(ventana),400,440);
477 gtk_window_set_position(GTK_WINDOW(ventana), GTK_WIN_POS_CENTER_ALWAYS);
478
479 apariencia = gtk_layout_new(NULL,NULL);
480 texto = gtk_label_new("Intruduzca el intervalo de visualización: ");
481 gtk_layout_put(GTK_LAYOUT(apariencia),texto,10,20);
482
483 entrada = gtk_entry_new();
484 gtk_entry_set_width_chars(GTK_ENTRY(entrada),4);
485 gtk_entry_set_max_length(GTK_ENTRY(entrada),4);
486 gtk_layout_put(GTK_LAYOUT(apariencia),entrada, 300,14);
487
488 GdkPixbuf *escala;
489 escala =
gdk_pixbuf_new_from_file_at_scale("Velodyne.png",300,300,FALSE,NULL);
490 imagen = gtk_image_new_from_pixbuf(escala);
491
492 gtk_layout_put(GTK_LAYOUT(apariencia),imagen, 53,160);
493
494 boton = gtk_button_new_with_label("Visualizar .pcd");
495 g_signal_connect(boton,"clicked",G_CALLBACK(LeerPCDfile),NULL);
496 gtk_layout_put(GTK_LAYOUT(apariencia),boton,30,70);
497
498 boton1 = gtk_button_new_with_label("Guardar en .pcd");
499 g_signal_connect(boton1,"clicked",G_CALLBACK(GuardarPCD),NULL);
500 gtk_layout_put(GTK_LAYOUT(apariencia),boton1,200,70);
501
502 boton2 = gtk_button_new_with_label("Abrir visualizador");
503
g_signal_connect(boton2,"clicked",G_CALLBACK(AbrirVisualizador),NULL)
;
504 gtk_layout_put(GTK_LAYOUT(apariencia),boton2,30,130);
505
506 boton3 = gtk_button_new_with_label("Detectar movimiento");
507 g_signal_connect(boton3,"clicked",G_CALLBACK(Deteccion),NULL);
508 gtk_layout_put(GTK_LAYOUT(apariencia),boton3,200,130);
509 510 511
512 gtk_container_add(GTK_CONTAINER(ventana),apariencia);
513 gtk_widget_show_all(ventana);
515 return 0;
516 }
Capítulo 3 Interfaz de usuario
48 En primer lugar, hay que definir la ventana principal de la interfaz, para ello se utiliza una variable tipo GtkWidget.
GtkWidget *ventana;
Esta variable será definida como ventana mediante la función (ver línea 471):
GtkWidget * gtk_window_new (GtkWindowType);
La función anterior permite definir el tipo de ventana a emplear. Se distinguen las siguientes opciones:
GTK_WINDOW_TOPLEVEL. Para una ventana regular, como un diálogo.
GTK_WINDOW_POPUP. Para una ventana especial, por ejemplo, una ventana para mostrar información sobre alguna herramienta.
Para asignar la función de “cerrar” a la ventana se hará uso de la función (ver línea 472):
g_signal_connect(instance, detailed_signal, c_handler, data);
Esta función cerrará la ventana cuando se pulse la “x” de cerrar. A continuación, se definen los parámetros de la función a utilizar.
g_signal_connect(ventana, “destroy”, G_CALLBACK(gtk_main_quit), NULL);
instance. Instancia a conectar.
detailed_signal. Acción necesaria para ejecutar la función.
c_handler. Usando G_CALLBACK, se indica la función a ejecutar. G_CALLBACK es el tipo utilizado por funciones de devolución en estructuras de definición. Por ejemplo, se utiliza para “conectar” una función a otra.
Es necesario dotar de ciertas propiedades a la ventana, para ello se utilizarán varias funciones de la librería GTK, dando, entre otras propiedades, nombre y tamaño.
Para darle nombre a la ventana creada (ver línea 474), se utiliza:
gtk_window_set_title (GtkWindow *window, const gchar *title);
Capítulo 3 Interfaz de usuario
49 Es necesario primero utilizar la función GTK_WINDOW() para convertir la variable ventana (tipo GtkWidget) a “GtkWindow” (tipo de variable que permite recibir la función). Como segundo parámetro, simplemente se indica, entre dobles comillas, el nombre que se le quiere dar a la ventana.
Para definir el borde de las ventanas (o la cantidad de espacio, hacia dentro o hacia fuera) se utiliza la siguiente función (ver línea 475):
void gtk_container_set_border_width(GtkContainer *container, guint
border_width);
Al igual que en el caso anterior, es necesario utilizar previamente una función que convierta la variable al tipo requerido por la función, por ello se usa GTK_CONTAINER() para convertir la variable ventana al tipo “GtkContainer”. Según sea el tipo de ventana (definido en el función
“gtk_window_new”), el valor indicado, comprendido entre 0 y 65535, en el segundo parámetro (cantidad de espacio en blanco), se deja fuera de la ventana (para ventanas tipo TOP LEVEL) o dentro (para ventanas tipo POPUP).
Para definir el tamaño, por defecto, de la ventana, se utiliza la función (ver línea 476):
void gtk_window_set_default_size(GtkWindow *ventana, gint width,gint
height);
Indicando -1 en la altura y en la anchura, se deshacen los valores predeterminados.
Para situar la ventana en la posición que se desee, es muy útil utilizar la siguiente función (ver línea 477):
void gtk_window_set_position(GtkWindow *ventana, GtkWindowPosition
position);
El segundo parámetro puede ser cualquiera de los siguientes:
GTK_WIN_POS_NONE. No se influye en la ubicación de la ventana.
GTK_WIN_POS_CENTER. La ventana se coloca en el centro de la pantalla.
GTK_WIN_POS_MOUSE. La ventana se coloca en la posición actual del ratón.
GTK_WIN_POS_CENTER_ALWAYS. Mantiene la ventana centrada aunque cambie de tamaño.
GTK_WIN_POS_CENTER_ON_PARENT. Centra la ventana en la ventana desde la que la haya llamado.
Capítulo 3 Interfaz de usuario
50 Una vez definida la ventana principal, es necesario crear el contenido (botones, entradas de texto, etc.). Para ello se usará un “GtkLayout” (tipo de variable de la librería). Este objeto se crea como “GtkWidget”, y cuando se necesite usar en funciones que necesiten como parámetro el tipo “GtkLayout”, se utilizará la función GTK_LAYOUT() para convertirlo. Como se puede ver, la conversión de variables es algo habitual para el empleo de muchas funciones.
Una vez creado el objeto que actuará de contenedor, se utiliza la siguiente función para definirlo:
GtkWidget * gtk_layout_new (GtkAdjustment *hadjustment, GtkAdjustment
*vadjustment);
Al no ser necesario realizar ningún ajuste, la función se utiliza de la siguiente forma (ver línea 479):
apariencia = gtk_layout_new(NULL, NULL);
Como para establecer la conexión con VLP-16 es necesario indicar el número de puerto (socket) en el que está emitiendo paquetes el láser, se dotará a la interfaz de un texto y una entrada de texto, para este fin. Para ello, se crean dos objetos “GtkWidget”.
Para crear el texto se usarán dos funciones, la primera para crearlo e indicar el texto que se quiere poner (gtk_label_new()), y la segunda para situarlo en una determinada coordenada de la ventana (ver línea 486), como se puede ver:
gtk_layout_put( GTK_LAYOUT(apariencia), texto, 10, 20);
En la entrada de texto, el procedimiento es similar, una función para crearla y otra para situarla en una coordenada concreta (idéntica función a la usada en el texto). Adicionalmente, se utilizarán dos funciones para definir el ancho y el máximo número de caracteres permitidos.
Estas funciones son:
Entrada de texto (ver línea 483).
entrada = gtk_entry_new();
Ancho de la entrada de texto (ver línea 484):
gtk_entry_set_width_chars(GTK_ENTRY(entrada),4);
número máximo de caracteres (ver línea 485):
Capítulo 3 Interfaz de usuario
51
gtk_entry_set_max_lenght(GTK_ENTRY(entrada),4);
Además de botones, texto, etc., se pondrá en la interfaz la siguiente imagen del VLP – 16:
La imagen tiene más resolución que el tamaño indicado para la ventana, será por tanto necesario escalar la imagen antes de insertarla en la interfaz. Para ello, se utilizará un objeto
“GdkPixbuf”. Este tipo de objeto contiene toda la información que describe una imagen.
La imagen a utilizar está ubicada en algún directorio del ordenador, por ello habrá que, utilizar, previamente, una función que le lea y la escale.
GdkPixbuf * gdk_pixbuf_new_from_file_at_size(const char *filename, int
width, int height, Gerror **error);
filename. Se indica la ruta donde está la imagen a usar.
width. Ancho nuevo de la imagen.
height. Alto nuevo de la imagen.
error. Devuelve error en caso de no encontrar la imagen.
Quedando finalmente (ver línea 489):
escala = gtk_pixbuf_new_from_file_at_scale(“Velodyne.png”, 300, 300, FALSE,
NULL);
Figura 3.21. Velodyne VLP-16.
Capítulo 3 Interfaz de usuario
52 Una vez leída la imagen e indicada la escala, se crea un objeto “GtkWidget” llamado imagen, y se iguala a la imagen leída. Esto se hace mediante la siguiente función (ver línea 490):
imagen = gtk_image_new_from_pixbuf(escala);
Finalmente, se crean cada uno de los botones necesarios. Para ello se utilizará la siguiente función (ver línea 494):
boton = gtk_button_new_with_label(“Visualizar .pcd”);
Para ejecutar una determinada función al hacer “click” sobre el botón, se usará, como ya se ha visto antes, la siguiente función (ver línea 495):
g_signal_connect(boton, “clicked”, G_CALLBACK(LeerPCDfile), NULL);
Una vez creados todos los widget requeridos para la interfaz gráfica, es necesario incluirlos en la ventana, que en este caso, actúa como el contenedor de los demás widget. Para ello, se utiliza la función (ver línea 512):
void gtk_container_add(GtkContainer *container, GtkWidget *widget);
La función agrega “widget” a “container”. Como “apariencia” se ha utilizado para situar todos los widget, será utilizada como segundo parámetro. Se consigue así, que todos los widget creados aparezcan en la interfaz.
Se utiliza, finalmente, una función para mostrar la interfaz (ver línea 513):
gtk_widget_show_all(ventana);
Una de las funcionalidades que ofrece el programa es la lectura de archivos .pcd, y su posterior visualización. Para la lectura de estos archivos se ha creado mediante GTK una ventana de búsqueda. Cuando se selecciona el archivo y se le da a aceptar, se puede obtener su ruta exacta.
En primer lugar, hay que crear 4 variables:
GtkWidget *dialog. Objeto que va a actuar como la ventana de diálogo.
GtkFileChooserAction action. Indica el tipo de acción a realizar. Puede ser una de estas cuatro: