Pular para o conteúdo

Treine modelos em volumes, nuvens de pontos e vídeo

Ajuste uma U-Net 3D com máscaras de TC pintadas, execute o seu próprio detector OpenPCDet em cuboides de lidar e treine um rastreador de vídeo, cada um pontuado em itens reservados.

Novo na v2.14.0

Este tutorial treina três modelos em dados 3D e em movimento a partir das anotações de um estudo do Potato. volume-unet treina uma U-Net 3D que segmenta volumes de TC e RM, external-3d executa um detector de lidar que você mesmo compila e executa, e video-track treina um rastreador de objetos em vídeo. A página é para equipes que já anotam volumes, nuvens de pontos ou vídeo no Potato e querem um modelo que pré-rotule o restante. Para cada treinador, a página cobre o esquema que produz os dados de treinamento, um config que passa em potato validate, o que uma execução faz, a pontuação em itens reservados e como as predições chegam aos anotadores. O tutorial do primeiro modelo percorre a página Training com um classificador de texto que dispensa GPU, e este tutorial pressupõe essa explicação.

Resumo dos treinadores

Os três treinadores ajustam um esquema por execução, pontuam o modelo nos itens val e test reservados da execução e gravam de volta as predições como pré-rótulos que os anotadores editam. volume-unet e os detectores por trás de external-3d precisam de uma GPU na prática, então os configs desses dois treinadores enviam as execuções a um servidor com GPU por ssh, como o tutorial de treinamento remoto configura.

TreinadorEsquemaItensInstalação na máquina de treinamentoPontuação principal
volume-unetvolume_annotation (máscaras pintadas)volumes NIfTI, séries DICOMpip install "potato-annotation[train-vision,volumes]"; uma GPU na práticamean_dice
external-3dspatial_annotation (cuboides)nuvens de pontoso seu próprio detector (OpenPCDet, MMDetection3D)map_3d_50
video-trackvideo_annotation no modo tracking ou combinedvídeopip install "potato-annotation[train-vision]" opencv-pythonidf1

Todo treinador lê a mídia de arquivos em disco. O caminho de uma mídia deve apontar para dentro do diretório da tarefa ou de media_directory, e os itens cuja mídia é uma URL http(s) ficam fora do treinamento e da predição.

Segmente volumes de TC e RM com volume-unet

volume-unet aprende as máscaras que os anotadores pintam em um esquema volume_annotation e pinta máscaras nos volumes que ninguém segmentou ainda. O esquema mostra um arquivo NIfTI ou uma série DICOM em três vistas ligadas (axial, coronal e sagital), e os anotadores pintam cada rótulo com o pincel ou fechando um polígono. O campo do item que source_field nomeia contém um arquivo .nii ou .nii.gz, ou um diretório com uma série DICOM, relativo a media_directory. Uma caixa desenhada em um corte não tem máscara, então uma resposta só com caixas não é um alvo de treinamento. O Potato é um software de pesquisa sem autorização clínica ou regulatória, e um modelo treinado aqui propõe máscaras para os anotadores corrigirem.

O config abaixo segmenta o baço e o fígado, treina em um servidor com GPU e pré-rotula todo volume fora da divisão train:

yaml
annotation_task_name: "Spleen segmentation"
task_dir: .
media_directory: media
data_files:
  - data/scans.jsonl
item_properties:
  id_key: id
  text_key: volume
output_annotation_dir: annotation_output
user_config:
  allow_all_users: true
 
annotation_schemes:
  - annotation_type: volume_annotation
    name: organs
    description: "Paint the spleen and the liver on every slice where each organ appears."
    source_field: volume
    labels:
      - {name: Spleen, color: "#e6553a", key_value: "1"}
      - {name: Liver, color: "#3a8ee6", key_value: "2"}
    tools: [brush, eraser, polygon]
    default_window: Soft tissue
 
