Treinando um Detetor de Objetos AI com Label Studio & MMDetection

A etiquetagem e o treinamento exigem algum colagem.

Conteúdo da página

Quando eu treinei um detector de objetos AI 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 pela estrutura MMDetection..

Ela precisava de algumas ferramentas e scripts para que tudo funcionasse.

topimage

Listando aqui os bits faltantes e alguns scripts alguns dos quais encontrei na internet e outros que escrevi eu mesmo.

Passos básicos

Preparar, treinar e usar IA envolve

  1. obter dados fonte (imagens para detecção de objetos e classificação)
  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 parâmetros hiper
  5. usar o modelo para prever rótulos para novas imagens (ingerring)

Aqui estou dando passos exatos para rotular dados com o Label Studio (etapa 2) e treinar com mmdetection e torchvision (etapa 4), e tocando na inferência (etapa 5)

Label Studio

Ele tem algumas raízes abertas no programa LabelImg mas agora é desenvolvido e mantido de forma muito centralizada. E tem uma versão empresarial, é claro.

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

Configurar e executar

É possível instalar e executar o Label Studio de várias formas, como por exemplo, o pacote pip, ou um grupo de contêineres docker compose. Aqui estou usando um único contêiner docker.

Preparar 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

# configurar algumas permissões adicionais

Na pasta ~/ai-local-store você mantém os arquivos de imagem, ~/ai-local-labels - rótulos sincronizados.

Iniciar o contêiner 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 - devido aos redirecionamentos da interface web do LS. DATA_UPLOAD_MAX_NUMBER_FILES … Django restringiu o número de arquivos carregados para 100 e teve um efeito terrível no Label Studio, então foi necessário fornecer esse novo limite. Todas as outras configurações estão muito bem documentadas no Label Studio docs.

Importar configuração. Nos ajustes do projeto no Armazenamento em Nuvem, adicione um Armazenamento de Arquivos Locais do tipo similar a:

topimage

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

Configure o mesmo para o Armazenamento em Nuvem de Destino e ai-local-labels - se quiser sincronizar para fora.

Importar dados previamente rotulados no Label Studio

Adoro o formato JSON COCO. Um VOC Pascal também. Mas eles não são diretamente compatíveis com o Label Studio, então é necessário converter para o formato proprietário do LS primeiro.

Mas antes disso, provavelmente será necessário 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

Okay, 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 . 

Converter de COCO e definir a pasta correta

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

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

Rotulagem

Muita criatividade extremamente original é feita aqui.

Exportação

Após clicar no botão Exportar no Label Studio e selecionar o formato de exportação COCO - dê uma olhada dentro desse arquivo e admire os nomes das imagens. Eles pareceriam assim se você importou rótulos antes sem substituir o caminho base da imagem

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

Ou pareceriam assim se você sincronizou o armazenamento em nuvem externo.

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

Esses não são muito agradáveis. queremos algo como

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

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

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

Para parar cuidadosamente seu contêiner docker do Label Studio e depois executar 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, 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. pode mesclar apenas dois arquivos. Então, se você tiver 10 iterações - precisa fazer um esforço extra. Ainda gosto disso.

MMDetection

topimage

Dividindo 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 há muito a fazer:

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

Divide o arquivo de anotações COCO em conjuntos de treinamento e teste.

argumentos posicionais:
  coco_annotations      Caminho para o arquivo de anotações COCO.
  train                 Onde armazenar as anotações de treinamento COCO
  test                  Onde armazenar as anotações de teste COCO

argumentos opcionais:
  -h, --help            mostrar esta ajuda e sair
  -s SPLIT              Porcentagem de divisão; um número em (0, 1)
  --having-annotations  Ignorar todas as imagens sem anotações. Manter apenas aquelas com pelo menos uma anotação
  --multi-class         Dividir um conjunto de dados multi-classe preservando as distribuições de classe em treinamento e teste

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 de adicionar a propriedade licenses no início do json do conjunto de dados, em algum lugar após o primeiro “{”. Essa ferramenta de divisão realmente quer isso.

  "licenses": [],

Configuração

Sim, as configurações do modelo são complexas.

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

# A nova config herda uma config 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 no cabeçalho para corresponder às anotações do conjunto de dados
model = dict(
    roi_head=dict(
        bbox_head=dict(num_classes=3),
        mask_head=dict(num_classes=3)))

# Modificar as 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 as configurações relacionadas à métrica
val_evaluator = dict(ann_file=data_root+'test.json')
test_evaluator = val_evaluator

# Podemos usar o modelo pre-treinado Mask RCNN para obter um desempenho mais alto
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 eu me lembro corretamente, é 12
train_cfg = dict(max_epochs=24) 

Algo para olhar se os masks não forem necessários: https://mmdetection.readthedocs.io/en/latest/user_guides/single_stage_as_rpn.html

Treinamento

Vamos supor que você tenha sua configuração do 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 para a 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

Alguns documentos 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-o para um único arquivo:
# inferencer('demo/demo.jpg', out_dir='/home/someusername/myproject/test-output/1.0/', show=True)

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

Espero que isso ajude você de alguma forma.

Outras leituras úteis, por favor, veja em