Entrenar un detector de objetos AI con Label Studio y MMDetection

Etiquetar y entrenar necesita algo de pegamento

Índice

Cuando entrené un detector de objetos AI hace algún tiempo - LabelImg fue una herramienta muy útil, pero la exportación desde Label Studio a formato COCO no era aceptada por el marco MMDetection..

Necesitaba algunas herramientas y scripting para que todo funcionara.

topimage

Listando aquí los elementos faltantes y algunos scripts algunos de los cuales encontré en internet y otros los escribí yo mismo.

Pasos básicos

Preparar, entrenar y usar AI implica

  1. obtener datos de origen (imágenes para detección de objetos y clasificación)
  2. etiquetar imágenes y preparar el conjunto de datos
  3. desarrollar un nuevo modelo o encontrar uno existente adecuado
  4. entrenar el modelo, a veces con ajuste de parámetros hiper
  5. usar el modelo para predecir etiquetas para nuevas imágenes (ingerring)

Aquí doy pasos exactos para etiquetar datos con Label Studio (paso 2) y entrenar con mmdetection y torchvision (paso 4), y tocar la inferencia (paso 5)

Label Studio

Tiene algunas raíces de código abierto en el programa LabelImg pero ahora se desarrolla y mantiene de manera muy centralizada. Y tiene una versión empresarial, por supuesto.

Aún así, está disponible para autohospedaje, lo cual es muy útil.

Configurar y ejecutar

Puedes instalar y ejecutar Label Studio de varias maneras, como por ejemplo el paquete pip, o un grupo de contenedores docker compose. Aquí estoy usando un solo contenedor docker.

Preparar la carpeta de origen

mkdir ~/ls-data
sudo chown -R 1001:1001 ~/ls-data10

Configuración de almacenamiento local

mkdir ~/ai-local-store
mkdir ~/ai-local-labels

# configurar algunos permisos adicionales

En ~/ai-local-store guardas los archivos de imagen, ~/ai-local-labels - etiquetas sincronizadas.

Iniciar el contenedor docker

docker run -it -p 8080:8080 \
    -e LABEL_STUDIO_HOST=http://your_ip_address:8080/ \
    -e LABEL_STUDIO_LOCAL_FILES_SERVING_ENABLED=true \
    -e LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT=/ai-local-store \
    -e DATA_UPLOAD_MAX_NUMBER_FILES=10000000 \
    -v /home/somename/ls-data:/label-studio/data \
    -v /home/somename/ai-local-store:/ai-local-store \
    -v /home/somename/ai-local-labels:/ai-local-labels \
    heartexlabs/label-studio:latest \
    label-studio \
    --log-level DEBUG

LABEL_STUDIO_HOST - debido a las redirecciones del webui de LS. DATA_UPLOAD_MAX_NUMBER_FILES … Django restringió la cantidad de archivos subidos a 100 y tuvo un efecto terrible en label studio, por lo que fue necesario proporcionar este nuevo límite. Todas las demás configuraciones están muy bien documentadas en las documentaciones de Label Studio.

Importar configuración. En la configuración del proyecto en Almacenamiento en la nube, agregar Almacenamiento de archivos local de tipo similar a:

topimage

No olvides marcar “Tratar cada objeto de cubo como un archivo de origen” si planeas sincronizar imágenes (creo que sí lo estás haciendo), no los jsons. Si ya tienes algunas etiquetas para estas imágenes en un json separado - simplemente configuras este Almacenamiento en la nube. No hacer clic en Sincronizar. Y luego Importar el json.

Configura lo mismo para el Almacenamiento en la nube de destino y ai-local-labels - si deseas sincronizarlos.

Importar datos preetiquetados en Label Studio

Me encanta el formato JSON COCO. También el formato Pascal VOC. Pero no son directamente compatibles con Label Studio, por lo que necesitas convertirlos primero al formato propio de LS.

Pero antes, probablemente necesitarás filtrar el conjunto de datos. Puede que necesites solo algunas etiquetas allí, no todas. Me gusta el script filter.py de coco-manager: https://github.com/immersive-limit/coco-manager/blob/master/filter.py