model_training:
  enabled: true
  backend: gpu-server
  backends:
    gpu-server:
      type: ssh
      host: gpu.example.edu
      python: /opt/conda/bin/python3
      gpus: auto
      min_free_gpu_mb: 8000
  trainer_params:
    volume-unet:
      patch: 96
  writeback:
    schemas: [organs]
    min_confidence: 0.6
    include_unlabeled: true

Cada linha de dados nomeia um volume:

json
{"id": "case_001", "volume": "case_001.nii.gz"}
{"id": "case_002", "volume": "case_002_ct"}

O valor de patch define o lado do bloco cúbico em que a rede treina e deve ser um múltiplo de 4 nos três níveis padrão (2 elevado a levels menos 1). Um bloco de 96 voxels dá à rede mais contexto em torno de cada voxel que o padrão de 64 e precisa de mais memória de GPU. Com min_free_gpu_mb: 8000, o Potato inicia uma execução apenas em uma GPU com pelo menos 8.000 MB livres. O Potato toma a probabilidade média do modelo dentro da máscara de cada rótulo pintado e informa a menor dessas médias como a confiança do volume. Com min_confidence: 0.6, um volume em que algum órgão foi pintado com probabilidade média abaixo de 0,6 não recebe pré-rótulo.

Etapas de treinamento em uma execução de volume-unet

Uma execução lê cada volume anotado na ordem de voxels em que o widget pinta e transforma as máscaras em um rótulo por voxel. Onde dois rótulos se sobrepõem, vence o rótulo posterior na ordem de rótulos do pacote, como na exportação NIfTI. As intensidades são cortadas nos percentis 0,5 e 99,5 de cada volume e padronizadas. A rede é uma U-Net 3D em torch puro (três níveis, normalização de instância), treinada com entropia cruzada mais Dice suave em 100 blocos aleatórios por época. Metade dos blocos é centrada em um voxel pintado, de modo que estruturas pequenas aparecem no treinamento. Após cada época, a execução pontua o Dice médio nos volumes val e mantém a melhor época, e a execução para após 10 épocas sem uma pontuação melhor. A predição desliza o bloco por todo o volume com sobreposição de meio bloco e faz a média das probabilidades.

A divisão train precisa de pelo menos três volumes anotados (min_instances). Um volume cuja forma pintada difere da forma do arquivo fica de fora com um aviso no log da execução, e o mesmo vale para um volume cujo arquivo não existe. Com vários leitores em um volume, a execução treina com as máscaras do único leitor para o qual aggregation resolve, porque os agregadores comparam respostas inteiras. Segmente volumes de TC com vários leitores e STAPLE trata da concordância entre leitores e de uma máscara de consenso construída fora do Potato.

Pontuação em itens reservados para volumes

O Potato calcula o coeficiente de Dice para cada rótulo em cada volume reservado (dice_<label>) e informa a média sobre todos os volumes e rótulos como mean_dice. Um rótulo que o modelo omitiu em um volume que tem o rótulo recebe 0. O treinador escolhe a melhor época em val, então a tabela Runs toma a pontuação principal de test (test_mean_dice). A lista completa de pontuações por rótulo está na página da execução.

Pré-rótulos de máscara nas vistas de corte

As máscaras previstas voltam no formato próprio do widget, por rótulo e por corte axial, marcadas com prefilled_from: model, e aparecem nas vistas de corte quando um anotador abre o volume. O anotador corrige as máscaras com o pincel e a borracha, e as máscaras corrigidas são salvas como a resposta desse anotador. Itens da divisão train da execução nunca recebem pré-rótulo. Sem include_unlabeled, uma execução prevê apenas nos volumes val e test da execução.

Detecte cuboides 3D com external-3d

external-3d entrega as varreduras anotadas a um detector 3D que você instala no host de treinamento, executa os seus comandos de treinamento e de predição e lê as caixas previstas de volta como cuboides. O Potato não inclui um detector de nuvem de pontos. Os dois detectores para os quais o treinador foi feito, OpenPCDet e MMDetection3D, precisam de operadores CUDA compilados no host com GPU. Do lado do Potato, o treinador precisa apenas de numpy.

