Full text
Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de Electrónica, Robótica y Mecatrónica Plataforma móvil para la detección y el seguimiento de personas con alta temperatura corporal a través del procesamiento en tiempo real de imágenes en el espectro infrarrojo Dpto. Ingeniería de Sistemas y Automática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Autor: Daniel Clavijo Calvo Tutor: Jesús Capitán Fernández Rafael de la Rosa Vidal Sevilla, 2021
Trabajo Fin de Grado Grado en Ingeniería Electrónica, Robótica y Mecatrónica. Intensificación de Robótica Plataforma móvil para la detección y el seguimiento de personas con alta temperatura corporal a través del procesamiento en tiempo real de imágenes en el espectro infrarrojo Autor: Daniel Clavijo Calvo Tutor: Jesús Capitán Fernández Profesor Contratado doctor Rafael de la Rosa Vidal Contratado predoctoral Dpto. Ingeniería de Sistemas y Automática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2021
Trabajo Fin de Grado: Plataforma móvil para la detección y el seguimiento de personas con alta temperatura corporal a través del procesamiento en tiempo real de imágenes en el espectro infrarrojo Autor: Daniel Clavijo Calvo Tutor: Jesús Capitán Fernández Rafael de la Rosa Vidal El tribunal nombrado para juzgar el Proyecto arriba indicado, compuesto por los siguientes miembros: Presidente: Vocales: Secretario: Acuerdan otorgarle la calificación de: Sevilla, 2021 El Secretario del Tribunal
vii A mi familia A mis maestros
viii
ix Agradecimientos Después de los años de esfuerzo dedicados, toca cerrar una etapa muy importante de mi vida. Simplemente dar las gracias a todas las personas que de un modo u otro han sido partícipes de haber llegado hasta aquí. Recordaré siempre esos grandes profesores que me he encontrado en el camino, que disfrutan de su trabajo y que siempre tienen la mejor actitud ante sus alumnos, incluso en días no tan buenos. En especial, dar las gracias a mis tutores, Rafael de la Rosa, quién ha puesto a mi disposición todo el conocimiento necesario para poder llevar a cabo este trabajo y me ha prestado su ayuda siempre que ha sido necesario. A Jesús Capitán, por haberme prestado su atención cuando lo he necesitado y darme los mejores consejos en los momentos imprescindibles para seguir avanzando en la dirección correcta. Dar las gracias también a Juan Antonio Leñero, quién ha tenido un papel muy importante en el desarrollo de este trabajo, aportando su conocimiento, herramientas y materiales necesarios para hacerlo posible. A Francisco José Garrido, por ser una gran persona y hacer más llevaderas las mañanas de prácticas en el IMSE (Instituto de Microelectrónica de Sevilla), además de contribuir con su trabajo al desarrollo de este, aportando parte del desarrollo de la tarea de detección de rostros. Por último, agradecer el apoyo de mi familia, quienes me soportan día a día y han hecho de mí la persona que soy hoy.
xvi Anexo B Temperature.cpp 67 Anexo C Clase PID 71 Anexo D Rutina principal: peopledetect.cpp 75
xvii Índice de figuras Figura 1-1: Termómetro infrarrojo de pistola. .......................................................................................... 1 Figura 3-1:Arquitectura hardware del dispositivo. ................................................................................... 7 Figura 3-2: Primer prototipo de Raspberry Pi. .......................................................................................... 8 Figura 3-3:Primera prueba CPU. ............................................................................................................. 10 Figura 3-4:Primera prueba GPU. ............................................................................................................. 10 Figura 3-5:Segunda prueba GPU. ............................................................................................................ 11 Figura 3-6:Tercera prueba GPU. ............................................................................................................. 11 Figura 3-7:Cuarta prueba GPU. ............................................................................................................... 11 Figura 3-8:GPIO de la Raspberry Pi 3 B. ................................................................................................ 12 Figura 3-9: Representación de Duty Cycle, periodo y anchura de pulso de señal PWM. ................... 13 Figura 3-10:División del espectro infrarrojos. ........................................................................................ 14 Figura 3-11:PureThermal 2 junto con sensor Lepton 3.5 by FLIR. ...................................................... 15 Figura 3-12:Conexión puerto CSI entre Pi Cam y Raspberry Pi 3 B. ................................................... 16 Figura 3-13:Pi Cam v2.1 (izquierda) y Pi Cam NoIR v2.1 (derecha). .................................................. 17 Figura 3-14:Pi Cam v2.1 (derecha) y Pi Cam NoIR v2.1 (izquierda). Imagen en condiciones de visibilidad nula, con iluminación de LEDs infrarrojos. ......................................................................... 17 Figura 3-15:Pi Cam v2.1 (derecha) y Pi Cam NoIR v2.1 (izquierda). Imagen en condiciones normales. .................................................................................................................................................................... 18 Figura 4-1:Funciones de Haar (izquierda). Aplicación de funciones de Haar (derecha). .................... 20 Figura 4-2: Imagen negativa. ................................................................................................................... 21 Figura 4-3: Imagen positiva. .................................................................................................................... 21 Figura 4-4:ROI del frame (imagen positiva). .......................................................................................... 21 Figura 4-5: Frame completo. .................................................................................................................... 21 Figura 4-6: Paso 1 del entrenamiento de la red. (Captura de pantalla de mi PC). ................................ 22 Figura 4-7: Paso 2 del entrenamiento de la red. (Captura de pantalla de mi PC) ................................. 23 Figura 4-8: Paso 3 del entrenamiento de la red. (Captura de pantalla de mi PC). ................................ 24 Figura 4-9: Pestaña “Boost”. Se dejan los parámetros propuestos por defecto. (Captura de pantalla de mi PC). ....................................................................................................................................................... 25 Figura 4-10:Orden de ejecución............................................................................................................... 25 Figura 4-11: número mínimo de vecinos 0 (izquierda); número mínimo de vecinos 50 (derecha). ... 26
xviii Figura 4-12: Resultados de la detección usando “cascade.xml”. .......................................................... 26 Figura 4-13:Imagen de Trackbars en sus valores correctos junto con imágenes de ambos sensores. 28 Figura 4-14: Orden de ejecución mostrando nombre del programa, cascada y escala empleadas. ..... 28 Figura 4-15:Medidas de Offset entre sensores en la pieza real. ............................................................. 30 Figura 4-16:Resultado de la correspondencia de puntos entre ambas imágenes. ................................. 30 Figura 5-1: Esquema de uso de termómetro de pistola. ......................................................................... 34 Figura 5-2: Resultados de la estimación de la temperatura usando distintas ROI, y tabla de resultados de sensibilidad y especificidad de cada método. .................................................................................... 35 Figura 5-3: Kernel Gaussiano 3x3. .......................................................................................................... 36 Figura 5-4: ROI sin aplicar filtro de Gauss (izquierda); ROI aplicando filtro de Gauss (derecha). .... 37 Figura 5-5:Imagen de la que obtendremos el histograma. ..................................................................... 38 Figura 5-6: Histograma de la imagen suavizada y umbral de segmentación (marcado en rojo). ........ 38 Figura 5-7:ROI detectada (izquierda); Imagen suavizada(centro); Imagen segmentada (derecha). ... 38 Figura 5-8: Error absoluto de temperatura frente a distancia entre objeto y sensor. Curva azul: resultados experimentales. Curva roja: curva de mejor ajuste. .............................................................. 40 Figura 5-9: Medida de temperatura corporal a 40 cm de la persona (izquierda). Medida de temperatura corporal a 180 cm de la persona (derecha). ............................................................................................. 41 Figura 6-1: Vista de plataforma de cámaras y Raspberry Pi 3B. ........................................................... 43 Figura 6-2: Vista explosionada de plataforma de cámaras y Raspberry Pi 3 B.................................... 44 Figura 6-3: Vista de pieza real de plataforma de cámaras y Raspberry Pi 3 B. .................................... 44 Figura 6-4: Soporte base y anclaje del motor 1. ..................................................................................... 45 Figura 6-5: Anclaje del motor 2 (izquierda) y soporte del sistema de cámaras y Raspbery Pi 3B (derecha). ................................................................................................................................................... 45 Figura 6-6: Diseño inicial de la estructura en la que se han marcado los diferentes ejes de relevancia (Pitch y Yaw) y los momentos que se van a crear (MPitch y MYaw). ................................................. 46 Figura 6-7: Montaje de la plataforma incluyendo modelos de los motores. ......................................... 47 Figura 6-8: Montaje de la plataforma incluyendo modelo de los motores y sistema de cámaras. ...... 47 Figura 6-9: Vista del resultado final del montaje completo. .................................................................. 48 Figura 6-10: Parallax Standard Servo. ..................................................................................................... 48 Figura 6-11: Diagrama de bloques general. ............................................................................................ 50 Figura 6-12: Diagrama de bloques del control PI. .................................................................................. 51
xix Lista de abreviaturas PWM Pulse Width Modulaltion ROI Region Of Interest IR Infrarrojo Hz Hertzios XML Extensible Markup Language GPIO General Purpose Input/Output CSI Camera Serial Interface USB Universal Serial Bus RAM Random Access Memory Wi-Fi Wireless Fidelity HDMI High-Definition Multimedia Interface GPU Graphics Processing Unit CPU Central Processing Unit SWIR Short-wavelength infrared MWIR Medium-wavelength infrared LWIR Long-wavelength infrared RGB Red-Green-Blue AGC Automatic Gain Control LBP Local Binary Pattern DC Direct Current FPS Frames per Second
1 Introducción y motivación Con la aparición del virus SARS-CoV-2 en diciembre de 2019, causante de la COVID-19, la OMS ha establecido como rutina ciertos protocolos [1], o recomendaciones, que se deben cumplir a modo de prevenir la expansión de dicha enfermedad [2]. Estas recomendaciones, seguidas por la mayoría de personas, van desde el uso de mascarilla en lugares públicos, mantener una distancia de 1.5 a 2 metros y evitar las aglomeraciones de personas, mantener una correcta higiene de manos y de las superficies de contacto, así como mantener un control de nuestra salud. Relacionado con esto último, uno de los protocolos más habituales consiste en la medición de la temperatura corporal. Por esto, las personas que acuden a lugares frecuentados por otras personas, como puede ser su puesto de trabajo, deben comprobar que su temperatura corporal se encuentra en niveles correctos (entre 35.5 y 37.2ºC). Esto es, no por encima de 38 grados, que es el momento en el que se considera que una persona tiene fiebre a causa de una infección o enfermedad [3]. Debido a esto, el uso de termómetros de pistola (Figura 1-1) se ha incrementado sustancialmente por su facilidad de uso, precio reducido, medición en pocos segundos y precisión. Además, este tipo de termómetro permite realizar una medición a distancia, sin entrar en contacto con la persona que sea objeto de estudio, lo que hace que la distancia de seguridad entre personas mencionada anteriormente se siga cumpliendo. Figura 1-1: Termómetro infrarrojo de pistola. La finalidad de este trabajo es construir un sistema automático para la medición del valor de temperatura corporal, añadiendo además la funcionalidad de seguimiento de rostros. De esta forma tendremos la funcionalidad del termómetro infrarrojo de pistola sin tener que hacerlo manualmente. Esto se llevará a cabo empleando componentes de fácil adquisición en el mercado (productos “off-
2 the-shelf”). Se han cumplido los siguientes objetivos: • Estudiar el estado del arte de los diferentes sistemas existentes que cumplen funciones parecidas, tanto de medición de temperatura como del seguimiento de objetos. De este análisis podremos extraer, por ejemplo, algoritmos de detección de rostros. • Realizar la calibración térmica del sensor infrarrojos montado en el sistema. • Justificar la elección del hardware empleado. • Estudiar, diseñar y construir la plataforma que permitirá el seguimiento automático de rostros. • Implementar el controlador para la función de seguimiento de rostros. • Ajuste y puesta en marcha del sistema. En este trabajo se ha organizado la información de la siguiente forma: en el Capítulo 2 se desarrolla el estado del arte de los distintos sistemas que realizan operaciones similares, así como de los dispositivos que desempeñen por separado las dos funciones más destacadas de nuestro sistema, como son: la medición de la temperatura y el seguimiento de objetos. En el Capítulo 3 se verá en detalle cada elemento de hardware que se ha elegido, de forma justificada, para cumplir con los objetivos propuestos. El Capítulo 4 nos presenta las diferentes formas que se han estudiado para realizar el reconocimiento de rostros, trabajando en un primer método basado en la creación y entrenamiento de una red neuronal en el espectro infrarrojo, y un segundo método en el que a partir del uso de redes neuronales ya existentes en el espectro visible, aplicaremos correspondencia de puntos entre imagen visible e infrarroja. En el Capítulo 5 veremos el trabajo realizado para el procesamiento de la imagen infrarrojos y la forma en que se obtiene un valor de temperatura a partir de niveles de intensidad de píxeles. En el Capítulo 6 se detalla el diseño mecánico de la plataforma propuesta para poder acoplar los servomotores al sistema, así como el algoritmo de control aplicado a cada servomotor para conseguir el movimiento necesario en el seguimiento de rostros. Por último, en el Capítulo 7 se hace un repaso de los objetivos alcanzados y de las posibles líneas de trabajo futuras, que mejorarán el funcionamiento del sistema.
3 Estado del arte El hombre siempre ha tratado de mejorar su calidad de vida, y por ello, el desarrollo y construcción de máquinas que resolviesen operaciones complejas de forma automática y rápida, ha jugado un papel muy importante. Fue en 1946 cuando se construyó ENIAC, la primera computadora electrónica de propósito general, y fue entonces cuando el desarrollo de este campo entró en auge [4, 5]. Estas máquinas implementan algoritmos que permiten resolver problemas de dificultad elevada, pero están limitadas a resolver problemas que admitan la aplicación de dichos algoritmos. Si nos enfrentamos a problemas como la clasificación de objetos por rasgos similares, no podremos resolverlos con estas máquinas. Por eso, estas máquinas debían evolucionar. Esta evolución nos lleva al concepto de inteligencia artificial (IA), que podría definirse como la capacidad de las máquinas para, mediante el uso de ciertos algoritmos, aprender de esos datos y tomar decisiones de la forma en que lo haría un ser humano. Las ventajas de las máquinas sobre las personas las hacen mucho más eficientes y rentables, ya que no necesitan descansar y pueden analizar grandes volúmenes de información al mismo tiempo con una tasa de error mucho menor [6, 7, 8]. La inteligencia artificial surgió en 1956, cuando en una conferencia en Estados Unidos, Allen Newell y Herbert Simon presentaron el primer programa de ordenador que emulaba características del cerebro humano, llamado Logic Theorist [9]. Dentro del ámbito de la inteligencia artificial, se suelen utilizar las redes neuronales, como herramientas para la predicción de tendencias y clasificación de objetos. Además, tienen la capacidad de aprender y mejorar su funcionamiento [10, 4]. También se introduce el concepto de Visión por Computador, que se entiende como el estudio y entendimiento de los procesos de la visión para poder construir máquinas que sean capaces de realizar tareas similares [11]. Entre los objetivos perseguidos por la inteligencia artificial se encuentra la clasificación de objetos en tiempo real. En nuestro caso, nos centramos en la detección de personas, y más concretamente de rostros. En visión artificial y machine learning, un software muy popular es OpenCV, una biblioteca utilizada en multitud de proyectos para llevar a cabo la detección de objetos en imágenes o vídeos y será también la que nos permitirá realizar este trabajo. En nuestro caso nos centraremos en la detección de rostros en tiempo real, para lo cual, OpenCV dispone de numerosos clasificadores entrenados y algoritmos optimizados. Para el reconocimiento facial en el espectro visible existen varios algoritmos capaces de obtener la información necesaria de las imágenes que luego se usarán para el entrenamiento de la red neuronal. Entre los algoritmos más conocidos se encuentran: EigenFace, FisherFace y Viola-Jones [12, 13]. Todos ellos implementan el mismo método de extracción de características, conocido como PCA [14], en el que se eliminan las características que aportan poca o nula información, reduciendo así la cantidad de datos necesaria para el posterior entrenamiento. En concreto, el algoritmo que implementa OpenCV es el de Viola-Jones [15], que introduce el concepto de “Cascade of Classifiers”. Este método consiste en llevar a cabo un algoritmo de detección escalonado en diferentes etapas, condicionadas por las denominadas características de “Haar”. Las
4 funciones de Haar extraen información (características) de la imagen de rostros que luego se utiliza para el entrenamiento de una red neuronal. Cada una de estas características definen una etapa de un clasificador en cascada, y para determinar si una imagen contiene rostro o no, deberá pasar por cada una de las etapas. Si en alguna etapa la imagen no cumple la característica, se descarta y pasa a analizarse la siguiente imagen. Esto hace que sea un algoritmo más eficiente que si se comprobase el cumplimiento de todas las características en una misma etapa. Los algoritmos mencionados tienen buenos resultados cuando se trabaja en el espectro visible, pero para el caso que nos ocupa, el espectro infrarrojo de onda larga (LWIR), ninguno de ellos resulta válido. Esto se debe a que en esta banda del espectro infrarrojo, algunos elementos como la ropa, gafas y distintos complementos filtran la radiación IR, lo que hace que estas zonas aparezcan en tonos oscuros (zonas frías) y resulten imposibles de detectar térmicamente [16]. Por lo tanto, para llevar a cabo la tarea de detección de rostros en el espectro infrarrojo, necesitaremos una base de datos de imágenes infrarrojas, con la que podremos crear una base de datos específica para este uso. La tarea de encontrar una base de datos de imágenes de rostros de personas en el espectro infrarrojo ha resultado complicada y finalmente sin éxito. Se ha intentado pedir permiso para acceder a bases de datos (“LWIR UdeC” y “UCHThermalFace”) de un repositorio privado de la Universidad de Chile [17], pero también sin éxito. Las pocas bases de datos públicas encontradas se encuentran en la bibliografía [18], un repositorio donde podemos encontrar diferentes bases de datos en el espectro infrarrojo, pero necesitan ser procesadas antes de utilizarlas para el entrenamiento de la red neuronal, ya que cada imagen está enmarcada con una serie de datos numéricos que no deberían aparecer a la hora de comenzar el entrenamiento. Por otro lado, La detección de rostros en el espectro visible ha sido un tema muy desarrollado y con una gran cantidad de trabajos realizados sobre este tema. Sin embargo, la detección de rostros en el espectro infrarrojo no ha sido un tema muy tratado, y por eso la escasez de bases de datos de este tipo. Aunque sí existen varios trabajos, todos ellos utilizan métodos distintos a las redes neuronales para la detección de rostros. El método en el que se basan estos trabajos [19, 20, 21, 22] consiste en la segmentación de la imagen por temperatura, partiendo de la idea de que la temperatura corporal se encuentra normalmente en un rango superior a la temperatura del ambiente. Considerando que la temperatura corporal se encuentra por encima de los 30ºC aproximadamente, y que el resto de la imagen (ambiente y fondo) se encuentra a valores de temperatura inferiores. De esta forma, se puede segmentar la imagen para los valores de temperatura más altos, quedándonos así con imágenes en la que las intensidades de píxeles bajas (inferiores al umbral establecido) se toman como regiones a 0ºC. Una vez segmentada la imagen, se recorre para el encasillamiento encontrando los segmentos horizontales y verticales con intensidades correspondientes al rostro de la persona. Otro método utilizado para la detección de rostros en el espectro infrarrojo consiste en aplicar un descriptor de texturas para la segmentación de la piel en imágenes [23]. Al fin y al cabo, este método, como el de segmentación de piel por temperatura, son métodos que funcionan, pero tienen problemas similares, ya que en determinadas condiciones pueden dar un alto número de falsos positivos. Por último, existe un trabajo en el que se ha usado una red neuronal para llevar a cabo la detección de rostros en el espectro infrarrojo [24]. Esta red neuronal ha sido entrenada con las bases de datos nombradas anteriormente, “LWIR UdeC” y “UCHThermalFace”. La primera está formada por 612 imágenes infrarrojas en las que participaron 102 personas. La segunda es una colección de 1484 imágenes tomadas a 53 personas. En este caso se obtienen buenos resultados en la detección. Este trabajo se implementó en un System on Chip Zynq-7000, que integra un procesador ARM con una FPGA. La ejecución del detector de rostros se lleva a cabo con OpenCV (implementación en software), lo que hace que sea la parte limitante del sistema, obteniendo como resultado una tasa de 1 fps. Dadas las dificultades para conseguir una base de datos de imágenes infrarrojas, en este trabajo se expondrán y detallarán dos métodos posibles a seguir para la detección de rostros en el espectro infrarrojo: la creación de una base de datos propia de imágenes en el infrarrojo para luego poder entrenar una red neuronal(ver apartado 4.1), y la utilización de una red neuronal ya entrenada con una base de datos en el espectro visible, disponible en el repositorio de OpenCV (ver apartado 4.2). Con
este segundo método podremos encontrar puntos significativos en la imagen IR análogos a la imagen de espectro visible. El trabajo desarrollado se ha implementado en la plataforma Raspberry Pi, siendo OpenCV, el software encargado de llevar a cabo la detección de rostros. Por lo que estaríamos realizando una implementación software en hardware de propósito general. Además, aunque la generación de Raspberry Pi usada no sea la más actual, se consiguen tasas de ejecución de unos 3 fps cuando en la imagen se detecta rostro, y de 5 fps cuando no se da ninguna detección, por lo que podemos darnos cuenta de que el cuello de botella del sistema sigue siendo la detección de rostros. Como vemos, la ejecución de este sistema es 3 veces mayor que el sistema comentado anteriormente en el que se utiliza una red neuronal específica del espectro infrarrojo. Para crear la plataforma en la que integraremos el sistema, se ha optado por una plataforma tipo gimbal [25]. Este tipo de plataformas, en las que el eje del motor se suele utilizar directamente como anclaje de las distintas piezas que forman la estructura, pueden llegar a ser bastante complejas en función de los grados de libertad que tenga. Esta plataformas son muy usadas para la estabilización de sensores de cámara, utilizándose tanto en drones o helicópteros [26, 27], como en teléfonos móviles a modo de “palo selfie” [28]. En nuestro caso, será un gimbal de dos ejes, con el que podremos modificar los valores de Pich y Yaw mediante la inclusión de dos servomotores.
12 3.2 RASPBERRY PI 3 MODELO B y en cada división se mantiene la tensión abierta únicamente un 5%, encontramos que la tensión promedio a la salida es un 5% del valor de mantener la señal a valor alto. Entonces, el resultado final de la tensión varía proporcionalmente al porcentaje de tiempo que mantenemos a valor alto la señal, es decir, el valor eficaz de la tensión depende directamente del ancho de pulso. Por eso, a esta técnica se le llama modulación de anchos de pulsos (PWM). Se introducen entonces dos conceptos: la frecuencia de la señal PWM, y el Duty Cycle. La frecuencia de la señal PWM se corresponde con el tiempo entre disparo de pulsos. Si dividimos 1 segundo en 500 divisiones, la frecuencia de PWM será de 500Hz (el período entre pulsos será 1/500 segundos). En cambio, el Duty Cycle es un valor en porcentaje que representa el tiempo que tenemos en valor alto la señal respecto del tiempo máximo posible [39]. Para el caso anterior, en el que tenemos en alta la señal un 5% en cada división, ese será el valor de Duty Cycle (ver Figura 3-9). Para entender el funcionamiento de las señales PWM como un ejemplo de la vida real y que realizamos todos los días sin darnos cuenta y sin pensar en ello, podemos pensar en el caso práctico de llenar un vaso de agua del grifo. Cuando veamos que el vaso está casi lleno, cerraremos el grifo poco a poco y no por completo de manera instantánea, para evitar que el agua se derrame del vaso. Esta regulación del caudal de agua es algo sencillo si se trata de hacerlo en modo analógico. En sistemas digitales es algo complejo, ya que digitalmente podemos abrir (1) o cerrar (0) el grifo. Es entonces cuando usamos señales PWM, cuando queremos simular, con un sistema digital, el comportamiento de un sistema analógico. Figura 3-8:GPIO de la Raspberry Pi 3 B.
CAPÍTULO 3 HARDWARE 13 Figura 3-9: Representación de Duty Cycle, periodo y anchura de pulso de señal PWM. Para el caso de laFigura 3-9, si tenemos una tensión de alimentación Vcc de 5V y tan sólo queremos una señal de 1V, podemos generar una señal PWM que el 20% del tiempo valdrá 5V y el 80% restante valdrá 0V siguiendo las siguientes ecuaciones que modelan el valor de Duty Cycle y el valor de la señal PWM [40]. Teniendo en cuenta la gráfica c) (50% Duty Cycle) de la Figura 3-9, y a partir de la ecuación 3-1, obtenemos la ecuación 3-2. De forma general podemos obtener el valor de salida de la señal PWM (Vsalida), como indica la ecuación 3-3. 𝑉𝑚𝑒𝑑 =1 𝑇∫ 𝑉(𝑡)𝑑𝑡 𝑇 0 (3-1) 𝑉𝑚𝑒𝑑 =1 𝑇(∫ 𝑉(𝑡)𝑑𝑡+∫ 𝑉(𝑡)𝑑𝑡) 1 0.5 0.5 0 (3-2) 𝑉𝑠𝑎𝑙𝑖𝑑𝑎 =(𝑉𝑐𝑐𝑚á𝑥 +𝑉𝑐𝑐𝑚𝑖𝑛)∗ 𝐷𝑢𝑡𝑦 𝐶𝑦𝑐𝑙𝑒 (3-3) 𝐷𝑢𝑡𝑦 𝑐𝑦𝑐𝑙𝑒=𝑇𝑂𝑁 𝑇 (3-4) Donde: TON = tiempo con señal a valor alto. T = tiempo total o Periodo. 3.3. Sensor Flir Lepton 3.5 Gracias a la termografía infrarroja, somos capaces de medir temperaturas a distancia sin tener un contacto físico con el objeto a analizar. Esto se hace mediante la captación de la intensidad de radiación infrarroja que emiten los cuerpos. La termografía es un campo de la ciencia que está evolucionando muy rápidamente en las últimas décadas debido a los grandes avances realizados en la tecnología de
14 3.3 SENSOR FLIR LEPTON 3.5 microsistemas y el diseño de los detectores infrarrojos, entre otros [41]. En la actualidad se puede aplicar estos conocimientos en diferentes campos de la industria, como por ejemplo la monitorización de sistemas y el mantenimiento predictivo de estos, lo que puede significar la reducción de costes. Por ejemplo, la termografía permite la detección de escapes de gas invisibles para el ojo humano. Utilizando cámaras termográficas podemos convertir la energía radiada, que es invisible al ojo humano, en una imagen visible formada por la temperatura superficial de los objetos captados por la cámara. Estos dispositivos a los que llamamos ‘cámaras’ son en realidad sensores que detectan el calor radiado y nos lo muestra en forma de imagen. A los dispositivos que operan en la longitud de onda infrarroja se les conoce como ‘generadores de imágenes’ [41, 42, 43]. En termografía infrarroja nos podemos encontrar tres longitudes de onda: infrarrojos de longitud de onda corta (SWIR), infrarrojos de longitud de onda media (MWIR) e infrarrojos de longitud de onda larga (LWIR). Figura 3-10:División del espectro infrarrojos. Como vemos en la Figura 3-10, el infrarrojo de longitud de onda corta (SWIR) se define en el rango de longitud de onda de 1 a 2.7 micras. El infrarrojo de longitud de onda media (MWIR) se define en el rango de 3 a 5 micras [44]. Por su parte, el infrarrojo de longitud de onda larga (LWIR) se define entre las 8 y 14 micras. Este será nuestro rango de interés, ya que para la aplicación con la que vamos a trabajar, implementaremos un sensor de espectro infrarrojos LWIR. Este tipo de dispositivos son los que se suelen utilizar en las prácticas de inspección de temperatura. La forma en que lo hacen es determinando si un punto está más frío o más caliente que otro captando su emisión infrarroja [45]. En este proyecto usaremos el sensor “Lepton 3.5” desarrollado por FLIR junto con la placa de conexiones “PureThermal 2” como se muestra en la Figura 3-11, que deberá ir conectado a uno de los puertos USB de los que dispone la Raspberry Pi 3 B.
CAPÍTULO 3 HARDWARE 15 Figura 3-11:PureThermal 2 junto con sensor Lepton 3.5 by FLIR. Características del sensor Lepton 3.5 de FLIR En la web del sensor podemos encontrar las siguientes características para este sensor [46] (Figura 3-11): • Sensibilidad térmica: <50 mK (0.050˚C). • Rango espectral: 8-14 micrones (nominal) Infrarrojos de onda larga (LWIR). • Resolución imagen: 160 x 120 píxeles. • Precisión radiométrica 1 [47]: Modo de alta ganancia: mayor de +/- 5 ° C o 5%; Modo de baja ganancia: mayor de +/- 10 ° C o 10%. • Rango dinámico 2 de escena [48]: Modo de alta ganancia: -10 ° a + 140 ° C; Modo de baja ganancia: -10 ° a + 400 ° C (a temperatura ambiente) -10 ° a + 450 ° C (típico). • Tamaño de píxel: 12 micrómetros. • Velocidad de fotogramas: 8,7 Hz (efectivo). • Formato de salida: seleccionable por el usuario de 14 bits, 8 bits (AGC aplicado) o RGB de 24 bits (AGC y coloración aplicados). • Campo de visión horizontal (HFOV): 57 °. • Tipo de lente: f / 1.1 • Consumo de energía: 150 mW típico, 650 mW durante el evento del obturador, 5mW en espera. 1 La precisión radiométrica hace referencia al número de niveles digitales utilizados para representar los datos recogidos por el sensor. 2 El rango dinámico de un sensor hace referencia a la capacidad de éste para obtener una gran variedad de blancos, grises y negros. A mayor rango dinámico, más detalles será capaz de detectar el sensor.
16 3.4 SENSOR PI CAM NOIR 2.1 • Dimensiones (ancho x largo x alto): 10,50 x 12,70 x 7,14 mm. • Peso: 0,9 gramos. 3.4. Sensor Pi Cam NoIR 2.1 Para capturar imágenes en el espectro visible, usaremos la cámara Raspberry Pi Cam de 8 megapíxeles. Más concretamente la versión NoIR v2.1 que utiliza un sensor SONY IMX219 [49]. Este modelo de cámara es compatible con todos los modelos de Raspberry Pi con cámara, y es prácticamente idéntico al sensor del modelo anterior (v1.3). La serie de dispositivos Pi Cam están fabricados especialmente para su uso con los modelos de Raspberry, por lo que cuentan con el conector CSI para permitir la comunicación entre ambos dispositivos. En la Figura 3-12 se muestra dicha conexión. Este puerto CSI ofrece un bus de conexión eléctrica unidireccional (de la cámara al procesador) entre dos dispositivos con 15 conductores, con un ancho de banda de 4 GBps para la transferencia de datos entre cámara y procesador [31]. Figura 3-12:Conexión puerto CSI entre Pi Cam y Raspberry Pi 3 B. Podemos ver en la web de “raspi.tv” sus especificaciones principales [49]: Lente: Enfoque fijo (No ajustable). Megapíxeles: 8 MP. Resolución de cámara: 3280 x 2464 píxeles. Resolución de vídeo: 1080p30, 720p60 y 640x480p90 Interfaz: CSI. Conector: conector plano. Dimensiones: 25 mm x 23 mm x 9 mm. Peso: ~3 g.
CAPÍTULO 3 HARDWARE 17 Figura 3-13:Pi Cam v2.1 (izquierda) y Pi Cam NoIR v2.1 (derecha). Como vemos en la Figura 3-13, existen dos versiones de esta Pi Cam v2.1 [49]. La que usaremos nosotros en este proyecto será la versión NoIR v2.1. Esta denominación NoIR nos indica que el sensor no incluye filtro de infrarrojos, y es sensible a la radiación por infrarrojos de onda corta (SWIR), pero no es sensible a la radiación por infrarrojos de longitud de onda larga (calor, LWIR) [50]. Por tanto, no sería posible utilizar este sensor para detectar a personas mediante su calor corporal. Además, para poder ver en la oscuridad con este sensor, se necesita iluminación mediante LEDs infrarrojos, con los que podemos iluminar la escena en el espectro infrarrojo y captar imágenes claras en la oscuridad. En la Figura 3-14 podemos ver la diferencia entre ambos modelos mostrados cuando se enfoca a una pared de ladrillos en la oscuridad utilizando una iluminación de LEDs infrarrojos [51]. Figura 3-14:Pi Cam v2.1 (derecha) y Pi Cam NoIR v2.1 (izquierda). Imagen en condiciones de visibilidad nula, con iluminación de LEDs infrarrojos. Se puede apreciar que el sensor NoIR recoge una imagen bastante clara en el espectro infrarrojos mientras que el sensor de visión normal no es capaz de recoger ningún tipo de información de la escena. Otra diferencia a destacar entre ambos sensores, es la calidad de imagen que se obtiene en condiciones normales con clara visibilidad. El sensor NoIR, al no tener filtro de infrarrojos obtiene una imagen con colores más “desgastados” (más rosados) que la imagen que podemos ver con el sensor que sí incluye este filtro, el sensor de visión normal.
18 3.4 SENSOR PI CAM NOIR 2.1 Figura 3-15:Pi Cam v2.1 (derecha) y Pi Cam NoIR v2.1 (izquierda). Imagen en condiciones normales.
19 Algoritmos de detección de rostros Como ya sabemos, llevar a cabo la tarea de detección de caras será el primer objetivo y más básico e importante de todos los que componen este proyecto, ya que sobre él se irán añadiendo el resto de funcionalidades del sistema. Para llevar a cabo este primer objetivo, se proponen dos métodos totalmente diferentes uno de otro. Como tenemos dos sensores de cámara instalados en nuestro sistema, el sensor de infrarrojos y el sensor de espectro visible, disponemos de varias posibilidades y caminos a seguir para tener un buen reconocimiento de rostros. Se describirán los dos métodos que se han implementado y testado, y que se proponen como solución a este problema. El primero de los métodos de detección de caras que se propone se basa en el uso del sensor de espectro infrarrojos para conseguir una detección rápida y precisa, que, aunque a priori parece ser la manera más óptima para nuestra aplicación, también presenta algunos inconvenientes, como veremos en la sección correspondiente. El segundo método que veremos se basa en la utilización simultánea de ambos sensores de cámara para llevar a cabo la detección de rostros con uno de ellos, y representar esa cara detectada en la imagen recogida por el otro sensor. Es decir, se utilizará el sensor de espectro visible para reconocer caras y encuadrar cada una de ellas en un Bounding-Box. Seguidamente, esas coordenadas de los puntos que conforman ese Bounding-Box se pasará de una imagen (espectro visible) a otra (espectro infrarrojo) mediante correspondencia de puntos. Este método tampoco es perfecto y se detallarán sus carencias en su respectiva sección. 4.1. Método 1: Creación y entrenamiento de un detector Haar Cascade El principal problema del proyecto consiste en poder dotar al sistema de la capacidad de detección de rostros de personas en el espectro infrarrojo. Para poder llevarlo a cabo, primero se debe explicar el concepto de “Haar Cascade” [52]. En 2001, Paul Viola y Michael Jones proponen en su artículo "Rapid Object Detection using a Boosted Cascade of Simple Features" [15] un método eficaz de detección de objetos utilizando clasificadores en cascada basados en características de “Haar”. Al trabajar con detecciones de rostros, necesitaremos imágenes para entrenar el clasificador. Se necesitan muchas imágenes positivas (imágenes de rostros) e imágenes negativas (imágenes sin rostros). Las funciones de Haar se utilizan para extraer características del algoritmo y entrenar así el clasificador [53].
20 4.1 MÉTODO 1: CREACIÓN Y ENTRENAMIENTO DE UN DETECTOR HAAR CASCADE Figura 4-1:Funciones de Haar (izquierda). Aplicación de funciones de Haar (derecha). Cada característica representa un valor obtenido de realizar la resta del valor de los píxeles que se encuentran bajo el rectángulo blanco a la suma de los píxeles que se encuentran bajo el rectángulo negro. Estas funciones de Haar se aplican sobre la matriz de píxeles como una máscara, dependiendo de si se trata de una función de dos, tres o cuatro rectángulos. Esto provoca que el número de características sea muy elevado. La imagen que cumpla con las características que se consideren como más significativas de una imagen en la que se encuentra rostro, se considerará imagen que contiene rostro. Para hacer la detección más eficiente y no tener que aplicar todas las características en una misma ventana, es decir, en cada región de la imagen para detectar cara, el método de ‘Viola-Jones’ introduce el concepto de “Cascade of Classifiers”, que propone hacerlo de forma escalonada y por etapas. Esto es, aplicar una serie de características básicas a la región que se analiza, y si no las cumple, desecharla y pasar a analizar la siguiente región. La región que cumpla con las características de la primera etapa pasa a la segunda etapa donde se le aplican nuevas características. Así de manera sucesiva hasta cumplir con todas las etapas. La región que pasa por todas ellas es una región de cara [15]. Existen varias “Haar Cascade” ya entrenadas y con la información de dicho entrenamiento almacenada en archivos XML en el repositorio de OpenCV en GitHub [54]. Estas redes neuronales son específicas para la detección de personas o partes de ella (rostros, cuerpos completos, ojos, etc.) disponibles para todo aquel que desee usarlas. Además, OpenCV también pone a nuestra disposición ficheros de código que hacen uso e implementan estos archivos “Haar Cascade”. Estos archivos XML se han creado a partir de bases de datos formadas por imágenes del espectro visible convertidas a escala de grises como podemos ver en las imágenes de explicación del funcionamiento de las características de Haar en la web de OpenCV [52], para usarse con sensores de cámara de espectro visible, y no para su uso con cámaras térmicas como es el caso de este proyecto. Por este motivo, el uso de estas “Haar Cascade” en el infrarrojo ofrecen un rendimiento muy bajo. Este problema nos lleva a la creación de nuestro propio archivo XML, que debe ser específico para nuestra aplicación y para ello debemos emplear una base de datos compuesta por imágenes en el espectro infrarrojo. Vamos a describir paso a paso cómo se ha creado y entrenado una red neuronal, y con la información de dicho entrenamiento se genera el fichero que finalmente usaremos, llamado “cascade.xml”. Creación de la base de datos Los archivos XML que podemos encontrar en el repositorio de OpenCV se han creado a partir de una base de datos de imágenes tomadas con una cámara de espectro visible y convertidas a escala de grises. Para crear nuestro propio clasificador Haar Cascade, y que sea específico para su uso en el espectro infrarrojo, primero debemos crear una base de datos compuesta de imágenes en el infrarrojo. Crearemos una carpeta de trabajo específica para el desarrollo de nuestro archivo XML, que
CAPÍTULO 4 ALGORITMOS DE DETECCIÓN DE ROSTROS 21 llamaremos por ejemplo “IRFacesCascade”. Para tomar estas imágenes térmicas se ha usado el propio sistema de cámaras que tenemos ya montado. Tendremos que usar dos tipos de imágenes: imágenes positivas (imágenes de caras) e imágenes negativas (imágenes que no contienen cara) [55]. En nuestra carpeta de trabajo “IRFacesCascade” creamos ahora una carpeta “p” en la que guardaremos todas las imágenes positivas, y una carpeta “n” en la que se guardarán todas las imágenes negativas. La Figura 4-2 y la Figura 4-3 son ejemplos de lo que guardaremos en las carpetas “n” y “p”: Para tomar estas imágenes haciendo uso del propio sensor de infrarrojos de nuestro sistema, utilizamos un fichero de código escrito en lenguaje ‘Python’ [56] que podemos ver en la sección de ‘Anexos’ donde se da una breve explicación de su funcionamiento (ver Anexo A). De esta forma podemos añadir imágenes reducidas de tamaño a las carpetas “n” y “p” de manera sencilla y rápida. Las imágenes que guardamos en estas carpetas son en realidad la región de interés de la imagen captada por el sensor, es decir, en el caso de tener una imagen positiva, guardaremos en la carpeta “p” (cuando pulsemos la tecla ‘p’) únicamente la ROI. Además, la ROI se almacena en las carpetas de imágenes correspondientes con un tamaño reducido, concretamente de un ancho de 38 píxeles y alto de 46 píxeles. Esto ayudará a que la base de datos ocupe un menor espacio, y el posterior entrenamiento de la red neuronal tendrá un coste computacional menor, ya que si no reducimos el tamaño de estas imágenes, tendría que entrenar con imágenes de 65 píxeles de ancho y 80 de alto (tienen este tamaño porque sólo almacenamos la ROI, serían de 160x120 píxeles si almacenamos la imagen completa, como vimos en las especificaciones del sensor en el apartado 3.3.1 Características del sensor Lepton 3.5 de FLIR. En esta reducción se ha tenido en cuenta que en la imagen guardada para el entrenamiento se sigan apreciando de manera clara los detalles que harán característicos cada rostro que se pueda almacenar como ojos, cejas, nariz, etc, (ver Figura 4-4). En definitiva, se ha creado una base de datos compuesta de 477 imágenes positivas y 1000 imágenes negativas. Como imágenes positivas, se han tomado imágenes de personas con gafas y sin ellas para tener en cuenta estos dos posibles escenarios. Aun así, la carpeta de imágenes positivas es bastante escasa y la variedad de rostros que incluye es muy pobre. La gran variedad de casos posibles que se pueden dar en la realidad, con rostros y expresiones distintas, hace que el funcionamiento de esta red neuronal no sea muy efectivo, excepto con las personas a las que se le tomaron fotografías de sus rostros para la creación de la base de datos, a las que detectará de manera rápida y eficiente. A este conjunto de imágenes positivas podríamos incluir también imágenes de personas con mascarilla para hacerla más completa, mejorando la capacidad detección. Figura 4-2: Imagen negativa. Figura 4-3: Imagen positiva. Figura 4-5: Frame completo. Figura 4-4:ROI del frame (imagen positiva).
28 4.2 MÉTODO 2: MAPEO ENTRE LOS PÍXELES DE LA CÁMARA VISIBLE Y LOS DE LA INFRARROJA. “iSliderValue2” hacemos el cambio contrario para obtener los valores reales de offset (líneas 306 y 307) mostrado en las ecuaciones 4-1 y 4-2. 𝑋𝑂𝐹𝐹𝑆𝐸𝑇 = 𝑖𝑆𝑙𝑖𝑑𝑒𝑟𝑉𝑎𝑙𝑢𝑒1 − 50; 𝑌𝑂𝐹𝐹𝑆𝐸𝑇 = 𝑖𝑆𝑙𝑖𝑑𝑒𝑟𝑉𝑎𝑙𝑢𝑒2 − 50; (4-1) (4-2) Figura 4-13:Imagen de Trackbars en sus valores correctos junto con imágenes de ambos sensores. Para tener el Bounding-Box correctamente centrado en la imagen IR, como ocurre en la imagen del sensor de espectro visible, los valores correctos de las variables “iSliderValue1” y “iSliderValue2” son 40 y 75 respectivamente. Por lo que “X_OFFSET” e “Y_OFFSET”, al despejar de la ecuación vista más arriba, nos quedan con los valores (en píxeles) de -10 y 25 respectivamente. Los valores de “X_OFFSET” e “Y_OFFSET” dependerán del tamaño de la imagen de espectro visible. Este tamaño podemos modificarlo haciendo uso de un parámetro de entrada que debemos aportarle al código de OpenCV que estamos usando. Esto lo indicamos en la línea de comandos junto con el ejecutable del programa y la red neuronal que vamos a emplear para el reconocimiento de caras: Figura 4-14: Orden de ejecución mostrando nombre del programa, cascada y escala empleadas. El parámetro ‘scale’ se usa para reducir la imagen un factor de escala que nosotros indiquemos. Por ejemplo, si indicamos en la línea de comandos un valor ‘scale=1.3’, la imagen se reducirá en ambos ejes por igual. Cuanto más reduzcamos la imagen menor coste computacional supondrá el tratamiento de la misma. Esta reducción de tamaño se lleva a cabo con la función “resize()” [64], a la que se le indican como parámetros la matriz de entrada (frame original), la matriz de salida (frame redimensionado), el tamaño que queremos que tenga la matriz de salida, el cuál se puede indicar directamente con el propio tamaño de la matriz que queremos, o mediante factores de escala para cada eje de manera independiente. También se indica la forma de interpolación usada, y en nuestro caso usamos la interpolación bilineal. Para reducir la imagen (o ampliarla si quisiéramos) se han usado los factores de escala, aplicando el mismo factor a ambos ejes. El factor de escala que se emplea tiene la forma indicada en la ecuación 4-3.
CAPÍTULO 4 ALGORITMOS DE DETECCIÓN DE ROSTROS 29 𝑓𝑥=1 𝑠𝑐𝑎𝑙𝑒 (4-3) En el método de interpolación bilineal [65] se toman los cuatro vecinos más cercanos al píxel, interpolando en primer lugar linealmente en la dirección de las filas de una imagen y posteriormente el resultado se interpola linealmente en la dirección de las columnas. En el método de interpolación al vecino más cercano, por el contrario, se toma el valor del pixel más cercano al pixel que estamos calculando. 4.2.2. Bounding-Box en la imagen de infrarrojos. Hemos visto en el apartado anterior la forma en la que podemos obtener el offset que debemos aplicar a cualquier punto que tengamos en la imagen del espectro visible para obtener el punto correspondiente en la imagen de infrarrojos. Ahora veremos el segundo paso, o paso que engloba al anterior, ya que explicaremos cómo calcular dicha correspondencia de puntos, mientras que en el apartado anterior vimos únicamente una explicación de los valores de offset y el cómo poder variar sus valores de forma sencilla. Una vez conocidos esos valores de offset, tenemos que aplicárselos a los puntos de interés en nuestra matriz de visión normal, “VMat”, para encontrar esos mismos puntos en el frame correspondiente de la imagen de infrarrojos, “IRMat”. Esos puntos de interés serán para nosotros los que conforman cada rectángulo de cada frame de la imagen en el que se produce una detección de rostro, es decir, el Bounding-Box. Se puede observar que lo primero que tenemos que hacer es detectar los rostros de las personas que aparezcan en la imagen. Para ello hacemos uso de nuevo de la función “detectMultiScale” (línea 315), ahora junto con la red neuronal de OpenCV (“haarcascade_frontalface_default.xml”). El uso de esta red neuronal se la indicamos al programa desde la línea de comandos. La región formada por estas caras detectadas, se guardarán en una variable definida como un vector de rectángulos, “faces” (segundo argumento de la función detectMultiScale), en la que se almacena toda la información de un rectángulo: coordenadas X e Y de la esquina superior izquierda (“faces.x, faces.y”) y el ancho y alto (“faces.width, faces.heigh”). Una vez hayamos obtenido el Bounding-Box de la imagen de espectro visible (los cuatro puntos que se han almacenado en la variable “faces”) podemos pasar, mediante los valores de offset calculados anteriormente, los puntos que forman la esquina superior izquierda e inferior derecha, a la imagen infrarroja. Esto lo hacemos con cada una de las esquinas de nuestro Bounding-Box. El método seguido consiste en dividir cada coordenada (X e Y) de las dos esquinas de cada rectángulo por una constante a la que se ha llamado “PROP” (línea 350), cuyo valor es la proporción que hay entre los tamaños de la imagen tomada por el sensor de espectro visible (640x320 píxeles) y la imagen tomada por el sensor infrarrojos (160x80 píxeles). Podemos ver que la proporción que existe entre cada imagen es de 4 veces menor la imagen infrarroja que la imagen visible, por tanto, PROP = 4. Para obtener el punto en la imagen térmica, dividimos las coordenadas X e Y de cada uno de los elementos del vector “faces” por esa constante de proporción. Esto nos daría el punto correspondiente en la imagen térmica si suponemos que ambos sensores están superpuestos, es decir, con offset 0. Para ajustar ese offset no nulo entre sensores, sumamos a las coordenadas resultantes en la imagen IR el offset calculado en el apartado anterior, las variables “X_OFFSET” y “Y_OFFSET”.
30 4.2 MÉTODO 2: MAPEO ENTRE LOS PÍXELES DE LA CÁMARA VISIBLE Y LOS DE LA INFRARROJA. Figura 4-15:Medidas de Offset entre sensores en la pieza real. Se puede apreciar en la Figura 4-15 que el offset entre los objetivos de cada sensor es de 25 mm en horizontal y de 8 mm en vertical. El sensor colocado más arriba es el sensor de espectro infrarrojo (cuadrado), y el otro el sensor de visible (redondo). Además, se usa también otra constante, “MARGIN”, con valor 5. Esta constante representa un margen de píxeles que le damos a cada esquina del Bounding-Box en la imagen infrarrojos para agrandarlo un poco más y que encuadre por completo la cara de la persona. Para ello se debe restar este valor a la esquina superior izquierda del rectángulo, y sumarlo a la esquina inferior derecha del mismo. De esta forma conseguimos un rectángulo de mayor dimensión y un encuadre de la cara más preciso, como ocurre en la imagen de espectro visible. Finalmente comprobamos que el rectángulo que vamos a dibujar en la imagen térmica no se salga de los límites de la matriz “IRMat”, limitando así los valores de las esquinas de cada rectángulo a los valores máximos que pueden tomar (línea 356). Figura 4-16:Resultado de la correspondencia de puntos entre ambas imágenes.
CAPÍTULO 4 ALGORITMOS DE DETECCIÓN DE ROSTROS 31 4.3. Ventajas e inconvenientes de ambos métodos. Entendiendo que el método 1 se corresponde con la creación y entrenamiento de una red neuronal específica del infrarrojo, y que el método 2 hace referencia al mapeo entre píxeles de ambos sensores de imagen, veremos las ventajas y desventajas que suponen la utilización de cada uno de ellos. El método 1 sería el óptimo, ya que, al crear una red neuronal específica para nuestra aplicación, el funcionamiento será más eficiente. Además, presenta una ventaja importante a tener en cuenta, como puede ser: el uso del sistema en situaciones de poca luz ambiente, ya que, al usar directamente el sensor de infrarrojos, se podrá detectar a la persona en cualquier situación de luminosidad que se pudiera dar. Esto es así porque el sensor de infrarrojos utilizado, descrito en el apartado Sensor Flir Lepton 3.5 de este trabajo, es un sensor que capta información del espectro infrarrojos de longitud de onda larga. Es decir, es capaz de captar la información del calor superficial de los objetos del entorno [45] por lo que incluso en una situación completamente a oscuras, se seguiría reconociendo a la persona que se enfoca. Como características a destacar del método 1, es capaz de reconocer rostros estando la persona a una distancia máxima de unos 2 metros aproximadamente. A esta distancia empieza a detectar algunos rostros con una tasa de acierto baja. Pero si la persona se acerca al dispositivo hasta los 1.5 metros aproximadamente, el funcionamiento mejora y la tasa de acierto aumenta considerablemente, reconociendo el rostro de la persona con una tasa de acierto de más del 90%. Para detectar rostros a esta distancia, es necesario bajar un poco el valor del número mínimo de vecinos en la función “detectMultiScale”, ya que, al encontrarse la persona más alejada del sensor se producirán menos reconocimientos de una misma persona en el barrido de la ventana, como se explicó en el apartado 4.1.3 Esto también provoca que al acercarnos más al dispositivo, se produzcan más detecciones falsas, por lo que debemos ajustar este parámetro en función de la cercanía a la que queramos obtener las detecciones más fiables. Se ha optado por un valor de número mínimo de vecinos de 50, que nos dará buenas detecciones a distancias cercanas. Además, si la persona usa mascarilla y gafas al mismo tiempo, el funcionamiento de la red a distancias más alejadas empeora un poco, pero a distancias cortas (1 metro o inferior) también tiene una tasa de reconocimiento elevada, como para el caso anterior. El tiempo de ejecución para este algoritmo es bastante reducido, ya que se han medido tiempos que varían entre los 1,5 ms y 2,5 ms, lo que podemos decir que se tarda una media de 2 ms en reconocer el rostro de la persona que aparece en imagen. Pese a ser el método para reconocimiento de rostros más adecuado, contamos con varios inconvenientes en el momento de ponerlo en práctica en lo que sería su entorno de funcionamiento. El tipo de entrenamiento que se ha realizado con esta red neuronal (a partir de una base de datos reducida) es válido para aplicaciones en las que se busque el reconocimiento de objetos que no van a cambiar de forma, o que en todas aquellas situaciones posibles en las que pudiera aparecer, lo hiciera siempre con esa misma forma. Así, con una base de datos con pocas imágenes (alrededor de 1500 aproximadamente) podemos cubrir casi la totalidad de situaciones posible, tomando imágenes de dicho objeto en diferentes posiciones y ángulos respecto del sensor. Esto no es así con la aplicación que queremos abordar en este proyecto, ya que cada rostro posee unos rasgos determinados (barba, pelo largo, bigote, etc), y hace que la cantidad de situaciones posibles que podemos encontrar sea muy grande. El mayor inconveniente en este caso, es la necesidad de crear una base de datos lo suficientemente grande y diversa, es decir, se necesitan miles de imágenes tanto positivas como negativas de distintas personas manteniendo diferentes gestos y posiciones. Cuantas más personas participen en la creación de dicha base de datos, mayor diversidad de rostros posibles será capaz de detectar y mejor entrenada estará la red, consiguiendo así una precisión mayor. Por este motivo, emplearemos el método 2 para la tarea de reconocimiento facial, en el que usaremos una de las redes neuronales que podemos encontrar en el repositorio de OpenCV, que han sido entrenadas con bases de datos mucho más extensas (método 2) y que cubren casi la totalidad de casos posibles. Esto nos ahorra todo el trabajo que conlleva la creación de una base de datos propia para nuestra aplicación, además de entrenar una red neuronal y guardar esa información del
32 4.3 VENTAJAS E INCONVENIENTES DE AMBOS MÉTODOS. entrenamiento en un fichero de tipo XML, tal y como se ha visto en el apartado 4.1. A pesar de esta ventaja que nos aporta y, aunque resulta efectivo para nuestra aplicación, presenta varios problemas a la hora de llevarlo a la práctica. Estos problemas se deben sobre todo a la iluminación que necesita la cámara de espectro visible para funcionar correctamente. Al no tener ningún foco de luz integrado en el sistema, a modo de flash como en cualquier teléfono móvil moderno, es necesario tener una buena iluminación en el ambiente en el que se va a colocar el conjunto de cámaras, para que el sensor de espectro visible sea capaz de obtener una imagen lo suficientemente nítida y clara como para que la red neuronal que estamos empleando pueda realizar detecciones de la manera más eficiente posible. En definitiva, la cantidad de iluminación del ambiente, ya sea excesiva o insuficiente, influye sobre la capacidad de detección en el caso de emplear una red neuronal de espectro visible, por lo que es necesario encontrar el punto óptimo para que el funcionamiento sea el correcto. Por lo tanto, y a modo de conclusión, el método 2 podría servir de reemplazo al método 1, siempre y cuando conozcamos las condiciones del ambiente en el que trabajará nuestro sistema, y estas cumplan con los requisitos de iluminación desarrollados. Es decir, si este sistema finalmente se coloca en una recepción de alguna empresa, en la que siempre se tendrá buenas condiciones de luz ambiente durante el período laboral, y su función es únicamente medir la temperatura corporal de los trabajadores a su llegada, cumplirá con éxito su misión. Por otra parte, conociendo las características del sensor de espectro visible que estamos usando (Pi Cam NOIR v2.1, sensible a la radiación infrarroja de onda corta, SWIR), podríamos colocar iluminación mediante LEDs infrarrojos (que emitan en la banda del espectro infrarrojo de onda corta) en el lugar en el que se utilizará el sistema. Esto ayudará a que el sensor pueda obtener información del entorno en la oscuridad, gracias a la ausencia del filtro de radiación infrarroja, y así poder detectar rostros, calcular el Bounding-Box correspondiente y funcionar como lo hace en condiciones de buena luminosidad. La inclusión de LEDs infrarrojos no influye en la medida de la temperatura corporal, ya que esta medida la obtenemos con el sensor infrarrojos de FLIR, el cuál es sensible a la radiación infrarroja de onda larga (LWIR) pero no a la de onda corta (SWIR). Esta solución es una propuesta que no ha sido probada, dado que no se conoce el funcionamiento de la red neuronal en estas condiciones de iluminación infrarroja, su respuesta puede que no sea óptima.
33 Estimación de la temperatura en la región de interés Como se dijo al inicio de este trabajo, el objetivo es llegar a calcular la temperatura corporal de la persona que aparece en la imagen, y de la cual se ha obtenido la región de interés gracias al uso de una red neuronal, es decir, la cara de la persona se separa del resto de la imagen en una imagen más pequeña (ROI). Este cálculo de temperatura es posible gracias a la termografía. Como se puede leer en el libro “Termografía, guía de bolsillo” [42], cualquier objeto cuya temperatura sea mayor a 0 Kelvin (-273ºC) emite radiación infrarroja. El físico Max Planck demostró que existe una relación entre la intensidad de la radiación infrarroja que emite un cuerpo y su temperatura. Basándose en esto, se define la termografía como la medición de la temperatura superficial de un objeto con una cámara termográfica, sin necesidad de contacto. Para calcular la temperatura corporal de una persona adulta existe la posibilidad de hacerlo en diferentes partes del cuerpo, obteniendo una medida más o menos precisa dependiendo del lugar en que se realice dicha medición. Como nos informa la enciclopedia médica “MedlinePlus” [66], y la revista de salud y bienestar “Webconsultas” [67], la temperatura corporal puede ser medida en lugares como la boca, recto, axila, oído, o frente, entre otros. Cada uno de los lugares nos ofrece mejor o peor precisión en el cálculo, siendo la axila el lugar más usado por la mayoría de personas, debido a que se trata de un lugar de fácil alcance y a que los termómetros que se emplean en esta zona suelen ser económicos. Aun así, la axila es la zona en la que se requiere un mayor tiempo a la hora de tomar la medida, y puede ser de 0.3ºC a 0.6ºC más baja que la temperatura tomada en la zona de la boca, y a su vez, la temperatura tomada en la boca puede ser alterada debido a la saliva, que no es buen conductor del calor. Al igual que existen diferentes partes del cuerpo en las que se puede realizar la medición, también existen distintos tipos de termómetros. Tenemos los termómetros de mercurio (retirados del mercado en 2009 por el riesgo de toxicidad que suponen) y de galistán (mezcla de galio, indio y estaño), termómetros de oído (tecnología de infrarrojos), digitales (más usado por su precio reducido), de pistola (tecnología de infrarrojos), de chupete para bebés, e incluso se puede medir la temperatura usando un dispositivo móvil (resulta poco fiable) [67]. Nosotros en este trabajo nos vamos a centrar en el termómetro de pistola, el cual ha incrementado su uso en este último año a causa de la pandemia causada por el virus SARS-CoV-2. Para obtener una medida correcta de la temperatura usando este tipo de termómetros debemos colocar el plano de la lente del termómetro lo más paralelo posible a la frente de la persona. De esta forma podemos garantizar la perpendicularidad de la radiación incidente, como se muestra en la Figura 5-1 [68]. Así obtenemos de manera rápida y precisa el valor de la temperatura corporal de la persona. En este caso, el plano del objeto a medir sería el plano de la frente de la persona que se somete al análisis.
34 4.3 VENTAJAS E INCONVENIENTES DE AMBOS MÉTODOS. Figura 5-1: Esquema de uso de termómetro de pistola. De forma muy parecida, en nuestro caso obtendremos la temperatura corporal con el uso del sensor infrarrojos implementado en el sistema de cámaras. La idea, de manera general será: 1) Obtener ROI de la imagen donde aparezca rostro. 2) Aplicar el método de Otsu [69] a la ROI para quedarnos solamente con la cara (eliminando el pelo de la persona y el resto de la imagen). 3) Calcular el valor medio de temperatura de la cara a partir de los valores de los píxeles que la forman y de unos parámetros previamente definidos. El primer paso en el que obtenemos la región de interés de la imagen en la que aparece rostro ya lo hacíamos en los apartados Método 1: Creación y entrenamiento de un detector Haar Cascade y Método 2: Mapeo entre los píxeles de la cámara visible y los de la infrarroja., donde, aplicando los distintos métodos propuestos de detección de rostros obtenemos el Bounding-Box que separa la región de rostro del resto de la imagen. A pesar de ser el método empleado en prácticamente la totalidad de pruebas rápidas que se realizan para detectar fiebre causada por la enfermedad COVID-19, la técnica de medición de la temperatura corporal en la zona de la frente tiene varias limitaciones y podría llegar a ser, en algunos casos, no representativa de la verdadera temperatura corporal. Un ejemplo sencillo de esto es el que comenta el profesor y director del Centro de Investigación en Ciencias del Movimiento Humano (Universidad de Costa Rica), Luis Fernando Aragón-Vargas, en la revista “Pensar en Movimiento” [68]: “Así, una persona que se ejercita en el calor puede tener una temperatura central alta de 38°C o 39°C, mientras que la temperatura de su piel está mucho más baja, por la evaporación del sudor. Otra persona en reposo al aire libre en un día soleado se verá expuesta al calor por radiación, con lo cual su temperatura cutánea se elevará antes de que la temperatura central comience a subir. Por lo tanto, usar la temperatura de la frente para decidir si una persona tiene fiebre o no podría no ser un método suficientemente riguroso.”
CAPÍTULO 5 ESTIMACIÓN DE LA TEMPERATURA EN LA REGIÓN DE INTERÉS 35 5.1. Procesado de la región de interés Una vez se haya obtenido la región de interés de la imagen en la que se detecta rostro, debemos analizar y procesar esa ROI. La ROI será en nuestro código un objeto de la clase “Rect()” de OpenCV [61]. Como podemos ver en la documentación correspondiente a la clase, para crear este objeto que contendrá las coordenadas del Bounding-Box que encuadra la cara, necesitamos aportarle dichas coordenadas como argumentos. En la línea 369 se muestra la línea de código en la que se crea el objeto que será la región a analizar para el cálculo de la temperatura corporal. Los argumentos de la función son: Rect(Esquina superior izquierda X, Esquina superior izquierda Y, Ancho, Alto). En nuestro caso, “x1”, “y1”, “x2” e “y2” son las coordenadas calculadas de la forma que se explica en el apartado 4.2.2 Bounding-Box en la imagen de infrarrojos. Este objeto de nombre ROI será el que, a su vez, se le pasará como argumento a la función que se encargue de calcular la temperatura de la persona. La precisión de la medida de la temperatura corporal realizada únicamente en la región de la frente es baja. En Silawan et al. [70] se realizan diversas pruebas con distintas regiones de interés, comparando los resultados con el termómetro de oído Braun ThermoScan PRO 4000. En la Figura 5-2 se muestran los resultados obtenidos usando la ROI de la frente (D), de la frente junto con la zona de los cachetes (DC), y la formada por la frente, cachetes y boca la ROI (DDC). Esta última es la opción que presenta una mayor precisión. Basándonos en estos resultados, en este proyecto tomaremos como región de interés todo el rostro de la persona. Al fin y al cabo, utilizar todo el rostro de la persona se puede considerar una aproximación del método descrito, en el que se utiliza la frente, cachetes y boca. Figura 5-2: Resultados de la estimación de la temperatura usando distintas ROI, y tabla de resultados de sensibilidad y especificidad de cada método. El primer paso en el proceso de medición de la temperatura corporal en nuestro sistema consistirá en un filtrado de la ROI para eliminar ruido y la eliminación de zonas en la ROI candidatas a distorsionar la medida. El filtrado de ruido se realiza empleando un filtro Gaussiano. A continuación, se implementará el método de Otsu [69] con objetivo de eliminar regiones de la imagen donde el valor de sus píxeles sean muy inferior al resto de píxeles que la rodean y puedan alterar el valor de la medida. Un ejemplo de este tipo de regiones son los cristales de unas gafas de sol. Los cristales son opacos a la radiación infrarroja, ya que absorben toda la radiación de este rango y resultarían en zonas de píxeles completamente oscuros. Cualquier objeto que se encuentre detrás del cristal, aunque tenga una temperatura elevada, no se verá en una imagen termográfica [71]. Filtrado Gaussiano de la imagen
36 5.1 PROCESADO DE LA REGIÓN DE INTERÉS El primer paso será filtrar la imagen aplicando un filtro de Gauss. Con esto conseguiremos eliminar de la imagen el ruido Gaussiano que pudiera aparecer y que afecta al valor de los píxeles. Este tipo de ruido es el que se puede producir en circuitos electrónicos debido al ruido de los sensores, falta de iluminación o altas temperaturas [72]. La función Gaussiana es la indicada en la ecuación 5-1: 𝐺(𝑥,𝑦)=1 2𝜋𝜎2𝑒−𝑥2+𝑦2 2𝜎2 (5-1) En la ecuación 5-1, "𝜎" se corresponde con la desviación estándar y de su valor dependerá el grado de suavizado de la imagen. A mayor valor de "𝜎", mayor cantidad de ruido conseguimos eliminar, pero también se difuminará más la imagen. Los valores de “x” e “y” son las coordenadas del pixel dentro de la máscara Gaussiana. En nuestro caso se utilizará una máscara Gaussiana de 3x3 píxeles, en la que se da mayor ponderación a los píxeles más cercanos al píxel central. La Figura 5-3 muestra la máscara Gaussiana empleada, en la que se aprecia la mayor importancia que se le da a los píxeles cercanos al pixel central. Figura 5-3: Kernel Gaussiano 3x3. Para obtener el valor del pixel suavizado se realiza una operación de convolución de la imagen original y la máscara que se esté usando [73]. La ecuación 5-2 representa la operación de convolución. 𝑔(𝑖,𝑗)=∑∑𝑤(𝑠,𝑡) · 𝑓 (𝑖+ 𝑠,𝑗 + 𝑡) 𝑏 −𝑏 𝑎 −𝑎 (5-2) Donde: • “a” y “b” se refieren al radio (número de columnas que definen la mitad del ancho de la matriz) de la máscara Gaussiana empleada. Una máscara de 3x3 como la que se usará para el filtrado en nuestro caso tendrá radio 1, mientras que una máscara 5x5 tendrá un radio de 2 píxeles (número de columnas o filas que rodean al pixel central). El pixel central de la máscara corresponde a los valores de a=0 y b=0 [73]. • w(s,t) es la máscara Gaussiana. • f(i,j) es el pixel correspondiente en la imagen original. • g(i,j) será el valor del pixel resultado de la convolución. Para eliminar el ruido en la imagen disponemos de varios tipos de filtros, como pueden ser: filtro de la media, filtro de la mediana, o filtro Gaussiano, entre otros muchos. El filtro de la mediana es muy útil para eliminar el ruido de tipo sal y pimienta (puntos de nivel de intensidad muy diferentes a los valores de los puntos de alrededor), que puede producirse por un mal funcionamiento de los sensores de cámara o errores durante la digitalización de la imagen, pero en nuestro caso, este tipo de ruido es prácticamente nulo (ver Figura 4-13). Trataremos entonces de suavizar la imagen reduciendo el ruido Gaussiano, que simula el ruido generado por dispositivos electrónicos durante la adquisición de imágenes [74]. Para reducir el ruido Gaussiano podremos utilizar los otros dos tipos de filtros nombrados, el filtro de la media y el filtro Gaussiano.
CAPÍTULO 5 ESTIMACIÓN DE LA TEMPERATURA EN LA REGIÓN DE INTERÉS 37 Usaremos el filtro Gaussiano en lugar del filtro de la media porque a pesar de que ambos tienen un comportamiento similar (disminución de la nitidez, aumento de borrosidad y pérdida de detalles), el filtro Gaussiano produce un suavizado más uniforme que el filtro de la media [72]. Podemos ver el resultado del filtrado de la imagen en la Figura 5-4. Figura 5-4: ROI sin aplicar filtro de Gauss (izquierda); ROI aplicando filtro de Gauss (derecha). Todo este procedimiento descrito está programado en lenguaje C++ en el Temperature.cpp. Concretamente la función “GaussianBlur”, en la línea 43, es la encargada de realizar el filtrado de la imagen. Podemos ver la información correspondiente a esta función y su funcionamiento en la web de OpenCV [75]. Como primer argumento recibe la imagen a la que se le aplicará el filtrado, que en este caso será la ROI (variable ImgROI); el segundo argumento hace referencia a la variable en la que guardaremos el resultado de imagen filtrada (ImgROIBlur) y como tercer argumento indicaremos el tamaño de la máscara empleada en el suavizado (tamaño 3x3). Una vez tenemos la imagen filtrada y sin ruido podemos pasar a la segunda etapa del procesado de la imagen previa al cálculo del valor de temperatura corporal. Este segundo paso consistirá en segmentar la imagen aplicando el método de Otsu. Segmentación de la imagen aplicando el método de Otsu. El método de Otsu es un método que nos ayuda a segmentar imágenes. Se suele utilizar este método para separar objetos del fondo de la imagen, es decir, calcula de forma automática el umbral (o valor de nivel de gris) en una imagen en escala de grises óptimo para separar los objetos del fondo de la imagen. Dicho de otra forma, el umbral que produzca la mejor separación entre clases, o que maximice la varianza entre dichas clases, ese será el umbral óptimo para la segmentación [69], entendiéndose por clases el conjunto de píxeles de valores cercanos entre ellos. El método de Otsu debe utilizarse con imágenes bimodales para obtener segmentaciones más precisas. Una imagen es bimodal cuando en su histograma podemos ver dos picos dominantes [76]. Vamos a analizar el histograma de una de las imágenes captadas por el sensor a la que ya se le haya aplicado el filtro Gaussiano, y aplicando Otsu veremos qué valor umbral es el óptimo para la segmentación de la imagen. Para el cálculo del histograma utilizaremos MATLAB.
44 Figura 6-2: Vista explosionada de plataforma de cámaras y Raspberry Pi 3 B. Figura 6-3: Vista de pieza real de plataforma de cámaras y Raspberry Pi 3 B. Para la plataforma que soportará el sistema de cámaras junto con los motores se ha diseñado el conjunto de piezas mostrado en la Figura 6-4 y la Figura 6-5. El objetivo es, como se muestra en la Figura 6-6, es crear una plataforma sobre la que colocar el sistema de cámaras de la Figura 6-3 permitiendo modificar su pitch y su yaw. Estos ángulos representan las rotaciones en los ejes Y, Z, respectivamente. Para dotar de movimiento a la estructura se usarán dos servomotores.
CAPÍTULO 6 DISEÑO Y CONTROL DE LA PLATAFORMA MÓVIL 45 Figura 6-4: Soporte base y anclaje del motor 1. Figura 6-5: Anclaje del motor 2 (izquierda) y soporte del sistema de cámaras y Raspbery Pi 3B (derecha). Podemos ver en la Figura 6-5 (izquierda) una serie de orificios alineados. Estos orificios serán el lugar de anclaje de esta pieza con el motor del eje de yaw. Todos estos orificios alinados y separados entre ellos una distancia de 5 milímetros nos servirá a la hora del montaje final, donde tendremos que alinear la estructura superior e inferior de la plataforma para hacer que el centro de masas de la plataforma coincida con el centro geométrico de la misma. Con estos orificios tendremos la opción de colocar la plataforma en varias posiciones, para ajustar ese balance de pesos. A esto se le denomina “Balancear la estructura”. En la Figura 6-6 se puede ver dónde deberían coincidir el centro de gravedad y el centro geométrico de la carga a colocar, que en este caso será la pieza que contiene la Raspberry
46 Pi 3 B junto con las cámaras (Figura 6-1): Figura 6-6: Diseño inicial de la estructura en la que se han marcado los diferentes ejes de relevancia (Pitch y Yaw) y los momentos que se van a crear (MPitch y MYaw). En esta imagen (Figura 6-6), la flecha marcada en color naranja se corresponde con la línea de orificios de la pieza. Para centrar correctamente la carga en la plataforma se debe hacer coincidir, de manera lo más precisa posible, el centro del anclaje de la carga con el punto de corte de los ejes Pitch y Yaw. Para ellose ve que debería desplazarse la pieza soporte del motor superior sobre el raíl naranja hasta que el centro del anclaje de la carga (carga situada en la pieza soporte superior, Figura 6-5 derecha) quede lo más cerca posible del corte entre ambos ejes. Algo que se ha aplicado a la estructura que se ha diseñado es la sencilla idea, o forma de comprender el comportamiento físico de una estructura, que nos muestra el profesor ‘Miguel de Vicente López’ en su breve presentación “Estabilidad y Centro de Gravedad” [82]. Se hace hincapié en las distintas formas de aumentar la estabilidad de una plataforma para que esta no vuelque como por ejemplo: aumentar el peso de la base para bajar el centro de gravedad; haciendo la base más grande; o anclando el objeto al suelo. En nuestro caso, se han llevado a cabo las dos últimas ideas, proporcionando a la base una especie de “patas” o “brazos” a cada una de las caras de la pieza, que consiguen aumentar de tamaño esta base, y además, se pueden anclar a través de los orificios en los extremos de cada una de estas “patas”, quedando la estructura completamente fija y estable.
CAPÍTULO 6 DISEÑO Y CONTROL DE LA PLATAFORMA MÓVIL 47 Figura 6-7: Montaje de la plataforma incluyendo modelos de los motores. Figura 6-8: Montaje de la plataforma incluyendo modelo de los motores y sistema de cámaras.
48 Figura 6-9: Vista del resultado final del montaje completo. 6.2. Control de la plataforma. Para controlar la posición de la plataforma móvil, es decir, los valores de Pitch y Yaw, aplicaremos un control PI a cada uno de los servomotores. El servomotor empleado es el Parallax Standard Servo, mostrado en la Figura 6-10 [83]. El sistema deberá alinearse con la ROI, formada por el rostro de la persona detectada, de manera que esta quede en el centro de la imagen. Para ello, tomaremos como referencia el pixel central de la imagen. Figura 6-10: Parallax Standard Servo. El objetivo es reducir al mínimo posible la diferencia entre ambos puntos centrales: el punto central de la imagen (referencia) y el punto central de la ROI (punto que debe seguir la referencia). Para realizar este control se realiza una implementación software de un controlador PID (ver Anexo C), del cual sólo se usará la acción proporcional e integral, es decir, el control será de tipo PI. Este tipo de control ha sido suficiente para alcanzar las características dinámicas y de estabilización requeridas por la aplicación.
CAPÍTULO 6 DISEÑO Y CONTROL DE LA PLATAFORMA MÓVIL 49 Control PI En el Anexo C podemos ver los dos ficheros (PID.h y PID.cpp) creados para implementar la clase PID encargada de controlar los servomotores de Pitch y de Yaw. En PID.h se definen los distintos métodos y atributos de la clase, y su constructor. En PID.cpp se inicializan dichos métodos. Mediante el método Reference indicamos el valor de la referencia a seguir y el que debe tomar como último valor de la señal de control (LastOutput). El primer valor de LastOutput será aquel con el que el sistema llegó a la posición inicial. Posición en la que se establece el sistema al iniciar su funcionamiento. El método Update nos servirá para definir el comportamiento del controlador. Lo primero que haremos será comprobar si se ha sobrepasado el rango de error que consideraremos como válido. Este rango de error admisible es necesario debido a que la precisión de los servomotores no es muy elevada y cada mínimo cambio de posición del motor supone una gran variación de píxeles (dependiendo también de la distancia a la que se encuentre la ROI del sensor). En este caso, tras un ajuste fino del valor, se ha considerado un offset (error en píxeles admitido) de 50 píxeles. Entonces el rango de error viene definido por [-offset, +offset]. De esta forma, sólo se calculará una nueva señal de control cuando el error cometido tenga un valor absoluto de 50 (ver línea 44). Mientras tengamos un error menor, no se ejecutará una acción de control y la plataforma se mantendrá en la misma posición. Al valor de la señal de control calculado, le aplicaremos un filtro (ver línea 49), con el que conseguiremos eliminar ruido de alta frecuencia, evitando cambios bruscos en la posición del motor. Aunque esto perjudica a la dinámica del sistema, evita situaciones en las que la ROI sale del plano de la imagen y se pierde su seguimiento. El filtro aplicado es el filtro exponencial EMA [84] (Exponential Moving Average), muy usado en electrónica digital por sus buenos resultados e implementación sencilla mediante la ecuación 6-1. 𝐴𝑛= 𝛼𝑀+ (1 − 𝛼)𝐴𝑛−1 (6-1) Donde 𝐴𝑛 es el valor filtrado, 𝐴𝑛−1 el valor filtrado anterior, 𝑀 es el valor de la señal sin filtrar y 𝛼 es un coeficiente entre 0 y 1. Del valor de este coeficiente dependerá el comportamiento del filtro exponencial. Dando como resultado, para 𝛼 igual a 0, un valor nulo a la salida del filtro, y para 𝛼 igual a 1, obtendremos la señal sin filtrar. El valor de este coeficiente dependerá de las características del sistema, siendo lo más habitual, valores entre 0.2 y 0.6. En nuestro caso, tras un proceso empírico, se ha obtenido un comportamiento adecuado con un valor de 0.4. Una vez calculada la señal de control a aplicar a cada servo (llamada EMA_LP en el código) comprobaremos ahora que se encuentra entre los límites en los que puede trabajar dicho servomotor (ver líneas 52 y 58). Para el servomotor de Yaw se tiene disponible todo el rango de operación que este es capaz de dar, es decir, un giro de 180º, siendo el valor de 0º el correspondiente a enviar un pulso de 0.4ms, y el valor de 180º correspondiente a un pulso de 2.2ms (valores obtenidos mediante ensayo del servomotor), configurado para un intervalo de 20ms entre cada pulso. El servo de Pitch, siendo el mismo modelo, se ha limitado más el rango debido a restricciones mecánicas y a que no se necesita tanto rango de movimiento, ya que nunca necesitaremos que la plataforma apunte, por ejemplo, directamente al suelo , a 0º. Por esto el rango operativo para el servo de Pitch comprende pulsos que van de 1.3 a 2.0ms. Si se sobrepasa algunos de estos umbrales, el valor de la señal de control pasará a ser el valor límite que se ha superado. En un controlador PI, la acción integradora decimos que tiene memoria, por el hecho de guardar datos de error anteriores. Esto provoca que la señal integradora se sobrecargue cuando se sobrepasan los límites del actuador (límites de los servomotores). Cuando esto ocurre, se debe esperar a que se descargue la acción integral antes de que el control pueda actuar. Para evitar esta sobrecarga, y que la señal de control continue creciendo, aplicaremos una estrategia Anti-Windup. De esta forma, la señal
50 integradora no saturará y el control podrá actuar de forma inmediata cuando desaparece la saturación. La técnica seguida consiste en dejar de actualizar la acción integral con valores de error pasados cuando se satura el sistema (ver desde línea 52 hasta línea 66). En la secuencia de control, primero se calcula el valor de salida del controlador PI de Yaw y luego el de Pitch (líneas 210 y 211), y se aplican a los servos en el mismo orden (líneas 223 y 238). Para que no se produzcan fallos de temporización, es decir, que la señal de control se aplique a los servomotores antes de volver a procesar un nuevo frame, se deben introducir las esperas y retardos adecuados. Aquí nos podemos encontrar con el caso en el que se detecta rostro, y aquel en el que no se encuentre rostro, en el que el tiempo de procesamiento será menor. Midiendo los tiempos de procesamiento para ambos casos se ha comprobado que cuando se detecta rostro, el peor de los tiempos obtenidos suele estar en torno a los 120ms, y cuando no se detecta rostro, el tiempo disminuye hasta los 80ms. En el caso en el que se detecten muchos rostros en una misma imagen (más de 5),el tiempo de procesamiento puede llegar hasta los 150ms, aunque para nuestra aplicación, este caso no será habitual. Para cubrir todas las posibilidades, introducimos una espera que sea suficiente para el peor de los casos (cuando se produce detección de rostro). Definiremos esta espera como el tiempo de muestreo del control PI de los servomotores (definido a 200ms) menos el tiempo de procesamiento de la imagen. Por lo que realmente la espera será de entre 80ms como mínimo y 120ms como máximo. Cabe destacar que cuando se dé el caso en el que se detecte más de un rostro en una misma imagen (línea 346), el sistema se quedará en la posición en la que permanecía anteriormente, es decir, no se realiza una selección de rostro a la que seguir, ya que para la aplicación que se ha pensado (medición de temperatura en alguna recepción) no sería del todo necesario. Además, de esta forma podremos evitar que el sistema se dirija a una posición errónea cuando se detecte un falso positivo a la vez que se está analizando un rostro verdadero. Una vez explicado el funcionamiento, veamos el diagrama de bloques del control implementado. En la Figura 6-11 aparece el diagrama general del control, en el que se puede ver un primer bloque, “Frame k-1”, que se corresponde con el frame anterior al actual. El segundo bloque, “Face Detection”, hace referencia al procesamiento del frame actual en busca de rostros. En la primera iteración, “centerX[K-1] y centerY[K-1]” es el punto central de la imagen, mientras que “centerX[K]” y “centerY[K]” se corresponden con el centro de la ROI detectada. La diferencia entre estas salidas será el error de posición cometido, que lo recibe el tercer bloque como entrada, “PI controller”, y se obtiene como resultado una señal de posición para el servomotor (pulsos en milisegundos), “salidaX y salidaY”. El cuarto bloque es el controlador interno del servomotor, el cuál recibe las órdenes de posición del servo y aplica al motor DC de su interior el voltaje necesario para llevarlo a dicha posición. Al enviar las órdenes de movimiento al motor, debemos tener en cuenta el tiempo que le puede llevar al servo ir de una posición a otra, por lo que se añade el bloque “Delay” en el que se realiza dicha espera para esperar el posicionamiento de la plataforma. Una vez estabilizada la plataforma en una nueva posición, se analiza el siguiente frame y se repite el ciclo. Figura 6-11: Diagrama de bloques general.
CAPÍTULO 6 DISEÑO Y CONTROL DE LA PLATAFORMA MÓVIL 51 En la Figura 6-12 podemos ver el diagrama interno del bloque “PI controller” de la Figura 4-11. Este es el diagrama correspondiente al controlador PI conocido por todos. Para ajustar los parámetros 𝐾𝑝 𝑦 𝐾𝐼 se han seguido los siguientes pasos: • Para el servomotor de Yaw, y teniendo bloqueado el de Pitch, aumentar lenta progresivamente desde cero el valor de 𝐾𝑝 hasta que notemos que la plataforma sigue el rostro sin pasarse, es decir, sin que se produzca sobreoscilación. • Una vez encontrado el valor correcto de 𝐾𝑝, hacer lo mismo con la constante 𝐾𝐼. Esto ajustará un poco mejor la posición del servo, pero debemos tener en cuenta que se produzcan las menores oscilaciones posibles. Hacer lo mismo para el servomotor de Pitch, dejando bloqueado ahora el de Yaw para que sea más sencillo. Figura 6-12: Diagrama de bloques del control PI.
53 Conclusiones y líneas de trabajo futuras Durante la realización de este trabajo se han llevado a cabo diferentes tareas para cumplir el objetivo de construir un sistema de seguimiento de personas y medición de su temperatura corporal en tiempo real. Se han realizado tareas de programación, tareas de diseño hardware para la implementación de dos sensores de imagen en una Raspberry Pi, se han realizado tareas de diseño mecánico y fabricación aditiva (haciendo uso de las impresoras 3D del IMSE) de las distintas piezas que forman la plataforma y, finalmente, se han integrado los servomotores que otorgan de movilidad al sistema. Las tareas de programación de alto nivel en lenguaje C++ han consistido en llevar a cabo las tareas de reconocimiento de rostros y control de la plataforma móvil. En el caso de la detección de rostros, se ha usado el código propuesto en la web de OpenCV como modelo a seguir. Respecto al control de la plataforma, ha sido necesario añadir las instrucciones relacionadas con la generación de señales PWM para el uso de los servomotores; la implementación de una clase PID, y la integración del método de cálculo de puntos singulares (coordenadas del Bounding-Box) en la imagen IR a partir de puntos en la imagen de espectro visible (visto en el apartado 4.2). También en alto nivel, en lenguaje Python, utilizando como guía el código de Gabriela Solano se ha podido crear una base de datos con la que entrenar nuestra propia red neuronal (apartado 4.1) en el infrarrojo, aunque finalmente no ha sido el método elegido para la detección de rostros. Se ha estudiado la forma más eficiente de crear una plataforma para esta aplicación, desarrollando distintos diseños 3D en Catia V5. Finalmente se ha optado por un diseño simple con piezas pequeñas en las que se han evitado los ángulos rectos para evitar crear puntos débiles y que además no implicasen un aumento de peso considerable al sistema, lo que provocaría un funcionamiento inadecuado de los servomotores (ver apartado 6.1). Como resultado tenemos una plataforma capaz de realizar un seguimiento adecuado de objetos (rostros en este caso) a velocidad moderada. La distancia a la que es capaz de detectar estos rostros dependerá mucho de la exhaustividad en el entrenamiento de la red neuronal empleada. En nuestro caso, la red de OpenCV muestra un rendimiento aceptable a distancias de entre 1,5 a 2 metros desde el objetivo a detectar. Es necesario ajustar bien los parámetros de la función “detectMultiScale”, con la que realizamos la detección de rostros en la imagen, para que no se produzcan falsas detecciones. Durante el desarrollo de este trabajo han surgido diferentes ideas que podrían servir como futuras líneas de trabajo: • Sustitución del método de detección de rostros. En este trabajo se ha utilizado el método de mapeo entre los píxeles de las imágenes infrarroja y visual (descrito en el apartado 4.2) usando una red neuronal para el especto visible. Se propone utilizar el método descrito en el apartado 4.1, en el que se debe crear una base de datos de imágenes en el espectro infrarrojo para luego entrenar una red neuronal. Así conseguiríamos una red neuronal específica para su uso en el espectro infrarrojo. Para ello necesitaríamos repetir el proceso del apartado 4.1 pero creando una base de datos mucho más extensa y con más personas que participasen en la toma de imágenes, para tener así un mayor número de casos (rostros de distintas características).
BIBLIOGRAFÍA 60 [71] S. F. Larsen y M. Hongn, «Termografía infrarroja en la edificación: aplicaciones cualitativas,» Avances En Energias Renovables y Medio Ambiente, vol. 16, 2012. [72] Universidad de Sevilla, «Tema 2: Procesamiento en el dominio espacial (parte 2),» pp. 1-23. [73] P. Turmero, «monografias.com,» 1 Abril 2016. [En línea]. Available: https://www.monografias.com/trabajos108/filtros-y-convoluciones/filtros-y- convoluciones.shtml. [Último acceso: 20 Mayo 2021]. [74] U. d. Córdoba, «uco.es,» [En línea]. Available: http://www.uco.es/users/ma1fegan/2015- 2016/vision/Temas/ruido.pdf. [Último acceso: 28 Julio 2021]. [75] OpenCV (GaussianBlur), «docs.opencv.org,» [En línea]. Available: https://docs.opencv.org/master/d4/d13/tutorial_py_filtering.html. [Último acceso: 14 Junio 2021]. [76] S. M. Valencia, Medidor de área foliar utilizando una cámara digital, Pereira, Colombia, 2019. [77] OpenCV (threshold), «docs.opencv.org,» [En línea]. Available: https://docs.opencv.org/4.5.0/d7/d4d/tutorial_py_thresholding.html. [Último acceso: 14 Junio 2021]. [78] J. A. Leñero-Bardallo, R. D. l. Rosa-Vidal, R. Padial-Allué, J. Ceballos-Cáceres, Á. Rodríguez-Vázquez y J. Bernabéu-Wittel, «A Customizable Thermographic Imaging System for Medical Image Acquisition and Processing,» EEE Sensors Journal, 2021. [79] C.-S. a. L. C.-S. Fahn, «A high-definition human face tracking system using the fusion of omni-directional and PTZ cameras mounted on a mobile robot,» de 2010 5th IEEE Conference on Industrial Electronics and Applications, Taichung,Taiwan, IEEE, 2010, pp. 6-11. [80] M. T. Kristjan Tiimus, Camera gimbal control system for unmanned platforms, Pärnu, Estonia, 2010. [81] R. J. Rajesh y P. Kavitha, Camera gimbal stabilization using conventional PID controller and evolutionary algorithms, Indore, India: IEEE, 2015. [82] M. d. V. López, «es.slideshare.net,» 22 Enero 2015. [En línea]. Available: https://es.slideshare.net/tecnoparador/estabilidad-y-centro-de-gravedad. [Último acceso: 2021 Abril 27]. [83] P. INC, «docs.rs-online.com,» 24 Octubre 2011. [En línea]. Available: https://docs.rsonline.com/0e85/0900766b8123f8d7.pdf. [Último acceso: 23 Junio 2021]. [84] C. Rosero y J. Enrique, Estudio de un sistema de posicionamiento para interiores, Universitat Politècnica de València, 2018. [85] Dejan, «howtomecatronics.com,» 9 abril 2019. [En línea]. Available: https://howtomechatronics.com/projects/diy-arduino-gimbal-self-stabilizing-platform/. [Último acceso: 23 abril 2021]. [86] C. Y. O. C. H. C. DETECCIÓN DE ROSTROS, «unipython.com,» 5 Abril 2018. [En línea].
61 Available: https://unipython.com/deteccion-rostros-caras-ojos-haar-cascad/. [Último acceso: 27 4 2021]. [87] M. Ángel, «mbrobotics.es,» 2 Mayo 2019. [En línea]. Available: http://mbrobotics.es/blog/raspberry-pi-gpios/. [Último acceso: 2021 Abril 29]. [88] N. Castejón, «webconsultas.com,» 17 Abril 2020. [En línea]. Available: https://www.webconsultas.com/belleza-y-bienestar/habitos-saludables/donde-colocar-el- termometro-para-medir-la-temperatura. [Último acceso: 8 Mayo 2021]. [89] Gesa, «termometros.com,» [En línea]. Available: https://www.termometros.com/Comofunciona-un-termometro. [Último acceso: 8 Mayo 2021]. [91] OpenCV (bitwise_and), «docs.opencv.org,» [En línea]. Available: https://docs.opencv.org/master/d2/de8/group__core__array.html#ga60b4d04b251ba5eb1392 c34425497e14. [Último acceso: 14 Junio 2021]. [92] Benewake, «sparkfun.com,» [En línea]. Available: https://cdn.sparkfun.com/assets/1/4/2/1/9/TFmini_Plus_A02_Product_Manual_EN.pdf. [Último acceso: 28 Julio 2021]. [93] «andorra.desertcart.com,» [En línea]. Available: https://andorra.desertcart.com/products/100007042-fityle-2208-90-kv-3-mm-shaft-gimbal- brushless-motor-for-100-200-g-gopro-3-rc-quadcopter. [Último acceso: 29 Julio 2021]. [94] «tutorialspoint.com,» [En línea]. Available: https://www.tutorialspoint.com/arduino/arduino_ultrasonic_sensor.htm. [Último acceso: 25 Agosto 2021]. [95] «datasheet.es,» [En línea]. Available: http://www.datasheet.es/PDF/779948/HC-SR04- pdf.html. [Último acceso: 25 Agosto 2021]. [96] «basecamelectronics.com,» [En línea]. Available: https://www.basecamelectronics.com/simplebgc32bit/. [Último acceso: 29 Agosto 2021].
63 Anexos
ANEXOS 64
65 Anexo A IRFacesDetector.py El fichero “IRFacesDetector.py”, que se encuentra en la carpeta que creamos exclusivamente para el entrenamiento de la red neuronal (“IRFacesCascade”) se usará para la creación de una base de datos formada por imágenes en el espectro de infrarrojos y su funcionamiento es el siguiente: Primero, se crean ambas carpetas “n” y “p” e iniciamos la grabación de imágenes con el sensor infrarrojos (tiene índice 1 en nuestro caso). Iniciada la grabación, se copia el frame y se dibuja aproximadamente en el centro de la imagen un rectángulo que delimitará una región de interés. Finalmente, cuando queramos guardar como imagen positiva o negativa la imagen formada por la ROI, presionamos la letra ‘p’ (si es imagen positiva) o ‘n’ (si es imagen negativa). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import cv2 import numpy as np import imutils import os Positivas = 'p' # Llamaremos p a la carpeta de imágenes positivas Negativas = 'n' # Llamaremos n a la carpeta de imágenes negativas if not os.path.exists(Positivas): # Si no existe, se crea carpeta p print('Carpeta creada: ',Positivas) os.makedirs(Positivas) if not os.path.exists(Negativas): # Si no existe, se crea carpeta n print('Carpeta creada: ',Negativas) os.makedirs(Negativas) cap = cv2.VideoCapture(1) # Capturamos imagen con cámara IR, índice 1 #Dimensiones de rectángulo que contendrá la ROI x1, y1 = 45, 20 x2, y2 = 110, 100 #contador de imágenes guardadas positivas y negativas countP = 0 countN = 0
ANEXOS 66 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 while True: ret, frame = cap.read() if ret == False: break imAux = frame.copy() # Copiamos el frame cv2.rectangle(frame,(x1,y1),(x2,y2),(255,0,0),2) # Dibujamos el rectángulo objeto = imAux[y1:y2, x1:x2] # Imagen delimitada por rectángulo objeto = imutils.resize(objeto, width=38) # Redimensionamos "objeto" para hacerlo más pequeño k = cv2.waitKey(1) if k == 27: break cv2.imshow('Frame',frame) # Muestra imagen cv2.imshow('Objeto',objeto) # Muestra ROI de la imagen # Si presionamos "p" guardaremos la imagen formada por objeto en la carpeta "p" if k == ord('p'): cv2.imwrite(Positivas+'/objeto_{}.jpg'.format(countP),objeto) print('Imagen almacenada:','objeto_{}.jpg'.format(countP)) countP = countP + 1 # Si presionamos "n" guardaremos la imagen formada por objeto en la carpeta "n" if k == ord('n'): cv2.imwrite(Negativas+'/objeto_{}.jpg'.format(countN),objeto) print('Imagen almacenada:','objeto_{}.jpg'.format(countN)) countN = countN + 1 cap.release() cv2.destroyAllWindows()
67 Anexo B Temperature.cpp El fichero “Temperature.cpp” implementa lo descrito en el apartado “¡Error! No se encuentra el o rigen de la referencia.. ¡Error! No se encuentra el origen de la referencia.”. Este fichero contiene las funciones necesarias para el procesado de la imagen y estimación de la temperatura corporal de la persona que aparece en imagen. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 /** * @file Temperature.cpp * @author Daniel Clavijo Calvo * @brief Segmentación por método de Otsu y estimación de temperatura * @version 0.1 * @date 2021-04-14 * * @copyright Copyright (c) 2021 * */ #include <opencv2/imgproc.hpp> // Librería para el procesado de imagen using namespace cv; double IRtoTempComversion(const double& IR); double ApplySegmentation(const Mat& Image, Mat& result); /** * @brief Función principal para la estimación de temperatura * * @param Image: frame en el que se detecta rostro * @param ROI: región de interés del frame * @param applySegmentation: booleano para aplicar, o no, el método de Otsu * @param otsuThreshold: valor de segmentación mediante Otsu * @return double Temperature: valor de temperatura de la región de interés */ double GetTempFromIRImage(const Mat& Image, const Rect& ROI, bool applySegmentation, double* otsuThreshold) { double IR = 0, temperature = 0, threshold; Mat ImgROI = Image(ROI);
ANEXOS 68 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 if(applySegmentation) { Mat result, ImgROIBlur, ImgSegmented; // Matriz que usaremos de máscara double numPixel = 0, pixelValue = 0; // Nº total, valor de píxeles GaussianBlur(ImgROI,ImgROIBlur,Size(3,3),0); // Filtro de Gauss threshold = ApplySegmentation(ImgROIBlur, ImgSegmented); // Aplica Otsu. //imshow("ROI",ImgROI); // ROI sin aplicar Otsu //imshow("filtrado",ImgROIBlur); // Imagen suavizada con Gauss //imshow("mascara",ImgSegmented); // Imagen segmentada IR = mean(final)[0]; //valor medio de los píxeles de la imagen } else { IR = mean(ImgROI)[0]; // Media de la imagen sin segmentar. } temperature = IRtoTempComversion(IR); // Pasa valor IR medio a Tª media. if(otsuThreshold != 0) { *otsuThreshold = threshold; // Devuelve el valor umbral utilizado por referencia. } return temperature; // Devuelve valor de Tª media. } /** * @brief Cálculo de temperatura de la región de interés * * @param IR: valor medio de todos los píxeles de la región de interés * @return double: valor de temperatura */ double IRtoTempComversion(const double& IR) { double T1 = 36.0, T2 = 36.04; // Parámetros de calibración de la Tª double R1 = 8250, R2 = 8400; double IRSensorTo8Bit = 16383.0/255; // Conversión de 14 a 8 bits double a, b; a = (T2-T1)/(R2-R1); b = T2 - a*R2; return IR*IRSensorTo8Bit*a + b; // Cálculo de Tª } /** * @brief Función que aplica Otsu a la Imagen * * @param Image: Imagen a la que se le applica Otsu formada por ROI * @param result:Resultado de la segmentación
69 ANEXOS 93 94 95 96 97 98 * @return double: valor de segmentación de Otsu */ double ApplySegmentation(const Mat& Image, Mat& result) { return threshold(Image,result,0, 0, THRESH_TOZERO | THRESH_OTSU); }
ANEXOS 76 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 */ static void help(const char** argv) { cout << "\nThis program demonstrates the use of cv::CascadeClassifier class to detect objects (Face + eyes). You can use Haar or LBP features.\n" "This classifier can recognize many kinds of rigid objects, once the appropriate classifier is trained.\n" "It's most known use is for faces.\n" "Usage:\n" << argv[0] << " [--cascade=<cascade_path> this is the primary trained classifier such as frontal face]\n" " [--nested-cascade[=nested_cascade_path this an optional secondary classifier such as eyes]]\n" " [--scale=<image scale greater or equal to 1, try 1.3 for example>]\n" " [--try-flip]\n" " [filename|camera_index]\n\n" "example:\n" << argv[0] << " --cascade=\"data/haarcascades/haarcascade_frontalface_alt.xml\" -- nested-cascade=\"data/haarcascades/haarcascade_eye_tree_eyeglasses.xml\" -- scale=1.3\n\n" "During execution:\n\tHit any key to quit.\n" "\tUsing OpenCV version " << CV_VERSION << "\n" << endl; } void detectAndDraw( Mat& img, CascadeClassifier& cascade,CascadeClassifier& nestedCascade,double scale, bool tryflip, int& centerX, int& centerY, int iSliderValue2,int iSliderValue1, Mat IRMat); double GetTempFromIRImage(const Mat& Image, const Rect& ROI, bool applySegmentation, double* otsuThreshold); string cascadeName; string nestedCascadeName; bool detect; // Indicará si se ha producido detección de rostro bool multidetect; // Indicará si se ha detectado más de un rostro int main( int argc, const char** argv ) { string inputName; bool tryflip; CascadeClassifier cascade, nestedCascade; PID PIDX(0.0025,0.00001,0,0.4,4,22,50); // Crea PID de Yaw PID PIDY(0.004,0.00001,0,0.4,13,20,50); // Crea PID de Pitch double scale; int CentralPointX = 246; // punto central de imagen en eje X escala 1.3 int CentralPointY = 123; // punto central de imagen en eje Y escala 1.3 int centerX = 246; // punto central de ROI (eje X) int centerY = 123; // punto central de ROI (eje Y) int pos_iniX = 13, pos_iniY = 17; // posiciones iniciales del motor double salidaPIDX, salidaPIDY; // valor de señal de control double new_salidaPIDX, new_salidaPIDY; // actualización de posición de servo double tini, tfin, t_proc; // medidas de tiempo double Tm=200; // tiempo de muestreo del control
77 ANEXOS 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 int reset=0; // contador de iteraciones sin detección de rostro PIDX.Reference(CentralPointX, pos_iniX); //Referencia de control, pos. inicial PIDY.Reference(CentralPointY, pos_iniY); wiringPiSetup(); // configurar GPIO como pines PWM pinMode(P_PIN12, PWM_OUTPUT); pinMode(P_PIN33, PWM_OUTPUT); digitalWrite(P_PIN12,0); // se inicia a 0 el valor de cada pin digitalWrite(P_PIN33,0); softPwmCreate(P_PIN12,0,200); //Crea señal PWM, inicializado a 0 y frec. 20ms softPwmCreate(P_PIN33,0,200); softPwmWrite(P_PIN12,13); // motor de eje X en posición central (90º) softPwmWrite(P_PIN33,17); delay(500); // tiempo de movimiento del motor softPwmWrite(P_PIN12,0); // valor de duty-cycle 0 para evitar vibraciones softPwmWrite(P_PIN33,0); Mat VMat, IRMat, image; // Matrices de imagen visual e infrarroja int X_OFFSET = 0; // Offset de puntos entre VMat e IRMat. int Y_OFFSET = 0; VideoCapture capture; // Iniciamos grabación visual VideoCapture IR(1); // Iniciamos grabación infrarrojos // Crea ventana y trackbars para modificar offset en tiempo real. /*namedWindow("Offset", 1); int iSliderValue1 = 50 + X_OFFSET; createTrackbar("Offset X", "Offset", &iSliderValue1, 100); // Offset en X. int iSliderValue2 = 50 + Y_OFFSET; createTrackbar("Offset Y", "Offset", &iSliderValue2, 100); // Offset en Y.*/ int iSliderValue1 = 40; // Valores finales de sliders. Factor de escala 1.3 int iSliderValue2 = 70; cv::CommandLineParser parser(argc, argv, "{help h||}" "{cascade|data/haarcascades/haarcascade_frontalface_alt.xml|}" "{nested-cascade|data/haarcascades/haarcascade_eye_tree_eyeglasses.xml|}" "{scale|1|}{try-flip||}{@filename||}" ); /*if (parser.has("help")) { help(argv); return 0; }*/ cascadeName = parser.get<string>("cascade"); // Lee “cascade” empleada nestedCascadeName = parser.get<string>("nested-cascade");//Lee “nestedcascade” scale = parser.get<double>("scale"); // Lee factor de escala if (scale < 1) scale = 1; inputName = parser.get<string>("@filename"); // Valor de puerto de cámara
ANEXOS 78 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 if (!parser.check()) // Comprueba errores que haya producido parser.get { parser.printErrors(); // Lista de errores return 0; } if (!nestedCascade.load(samples::findFileOrKeep(nestedCascadeName))) cerr << "WARNING: Could not load classifier cascade for nested objects" << endl; if (!cascade.load(samples::findFile(cascadeName))) //Si no encuentra “cascade” { cerr << "ERROR: Could not load classifier cascade" << endl; help(argv); return -1; } if( inputName.empty() || (isdigit(inputName[0]) && inputName.size() == 1) ) { int camera = inputName.empty() ? 0 : inputName[0] - '0'; if(!capture.open(camera)) { cout << "Capture from camera #" << camera << " didn't work" << endl; return 1; } // Disminución de resolución de cámara capture.set(CAP_PROP_FRAME_WIDTH, 640); capture.set(CAP_PROP_FRAME_HEIGHT,320); } if( capture.isOpened() && IR.isOpened()) //Espera imagen de ambos sensores { cout << "Video capturing has been started ..." << endl; for(;;) { tini = (double)getTickCount(); capture >> VMat; // Guarda frame visual en matriz VMat IR >> IRMat; // Guarda frame IR en matriz IRMat detect = false; // Aún no hay detección de rostro multidetect = false; if( VMat.empty() ) break; detectAndDraw( VMat, cascade, nestedCascade, scale, tryflip, centerX, centerY, iSliderValue2, iSliderValue1, IRMat); reset++; // contador de iteraciones sin detección tfin = ((double)getTickCount() - tini); // Tiempo de procesamiento t_proc = tfin*1000/getTickFrequency(); delay(Tm-t_proc); // Espera que finalice el procesado de la imagen if (detect) // Si se detecta rostro orientamos el sistema { reset = 0; salidaPIDX = PIDX.Update(centerX,Tm); // señal de control Yaw salidaPIDY = PIDY.Update(centerY,Tm); // señal de control Pitch
79 ANEXOS 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 // MOTOR EJE X if (salidaPIDX == new_salidaPIDX || multidetect)// si señal de control no cambia, no mover motor { softPwmWrite(P_PIN12,0); // pin a 0 para evitar vibraciones delay(100); } else // si el valor de posición cambia, enviamos señal al motor { new_salidaPIDX = salidaPIDX; softPwmWrite(P_PIN12,new_salidaPIDX); // enviamos nuevo valor de posición al motor delay(100); // espera posicionamiento del motor softPwmWrite(P_PIN12,0); // pin a 0 para evitar vibraciones } // MOTOR EJE Y if (salidaPIDY == new_salidaPIDY || multidetect) { softPwmWrite(P_PIN33,0); delay(100); } else { new_salidaPIDY = salidaPIDY; softPwmWrite(P_PIN33,new_salidaPIDY); delay(100); softPwmWrite(P_PIN33,0); } } if (reset >= 30) // Si no hay detección de cara en 30 iteraciones el sistema vuelve a la posición inicial { new_salidaPIDX = 13; // posición inicial del sistema new_salidaPIDY = 17; softPwmWrite(P_PIN12,new_salidaPIDX); softPwmWrite(P_PIN33,new_salidaPIDY); delay(450); // espera posicionamiento del motor softPwmWrite(P_PIN12,0); // pin a valor bajo softPwmWrite(P_PIN33,0); reset = 0; // reiniciamos índice PIDX.Reference(CentralPointX, pos_iniX); // reiniciar valores PIDY.Reference(CentralPointY, pos_iniY); } // Presionar q o ESC para abortar programa char c = (char)waitKey(10); if( c == 27 || c == 'q' || c == 'Q' ) break; } } return 0; } /**
ANEXOS 80 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 * @brief Función encargada de la detección y cálculo de Bounding-Box * * @param img * @param cascade * @param nestedCascade * @param scale * @param tryflip * @param centerX * @param centerY * @param iSliderValue2 * @param iSliderValue1 * @param IRMat */ void detectAndDraw( Mat& img, CascadeClassifier& cascade, CascadeClassifier& nestedCascade, double scale, bool tryflip, int& centerX, int& centerY, int iSliderValue2,int iSliderValue1, Mat IRMat ) { double t = 0; double Temperature=0; char Text[2]; Mat gray, smallImg; vector<Rect> faces, faces2; const static Scalar colors[] = { Scalar(255,0,0), Scalar(255,128,0), Scalar(255,255,0), Scalar(0,255,0), Scalar(0,128,255), Scalar(0,255,255), Scalar(0,0,255), Scalar(255,0,255) }; // Convierte el valor de las trackbars a offset real. int X_OFFSET = iSliderValue1 - 50; int Y_OFFSET = iSliderValue2 - 50; cvtColor( img, gray, COLOR_BGR2GRAY ); // Convierte img a escala de grises double fx = 1 / scale; // Factor de reducción de imagen resize( gray, smallImg, Size(), fx, fx, INTER_LINEAR_EXACT ); // Reduce gray un factor fx. Interpolacion bilineal equalizeHist( smallImg, smallImg ); // Histograma plano de smallImg cascade.detectMultiScale(smallImg, faces, 1.1, 5, 0, // Factor de escala; número de vecinos; flags Size(30, 30) ); //tamaño mínimo para ser detectado for ( size_t i = 0; i < faces.size(); i++ ) { Rect r = faces[i]; // Variable que almacena ROIs Mat smallImgROI; // ROI cara detectada vector<Rect> nestedObjects; Scalar color = colors[i%8]; Point center; int radius;
81 ANEXOS 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 Mat IRMatBW; // Matriz para almacenar imagen IR en escala de grises double otsuThreshold = 0; // Variable que almacena umbral de Otsu. double aspect_ratio = (double)r.width/r.height; if( 0.75 < aspect_ratio && aspect_ratio < 1.3 ) { center.x = cvRound((r.x + r.width*0.5)*scale); center.y = cvRound((r.y + r.height*0.5)*scale); rectangle( img, Point(cvRound(r.x*scale), cvRound(r.y*scale)), Point(cvRound((r.x + r.width-1)*scale), cvRound((r.y + r.height-1)*scale)),Scalar(255, 255, 255), 1, 8, 0); circle (img, center, 1 ,Scalar(255,255,255), 3,8,0); //Centro de ROI detect = true; } else rectangle( img, Point(cvRound(r.x*scale), cvRound(r.y*scale)), Point(cvRound((r.x + r.width-1)*scale), cvRound((r.y + r.height-1)*scale)),color, 3, 8, 0); if (faces.size() > 1) // Si detecta más de un rostro en la imagen multidetect = true; // Calcula el rectángulo con offset correspondiente en la imagen IR. int x1 = (r.x*scale)/PROP + X_OFFSET - MARGIN; int y1 = (r.y*scale)/PROP + Y_OFFSET - MARGIN; int x2 = ((r.x + r.width-1)*scale)/PROP + X_OFFSET + MARGIN; int y2 = ((r.y + r.height-1)*scale)/PROP + Y_OFFSET + MARGIN; // Comprueba que los valores son aceptables. (FIX?) if(x1 < 0) x1 = 0; if(y1 < 0) y1 = 0; if(x2 > IRCOLS - 1) x2 = IRCOLS - 1; if(y2 > IRROWS - 1) y2 = IRROWS - 1; // Convierte la imagen IR a blanco y negro para poder binarizar. cvtColor(IRMat, IRMatBW, COLOR_BGR2GRAY); // Coloca un rectángulo en la cara detectada en la imagen IR. Rect ROI = Rect(x1, y1, x2-x1, (y2-y1)); // ROI rectangle(IRMat, ROI, Scalar(255,255,255), 1, 1, 0); //Dibujar BoundingBox // Calcula la temperatura media de la cara. double Temperature = GetTempFromIRImage(IRMatBW,ROI,true,&otsuThreshold); sprintf(Text,"%.1f C" , Temperature); // Convierte double a string putText(IRMat,Text,Point(x1,y1-5), FONT_HERSHEY_PLAIN,1, Scalar(255, 255, 255), 0.8, LINE_8); // Comprobar si la temperatura pasa de un valor determinado if (Temperature >= 38){ int pausa = 1; imshow("IR",IRMat); // Muestra ROI con temperatura elevada delay(100);
ANEXOS 82 384 385 386 387 388 389 390 391 392 393 394 395 396 while(pausa == 1){ // Ejecución pausada. Pulsar 'p' para reanudar char a = (char)waitKey(10); if( a == 27 || a == 'p' || a == 'P' ) {pausa = 0; destroyWindow("IR"); } } } } //Muestra imágenes imshow("IR",IRMat); }