Detección de objetos en imágenes y video con YOLO usando Darkflow

Tutorial paso a paso de cómo entrenar YOLO para detectar nuestros propios objetos.

enrique a.
8 min readSep 9, 2018

Esta es otra historia en una serie en curso sobre detección de objetos usando YOLO (You Only Look Once), la primera parte es una introducción acerca del algoritmo y una breve exploración de (algunas de) las diferentes implementaciones.

Introducción

Las diferentes implementaciones de YOLO (Darknet, Darkflow, etc) son herramientas que nos pueden ayudar a detectar objetos justo después de instalarlas (sin necesidad de entrenarlas por nuestra cuenta). Para hacer eso únicamente hace falta instalar el programa y descargar los pesos de la red entrenada. Por ejemplo, en el sitio oficial de Darknet podemos encontrar los comandos para obtener y usar los pesos para el dataset COCO o para VOC PASCAL.

🐎 Podemos descargar los pesos y comenzar a detectar caballos 🐎

Sin embargo nos podemos encontrar casos en los que los objetos que queremos detectar simplemente no se encuentran en estos populares sets de datos. En estos casos será necesario crear (u obtener) nuestro propio set de datos y ejecutar nuestro propio entrenamiento de la red.

En este tutorial vamos a seguir paso a paso el procedimiento para crear el set de datos y ejecutar el entrenamiento usando Darkflow (una traducción de Darknet para correr sobre TensorFlow).

🗺️ Ejemplo real de una red entrenada con YOLO para detectar texto en mapas 🗺️

Paso 1: Obtener las imágenes

Para este tutorial nuestro caso ejemplo va a ser entrenar YOLO para detectar texto en mapas ilustrados, como el siguiente.

Propiedad de la oficina de turismo de la ciudad de Kioto, Japón.

Como te puedes imaginar, el principal problema de este tipo de mapas es que son bastante grandes en dimensiones, y que contienen demasiadas instancias del objeto a detectar (texto). Es por eso que para el set de datos vamos a cortar los archivos grandes en un mosaico de pequeñas imágenes del mismo tamaño. Para llevar a cabo este proceso podemos utilizar ✂️ esta herramienta ✂️.

En mi caso opté por crear mosaicos de imágenes cuadradas de 608 px cada una, como los siguientes ejemplos:

Algunos ejemplos de las imágenes resultantes

Naturalmente este paso ✂️ es opcional, y no será necesario si tus imágenes son consistentes en tamaño y además no contienen demasiados objetos.

Paso 2: Anotar manualmente las imágenes

Debido a que este problema únicamente requiere la detección de una clase de objetos, podemos usar mi fork de una herramienta llamada BBox-Label-Tool para anotar las imágenes. Esta herramienta es más fácil de instalar y usar que otras alternativas.

Este es un problema de aprendizaje automático supervisado, así que necesitamos anotar manualmente el set de datos para que la red pueda aprender a reconocer los objetos “observando” los ejemplos.

(Si tu problema incluye la detección de múltiples clases por imagen sugiero usar otra herramienta más completa, como esta.)

Para instalar BBox-Label-Tool, debemos ejecutar:

pip install pillow
git clone https://github.com/enriqueav/BBox-Label-Tool.git
cd BBox-Label-Tool

La estructura de directorios quedará como la siguiente:

BBox-Label-Tool
|
|--main.py # código fuente de la herramienta
|--Images/ # directorio que contiene las imágenes para anotar
|--Labels/ # contiene los datos de las anotaciones
|--Examples/ # no lo usaremos
|--AnnotationsXML/ # directorio que contiene las anotaciones en el formato esperado por Darkflow

Dentro deImages/ , Labels/ , Examples/ y AnnotationsXML/ la herramienta espera encontrar directorios numerados que van a contener subconjuntos de imágenes y sus anotaciones correspondientes. El número 001 viene creado por default, así que vamos a empezar creando el número 002 para guardar nuestro set de datos.

mkdir Images/002 Labels/002 Examples/002 AnnotationsXML/002