Os dados de treinamento vêm de um esquema spatial_annotation com a ferramenta cuboid_3d. Os anotadores posicionam caixas orientadas em varreduras PCD, PLY, LAS ou KITTI .bin, armazenadas em metros no referencial próprio da varredura (veja Nuvens de pontos 3D e cuboides). Os cuboides são os únicos alvos, então pontos, polilinhas e segmentos de pontos no mesmo esquema são ignorados. Cada item é uma varredura, e uma sequência de quadros com ids de trilha não é um alvo de treinamento.

Pasta de conjunto de dados gravada para o detector

Antes de o comando de treinamento executar, o Potato grava as varreduras em {dataset}, uma pasta chamada dataset dentro da pasta da execução, no layout de conjunto de dados personalizado do OpenPCDet:

  • points/<id>.npy contém uma varredura como linhas float32 de x y z intensity, com intensidade 0 quando o arquivo não tem nenhuma.
  • labels/<id>.txt contém uma linha por cuboide para as varreduras train e val, como x y z dx dy dz heading label. O heading é a rotação em torno de +z em radianos, e inclinação e rolagem não são mantidas. Um espaço em um nome de rótulo vira um sublinhado.
  • ImageSets/train.txt, val.txt e predict.txt listam os ids das varreduras. predict.txt lista as varreduras val e test, mais as varreduras não rotuladas quando writeback.include_unlabeled está ativado.

Cada <id> são os primeiros 16 caracteres hexadecimais do SHA-1 do id do item, porque os ids dos itens podem conter caracteres que um nome de arquivo não aceita. Os rótulos das varreduras test nunca são gravados na pasta. O Potato grava uma varredura georreferenciada relativa à origem da varredura, porque coordenadas georreferenciadas são grandes demais para float32, e desloca as caixas previstas de volta pelo mesmo deslocamento.

Arquivos de predição lidos de volta do detector

O comando de predição grava um arquivo por varredura em {predictions}/<id>.txt, uma caixa por linha, como x y z dx dy dz heading label score. A pontuação é opcional. O Potato deixa de fora uma linha que não é analisada, contém nan ou inf, nomeia um rótulo que o esquema não tem ou descreve uma caixa sem tamanho, e o log da execução conta as linhas deixadas de fora. As caixas restantes voltam como objetos cuboid_3d marcados com source: model, e a pontuação de cada caixa se torna a confiança da caixa. O Potato define a confiança de uma varredura como a menor pontuação de caixa da varredura, e min_confidence se aplica a esse valor.

Comandos de treinamento e de predição

Os dois comandos são definidos como parâmetros do treinador. O Potato divide cada comando em palavras como um shell faria, substitui {dataset}, {workdir} (a pasta da execução) e {predictions} pelos caminhos correspondentes e executa o comando na pasta da execução sem um shell. Outras chaves em um comando ficam como escritas. Pipes, && e $VARIABLES chegam ao programa como palavras literais, então trabalho de várias etapas pertence a um script. Adicione este bloco ao config do estudo de nuvens de pontos:

yaml
model_training:
  enabled: true
  backend: gpu-server
  backends:
    gpu-server:
      type: ssh
      host: gpu.example.edu
      python: /opt/conda/envs/pcdet/bin/python
      gpus: auto
  trainer_params:
    external-3d:
      train_command: "/opt/pcdet-jobs/train.sh {dataset} {workdir}"
      predict_command: "/opt/conda/envs/pcdet/bin/python /opt/pcdet-jobs/predict.py {dataset} {workdir} {predictions}"
      timeout_s: 43200
  writeback:
    schemas: [objects]
    min_confidence: 0.3
    include_unlabeled: true

Os caminhos estão no host de treinamento, onde a pasta da execução também fica. timeout_s limita cada comando, e 12 horas aqui substituem o padrão de 24. Um comando de treinamento que sai com código diferente de zero faz a execução falhar, e o erro da execução mostra as últimas linhas da saída do comando.

