• Black Mirror

    La serie Black Mirror trata sobre un futuro distópico no muy lejano (o lejano) donde todo está conectado gracias a la tecnología de una forma u otra, y que permitirá al ser humano disfrutar de nuevas capacidades, las cuales pueden tener sus riesgos y sobrepasar cualquier aspecto ético y moral que nos pueda quedar como especie.

    Black Mirror

    Hay capítulos en los que gracias a la tecnología podemos viajar sin tener que conducir, acceder a los recuerdos (para resolver un crimen), tener una videocámara permanente en nuestro cerebro (siendo capaces de ver todo lo que nos ha ido ocurriendo en el pasado), estar monitorizados en todo momento y en toda acción o tener una vida virtual paralela. Hay otros capítulos en los que parece que alguien nos vigila y sabe todo de nosotros, sin tener privacidad alguna, o en la que nuestro círculo social nos juzga según los me gusta del resto de personas que nos rodean.

    Cada capítulo te hace pensar y reflexionar sobre muchos de estos aspectos (sobre todo los éticos y morales) para darte cuenta de que en la época actual en la que nos está tocando vivir ya se dan muchos de conceptos de una forma u otra.

    Lo que vengo a contar hoy tiene algo de similitud con Black Mirror. Como he comentado, el uso de la tecnología puede sobrepasar la delgada línea que separa el bien del mal. Un cuchillo (la tecnología o herramienta) no es ni bueno ni malo, pero en principio ha sido diseñado para el bien (cortar la comida que vas a comer), pero se puede usar también para hacer el mal (asestar veintisiete puñaladas). Así, la bondad o maldad de la tecnología dependerá más de la persona que la use, por lo que tenemos que tener siempre presente una cosa: En este mundo hay gente buena, y gente mala, y por desgracia en algunos aspectos el número de gente mala sobrepasa en número (y por mucho) a la gente buena.

    El otro día (período comprendido entre una semana y la fecha de creación del Universo), leí sobre cómo un personaje había sido capaz de robar miles de datos de personas (incluyendo números de tarjetas de crédito, etc…). Es decir, sin hacer nada, sin darnos cuenta, podemos haber sido monitorizados y desvalijados como si estuviésemos en ese futuro distópico no tan lejano.

    Normalmente, si eres un ser maligno, puedes realizar diferentes pasos para tratar de acceder a bases de datos remotas, encontrar brechas de seguridad en sistemas, etcétera… o bien preguntarle al usuario directamente.

    El usuario siempre es el eslabón más débil de la cadena de seguridad, y en cuanto se rompe un eslabón, está todo vendido. Pero ir usuario a usuario puede ser lento (tan lento como los humanos), y puede hacer que alguien con dos dedos de frente se entere y dé la voz de alarma (nota mental: Nunca déis vuestras contraseñas, ni por teléfono, ni por email, ni por mensaje, ni escritas en un post-it…).

    Mejor hacemos lo siguiente: Creamos una librería open source y la compartimos con el mundo.

    Al ser de código abierto, cualquier persona puede acceder al repositorio donde se encuentre el código, modificarlo, actualizarlo y mejorarlo. El disponer de software de código abierto siempre es bueno, porque cualquiera de los miles y miles de desarrolladores pueden echarle un ojo, encontrar bugs, mejorarlo, hacerlo más seguro… Además, hay gran cantidad de librerías que se ofrecen sin coste alguno para los desarrolladores (aunque hay que revisar sus licencias), por lo que pueden ser utilizadas para mejorar nuestros proyectos con nuevas funcionalidades de forma rápida, eficiente e indolora.

    Ahora bien, ¿qué tipo de librería tenemos que desarrollar y compartir para tener éxito? Una que resuelva una funcionalidad que mucha gente quiera resolver y que no existan otras librerías que ya lo solucionen. Esto es complicado, porque si alguien ha necesitado en el pasado una librería suele haber ya librerías muy utilizadas.

    Entonces, ¿qué es lo que más le gusta y llama la atención a los seres humanos? Las luces de colores, cuanto más brillantes mejor. En serio, las luces de colores es lo que más llama la atención a los humanos. Somos así de simples. En algún proyecto en ITERNOVA nos han indicado nuestros clientes que no podían trabajar usando el sistema desarrollado hasta que no cambiáramos los colores de los botones a colores más chillones, porque si no se confundían de botón (a pesar de que la leyenda de uno era Aceptar y la del otro Cancelar).

    Entonces, ¿qué librería desarrollamos? Una que nos muestre los logs de errores con colores. Color verde, azul, amarillo, naranja, rojo… y sus variantes de nombres rocambolescos: rojo tomate, rojo tierra, amarillo pollo, rosa chicle, azul aguamarina…

    Una vez desarrollada la librería, se pone a disposición de los desarrolladores en diferentes repositorios. Se publican un par de posts en Facebook, Reddit, Medium o en cualquier otra red social de moda donde puedan ser leídos por desarrolladores. La gente empieza a usarlo porque bueno… saca cosas de colores. No sirve para nada más, pero cada vez más desarrolladores la usan, más páginas y proyectos web la usan internamente… incluso webs de grandes corporaciones con miles y miles de usuarios diarios.

    En este momento, hay que tener en cuenta que las librerías Javascript, como la que hemos desarrollado, suelen tener un fichero package.js donde cualquier otro programador puede ver el código fuente y modificarlo si lo desea. Cuando se compila / reduce el fichero con dicho código (cosa que se suele hacer automáticamente sin que nadie tenga que hacer nada de forma manual), se genera un fichero package-min.js, de tamaño más reducido / minimizado / optimizado, que es el que se suele descargar y utilizar en las webs y proyectos.

    Aquí llega el punto interesante: Nadie se fija que realmente el fichero package-min.js tenga el código definido en package.js, por lo que el desarrollador maligno decide cambiar dicho fichero por uno creado por él de forma manual (sin tocar package.js, que es el que los desarrolladores que buscan bugs y crean mejoras van a leer y modificar). Cuando miles de webs actualizan esta librería, el fichero package-min.js se actualiza en todas ellas.

    Si bien el comportamiento inicial de package-min.js era sacar mensajes de error de colores, ahora además de seguir sacando dichos mensajes lo que hará es buscar campos de formulario en las páginas en las que se ejecute el código: Campos de usuarios, contraseñas, cuentas bancarias, números de tarjeta… datos que serán enviados sin que nos demos cuenta al desarrollador maligno.

    Y así, sin que nadie se lo espere, miles de usuarios, sin saberlo, están siendo robados. Y miles de desarrolladores, sin saberlo, han confiado en una librería de terceros que ha cruzado la línea del bien y del mal sin que se den cuenta. Un caballo de Troya en toda regla.

    Incluso hay desarrolladores malignos que usando esta misma técnica han conseguido que webs de “grandes” empresas (como Movistar) minen criptomonedas para su beneficio personal gracias a los miles de usuarios que las visualizan a diario (algunos de ellos ni se dieron cuenta de que su ordenador iba un 100% más lento que de costumbre al visitar dichas webs).

    Y tras leer este ladrillo, si no tenéis un poco de miedo a este futuro distópico es que vivís en uno de los mundos de Black Mirror y ya lo tenéis asumido. Bienvenidos a San Junípero.

  • 2017

    Como ya hice el año pasado por estas fechas, voy a hacer un resumen de lo que ha sido para mí el año 2017, para ver las desviaciones típicas entre lo planificado / deseado y la realidad. En general, ha sido un año muy divertido en el que menos descansar he hecho de todo: Volver a competir con buenas sensaciones, disfrutar al aire libre con Bruno y Martina haciendo mil cosas diferentes (desde ir en bici, con el carro azul de correr o jugando en cualquier parte), aprender un montón de cosas del movimiento maker (desde impresión 3D hasta empezar a trastear con Arduino / Raspberry Pi y el mundo IoT), volver a ver a viejos amigos…

    Feliz 2018 y siguientes

    La lista de cosas que tenía pensado hacer este año no era muy larga, pero aún así…

    • Mantener este blog personal:
      • Conseguido. Sobre todo he ido poniendo posts sobre el proyecto personal de esta temporada Construyendo un vehículo RC autónomo, por lo que he conseguido mantenerlo vivo.
      • También he hecho un poco de limpieza / organización, y le he introducido alguna pequeña mejora (sobre todo, de estilos)
    • Leer libros, escuchar podcasts y participar en webinarios tecnológicos de forma frecuente (en definitiva, para aprender nuevas cosas)
    • Ir en bici y patinar con Bruno y Martina:
      • Hemos ido en bici, hemos patinado y hemos ido a entrenar (Bruno en bici, Martina en su carro y yo corriendo empujando el carro)
      • También hemos jugado a la pelota, hemos ido al cine y a la piscina, hemos participado en la Hora de código, hemos aprendido a programar usando bloques (Scratch, Arduino)…
    • Terminar al menos uno de los proyectos inacabados
    • Correr / ir en bici, al menos 4 veces por semana:
      • Conseguido y con creces. He conseguido no lesionarme en todo el año (desde que el año pasado me rompiera el metatarso). He tenido alguna molestia, pero no me ha impedido poder ir en bici o salir a correr con el carro de Martina, por lo que no he necesitado parar del todo.
      • He competido poco, pero con muy buenas sensaciones (para lo que entreno y para la edad que vamos teniendo), e incluso he conseguido hacer marcas que no conseguía desde hace 7 años (como bajar de 35 minutos el 10K o de 1h12 en la Behobia San Sebastián):
        • 10K (10K Pilar - Zaragoza): 34:57
        • Behobia San Sebastián (20K): 71:44 (puesto 98)
      • También he salido bastante con la bici (bastante para lo que suelo salir yo), y este año sólo he pinchado 2 veces (el mismo día, las dos ruedas). También fue divertido un día que salí de día y volví de noche (con focos y con liebres cruzándose delante de la luz)…

    En definitiva ha sido un buen año personal. Así, voy a hacer la lista de TO-DO para el año 2018:

    • Mantener este blog personal
    • Leer libros, escuchar podcasts y participar en webinarios tecnológicos de forma frecuente (en definitiva, para aprender nuevas cosas)
    • Hacer muchas cosas interesantes con Bruno y Martina: Esto sé que lo voy a conseguir.
    • Terminar el proyecto inacabado Construyendo un vehículo RC autónomo y comenzar otro
    • Correr / ir en bici, al menos 4 veces por semana: Ya tenemos planificadas algunas carreras (como el campeonato del mundo de Media Maratón a celebrarse en Valencia, en marzo). Ahora solo falta entrenar…

    Y hasta aquí los buenos propósitos. ¡El año que viene más y mejor!

  • Coche RC autónomo (IX) - Detectando señales de STOP con clasificadores y OpenCV

    Al llegar este post llevamos ya varios puntos del proyecto Coche RC autónomo - Construyendo un vehículo autónomo completados. Ya sabemos cómo configurar la Raspberry Pi con todas las librerías necesarias para la visión por computador usando OpenCV, sabemos utilizar un sensor (el de ultrasonidos) para detectar si hay un obstáculo delante nuestro y a qué distancia, y sabemos enviar las imágenes y estos datos desde la Raspberry Pi a nuestro ordenador.

    Los siguientes pasos son:

    • Aprender a procesar las imágenes para detectar señales de STOP y semáforos
    • Aprender a calibrar las imágenes recibidas para saber a qué distancia aproximada se encuentran esas señales de STOP y los semáforos
    • Aprender a crear una red neuronal que procese las imágenes para saber cuando tendremos que acelerar, frenar, girar a la izquierda o a la derecha
    • Aprender a controlar servos con la Raspberry Pi para que el vehículo acelere, frene, gire a izquierda o derecha cuando corresponda.

    En este post vamos a aprender cómo detectar señales de STOP.

    Señales de STOP detectadas

    Para ello, vamos a utilizar clasificadores en cascada que ofrece la propia librería OpenCV. Eso sí, necesitamos ficheros descriptores de los elementos que tenemos que detectar para que los clasificadores puedan detectar si en las imágenes aparecen o no esos objetos y la posición en la que aparecen. En nuestro caso, señales de STOP y semáforos, pero necesitaríamos descriptores para cada tipo de objeto adicional (cualquier otro tipo de señal, balizamiento, peatones, bicicletas, maceteros y bolardos…).

    Estos ficheros descriptores los podemos crear nosotros mismos, utilizando un montón de imágenes positivas (donde aparezca el objeto a detectar en multitud de posiciones, condiciones ambientales, etcétera) y un montón de imágenes negativas (donde no aparezca el objeto, pudiendo aparecer otros objetos similares diferentes). En muchos blogs indican que se necesitan como poco unas 60 imágenes positivas para la señal de STOP, y unas 600 para las imágenes negativas, pero cuantas más tengamos de cada una de ellas mejor.

    Obteniendo imágenes positivas y negativas

    Las imágenes positivas (las de la señal de STOP que queremos detectar) tienen que ser recortadas para que aparezca únicamente el objeto. Por ejemplo:

    Señales de STOP para entrenar al clasificador en cascada

    Las imágenes negativas pueden ser de cualquier cosa, pero si vamos a crear un sistema de conducción lo suyo sería usar fotografías de situaciones comunes que nos vayan a surgir: Fotografías de carreteras, con otras señales diferentes, etcétera. En este punto, como es muy aburrido conseguir un banco de fotos de unas 1000 fotografías, podemos exportar de una película MP4 todos los fotogramas. Por ejemplo, si tenemos ffmpeg instalado podemos ejecutar el siguiente comando para obtener de un vídeo INPUT.mp4 grabado con el móvil 5 imágenes cada segundo, con nombre image-000001.jpg (incrementándose el número de imagen de forma consecutiva). Así, si el vídeo dura 100 segundos, conseguiremos 500 imágenes en un momento:

    ffmpeg -i INPUT.mp4 -f image2 -vf fps=5 image-%06d.jpg
    

    Como veremos, hay más métodos para obtener imágenes, ya que OpenCV permite utilizar una imagen base para rotarla, distorsionarla, proyectarla… de forma automática sobre las imágenes negativas que tengamos. Ello permite crear en lote miles de imágenes en un momento (a más imágenes, el momento será más largo…).

    Todas las imágenes tienen que ser jpg, por lo que si tenemos alguna en formato png tendremos que convertirlas. Para ello podemos usar en Linux la herramienta mogrify, que permite convertir todas en lote mediante el comando:

    mogrify -format jpg *.png
    

    Además, habrá que convertirlas todas al mismo tamaño, cosa que podemos hacer también en lote:

    mogrify -path . -resize 256x256 *.jpg
    

    Luego las podremos renombrar en lote con el programa pyrenamer, que podemos instalar usando sudo apt-get install pyrenamer. Así, podemos ponerlas todas con el mismo formato de nombre (por ejemplo, numérico incremental).

    Una vez que tenemos las imágenes, vamos a crear los ficheros descriptores de los objetos (de nuestra señal de STOP). Para ello, necesitamos un listado (fichero txt) con los nombres de las imágenes. Suponiendo que las tenemos en los directorios training_images/positive y training_images/negative, ejecutaremos los siguientes comandos desde training_images:

    find ./positive -iname "*.jpg" -exec identify -format '%i 1 0 0 %w %h\n' \{\} \; > positives.dat
    find ./negative -iname "*.jpg" > negatives.dat
    

    Con esos comandos creamos el fichero de positivos, que tendrá la siguiente forma:

    [imagen] [Número de objetos en la imagen] [[x y width height] [Segundo objeto en la imagen ] ...]
    [imagen] [Número de objetos en la imagen] [[x y width height] [Segundo objeto en la imagen ] ...]
    [imagen] [Número de objetos en la imagen] [[x y width height] [Segundo objeto en la imagen ] ...]
    
    

    Así, estamos indicando que en cada imagen positiva tenemos 1 objeto, que va desde pixel del vértice superior izquierdo (0,0) y que tienen una anchura / altura según el objeto dentro de la imagen. Si todas imágenes fueran 256x256 sería más rápido, porque no tendríamos que calcular el ancho y alto de cada imagen. Asimismo, si tuviéramos varias coincidencias en la misma imagen, habría que indicar que hay 2 objetos y las coordenadas y ancho / alto de ambos objetos.

    El fichero resultante será algo así:

    ./positive/0001.jpg 1 0 0 243 256
    ./positive/0002.jpg 1 0 0 219 256
    ./positive/0003.jpg 1 0 0 243 256
    

    Creando muestras (samples) positivas y negativas

    OpenCV cuenta con dos aplicaciones que nos permiten crear samples (opencv_createsamples) y entrenar al calsificador en cascada que vayamos a utilizar (opencv_traincascade). Se puede encontrar más información en los tutoriales de OpenCV - Cascade classifier

    Los samples no dejan de ser las imágenes positivas (con el objeto a detectar) y las negativas (sin el objeto a detectar), solo que con opencv_createsamples vamos a crear muchas más imágenes positivas mezclando las imágenes positivas con las negativas. Así, se modificará las imágenes positivas cambiándoles la orientación, color, luminosidad, deformándolas, etcétera, introduciéndolas en las imágenes negativas.

    Ejecutaremos el siguiente comando (indicando que vamos a usar el fichero positives.dat creado en pasos anteriores), el cual nos devolverá un fichero positive-samples.vec. El parámetro -num 248 es el número de imágenes positivas que tengamos, mientras que el -w 50 -h 50 es la anchura / altura de los samples a generar:

    opencv_createsamples -info positives.dat -num 248 -w 50 -h 50 -vec positive-samples.vec
    

    Para crear, por ejemplo, 1000 samples a partir de una imagen:

    opencv_createsamples -img positives/0001.jpg -bg negatives.dat -num 1000 -w 50 -h 50 -maxxangle 1.1 -maxyangle 1.1 -maxzangle 0.5 -maxidev 40 -vec positive-samples.vec
    

    Para visualizar los samples generados, podemos ejecutar el siguiente comando:

    opencv_createsamples -vec positive-samples.vec -w 50 -h 50
    

    Una vez tengamos suficientes samples, tenemos que entrenar el clasificador. Necesitaremos miles de samples (cuantos más, mejor… sobre todo cuanto más complejo sea el objeto), o nos dará error el siguiente paso indicando que tenemos insuficientes samples. Las opciones son conseguir más imágenes o distorsionarlas y crear varios ficheros .vec para luego unirlos. Para unirlos podemos usar mergevec.

    En nuestro caso, para detectar la señal de STOP utilizamos 248 samples positivos (recortados para que únicamente se visualice el objeto, la señal de STOP) y 1000 negativos.

    Entrenando al clasificador con los samples positivos y negativos

    Utilizando todos los ficheros .vec anteriormente generados, pasamos a entrenar el clasificacdor. Necesitamos crear el directorio obj-classifier-stop, ya que es el directorio donde se almacenarán los parámetros generados. Es recomendable que este proceso se lleve a cabo en nuestro ordenador, ya que al ser más potente que la Raspberry Pi llevará bastante menos tiempo.

    El entrenamiento se realiza mediante el siguiente comando (podemos ver más opciones en el tutorial de OpenCV - Cascade Classifier Training, a la vez que nos tomamos uno o varios cafés, porque puede llevar horas según los parámetros que utilicemos…).

    opencv_traincascade -data obj-classifier-stop -vec positive-samples.vec -bg negatives.dat -precalcValBufSize 2048 -precalcIdxBufSize 2048 -numPos 200 -numNeg 2000 -nstages 20 -minhitrate 0.999 -maxfalsealarm 0.5 -w 50 -h 50 -nonsym -baseFormatSave
    

    Este comando iterará durante 20 rondas (stages), indicándonos para cada propiedad que está siendo entrenada (N) los ratios de tasa de acierto (HR: Hit Rate) y de falsas alarmas (FA: False Alarm). Nos devolverá para cada stage información como la tabla que se muestra a continuación. Si sólo se visualizan unas pocas propiedades (por ejemplo, N = 2), es posible que haya problemas con las imágenes que estemos usando para el entrenamiento, por lo que deberemos corregirlas o conseguir nuevas (o mayor cantidad).

    ===== TRAINING 12-stage =====
    <BEGIN
    POS count : consumed   200 : 200
    NEG count : acceptanceRatio    2000 : 6.10376e-06
    Precalculation time: 51
    +----+---------+---------+
    |  N |    HR   |    FA   |
    +----+---------+---------+
    |   1|        1|        1|
    +----+---------+---------+
    |   2|        1|        1|
    +----+---------+---------+
    |   3|        1|        1|
    +----+---------+---------+
    |   4|        1|   0.8165|
    +----+---------+---------+
    |   5|        1|   0.6775|
    +----+---------+---------+
    |   6|        1|    0.676|
    +----+---------+---------+
    |   7|        1|   0.6915|
    +----+---------+---------+
    |   8|        1|    0.506|
    +----+---------+---------+
    |   9|        1|   0.2185|
    +----+---------+---------+
    END>
    Training until now has taken 0 days 12 hours 35 minutes 16 seconds.
    

    Tras cada fase iterada (stage), se almacenará en el directorio indicado que creamos antes (en nuestro caso, obj-classifier-stop) ficheros XML. Podemos parar tras cada fase el procesado, modificar la configuración de nuestro ordenador o usar otro ordenador más potente, ya que el procesado seguirá desde la fase en la que lo dejamos.

    En nuestro caso, tras 15 horas 42 minutos y 27 segundos procesando el paso anterior (no tenemos hardware específico para estos tipos de procesado, de ahí que cueste tanto tiempo…), habremos obtenido el fichero obj-classifier-stop/cascade.xml, que lo copiaremos a cascade_xml/stop_sign.xml. Este descriptor lo utilizaremos en nuestro script Python con OpenCV para detectar las señales de STOP.

    Para detectar los semáforos el proceso sería similar, pero como ya hemos comentado, hay mucha gente que comparte su trabajo y ya lo han hecho por nosotros, como es el caso de Hamuchiwa. Suyo es el descriptor de detección de semáforos cascade_xml/traffic_lights.xml que vamos a usar.

    Como se puede ver, podemos crear un descriptor para poder clasificar cualquier tipo de señal u objeto que encontremos en la carretera. Los podemos compartir muy fácilmente, creando nuevas versiones y mejorando las existentes poco a poco. Al compartirlos, en todas las máquinas en las que se ejecuten se ejecutarán de forma similar, por lo que si somos capaces de detectar un tipo de señal, lo podrán hacer absolutamente todos los vehículos que contengan nuestros descriptores actualizados. Es decir, nuestros vehículos autónomos podrán ser actualizados de forma periódica como se actualiza nuestro teléfono móvil con nuevas versiones mejoradas de las aplicaciones instaladas.

    Estos clasificadores se pueden usar también para otras maravillas de la ciencia como detectar ciertos tipos de cáncer mediante imágenes con una eficiencia mayor a la que muchos especialistas puedan alcanzar en su vida. El código se puede traspasar y replicar en cualquier otra máquina para obtener los mismos resultados positivos con sólo copiar unos ficheros mientras que el conocimiento absoluto que tenga una persona sobre un tema es imposible compartirlo al instante al 100% con otra persona…

    En este punto hay que tener en cuenta que cuando nuestro vehículo viaje a otros paises quizá tenga que revisar qué descriptores tiene instalados, porque lo habitual en nuestro país es no encontrarse con señales de canguros y otras similares. Incluso en otros países, la señal de STOP es algo diferente y pone PARE. En este punto tendremos que actualizar y añadir nuevos descriptores para clasificar de forma correcta.

    En ambos scripts he ido incluyendo variables para facilitar la configuración usada, como puede ser la activación / desactivación de logs, dimensiones de imágenes, etcétera, por lo que es recomendable revisar dicha configuración cuando estéis haciendo pruebas. Ahora sólo queda probarlo arrancando primero el servidor y luego el cliente (accediendo previamente al entorno virtualizado python mediante workon cv):

    • En el servidor: python server.py
    • En el cliente: python client.py

    El resultado será similar al de la imagen que acompaña este post: Somos capaces de detectar semáforos y su estado, así como señales de STOP. En próximas iteraciones de nuestro script también trataremos de detectar a peatones (ya que existen modelos en OpenCV ya predefinidos para detectarlos).

    En próximos capítulos continuaremos con la calibración de las imágenes obtenidas de la cámara para poder calcular a qué distancia está la señal de STOP. ¡Espero que os guste!

    Actualización 2017-10-19

    En el directorio script_tests del repositorio de Github - jorgecasas/autonomous-rc-car he añadido el script cascade_classifier_test.py, junto al directorio cascade_classifier_test en el que hay varias imágenes que contienen señales de tráfico en entornos reales. Ejecutando dicho script podemos probar nuestro clasificador directamente en nuestro ordenador sin necesidad de conectar la Raspberry Pi, indicando qué imagen de prueba queremos usar y la ruta al descriptor XML de nuestro clasificador en cascada. Así lo podemos probar y podemos comprobar como nuestro clasificador está bien entrenado, comparándolo con otros ficheros descriptores.

    python cascada_classifier_test.py -c ../cascade_xml/stop_sign.xml -i images/stop-0020.jpg 
    

    Si creamos otros descriptores de otros objetos, este script nos puede servir para probarlos. Asimismo, podremos ver que pueden darse algunos falsos positivos (encuentra señal de STOP donde no la hay), y algunos falsos negativos (hay señal de STOP pero no la reconoce). Tampoco nos tiene que preocupar en nuestro proyecto que no detecte alguna imagen, porque en modo vídeo lo detectará en los siguientes fotogramas. El resultado será algo como lo siguiente:

    Señal de STOP detectada

    Más información sobre el proyecto