Acto seguido necesitamos copiar todas las imágenes obtenidas en el paso anterior en Images/002 .

cp /path/to/your/images/*.jpg Images/002

Ahora podemos lanzar la herramienta y comenzar a anotar ✏️.

python main.py

En la GUI inicial, en el campo “Image Dir:” escribimos “2” (para cargar 002) y damos click en “Load”:

Esto va a cargar todas las imágenes en el directorio Images/002 . Ahora podemos comenzar a dibujar, una por una, las cajas que contienen los objetos que queremos detectar una vez entrenada la red.

Un ejemplo de una imagen anotada manualmente

Cuando terminemos con una imagen damos click en “Next >>” para pasar a la siguiente. También es posible navegar hacia atrás o incluso a un número específico usando la barra de navegación de la parte inferior.

Necesitamos hacer este proceso para todas y cada una de las imágenes en el set. Este va a ser el paso que parezca más largo y aburrido de todo el proceso, pero vamos, no hay nada que hacer al respecto ¯\_(ツ)_/¯.

Una vez terminadas las anotaciones, estas quedarán guardadas en archivos XML dentro del directorio AnnotationsXML/002 esto es lo que utilizaremos para entrenar Darkflow.

Paso 3: Instalar Darkflow

Para descargar e instalar el sistema, la forma más sencilla es ejecutar los siguientes pasos (puede ser que necesites instalar TensorFlow y numpy con anterioridad).

git clone https://github.com/thtrieu/darkflow.git
cd darkflow
python3 setup.py build_ext --inplace

Puedes encontrar más información en el sitio de github del proyecto.

Paso 4: Modificar los archivos de configuración (configurar la red)

Hay dos configuraciones de red que podemos usar para entrenar, yolo y tiny-yolo. Como su nombre lo indica, tiny-yolo se trata de una red mucho más pequeña. Naturalmente es más rápida y podrá ejecutarse mejor en ambientes con prestaciones reducidas, pero sufrirá de una menor exactitud. En el directorio cfg/ se encuentran los archivos de configuración iniciales para ambas versiones:

$ ls -1 cfg/ | grep yolo.cfg
tiny-yolo.cfg
yolo.cfg

Para este ejemplo vamos a usar la versión completa de yolo, para ello vamos a necesitar crear una copia del archivo yolo.cfg y vamos a modificar esta copia para corresponder a las características de nuestro problema.

cp cfg/yolo.cfg cfg/yolo-new.cfg
# modify cfg/yolo-new.cfg
vi cfg/yolo-new.cfg

Necesitamos modificar dos líneas:

  1. En la última sección con título [convolutional], cambiaremos el numero de filtros , la fórmula es filtros=(cantidad de clases + 5)*5 . Como únicamente tenemos una clase, vamos a modificar a filters=30 .
  2. En la sección [region] hay una línea que especifica la cantidad de clases (al rededor de la línea 244), la cambiaremos a classes=1 o la cantidad de clases que tengas.

NOTA: Hay una serie de parámetros, sobre todo al inicio del archivo, que son tomados directamente de Darknet pero no son reconocidos por Darkflow. Por ejemplo batch=32 debería servir para especificar el tamaño de batch al entrenar. Sin embargo aunque modifiquemos el parámetro en el archivo de texto, será ignorado por Darkflow. Otro ejemplo es el learning rate.

Para utilizar valores diferentes a los que usa por defecto, deberemos especificar por línea de comandos --batch <tamaño> o --lr <learning_rate> , respectivamente.

Hay otro archivo que vamos a necesitar. Un archivo que contenga los nombres de las clases, uno por línea. Como únicamente tenemos una clase en este ejemplo, lo vamos a crear directamente por línea de comandos:

echo "map_text" >> one_label.txt

Por cierto, recomiendo revisar esta historia con varios tips y trucos para línea de comandos (en inglés).

Paso 5: Comenzar el entrenamiento

Vaya que hemos avanzado ¿verdad?. La buena noticia es que por fin estamos listos para iniciar el entrenamiento.

Solo para recordar, en el paso 2 creamos el set de entrenamiento. Este consiste en una serie de imágenes y sus correspondientes anotaciones guardadas en archivos XML. Estos archivos estarán localizados en las siguientes ubicaciones (necesitas substituir <ruta_a_bbox-label-tool> con la verdad ruta donde guardaste dicha herramienta.

<ruta_a_bbox-label-tool>/Images/002
<ruta_a_bbox-label-tool>/AnnotationsXML/002

Ahora, regresando a Darkflow, para iniciar el entrenamiento debemos ejecutar

python3 flow --model cfg/yolo-new.cfg \
--labels one_label.txt \
--train --trainer adam \
--dataset "<ruta_a_bbox-label-tool>/Images/002" \
--annotation "<ruta_a_bbox-label-tool>/AnnotationsXML/002"

Si cuentas con GPU para entrenar (¡Y vaya que deberías!), puedes agregar esto al comando anterior

--gpu 1.0

Darkflow iniciará entonces y cargará las imágenes. Eventualmente deberías a comenzar a ver líneas como las siguientes. Estas líneas contienen los detalles de cada uno de los pasos de entrenamiento que ha completado.

...
step 1 - loss 227.32052612304688 - moving ave loss 227.3205261230469
step 2 - loss 226.1829376220703 - moving ave loss 227.2067672729492
step 3 - loss 225.60186767578125 - moving ave loss 227.046277313232
step 4 - loss 227.2750701904297 - moving ave loss 227.0691566009522
step 5 - loss 227.2261199951172 - moving ave loss 227.0848529403687
...

Como sabemos, deep learning regularmente demora bastante tiempo ⏱ para entrenar. Este tiempo naturalmente dependerá de las prestaciones y características del hardware disponible, el tamaño del set de entrenamiento, la configuración de la red, etc. Podría tardar cualquier cantidad de tiempo entre una hora y varios días para arrojar resultados satisfactorios ⏱.

Por defecto Darflow va a guardar un checkpoint de los pesos de la red cada 250 pasos, así que puedes detener el entrenamiento en cualquier momento para tomar una pausa y/o validar los pesos actuales (ver el siguiente paso). Si necesitas retomar el entrenamiento desde el último checkpoint solamente necesitas agregar --load -1 al mismo comando que utilizamos para iniciar el proceso.

De nuevo, recomiendo revisar esta historia con varios tips y trucos para línea de comandos (en inglés). Por ejemplo podemos obtener una gráfica instantáneamente desde la terminal para revisar cómo va el nivel de pérdida (loss).📈

Ejemplo de una gráfica generada con Gnuplot desde la terminal de Linux/Mac OSX

Paso 6: Validar los resultados y usar la red entrenada para detectar

En cualquier punto del entrenamiento podemos detener el proceso y hacer una prueba utilizando un conjunto de imágenes (de preferencia uno nunca visto por el entrenamiento). Suponiendo que este segundo conjunto se encuentra en <ruta_al_conjunto_de_prueba> .

python3 flow --model cfg/yolo-new.cfg \
--imgdir <ruta_al_conjunto_de_prueba> \
--load -1 \
--labels one_label.txt \
--gpu 1.0

Por defecto Darkflow va a crear un directorio llamado out dentro de <ruta_al_conjunto_de_prueba> y va guardar ahí las imágenes anotadas con los objetos que la red detectó. También podríamos usar --json para obtener el resultado en un formato para ser consumido por software.

Estos son algunos ejemplos de la detección de texto después de haber entrenado la red por un poco más de un día. No es perfecto, pero es bastante aceptable dado el tamaño del set de entrenamiento que utilicé (no muy grande), y la dificultad del problema.

Algunos ejemplos de la detección de texto en mapas usando YOLO.

Por favor aplaude, comparte y suscríbete si te gustó el tutorial, comenta si no te gustó. ¡Gracias por leer 🙏!

--

--

enrique a.

Writing about Machine Learning, software development, python. Living in Japan working as a machine learning leader in a Japanese company.