train.sh é o seu próprio script. Para o OpenPCDet, o script aponta o config de conjunto de dados personalizado do OpenPCDet para {dataset}, constrói os arquivos de informação com python -m pcdet.datasets.custom.custom_dataset create_custom_infos <dataset config>, executa o script de treinamento do OpenPCDet e salva o checkpoint em {workdir}. Os nomes de classe no config do detector devem coincidir com os nomes de rótulo como escritos em labels/. O guia de conjunto de dados personalizado do MMDetection3D lê nuvens de pontos apenas como arquivos .bin, então, para o MMDetection3D, o script primeiro grava cada points/<id>.npy como um arquivo .bin. Deixe train_command vazio para pular o treinamento, e predict_command passa a executar um modelo que você treinou em outro lugar.

predict.py lê os ids em predict.txt, executa o detector em cada varredura e grava as caixas. As funções load_model e detect são o seu próprio código em torno do detector que você treinou:

python
"""predict.py DATASET WORKDIR PREDICTIONS: one line per box, one file per scan."""
import os
import sys
 
import numpy as np
 
from my_detector import load_model, detect  # your code around OpenPCDet or MMDetection3D
 
dataset, workdir, out = sys.argv[1:4]
model = load_model(os.path.join(workdir, "output"))  # where train.sh saved the checkpoint
os.makedirs(out, exist_ok=True)
 
with open(os.path.join(dataset, "ImageSets", "predict.txt")) as fh:
    scan_ids = fh.read().split()
 
for scan_id in scan_ids:
    points = np.load(os.path.join(dataset, "points", scan_id + ".npy"))  # float32, (N, 4)
    with open(os.path.join(out, scan_id + ".txt"), "w") as fh:
        for x, y, z, dx, dy, dz, heading, label, score in detect(model, points):
            fh.write(f"{x} {y} {z} {dx} {dy} {dz} {heading} {label} {score}\n")

Sem um train_command, o comando de predição é a execução inteira, e um comando de predição que sai com código diferente de zero faz a execução falhar. Um comando de predição que sai com código 0 mas não grava nenhum <id>.txt para qualquer scan, ou grava apenas linhas que o Potato deixa de fora, também conta como falha, porque uma execução pontuada com essa saída pareceria um modelo que não encontrou nada. Um arquivo vazio é um scan sem objetos. Depois de um comando de treinamento, um comando de predição que falha não faz a execução falhar. A execução termina com um aviso, "Prediction pass failed after 0 prediction(s)", e as últimas linhas do comando, e a execução não tem predições nem pontuação em itens reservados. Verifique o log da execução por esse aviso quando uma execução concluída mostrar "not scored".

Pontuação em itens reservados para cuboides

O Potato calcula a precisão média 3D por rótulo com um IoU volumétrico de 0,5 (ap_3d_50_<label>). O IoU é exato para cuboides orientados em qualquer rotação. O Potato também informa a média sobre os rótulos com IoU 0,25 e 0,5, e precisão, revocação e F1 em 0,5. A pontuação principal é map_3d_50. Com um train_command, a tabela Runs toma a pontuação principal primeiro de test, porque o comando de treinamento recebe os rótulos val em {dataset} e pode escolher um checkpoint com esses rótulos. Sem um train_command, o modelo nunca viu os itens val do estudo, e a pontuação principal vem de val.

Pré-rótulos de cuboide no visualizador de nuvem de pontos

Os cuboides previstos aparecem no visualizador de nuvem de pontos como caixas editáveis, e uma caixa editada é salva como a resposta do anotador. O Potato verifica cada objeto previsto antes de o visualizador desenhá-lo e deixa de fora um objeto cuja geometria não é analisada, um cuboide sem tamanho e um rótulo que o esquema não tem. Como anotar nuvens de pontos 3D trata do lado da anotação e da concordância em caixas 3D.

Rastreie objetos em vídeo com video-track

