Formation d'un détecteur d'objets AI avec Label Studio & MMDetection

L’étiquetage et l’entraînement nécessitent un peu de collage.

Sommaire

Quand j’ai entraîné un détecteur d’objets AI il y a un certain temps - LabelImg était un outil très utile, mais l’exportation depuis Label Studio vers le format COCO n’était pas acceptée par le framework MMDetection..

Il avait besoin d’outils et de scripts pour que tout fonctionne.

topimage

Liste ici les éléments manquants et certains scripts certains que j’ai trouvés sur Internet et d’autres que j’ai écrits moi-même.

Étapes de base

Préparer, entraîner et utiliser un AI implique

  1. obtenir des données sources (images pour la détection d’objets et la classification)
  2. étiqueter les images et préparer le dataset
  3. développer un nouveau modèle ou trouver un modèle existant adapté
  4. entraîner le modèle, parfois avec un réglage des hyperparamètres
  5. utiliser le modèle pour prédire des étiquettes pour de nouvelles images (ingerring)

Ici, je donne les étapes exactes pour étiqueter des données avec Label Studio (étape 2) et entraîner avec mmdetection et torchvision (étape 4), et aborder l’inférence (étape 5)

Label Studio

Il a certaines racines open source dans le programme LabelImg mais maintenant développé et maintenu de manière très centralisée. Et a bien sûr une version d’entreprise.

Toutefois, il est disponible pour le déploiement en interne, ce qui est très pratique.

Configurer et exécuter

On peut installer et exécuter Label Studio de plusieurs façons, comme par exemple via le package pip, ou via un groupe de conteneurs docker compose. Ici, j’utilise un seul conteneur docker.

Préparer le dossier source

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

Configuration du stockage local

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

# configurer quelques permissions supplémentaires

Dans ~/ai-local-store, vous conservez les fichiers d’images, ~/ai-local-labels - les étiquettes synchronisées.

Démarrer le conteneur 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 - à cause des redirections du LS webui. DATA_UPLOAD_MAX_NUMBER_FILES … Django a restreint le nombre de fichiers uploadés à 100 et cela avait un effet terrible sur label studio, donc il fallait fournir cette nouvelle limite. Toutes les autres configurations sont très bien documentées dans les docs Label Studio.

Importer la configuration. Dans les paramètres du projet, dans le Stockage Cloud, ajouter un Stockage Source de type Fichiers Locaux similaire à :

topimage

N’oubliez pas de cocher “Traiter chaque objet de bucket comme un fichier source” si vous prévoyez de synchroniser des images (je crois que oui), pas les jsons. Si vous avez déjà certaines étiquettes pour ces images dans un json séparé - vous configurez simplement ce Stockage Cloud. Ne pas cliquer sur Synchroniser. Et ensuite importer le json.

Configurer de la même façon le Stockage Cloud cible et ai-local-labels - si vous souhaitez les synchroniser.

Importer des données pré-étiquetées dans Label Studio

J’adore le format JSON COCO. Un format Pascal VOC aussi. Mais ils ne sont pas directement compatibles avec Label Studio, il faut donc les convertir d’abord au format propre à LS.

Mais avant, vous pourriez probablement avoir besoin de filtrer le dataset. Vous pourriez avoir besoin uniquement de certaines étiquettes, pas de toutes. J’aime le script filter.py du 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

D’accord, maintenant, installez le convertisseur. Comme le site officiel du convertisseur le recommande :

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 depuis COCO et configurer le dossier correct

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

Importer le output.json en cliquant sur le bouton Importer dans Label Studio.

Étiquetage

Beaucoup de travail créatif extrêmement intense est fait ici.

Export

Après avoir cliqué sur le bouton Exporter dans Label Studio et avoir sélectionné le format d’exportation COCO - jetez un œil à l’intérieur de ce fichier et admirez les noms des images. Ils ressembleraient à ceci si vous avez importé des étiquettes avant sans remplacer le chemin de base des images

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

Ou ressembleraient à ceci si vous avez synchronisé un stockage cloud externe.

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

