scieee AI-readable full text Open interactive document viewer

Detección de señales de tráfico para sistemas de conducción autónoma

Palma Espada, Juan Manuel

Abstract

Actualmente en el mundo de la tecnología se le da mucha importancia a la inteligencia artificial y a los modelos de machine y Deep Learning para la resolución de multitud de problemas. Uno de los puntos críticos de estas aplicaciones es la automatización de tareas de forma correcta y eficaz, de lo contrario podrían suponer grandes problemas a la hora de incorporar estos modelos en el mundo tecnológico. En este proyecto realizado se pretenderá construir un prototipo de vehículo automatizado que detecte un conjunto de señales de tráfico y las clasifique realizando inferencia con la placa Google Coral Dev Board, a la que previamente se le ha introducido el modelo ya entrenado. Además, se estudiarán y se compararán diferentes redes neuronales con el fin de encontrar un resultado óptimo para la aplicación propuesta. Finalmente, también como parte del desarrollo de este Trabajo Fin de Máster, se pondrá de manifiesto el uso de microcontroladores y actuadores adicionales a la Google Coral Dev Board, como son el Arduino junto con sus actuadores (motores, controladores, etc.) y el sensor ambiental de la propia placa de Google para establecer un envío de ciertos parámetros mediante el protocolo MQTT para la correcta representación de los mismos usando la herramienta Grafana.

Full text

