Desarrollo de un vehículo autónomo a escala con capacidad de navegación y seguimiento de marcas viales
Abstract
Departamento de Ingeniería de Sistemas y Automática
Full text
Máster en Ingeniería Industrial MASTER EN INGENIERÍA INDUSTRIAL ESCUELA DE INGENIERÍAS INDUSTRIALES UNIVERSIDAD DE VALLADOLID TRABAJO FIN DE MÁSTER Desarrollo de un vehículo autónomo a escala con capacidad de navegación y seguimiento de marcas viales Autor: D. Fernando Eduardo Cuenca Rodríguez-Monsalve Tutor: D. Eduardo Zalama Casanova Valladolid, Septiembre de 2020
I RESUMEN En la actualidad, los fabricantes de automóviles desarrollan cada vez más sistemas que aumentan la capacidad de conducción autónoma del vehículo y reducen las acciones que debe realizar el conductor. El objetivo final de estos avances es lograr que los coches conduzcan sin la necesidad de que haya nadie dirigiéndolos. Este proyecto tiene como objetivo poner en funcionamiento un modelo de coche a escala con los actuadores y sensores necesarios para conducir de forma autónoma. De esta forma la Escuela de Ingenierías Industriales dispondrá en sus laboratorios de vehículos que podrán ser utilizados para realizar pruebas y estudios en este campo. Además de la puesta en marcha de uno de estos coches, el proyecto también pretende ser una guía rápida para crear otros o para arreglarlos en caso de ser necesario. Estos coches están dirigidos por una Odroid y un Arduino que utilizan el framework ROS para comunicarse con los sensores y actuadores. El lenguaje de programación de estos dispositivos es C++. Palabras clave: coche autónomo, ROS, openCV, Arduino, C++ ABSTRACT Currently, vehicle manufacturers are growing their investment in developing systems that increase de autonomous capacities of cars which reduces the amount of actions that drivers have to execute to perform their tasks. The final goal of this advances is to achieve driverless vehicles. This project's goal is the creation of a scaled car with the actuators and sensors needed to drive in an autonomous way. In this way, the Escuela de Ingenierías Industriales will have in its laboratories vehicles that can be used to develop projects or realize studies in this field. This project also has the purpose of being an easy guide to create more cars or in the case there is the need of solving problems. These cars are controlled with an Odroid and an Arduino that use ROS to communicate with the sensors and actuators. The programming language used for these devices is C++. Keywords: autonomous car, ROS, OpenCV, Arduino, C++
II
III AGRADECIMIENTOS Quiero agradecer a mi familia y amigos por todo el apoyo que me han proporcionado a lo largo de estos años. Muchas gracias por haber estado ahí siempre que os he necesitado. También quiero agradecer a Eduardo Zalama por la oportunidad de realizar este proyecto y por la ayuda que me ha proporcionado a lo largo de éste.
IV
V ÍNDICE 1) INTRODUCCIÓN Y OBJETIVOS .......................................................................................................... 1 1.1. EL COCHE AUTÓNOMO ........................................................................................................... 1 1.1.1. ¿POR QUÉ DESARROLLAR EL COCHE AUTÓNOMO? ........................................................ 1 1.1.2. DESARROLLO DEL COCHE AUTÓNOMO ........................................................................... 1 1.2. CONTEXTO DEL PROYECTO ...................................................................................................... 1 1.3. OBJETIVOS DEL PROYECTO ...................................................................................................... 2 1.4. CONTENIDO DE LA MEMORIA ................................................................................................. 3 2) DISEÑO DEL HARDWARE DEL VEHÍCULO ......................................................................................... 5 2.1. LISTA DE COMPONENTES ........................................................................................................ 5 2.1.1. COCHE DE RADIOCONTROL ............................................................................................. 5 2.1.2. ODROID-XU4 .................................................................................................................... 6 2.1.3. ARDUINO NANO .............................................................................................................. 6 2.1.4. RPLIDAR A2M8-R3 ........................................................................................................... 7 2.1.5. CÁMARA Y MONTURA ..................................................................................................... 7 2.1.6. MOTOR CON ENCODER ................................................................................................. 11 2.1.7. SERVOMOTOR XCITERC XLS-19S ................................................................................... 12 2.1.8. POLOLU MD07B ............................................................................................................. 12 2.1.9. PDB-XT60 ....................................................................................................................... 12 2.1.10. BATERIA LIPO DE 3 CELDAS ........................................................................................... 13 2.1.11. QSKJ DC-DC CONVERTER 10-32V TO 12-35V ................................................................. 13 2.2. CONEXIONES.......................................................................................................................... 13 3) APRENDIZAJE Y MANEJO DE HERRAMIENTAS PARA LA PROGRAMACIÓN. PROGRAMACIÓN ROS. 15 3.1. LINUX ..................................................................................................................................... 15 3.2. C++ ......................................................................................................................................... 15 3.3. ARDUINO ............................................................................................................................... 16 3.4. ROS ........................................................................................................................................ 16 3.4.1. PAQUETES ...................................................................................................................... 17 3.4.2. NODOS ........................................................................................................................... 18 3.4.3. COMUNICACIÓN ENTRE NODOS ................................................................................... 18 4) CONTROL DE ALTO NIVEL EN ODROID. INSTALACIÓN Y PROGRAMACIÓN DE MODULOS ROS .... 21 4.1. INSTALAR ROS EN EL ORDENADOR ....................................................................................... 21
VI 4.2. PREPARACIÓN DE LA ODROID ............................................................................................... 21 4.2.1. INSTALAR EL SISTEMA OPERATIVO DE LA ODROID ....................................................... 21 4.2.2. ESTABLECER LA COMUNICACIÓN VÍA ROS ENTRE EL ORDENADOR Y LA ODROID ........ 24 4.2.3. PREPARACIÓN DEL LIDAR .............................................................................................. 26 4.2.4. PREPARACIÓN DE LA CÁMARA USB .............................................................................. 28 5) CONTROL DE BAJO NIVEL ARDUINO .............................................................................................. 31 5.1. INSTALACIÓN DEL IDE DE ARDUINO ...................................................................................... 31 5.2. CÓDIGO DEL ARDUINO .......................................................................................................... 31 6) RESULTADOS EXPERIMENTALES .................................................................................................... 39 6.1. CREACIÓN DE UN ESPACIO DE TRABAJO ROS ....................................................................... 39 6.2. CREACIÓN DEL PAQUETE SIGUE_LINEAS ............................................................................... 39 6.2.1. PROGRAMA CAMERA_PUBLISHER ................................................................................ 40 6.2.2. PROGRAMA CAMERA_SIGUE_LINEAS ........................................................................... 40 6.2.3. VERIFICACIÓN DEL PAQUETE SIGUE_LINEAS ................................................................. 45 6.3. CREACIÓN DEL PAQUETE EVITA_OBSTACULOS ..................................................................... 48 6.3.1. CALIBRACIÓN DEL LIDAR ............................................................................................... 48 6.3.2. PROGRAMA EVITA ......................................................................................................... 50 6.3.3. VERIFICACIÓN DEL PAQUETE EVITA_OBSTACULOS ....................................................... 51 6.4. CREACIÓN DEL PAQUETE APARCAMIENTO ........................................................................... 53 6.4.1. PROGRAMA APARCAMIENTO ........................................................................................ 53 6.4.2. VERIFICACIÓN DEL PAQUETE APARCAMIENTO ............................................................. 54 6.5. CREACIÓN DEL PAQUETE SEMAFORO ................................................................................... 55 6.5.1. PROGRAMA SEMAFORO ................................................................................................ 55 6.5.2. VERIFICACIÓN DEL PAQUETE SEMAFORO ..................................................................... 56 7) CONCLUSIONES ............................................................................................................................. 57 7.1. LOGROS TÉCNICOS ................................................................................................................ 57 7.2. CONOCIMIENTOS ADQUIRIDOS ............................................................................................ 57 7.3. POSIBLES LÍNEAS DE DESARROLLO ........................................................................................ 58 8) BIBLIOGRAFÍA ................................................................................................................................ 59 9) ANEXOS.......................................................................................................................................... 61 9.1. Anexo 1: Código del Arduino ................................................................................................. 62 9.2. Anexo 2: Programa camera_publisher .................................................................................. 64 9.3. Anexo 3: Programa camera_sigue_lineas ............................................................................. 65 9.4. Anexo 4: ROSlaunch camera.launch ...................................................................................... 67 9.5. Anexo 5: client.cpp ................................................................................................................ 68
VII 9.6. Anexo 6: evita.cpp ................................................................................................................. 69 9.7. Anexo 7: aparcamiento.cpp .................................................................................................. 70 9.8. Anexo 8: semaforo.cpp ......................................................................................................... 71
Desarrollo de un vehículo autónomo a escala 4 Capítulo séptimo Resumen final de los resultados del proyecto y planteamiento de posibles futuras líneas de desarrollo. Capítulo octavo Bibliografía con las fuentes consultadas para realizar este proyecto. Capítulo noveno Anexos que incluyen el código de los programas utilizados para las pruebas de conducción autónoma.
Desarrollo de un vehículo autónomo a escala 5 2) DISEÑO DEL HARDWARE DEL VEHÍCULO El primer paso para poder comenzar el desarrollo del coche autónomo ha sido escoger el hardware necesario para su funcionamiento. Para realizar estas decisiones se han tomado como referencia los componentes de los vehículos utilizados en el "SEAT autonomous driving challenge", especificados en la página web de la Universidad de Berlín [4]. Después se ha preguntado a los alumnos que participaron en la competición cuales habían utilizado y cuáles no. 2.1. LISTA DE COMPONENTES 2.1.1. COCHE DE RADIOCONTROL Se ha utilizado un coche de radiocontrol a escala 1:10 como base del vehículo y se han retirado la carrocería y los dispositivos que no eran necesarios. Se ha conservado la base del coche sobre la que se apoyan el resto de componentes, el parachoques para protegerlo en caso de colisión, la suspensión, las ruedas y el sistema de correas y engranajes que transmite el giro del motor a las ruedas. Sobre este armazón se ha colocado una plancha rectangular de plástico rígido transparente sobre el cual se han montado el resto de dispositivos, a excepción del motor y el servomotor que están montados en la propia base del coche. Ilustración 1: Vista superior del coche
Desarrollo de un vehículo autónomo a escala 6 Ilustración 2: Vista inferior del coche 2.1.2. ODROID-XU4 Ilustración 3: Odroid-XU4 La Odroid-XU4 es un ordenador de tamaño y precio reducido (similar a una Raspberry Pi, aunque más potente). Funciona con el sistema operativo Linux y es el ordenador a bordo desde el cual se ejecutan los programas que controlan el coche. Dispone de 2 puertos USB 3.0 que se utilizan para conectarlo a los demás dispositivos. 2.1.3. ARDUINO NANO Ilustración 4: Arduino nano El Arduino nano es un microcontrolador que, en este caso, tiene como objetivo comunicarse con la Odroid para controlar el motor y el servomotor, además de transmitir la información del encoder.
Desarrollo de un vehículo autónomo a escala 7 2.1.4. RPLIDAR A2M8-R3 Ilustración 5: Rplidar A2M8-R3 Un lidar (Laser Imaging Detection And Ranging) es un dispositivo que permite determinar la distancia desde un emisor láser a un objeto o superficie utilizando un haz láser pulsado. La distancia al objeto se determina midiendo el tiempo de retraso entre la emisión del pulso y su detección a través de la señal reflejada. El Rplidar A2M8-R3 es un lidar de bajo coste que proporciona una detección de 360º, con una frecuencia de rotación de 10 Hz y capaz de detectar obstáculos hasta a 16 metros de distancia. Este Lidar permite al coche detectar obstáculos para así poder evitarlos. 2.1.5. CÁMARA Y MONTURA El coche utilizado en el "SEAT autonomous driving challenge" utilizaba una cámara tipo Kinect. Este tipo de cámaras no solo obtienen una imagen RGB sino que además posee un sensor de profundidad que obtiene la distancia a la que se encuentran los objetos que observa. El equipo que participó en la competición comunicó que solo se utilizó la funcionalidad de la imagen ya que para obtener la distancia a la que se encuentran los objetos utilizaron solo el lidar. Por las razones que se han mencionado anteriormente, al realizar este modelo, se ha decidido emplear una cámara RGB. La cámara escogida es una Logitech Webcam c170 que permite obtener video con una calidad de 640x480 pixeles.
Desarrollo de un vehículo autónomo a escala 8 Ilustración 6: Logitech webcam c170 Las imágenes obtenidas con esta cámara tienen dos objetivos: detectar las líneas del carril, lo que permitirá al coche permanecer entre ellas, y detectar señales como pueden ser las de semáforo. Debido a que esta cámara no es de gran angular, para obtener en una misma imagen objetos cercanos (las líneas de la carretera a la altura del parachoques) y objetos lejanos (un semáforo) es necesario colocar a la cámara a mayor altura que el resto de los dispositivos. Había que diseñar, por tanto, un soporte para colocar la cámara teniendo en cuenta que debía ser pequeño para interferir lo mínimo con el lidar, el cual no sería capaz de detectar objetos que estuviesen situados detrás del soporte. Sabiendo que el lidar toma información en un plano horizontal, lo importante en el diseño del soporte era que a la altura a la cual obtiene los datos haya la mínima cantidad de interferencias. Finalmente se tomó como decisión colocar el soporte de la cámara sobre dos columnas. De esta forma se interfiere lo mínimo con el lidar como se observa en la Ilustración 7. En rojo aparecen los puntos en los cuales las columnas producen interferencia en el lidar. En verde se muestra toda la zona en la cual se producirían interferencias si se hubiese hecho el soporte macizo en lugar de con columnas. Ilustración 7: Interferencia soporte cámara Sin embargo, la forma de las columnas es poco estable si se quiere lograr más altura.
Desarrollo de un vehículo autónomo a escala 9 Por lo tanto, había que diseñar una pieza para lograr la altura necesaria. Esta pieza debía de cumplir los siguientes requisitos: La forma de la parte inferior debía estar adaptada para poder ser atornillada a las columnas. La forma de la parte superior debía estar adaptada para que la cámara se pudiera encajar. Debía pesar poco para evitar que las columnas pandeasen con el peso. Debía ser rígida para evitar que la propia pieza pandease. Ante estas premisas se consideró que la mejor forma de obtener esta pieza era mediante la impresión 3D ya que permite diseñar la pieza con la forma deseada. Además, el PLA (material que se suele utilizar en este tipo de fabricación) cumple las características de rigidez y ligereza. El siguiente paso, por tanto, era diseñar la pieza. Para el diseño de la pieza se ha utilizado el programa FreeCAD. Ilustración 8: Icono FreeCAD FreeCAD es un programa de diseño asistido por computadora en tres dimensiones. A diferencia de CATIA (programa de CAD que se enseña en la asignatura de Dibujo Asistido por Ordenador en el grado de Tecnologías Industriales), su utilización es gratuita y es de código abierto. La pieza diseñada que se puede observar en la Ilustración 9 consta de tres partes: La parte inferior de forma rectangular con las esquinas redondeadas y con dos agujeros por los cuales pasar los tornillos que se unen a las columnas sobre las cuales se apoya el soporte. La parte principal, que es el bloque que va a proporcionar altura a la cámara de una forma estable. La parte superior, que dispone de cuatro prismas rectangulares en los cuales se va a apoyar la cámara. Estos prismas sirven para encajar la cámara utilizando los trozos de plástico que sobresalen de su parte inferior como se observa en la Ilustración 10.
Desarrollo de un vehículo autónomo a escala 10 Ilustración 9: Diseño soporte en FreeCAD Ilustración 10: Parte inferior de la cámara Una vez realizado el diseño de la pieza se ha exportado al formato stl, formato que define la geometría de objetos 3D y que es utilizado por el software de control de las impresoras 3D. Después de obtener la pieza en formato stl hay que introducirla en un programa que permita ajustar los parámetros de impresión 3D. El programa que ha sido utilizado para este propósito es Ultimaker Cura. Ilustración 11: Logo de Ultimaker Cura Lo primero que hay que introducir en este programa es la impresora 3D utilizada (en este caso una Creality 3D Ender 5) y después las características de impresión. Debido a que es una pieza sencilla, se han utilizado los parámetros que el programa clasifica como "Standard Quality". Siendo los parámetros más característicos:
Desarrollo de un vehículo autónomo a escala 11 Altura de capa: 0,2 mm Relleno del 20% con patrón cúbico Velocidad de impresión: 80 mm/s Además, es importante la posición en la que se coloca la pieza para su impresión como se observa en la Ilustración 12. A la hora de escoger la colocación, hay que evitar en la medida de lo posible que haya voladizos. Para esta pieza la colocación es muy intuitiva. Ilustración 12: Soporte para imprimir en 3D 2.1.6. MOTOR CON ENCODER El motor utilizado es un motor con escobillas maxon DC motor, que posee un encoder rotatorio incremental de dos canales. El motor está conectado mediante una serie de engranajes y correas dentadas a las ruedas delanteras y traseras (todas giran a la misma velocidad). Ilustración 13: Motor con encoder y mecanismo de transmisión a las ruedas
Desarrollo de un vehículo autónomo a escala 12 El motor es el actuador que se encarga del movimiento lineal del coche, a través del cual acelera y frena. El encoder permite saber qué velocidad lleva el vehículo. 2.1.7. SERVOMOTOR XCITERC XLS-19S Ilustración 14: Servomotor XLS-19s El servomotor está unido a la dirección delantera del coche y es el que permite controlar la dirección de este. 2.1.8. POLOLU MD07B Ilustración 15: Pololu MD07b El pololu MD07b es un controlador de motores que sirve de interfaz entre el Arduino, el motor y su alimentación. Este controlador es necesario ya que las señales de control del Arduino con las que controla la velocidad del motor funcionan con 5V, mientras que el motor trabaja con 12V. Además, permite controlar el sentido en el que gira el motor, lo que va a permitir que el coche se mueva hacia delante o hacia detrás. 2.1.9. PDB-XT60 Ilustración 16: PDB-XT60
Desarrollo de un vehículo autónomo a escala 13 El PDB-XT60 es una placa de distribución de alimentación. Esta placa tiene un conector que permite acoplarla fácilmente a la batería LiPo que se utiliza para alimentar los componentes del coche. En esta placa es fácil soldar cables para así llevar la potencia a los distintos dispositivos. 2.1.10. BATERIA LIPO DE 3 CELDAS Para la alimentación eléctrica del coche se utiliza una batería LiPo de 3 celdas (11,1 V) con conector XT60 para poder acoplarla a la PDB-XT60 2.1.11. QSKJ DC-DC CONVERTER 10-32V TO 12-35V Ilustración 17: QSKJ DC-DC Converter 10-32V to 12-35V Este dispositivo es un transformador que va a permitir transformar los 11,1V proporcionados por la batería LiPo en 12V para alimentar a la Odroid. 2.2. CONEXIONES Una vez seleccionado todos los componentes hay que conectarlos entre ellos para que el coche funcione. En la Ilustración 18 se puede observar un esquema que muestra el cableado entre los distintos dispositivos (los colores utilizados para los cables del esquema son los colores de los cables que tiene el coche realmente).
Desarrollo de un vehículo autónomo a escala 20 Como se ha descrito, los mensajes vía topic son unidireccionales. Esto puede ser un problema ya que el publicador no sabe si un suscriptor concreto ha recibido el mensaje. En algunos casos puede que sea necesario saber si otro nodo ha recibido el mensaje. Es por esto que en ROS existe también la comunicación por servicios. Los servicios permiten la comunicación bidireccional entre nodos. La estructura de los servicios se divide en cuatro bloques: Servidor: Es el nodo que va a suministrar los servicios a los nodos clientes Cliente: Los nodos clientes usan los servicios del nodo servidor y pueden transmitir y recibir información al servidor Variable de servicio: Es la variable que comparten el cliente y el servidor con la que comparten información. Posee dos campos: request y response. El campo request es rellenado por el cliente y transmitido al servidor mientras que el campo response es rellenado por el servidor y lo transmite como respuesta al cliente. Acción asociada al servicio: En el nodo del servidor se ejecutará una acción asociada al servicio que podrá utilizar la información de la sección request rellenada por el cliente. Una vez realizada la acción el servidor puede rellenar el campo response de la variable compartida que podrá ser leído por el cliente.
Desarrollo de un vehículo autónomo a escala 21 4) CONTROL DE ALTO NIVEL EN ODROID. INSTALACIÓN Y PROGRAMACIÓN DE MODULOS ROS 4.1. INSTALAR ROS EN EL ORDENADOR ROS tiene un gran número de distribuciones pero como la distribución instalada en la Odroid es kinetic hay que instalar la misma versión en el ordenador con distribución Ubuntu que se utiliza en el proyecto [11]. Para ello hay que seguir los siguientes pasos: 1. En el menú de búsqueda del ordenador (se accede pulsando el botón de Windows en el teclado) escribir Software & Updates y clicar en el botón que aparece. 2. Seleccionar las casillas de multiverse, universe y restricted (configurar Ubuntu para que acepte este tipo de repositorios). 3. Abrir el terminal y escribir (permite al ordenador acceder a los archivos de packages.ros.org): sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' 4. Después hay que escribir también: sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recvkey C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 5. Hay que asegurar que la versión Debian esta actualizada: sudo apt-get update 6. Finalmente se instala ROS kinetic en el ordenador: sudo apt-get install ros-kinetic-desktop-full 4.2. PREPARACIÓN DE LA ODROID 4.2.1. INSTALAR EL SISTEMA OPERATIVO DE LA ODROID Al igual que en las Raspberry PI toda la información se guarda en una tarjeta microSD en las Odroid la información se guarda en una tarjeta eMMC (de al menos 16GB de memoria) donde habrá que instalar el sistema operativo. Este sistema operativo se encuentra para descargar en la página de la universidad de Berlín [4]. Existen varias versiones pero en este proyecto se va a trabajar con la 3.1 al ser la más reciente. Esta versión contiene Ubuntu 16.04 y ROS kinetic. Una vez descargado el fichero que contiene el sistema operativo hay que instalar en el ordenador personal el paquete libterm-readkey-perl que es el que va a permitir instalar la eMMC. Este paquete se puede descargar escribiendo en la consola Linux (para abrir la consola Linux usar Ctrl+Alt+T): sudo apt-get install libterm-readkey-perl Una vez hecho esto hay que conectar la tarjeta eMMC al ordenador, para ello se usa un adaptador SD a eMMC
Desarrollo de un vehículo autónomo a escala 22 Ilustración 23: Adaptador SD a eMMC Se extrae la carpeta comprimida que se ha descargado y se navega hasta ella en el terminal: cd linux-image-for-odroid-xu4-ubuntu16.04-kinetic Después, con el comando ls buscamos la ubicación de la carta eMMC escribiendo: ls /dev/sd* Con este comando aparecerán todos los dispositivos SD conectados al ordenador, en caso de duda de cuál es el de la tarjeta eMMC es aconsejable poner el comando y mirar el listado que aparece, después retirar el adaptador del ordenador y volver a escribir el comando. Aparecerá la misma lista de antes pero faltara la tarjeta eMMC. El nombre será algo parecido a /dev/sdb. Después se escribe en el terminal (sustituyendo /dev/sdb por el nombre que haya aparecido en nuestro ordenador): sudo ./install.pl /dev/sdb La instalación dura bastante tiempo. Cuando este acabando se pide al usuario que escoja una de las configuraciones propuestas como se observa en la Ilustración 24. Ilustración 24: Escoger configuración Odroid Para escoger una configuración hay que escribir la letra deseada, en el caso de este proyecto se escogió la configuración "a". Una vez escogida aparecen en pantalla los parámetros de configuración de la Odroid como su dirección IP y la contraseña como se observa en la Ilustración 25:
Desarrollo de un vehículo autónomo a escala 23 Ilustración 25: Parámetros de la configuración Es muy importante conservar estos parámetros de configuración ya que son necesarios para trabajar con la Odroid. Una vez finalizada la instalación ya se puede retirar la tarjeta del ordenador y colocarla en la Odroid. Después de colocarla en la Odroid es muy importante colocar el interruptor de la Odroid en la posición eMMC como se observa en la Ilustración 26. Ilustración 26: Interruptor eMMC Una vez realizado esto, ya se puede alimentar la Odroid. Si la luz azul que se encuentra al lado de la conexión de la alimentación parpadea es buena señal. La primera vez que encendamos la Odroid requerirá inicializarse lo que puede llevar un par de minutos. Una vez esperado el par de minutos hay que conectar la Odroid al ordenador utilizando un cable Ethernet. Después de conectar el cable en el ordenador hay que realizar los siguientes pasos: 1. Ir a "Network connections" y clicar en el botón "Add". 2. Seleccionar como tipo de conexión "Ethernet" y clicar en el botón "Create". 3. En la pestaña Ethernet en la lista de "Device" escoger la de la Odroid. En la pestaña IPv4 Settings asegurarse que el método "Automatic" (DHCP) esta seleccionado.
Desarrollo de un vehículo autónomo a escala 24 En el menú de conexiones escoger la nueva conexión creada. Para asegurar que la configuración de la conexión se ha realizado con éxito hay que comprobar el ping entre los componentes. Para ello escribir en el terminal el comando siguiente (el número a escribir es el que aparece en el Ethernet IP address que se observa en la Ilustración 25): ping 192.168.1.199 -c 1 En caso que la conexión este bien configurada aparecerá un mensaje como el de la Ilustración 27. Ilustración 27: Ping correcto Odroid En caso de haber un problema de conexión el mensaje será como el de la Ilustración 28. Ilustración 28: Ping incorrecto Odroid 4.2.2. ESTABLECER LA COMUNICACIÓN VÍA ROS ENTRE EL ORDENADOR Y LA ODROID Para comprobar la comunicación hay que tener el ordenador conectado a la Odroid mediante un cable Ethernet. Se escribe en el terminal del ordenador lo siguiente (192.168.1.199 es la dirección IP de la Odroid): ssh [email protected] Una vez conectado hay que introducir la contraseña de la Odroid. La contraseña es elfmeter (ver apartado [Wireless password] de la Ilustración 25. Si se logra conectar se observa que lo que aparece ahora en la ventana de comandos es una línea del estilo root@<pc-name> (siendo <pc-name> el nombre de la Odroid, en el caso de la configuración 1 el nombre es Konrad). Si se logra conectar con éxito entonces hay que cerrar esta ventana del terminal. Con esta acción lo que se logra es poder controlar la Odroid, los comandos que se lancen en esta terminal no se ejecutaran en el ordenador si no que se ejecutan en la Odroid. Esta va a ser la forma en la que se trabaja con ella y que va a permitir programarla. El ordenador se va a conectar al roscore que se ejecuta en la Odroid. Para ello hay que cambiar la variable ROS_HOSTNAME a la dirección IP del ordenador y la variable ROS_MASTER_URI a la dirección IP de la Odroid. Si no cambiásemos estas variables los nodos de ROS ejecutados en el ordenador no se conectarían al roscore de la Odroid ya que
Desarrollo de un vehículo autónomo a escala 25 intentarían conectarse al roscore del ordenador y por tanto no se podría lograr la comunicación entre los dos dispositivos. Para obtener la dirección IP del ordenador hay que escribir en el terminal: ifconfig La dirección aparece en el apartado enp4s0 en la sección inet addr y tiene la forma: 192.168.1.### (en mi caso era 192.168.1.105). Una vez se conoce la dirección IP del ordenador se escribe en el terminal las dos siguientes líneas: export ROS_MASTER_URI=http://192.168.1.199:11311 export ROS_IP=192.168.1.105 Esto es necesario hacerlo en cada uno de los terminales que se creen en los que se quiera comunicar con la Odroid. Para evitar esto es aconsejable añadir estas dos líneas al fichero ~/.bashrc. Para ello hay que escribir en el terminal: vi ~/.bashrc Se abrirá un fichero de texto y hay que añadir al final de este documento las dos líneas que se escribieron en el terminal. A partir de este momento los nodos de ROS creados en el ordenador se conectan automáticamente al roscore de la Odroid. Si en algún momento se quiere probar un programa de ROS en el ordenador sin usar la Odroid habría que escribir en cada terminal: export ROS_HOSTNAME=localhost export ROS_MASTER_URI=http://localhost:11311 De forma parecida, si se quiere evitar tener que escribir la contraseña de la Odroid cada vez que se conecte a ella entonces hay que escribir en el terminal la siguiente línea: ls ~/.ssh/id_rsa.pub || ssh-keygen Se presiona la tecla enter varias veces hasta que dejen de salir mensajes y después se escribe en el terminal: ssh-copy-id [email protected] Si todo se ha configurado correctamente entonces debería ser posible ver desde el ordenador los topics creados por la Odroid y también publicar información del ordenador a topics que la Odroid puede leer. Para verificar esto hay que abrir 6 terminales que denominaremos OT1, OT2, OT3 (Odroid Terminal) y CT1, CT2, CT3 (Computer Terminal). En OT1, OT2, OT3 escribimos: ssh [email protected] Debido a que el sistema descargado para la Odroid viene con un fichero Launch que se lanza cuando se enciende ya hay ciertos nodos que se ejecutan en cuanto se alimenta la Odroid. Una forma de ver esto es darse cuenta que el lidar empieza a girar cuando se alimenta la Odroid. El script que permite esto es el autostart.sh que se encuentra en el fichero root. Este script se puede modificar si se quiere añadir otros programas que se ejecuten al inicializar la Odroid.
Desarrollo de un vehículo autónomo a escala 26 En OT1 se puede observar los nodos inicializados por autostart.sh escribiendo: rosnode list Si el terminal muestra un error porque no es capaz de comunicarse con el máster entonces en OT1 escribir: roscore Una vez hecho esto escribir en OT2 lo siguiente: rostopic pub -r 1 /first std_msgs/String "data: 'hello'" Con este comando la Odroid publica en el topic first con un ratio de 1 hercio el mensaje de tipo String "hello". Y en CT1 escribir lo siguiente: rostopic echo /first Con este comando el ordenador se subscribe al topic first. Si todo funciona bien entonces en CT1 deberían verse los mensajes creados en OT2 (en este caso los mensajes de "hello") Para comprobar la comunicación en la otra dirección escribir en CT2: rostopic pub -r 1 /second std_msgs/String "data: 'bye'" Y en OT3 escribir: rostopic echo /second Si todo funciona correctamente entonces en OT3 se leerán los mensajes creados en CT2 ("bye"). Se puede obtener una representación visual de cómo están conectados estos nodos y topics escribiendo en CT3 lo siguiente: rqt_graph En la Ilustración 29 se observan los dos topics (first y second) y como en ambos hay un subscriptor y un publicador. Ilustración 29: Ejemplo rqt_graph Con esto queda comprobado que la comunicación entre el ordenador y la Odroid funciona. 4.2.3. PREPARACIÓN DEL LIDAR Para trabajar con el Lidar en ROS se utiliza la biblioteca rplidar_ros. Esto nos va a permitir detectar la distancia a la que se encuentran los objetos que rodean al coche autónomo. Hay que tener en cuenta que también detectara aquellas partes del coche que se encuentren a la misma altura que el Lidar y estos puntos por tanto deberían ser ignorados.
Desarrollo de un vehículo autónomo a escala 27 Para verificar el comportamiento del Lidar hay que cerrar todos los terminales y abrir tres nuevos que llamaremos OT1, OT2 y CT1. En OT1 y OT2 hay que escribir: ssh [email protected] En OT1 detenemos todos los procesos de ROS que se estén ejecutando en la Odroid escribiendo: pkill ros Se debe observar como el Lidar deja de girar. Después en OT1 hay que escribir: roscore En OT2 se escribe: roslaunch rplidar_ros rplidar.launch En CT1 hay que escribir: rostopic list Si todo funciona correctamente en la lista debería aparecer el topic /scan que es donde aparecen los datos obtenidos por el lidar. En este topic aparecen los puntos obtenidos por el Lidar y se podría ver la información escribiendo rostopic echo /scan sin embargo esto para nosotros no nos daría una idea de que está ocurriendo. Lo mejor es ver la información en una interfaz gráfica. Para eso se va a utilizar Rviz. En CT1 se escribe: rviz Con esto se abre la interfaz de Rviz. Para observar los datos obtenidos por el Lidar hay que realizar los siguientes pasos: En Global Options -> Fixed frame escribir laser Clicar en el botón de abajo a la izquierda "Add" clicar en la pestaña "By topic" y escoger /LaserScan Después de realizar estos pasos deberían aparecer en pantalla (ver Ilustración 30) unos puntos rojos que son los objetos que rodean al Lidar.
Desarrollo de un vehículo autónomo a escala 28 Ilustración 30: Mapa de puntos Lidar 4.2.4. PREPARACIÓN DE LA CÁMARA USB Para verificar el buen funcionamiento de la cámara el proceso es similar al de la verificación del Lidar. Para ello hay que cerrar todos los terminales y abrir tres nuevos que llamaremos OT1, OT2 y CT1. En OT1 y OT2 hay que escribir: ssh [email protected] En OT1 detenemos todos los procesos de ROS que se estén ejecutando en la Odroid: pkill ros Ahora hay que determinar en qué puerto se encuentra la cámara. Para ello se escribe en OT1: ls -ltrh /dev/video* Aparece una lista con varios puertos. Para saber cuál es el de la cámara lo mejor es desconectar la cámara de la Odroid y volver a ejecutar el comando. El puerto debería ser de la forma /dev/videoX Una vez encontrado se inicia el roscore en OT1. En OT2 se inicia la cámara escribiendo: rosrun usb_cam usb_cam_node _video_device:=/dev/video0 _pixel_format:=mjpeg _image_width:=640 _image_height:=480 Aparecerán los siguientes mensajes de error que se pueden ignorar: [swscaler @ 0x14a5f0] No accelerated colorspace conversion found from yuv422p to rgb24 [swscaler @ 0x14a5f0] deprecated pixel format used, make sure you did set range correctly
Desarrollo de un vehículo autónomo a escala 29 En CT1 se observa que los topics con la información de las imágenes se está transmitiendo escribiendo: rostopic list Deberían aparecer varios topics precedidos por /usb_cam. Para ver las imágenes de la cámara del topic /usb_cam/image_raw hay que escribir en CT1: rosrun image_view image_view image:=/usb_cam/image_raw Tras ejecutar este comando se debería poder ver en pantalla lo que está viendo la cámara Ilustración 31: Vista de la cámara USB
Desarrollo de un vehículo autónomo a escala 36 PWM DIR OUTA OUTB MARCHA MOTOR H L L H DELANTE H H H L DETRAS L X L L FRENADO setup Es la función que se ejecuta nada más iniciar el Arduino. Permite inicializar el resto de funciones y los modos de los pins utilizados. loop Es la función principal que se ejecuta en bucle mientras el Arduino está alimentado. Esta función va a ser la encargada de obtener la velocidad a la que se mueve el coche gracias al Encoder. El publicador pubTwist va a publicar en el topic twist la velocidad de giro en rad/s a la que están girando las ruedas. Este valor puede deducir a partir de la variable deltatime obtenida gracias a la función Encoder. Esta deducción se puede realizar de forma experimental o forma teórica: Forma teórica La variable deltatime almacena la cantidad de ticks del timer2 dividida entre 10 que ocurren entre dos pulsos del encoder. Sabiendo que el timer2 funciona con una frecuencia de 7812,5 Hz, es decir un periodo de y que el encoder posee 1024 divisiones se puede obtener el tiempo que el motor tarda en dar una vuelta con la formula siguiente: Sabiendo que: El valor obtenido por deltatime es dividido entre 10 El numero de es la inversa de la frecuencia, el periodo es el numero de divisiones del encoder (que vale 1024) De aquí todos los parámetros son conocidos desde el principio salvo que obtiene el encoder a partir del Arduino. Sin embargo, como el encoder está acoplado al motor y no a las ruedas este periodo de giro no da la información a la que giran. Para saberlo sería necesario conocer el ratio de giro entre el motor y las ruedas que puede obtenerse mirando los engranajes que los unen. Debido a que el acceso a estos engranajes era complicado sin tener que desmontar todo el coche decidí utilizar la forma experimental para obtener el valor que relaciona deltatime con la velocidad de giro de las ruedas.
Desarrollo de un vehículo autónomo a escala 37 Forma experimental Para la forma experimental lo que se ha realizado es modificar la línea de código twist_msg.linear.x = deltatime*0.006 Por la siguiente: twist_msg.linear.x = deltatime De esta forma el mensaje que se publicara al topic twist no es la velocidad de giro si no el valor de deltatime. Una vez modificado el código se sube al Arduino. A continuación se realiza una marca fácilmente identificable en una de las ruedas del coche (con un bolígrafo por ejemplo). Ilustración 32: Marca realizada en la rueda para calibrar el encoder Antes de realizar el siguiente paso hay que asegurarse de que las ruedas del coche no están en contacto con el suelo, es recomendable apoyar el vehículo en una caja para que rueden en el aire. Después se realizan los pasos para conectar la Odroid al Arduino que se realizaron para la calibración del encoder (apartado de la función onSteeringCommand) cambiando el comando que se escribía en OT3 por el siguiente: rostopic pub speed std_msgs/Int16 "data: 100" --once Con este comando las ruedas deberían empezar a girar lentamente. Se abre una cuarta consola CT1 y se escribe el siguiente comando: rostopic echo /twist En el terminal aparece entonces el valor de twist que al haber modificado el código el valor que se observa es el de deltatime. Una vez todo esto está preparado hay que coger un cronometro, empezar a calcular el tiempo cuando la marca realizada en la rueda pase por una determinada posición y parar cuando la marca vuelva a pasar por ese punto. Con esto se tiene entonces el tiempo que tarda en dar una vuelta la rueda con el respectivo valor de deltatime. Es recomendable repetir este proceso varías veces ya que el valor de deltatime puede variar un poco de la misma forma que el tiempo obtenido por el cronometro. Una vez realizado el proceso varias veces, eliminar aquellos valores que se
Desarrollo de un vehículo autónomo a escala 38 diferencien mucho de los demás que seguramente sean culpa de un error de medida y realizar la media con los restantes. Esto se ha realizado publicando un valor de 100 en el topic /speed. Realizar este proceso para otros valores también, hasta que las ruedas giren a una velocidad a partir de la cual no es posible de distinguir con claridad cuando la marca vuelve a pasar por el sitio observado. En la tabla siguiente se observan los valores que obtuve durante la calibración del coche y con los que obtuve el valor de 0,006. Dividiendo el tiempo que tarda en dar una vuelta por deltaTime se obtiene el cociente por el cual hay que multiplicar deltaTime para saber el tiempo que tarda en girar la rueda. Valor motor Tiempo para dar una vuelta (s) deltaTime Tiempo/deltaTime 100 6,5 1000 0,0065 150 3,42 590 0,0058 200 2,4 400 0,006 250 1,84 300 0,0061 300 1,5 250 0,006 Con esto se obtiene el tiempo que tarda la rueda en dar una vuelta. (3) Si se quieren obtener los rad/s se obtienen con la siguiente ecuación: (4) Si se quiere obtener la velocidad del coche hay que tener en cuenta que el diámetro D de las ruedas es de 6 cm: (5)
Desarrollo de un vehículo autónomo a escala 39 6) RESULTADOS EXPERIMENTALES Una vez realizados todos los pasos anteriores, el coche autónomo ha quedado listo para ser programado y ser utilizado como réplica de los coches autónomos proporcionados por SEAT para el "SEAT autonomous driving challenge". En este capítulo se pretende mostrar cómo se han calibrado y programado los distintos programas de prueba para determinar que el coche realmente puede conducir de forma autónoma. Estos programas pretenden servir como plantilla para los próximos usuarios dando ejemplos de cómo hacer los programas que utilizan los diferentes sensores y actuadores. 6.1. CREACIÓN DE UN ESPACIO DE TRABAJO ROS El primer paso que hay que realizar antes de empezar a programar en ROS es crear un espacio de trabajo ROS en la Odroid y en el ordenador. Es necesario crearlo también en el ordenador ya que la Odroid carece de interfaz gráfica y para programar los programas que usan la cámara es necesario poder observar las imágenes obtenidas y ver cómo están siendo manipuladas por el programa. Se van a describir los pasos necesarios para crear el espacio de trabajo en la Odroid que, en este caso, son los mismos que para crearlo en el ordenador. El primer paso es conectarse con la Odroid escribiendo en el terminal ssh [email protected] Una vez conectado se crea el espacio de trabajo el cual se va a almacenar en la carpeta catkin_ws . Para ello se escribe en el terminal lo siguiente: mkdir -p ~/catkin_ws/src cd ~/catkin_ws/ catkin_make source devel/setup.bash Para poder utilizar los programas guardados en este espacio de trabajo utilizando el comando rosrun es necesario modificar el archivo .bashrc. Para ello se escribe en el terminal: cd nano .bashrc Se abre un editor de texto y se añade la siguiente línea al final del fichero: source ~/catkin_ws/devel/setup.bash 6.2. CREACIÓN DEL PAQUETE SIGUE_LINEAS Una vez creado el espacio de trabajo ya se pueden crear los paquetes con los que se va a trabajar. En este caso se crea el paquete que se va a encargar de dirigir a la cámara para que el coche siga las líneas de la carretera. Este paquete va a tener varias dependencias, las cuales se incluyen desde su creación para que resulte más fácil. Se pueden añadir nuevas dependencias más tarde modificando el fichero CMakeLists.txt y package.xml, por lo que no es necesario incluir todas desde el comienzo. Para crear el paquete sigue_lineas se escribe lo siguiente en el terminal cd ~/catkin_ws/src
Desarrollo de un vehículo autónomo a escala 40 catkin_create_pkg sigue_lineas camera_info_manager cv_bridge image_transport message_runtime roscpp rospy sensor_msgs std_msgs geometry_msgs Estas dependencias son las que permiten a este paquete obtener información de la cámara y poder trabajar así con las imágenes recibidas. Las dependencias std_msgs y geometry_msgs son las que permiten enviar y recibir mensajes al Arduino para poder controlar el motor y el servomotor. Debido a que este paquete va a utilizar la biblioteca OpenCV para tratar las imágenes es necesario añadir también el paquete de OpenCV. Para ello se escribe en el terminal: cd ~/catkin_ws/src/sigue_lineas nano CMakeLists.txt Este comando abre con el editor de texto nano el documento CMakeLists.txt. A este documento hay que añadirle la siguiente línea: find_package(OpenCV) Además, hay que modificar la sección "include_directories" para que incluya también OpenCV. El texto debería quedar de la siguiente forma: include_directories( # include ${catkin_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ) Este paquete contiene dos programas: camera_publisher.cpp y camera_sigue_lineas.cpp es por ello que al final de CMakeLists.txt hay que indicarlo escribiendo: add_executable(camera_publisher src/camera_publisher.cpp) target_link_libraries(camera_publisher ${catkin_LIBRARIES} ${OpenCV_LIBRARIES}) add_executable(camera_sigue_lineas src/camera_sigue_lineas.cpp) target_link_libraries(camera_sigue_lineas ${catkin_LIBRARIES} ${OpenCV_LIBRARIE$ 6.2.1. PROGRAMA CAMERA_PUBLISHER El programa camera_publisher que se puede leer en el Anexo 2 es el encargado de obtener las imágenes de la cámara y publicarlas en forma de topic. Este programa crea el nodo image_publisher el cual pública en el topic camera/image las imágenes obtenidas por la cámara. Las imágenes de la cámara son obtenidas en el programa gracias a la biblioteca OpenCV, sin embargo, no se pueden publicar estas imágenes en ROS directamente. Es necesario utilizar la biblioteca cv_bridge para poder publicar las imágenes en un topic. 6.2.2. PROGRAMA CAMERA_SIGUE_LINEAS El programa camera_sigue_lineas que se puede leer en el Anexo 3 es el encargado de procesar las imágenes obtenidas por la cámara para determinar cómo debe girar el coche para mantenerse dentro del carril.
Desarrollo de un vehículo autónomo a escala 41 Para realizar estas acciones el programa se subscribe al topic camera/image en el cual el programa camera_publisher publica la información de la cámara. Para controlar el servomotor, el programa publica en el topic steering al cual está subscrito el Arduino. La función imageCallback es la encargada de procesar la imagen y determinar el ángulo necesario del servomotor. Lo primero que realiza es transformar la información del topic camera/image en una matriz BGR (blue green red) que puede ser utilizada por OpenCV que se puede observar en la Ilustración 33. Ilustración 33: Imagen original obtenida por la cámara USB La imagen obtenida se pasa a una matriz en escala de grises utilizando la función cvtColor (ver Ilustración 34). En este tipo de escala los pixeles solo tienen un valor a diferencia de las matrices BGR en las cuales los pixeles están compuestos de tres valores diferentes. Ilustración 34: Imagen convertida a escala de grises Este cambio se realiza para poder aplicar la función threshold a la imagen. La función threshold tiene como objetivo transformar la imagen en escala de grises en una imagen binarizada con solo blancos y negros. En esta escala, los pixeles blancos tienen el valor máximo (255) y los pixeles negros tienen el valor mínimo (0).
Desarrollo de un vehículo autónomo a escala 42 La función threshold tiene 5 modos distintos de funcionamiento, como se puede observar en la página de OpenCV [12]. En este caso se va a utilizar el método threshold binary inverted. Este modo lo que hace es que los pixeles con un valor superior al valor límite seleccionado por el usuario pasan a tener un valor de 0 mientras que los pixeles que tienen un valor inferior pasan a tener un valor máximo, también definido por el usuario. Esta función se representa con la siguiente expresión: Si ValorPixel > Límite entonces ValorPixel = 0 Si ValorPixel <= Límite entonces ValorPixel=ValorMáx El objetivo de obtener esta imagen en blanco y negro es que las líneas que delimitan el carril de la carretera tengan un color distinto al del resto de la imagen para así poder obtener fácilmente dónde se encuentran. Esto se puede observar en la Ilustración 35. El siguiente paso, por tanto, es determinar cuál es el valor límite a seleccionar. Para ello hay que tener en cuenta que las líneas de la carretera son blancas y por tanto tendrán valores de 255. Sin embargo, debido a las condiciones de iluminación de donde se estén realizando las pruebas este valor no va a ser exactamente de 255. Por tanto, la mejor manera de obtener este valor es obtener la imagen en escala de grises de la carretera y observar cual es el valor de los pixeles. Hay que tener cuidado ya que si se escoge un valor límite muy alto podría haber ciertas zonas de las líneas menos iluminadas que tengan un valor inferior y no se reconocerían como tales. Por otro lado, un valor límite muy pequeño tendría el efecto contrario provocando que el programa interprete que ciertos pixeles forman parte de las líneas de la carretera cuando no es así, como se puede observar en la Ilustración 36. Ilustración 35: Imagen binarizada. Líneas de la carretera en negro
Desarrollo de un vehículo autónomo a escala 43 Ilustración 36: Imagen binarizada con un valor límite incorrecto Una vez obtenidas las imágenes binarizadas que permiten separar los pixeles de las líneas de la carretera de los demás, falta el algoritmo para determinar la dirección del coche. El primer objetivo, por tanto, es determinar qué dirección tiene la carretera. Para ello, se toman dos filas de la matriz de la imagen binarizada y se obtiene el "centro de gravedad" de las marcas viales. Este "centro de gravedad" se obtiene sumando la posición en el eje horizontal de los pixeles de las líneas de la carretera y dividiendo este sumatorio por el número estos pixeles que hay en esa fila. Este cálculo se puede expresar con la siguiente fórmula: (6) Siendo: c: el número de columnas de la matriz x: el valor del pixel numero i de la fila seleccionada. Valiendo 255 aquellos que corresponden con una línea de la carretera y 0 los demás. n: el número de pixeles de la fila seleccionada correspondientes a líneas de la carretera Utilizando esta fórmula se obtiene el punto central del carril en la fila escogida. Se repite este proceso con otra fila de la matriz obteniéndose así el punto central del carril en dos zonas diferentes. Se puede observar la obtención de estos centros de gravedad en la Ilustración 37.
Desarrollo de un vehículo autónomo a escala 44 Ilustración 37: Centro de gravedad de las líneas de la carretera Uniendo estos dos puntos se crea una recta que determina la dirección de la carretera. Para determinar la dirección que debe tomar el servomotor, se usa un controlador proporcional que toma como parámetros la distancia del coche a la recta y el ángulo de la recta. La variable de la distancia es la que va a permitir al coche ir centrado en la carretera mientras que la variable del ángulo es la que le va a permitir ir con la misma dirección que la carretera. Ilustración 38: Distancia y ángulo del coche a la dirección de la carretera Para obtener los parámetros del ángulo y la distancia hay que tener en cuenta que ya se conoce la posición de los dos centros de gravedad y que se considera que el coche se encuentra en la columna central de la matriz en la fila inferior. Por tanto, hay que utilizar las distancias verticales y horizontales de los centros de gravedad respecto del coche como se pueden observar en la Ilustración 39.
Desarrollo de un vehículo autónomo a escala 45 Ilustración 39: Distancias de los centros de gravedad al coche Aplicando trigonometría se obtiene la distancia y el ángulo usando las siguiente fórmulas: (7) (8) Una vez se tienen estos parámetros se obtiene el ángulo que hay que enviar al servomotor utilizando la siguiente fórmula: (9) Siendo Ka y Kd coeficientes obtenidos empíricamente hasta seguir correctamente la carretera. 6.2.3. VERIFICACIÓN DEL PAQUETE SIGUE_LINEAS Los códigos del paquete sigue_lineas hay que pegarlos dentro de la carpeta src que se encuentra en la carpeta sigue_lineas. Una vez hecho esto hay que compilar el código, para ello se escribe en el terminal: cd ~/catkin_ws catkin_make Una vez realizado esto, el coche está listo para realizar el seguimiento de las líneas. Para ello lo primero es realizar una carretera con alguna curva para comprobar que sigue correctamente las líneas. Es aconsejable utilizar una superficie oscura y con unas condiciones de iluminación en las cuales la luz no refleje demasiado en el suelo. Después, utilizando cinta aislante blanca se crean las líneas que delimitan la carretera.
Desarrollo de un vehículo autónomo a escala 52 Ilustración 46: Diagrama de flujo adelantamiento
Desarrollo de un vehículo autónomo a escala 53 La zona de detección 2 determina a partir de qué momento el coche procede a girar en el otro sentido para ponerse paralelo al obstáculo. Empieza a girar en el momento en el que no detecta obstáculo en esa zona, en este caso la distancia no tiene importancia puesto que el coche se encontrara cerca del objeto. Valores con los que se ha iterado: Numero de iteración Rango del ángulo Comentarios 1 0º - 90º El coche tarda demasiado en iniciar la maniobra alejándose demasiado del obstáculo. 2 45º - 90º El coche adelanta de forma correcta 3 60º - 90º El coche inicia demasiado pronto el giro a la derecha y golpea contra el obstáculo. Con la iteración 2 el coche realizaba la maniobra correctamente, sin embargo, se quiso apurar más la maniobra con la iteración 3. Finalmente se guardaron los valores de la iteración 2. Finalmente queda por calibrar la zona de detección 3 que determina a partir de qué momento se considera que el coche ha rebasado el obstáculo y puede por tanto empezar a girar para recuperar su posición en el carril derecho. Los valores con los que se ha iterado son los siguientes: Numero de iteración Rango del ángulo Comentarios 1 45º - 90º El coche intenta la maniobra demasiado pronto y se golpea contra el obstáculo. 2 0º - 90º El coche adelanta de forma correcta. Tras calibrar las tres zonas de detección el coche logra realizar la maniobra de adelantamiento de forma correcta sin golpear el obstáculo. 6.4. CREACIÓN DEL PAQUETE APARCAMIENTO Tras haber creado el paquete evita_obstaculos se procedió a crear el programa aparcamiento.cpp (ver anexo 7) en el paquete aparcamiento.. Las dependencias del paquete son las siguientes: roscpp rospy sensor_msgs std_msgs geometry_msgs 6.4.1. PROGRAMA APARCAMIENTO El objetivo de este programa es que el coche realice un aparcamiento en paralelo entre dos coches. Este programa es similar al utilizado para evitar obstáculos. Al igual que en ese programa, se ha dividido la maniobra en distintos movimientos que se pueden observar con las flechas de colores en la Ilustración 47.
Desarrollo de un vehículo autónomo a escala 54 Ilustración 47: Maniobra de aparcamiento En la primera maniobra, que se encuentra representada en azul, el coche se mueve hacia detrás girando las ruedas hacia la derecha. En la segunda maniobra, representada en rojo, el coche se mueve hacia detrás girando las ruedas hacia la izquierda. En la última maniobra, representada en verde, el coche se mueve hacia delante girando las ruedas hacia la derecha. El coche debe realizar estas maniobras sin sobrepasar la línea negra que representa el borde de la carretera. Estas maniobras buscan reproducir el comportamiento que tiene habitualmente un conductor a la hora de aparcar en paralelo. Al acabar estas dos maniobras el coche autónomo debería encontrarse entre el coche1 y el coche2 y estar alineado con ellos. 6.4.2. VERIFICACIÓN DEL PAQUETE APARCAMIENTO Después de realizar el programa hay que ajustar los parámetros para que el aparcamiento se realice correctamente. Para realizar las pruebas se colocan dos objetos que representen al coche1 y al coche2 (para este trabajo se han utilizado dos cajas de zapato) y se disponen de forma que quede un espacio entre ellos que será donde el coche tendrá que aparcar. A continuación, se coloca el coche autónomo en paralelo a uno de estos obstáculos de forma que la parte trasera sea la parte más cercana del vehículo al aparcamiento. Estas maniobras disponen de tres parámetros: tiempo girando, ángulo de giro y velocidad de desplazamiento. Al ser una maniobra que debe realizarse en poco espacio se utilizan los ángulos de giro más altos permitidos por el servomotor. Se escoge una velocidad suficientemente lenta para que el coche maniobre sin riesgo y lo suficientemente rápida para no tardar demasiado en ejecutar los movimiento. Finalmente el valor escogido es de 300 cuando avanza hacia delante y -300 cuando retrocede. De esta forma quedan fijadas dos de las tres variables. Al ser un proceso secuencial, se ha ajustado el tiempo de cada maniobra en el mismo orden en el que se ejecutan en el programa.
Desarrollo de un vehículo autónomo a escala 55 No hay una fórmula exacta para determinar hasta donde debe llegar cada una de las tres maniobras, hay que aplicar el sentido común y fijarse en que el coche se encuentra aparcado correctamente tras realizarlas. Para la primera maniobra se probaron los siguientes tiempos: Iteración Tiempo maniobra 1 Observaciones 1 4 segundos El coche no gira suficiente. Cuando realice la siguiente maniobra no va a quedar bien situado. 2 6 segundos El coche gira demasiado. Cuando realice la siguiente maniobra va a superar el límite de la carretera. 3 5 segundos El coche queda bien situado para las siguientes maniobras. Para la segunda maniobra se trabajó con los siguientes tiempos Iteración Tiempo maniobra 2 Observaciones 1 4 segundos El coche gira demasiado quedando mal situado. 2 3 segundos El coche queda en una buena situación para iniciar la siguiente maniobra. Al finalizar la última maniobra, el coche debe quedar bien aparcado. Se trabajo con los siguientes tiempos: Iteración Tiempo maniobra 3 Observaciones 1 1 segundo El coche no gira lo suficiente como para quedar bien alineado. 2 2 segundos El coche queda bien situado y alineado con los otros dos. 6.5. CREACIÓN DEL PAQUETE SEMAFORO El último de los paquetes realizados es el semaforo. Este paquete al utilizar la cámara tiene las mismas dependencias que el paquete sigue_lineas 6.5.1. PROGRAMA SEMAFORO El objetivo de este programa es que el coche sea capaz de detectar la luz de los semáforos deteniéndose en caso de que se encuentren en rojo. Para la realización de este programa (ver anexo 8) se han realizado las siguientes hipótesis: Los semáforos siempre se sitúan en el lado izquierdo de la carretera. Esta hipótesis permite reducir la carga computacional del programa ya que solo es necesario realizar el estudio de la parte izquierda de la imagen en la búsqueda de la luz del semáforo.
Desarrollo de un vehículo autónomo a escala 56 Los semáforos son los únicos objetos rojos con los que el coche se va a cruzar. Por la forma en la que se realiza el programa si hubiese otros objetos rojos el coche podría confundirlos con las luces del semáforo. El programa se suscribe al topic camera/image (al igual que el programa sigue_lineas), el cual contiene las imágenes obtenidas por la cámara en formato bgr (blue green red). Si el semáforo está en rojo el valor red de los pixeles obtenidos será muy alto, sin embargo, está no es condición suficiente para detectar el rojo ya que el color blanco de las líneas de la carretera también tiene un valor red elevado. Por tanto para determinar si realmente los pixeles observados son rojos no solo es necesario que su valor red sea alto si no que además tienen que tener un valor reducido de blue y de green. Teniendo todo esto en cuenta, las acciones que realiza el programa son las siguientes: Obtiene el valor red de los pixeles situados en la parte izquierda de la imagen. Obtiene el valor blue y green de aquellos pixeles con un valor red elevado. Si ningún pixel cumple los criterios para ser considerado rojo entonces el coche sigue avanzando (o arranca en caso de que el coche estuviese parado por culpa de haber detectado previamente que el semáforo estaba en rojo). Si algún pixel es considerado rojo entonces se para el motor del vehículo. 6.5.2. VERIFICACIÓN DEL PAQUETE SEMAFORO Para verificar el funcionamiento del paquete es necesario disponer de un dispositivo que actué como semáforo. Aunque la idea más intuitiva pueda ser realizarlo con unos LEDs esta no es buena idea ya que cuando la cámara observa directamente los LEDs se satura y los interpreta como blancos. Para este trabajo se ha utilizado un móvil en el cual se muestran imágenes rojas o verdes por la pantalla utilizando un valor poco elevado de luminosidad (para evitar saturar la imagen de la cámara). Para este paquete los únicos valores que hace falta calibrar son los valores de red, green y blue utilizados para determinar si un pixel es de color rojo. Estos valores se obtienen fácilmente a través de la pantalla de visualización de imágenes de la cámara como se observa en la Ilustración 31.
Desarrollo de un vehículo autónomo a escala 57 7) CONCLUSIONES 7.1. LOGROS TÉCNICOS Como se ha descrito en el capítulo 1, para el desarrollo de este trabajo se definieron varios subobjetivos. En este apartado se van a describir los logros técnicos obtenidos correspondientes a estos objetivos: Diseño hardware del vehículo Este era el primer objetivo del proyecto ya que era necesario conocer el hardware que se iba a utilizar para poder trabajar con él. El proyecto buscaba realizar un coche a escala con los componentes necesarios para que pueda conducir de forma autónoma. De esta forma se ha desarrollado un vehículo funcional que únicamente necesita una cámara y un lidar para obtener la información de su entorno. Control de alto nivel Odroid. Instalación y programación del módulo ROS El siguiente objetivo era acondicionar el ordenador montado en el coche para que funcione correctamente. Se ha instalado en la Odroid todos los programas y bibliotecas ROS necesarios y se ha verificado su capacidad para comunicarse y obtener información del lidar y de la cámara. Este proceso ha quedado documentado de forma exhaustiva en forma de guía asegurando que el trabajo sea fácilmente reproducible en caso de ser necesario. Control de bajo nivel Arduino El programa realizado para el Arduino le permite recibir y enviar ordenes correctamente a la Odroid y controlar los actuadores del coche. Además se ha calibrado el servomotor para obtener un control adecuado del mismo y el encoder para conocer la velocidad del coche. Resultados experimentales Este último subobjetivo pretendía confirmar la validez del modelo como coche autónomo. Para ello se ha realizado con éxito cuatro programas que permiten al coche seguir las líneas de la carretera, adelantar a un obstáculo que se encuentre en el carril ,realizar una maniobra de aparcamiento y detectar la luz de un semáforo. Realización de un coche autónomo Este era el objetivo principal del proyecto y habiendo cumplido todos los subobjetivos que lo componen se verifica la creación de un modelo a escala capaz de conducir de forma autónoma y de participar en próximas competiciones. 7.2. CONOCIMIENTOS ADQUIRIDOS Los logros realizados durante el proyecto no son solo técnicos ya que la realización de este proyecto también tiene un carácter pedagógico. La realización de este proyecto permite a su realizador adquirir las siguientes competencias: Trabajar en un entorno Linux sin interfaz gráfica.
Desarrollo de un vehículo autónomo a escala 58 Profundizar en el lenguaje de programación C++. Aprendizaje de la herramienta ROS. Manipulación de imágenes obtenidas con una cámara. Tratamiento de datos obtenidos con un Lidar. Impresión de modelos 3D. 7.3. POSIBLES LÍNEAS DE DESARROLLO En este apartado se incluyen posibles mejoras que pueden realizarse en el futuro: Afrontar nuevos retos que puedan ser incluidos en las próximas competiciones como puede ser el adelantamiento de objetos móviles o el reconocimiento de otros tipos de marcas viales horizontales o verticales. Realizar un circuito completo y realista a escala en el que pueda funcionar este prototipo. Este circuito abre la posibilidad de desarrollar nuevos algoritmos y estrategias de conducción autónoma. Al estar en unas condiciones realistas este desarrollo puede ser el paso previo a la aplicación de los algoritmos en un vehículo real simplificando el proceso y aumentando la seguridad.
Desarrollo de un vehículo autónomo a escala 59 8) BIBLIOGRAFÍA [1] Dirección General de Tráfico (2015). "CUESTIONES DE SEGURIDAD VIAL, CONDUCCIÓN EFICIENTE, MEDIO AMBIENTE Y CONTAMINACIÓN" . http://www.dgt.es/Galerias/seguridad-vial/formacion-vial/cursos-para-profesores-ydirectores-de-autoescuelas/XVIII-Curso-de-Profesores/Seguridad-Vial.pdf p.17 [2] Toyota (2018). "NIVELES DE CONDUCCIÓN AUTÓNOMA". https://www.toyota.es/world-of-toyota/articles-news-events/niveles-de-conduccionautonoma [3] SEAT (16 de Noviembre 2017). "Alumnos de la Universidad de Valladolid ganan la primera edición del SEAT Autonomous Driving Challenge". https://www.seatmediacenter.es/newspage/allnews/company/2017/Alumnos-de-la-Universidad-deValladolid-ganan-la-primera-edicion-del-SEAT-Autonomous-Driving-Challenge.html [4] Freie Universität Berlin (2018). "AutoNOMOS Model". https://github.com/AutoModelCar/AutoModelCarWiki/wiki [5] Michael Stonebank (2001). "UNIX Tutorial" http://www.ee.surrey.ac.uk/Teaching/Unix/unix1.html [6] Página oficial de Arduino (En línea). https://www.arduino.cc/en/main/software [7] Página oficial de ROS (En línea). https://www.ros.org/ [8] Página ROS rplidar (En línea). https://wiki.ros.org/rplidar [9] Página ROS rosserial (En línea). http://wiki.ros.org/rosserial [10] Página ROS usb_cam (En línea). http://wiki.ros.org/usb_cam [11] Página ROS Kinetic (En línea). http://wiki.ros.org/kinetic/Installation/Ubuntu [12] Rubén E-Marmolejo (2017). "Arduino Timer - Interrupciones con el Timer2" https://hetpro-store.com/TUTORIALES/arduino-timer/ [13] Página OpenCV (2019) "Basic thresholding operations" https://docs.opencv.org/2.4/doc/tutorials/imgproc/threshold/threshold.html
Desarrollo de un vehículo autónomo a escala 60
Desarrollo de un vehículo autónomo a escala 61 9) ANEXOS
Desarrollo de un vehículo autónomo a escala 68 9.5. Anexo 5: client.cpp #include "ros/ros.h" #include "sensor_msgs/LaserScan.h" #define RAD2DEG(x) ((x)*180./M_PI) void scanCallback(const sensor_msgs::LaserScan::ConstPtr& scan) { int count = scan->scan_time / scan->time_increment; ROS_INFO("I heard a laser scan %s[%d]:", scan- >header.frame_id.c_str(), count); ROS_INFO("angle_range, %f, %f", RAD2DEG(scan- >angle_min), RAD2DEG(scan->angle_max)); for(int i = 0; i < count; i++) { float degree = RAD2DEG(scan->angle_min + scan- >angle_increment * i); ROS_INFO(": [%f, %f]", degree, scan->ranges[i]); } } int main(int argc, char **argv) { ros::init(argc, argv, "rplidar_node_client"); ros::NodeHandle n; ros::Subscriber sub = n.subscribe<sensor_msgs::LaserScan>("/scan", 1000, scanCallback); ros::spin(); return 0;
Desarrollo de un vehículo autónomo a escala 69 9.6. Anexo 6: evita.cpp #include "ros/ros.h" #include "sensor_msgs/LaserScan.h" #include <std_msgs/Float32.h> #include <ctime> #include <sys/time.h> #define RAD2DEG(x) ((x)*180./M_PI) ros::Publisher pubSteering; int maniobra=0; struct timeval tCGiro,tFGiro; unsigned long tGirando; void scanCallback(const sensor_msgs::LaserScan::ConstPtr& scan) { std_msgs::Float32 ang_msg; int count = scan->scan_time / scan->time_increment; int contadorObstaculo=0; ROS_INFO("Maniobra %d", maniobra); if(maniobra == 0){ for(int i = 225; i < 270; i++) { //float degree = RAD2DEG(scan- >angle_min + scan->angle_increment * i); if(scan->ranges[i]<0.65){ contadorObstaculo += 1; } } if (contadorObstaculo >= 3){ maniobra=1; ang_msg.data=-20; pubSteering.publish(ang_msg); gettimeofday(&tCGiro, NULL); } } else if(maniobra == 1){ for(int i = 225; i < 270; i++) { if(scan->ranges[i]<0.65){ contadorObstaculo += 1; } } if (contadorObstaculo == 0){ maniobra = 2; ang_msg.data = 20; pubSteering.publish(ang_msg); gettimeofday(&tFGiro, NULL); tGirando = (tFGiro.tv_sec - tCGiro.tv_sec) * 1000000 + tFGiro.tv_usec - tCGiro.tv_usec; gettimeofday(&tCGiro, NULL); } } else if(maniobra == 2){ gettimeofday(&tFGiro, NULL); if ((tFGiro.tv_sec - tCGiro.tv_sec) * 1000000 + tFGiro.tv_usec - tCGiro.tv_usec >= tGirando){ maniobra = 3; ang_msg.data = 0; pubSteering.publish(ang_msg); } } else if(maniobra == 3){ for(int i = 180; i < 270; i++) { if(scan->ranges[i]<0.65){ contadorObstaculo += 1; } } if (contadorObstaculo == 0){ maniobra = 4; ang_msg.data = 20; pubSteering.publish(ang_msg); gettimeofday(&tCGiro, NULL); } } else if(maniobra == 4){ gettimeofday(&tFGiro, NULL); if ((tFGiro.tv_sec - tCGiro.tv_sec) * 1000000 + tFGiro.tv_usec - tCGiro.tv_usec >= tGirando){ maniobra = 5; ang_msg.data = -20; pubSteering.publish(ang_msg); gettimeofday(&tCGiro, NULL); } } else if(maniobra == 5){ gettimeofday(&tFGiro, NULL); if ((tFGiro.tv_sec - tCGiro.tv_sec) * 1000000 + tFGiro.tv_usec - tCGiro.tv_usec >= tGirando){ maniobra = 0; ang_msg.data = 0; pubSteering.publish(ang_msg); } } } int main(int argc, char **argv) { ros::init(argc, argv, "rplidar_node_client"); ros::NodeHandle nh; ros::Subscriber sub = nh.subscribe<sensor_msgs::LaserScan>("/scan", 1000, scanCallback); pubSteering = nh.advertise<std_msgs::Float32>("steering",1000); ros::spin(); return 0; }
Desarrollo de un vehículo autónomo a escala 70 9.7. Anexo 7: aparcamiento.cpp #include "ros/ros.h" #include "sensor_msgs/LaserScan.h" #include <std_msgs/Float32.h> #include <std_msgs/Int16.h> #include <ctime> #include <sys/time.h> ros::Publisher pubSteering; ros::Publisher pubSpeed; std_msgs::Float32 ang_msg; std_msgs::Int16 speed_msg; struct timeval tCGiro,tFGiro; void move(int time, int angle, int speed){ speed_msg.data = speed; pubSpeed.publish(speed_msg); ang_msg.data = angle; pubSteering.publish(ang_msg); gettimeofday(&tCGiro, NULL); gettimeofday(&tFGiro, NULL); while ((tFGiro.tv_sec - tCGiro.tv_sec) * 1000000 + tFGiro.tv_usec - tCGiro.tv_usec < time * 1000000){ gettimeofday(&tFGiro, NULL); } } int maniobra = 0; int main(int argc, char **argv) { ros::init(argc, argv, "aparcamiento_node"); ros::NodeHandle nh; pubSteering = nh.advertise<std_msgs::Float32>("steering",1000); pubSpeed = nh.advertise<std_msgs::Int16>("speed",1000); ros::Rate loop_rate(10); while(ros::ok()){ if(maniobra == 0){ ROS_INFO("primera maniobra"); move(5,20,-400); ROS_INFO("segunda maniobra"); move(3,-20,-400); ROS_INFO("tercera maniobra"); move(2,20,400); ROS_INFO("parar"); move(0,0,0); maniobra = 1; } ros::spinOnce(); loop_rate.sleep(); } //return 0; }
Desarrollo de un vehículo autónomo a escala 71 9.8. Anexo 8: semaforo.cpp #include <ros/ros.h> #include <image_transport/image_transport.h> #include <opencv2/highgui/highgui.hpp> #include <cv_bridge/cv_bridge.h> #include <std_msgs/Int16.h> using namespace std; using namespace cv; ros::Publisher pubSpeed; void imageCallback(const sensor_msgs::ImageConstPtr& msg) { std_msgs::Int16 speed_msg; try { Mat colorFrame=cv_bridge::toCvShare(msg, "bgr8")->image; cv::imshow("view",colorFrame); cv::waitKey(30); int nf=colorFrame.rows; int nc=190; //Se observa solo unas columnas de la imagen colorFrame.cols; //Vec3b pixel = colorFrame.at<Vector3b>(Point(0,0)); uint8_t* pixelPtr = (uint8_t*)colorFrame.data; int cn = colorFrame.channels(); Scalar_<uint8_t> bgrPixel; int rojo = 0; for(int i = 0; i < nf; i++) { for(int j = 0; j < nc; j++) { if(pixelPtr[i*190*cn + j*cn + 2] > 200) //Se observa si valor del rojo alto { if(pixelPtr[i*190*cn + j*cn] < 200 && pixelPtr[i*190*cn + j*cn + 1] < 200) //Azul y verde bajos. Así sabemos que no es Blanco { rojo = 1; i = nf; j = nc; } } } } speed_msg.data = (1-rojo)*400; pubSpeed.publish(speed_msg); ROS_INFO_STREAM("Valor rojo: " << rojo << " "); } catch (cv_bridge::Exception& e) { ROS_ERROR("Could not convert from '%s' to 'bgr8'.", msg- >encoding.c_str()); } } int main(int argc, char **argv) { ros::init(argc, argv, "semaforo_listener"); ros::NodeHandle nh; cv::namedWindow("view"); cv::startWindowThread(); image_transport::ImageTransport it(nh); image_transport::Subscriber sub = it.subscribe("camera/image", 1, imageCallback); pubSpeed = nh.advertise<std_msgs::Int16>("speed",1000); ros::spin(); cv::destroyWindow("view"); }