python filter.py --input_json instances_train2017.json --output_json filtered.json --categories person dog cat

Bien, ahora, instala el convertidor. Como recomienda el sitio oficial del convertidor:

python -m venv env
source env/bin/activate
git clone https://github.com/heartexlabs/label-studio-converter.git
cd label-studio-converter
pip install -e . 

Convertir desde COCO y establecer la carpeta correcta

label-studio-converter import coco -i your-input-file.json -o output.json

Importar el output.json haciendo clic en el botón Importar en Label Studio.

Etiquetado

Aquí se hace mucho trabajo creativo.

Exportar

Después de hacer clic en el botón Exportar en Label Studio y seleccionar el formato de exportación COCO - echa un vistazo dentro de este archivo y admira los nombres de las imágenes. Se verían así si importaste etiquetas antes sin sobrescribir la ruta base de la imagen

  "images": [
    {
      "width": 800,
      "height": 600,
      "id": 0,
      "file_name": "\/data\/local-files\/?d=\/iteration1001\/123.jpg"
    },

O así si sincronizaste el almacenamiento en la nube externo.

  "images": [
    {
      "width": 800,
      "height": 600,
      "id": 0,
      "file_name": "http:\/\/localhost:8080\/data\/local-files\/?d=iteration1001\/123.jpg"
    },

Estos no son muy bonitos. Queremos algo como

  "images": [
    {
      "width": 800,
      "height": 600,
      "id": 0,
      "file_name": "iteration1001/123.jpg"
    },

Para corregir los nombres de los archivos usé scripts muy agradables. Ellos sobrescribirán el archivo result.json, así que si necesitas un respaldo antes - cuida de ello tú mismo:

sed -i -e 's/\\\/data\\\/local-files\\\/?d=\\\///g' ~/tmp/result.json
sed -i "s%http:\\\/\\\/localhost:8080\\\/data\\\/local-files\\\/?d=%%" ~/tmp/result.json
sed -i "s%http:\\\/\\\/your_ip_address:8080\\\/data\\\/local-files\\\/?d=%%" ~/tmp/result.json

Backup de datos y base de datos de Label Studio

Detén cuidadosamente tu contenedor docker de Label Studio y luego ejecuta algo como

cp ~/ls-data ~/all-my-backups
cp ~/ai-local-store ~/all-my-backups
cp ~/ai-local-labels ~/all-my-backups

Fusionar

A veces necesitas fusionar varios conjuntos de datos en uno, especialmente si estás realizando varias iteraciones.

Usé la herramienta COCO-merger. Después de instalar y ejecutar con el parámetro -h:

python tools/COCO_merger/merge.py -h

COCO Files Merge Usage

python -m COCO_merger.merge --src Json1.json Json2.json --out OUTPUT_JSON.json

Argument parser

usage: merge.py [-h] --src SRC SRC --out OUT

Merge two annotation files to one file

optional arguments:
  -h, --help     show this help message and exit
  --src SRC SRC  Path of two annotation files to merge
  --out OUT      Path of the output annotation file

Sí, puedes fusionar solo dos archivos. Así que si tienes 10 iteraciones - necesitas hacer un esfuerzo adicional. Aún así, me gusta.

MMDetection

topimage

Dividir el conjunto de datos

Para dividir el conjunto de datos en entrenamiento y prueba usé la herramienta COCOSplit.

git clone https://github.com/akarazniewicz/cocosplit.git
cd cocosplit
pip install -r requirements

No hay mucho en ello:

$ python cocosplit.py -h
usage: cocosplit.py [-h] -s SPLIT [--having-annotations]
                    coco_annotations train test

Divide el archivo de anotaciones COCO en conjuntos de entrenamiento y prueba.

argumentos posicionales:
  coco_annotations      Ruta al archivo de anotaciones COCO.
  train                 Donde almacenar las anotaciones de entrenamiento COCO
  test                  Donde almacenar las anotaciones de prueba COCO

argumentos opcionales:
  -h, --help            mostrar esta ayuda y salir
  -s SPLIT              Porcentaje de división; un número en (0, 1)
  --having-annotations  Ignorar todas las imágenes sin anotaciones. Solo mantener estas con al menos una anotación
  --multi-class         Dividir un conjunto de datos de múltiples clases manteniendo la distribución de clases en los conjuntos de entrenamiento y prueba

Para ejecutar la división de COCO:

python cocosplit.py --having-annotations \
  --multi-class \
  -s 0.8 \
  source_coco_annotations.json \
  train.json \
  test.json

Recuerda agregar la propiedad licenses al principio del json del conjunto de datos, en algún lugar después del primer “{”. Esta herramienta de división realmente lo requiere.

  "licenses": [],

Configuración

Sí, las configuraciones del modelo son complicadas.

Pero mask-rcnn es bastante rápido y tiene una tasa de detección razonable. Ver aquí para los detalles de la configuración: https://mmdetection.readthedocs.io/en/latest/user_guides/train.html#train-with-customized-datasets

# La nueva configuración hereda una configuración base para destacar la modificación necesaria
_base_ = '/home/someusername/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py'

# También necesitamos cambiar el num_classes en la cabeza para que coincida con la anotación del conjunto de datos
model = dict(
    roi_head=dict(
        bbox_head=dict(num_classes=3),
        mask_head=dict(num_classes=3)))

# Modificar la configuración relacionada con el conjunto de datos
data_root = '/home/someusername/'
metainfo = {
    'classes': ('MyClass1', 'AnotherClass2', 'AndTheLastOne3'),
    'palette': [
        (220, 20, 60),
        (20, 60, 220),
        (60, 220, 20),
    ]
}
train_dataloader = dict(
    batch_size=1,
    dataset=dict(
        data_root=data_root,
        metainfo=metainfo,
        ann_file='train.json',
        data_prefix=dict(img='')))
val_dataloader = dict(
    dataset=dict(
        data_root=data_root,
        metainfo=metainfo,
        ann_file='test.json',
        data_prefix=dict(img='')))
test_dataloader = val_dataloader

# Modificar la configuración relacionada con la métrica
val_evaluator = dict(ann_file=data_root+'test.json')
test_evaluator = val_evaluator

# Podemos usar el modelo preentrenado Mask RCNN para obtener un mejor rendimiento
load_from = 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco/mask_rcnn_r50_caffe_fpn_mstrain-poly_3x_coco_bbox_mAP-0.408__segm_mAP-0.37_20200504_163245-42aa3d00.pth'

# si te gustan las películas largas
# el valor predeterminado aquí, si recuerdo bien, es 12
train_cfg = dict(max_epochs=24) 

Algo para ver si no se necesitan máscaras: https://mmdetection.readthedocs.io/en/latest/user_guides/single_stage_as_rpn.html

Entrenamiento

supongamos que tienes tu configuración del modelo en /home/someusername/myproject/models/configs/mask-rcnn_r50-caffe_fpn_ms-poly-1x_v1.0.py . El script de entrenamiento es una llamada estándar a la herramienta mmdetection:

cd ~/mmdetection
python tools/train.py \
    /home/someusername/myproject/models/configs/mask-rcnn_r50-caffe_fpn_ms-poly-1x_v1.0.py \
    --work-dir /home/someusername/myproject/work-dirs/my-object-detector-v1.0-mask-rcnn_r50-caffe_fpn_ms-poly-1x

Inferencia

Algunas documentaciones están aquí: https://mmdetection.readthedocs.io/en/latest/user_guides/inference.html

from mmdet.apis import DetInferencer

inferencer = DetInferencer(
    model='/home/someusername/myproject/models/configs/mask-rcnn_r50-caffe_fpn_ms-poly-1x_v1.0.py',
    weights='/home/someusername/myproject/work-dirs/my-object-detector-v1.0-mask-rcnn_r50-caffe_fpn_ms-poly-1x/epoch_12.pth')

# ejecútalo para un solo archivo:
# inferencer('demo/demo.jpg', out_dir='/home/someusername/myproject/test-output/1.0/', show=True)

# o para toda una carpeta
inferencer('/home/someusername/myproject/test-images/', out_dir='/home/someusername/myproject/test-output/1.0/', no_save_pred=False)

Enlaces útiles

Espero que esto te ayude de alguna manera.

Otras lecturas útiles por favor consulte en