video-track aprende com as trilhas que os anotadores desenham em um esquema video_annotation no modo tracking ou combined e prevê trilhas nos vídeos que ninguém rastreou. Uma trilha é um rótulo e uma caixa em cada quadro-chave, e o widget interpola a caixa entre os quadros-chave. Um esquema video_annotation em outro modo armazena segmentos em vez de trilhas, e video-track não é oferecido para esse esquema.

yaml
annotation_task_name: "Pedestrian tracking"
task_dir: .
media_directory: media
data_files:
  - data/clips.jsonl
item_properties:
  id_key: id
  text_key: video
output_annotation_dir: annotation_output
user_config:
  allow_all_users: true
 
annotation_schemes:
  - annotation_type: video_annotation
    name: tracks
    description: "Track every pedestrian and cyclist with a box at each keyframe."
    source_field: video
    mode: tracking
    video_fps: 25
    labels:
      - {name: pedestrian, color: "#FF6B6B"}
      - {name: cyclist, color: "#45B7D1"}
    tracking_options:
      interpolation: linear
      auto_advance_frames: 5
    frame_stepping: true
 
model_training:
  enabled: true
  trainer_params:
    video-track:
      frame_fps: 2
      predict_fps: 5
      max_age: 4
  writeback:
    schemas: [tracks]
    include_unlabeled: true

Cada linha de dados nomeia um clipe em media_directory:

json
{"id": "clip_001", "video": "/media/clip_001.mp4"}
{"id": "clip_002", "video": "/media/clip_002.mp4"}

Defina video_fps como a taxa de quadros real dos clipes. Os quadros-chave são numerados por quadro, e o treinador numera os quadros-chave previstos no video_fps do esquema (30 quando não definido). O treinador lê a caixa em um quadro amostrado pela configuração interpolation de cada trilha (linear, Catmull-Rom cúbica ou constante). As caixas com que o modelo aprende, portanto, seguem o caminho que os anotadores viram. Como no widget, uma trilha com um só quadro-chave é mantida em todos os quadros, e uma trilha a que o widget deu um startFrame e um endFrame corre entre esses dois quadros. Um quadro-chave fora desses dois quadros ainda aparece no próprio quadro. predict_fps: 5 detecta em cinco quadros por segundo em vez de dois. Cinco quadros por segundo dão a um objeto que se move rápido mais quadros-chave e custam mais passagens do detector. Uma trilha termina após mais de max_age quadros amostrados sem uma detecção correspondente, então max_age: 4 a 5 quadros por segundo permite que uma trilha sobreviva a 0,8 segundo de detecções perdidas. Um pedestre que passa atrás de um poste mantém uma trilha quando o detector o encontra de novo em até 0,8 segundo. O config treina na máquina que executa o Potato (o backend local), e o backend gpu-server do config de volumes também funciona para video-track.

Treinamento e ligação de trilhas em uma execução de video-track

Uma execução amostra cada vídeo anotado a frame_fps quadros por segundo, do início ao último quadro-chave, e lê cada quadro amostrado com OpenCV. Os pixels do canvas dependem da tela do anotador, então o treinador lê a caixa de cada trilha nesse quadro como uma fração da imagem. Um quadro amostrado sem nenhuma trilha permanece no treinamento como exemplo de uma cena vazia. A execução ajusta um Faster R-CNN do torchvision nesses quadros, com os parâmetros e os padrões de torchvision-detect. O detector parte dos pesos COCO do torchvision. Após cada época, a execução pontua o mAP de caixas com IoU 0,5 nos quadros val (frame_map_50) e mantém a melhor época.

A predição amostra cada vídeo a predict_fps, detecta em cada quadro amostrado e liga as detecções em trilhas. As detecções de cada quadro são casadas um a um com as trilhas ativas do mesmo rótulo, pela sobreposição com a última caixa de cada trilha, e um casamento precisa de um IoU de pelo menos match_iou (0,3). Uma detecção sem casamento com pontuação de pelo menos new_track_score (0,5) inicia uma nova trilha. O treinador descarta uma trilha prevista com menos de min_track_keyframes detecções (2 por padrão) antes de gravá-la e pontuá-la. Sem isso, o widget manteria em todos os quadros uma trilha de uma só detecção. Toda detecção se torna um quadro-chave, então o widget interpola entre os quadros-chave do modelo como faz entre os de um anotador.

