prefacio
MMDetection
Es una caja de herramientas de detección de objetos, que contiene una gran cantidad de detección de objetos, segmentación de instancias, algoritmos de segmentación panorámica y componentes y módulos relacionados, dirección de proyecto github .- Modelos de detección de objetos ( ) admitidos
Object Detection
(algunos modelos SOTA en los últimos años): DAB-DETR, RTMDet, GLIP, Detic, DINO - Modelos de segmentación de instancias compatibles
Instance Segmentation
(algunos modelos SOTA en los últimos años): Mask2former, BoxInst, SparseInst, RTMDet - Modelos de segmentación panóptica compatibles (
Panoptic Segmentation
): Panoptic FPN, MaskFormer, Mask2Former - La diferencia entre la segmentación de instancias y la segmentación panorámica: la segmentación panorámica proporciona categorías semánticas a nivel de píxel e identificadores de instancias, mientras que la segmentación de instancias solo se enfoca en los límites y la segmentación de instancias de objetos. La segmentación panorámica proporciona información más completa y es adecuada para tareas que requieren un análisis detallado de cada píxel, como la conducción autónoma. La segmentación de instancias se centra más en la detección y segmentación de instancias de objetos, y es adecuada para tareas como la detección de objetos y la segmentación de imágenes.
- Este artículo presenta principalmente
MMDetection
el proceso de capacitación y prueba,Dog and Cat Detection
ajustóRTMDet
el modelo en el conjunto de datos, analizóRTMDet
el modelo y el índice final del modelobbox_mAP
alcanzó 0.952.
Configuración del entorno
- El código completo de configuración del entorno es el siguiente, si no desea ver el análisis paso a paso, puede omitir el resto de esta sección:
import IPython.display as display
!pip install openmim
!mim install mmengine==0.7.2
# 构建wheel,需要30分钟,构建好以后将whl文件放入单独的文件夹
# !git clone https://github.com/open-mmlab/mmcv.git
# !cd mmcv && CUDA_HOME=/usr/local/cuda-11.8 MMCV_WITH_OPS=1 pip wheel --wheel-dir=/kaggle/working .
!pip install -q /kaggle/input/frozen-packages-mmdetection/mmcv-2.0.1-cp310-cp310-linux_x86_64.whl
!rm -rf mmdetection
!git clone https://github.com/open-mmlab/mmdetection.git
!git clone https://github.com/open-mmlab/mmyolo.git
%cd mmdetection
%pip install -e .
!pip install wandb
display.clear_output()
- Primero instale
open-mmlab
la biblioteca de administración de paquetesopenmim
y luego instalemmengine
la biblioteca, el código es el siguiente:
!pip install openmim
!mim install mmengine==0.7.2
- Como
kaggle
no se puedemim
instalar directamentemmcv
(los entrenamientos posteriores reportarán un error), solo podemoswheel
instalarlo construyendo, el código es el siguiente:
!git clone https://github.com/open-mmlab/mmcv.git
!cd mmcv && CUDA_HOME=/usr/local/cuda-11.8 MMCV_WITH_OPS=1 pip wheel --wheel-dir=/kaggle/working .
- El paso anterior debe esperar unos 30 minutos, y luego encontrará
/kaggle/working
elmmcv-2.0.1-cp310-cp310-linux_x86_64.whl
archivo en el directorio,pip install -q /kaggle/working/mmcv-2.0.1-cp310-cp310-linux_x86_64.whl
solo use la instalación. Pero para ahorrar tiempo y evitar la necesidad de esperar mucho tiempo para cada ejecución, descargaréwheel
y cargaré la compilaciónkaggle Datasets
para que pueda instalarse solo cargando el conjunto de datos cada vez, y la dirección de datos se proporciona aquí . Entonces el código de instalación se convierte en:
!pip install -q /kaggle/input/frozen-packages-mmdetection/mmcv-2.0.1-cp310-cp310-linux_x86_64.whl
- Instalado a través
git clone
del métodommdetection
, debido a que el conjunto de datos es.xml
un sufijo, necesitamos usarmmyolo
la herramienta para convertir el formato más tarde, así que descárguelo juntos, pero no lo instalemmyolo
.
!rm -rf mmdetection
!git clone https://github.com/open-mmlab/mmdetection.git
!git clone https://github.com/open-mmlab/mmyolo.git
# 进入mmdetection项目文件夹
%cd mmdetection
# 安装mmdetection
%pip install -e .
- Si hay problemas de instalación de pycocotools durante el proceso de instalación, puede consultar mi artículo anterior MMYOLO framework etiquetado, capacitación y prueba de todo el proceso (suplemento) , que contiene soluciones detalladas.
- Debido a que se deben visualizar varios indicadores durante el proceso de capacitación, instale
wandb
el paquete e inicie sesión.
!pip install wandb
import wandb
wandb.login()
modelo de razonamiento
- Primero creamos una carpeta
checkpoints
para almacenar los pesos preentrenados del modelo. Porque elegimosRTMDet
el modelo, descarga los pesos correspondientes. - Podemos abrir
mmdetection
la dirección del proyecto github, ingresarconfigs/rtmdet
la ruta yREADME.md
hay pesos de preentrenamiento detallados en el archivo.
Params
Se puede observar que cuantos más parámetros del modelo ( ),box AP
mayor índice de precisión ( ), elegimos un modelo con una cantidad moderada de parámetrosRTMDet-l
, y elconfigs
nombre del archivo correspondiente esrtmdet_l_8xb32-300e_coco.py
. Significa modelo RTMDet-l, sobre 8 GPUsbatch size
de 32 cada una, entrenadas con 300 pesoscoco
sobre el dataset .epochs
Descargar y guardar encheckpoints
carpeta
!mkdir ./checkpoints
!mim download mmdet --config rtmdet_l_8xb32-300e_coco --dest ./checkpoints
- Usar el modelo para razonar y visualizar los resultados del razonamiento
from mmdet.apis import DetInferencer
model_name = 'rtmdet_l_8xb32-300e_coco'
checkpoint = './checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth'
device = 'cuda:0'
inferencer = DetInferencer(model_name, checkpoint, device)
img = './demo/demo.jpg'
result = inferencer(img, out_dir='./output')
display.clear_output()
from PIL import Image
Image.open('./output/vis/demo.jpg')
- Si no hay problemas hasta aquí, significa que la configuración del entorno es muy exitosa y el modelo RTMDet hace una inferencia.
recopilación de datos
Dog and Cat Detection
Información de la organización del archivo del conjunto de datos :
- Dog-and-Cat-Detection
- annotations
- Cats_Test0.xml
- Cats_Test1.xml
- Cats_Test2.xml
- ...
- images
- Cats_Test0.png
- Cats_Test1.png
- Cats_Test2.png
- ...
- Dado que el conjunto de datos debajo de la ruta es de solo lectura, no se permite cambiarlo, y el archivo marcado está en un
kaggle
formato que debe convertirse. Aquí, primero copie la imagen en el directorioinput
.xml
./data/images
import shutil
# 复制文件到工作目录
shutil.copytree('/kaggle/input/dog-and-cat-detection/images', './data/images')
- Dado que la segmentación posterior del conjunto de datos requiere información de etiquetado como
.json
formato, convertimos los archivosdog-and-cat-detection/annotations
de la carpeta.xml
en un solo.json
archivo.
import xml.etree.ElementTree as ET
import os
import json
coco = dict()
coco['images'] = []
coco['type'] = 'instances'
coco['annotations'] = []
coco['categories'] = []
category_set = dict()
image_set = set()
category_item_id = -1
image_id = 0
annotation_id = 0
def addCatItem(name):
global category_item_id
category_item = dict()
category_item['supercategory'] = 'none'
category_item_id += 1
category_item['id'] = category_item_id
category_item['name'] = name
coco['categories'].append(category_item)
category_set[name] = category_item_id
return category_item_id
def addImgItem(file_name, size):
global image_id
if file_name is None:
raise Exception('Could not find filename tag in xml file.')
if size['width'] is None:
raise Exception('Could not find width tag in xml file.')
if size['height'] is None:
raise Exception('Could not find height tag in xml file.')
image_id += 1
image_item = dict()
image_item['id'] = image_id
image_item['file_name'] = file_name + ".png"
image_item['width'] = size['width']
image_item['height'] = size['height']
coco['images'].append(image_item)
image_set.add(file_name)
return image_id
def addAnnoItem(object_name, image_id, category_id, bbox):
global annotation_id
annotation_item = dict()
annotation_item['segmentation'] = []
seg = []
seg.append(bbox[0])
seg.append(bbox[1])
seg.append(bbox[0])
seg.append(bbox[1] + bbox[3])
seg.append(bbox[0] + bbox[2])
seg.append(bbox[1] + bbox[3])
seg.append(bbox[0] + bbox[2])
seg.append(bbox[1])
annotation_item['segmentation'].append(seg)
annotation_item['area'] = bbox[2] * bbox[3]
annotation_item['iscrowd'] = 0
annotation_item['ignore'] = 0
annotation_item['image_id'] = image_id
annotation_item['bbox'] = bbox
annotation_item['category_id'] = category_id
annotation_id += 1
annotation_item['id'] = annotation_id
coco['annotations'].append(annotation_item)
def parseXmlFiles(xml_path):
for f in os.listdir(xml_path):
if not f.endswith('.xml'):
continue
xmlname = f.split('.xml')[0]
bndbox = dict()
size = dict()
current_image_id = None
current_category_id = None
file_name = None
size['width'] = None
size['height'] = None
size['depth'] = None
xml_file = os.path.join(xml_path, f)
tree = ET.parse(xml_file)
root = tree.getroot()
if root.tag != 'annotation':
raise Exception('pascal voc xml root element should be annotation, rather than {}'.format(root.tag))
for elem in root:
current_parent = elem.tag
current_sub = None
object_name = None
if elem.tag == 'folder':
continue
if elem.tag == 'filename':
file_name = xmlname
if file_name in category_set:
raise Exception('file_name duplicated')
elif current_image_id is None and file_name is not None and size['width'] is not None:
if file_name not in image_set:
current_image_id = addImgItem(file_name, size)
else:
raise Exception('duplicated image: {}'.format(file_name))
for subelem in elem:
bndbox['xmin'] = None
bndbox['xmax'] = None
bndbox['ymin'] = None
bndbox['ymax'] = None
current_sub = subelem.tag
if current_parent == 'object' and subelem.tag == 'name':
object_name = subelem.text
if object_name not in category_set:
current_category_id = addCatItem(object_name)
else:
current_category_id = category_set[object_name]
elif current_parent == 'size':
if size[subelem.tag] is not None:
raise Exception('xml structure broken at size tag.')
size[subelem.tag] = int(subelem.text)
for option in subelem:
if current_sub == 'bndbox':
if bndbox[option.tag] is not None:
raise Exception('xml structure corrupted at bndbox tag.')
bndbox[option.tag] = int(float(option.text))
if bndbox['xmin'] is not None:
if object_name is None:
raise Exception('xml structure broken at bndbox tag')
if current_image_id is None:
raise Exception('xml structure broken at bndbox tag')
if current_category_id is None:
raise Exception('xml structure broken at bndbox tag')
bbox = []
bbox.append(bndbox['xmin'])
bbox.append(bndbox['ymin'])
bbox.append(bndbox['xmax'] - bndbox['xmin'])
bbox.append(bndbox['ymax'] - bndbox['ymin'])
addAnnoItem(object_name, current_image_id, current_category_id, bbox)
os.makedirs('./data/annotations')
xml_path = '/kaggle/input/dog-and-cat-detection/annotations'
json_file = './data/annotations/annotations_all.json'
parseXmlFiles(xml_path)
json.dump(coco, open(json_file, 'w'))
- Información de la organización del archivo de almacenamiento de datos del directorio de trabajo actual:
- mmdetection
- data
- annotations
- annotations_all.json
- images
- Cats_Test0.png
- Cats_Test1.png
- Cats_Test2.png
- ....
- ...
- Dado que necesitamos usar
mmyolo
un script en el archivo del proyecto para dividir los datos en conjuntos de entrenamiento y prueba, primero ingresemmyolo
a la carpeta del proyecto
# 切换到mmyolo项目文件夹
%cd /kaggle/working/mmyolo
- El archivo de secuencia de comandos de segmentación se encuentra
tools/misc/coco_split.py
y los parámetros de arriba a abajo son: --json (.json
ruta del archivo generado); --out-dir (.json
ruta de la carpeta de almacenamiento del archivo de segmentación generado); --ratios 0.8 0.2 (conjunto de entrenamiento, conjunto de prueba proporción); –shuffle (si barajar el orden); –seed (semilla de número aleatorio)
# 切分训练、测试集
!python tools/misc/coco_split.py --json /kaggle/working/mmdetection/data/annotations/annotations_all.json \
--out-dir /kaggle/working/mmdetection/data/annotations \
--ratios 0.8 0.2 \
--shuffle \
--seed 2023
- producción:
Split info: ======
Train ratio = 0.8, number = 2949
Val ratio = 0, number = 0
Test ratio = 0.2, number = 737
Set the global seed: 2023
shuffle dataset.
Saving json to /kaggle/working/mmdetection/data/annotations/trainval.json
Saving json to /kaggle/working/mmdetection/data/annotations/test.json
All done!
- A continuación, vuelva a
mmdetection
la carpeta del proyecto:
%cd /kaggle/working/mmdetection
- En este momento, la información de organización del archivo de almacenamiento de datos del directorio de trabajo:
- mmdetection
- data
- annotations
- test.json
- trainval.json
- annotations_all.json
- images
- Cats_Test0.png
- Cats_Test1.png
- Cats_Test2.png
- ....
- ...
Editar la configuración del modelo RTMDet
-
RTMDet
El diagrama de arquitectura del modelo se puede encontrar en la documentación de la carpeta de parámetros correspondienteREADME.md
.
-
Puede
github
abrirconfigs/rtmdet/rtmdet_l_8xb32-300e_coco.py
el archivo de configuración en (observe el valor _base_, si hay una relación de herencia, puede buscar hasta encontrar el archivo principal), donde elRTMDet-l
modelo modelo ya es el archivo principal y se puede ver directamente. -
Las cosas principales que queremos cambiar son
_base_
(archivos superiores heredados),data_root
(carpeta de almacenamiento de datos),train_batch_size_per_gpu
(porGPU
entrenamientobatch size
),train_num_workers
(número de trabajos principales, generalmenten GPU x 4
), ( númeromax_epochs
máximo ), (tasa de aprendizaje básica), (tipo de información y el correspondiente a cada categoría), (información de etiquetado de ruta de imagen y conjunto de entrenamiento), (información de etiquetado de ruta de imagen y conjunto de validación), (información de etiquetado de conjunto de validación), (número de redes troncales congeladas , número de categorías), (decaimiento de la tasa de aprendizaje tendencia), (asignación de tasa de aprendizaje), (estrategia de conservación del peso del modelo), ( cambio de tubería de datos), ( ruta de carga de peso previa al entrenamiento), (medición de asignación y verificación), (semilla de número aleatorio fijo), (seleccionar plataforma de visualización)epoch
base_lr
metainfo
train_dataloader
val_dataloader
val_evaluator
model
stages
param_scheduler
optim_wrapper
default_hooks
custom_hooks
load_from
train_cfg
max_epochs
randomness
visualizer
-
Lo más importante en el archivo de configuración son
metainfo
los parámetros ymodel
parámetros. Asegúrese de verificar si la cantidad de categorías es correcta y la cantidad de paletas es consistente. Nota: incluso si solo hay 1 categoría,metainfo
debe escribirse como'classes': ('cat', ),
Debe haber comas entre paréntesis., de lo contrario se informará de un error.model
tambiénbbox_head
debe ser consistente con el número de especies. -
La escala de la tasa de aprendizaje generalmente sigue una regla general:
base_lr_default * (your_bs / default_bs)
. Del diagrama de estructura anterior, podemos verRTMDet
que hay 4 modelosstages
, ymodel
la configuracióndict(backbone=dict(frozen_stages=4), bbox_head=dict(num_classes=2))
indica que 4 modelos están congeladosstages
, es decir, la red troncal está completamente congelada.
config_animals = """
# Inherit and overwrite part of the config based on this config
_base_ = './rtmdet_l_8xb32-300e_coco.py'
data_root = './data/' # dataset root
train_batch_size_per_gpu = 24
train_num_workers = 4
max_epochs = 50
stage2_num_epochs = 6
base_lr = 0.000375
metainfo = {
'classes': ('cat', 'dog', ),
'palette': [
(252, 215, 99), (153, 197, 252),
]
}
train_dataloader = dict(
batch_size=train_batch_size_per_gpu,
num_workers=train_num_workers,
dataset=dict(
data_root=data_root,
metainfo=metainfo,
data_prefix=dict(img='images/'),
ann_file='annotations/trainval.json'))
val_dataloader = dict(
batch_size=train_batch_size_per_gpu,
num_workers=train_num_workers,
dataset=dict(
data_root=data_root,
metainfo=metainfo,
data_prefix=dict(img='images/'),
ann_file='annotations/trainval.json'))
test_dataloader = val_dataloader
val_evaluator = dict(ann_file=data_root + 'annotations/trainval.json')
test_evaluator = val_evaluator
model = dict(backbone=dict(frozen_stages=4), bbox_head=dict(num_classes=2))
# learning rate
param_scheduler = [
dict(
type='LinearLR',
start_factor=1.0e-5,
by_epoch=False,
begin=0,
end=1000),
dict(
# use cosine lr from 10 to 20 epoch
type='CosineAnnealingLR',
eta_min=base_lr * 0.05,
begin=max_epochs // 2,
end=max_epochs,
T_max=max_epochs // 2,
by_epoch=True,
convert_to_iter_based=True),
]
train_pipeline_stage2 = [
dict(type='LoadImageFromFile', backend_args=None),
dict(type='LoadAnnotations', with_bbox=True),
dict(
type='RandomResize',
scale=(640, 640),
ratio_range=(0.1, 2.0),
keep_ratio=True),
dict(type='RandomCrop', crop_size=(640, 640)),
dict(type='YOLOXHSVRandomAug'),
dict(type='RandomFlip', prob=0.5),
dict(type='Pad', size=(640, 640), pad_val=dict(img=(114, 114, 114))),
dict(type='PackDetInputs')
]
# optimizer
optim_wrapper = dict(
_delete_=True,
type='OptimWrapper',
optimizer=dict(type='AdamW', lr=base_lr, weight_decay=0.05),
paramwise_cfg=dict(
norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True))
default_hooks = dict(
checkpoint=dict(
interval=5,
max_keep_ckpts=2, # only keep latest 2 checkpoints
save_best='auto'
),
logger=dict(type='LoggerHook', interval=20))
custom_hooks = [
dict(
type='PipelineSwitchHook',
switch_epoch=max_epochs - stage2_num_epochs,
switch_pipeline=train_pipeline_stage2)
]
# load COCO pre-trained weight
load_from = './checkpoints/rtmdet_l_8xb32-300e_coco_20220719_112030-5a0be7c4.pth'
train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=max_epochs, val_begin=20, val_interval=1)
randomness = dict(seed=2023, deterministic=True, diff_rank_seed=False)
visualizer = dict(vis_backends=[dict(type='LocalVisBackend'), dict(type='WandbVisBackend')])
"""
with open('./configs/rtmdet/rtmdet_l_1xb4-100e_animals.py', 'w') as f:
f.write(config_animals)
entrenamiento modelo
- Después de hacer el trabajo anterior, puede comenzar el entrenamiento del modelo.
!python tools/train.py configs/rtmdet/rtmdet_l_1xb4-100e_animals.py
- modelo
epoch = 50
-precisión de tiempo
Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.952
Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 1.000
Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.995
Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.800
Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.919
Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.959
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.964
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.965
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.965
Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.800
Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.939
Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.970
07/10 07:35:26 - mmengine - INFO - bbox_mAP_copypaste: 0.952 1.000 0.995 0.800 0.919 0.959
07/10 07:35:27 - mmengine - INFO - Epoch(val) [50][123/123] coco/bbox_mAP: 0.9520 coco/bbox_mAP_50: 1.0000 coco/bbox_mAP_75: 0.9950 coco/bbox_mAP_s: 0.8000 coco/bbox_mAP_m: 0.9190 coco/bbox_mAP_l: 0.9590 data_time: 0.0532 time: 0.8068
- Podemos abrir
wandb
la plataforma, rastrear la precisión del entrenamiento y visualizar varios indicadores
modelo de razonamiento
- Después de afinar el modelo, podemos razonar sobre la imagen
from mmdet.apis import DetInferencer
import glob
config = 'configs/rtmdet/rtmdet_l_1xb4-100e_animals.py'
checkpoint = glob.glob('./work_dirs/rtmdet_l_1xb4-100e_animals/best_coco*.pth')[0]
device = 'cuda:0'
inferencer = DetInferencer(config, checkpoint, device)
img = './data/images/Cats_Test1011.png'
result = inferencer(img, out_dir='./output', pred_score_thr=0.6)
display.clear_output()
Image.open('./output/vis/Cats_Test1011.png')
img = './data/images/Cats_Test1035.png'
result = inferencer(img, out_dir='./output', pred_score_thr=0.6)
display.clear_output()
Image.open('./output/vis/Cats_Test1035.png')