Treinamento de Detetor de Objetos de IA com Label Studio e MMDetection

A rotulagem e o treinamento precisam de alguma colagem.

Conteúdo da página

Quando treinei um detector de objetos com IA há algum tempo, o LabelImg foi uma ferramenta muito útil, mas a exportação do Label Studio para o formato COCO não era aceita pelo framework MMDetection..

Foi necessário algum suporte de ferramentas e scripts para fazer tudo funcionar.

topimage

Listo aqui as partes faltantes e alguns scripts, alguns que encontrei na internet e outros que eu próprio escrevi.

Passos básicos

Preparar, treinar e usar IA envolve:

  1. obter dados de origem (imagens para detecção e classificação de objetos)
  2. rotular imagens e preparar o conjunto de dados
  3. desenvolver um novo modelo ou encontrar um existente adequado
  4. treinar o modelo, às vezes com ajuste de hiperparâmetros
  5. usar o modelo para prever rótulos para novas imagens (inferência)

Aqui, dou passos exatos sobre como rotular dados com o Label Studio (passo 2) e treinar com mmdetection e torchvision (passo 4), além de abordar a inferência (passo 5).

Label Studio

Tem raízes de código aberto no programa LabelImg, mas agora é desenvolvido e mantido de forma muito centralizada. E tem, claro, uma versão empresarial.

Ainda assim, está disponível para auto-hospedagem, o que é muito bom.

Configurar e executar

Pode instalar e executar o Label Studio de várias maneiras, como o pacote pip, por exemplo, ou um grupo de containers docker compose. Aqui estou usando um único container docker.

Prepare a pasta de origem

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

Configuração de armazenamento local

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

# configure algumas permissões extras

Em ~/ai-local-store você mantém os arquivos de imagem, em ~/ai-local-labels - os rótulos sincronizados para fora.

Inicie o container 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/someusername/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 - devido aos redirecionamentos da web UI do LS. DATA_UPLOAD_MAX_NUMBER_FILES … O Django limitava o número de arquivos de upload para 100 e isso teve um efeito terrível no label studio, então foi necessário fornecer este novo limite. Todas as outras configurações estão muito bem documentadas na documentação do Label Studio.

Importe a configuração. Nas configurações do projeto em Armazenamento em Nuvem, adicione um Armazenamento de Origem do tipo Arquivos Locais, semelhante a:

topimage

Não se esqueça de marcar “Tratar cada objeto do bucket como um arquivo de origem” se você planeja sincronizar imagens (acho que sim), e não os jsons. Se você já tem alguns rótulos para essas imagens em um json separado - você apenas configura este Armazenamento em Nuvem. Não clique em Sincronizar. E então importe o json.

Configure o mesmo para o Armazenamento em Nuvem de Destino e ai-local-labels - se você quiser sincronizá-los para fora.

Importar dados pré-rotulados no Label Studio

Adoro o formato json COCO. E o Pascal VOC também. Mas eles não são diretamente compatíveis com o Label Studio, então precisam ser convertidos para o formato proprietário do LS primeiro.

Mas antes - provavelmente precisará filtrar o conjunto de dados. Você pode precisar apenas de alguns rótulos ali, não de todos. Gosto do script filter.py do 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

Ok, agora, instale o conversor. Como o site oficial do conversor recomenda:

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 . 

Converta do coco e definindo a pasta correta

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

Importe o output.json clicando no botão Importar no Label Studio.

Rotulagem

Muito trabalho extremamente criativo é feito aqui.

Exportação

Após clicar no botão Exportar no Label Studio e selecionar o formato de exportação COC - olhe dentro deste arquivo e admire os nomes das imagens. Eles ficariam assim se você importasse rótulos antes de sobrescrever o caminho base da imagem

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

Ou ficariam assim se você sincronizasse o Armazenamento em Nuvem externo.

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

Estes não são muito bons. Queremos algo como

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

Para corrigir os nomes dos arquivos, usei scripts adoráveis. Eles estão sobrescrevendo o arquivo result.json, então se você precisar de um backup antes - cuide disso vocês mesmos:

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 dados e banco de dados do Label Studio

Pare cuidadosamente o container docker do seu Label Studio e depois execute algo como

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

Mesclar

Às vezes é necessário mesclar vários conjuntos de dados em um só, especialmente se estiver executando várias iterações.

Usei a ferramenta COCO-merger. Após instalar e executar com o 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

Sim. Só pode mesclar dois arquivos. Então se você tem 10 iterações - precisa de um esforço extra. Ainda assim, gosto disso.

MMDetection

topimage

Dividir o conjunto de dados

Para dividir o conjunto de dados em treinamento e teste, usei a ferramenta COCOSplit.

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

Não é muito complicado:

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

Splits COCO annotations file into training and test sets.

positional arguments:
  coco_annotations      Path to COCO annotations file.
  train                 Where to store COCO training annotations
  test                  Where to store COCO test annotations

optional arguments:
  -h, --help            show this help message and exit
  -s SPLIT              A percentage of a split; a number in (0, 1)
  --having-annotations  Ignore all images without annotations. Keep only these
                        with at least one annotation
  --multi-class         Split a multi-class dataset while preserving class
                        distributions in train and test sets

Para executar a divisão do coco:

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

Lembre-se apenas de adicionar a propriedade de licenças no início do json do conjunto de dados, em algum lugar após o primeiro “{”. Esta ferramenta de divisão realmente quer isso.

  "licenses": [],

Configuração

Sim, as configurações de modelo são delicadas.

Mas o mask-rcnn é bastante rápido e tem uma taxa de detecção razoável. Veja aqui os detalhes da configuração: https://mmdetection.readthedocs.io/en/latest/user_guides/train.html#train-with-customized-datasets

# A nova configuração herda uma configuração base para destacar a modificação necessária
_base_ = '/home/someusername/mmdetection/configs/mask_rcnn/mask-rcnn_r50-caffe_fpn_ms-poly-1x_coco.py'

# Também precisamos alterar o num_classes na cabeça para corresponder à anotação do conjunto de dados
model = dict(
    roi_head=dict(
        bbox_head=dict(num_classes=3),
        mask_head=dict(num_classes=3)))

# Modificar configurações relacionadas ao conjunto de dados
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 configurações relacionadas a métricas
val_evaluator = dict(ann_file=data_root+'test.json')
test_evaluator = val_evaluator

# Podemos usar o modelo Mask RCNN pré-treinado para obter desempenho superior
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'

# se você gosta de filmes longos
# o padrão aqui, se não me engano, é 12
train_cfg = dict(max_epochs=24) 

Algo para olhar se as máscaras não forem necessárias: https://mmdetection.readthedocs.io/en/latest/user_guides/single_stage_as_rpn.html

Treinamento

Vamos assumir que você tem sua configuração de modelo em /home/someusername/myproject/models/configs/mask-rcnn_r50-caffe_fpn_ms-poly-1x_v1.0.py . O script de treinamento é uma chamada padrão à ferramenta 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

Inferência

Algumas documentações estão aqui: 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')

# execute para um arquivo único:
# inferencer('demo/demo.jpg', out_dir='/home/someusername/myproject/test-output/1.0/', show=True)

# ou para a pasta inteira
inferencer('/home/someusername/myproject/test-images/', out_dir='/home/someusername/myproject/test-output/1.0/', no_save_pred=False)

Espero que isso ajude de alguma forma.

Outras leituras úteis, veja em