Equation Chapter 1 Section 1 Trabajo Fin de Máster Máster Universitario en Ingeniería de Telecomunicación Detección de señales de tráfico para sistemas de conducción autónoma Dpto. Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Autor: Juan Manuel Palma Espada Tutores: Rubén Martín Clemente, Juan José Murillo Fuentes dde Sevilla, 2024 ii iii Trabajo Fin de Máster Máster Universitario en Ingeniería de Telecomunicación Detección de señales de tráfico para sistemas de conducción autónoma Autor: Juan Manuel Palma Espada Tutores: Rubén Martín Clemente Juan José Murillo Fuentes Catedráticos de Universidad Dpto. de Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2024 iv v Trabajo Fin de Máster: Detección de señales de tráfico para sistemas de conducción autónoma Autor: Juan Manuel Palma Espada Tutores: Rubén Martín Clemente, Juan José Murillo Fuentes El tribunal nombrado para juzgar el Proyecto arriba indicado, compuesto por los siguientes miembros: Presidente: Vocales: Secretario: Acuerdan otorgarle la calificación de: Sevilla, 2024 El Secretario del Tribunal vi vii A mis padres y hermanos A mis chavales y chavalas A Juan y a José viii ix Agradecimientos A mi familia principalmente por ser el apoyo fundamental y haberme enseñado a no rendirme y alcanzar mis metas con trabajo y esfuerzo siendo un pilar esencial en mi vida, sin ellos este proyecto no hubiese sido posible. A mis amigos por tanto buenos momentos vividos a su lado, ayudándome a evadirme de problemas y dándome consejos para afrontar esta larga travesía. Juan Manuel Palma Espada Sevilla, 2024 xvi ÍNDICE DE TABLAS 1 Características técnicas de la Google Coral Dev Board. 4 2 Sensores disponibles para la Google Coral y sus características. 11 3 Correspondencia de las señales de tráfico con sus clases. 26 xvii ÍNDICE DE FIGURAS 1 Distribución de puertos y pines de la Google Coral. 4 2 Diagrama de bloques interno de la Google Coral. 5 3 Diagrama del SoM incorporado en la Google Coral. 6 4 Posición de los switches para el modo de arranque. 8 5 Conexionado para el flasheo del dispositivo. 8 6 Conexión de la placa con el puerto serie. 9 7 Disposición de pines, botón y pantalla del módulo ambiental. 10 8 Módulo de la cámara. 11 9 Esquema de puertos del Arduino Mega. 13 10 Diagrama de bloques sobre el funcionamiento de la Plataforma IOT Cloud. 14 11 Esquema de las capas de una red convolucional típica. 17 12 Operación de convolución con el kernel. 17 13 Representación gráfica de la operación Maxpooling. 18 14 Representación de las capas densas 18 15 Representación del dropout. 19 16 Representación del flatten. 19 17 Fórmula matemática resultado de la capa softmax. 20 18 Concepto de Transfer Learning. 21 19 Convolución depthwise y pointwise. 22 20 Estructura de una red Resnet50. 22 21 Estructura de la red VGG19. 23 22 Diagrama de bloques del prototipo completo (obviando IoT). 25 23 Señales de tráfico involucradas en el Proyecto. 27 24 Directorio donde se almacenan todas las clases en subdirectorios. 27 25 Secuencia de inicio y parada en I2C. 32 26 Bus de reloj (SCL) y de datos (SDA). 33 27 Envío y recepción de la señal predicha entre dispositivos. 34 28 Esquema del funcionamiento del protocolo MQTT. 35 29 Estructura del servicio Prometheus. 36 30 Obtención de los datos en el puerto 8000 del localhost. 38 31 Representación de la temperatura en Prometheus. 38 32 Representación de la humedad en Prometheus. 39 33 Imagen para configurar la fuente de datos en Grafana. 40 34 Fuente de datos Prometheus correctamente creada. 40 xviii 35 Representaciones de los valores de temperatura y humedad en Grafana. 41 36 Ejemplo gráfico de una matriz de confusión. 43 37 Arquitectura de la red convolucional. 44 38 Matriz de confusión de las red CNN. 45 39 Gráficas de pérdidas y precisión de la red CNN. 45 40 Estructura de la red VGG19. 46 41 Estructura de la red Resnet50. 46 42 Estructura de la red MobilenetV2. 47 43 Matriz de confusión VGG19. 47 44 Matriz de confusión MobileNetV2. 48 45 Matriz de confusión ResNet50. 48 46 Prototipo final con el conexionado completo. 49 47 Comprobación de error ante un objeto que no sea una señal del dataset. 50 48 Inferencia con una señal de prohibido perteneciente al dataset. 51 49 Prototipo girando a la derecha al reconocer dicha señal. 51 Introducción 1.Fei Fei Li, profesora de ciencias de la computación en la Universidad de Stanford 1 1 INTRODUCCIÓN a evolución de la tecnología, en particular la IA, ha dado lugar a una nueva época en la capacidad humana para resolver problemas complejos, automatizar procesos y mejorar significativamente la eficiencia en una variedad de aplicaciones. La industria del automóvil es uno de los sectores más prometedores para la implementación de la IA, ya que está en constante transformación y busca incorporar soluciones innovadoras para mejorar la seguridad, eficiencia y sostenibilidad del transporte. La principal motivación para elegir este Trabajo de Fin de Máster (TFM) fue impulsada por el sector de la automoción, además, el hecho de que la IA pueda aportar soluciones a una infinidad de ámbitos en el mundo de la tecnología y a su vez sea accesible para cada vez más personas e instituciones también fue un factor determinante. “Como tecnólogo, veo cómo la IA y la cuarta revolución industrial afectarán todos los aspectos de la vida de las personas”.1 La incorporación de sistemas inteligentes en los automóviles no solo abre la puerta a la conducción autónoma, sino que también ofrece nuevas oportunidades para aumentar la seguridad en las carreteras mediante el reconocimiento y la interpretación precisas de las señales de tráfico. Esta capacidad es crucial porque los sistemas de IA deben comprender correctamente las señales de tráfico para tomar decisiones de conducción seguras y adecuadas en entornos dinámicos e impredecibles. Por tanto, este trabajo busca no solo aumentar la comprensión teórica y técnica de la IA y su aplicación utilizando herramientas hardware, sino también explorar su aplicación práctica en un contexto que afecta directamente el bienestar y la seguridad de la sociedad al abordar los desafíos técnicos. La utilización de redes neuronales convolucionales (CNN) para clasificar las señales de tráfico es un paso adelante en la creación de sistemas de vehículos inteligentes que tienen la capacidad de interpretar el entorno vial con una precisión comparable, o incluso superior, a la de los humanos. Este estudio busca demostrar cómo la inteligencia artificial puede mejorar la seguridad y la eficiencia en la conducción al lograr que un prototipo de automóvil operado por un sistema de IA reaccione adecuadamente a las señales de tráfico. A su vez, también se ha estudiado la implementación usando Transfer Learning, otra solución más como alternativa a las tradicionales redes CNN. Para realizar la inferencia de este modelo de IA se ha realizado el montaje de un prototipo de vehículo con dos servomotores manipulados por un controlador L298N conectado a un Arduino. El controlador se encarga tanto de alimentar a los motores como de activarlos o modificar la velocidad de estos. Además, en este trabajo intervienen más componentes en la utilización y manipulación de hardware, principalmente teniendo como protagonista a la placa de desarrollo Google Coral Dev Board junto con sus diferentes módulos como el sensor de ambiente y su cámara. La versión de Arduino utilizada es la Mega y su función es obtener los datos de la Coral Dev Board mediante una comunicación I2C para hacer que los actuadores conectados a él se ejecuten correctamente. Finalmente, se ha optado por extender el proyecto para dar cabida a las tecnologías IOT. Esto ha sido posible al utilizar el sensor de entorno de la Coral Dev Board que recopila información como por ejemplo la temperatura y humedad del ambiente. De esta forma, se obtienen y se almacenan estos datos mediante utilizando MQTT como protocolo de publicación y la herramienta Grafana para la representación de datos. L Introducción 2 Hardware, componentes y computación en la nube 3 2 HARDWARE, COMPONENTES Y COMPUTACIÓN EN LA NUBE 2.1 Descripción Hardware de la Google Coral Dev Board a Google Coral Dev Board se define como una placa de desarrollo para realizar inferencia y crear prototipos de Machine Learning. Este dispositivo presenta unas características ideales para reproducir un modelo de sistema integrado para más tarde, escalarlo a producción utilizando el Coral SoM (System on Module) y utilizar un hardware adicional como por ejemplo el uso de una PCB. Destaca por su rapidez a la hora de realizar inferencia en modelos de ML, con un coprocesador Edge TPU integrado es capaz de realizar 4 billones de operaciones (teraoperaciones) por segundo (TOPS), utilizando 0,5 vatios por cada TOPS (2 TOPS por vatio). Según estas especificaciones, puede ejecutar modelos de visión móviles de última generación, como MobileNet v2, a casi 400 FPS de manera eficiente desde el punto de vista energético. Además, ofrece conectividad inalámbrica y un sistema operativo derivado de la distribución Debian Linux llamado Mendel, al cual se le pueden incorporar múltiples herramientas y paquetes usando comandos convencionales de Debian. Otra de sus ventajas es la capacidad de soportar TensorFlow Lite, por lo que no es necesario construir modelos de ML desde 0, sino que se pueden compilar para ejecutarse directamente desde la Edge TPU. Por último, hay que considerar su fácil escalado. Al ser una placa de desarrollo con diseño modular, el SoM (parte de la placa en la que se encuentran todos los elementos imprescindibles que permiten el funcionamiento mínimo del dispositivo: SoC, memoria eMMC, LPDDR4 RAM, Wi-Fi, Bluetooth y el coprocesador Edge TPU) se puede separar de la placa permitiendo al usuario poder anexionar esta parte a otros elementos hardware. El precio de la Google Coral Dev Board ronda los 140$ en su modelo más básico de 1 GB, no obstante, también está disponible la versión de 4GB y la posibilidad de adquirir solo el SoM con un precio inicial de alrededor de 100$ en su modelo de 1 GB. Las especificaciones técnicas se pueden consultar desde la propia web del dispositivo, siendo estas mostradas a continuación. L Hardware, componentes y computación en la nube 4 Tabla 1. Características técnicas de la Google Coral Dev Board. La distribución de sus componentes se ofrece en la siguiente imagen donde se pueden observar algunos de sus puertos más importantes y utilizados en el desarrollo de este Trabajo Fin de Máster como por ejemplo los puertos USB-C data, el puerto serie y el CSI2 de la cámara. Imagen 1. Distribución de puertos y pines de la Google Coral. Hardware, componentes y computación en la nube 5 A su vez, también se pueden ver representados en el siguiente diagrama de bloques cómo ocurre la comunicación entre los puertos y componentes de la placa. Imagen 2. Diagrama de bloques interno de la Google Coral. En este diagrama se puede apreciar apreciar cómo la gran mayoría de componente se comunican con el SoM (System on Module) de la placa. El SoM integra componentes hardware en un diseño compacto y diseñado especialmente para aplicaciones de inteligencia artificial que requieran el uso del edge computing. Hardware, componentes y computación en la nube 6 Imagen 3. Diagrama del SoM incorporado en la Google Coral. Entre sus módulos más destacados se encuentran: • MX8M SoC (System on Chip). Proporciona las capacidades de procesamiento central del módulo, incluye dos procesadores ARM Cortex (A53 y M4) para manejar ciertas aplicaciones y tareas en tiempo real junto con una GPU encargada de la renderización y procesamiento de imágenes. Además, posee una gran cantidad de interfaces de comunicación como UART, SPI, I2C y GPIO y soporte para memoria DDR3L y LPDDR4. • Unidad de procesamiento de tensores (TPU). Se trata de una unidad de procesamiento cuya función es contribuir a la rapidez en el proceso de inferencia en los modelos de aprendizaje automático. Tiene capacidad para soportar billones de operaciones por segundo con un consumo muy reducido para esta alta velocidad de procesamiento. • Módulo Wifi/BT. Encargado de realizar todas las conexiones inalámbricas con otros dispositivos y redes, en este proyecto toma un papel de vital importancia en la parte de transmisión de datos IOT. • Memoria. Dispone de 1GB de LPDDR4 de memoria RAM para gestionar las operaciones en tiempo real y un almacenamiento ROM eMMC de 8GB disponible para el sistema operativo y datos. Hardware, componentes y computación en la nube 13 Imagen 9. Esquema de puertos del Arduino Mega La elección de este modelo de Arduino es debida a la cantidad de pines de entrada/salida que dispone respecto a sus demás versiones, permitiendo aumentar la complejidad del proyecto añadiendo más sensores, actuadores o periféricos y abarcando una gran posibilidad de modificaciones. Además, también dispone de cuatro puertos seriales (a diferencia de la mayoría de las demás placas que solo tienen uno), por lo que puede permitir la comunicación simultánea con otros microcontroladores o dispositivos como por ejemplo un módulo GPS para trackear la posición y ubicación del vehículo en todo momento. Otra alternativa sería utilizar una Raspberry pi, permitiendo todas las funcionalidades que realiza el Arduino mega en este proyecto e incluso ofreciendo más capacidad de memoria y mejor procesador, aunque con un precio más elevado. 2.5 Plataforma Google IOT Cloud Las plataformas IoT en Google Cloud Services proporcionan conectividad MQTT y HTTPS, así como autenticación, almacenamiento, administración y procesamiento de datos. Provee una interfaz unificada para la administración de distintos dispositivos aunque hay que tener en cuenta una serie de consideraciones en cuanto a la implementación de la arquitectura escogida ya que son múltiples productos y servicios diferentes los que se ofrecen dentro de este apartado de Google Cloud. Hardware, componentes y computación en la nube 14 En el siguiente diagrama se muestra un ejemplo de un tipo de arquitectura concreta. Imagen 10. Diagrama de bloques sobre el funcionamiento de la Plataforma IOT Cloud Al igual que se ha realizado para el desarrollo de este trabajo, en las plataformas IoT se implementa un agente MQTT encargado de la conectividad entre los dispositivos y Google Cloud. Además, con el fin de distribuir el tráfico correctamente desde los dispositivos que envían la información, la plataforma está conectada a un balanceador de cargas. La plataforma IoT también se encarga de proveer un conjunto de servicios de administración de dispositivos, estos son: • Almacenamiento de credenciales de los dispositivos. Recopilación de las credenciales de los equipos conectados para su posterior identificación. • Administración y actualización del dispositivo. Entrega de software, firmware y actualizaciones de aplicaciones a los dispositivos conectados. • Autenticación y autorización. Utilización de diferentes herramientas para la autenticación y acceso a la plataforma por parte de los dispositivos. • Motor de reglas. Definición de reglas y canalizaciones para el procesamiento de datos. Como se ha mencionado anteriormente, la mayoría de soluciones simples optan por la utilización del protocolo MQTT ya que solo conecta a los suscriptores de MQTT con un servicio de mensajería backend como Kafka o Pub/Sub. Como consecuencia, este tipo de implementación no incluye funciones como los niveles QoS 1 y 2 o las suscripciones compartidas, desembocando en una menor flexibilidad y funcionalidad para el transporte de mensajes MQTT. Por otro lado, la ventaja de esto es la mínima complejidad que se adopta, los costos operativos son más bajos Hardware, componentes y computación en la nube 15 y el mantenimiento es ínfimo en comparación con otras alternativas. Otro protocolo utilizado frecuentemente en este tipo de plataformas es HTTPS, un protocolo que tiene mayor sobrecarga que MQTT pero que es más compatible con dispositivos móviles como teléfonos, navegadores web y otras aplicaciones. En referencia al balanceo de cargas, está ligado a la arquitectura del agente MQTT, este servicio proporciona herramientas de redes y distribuciones de conexiones entrantes entre nodos de backend, encriptación de sesiones y autenticación. En cuanto al apartado de autenticación y administración de credenciales, es clave a la hora de operar con una plataforma IoT, los métodos de autenticación compatibles con los dispositivos conectados varían considerablemente en aplicaciones. Es imprescindible seleccionar el método de autenticación adecuado para implementar el esquema de autenticación. Una plataforma de IoT proporciona servicios integrados para administrar las identidades y las credenciales de los dispositivos. La mayoría usan la autenticación de certificados de cliente X.509 para la autenticación, aunque algunas otras plataformas admiten la integración con un proveedor de autenticación de LDAP externo. El aprovisionamiento de las credenciales de autenticación en el dispositivo perimetral durante la fabricación también es una parte importante del esquema de autenticación del dispositivo conectado. Las aplicaciones que administran varios dispositivos inteligentes son adecuadas para una plataforma de IoT. Un ejemplo de esta aplicación es una plataforma para administrar dispositivos de cocina, como lavavajillas y cafeteras. Por lo general, estos dispositivos se conectan a una aplicación basada en la nube, ya sea directamente a través de Wi- Fi o a través de una puerta de enlace local que use Bluetooth de bajo consumo (BLE) o algún otro protocolo local. Las capacidades de administración de una plataforma de IoT son importantes aquí para supervisar el estado de cada dispositivo, administrar las actualizaciones de software o parches de seguridad, y capturar la actividad del dispositivo a fin de proporcionar inteligencia crítica al fabricante y al cliente. Estas capacidades están fuera del alcance de un agente de MQTT básico. Como mínimo, un repositorio de información del dispositivo, una base de datos de estado del dispositivo, un almacén de datos de telemetría y una interfaz de estadísticas son fundamentales para compilar una plataforma de dispositivo inteligente exitosa. En una aplicación de seguimiento de recursos y logística, un producto de la plataforma de IoT ofrece una funcionalidad más completa que un agente de MQTT básico, por lo que es una mejor opción para este caso de uso. La supervisión del estado actual y el anterior, y la ubicación de una gran flota de recursos dependen de una base de datos de estado del dispositivo sólida y de un sistema de administración de identidades. A medida que se implementan recursos nuevos, deben conectarse a la plataforma con la menor cantidad de inconvenientes posible y, luego, supervisarse durante todo el ciclo de vida de los recursos. En muchos casos, la aplicación también recopila otra información del sensor sobre los elementos, como la temperatura local, la humedad y la presión atmosférica, o los datos de posicionamiento y aceleración 3D para detectar movimientos inesperados o caídas. Todos estos datos deben transferirse y asociarse con el recurso correcto para su análisis en cualquier aplicación de backend, por lo que la administración de dispositivos con todas las funciones que proporciona la plataforma de IoT es una capacidad importante. Redes neuronales 16 3 REDES NEURONALES n este apartado se va a presentar de una forma teórica el funcionamiento de distintas redes neuronales utilizadas en proyectos de Machine Learning y Deep Learning. Una de las alternativas más comunes es la utilización de las redes convolucionales o el uso de redes ya entrenadas, este último concepto es llamado Transfer Learning. 3.1. Redes neuronales convolucionales Este tipo de redes suele ser recurrente para la clasificación de formas, objetos e imágenes, así como el reconocimiento de texto, generación y estabilización de imágenes. En las redes convolucionales, la estructura de la red se basa en una combinación de capas convolucionales (con función de activación relu), capas densas, capas de MaxPooling, una capa de dropout, una capa de flatten y una última capa de softmax. 3.1.1. Capas convolucionales Su funcionamiento consiste en aplicar kernels (también llamados filtros) para extraer características, bordes y patrones de la imagen., estos filtros se van desplazando por toda la imagen hasta que la recorre por completo. La operación matemática que realizan estas capas es la convolución, este proceso consiste en una serie de operaciones matemáticas entre los valores del filtro y los píxeles de la imagen de entrada. Estos cálculos pueden detectar patrones diferentes según cuáles sean los valores del filtro que se defina, estos valores son aprendidos por la red neuronal poco a poco siendo esta una de las tareas más importantes en las redes convolucionales. La operación de convolución por sí sola tampoco puede detectar figuras de mucha complejidad, solo bordes y patrones muy simples, pero volver a realizar estas detecciones sobre las detecciones anteriores permite componer cada vez patrones más complejos. De hecho, las redes neuronales convolucionales se suelen representar en artículos como un embudo, conforme la resolución de la imagen va disminuyendo (se va comprimiendo espacialmente), los mapas de características van en aumento (su grosor se eleva). E Redes neuronales 17 Imagen 11. Esquema de las capas de una red convolucional típica A cada una de las imágenes generadas tras realizar la convolución de la imagen de entrada con el filtro se le conoce como mapa de características, ya que actúa como un mapa dónde se nos indica en qué parte de la imagen se ha detectado la característica buscada por dicho filtro. Cada píxel en blanco en este mapa de características se identifica con una activación que nos indica que el elemento buscado se encuentra en ese lugar. Los hiperparámetros más frecuentes utilizados para configurar estas capas son el tamaño del filtro o kernel, el padding y el stride. • Tamaño del kernel: define tanto el ancho como la altura del filtro, es un parámetro importante a la hora de definir la capa convolucional ya que cuanto más grande sea este, puede identificar características más grandes, aunque la resolución espacial disminuya considerablemente. Imagen 12. Operación de convolución con el kernel • Padding: se trata de un relleno de ceros que se realiza alrededor de la imagen para que la imagen resultante y la original sigan manteniendo el mismo tamaño. No es muy utilizado, pero permite que al mantener las mismas dimensiones la imagen se puedan hacer redes más profundas. • Stride: determina cuántos píxeles se desplaza el filtro para realizar la siguiente convolución, al aumentar el stride se reduce la resolución espacial. Redes neuronales 18 3.1.2. Capas Maxpooling Tienen la función de reducir la dimensión espacial de los mapas de características de las capas convolucionales. Conserva las características más importantes de estos mapas seleccionando el valor máximo de cada región local definida por la ventana de pooling. Estos valores máximos forman otro mapa de características distinto ayudando a resaltar las características más prominentes. Imagen 13. Representación gráfica de la operación Maxpooling 3.1.3. Capas densas Las capas densas o fully connected, están compuestas por neuronas en la que cada una de ellas está conectada a todas las neuronas de la capa anterior. Cada conexión entre neuronas tiene un peso asociado y cada neurona tiene un sesgo, la salida de cada neurona se calcula como la suma ponderada de sus entradas seguida por la adición del sesgo. Las capas convolucionales y de pooling extraen y resumen las características espaciales de las imágenes, mientras que las capas densas se utilizan para realizar la clasificación final o la regresión basada en estas características resumidas. Imagen 14. Representación de las capas densas Redes neuronales 19 3.1.4. Dropout Esta operación tiene como función evitar el overfitting o sobreaprendizaje, un problema muy común en redes convolucionales en las cuales el modelo se ajusta demasiado a los datos impidiendo generalizar correctamente. Para solucionarlo, se desactivan un porcentaje especificado de neuronas durante el entrenamiento. Imagen 15. Representación del dropout 3.1.5. Capa flatten La capa flatten es la transición entre las capas convolucionales y maxpooling y las capas densas. Su objetivo es aplanar su entrada en un vector unidimensional y tener así un formato adecuado para ser procesado por las capas densas. Sólo realiza un cambio en la disposición de los datos y no modifica ninguna de las características obtenidas en las capas anteriores. Imagen 16. Representación del flatten 3.1.6. Capa softmax Es una capa muy utilizada en problemas de clasificación multiclase, realiza un cálculo de la distribución de probabilidad sobre múltiples clases. El último número de neuronas de la capa que precede a la de softmax debe Redes neuronales 20 de tener el mismo número de clases que plantea el problema, de forma que cada neurona posee un cierto grado de activación sobre su clase correspondiente, por tanto, la capa de softmax toma estas puntuaciones y las normaliza en una distribución de probabilidad utilizando la función exponencial. Cada puntuación se convierte en una probabilidad que representa la confianza de la red en que la entrada pertenezca a una clase determinada. Imagen 17. Fórmula matemática resultado de la capa softmax 3.1.7. Diseño de la red neuronal convolucional En esta sección se va a describir la composición de la red CNN que mejor resultados ha presentado ante una serie de pruebas realizadas configurando y cambiando hiperparámetros de la red como el tamaño de los kernels, el porcentaje de dropout, etc. Código 1.4 Código para construir la red convolucional CNN. Como se puede observar, esta red se compone de una primera capa convolucional 2D de entrada con 32 filtros y un tamaño de filtro de 3x3 con función de activación ReLU. Esta capa de entrada acepta imágenes RGB con una extensión de 256x256 píxeles. Seguidamente, se encuentra una capa de MaxPooling para reducir las dimensiones espaciales de la salida de la primera cada a la mitad (tanto el ancho como el alto). A continuación, se vuelven a repetir estos dos bloques aunque con esta vez con el doble de filtros para la capa convolucional. La quinta capa se trata de la capa flatten o de aplanamiento para convertir una entrada tridimensional en un vector unidimensional que será la entrada de la siguiente capa densa de 64 neuronas, que realiza numero_clases=7 imagen_alto=256 imagen_ancho=256 canales=3 #Modelo de red neuronal model = keras.Sequential([ layers.Conv2D(32, (3, 3), activation='relu', input_shape=(imagen_alto, imagen_ancho, canales)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (2, 2), activation='relu'), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dense(64, activation='relu'), layers.Dropout(0.3), layers.Dense(numero_clases, activation='softmax') ]) Redes neuronales 21 combinaciones lineales de las características extraídas y aprende de estas relaciones. La penúltima capa se identifica con el dropout, un dropout impuesto del 30% (desactivación del 30% de las neuronas de la capa densa) para conseguir evitar el overfitting que inicialmente tenía la red. Finalmente, se encuentra la capa softmax, encargada de estimar un porcentaje de probabilidades para cada una de las clases. 3.2. Transfer Learning El Transfer Learning es una técnica que consiste en reutilizar un modelo preentrenado para una tarea específica y utilizarlo como parte de la estructura de otro modelo. En este proyecto al tener una cantidad de datos bastante limitada en comparación con otros, esta técnica resulta útil y efectiva al aprovechar el conocimiento previamente aprendido en otro modelo (formas, características, pesos del modelo, etc.). Otra función que se puede aplicar al Transfer Learning es congelar las capas, de esta forma se evita que los pesos de estas capas se actualicen durante el entrenamiento inicial y poder así utilizar las características de la arquitectura utilizada sin alterarlas. Todas estas arquitecturas de red normalmente suelen ser entrenadas con Imagenet, un repositorio de imágenes de código abierto que contiene más de 14 millones de imágenes y más de 21 mil categorías. Por tanto, el hecho de utilizar el Transfer Learning en este proyecto al tener un dataset de imágenes tan escaso es una opción considerable. Imagen 18. Concepto de Transfer Learning Para este Trabajo Fin de Máster se ha realizado el Transfer Learning con tres tipos de arquitecturas diferentes: VGG19, Resnet50 y MobilenetV2. 3.2.1. MobilenetV2 MobilenetV2 es una arquitectura que se adecua perfectamente al problema planteado en este proyecto ya que ha sido diseñada para dispositivos móviles y embebidos con recursos limitados donde la eficiencia computacional es un parámetro fundamental. Utiliza bloques residuales invertidos (un diseño inverso a los bloques residuales convencionales utilizados en las redes ResNet). En primer lugar, reducen las dimensiones (comúnmente conocido como bottleneck) para después expandirlas, mejorando la eficiencia computacional. El bloque residual invertido está compuesto por una capa de expansión, seguida de una capa dephtwise (capa que realiza la convolución estándar en dos operaciones más simples, una convolución depthwise y otra Redes neuronales 22 pointwise) para reducir el número de parámetros y operaciones y una capa de proyección. Imagen 19. Convolución depthwise y pointwise. 3.2.2. Resnet50 Consiste en una red neuronal denominada “residual” que permite el entrenamiento de redes mucho más profundas, RestNet50 en concreto posee 50 capas de profundidad. El funcionamiento de esta red se basa en las conexiones residuales o conexiones de salto, de esta forma, la salida de una capa no solo alimenta a la siguiente capa de forma secuencial sino que a su vez se suma a la entrada de una capa posterior saltándose una o más capas en el proceso. Esta técnica es utilizada para afrontar el problema de desvanecimiento de gradientes en redes neuronales, permitiendo entrenar redes más profundas. Así, el inicio de esta arquitectura está compuesto de una capa convolucional inicial y una capa de MaxPooling seguidas de múltiples capas convolucionales y finalmente una capa fully connected con una función de activación softmax para la clasificación. Imagen 20. Estructura de una red Resnet50 3.2.3. VGG19 Fue desarrollada en la universidad de Oxford en el año 2014 y se caracteriza por estar compuesta por 19 capas, 16 primeras capas convolucionales y 3 capas fully connected al final. Desarrollo del prototipo 29 4.3 Obtención e incorporación del modelo obtenido al prototipo Tras realizar el entrenamiento con el dataset descrito anteriormente se puede obtener un archivo con un formato determinado llamado Hierarchical Data Format version 5 (.h5), este formato es utilizado principalmente en modelos de Keras en TensorFlow el cuál tiene información necesaria para poder usar el modelo entrenado en el futuro sin la necesidad de volver a entrenarlo de nuevo. Esta información normalmente se descompone en: • Pesos del modelo. Almacena los pesos adquiridos durante el entrenamiento, estos pesos se identifican con los parámetros ajustables del modelo para realizar predicciones. • Configuración del entrenamiento. Contiene configuraciones del entrenamiento como la función de pérdida, el optimizador y los hiperparámetros. • Arquitectura del modelo. Incluye información sobre la arquitectura del modelo, es fundamental para reconstruir la estructura del modelo al cargarlo desde el archivo. Esta información se identifica con las capas, los parámetros de cada una de las capas y sus conexiones. Por tanto, este formato permite la reutilización y despliegue en diferentes aplicaciones y dispositivos como por ejemplo en la Google Coral Dev Board, sin embargo, este formato .h5 no es el óptimo para utilizarlo en sistemas embebidos o en aplicaciones móviles debido a varios motivos tales como el tamaño del archivo, optimización para la inferencia en tiempo real (punto muy crítico para la aplicación de este proyecto) y requisitos de memoria y CPU. Estos inconvenientes hacen que realmente esta opción no sea la más adecuada para la aplicación que se quiere desarrollar en este proyecto. Por tanto, surge la necesidad de buscar una nueva alternativa como el formato utilizado por TensorFlow Lite (.tflite) ya que soluciona estos problemas anteriores: • Tamaño reducido. Los modelos convertidos a la extensión .tflite tienen un tamaño mucho menor que la conversión .h5, solucionando los problemas de memoria en sistemas embebidos. • Mayor velocidad de inferencia. Pueden ejecutarse más rápido gracias a la optimización para la eficiencia en la memoria y cómputo. • Optimizado para sistemas embebidos. TensorFlow Lite es una plataforma diseñada para ejecutar modelos de Machine Learning en este tipo de dispositivos como la Google Dev Coral u otro tipo de dispositivos IoT. Una vez expuestos los dos tipos de formatos en los que se pueden guardar los modelos resultantes, se ha llegado a la conclusión de usar el modelo con el formato TensorFlow Lite gracias a sus ventajas, no obstante, al usarse la función save() sobre el modelo, el formato en el que se guarda es .h5 por lo que es necesaria una conversión a TensorFlow Lite. Dicha conversión se refleja en el siguiente fragmento de Código. Desarrollo del prototipo 30 Código 1.4 Código para convertir el modelo y guardarlo en formato TFLite. Una vez realizada la conversión, ya se tendría el modelo en el formato optimizado para que la Google Dev Coral pueda realizar la inferencia sobre este. Para enviar el archivo a la placa se utiliza el comando scp teniendo en cuenta que la IP de la Google Coral es 192.168.1.150: scp <Nombre_archivo.tflite> 192.168.1.150: 4.4 Integración de la cámara e inferencia del modelo En este apartado se va a describir el papel que tiene la cámara de la Coral Dev Board en el desarrollo del prototipo implementación de la inferencia, ya que va a capturar imágenes continuamente en el rango de visión disponible y en base a esa imagen capturada se va a proceder a la inferencia del modelo ya entrenado. En el script utilizado para ejecutarse en la placa de Google el primer paso es incorporar y cargar el modelo en formato Tensorflow Lite con extensión .tflite que se generó una vez realizado entrenamiento. Código 1.5 Código para cargar el modelo TFLite. import tensorflow as tf # Cargar tu modelo entrenado (por ejemplo, un modelo en formato ".h5") model = tf.keras.models.load_model('modelo_entrenado.h5') # Convertir el modelo a formato TFLite converter = tf.lite.TFLiteConverter.from_keras_model(model) tflite_model = converter.convert() # Guardar el modelo en formato TFLite with open('modelo_entrenado.tflite', 'wb') as f: f.write(tflite_model) bus.write_byte(arduino_address, len(mensaje)) # Carga el modelo TFLite interpreter = tflite.Interpreter(model_path="modelo_entrenado_1.tflite") interpreter.allocate_tensors() input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() for caracter in mensaje: Desarrollo del prototipo 31 Una vez cargado el modelo, el siguiente paso es capturar la imagen, para ello en primer lugar se inicializará el módulo de la cámara utilizando la función VideoCapture de la biblioteca openCV y la función read para obtener la correspondiente imagen. Para realizar la inferencia de forma correcta es necesario realizar una serie de modificaciones a la imagen capturada. • Cambio de espacio de colores de BGR a RGB (ya que la biblioteca OpenCV carga las imágenes normalmente en formato BGR mientras el modelo espera la imagen en formato RGB). • Cambio del tamaño y dimensiones de la imagen tomada al tamaño requerido originalmente por el modelo. • Conversión de la imagen en un array de la biblioteca numpy con un tipo de dato float32. • Normalización de los valores de los píxeles. Código 1.6 Código para aplicar modificaciones a la captura inicial. Con la imagen ya procesada y modificada, a continuación se procede a insertarla dentro del modelo utilizando el objeto interpreter anteriormente definido y usando la función set_tensor() para después realizar la inferencia mediante la función invoke() y obtener la correspondiente salida con get_tensor(). Código 1.7 Código para realizar la clasificación. A su vez, de la salida se obtienen la etiqueta predicha y el nivel de confianza en la predicción. Esta última variable se va a utilizar para realizar una comparación con otra variable llamada threshold, que va a actuar como nivel umbral de forma que, si el nivel de confianza de la clase predicha no alcanza un valor mínimo, la imagen no se considerará como una señal de tráfico y se descartará para que el vehículo no actúe de forma incorrecta. De lo contrario, si prediction_confidence sí supera el valor del umbral, se enviará la información correspondiente al módulo Arduino para que ejecute las acciones pertinentes en sus actuadores. bus.write_byte(arduino_address, ord(caracter)) image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) image = cv2.resize(image, (input_details[0]['shape'][2], input_details[0]['shape'][1])) image = np.array(image, dtype=np.float32) image /= 255.0 print(f"Mensaje enviado: {mensaje}") for caracter in mensaje: interpreter.set_tensor(input_details[0]['index'], [image]) interpreter.invoke() output_data = interpreter.get_tensor(output_details[0]['index']) predicted_label = np.argmax(output_data[0]) prediction_confidence = np.max(output_data[0]) bus.write_byte(arduino_address, ord(caracter)) print(f"Mensaje enviado: {mensaje}") Desarrollo del prototipo 32 4.5 Comunicación entre dispositivos para el control del prototipo La comunicación entre la Coral Dev Board y el Arduino constituye una parte esencial en la transmisión y procesamiento de la información del proyecto. En este apartado se va a documentar qué criterios se han seguido para realizar dicha transmisión de datos y cómo se realiza esta transmisión. Una vez obtenida la clase predicha como se ha documentado en el anterior apartado, el contenido correspondiente a tal información que se va a enviar es el número identificado con cada clase tal y como se describió en el punto 4.1. Para ello, se va a utilizar un protocolo de comunicación llamado I2C. Este protocolo se utiliza en dispositivos electrónicos para intercambiar datos mediante una comunicación serial utilizando un esquema maestro-esclavo de forma que en el prototipo diseñado el esclavo se identifica con la Coral Dev Board y el maestro se identifica con el Arduino (aunque se podría incorporar más de un maestro ya que el propio protocolo posee un detector de colisiones). Para establecer la comunicación únicamente se necesitarán dos líneas, una llamada SDA encargada de transmitir los datos y otra llamada SCL encargada de controlar la señal de reloj, produciéndose así una comunicación en serie y de forma síncrona. La transmisión de datos se produce de forma que cuando el dispositivo maestro produce una secuencia de inicio en el bus empieza la comunicación con el esclavo. De igual forma, cuando cesa la transacción de datos entre ambos dispositivos, el maestro manda una secuencia de parada. Estos son casos especiales puesto que son los dos únicos casos en los que se permite que la línea de datos SDA cambie cuando la línea de reloj SCL está activa a nivel alto. Imagen 25. Secuencia de inicio y parada en I2C Mientras se está transmitiendo un dato la línea SDA debe tener un valor estable y jamás cambiar cuando la línea SCL está a un nivel alto. Los datos se transfieren en secuencias de 8 bits siendo el primer bit que se envía el más significativo. Por cada 8 bits que se transfieren el dispositivo que recibe el dato envía de regreso un bit de reconocimiento o ACK, en caso de que este ACK tenga un valor de 0 significa que está preparado para recibir la siguiente secuencia de bits, de lo contrario, si recibe un 1 indica que no puede recibir más datos y se deberá de enviar una secuencia de parada para finalizar la conexión. Desarrollo del prototipo 33 Imagen 26. Bus de reloj (SCL) y de datos (SDA) A la hora de establecer la conexión I2C entre los dispositivos implicados en este proyecto, en el mismo script donde se realizó la inferencia en la Coral, hay que definir la dirección del bus I2C del Arduino, que en este caso es 0x68 e iniciar la comunicación del bus mediante la función SMBUS(). Una vez iniciada la comunicación ya se podría transferir el dato de la clase predicha por el modelo entre la placa de Google y el Arduino, para ello se define una función en python encargada de utilizar la instrucción write_byte() de la biblioteca smbus. Código 1.8 Código para enviar un mensaje de la Coral Dev Board a Arduino. En el otro extremo, en la placa de Arduino, se tendrá que incluir la biblioteca wire.h para poder especificarle el mismo bus de dirección que se indicó en la Coral (0x68), en el cual se va a recibir la información. # Dirección del Arduino en el bus I2C (7 bits) arduino_address = 0x68 # Inicializaci ón del bus I2C (1 indica el bus I2C-1) bus = smbus.SMBus(1) #Funci ón definida para mandar un mensaje al arduino desde la coral dev board def enviar_mensaje(mensaje): bus.write_byte(arduino_address, len(mensaje)) Desarrollo del prototipo 34 Código 1.9 Código para recibir el mensaje en Arduino. En la imagen siguiente, se realiza una prueba mostrando en el monitor serie las clases predichas mientras que la cámara apunta a una imagen de una señal de tráfico concreta se obtiene la siguiente salida. Imagen 27. Envío y recepción de la señal predicha entre dispositivos A la izquierda se observan los bits recibidos en el prompt o terminal de Arduino, mientras que en el lado derecho se puede observar los mensajes enviados desde la Google Coral Dev Board una vez se haya realizado la predicción de la señal correspondiente. bus.write_byte(arduino_address, len(mensaje)) #include <Wire.h> #define DEVICE_ADDRESS 0x68 void setup() { Wire.begin(DEVICE_ADDRESS); Wire.onReceive(receiveEvent); } void receiveEvent(int bytes) { while (Wire.available()) { char c = Wire.read(); ……… } } for caracter in mensaje: bus.write_byte(arduino_address, ord(caracter)) Desarrollo del prototipo 35 4.6 Implementación manual IOT En este apartado se va a describir la parte IOT del proyecto. Se va a hacer especial énfasis en el uso de tres herramientas concretas que sustituyen en gran parte las funciones que se obtendrían utilizando la plataforma Google IoT Cloud: MQTT, Prometheus y Grafana. De esta forma, en esta sección se busca realizar manualmente lo que ofrece Google en su plataforma (obviando algunos servicios como el de autenticación, seguridad y otros adicionales). 4.6.1. Protocolo MQTT Al trabajar con hardware con requisitos limitados, todo el software y programas que se ejecutan deben de ser lo más ligeros y optimizados posibles para obtener un rendimiento adecuado. Por este motivo, se ha escogido como protocolo de comunicaciones MQTT (Message Queuing Telemetry Transport) ya que ofrece una transmisión eficiente de mensajes y su consumo es muy reducido. Es un protocolo que se basa en el modelo publicador-suscriptor en el cual un dispositivo transmite la información (publicador) y otro dispositivo la recibe (suscriptor). El publicador se encarga de transmitir el mensaje en un topic determinado, esto es, un espacio determinado que se crea para acumular mensajes sobre un tema en concreto, el suscriptor puede suscribirse a este topic si lo desea y tener la posibilidad de acceder a esta información. Imagen 28. Esquema del funcionamiento del protocolo MQTT Otro elemento importante en MQTT es el broker, un servidor que distribuye la información a los clientes interesados conectados al servidor y la parte que más recursos necesita del protocolo, ya que tiene que lidiar con la autenticación, la autorización de los clientes y el almacenamiento de los mensajes. También ofrece QoS (Quality of Service) que proporciona tres niveles diferentes para garantizar la entrega de mensajes: • QoS 0 unacknowledged (at most one): El mensaje se envía una única vez, en caso de fallo por lo que puede que alguno no se entregue. • QoS 1 acknowledged (at least one): El mensaje se envía hasta que se garantiza la entrega, en caso de fallo, el suscriptor puede recibir algún mensaje duplicado. • QoS 2 assured (exactly one): Se garantiza que cada mensaje se entrega al suscriptor, y únicamente una vez. Desarrollo del prototipo 36 Para trabajar con esta herramienta en primer lugar habrá que fijar quién va a publicar y quién va a suscribirse al topic para recibir los mensajes. Para este proyecto, la Google Coral realizará el papel de publicador enviando los datos de temperatura y humedad a un PC local que ejercerá de suscriptor. La instalación y configuración de MQTT en ambos dispositivos es la misma, en un terminal se ejecutan las siguientes instrucciones para instalar y ejecutar mosquitto, un servidor de código abierto de MQTT. Código 1.10 Código para la instalación y habilitación de MQTT. 4.6.2. Prometheus Prometheus es una herramienta de monitorización y alertas para supervisar tus sistemas y aplicaciones. Su función en este proyecto es mandar los mensajes que recoge el suscriptor de MQTT a una herramienta de visualización de datos como lo es Graphana. Estos mensajes que se obtienen de MQTT se almacenan en una base de datos como series temporales, añadiendo también la posibilidad de adjuntar etiquetas de tipo clave-valor junto a la información almacenada. Imagen 29. Estructura del servicio Prometheus Su arquitectura se basa en tres partes: un servidor para almacenar los datos de las métricas, una librería cliente para calcular y exponer las métricas al cliente y un gestor de alertas que genera alertas basadas en reglas. Otros usos comunes que tiene Prometheus es el almacenamiento de métricas de todo tipo, como por ejemplo el uso de la CPU o de la memoria, número de conexiones, número de peticiones o cantidad de sesiones activas. Todas las mediciones y métricas recogidas ayudarán a diagnosticar errores o problemas de servicio en los } for caracter in mensaje: sudo apt-get update sudo apt-get install mosquitto sudo systemctl enable mosquitto sudo systemctl start mosquitto bus.write_byte(arduino_address, ord(caracter)) print(f"Mensaje enviado: {mensaje}") Desarrollo del prototipo 37 sistemas y aplicaciones que se monitorizan. Esta herramienta se ejecutará en el PC local que está suscrito a los mensajes que envíe la Google Coral, para la instalación de Prometheus en el PC es necesario ejecutar los siguientes comandos en un terminal. Código 1.11 Código para la instalación y habilitación de Prometheus. Una vez descargado y ejecutado este servicio, hay que realizar un cambio en uno de sus ficheros de configuración para que Prometheus recopile los mensajes recibidos por MQTT como métricas, este fichero se encuentra en el path /etc/prometheus/prometheus.yml y en él se pueden establecer los intervalos de tiempo a la hora de recoger las métricas o añadir diferentes servicios sobre los que Prometheus va a ir recogiendo información. Por tanto, al final de este fichero se agrega el siguiente endpoint en el apartado scrape_configs para poder scrapear toda la información recibida de MQTT. Código 1.12 Código para la scrapear la información. Una vez introducido este endpoint, todos los datos recibidos de MQTT con la información de la temperatura y la humedad de la Google Coral se van a enviar al puerto 8000 del PC. Esta información se puede visualizar desde cualquier navegador introduciendo en su barra de búsqueda: http://localhost:8000 sudo apt-get install prometheus sudo systemctl enable prometheus.service sudo systemctl start prometheus.service print(f"Mensaje enviado: {mensaje}") - job_name: 'mqtt' tatic_configs: - targets: ['localhost:8000'] static_configs: - targets: ['localhost:8000'] bus.write_byte(arduino_address, ord(caracter)) print(f"Mensaje enviado: {mensaje}") Desarrollo del prototipo 38 Imagen 30. Obtención de los datos en el puerto 8000 del localhost Como se puede observar en las últimas líneas de la imagen, se puede visualizar el valor del último dato de temperatura: 36.739 y el último dato de humedad: 39.379. A su vez, también se pueden visualizar estos datos en gráficas temporales si se accede a la dirección http://localhost:9090 e introduciendo el nombre de las métricas que se quieren representar (temperatura y humedad). Imagen 31. Representación de la temperatura en Prometheus Resultados 45 Imagen 38. Matriz de confusión de las red CNN El resto de clases tienen una predicción notoria, llegando a tener un 100% de precisión en la clase 3, correspondiente con la señal de giro a la derecha. A su vez, se obtienen las siguientes gráficas para evaluar las pérdidas y el acierto del modelo en cada época o ciclo de entrenamiento. Imagen 39. Gráficas de pérdidas y precisión de la red CNN Como se puede observar, ambas gráficas presentan resultados satisfactorios si se tiene en cuenta las explicaciones anteriores sobre la monotonía de las curvas de pérdidas y de acierto en los conjuntos de entrenamiento y validación. Este modelo presenta una tasa de acierto del 88%, un dato que para ciertas aplicaciones críticas puede resultar escaso o insuficiente. Resultados 46 5.2 Resultados obtenidos con Transfer Learning Como se ha comentado anteriormente, las redes que se van a utilizar como estudio del Transfer Learning para la solución a la clasificación de señales de tráfico son: VGG19, Resnet50 y MobilenetV2. La estructura de estas redes, difieren en cierto modo unas de otras, ya que cada una presenta un número determinado de capas y parámetros diferentes en cada una de ellas, sin embargo, estas tres redes tienen en común la utilización de capas convolucionales y funciones de activación ReLu. Imagen 40. Estructura de la red VGG19 Imagen 41. Estructura de la red Resnet50 Resultados 47 Imagen 42. Estructura de la red MobilenetV2 Las matrices de confusión ofrecen un indicativo sobre qué modelo ha tenido mejores resultados después del entrenamiento, obteniendo unas comparaciones en el rendimiento de los modelos muy significativas. Imagen 43. Matriz de confusión VGG19 Como se observa en la imagen anterior, la matriz de confusión que peores resultados refleja es la referente al modelo VGG19, obteniendo una muy mala clasificación en la clase uno, correspondiente a la señal de limitación a 60 km/h y confundiéndose frecuentemente con las otras señales de limitación de velocidad. También presenta fallos considerables en la predicción de la clase 3 perteneciente a la señal de giro a la derecha con la señal de giro a la izquierda. Por otro lado, la matriz de confusión correspondiente al modelo MobilenetV2, presenta mejores resultados que la del modelo VGG19, aunque sigue presentando predicciones erróneas en las clases pertenecientes a las señales de limitaciones de velocidad como se puede observar en el marco superior izquierdo de la matriz, obteniendo mejores resultados en la clasificación del resto de señales con una diferencia notoria. Resultados 48 Imagen 44. Matriz de confusión MobileNetV2 Por último, la matriz que mejores resultados ofrece es la del modelo Resnet50, obteniendo una diagonal principal casi perfecta y con muy pocos fallos en su predicción. Al igual que en los anteriores modelos de Transfer Learning, se sigue obteniendo un mayor error en la predicción de las señales de limitación de velocidad, aunque para el caso del modelo Resnet50, esta tasa de error se ha visto muy reducida respecto a los anteriores modelos, además, para el resto de las señales ha obtenido una predicción del 100%, no obteniendo así ningún error en la predicción de estas. Imagen 45. Matriz de confusión ResNet50 En referencia a las gráficas de las curvas de pérdidas y acierto en el transcurso del entrenamiento, todos los modelos presentan una dinámica similar con una función de pérdidas decreciente y una tasa de acierto ascendente. En las gráficas se puede discernir una cierta similitud en las curvas del modelo VGG19 y Resnet50, obteniendo resultados muy parecidos entre sí, mientras que las curvas del conjunto de entrenamiento y validación en el modelo MobilenetV2 presentan una cierta separación entre ambas, sinónimo de un posible sobreajuste u overfitting en el modelo. Resultados 49 Finalmente, realizando una comparación final del rendimiento de estos modelos junto con el modelo de la red convolucional descrita en el apartado anterior, se llega a la conclusión tras realizar el estudio e interpretación de las matrices de confusión y las gráficas de pérdidas y validación durante el entrenamiento de que el modelo que mejor rendimiento ofrece es el Resnet50 utilizando Transfer Learning. 5.3 Resultados obtenidos al realizar la inferencia con el prototipo En esta sección se va a describir los resultados del prototipo ante la aparición de diferentes señales de tráfico. En primer lugar, hay que diferenciar claramente si la imagen que se captura es realmente una señal de tráfico o no. Para ello, como se ha comentado anteriormente, se ha establecido un cierto umbral o threshold mediante el cual si la salida del modelo de la red neuronal no supera este valor límite se descartaría la imagen al no considerarse lo suficientemente fiable para tomar decisiones que impliquen un cambio en la movilidad del prototipo. Imagen 46. Prototipo final con el conexionado completo Se han realizado diferentes pruebas para verificar esta primera aproximación. Inicialmente, se ha probado a enfocar la cámara a distintos objetos que no se identifican con ninguna de las señales de tráfico que se quieren clasificar, por lo que la salida debería de imprimir el mensaje de “No es una señal de tráfico”. Resultados 50 Imagen 47. Comprobación de error ante un objeto que no sea una señal del dataset Para este caso, se puede observar que apuntando con la cámara a un objeto redondo y rojo como puede ser el tapón de una botella no la reconoce como la señal de prohibido el paso (una señal redonda y en su mayoría roja como el tapón que podría ser objeto de confusión). Para verificar que reconoce correctamente alguna de las señales pertenecientes a las diferentes clases de este proyecto, se ha hecho una prueba con cada una de ellas, dando un resultado satisfactorio en su mayoría, aunque en un cierto modo y dependiendo de la distancia en la que se encuentre el prototipo de la señal y el ángulo de la cámara respecto a la imagen, en ocasiones no diferencia con total exactitud las señales de velocidad, presentando una mayor tasa de error entre las señales de 30 km/h y 80km/h. Se adjunta una prueba del reconocimiento de la señal de prohibido el paso y su respectiva tasa de confianza, superando claramente el umbral establecido del 0.85 (85%). Resultados 51 Imagen 48. Inferencia con una señal de prohibido perteneciente al dataset Por último, se adjunta una foto de otra prueba realizada con el prototipo girando a la derecha al encontrarse una imagen representando esta señal. Imagen 49. Prototipo girando a la derecha al reconocer dicha señal Resultados 52 6 Trabajo Futuro 53 6 TRABAJO FUTURO Este trabajo ha establecido una base sólida para la aplicación de inteligencia artificial en la identificación y clasificación de señales de tráfico y su integración en un sistema de conducción prototipo. Sin embargo, el campo de la automoción inteligente es amplio y está en constante evolución, lo que ofrece una variedad de caminos para investigaciones y desarrollos futuros. A continuación, se enumeran algunas líneas futuras que podrían surgir de este proyecto: • Aumentar el dataset. Ampliar el conjunto de datos utilizado para el entrenamiento de la red neuronal con imágenes de señales de tráfico bajo diversas condiciones de iluminación, climáticas y ángulos de visión para mejorar la capacidad de la red para generalizar y funcionar de manera robusta en diferentes entornos y situaciones. • Aplicación final en vehículos y sistemas móviles. Posibilidad de adaptar y ampliar prototipo para su aplicación en vehículos reales. Esto requeriría abordar problemas adicionales relacionados con la integración de vehículos existentes, normativas de seguridad y posibles futuras regulaciones. • Uso de herramientas profesionales. Utilizar herramientas de software y hardware más complejas y potentes con el fin de obtener mejores resultados. Además, el desarrollo de un entorno de simulación para monitorear el sistema y ajustar los parámetros de funcionamiento. “Los países con la mayor densidad de robots tienen también las tasas de desempleo más bajas. La combinación correcta de tecnología y humanos impulsarán la prosperidad”2 2. Ulrich Spiesshofer, Presidente y CEO de ABB. 6 Trabajo Futuro 54 Apéndices 61 8.2 Código de las redes usando Transfer Learning. Código A3. Transfer Learning usando VGG19. #USANDO TRANSFER LEARNING CON VGG19 import tensorflow as tf from tensorflow.keras.applications import VGG19 from tensorflow.keras.models import Model from tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Input from tensorflow.keras.optimizers import Adam from sklearn.model_selection import train_test_split X_train, X_temp, y_train, y_temp = train_test_split(imagenes, etiquetas, test_size=0.3, random_state=42) X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42) # Cargar VGG19 como el modelo base base_model = VGG19(weights='imagenet', include_top=False, input_shape=(256, 256, 3)) # Congelar las capas del modelo base para no modificar sus pesos durante el entrenamiento inicial for layer in base_model.layers: layer.trainable = False # A ñadir nuevas capas personalizadas en la parte superior del modelo base x = base_model.output x = GlobalAveragePooling2D()(x) # A ñade un pooling global x = Dense(1024, activation='relu')(x) # A ñade una capa densa predictions = Dense(10, activation='softmax')(x) # Asume 10 clases, ajusta seg ún tu caso # Definir el modelo completo model = Model(inputs=base_model.input, outputs=predictions) # Compilar el modelo model.compile(optimizer=Adam(lr=0.0001), loss='sparse_categorical_crossentropy', metrics=['accuracy']) # Resumen del modelo model.summary() from tensorflow.keras.applications.vgg19 import preprocess_input from tensorflow.keras.preprocessing.image import ImageDataGenerator Apéndices 62 Código A4. Transfer Learning usando ResNet50. # Aplicar preprocesamiento específico de VGG19 train_datagen = ImageDataGenerator(preprocessing_function=preprocess_input) val_datagen = ImageDataGenerator(preprocessing_function=preprocess_input) history = model.fit(train_datagen.flow(X_train, y_train, batch_size=32), epochs=10, validation_data=val_datagen.flow(X_val, y_val)) test_loss, test_acc = model.evaluate(X_test, y_test) print("Precisi ón en el conjunto de prueba:", test_acc) model.save('modelo_transfer_learning_vgg19.h5') # USANDO TRANSFER LEARNING CON RESNET from tensorflow.keras.applications import ResNet50 X_train, X_temp, y_train, y_temp = train_test_split(imagenes, etiquetas, test_size=0.3, random_state=42) X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42) # Cargar ResNet50 como el modelo base base_model = ResNet50(weights='imagenet', include_top=False, input_tensor=Input(shape=(256, 256, 3))) # Congelar las capas del modelo base for layer in base_model.layers: layer.trainable = False # A ñadir nuevas capas personalizadas x = base_model.output x = GlobalAveragePooling2D()(x) # Reduce las dimensiones espaciales de la salida x = Dense(1024, activation='relu')(x) # Capa densa para aprendizaje predictions = Dense(len(categorias), activation='softmax')(x) # Capa de salida, ajustada al n ú mero de clases # Compilar el modelo model = Model(inputs=base_model.input, outputs=predictions) model.compile(optimizer=Adam(lr=0.0001), loss='sparse_categorical_crossentropy', metrics=['accuracy']) history = model.fit(X_train, y_train, batch_size=32, epochs=10, validation_data=(X_val, y_val)) model.save('modelo_transfer_learning_resnet.h5') Apéndices 63 Código A5. Transfer Learning usando MobileNetV2. # USANDO TRANSFER LEARNING CON MOBILENETV2 from sklearn.model_selection import train_test_split # Aseg úrate de que 'imagenes' y 'etiquetas' están definidas como se muestra en tu código previo # Dividir los datos en conjuntos de entrenamiento y validaci ón X_train, X_val, y_train, y_val = train_test_split(imagenes, etiquetas, test_size=0.2, random_state=42) import tensorflow as tf from tensorflow.keras.applications import MobileNetV2 from tensorflow.keras.models import Model from tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Input from tensorflow.keras.optimizers import Adam # Cargar MobileNetV2 como el modelo base base_model = MobileNetV2(weights='imagenet', include_top=False, input_tensor=Input(shape=(256, 256, 3))) # Congelar las capas del modelo base para no modificar sus pesos durante el primer ciclo de entrenamiento for layer in base_model.layers: layer.trainable = False # A ñadir nuevas capas para la tarea específica x = base_model.output x = GlobalAveragePooling2D()(x) # A ñadir una capa de pooling para reducir las dimensiones x = Dense(1024, activation='relu')(x) # A ñadir una capa densa predictions = Dense(len(categorias), activation='softmax')(x) # Capa de salida # Compilar el modelo model = Model(inputs=base_model.input, outputs=predictions) model.compile(optimizer=Adam(lr=0.0001), loss='sparse_categorical_crossentropy', metrics=['accuracy']) history = model.fit(X_train, y_train, batch_size=32, epochs=10, validation_data=(X_val, y_val)) model.save('modelo_transfer_learning.h5') Apéndices 64 8.3 Código para la elaboración de gráficas e interpretación de resultados. Código A6. Código para elaborar la matriz de confusión y las gráficas de pérdidas y acierto. import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix import seaborn as sns # C álculo de las predicciones en el conjunto de prueba y_pred = model.predict(X_test) y_pred_classes = np.argmax(y_pred, axis=1) y_true = y_test # Matriz de confusi ón cm = confusion_matrix(y_true, y_pred_classes) # Gr áfica de la matriz de confusión plt.figure(figsize=(8, 6)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues') plt.xlabel('Predicted') plt.ylabel('True') plt.title('Matriz de Confusi ón') plt.show() # Gr áfica de pérdidas de validación y test plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.plot(history.history['loss'], label='Training Loss') plt.plot(history.history['val_loss'], label='Validation Loss') plt.title('P érdidas') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() plt.subplot(1, 2, 2) plt.plot(history.history['accuracy'], label='Training Accuracy') plt.plot(history.history['val_accuracy'], label='Validation Accuracy') plt.title('Precisi ón') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend() plt.show() Apéndices 65 8.4 Código desarrollado en Arduino. Código A7. Código para la configuración y control de los controladores conectados a Arduino. // Motor 1 int ENA = 10; int IN1 = 9; int IN2 = 8; // Motor 2 int ENB = 5; int IN3 = 7; int IN4 = 6; void setup (){ // Declaramos todos los pines como salidas pinMode (ENA, OUTPUT); pinMode (ENB, OUTPUT); pinMode (IN1, OUTPUT); pinMode (IN2, OUTPUT); pinMode (IN3, OUTPUT); pinMode (IN4, OUTPUT); } void Adelante (){ //Direcci ón motor A digitalWrite (IN1, HIGH); digitalWrite (IN2, LOW); analogWrite (ENA, 220); //Velocidad motor A //Direcci ón motor B digitalWrite (IN3, HIGH); digitalWrite (IN4, LOW); analogWrite (ENB, 220); //Velocidad motor B } void Atras (){ //Direcci ón motor A digitalWrite (IN1, LOW); digitalWrite (IN2, HIGH); analogWrite (ENA, 128); //Velocidad motor A //Dirección motor B Apéndices 66 Código A7. Código para la configuración y control de los controladores conectados a Arduino. //Dirección motor B digitalWrite (IN3, LOW); digitalWrite (IN4, HIGH); analogWrite (ENB, 128); //Velocidad motor B } void Derecha (){ //Direcci ón motor A digitalWrite (IN1, HIGH); digitalWrite (IN2, LOW); analogWrite (ENA, 220); //Velocidad motor A //Direcci ón motor B digitalWrite (IN3, LOW); digitalWrite (IN4, HIGH); analogWrite (ENB, 210); //Velocidad motor A } void Izquierda (){ //Direcci ón motor A digitalWrite (IN1, LOW); digitalWrite (IN2, HIGH); analogWrite (ENA, 210); //Velocidad motor A //Direcci ón motor B digitalWrite (IN3, HIGH); digitalWrite (IN4, LOW); analogWrite (ENB, 220); //Velocidad motor A } void Parar (){ //Direcci ón motor A digitalWrite (IN1, LOW); digitalWrite (IN2, LOW); analogWrite (ENA, 0); //Velocidad motor A //Direcci ón motor B digitalWrite (IN3, LOW); digitalWrite (IN4, LOW); analogWrite (ENB, 0); //Velocidad motor A } Apéndices 67 Código A8. Código para recibir los mensajes I2C en Arduino. // Motor 1 int ENA = 10; int IN1 = 9; int IN2 = 8; // Motor 2 int ENB = 5; int IN3 = 7; int IN4 = 6; void setup (){ // Declaramos todos los pines como salidas pinMode (ENA, OUTPUT); pinMode (ENB, OUTPUT); pinMode (IN1, OUTPUT); pinMode (IN2, OUTPUT); pinMode (IN3, OUTPUT); pinMode (IN4, OUTPUT); } void Adelante (){ //Direcci ón motor A digitalWrite (IN1, HIGH); digitalWrite (IN2, LOW); analogWrite (ENA, 220); //Velocidad motor A //Direcci ón motor B digitalWrite (IN3, HIGH); digitalWrite (IN4, LOW); analogWrite (ENB, 220); //Velocidad motor B } void Atras (){ //Direcci ón motor A digitalWrite (IN1, LOW); digitalWrite (IN2, HIGH); analogWrite (ENA, 128); //Velocidad motor A //Dirección motor B Apéndices 68 8.5 Código desarrollado en la Google Coral Dev Board. Código A9. Código para realizar la inferencia desde el prototipo y envío de la señal a Arduino. #include <Wire.h> #define DEVICE_ADDRESS 0x68 // Direcci ón I2C del Arduino (debe coincidir con la direcció n configurada en la Dev Board) void setup() { Wire.begin(DEVICE_ADDRESS); // Inicializar el bus I2C con la direcci ón del dispositivo Wire.onReceive(receiveEvent); // Llama a la funci ón 'receiveEvent' cuando recibe datos Serial.begin(9600); // Inicializa la comunicaci ón serial Serial.println("Arduino ready"); } void loop() { delay(100); Wire.onReceive(receiveEvent); } void receiveEvent(int bytes) { while (Wire.available()) { char c = Wire.read(); // Lee el car ácter recibido del bus I2C Serial.print(c); // Muestra el car ácter recibido en el Monitor Serie } Serial.println(); } import os import time import cv2 import numpy as np import smbus from PIL import Image, ImageDraw, ImageFont import tflite_runtime.interpreter as tflite # Direcci ón del Arduino en el bus I2C (7 bits) arduino_address = 0x68 # Ejemplo: dirección 8 (debes verificar la dirección real de tu Arduino) Apéndices 69 arduino_address = 0x68 # Ejemplo: dirección 8 (debes verificar la dirección real de tu Arduino) # Inicializaci ón del bus I2C (1 indica el bus I2C-1) bus = smbus.SMBus(1) #Funci ón definida para mandar un mensaje al arduino desde la coral dev board def enviar_mensaje(mensaje): bus.write_byte(arduino_address, len(mensaje)) # Env ía la longitud del mensaje primero for caracter in mensaje: bus.write_byte(arduino_address, ord(caracter)) # Env ía cada carácter del mensaje print(f"Mensaje enviado: {mensaje}") # Carga el modelo TFLite interpreter = tflite.Interpreter(model_path="modelo_entrenado_1.tflite") interpreter.allocate_tensors() input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() # Abre la c ámara cap = cv2.VideoCapture(0) threshold=0.85 captured = False # Variable para rastrear si la imagen ya se ha capturado start_time = time.time() # Tiempo de inicio while True: # Captura un cuadro de la c ámara ret, frame = cap.read() if not ret: break # Verifica si han transcurrido 5 segundos y a ún no se ha capturado la imagen if not captured and time.time() - start_time >= 5: captured = True # Marca la imagen como capturada cv2.imwrite("captura.jpg", frame) # Guarda la imagen capturada como "captura.jpg" # Realiza la inferencia en el cuadro capturado image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) image = cv2.resize(image, (input_details[0]['shape'][2], input_details[0]['shape'][1])) image = np.array(image, dtype=np.float32) # Convierte a FLOAT32 image /= 255.0 # Normaliza los valores de p íxeles interpreter.set_tensor(input_details[0]['index'], [image]) interpreter.invoke() output_data = interpreter.get_tensor(output_details[0]['index']) Apéndices 70 Código A10. Código para enviar los mensajes de temperatura y humedad mediante MQTT. output_data = interpreter.get_tensor(output_details[0]['index']) predicted_label = np.argmax(output_data[0]) prediction_confidence = np.max(output_data[0]) # Imprime la etiqueta en la consola with open("labels.txt", "r") as file: labels = file.read().splitlines() if prediction_confidence >= threshold and 0 <= predicted_label < len(labels): print(f"Etiqueta: {labels[predicted_label]}, Confianza: {prediction_confidence}") try: # Envia la etiqueta predicha. enviar_mensaje(labels[predicted_label]) time.sleep(2) except KeyboardInterrupt: #pass if cv2.waitKey(1) & 0xFF == ord('q'): break else: print("No es una se ñal de tráfico") import time import paho.mqtt.client as mqtt # Ajusta el nombre de la importaci ón según el nombre real de la biblioteca y módulo from coral.enviro.board import EnviroBoard from influxdb import InfluxDBClient # Configuraci ón del broker MQTT broker_address = "192.168.1.150" port = 1883 # Puerto por defecto para MQTT topic_temperatura = "casa/sensor/temperatura" topic_humedad = "casa/sensor/humedad" def on_connect(client, userdata, flags, rc): print("Conectado con c ódigo de resultado: " + str(rc)) # Crear una instancia de cliente MQTT client = mqtt.Client()