A divisão train precisa de pelo menos dois vídeos anotados (min_instances). Um vídeo que o OpenCV não consegue decodificar fica de fora com um aviso que nomeia o item, e a execução continua com o resto.

Pontuação em itens reservados para trilhas

O Potato compara as trilhas previstas com as trilhas anotadas em quadros amostrados a 2 por segundo em cada vídeo reservado, mais todo quadro-chave anotado. As pontuações são MOTA, IDF1, trocas de identidade, omissões e falsos positivos, e uma caixa conta como encontrada com IoU 0,5. O MOTA debita omissões, falsos positivos e trocas de identidade contra o número de caixas anotadas. O IDF1 é a pontuação F1 das caixas casadas com a identidade correta ao longo de todo o vídeo, e o IDF1 é a pontuação principal (idf1). O treinador escolhe a melhor época em val, então a tabela Runs toma a pontuação principal de test.

Pré-rótulos de trilha no widget de vídeo

As trilhas previstas preenchem o widget de vídeo como trilhas com quadros-chave marcados como saída de IA e com a pontuação do detector. O widget converte as caixas para os pixels do próprio canvas ao desenhar as trilhas, e o anotador corrige as trilhas como qualquer outra trilha. A confiança de um vídeo é a pontuação média dos quadros-chave do modelo. No modo combined, um pré-rótulo contém apenas trilhas, e o anotador adiciona os segmentos. Como anotar rastreamento de objetos em vídeo trata do lado da anotação, da oclusão e da concordância temporal.

Inicie uma execução e verifique o resultado

Reinicie o servidor depois de adicionar model_training, abra /admin/training, escolha o esquema e o treinador e clique em Train now. A página oferece apenas os treinadores que se ajustam a cada esquema e lista um treinador que não está instalado como "not installed", com o comando de instalação. Para verificar um servidor com GPU antes da primeira execução, execute:

bash
potato training check gpu-server config.yaml

A verificação informa a versão do Python remoto, cada GPU com a memória livre da GPU, a GPU que uma execução receberia e os pacotes de treinamento que faltam no interpretador remoto. Uma execução também pode começar pela API, com parâmetros por execução que substituem trainer_params:

bash
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  http://localhost:8000/admin/training/api/runs \
  -d '{"trainer": "video-track", "schemas": ["tracks"], "params": {"predict_fps": 10}}'

Quando a execução termina, a tabela Runs lista a pontuação em itens reservados, com a divisão e a métrica em uma dica. Os pré-rótulos ancoram os anotadores, então ative include_unlabeled em um estudo novo quando a pontuação em itens reservados justificar mostrar as respostas do modelo. O tutorial do modelo no ciclo trata de servir primeiro os pré-rótulos de menor confiança e de retreinar com as correções, e Configuração e rotas do treinamento de modelos lista todas as chaves, os backends e as verificações de gravação de volta.

Leitura complementar

Referências

  • Bernardin, K., and Stiefelhagen, R. (2008). Evaluating Multiple Object Tracking Performance: The CLEAR MOT Metrics. EURASIP Journal on Image and Video Processing. https://doi.org/10.1155/2008/246309
  • Çiçek, Ö., Abdulkadir, A., Lienkamp, S. S., Brox, T., and Ronneberger, O. (2016). 3D U-Net: Learning Dense Volumetric Segmentation from Sparse Annotation. MICCAI 2016. https://arxiv.org/abs/1606.06650
  • Ristani, E., Solera, F., Zou, R. S., Cucchiara, R., and Tomasi, C. (2016). Performance Measures and a Data Set for Multi-Target, Multi-Camera Tracking. ECCV 2016 Workshop on Benchmarking Multi-Target Tracking. https://arxiv.org/abs/1609.01775