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.
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.
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).
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.
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:
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.
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:
- En la última sección con título [convolutional], cambiaremos el numero de
filtros
, la fórmula esfiltros=(cantidad de clases + 5)*5
. Como únicamente tenemos una clase, vamos a modificar afilters=30
. - 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).📈
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.
Por favor aplaude, comparte y suscríbete si te gustó el tutorial, comenta si no te gustó. ¡Gracias por leer 🙏!