写在前面:本人正在学习MMDetection3D的过程中,可能有理解错误,欢迎指正。
参考视频链接:4小时入门深度学习+实操MMDetection 第二课
官方中文文档:MMDetection 文档
在官方github上下载所需模型(预训练模型参数文件/pth文件)及其配置文件(后面会讲配置文件的内容/py文件)。
然后使用下列代码进行单张图片的推断:
from mmdet import init_detector, inference_detector, show_result_pyplot
config_file = "xxxx.py" # 刚才下载的配置文件路径
checkpoint_file = "xxxx.pth" # 刚才下载的模型文件路径
model = init_detector(config_file, checkpoint_file) # 初始化模型并从checkpoint加载模型
result = inference_detector(model, "demo.jpg") # 得到检测结果
show_result_pyplot(model, "demo.jpg", result) # 可视化检测结果
官方教程:教程 1: 学习配置文件 — MMDetection 2.25.1 文档
配置文件定义了完整的训练过程,通常包含多个字段,重要的有
model字段:定义模型(包含损失函数、训练和测试时的设置等)
data字段:定义数据(包含预处理)
optimizer、lr_config等字段:定义训练策略
load_from字段:定义预训练模型的参数文件
下面以yolov3_mobilenetv2_mstrain-416_300e_coco.py为例进行简单介绍(暂不深究,目前需要注意的地方见注释):
_base_ = '../_base_/default_runtime.py'
# 表明该配置文件继承自配置文件default_runtime.py(后面会讲到继承)
# 暂时可以理解为等价于将default_runtime.py中的内容复制到这里
# model settings
model = dict( # model字段
type='YOLOV3',
backbone=dict( # 主干网络
type='MobileNetV2',
out_indices=(2, 4, 6),
act_cfg=dict(type='LeakyReLU', negative_slope=0.1),
init_cfg=dict(
type='Pretrained', checkpoint='open-mmlab://mmdet/mobilenet_v2')),
neck=dict( # 颈部网络
type='YOLOV3Neck',
num_scales=3,
in_channels=[320, 96, 32],
out_channels=[96, 96, 96]),
bbox_head=dict( # 检测头
type='YOLOV3Head',
num_classes=80, # 分类的类别数
in_channels=[96, 96, 96],
out_channels=[96, 96, 96],
anchor_generator=dict(
type='YOLOAnchorGenerator',
base_sizes=[[(116, 90), (156, 198), (373, 326)],
[(30, 61), (62, 45), (59, 119)],
[(10, 13), (16, 30), (33, 23)]],
strides=[32, 16, 8]),
bbox_coder=dict(type='YOLOBBoxCoder'),
featmap_strides=[32, 16, 8],
loss_cls=dict( # 分类损失
type='CrossEntropyLoss', # 损失类型:交叉熵
use_sigmoid=True,
loss_weight=1.0,
reduction='sum'),
loss_conf=dict( # 置信度损失(YOLO特有的损失)
type='CrossEntropyLoss', # 损失类型:交叉熵
use_sigmoid=True,
loss_weight=1.0,
reduction='sum'),
loss_xy=dict( # 位置分类损失
type='CrossEntropyLoss', # 损失类型:交叉熵
use_sigmoid=True,
loss_weight=2.0,
reduction='sum'),
loss_wh=dict( # 长宽回归损失
type='MSELoss', # 损失类型:MSE
loss_weight=2.0,
reduction='sum')),
# training and testing settings
train_cfg=dict( # 训练配置
assigner=dict(
type='GridAssigner',
pos_iou_thr=0.5, # 正锚框的IoU阈值设置
neg_iou_thr=0.5, # 负锚框的IoU阈值设置
min_pos_iou=0)),
test_cfg=dict( # 测试配置
nms_pre=1000,
min_bbox_size=0,
score_thr=0.05,
conf_thr=0.005,
nms=dict( # NMS设置
type='nms',
iou_threshold=0.45), # NMS中的IoU阈值设置
max_per_img=100))
# dataset settings
dataset_type = 'CocoDataset'
data_root = 'data/coco/'
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
train_pipeline = [ # 训练数据读取和预处理
dict(type='LoadImageFromFile'), # 读取图像
dict(type='LoadAnnotations', with_bbox=True), # 读取标注
dict( # 下面都是图像预处理及数据增广等操作
type='Expand',
mean=img_norm_cfg['mean'],
to_rgb=img_norm_cfg['to_rgb'],
ratio_range=(1, 2)),
dict(
type='MinIoURandomCrop',
min_ious=(0.4, 0.5, 0.6, 0.7, 0.8, 0.9),
min_crop_size=0.3),
dict(
type='Resize',
img_scale=[(320, 320), (416, 416)],
multiscale_mode='range',
keep_ratio=True),
dict(type='RandomFlip', flip_ratio=0.5),
dict(type='PhotoMetricDistortion'),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(type='DefaultFormatBundle'),
dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels'])
]
test_pipeline = [ # 测试数据读取和预处理
dict(type='LoadImageFromFile'),
dict(
type='MultiScaleFlipAug',
img_scale=(416, 416),
flip=False,
transforms=[
dict(type='Resize', keep_ratio=True),
dict(type='RandomFlip'),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(type='DefaultFormatBundle'),
dict(type='Collect', keys=['img'])
])
]
data = dict( # data字段
samples_per_gpu=24, # batch size
workers_per_gpu=4,
train=dict( # 训练集
type='RepeatDataset', # use RepeatDataset to speed up training
times=10,
dataset=dict(
type=dataset_type,
ann_file=data_root + 'annotations/instances_train2017.json',
# 训练集标注文件路径
img_prefix=data_root + 'train2017/', # 训练集图像所在文件夹路径
pipeline=train_pipeline)),
val=dict( # 验证集
type=dataset_type,
ann_file=data_root + 'annotations/instances_val2017.json',
img_prefix=data_root + 'val2017/',
pipeline=test_pipeline),
test=dict( # 测试集
type=dataset_type,
ann_file=data_root + 'annotations/instances_val2017.json',
img_prefix=data_root + 'val2017/',
pipeline=test_pipeline))
# optimizer
optimizer = dict( # 优化器设置
type='SGD', # 优化器类型
lr=0.003, # 学习率
momentum=0.9,
weight_decay=0.0005)
optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))
# learning policy
lr_config = dict( # 学习率变化策略设置
policy='step',
warmup='linear',
warmup_iters=4000,
warmup_ratio=0.0001,
step=[24, 28])
# runtime settings
runner = dict(
type='EpochBasedRunner',
max_epochs=30) # 训练的总epoch数
evaluation = dict(
interval=1,
metric=['bbox'])
find_unused_parameters = True
# NOTE: `auto_scale_lr` is for automatically scaling LR,
# USER SHOULD NOT CHANGE ITS VALUES.
# base_batch_size = (8 GPUs) x (24 samples per GPU)
auto_scale_lr = dict(base_batch_size=192)
官方教程:教程 2: 自定义数据集 — MMDetection 2.25.1 文档
以目标检测任务为例,若要使用自己的数据集,在预训练模型上进行微调,则步骤如下:
1.下载基础模型的参数和配置文件
2.将自己的数据集整理为MMDetection支持的格式
一个方法是使自己的数据集与已支持的数据集有相同的组织方式(如标注结构与含义等)。
例如,要将自己的图像数据集组织为Coco数据集的形式,首先观察Coco数据集标注文件(json文件)格式:
{
"info" : info, # 数据集相关,可忽略
"images" : [image], # 图像列表
"annotations" : [annotation], # 标注列表
"license" : [license], # 数据集相关,可忽略
"categories" : [categories], # 类别名称列表
}
其中
image {
"id" : int,
"width" : int,
"height" : int,
"file_name" : str,
"license" : int,
"flickr_url" : str,
"coco_url" : str,
"date_captured" : datetime,
}
annotation {
"id" : int,
"image_id" : int,
"category_id" : int,
"segmentation" : RLE or [polygon],
"area" : float,
"bbox" : [x,y,width,height], # x,y为边界框左上角到图像左上角的距离
"iscrowd" : 0 or 1,
}
categories [{
"id" : int,
"name" : str,
"supercategory" : str,
}]
按照上述格式建立自己数据集的标注文件即可。
将自定义数据集转换为预训练时所用数据集的形式后,还需要在配置文件的data字段中修改相应的图像文件路径和标注文件路径(见下一步)。
3.修改配置文件(数据路径、分类头、预训练模型加载、优化器配置等)
配置文件的修改可通过继承的方式(当然,也可直接复制预训练模型的配置文件后修改)。
例如新建微调模型的配置文件(如new_model_cfg.py),想在预训练模型的配置文件上修改时,可先写上如下语句表明该配置文件是在原配置文件上进行修改的:
_base_ = ['yolov3_mobilenetv2_mstrain-416_300e_coco.py'] # 预训练模型的配置文件路径
然后修改数据路径只需要找到对应项进行修改(下列代码中未出现的项不变):
data = dict(
train = dict(
dataset = dict(
ann_file = 'xxx', # 标注文件路径
img = 'xxx', # 图像路径
classes = ("xx", ...)) # 类别名称(新增项)
),
val = dict(...), # 类似修改验证集和测试集
test = dict(...)
)
分类头的修改也类似,只需修改类别数:
model = dict(bbox_head(num_classes = xx))
训练配置的修改同理:
runner = dict(max_epochs=xx)
optimizer = dict(lr=xx)
lr_config = None
最后加上load_from字段以使模型从预训练模型开始进行微调:
load_from = "yolov3_mobilenetv2_mstrain-416_300e_coco_20210718_010823-f68a07b3.pth"
# 预训练模型参数文件路径
输出信息的频率设置如下(原始模型的配置文件中该部分继承自default_runtime.py):
log_config = dict(interval=xx) # 每xx迭代次数输出一次信息