MMDetection微调RTMDet模型针对实例分割任务
使用MMDetection微调RTMDet模型,使其用于实例分割任务,并在Motorcycle Night Ride数据集上进行测试,对训练过程中可能出现的一些问题进行了说明,并给出了解决方案
·
前言
- 在以前的博客中有介绍
MMdetection
框架除了适用于目标检测任务外,还可以做实例分割任务 - 但
MMdetection
框架的官方项目中关于实例分割任务的教程文件因为框架版本更新的问题,实际运行过程中会报错cannot import name 'build_dataset' from 'mmdet.datasets'
,所以本文主要是对新版本的框架做实例分割的教程 - 因为官方的
balloon
数据集数据量太小,这里使用kaggle
平台上的Motorcycle Night Ride
数据集,数据地址 - 下面所有代码均在
kaggle
平台,GPU
为P100
环境下运行。
环境配置
- 有关于这一部分的详细说明,请看博客MMDetection框架训练、测试全流程中环境配置部分,这里就不再赘述了
import IPython.display as display
!pip install openmim
!mim install mmengine==0.7.2
!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
!mkdir ./data
%pip install -e .
!pip install wandb
display.clear_output()
- 由于我们使用的是
wandb
平台对训练过程进行可视化,所以还需要先登录wandb
平台。
import wandb
wandb.login()
预训练模型推理
- 我们先下载需要微调的
RTMDet-l
模型权重,然后在测试图片上进行推理,顺便也可以检查环境是否齐全。 - 关于模型型号,可以在项目文件
configs/rtmdet
下找到。但要注意的是,在Readme.md
文档中有两个表格分别是Object Detection
和Instance Segmentation
,由于是实例分割任务,我们应该在`Instance Segmentation``表格中查找模型型号
!mkdir ./checkpoints
!mim download mmdet --config rtmdet-ins_l_8xb32-300e_coco --dest ./checkpoints
- 模型在测试图片上的推理
import mmcv
import mmengine
from mmdet.apis import init_detector, inference_detector
from mmdet.utils import register_all_modules
config_file = 'configs/rtmdet/rtmdet-ins_l_8xb32-300e_coco.py'
checkpoint_file = 'checkpoints/rtmdet-ins_l_8xb32-300e_coco_20221124_103237-78d1d652.pth'
register_all_modules()
model = init_detector(config_file, checkpoint_file, device='cuda:0')
image = mmcv.imread('demo/demo.jpg',channel_order='rgb')
result = inference_detector(model, image)
from mmdet.registry import VISUALIZERS
visualizer = VISUALIZERS.build(model.cfg.visualizer)
visualizer.dataset_meta = model.dataset_meta
visualizer.add_datasample('result',image,data_sample=result,draw_gt = None,wait_time=0,)
display.clear_output()
visualizer.show()
数据探索与可视化
- 由于数据集名字太长,这里将图片文件夹和标注文件复制到项目文件夹中的
data
子文件夹中
import os
import shutil
def copy_files(src_folder, dest_folder):
# 确保目标文件夹存在
os.makedirs(dest_folder, exist_ok=True)
# 遍历源文件夹中的所有内容
for root, _, files in os.walk(src_folder):
for file in files:
# 拼接源文件的完整路径
src_file_path = os.path.join(root, file)
# 拼接目标文件的完整路径
dest_file_path = os.path.join(dest_folder, os.path.relpath(src_file_path, src_folder))
# 确保目标文件的文件夹存在
os.makedirs(os.path.dirname(dest_file_path), exist_ok=True)
# 复制文件
shutil.copy(src_file_path, dest_file_path)
source_folder = '/kaggle/input/motorcycle-night-ride-semantic-segmentation/www.acmeai.tech ODataset 1 - Motorcycle Night Ride Dataset'
destination_folder = './data'
copy_files(source_folder, destination_folder)
- 数据可视化,由于数据集中已经提供了可视化后的图,我们将原图和标注后的图放在一起对比
import mmcv
import matplotlib.pyplot as plt
img_og = mmcv.imread('data/images/Screenshot (446).png')
img_fuse = mmcv.imread('data/images/Screenshot (446).png___fuse.png')
fig, axes = plt.subplots(1, 2, figsize=(15, 10))
axes[0].imshow(mmcv.bgr2rgb(img_og))
axes[0].set_title('Original Image')
axes[0].axis('off')
axes[1].imshow(mmcv.bgr2rgb(img_fuse))
axes[1].set_title('mask Image')
axes[1].axis('off')
plt.show()
- 使用
pycocotools
库读取标注文件,将类别信息输出
from pycocotools.coco import COCO
# 初始化COCO对象
coco = COCO('data/COCO_motorcycle (pixel).json')
# 获取所有的类别标签和对应的类别ID
categories = coco.loadCats(coco.getCatIds())
category_id_to_name = {cat['id']: cat['name'] for cat in categories}
display.clear_output()
# 打印所有类别ID和对应的类别名称
for category_id, category_name in category_id_to_name.items():
print(f"Category ID: {category_id}, Category Name: {category_name}")
- 输出:
Category ID: 1329681, Category Name: Rider
Category ID: 1323885, Category Name: My bike
Category ID: 1323884, Category Name: Moveable
Category ID: 1323882, Category Name: Lane Mark
Category ID: 1323881, Category Name: Road
Category ID: 1323880, Category Name: Undrivable
- 可以看到类别一共有6种,分别为:Rider, My bike, Moveable, Lane Mark, Road, Undrivable
修改配置文件
- 有关配置文件的详细解释,我在博客MMDetection框架训练、测试全流程,利用MMSegmentation微调Mask2Former模型,MMYOLO框架标注、训练、测试全流程(补充篇)都有很详细的说明,这里也不多讲了
- 主要修改的是预训练权重路径,图片路径,标注文件路径,
batch_size
,epochs
,学习率缩放,类别数量,多卡变单卡(SyncBN --> BN),类别标签与调色板。 - 要非常注意类别数量和类别标签与调色板这两个参数,否则会报错
class EpochBasedTrainLoop in mmengine/runner/loops.py: class CocoDataset in mmdet/datasets/coco.py: need at least one array to concatenate
,这类错误是非常非常常见的,大家一定要检查类别数和标签信息是否正确
from mmengine import Config
cfg = Config.fromfile('./configs/rtmdet/rtmdet-ins_l_8xb32-300e_coco.py')
from mmengine.runner import set_random_seed
cfg.load_from = 'checkpoints/rtmdet-ins_l_8xb32-300e_coco_20221124_103237-78d1d652.pth'
cfg.work_dir = './work_dir'
cfg.max_epochs = 100
cfg.stage2_num_epochs = 7
cfg.train_dataloader.batch_size = 4
cfg.train_dataloader.num_workers = 2
scale_factor = cfg.train_dataloader.batch_size / (8 * 32)
cfg.base_lr *= scale_factor
cfg.optim_wrapper.optimizer.lr = cfg.base_lr
# cfg.model.backbone.frozen_stages = 4
cfg.model.bbox_head.num_classes = 6
# 单卡训练时,需要把 SyncBN 改成 BN
cfg.norm_cfg = dict(type='BN', requires_grad=True)
cfg.metainfo = {
'classes': ('Rider', 'My bike', 'Moveable', 'Lane Mark', 'Road', 'Undrivable', ),
'palette': [
(141, 211, 197),(255, 255, 179),(190, 186, 219),(245, 132, 109),(127, 179, 209),(251, 180, 97),
]
}
cfg.data_root = './data'
cfg.train_dataloader.dataset.ann_file = 'COCO_motorcycle (pixel).json'
cfg.train_dataloader.dataset.data_root = cfg.data_root
cfg.train_dataloader.dataset.data_prefix.img = 'images/'
cfg.train_dataloader.dataset.metainfo = cfg.metainfo
cfg.val_dataloader.dataset.ann_file = 'COCO_motorcycle (pixel).json'
cfg.val_dataloader.dataset.data_root = cfg.data_root
cfg.val_dataloader.dataset.data_prefix.img = 'images/'
cfg.val_dataloader.dataset.metainfo = cfg.metainfo
cfg.test_dataloader = cfg.val_dataloader
cfg.val_evaluator.ann_file = cfg.data_root+'/'+'COCO_motorcycle (pixel).json'
cfg.val_evaluator.metric = ['segm']
cfg.test_evaluator = cfg.val_evaluator
cfg.default_hooks.checkpoint = dict(type='CheckpointHook', interval=10, max_keep_ckpts=2, save_best='auto')
cfg.default_hooks.logger.interval = 20
cfg.custom_hooks[1].switch_epoch = 300 - cfg.stage2_num_epochs
cfg.train_cfg.max_epochs = cfg.max_epochs
cfg.train_cfg.val_begin = 20
cfg.train_cfg.val_interval = 2
cfg.train_cfg.dynamic_intervals = [(300 - cfg.stage2_num_epochs, 1)]
# cfg.train_dataloader.dataset = dict(dict(type='RepeatDataset',times=5,dataset = cfg.train_dataloader.dataset))
cfg.param_scheduler[0].end = 100
cfg.param_scheduler[1].eta_min = cfg.base_lr * 0.05
cfg.param_scheduler[1].begin = cfg.max_epochs // 2
cfg.param_scheduler[1].end = cfg.max_epochs
cfg.param_scheduler[1].T_max = cfg.max_epochs //2
set_random_seed(0, deterministic=False)
cfg.visualizer.vis_backends.append({"type":'WandbVisBackend'})
#------------------------------------------------------
config=f'./configs/rtmdet/rtmdet-ins_l_1xb4-100e_motorcycle.py'
with open(config, 'w') as f:
f.write(cfg.pretty_text)
- 开始训练
!python tools/train.py {config}
- 展示模型效果最好的指标数值
Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.561
Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.758
Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.614
Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.017
Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.195
Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.633
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.543
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.645
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.649
Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.036
Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.246
Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.721
07/23 19:31:52 - mmengine - INFO - segm_mAP_copypaste: 0.561 0.758 0.614 0.017 0.195 0.633
07/23 19:31:52 - mmengine - INFO - Epoch(val) [98][40/40] coco/segm_mAP: 0.5610 coco/segm_mAP_50: 0.7580 coco/segm_mAP_75: 0.6140 coco/segm_mAP_s: 0.0170 coco/segm_mAP_m: 0.1950 coco/segm_mAP_l: 0.6330 data_time: 0.0491 time: 3.1246
可视化训练过程
- 我们可以登录
wandb
平台查看训练过程中的指标变化
- 可以看到
segm_mAP
值是还在上升的状态,因为时间关系,我只跑了100个epoch
,如果大家自己尝试可以跑300试试,估计效果会更好。
在测试图片上推理
- 在训练完成后,我们加载表现最好的模型,并对测试图片进行推理
from mmengine.visualization import Visualizer
import mmcv
from mmdet.apis import init_detector, inference_detector
import glob
img = mmcv.imread('data/images/Screenshot (446).png',channel_order='rgb')
checkpoint_file = glob.glob('./work_dir/best_coco_segm_mAP*.pth')[0]
model = init_detector(cfg, checkpoint_file, device='cuda:0')
new_result = inference_detector(model, img)
visualizer_now = Visualizer.get_current_instance()
visualizer_now.dataset_meta = model.dataset_meta
visualizer_now.add_datasample('new_result', img, data_sample=new_result, draw_gt=False, wait_time=0, out_file=None, pred_score_thr=0.5)
visualizer_now.show()
常见问题排查
- 官方写了一个文档,用于一些非常常见的问题排查,我将地址放置在此处
- 我在运行的过程中遇到的最频繁的错误就是
valueerror-need-at-least-one-array-to-concatenate
,上面的官方问题排查手册中也进行了说明。要检查种类数和标签及调色板,但是我检查以后发现没有上述问题,最后发现还有一个可能会引发此错误的因素 - 由于框架的更新,标签及调色板的写法发生了改变,错误的写法是:
cfg.metainfo = {
'CLASSES': ('Rider', 'My bike', 'Moveable', 'Lane Mark', 'Road', 'Undrivable', ),
'PALETTE': [
(141, 211, 197),(255, 255, 179),(190, 186, 219),(245, 132, 109),(127, 179, 209),(251, 180, 97),
]
}
- 大写的
CLASSES
和PALETTE
在新版本中已经不适用了,要换成小写,就不会保存了
cfg.metainfo = {
'classes': ('Rider', 'My bike', 'Moveable', 'Lane Mark', 'Road', 'Undrivable', ),
'palette': [
(141, 211, 197),(255, 255, 179),(190, 186, 219),(245, 132, 109),(127, 179, 209),(251, 180, 97),
]
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
已为社区贡献16条内容
所有评论(0)