Ce sont assez peu agréables. Nous voulons quelque chose comme

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

Pour corriger les noms de fichiers, j’ai utilisé des scripts très agréables. Ils remplacent le fichier result.json, donc si vous avez besoin d’une sauvegarde avant - assurez-vous de la faire vous-même :

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

Sauvegarde des données et de la base de données de Label Studio

Arrêtez soigneusement votre conteneur docker Label Studio et exécutez ensuite quelque chose comme

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

Fusionner

Parfois, il faut fusionner plusieurs datasets en un seul, surtout si vous faites plusieurs itérations.

J’ai utilisé l’outil COCO-merger. Après l’installation et l’exécution avec le paramètre -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

Oui, on peut fusionner uniquement deux fichiers. Donc si vous avez 10 itérations - il faut faire un effort supplémentaire. Toutefois, j’aime cette méthode.

MMDetection

topimage

Division du dataset

Pour diviser le dataset en ensembles d’entraînement et de test, j’ai utilisé l’outil COCOSplit.

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

Il n’y a pas trop de choses à faire :

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

Divise le fichier d'annotations COCO en ensembles d'entraînement et de test.

arguments positionnels :
  coco_annotations      Chemin vers le fichier d'annotations COCO.
  train                 Où stocker les annotations d'entraînement COCO
  test                  Où stocker les annotations de test COCO

arguments optionnels :
  -h, --help            affiche ce message d'aide et quitte
  -s SPLIT              Pourcentage de la division ; un nombre entre 0 et 1
  --having-annotations  Ignorer toutes les images sans annotations. Conserver uniquement celles avec au moins une annotation
  --multi-class         Diviser un dataset multi-classe tout en préservant la distribution des classes dans les ensembles d'entraînement et de test

Pour exécuter la division COCO :

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

N’oubliez pas d’ajouter la propriété licenses au début du fichier json du dataset, quelque part après le premier “{”. Ce outil de division veut vraiment cela.

  "licenses": [],

Configuration

Oui, les configurations de modèles sont complexes.

Mais mask-rcnn est assez rapide et a un taux de détection raisonnable. Voir ici pour les détails de la configuration : https://mmdetection.readthedocs.io/en/latest/user_guides/train.html#train-with-customized-datasets

# La nouvelle configuration hérite d'une configuration de base pour mettre en évidence les modifications nécessaires
_base_ = '/home/someusername/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py'

# Nous devons également changer le nombre de classes dans la tête pour qu'il corresponde aux annotations du dataset
model = dict(
    roi_head=dict(
        bbox_head=dict(num_classes=3),
        mask_head=dict(num_classes=3)))

# Modifier les paramètres liés au dataset
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

# Modifier les paramètres liés aux métriques
val_evaluator = dict(ann_file=data_root+'test.json')
test_evaluator = val_evaluator

# Nous pouvons utiliser le modèle Mask RCNN pré-entraîné pour obtenir une meilleure performance
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 vous aimez les longs films
# la valeur par défaut ici, si je me souviens bien, est 12
train_cfg = dict(max_epochs=24) 

Quelque chose à voir si les masques ne sont pas nécessaires : https://mmdetection.readthedocs.io/en/latest/user_guides/single_stage_as_rpn.html

Entraînement

supposons que vous ayez votre configuration de modèle dans /home/someusername/myproject/models/configs/mask-rcnn_r50-caffe_fpn_ms-poly-1x_v1.0.py . Le script d’entraînement est un appel standard à l’outil 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

Inférence

Quelques documents sont ici : 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')

# exécuter pour un seul fichier :
# inferencer('demo/demo.jpg', out_dir='/home/someusername/myproject/test-output/1.0/', show=True)

# ou pour tout un dossier
inferencer('/home/someusername/myproject/test-images/', out_dir='/home/someusername/myproject/test-output/1.0/', no_save_pred=False)

Liens utiles

J’espère que cela vous aidera de quelque manière que ce soit.

D’autres lectures utiles, veuillez consulter