0%

目标检测篇:MMDetection 推理使用与详细踩坑记录

最近喜欢上了听音乐,B 站关注了个 UP 主叫『咻咻满』,长得好看,戏腔唱『青花瓷』入坑了,也听了其它的『星辰大海』和『白月光和朱砂痣』,都挺好听。以后得关注点女 UP 了,看着多可爱,生活又不是只有代码。卧艹不对说回正题。

MMDetection 是一个基于 PyTorch 的目标检测开源工具箱 1,支持了众多主流的和最新的检测算法,例如 Faster R-CNN,Mask R-CNN,RetinaNet 等,官网也给出了详细的教程。既然如此,生命不息,开坑不止。前前后后被各种事情打断,大概花了一周搞懂了如何使用 MMdetection 去做检测的任务。本文收录:

  • 安装
  • 修改配置文件
  • 调用模型与训练好的参数,进行推理
  • 自定义训练
  • 数据处理流程

注意,本文更多像是记录学习过程,这也是我第一次用这个工具,遇到问题一步一步的 debug 与记录,并不是直接的教程。且,本文程序大多能直接复制运行,需要对应到自己的路径。我希望读者看完本文后具有解决问题的能力,而不只是会解决问题。

安装

MMDetection 检测框架包括 mmdetection 和 mmcv,两者是不可分割的。首先安装 mmcv,掏出官方文档 2,发现有两个版本可供安装,支持 CUDA 操作的 mmcv-full 和不支持 CUDA 的 mmcv,而图片处理显然需要 GPU。所以知道了要安装的是 mmcv-full,之后就是确定安装版本。

首先查看自己的 CUDApytorch 版本,然后查阅官方提供的表格,找到对应的安装命令。综上,我本地服务器的安装指令就是(我现在写代码都默认 Linux 系统了,本教程不知道适不适合 windows)

1
pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu92/torch1.7.0/index.html

而后安装 mmdetection,继续掏出官方文档 3,按着步骤一步一步来:

1
2
3
4
git clone https://github.com/open-mmlab/mmdetection.git
cd mmdetection
pip install -r requirements/build.txt
pip install -v -e .

也就是安装完成后,当前目录会保留一个 mmdetection 的文件夹,一些模型的配置文件都在里面,不要删除这个文件夹。在安装完成后,执行一段代码查看是否安装成功

1
from mmdet.apis import init_detector, inference_detector

简单推理

在网上查阅相关用法时,发现绝大多数教程已经过时,软件迭代重构、接口更新很正常。即使某一天本文被骂陈旧过时,我也不会感觉到任何意外。接口问题多查阅官方文档,其余问题可以多用谷歌搜索。但官方文档 4 写的实在是烂,甚至连返回类型都没写,是 tensor,还是 list。不仅没写类型,输出是什么也没写,是得分,是窗口还是类别,一个字都不肯多说,以上结论仅限博客发布的日期。所以只能自己一个一个打印了,然后自己分析了下,结果的形式是多个列表,因为有多个目标。每个列表的形式是盒子 xmin, ymin, xmax, ymax 以及得分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from mmdet.apis import init_detector, inference_detector

# 目标检测配置文件
config_file = 'mmdetection/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py'
# 训练模型
checkpoint_file = 'mmdetection/checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth'

# 配置模型
model = init_detector(config=config_file,
checkpoint=checkpoint_file,
device='cuda:0')

img = 'woman-3377839_1920.jpg'
# 推理实际调用语句
# results = model(return_loss=False, rescale=True, **data)
result = inference_detector(model=model, imgs=img)

# 打印结果
for i in result:
print(i)

在我打印 result 后,发现了一个震惊的消息,只有盒子、概率,没有类别,也就时说代码只能预测当前目标在哪,目标的概率,但不能预测目标是什么 5,没有类别输出。来看一下官方的回答。我当时属实没看懂,数据集有类别信息?既然有了类别信息还推理干啥…

没办法接着去翻 github,自己提了个问 6。才知道 result 的长度就是类别长度,而 print(model.CLASSES) 可以打印类别。比如 model.ClASSES 的长度是 80,那么 result 的长度也就是 80,result[0] 就对应 model.CLASSES 的第一个类别,表示第一个类别的盒子、概率。这样一切就解释通了。

  • 如果一个图片里只有一个目标,那么 result 类别索引对应的元素中,只有一组数据。
  • 如果一个图片里有多个目标,那么 result 类别索引对应的元素中,就有多组数据。

因为我的服务器没有 GUI,而官方画图程序需要调用 matplotlib 和 tkiner,所以无法显示图片,只能读取结果信息,自己用 PIL 画一下了。

1
2
3
4
5
6
7
8
9
10
from PIL import Image, ImageDraw
# 打开原图
img = Image.open('Hippopx.jpg').convert('RGB')
# 画出目标框,因为一个类别可能对应多个目标
for rec in result[0]:
x, y, w, h = rec[0], rec[1], rec[2], rec[3]
draw = ImageDraw.Draw(img)
draw.rectangle((x, y, w, h), width=2, outline='#41fc59')
# 保存结果图片
img.save('result.png')

一个类对应一个目标: 一个类有多个目标:

自定义推理

准备数据

将数据的软链接挂到 mmdetection 文件夹下,目录结构如下。 ln -s minidata /mmdetection/data/,创建软链时需要注意,使用绝对路径。因为之后会沿着软链访问数据,相对路径可能找不到数据。此时的目录结构如下:

1
2
3
4
5
6
7
8
mmdetection
├── mmdet
├── tools
├── configs
├── data
│ ├── minidata
│ │ ├── annotations/
│ │ ├── test_data/

annotations 是对图片信息的说明,test_data 就是要推理的图片。其实这里的结构也不太重要,重点是后面的配置文件要把路径给指对了。而后下载预训练的模型,对目标数据集进行推理。模型都可以在官方文档的 Model Zoo 中下载到。

修改配置文件(不建议)

因为源代码中指定的配置文件是 configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py,而打开这个文件,我们会发现以下几行信息:

1
2
3
4
5
_base_ = [
'../_base_/models/faster_rcnn_r50_fpn.py',
'../_base_/datasets/coco_detection.py',
'../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py'
]

也就是,配置文件是继承自 _base_ 文件夹下面的模型、数据、学习率、运行时设置这四个文件。所以要按照自己想要的方式运行,就需要修改这四个配置文件。

比如以数据为例,因为目标数据集位于 data/mini_data 下,所以要修改配置文件,使配置文件找到正确的数据路径。打开 configs/_base_/datasets/coco_detection.py,修改 data 字典中的 test类别数量修改同理,在models文件夹下。

1
2
3
4
5
6
7
test=dict(
type=dataset_type,
# 找到自己的 json
ann_file=data_root + 'annotations/openbrand_train.json',
# 找到自己的图片路径
img_prefix=data_root + 'test_data/',
pipeline=test_pipeline)
  • 而后在 mmdetection/mmdet/core/evaluation/classes_names 中的 coco_classes 函数中修改类别名称信息。(这个我没找到如何在配置文件中修改,只能在源文件中修改了)
  • 也在mmdetection/mmdet/datasets/coco.py 中的 class CocoDataset(CustomDataset): 类中的 CLASSES 字段修改为自己需要的类别信息。

开始推理

而最后执行推理的代码如下:

1
2
3
4
5
python tools/test.py \
${CONFIG_FILE} \
${CHECKPOINT_FILE} \
[--out ${RESULT_FILE}] \
[--eval ${EVAL_METRICS}] \
  • RESULT_FILE 结果序列化输出到指定文件中,必须是 .pkl 文件
  • EVAL_METRICS 结果要评估的项目,结果会打印到屏幕,对于 COCO 数据接而言,有 proposal, bbox, segm 等。但是需要注意的是,faster rcnn 没有 segm,mask rcnn 才有。

比如:

1
2
3
4
5
6
7
8
9
10
# mmdetection 文件夹下执行
python tools/test.py \
# 配置文件
configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py \
# 模型
checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth \
# 输出
--out /mmnet/out.pkl \
--eval bbox proposal \
--show-score-thr 0.5 # 概率低于 0.5 的预测结果都要被删除

打开 test.py,一步步追踪源码,发现推理时调用的东西和 inference_detector 接口调用的一样,所以推理阶段不输出类别,类别需要自己从 model.CLASSES 中获取。

1
2
with torch.no_grad():
result = model(return_loss=False, rescale=True, **data)

因为最后输出的结果保存到了 out.pkl 文件中,所以给一份精简代码,关于如何读取序列化数据。其实跟 json 的读取挺像的。

1
2
3
4
5
import pickle
data = None
with open('out.pkl', 'rb') as f:
data = pickle.load(f)
process(data)

推理结束后可以删除数据的软链,以备下次使用。删除软件时需要注意,可能一不小心删除原始数据。正确的删除软链方式是 rm minidata,注意不要加斜杠。

理论上应该是按着上面描述的那样执行,但其实我这里报错了:KeyError: "CocoDataset: 'categories'"。定位到报错代码,发现是因为使用 pycocotools 读取 json 文件时,没有读到分类这个属性。所以这里需要注意的是,推理要用的 json,自己要添加一下类别的信息。

通过配置文件推理

当我本能的以为又要修改源代码时,我意识到一个问题。以任何学过『设计模式』而言的人来说,初衷绝对不是让用户去修改源代码,每次修改来修改去,程序到最后可能都没法用了。而是应该通过添加额外配置文件,来达到用户想要的目的。对修改封闭,对扩展开放。所以以上修改我又还原了,决定自己写脚本进行推理。

自定义脚本推理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import json
from mmdet.apis import init_detector, inference_detector
import os
from pycocotools.coco import COCO
from tqdm import tqdm
import numpy as np

# 提交要求
# [{
# "image_id": int,
# "category_id": int,
# "bbox": [x_min,y_min,width,height],
# "score": float,
# }]

anno_path = 'test.json'
coco = COCO(anno_path)
ids = list(coco.imgs.keys())

filename_id = {}

# 获取文件名与对应的 ID
# 如 {'000000.jpg': 12}
for idx in tqdm(range(len(ids))):
img_id = ids[idx]
img_ids = coco.getImgIds(imgIds=img_id)
image = coco.loadImgs(img_ids)
for i in image:
filename_id[i['file_name']] = i['id']

# 目标检测配置文件
config_file = 'mmnet/mmdetection/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py'
# 训练模型
checkpoint_file = 'mmnet/mmdetection/checkpoints/faster_rcnn_r50_fpn_1x.pth'

# 配置模型
model = init_detector(config=config_file,
checkpoint=checkpoint_file,
device='cuda:0')

# 需要推理的图片的路径
root = 'val_data/'

# 存储结果,并生成 json
results = []

# 开始推理
for file in tqdm(os.listdir(root)):
result = inference_detector(model=model, imgs=root + file)
for cate, items in enumerate(result, 1):
# 同一类别的有很多结果
for item in items:
item = item.tolist()
x, y, w, h, s = item[0], item[1], item[2], item[3], item[4]
d = {}
d['image_id'] = filename_id[file]
d['category_id'] = cate
d['bbox'] = [x, y, w, h]
d['score'] = s
results.append(d)

# 保存
with open('result.json', 'w') as f:
json.dump(results, f, indent=4)

配置文件推理

之前说过,配置文件继承自以下四个文件:

1
2
3
4
5
_base_ = [
'../_base_/models/faster_rcnn_r50_fpn.py',
'../_base_/datasets/coco_detection.py',
'../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py'
]

所以我们自己定义一个配置文件,称为 mynet.py,位于 mmdetection/configs/faster_rcnn/ 文件夹下,把以上四个文件的内容全部拷贝到 mynet.py 中,并修改自己需要改的地方即可。mynet.py 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
_base_ = [
'../_base_/models/faster_rcnn_r50_fpn.py',
'../_base_/datasets/coco_detection.py',
'../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py'
]


# model settings
model = dict(
type='FasterRCNN',
pretrained='torchvision://resnet50',
backbone=dict(
type='ResNet',
depth=50,
num_stages=4,
out_indices=(0, 1, 2, 3),
frozen_stages=1,
norm_cfg=dict(type='BN', requires_grad=True),
norm_eval=True,
style='pytorch'),
neck=dict(
type='FPN',
in_channels=[256, 512, 1024, 2048],
out_channels=256,
num_outs=5),
rpn_head=dict(
type='RPNHead',
in_channels=256,
feat_channels=256,
anchor_generator=dict(
type='AnchorGenerator',
scales=[8],
ratios=[0.5, 1.0, 2.0],
strides=[4, 8, 16, 32, 64]),
bbox_coder=dict(
type='DeltaXYWHBBoxCoder',
target_means=[.0, .0, .0, .0],
target_stds=[1.0, 1.0, 1.0, 1.0]),
loss_cls=dict(
type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),
loss_bbox=dict(type='L1Loss', loss_weight=1.0)),
roi_head=dict(
type='StandardRoIHead',
bbox_roi_extractor=dict(
type='SingleRoIExtractor',
roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0),
out_channels=256,
featmap_strides=[4, 8, 16, 32]),
bbox_head=dict(
type='Shared2FCBBoxHead',
in_channels=256,
fc_out_channels=1024,
roi_feat_size=7,
# 修改类别
num_classes=515,
bbox_coder=dict(
type='DeltaXYWHBBoxCoder',
target_means=[0., 0., 0., 0.],
target_stds=[0.1, 0.1, 0.2, 0.2]),
reg_class_agnostic=False,
loss_cls=dict(
type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
loss_bbox=dict(type='L1Loss', loss_weight=1.0))),
# model training and testing settings
train_cfg=dict(
rpn=dict(
assigner=dict(
type='MaxIoUAssigner',
pos_iou_thr=0.7,
neg_iou_thr=0.3,
min_pos_iou=0.3,
match_low_quality=True,
ignore_iof_thr=-1),
sampler=dict(
type='RandomSampler',
num=256,
pos_fraction=0.5,
neg_pos_ub=-1,
add_gt_as_proposals=False),
allowed_border=-1,
pos_weight=-1,
debug=False),
rpn_proposal=dict(
nms_pre=2000,
max_per_img=1000,
nms=dict(type='nms', iou_threshold=0.7),
min_bbox_size=0),
rcnn=dict(
assigner=dict(
type='MaxIoUAssigner',
pos_iou_thr=0.5,
neg_iou_thr=0.5,
min_pos_iou=0.5,
match_low_quality=False,
ignore_iof_thr=-1),
sampler=dict(
type='RandomSampler',
num=512,
pos_fraction=0.25,
neg_pos_ub=-1,
add_gt_as_proposals=True),
pos_weight=-1,
debug=False)),
test_cfg=dict(
rpn=dict(
nms_pre=1000,
max_per_img=1000,
nms=dict(type='nms', iou_threshold=0.7),
min_bbox_size=0),
rcnn=dict(
score_thr=0.05,
nms=dict(type='nms', iou_threshold=0.3),
max_per_img=100)
# soft-nms is also supported for rcnn testing
# e.g., nms=dict(type='soft_nms', iou_threshold=0.5, min_score=0.05)
))


# dataset settings
dataset_type = 'CocoDataset'
data_root = 'mmnet/mmdetection/data/ljw/'
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='Resize', img_scale=(1333, 800), keep_ratio=True),
dict(type='RandomFlip', flip_ratio=0.5),
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=(1333, 800),
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='ImageToTensor', keys=['img']),
dict(type='Collect', keys=['img']),
])
]
data = dict(
samples_per_gpu=2,
workers_per_gpu=2,
train=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 + 'test.json',
img_prefix=data_root + 'val_data/',
pipeline=test_pipeline))
evaluation = dict(interval=1, metric='bbox')


# optimizer
optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
optimizer_config = dict(grad_clip=None)
# learning policy
lr_config = dict(
policy='step',
warmup='linear',
warmup_iters=500,
warmup_ratio=0.001,
step=[8, 11])
runner = dict(type='EpochBasedRunner', max_epochs=12)

最终,python tools/test.py configs/faster_rcnn/mynet.py ...... 就可以运行自己的配置文件,完成推理。尽量避免了破坏原有的代码结构。如果想加速执行,就需要多显卡,而 mmdetection 对这方面支持的很好:

1
2
3
4
5
6
7
8
bash tools/dist_test.sh \
configs/faster_rcnn/my_faster_rcnn.py \
checkpoints/faster_rcnn_r50_fpn_1x.pth \
# 4 个 GPU
4 \
--format-only \
# 输出推理得到的 json 文件
--options "jsonfile_prefix=results6"

推理程序

然后,我把一些简单的程序传到 colab 了,可以打开去实际执行一下,虽然也有一些分类的任务,但思想是一样的。

训练

Finetune

这个恐怕是最简单但也是最常用的一种方案,和前文一样,也是基于配置文件、命令行启动的方式进行训练。修改类别同前文。同样以 Faster RCNN 为例,先写好自己的配置文件,执行方式为:

1
2
3
python tools/train.py \
${CONFIG_FILE} \
[optional arguments]

optional arguments 是可选参数,这里需要加上模型的 log 输出文件目录和预加载模型的地址:

  • --work-dir work_dirs/,将日志、模型输出到 work_dirs 文件夹下
  • --load-from model.pth,只加载模型参数,从第 0 个 epoch 开始训练,但是我试了一下,这个参数不太行
  • --resume-from model.pth,加载模型参数与优化器状态,继承上次的 epoch,通常用于恢复意外中断的训练过程

当然如果想在多个 GPU 上训练也是可以的:

1
2
3
4
bash ./tools/dist_train.sh \
${CONFIG_FILE} \
${GPU_NUM} \
[optional arguments]

配置文件训练

官方文档给出了两种修改配置的方案 7

  • 一种是通过 python tools/train.py 时添加 --cfg-options 参数,例如 --cfg-options model.backbone.norm_eval=False 会将模型的 BN 层调为运行时 8 状态。但是这样参数会很多,且很容易出错,个人不建议这么修改。
  • 第二种方案就是自定义配置文件,并继承 _base_,具体操作可以见上文。模型、数据、学习率等都可以轻松设置。官网也给出了 Mask RCNN 的具体配置文件与解释 9。因为官方支持可以只在配置文件中写入修改部分,不修改的部分可以不必声明。而在子文件中修改配置文件时,如果重新定义了自己的变量,一定记得传入,否则默认还是原来的配置。如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 新的修改
test_pipeline = [
dict(type='LoadImageFromFile'),
dict(
type='MultiScaleFlipAug',
img_scale=(1333, 800),
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='ImageToTensor', keys=['img']),
dict(type='Collect', keys=['img']),
])
]
# 传入自己的修改,其它不用写,用默认的
data = dict(
test=dict(pipeline=test_pipeline))

数据处理流程

这个在程序中的字段是 pipeline,也就是数据处理的一个管道。来看默认配置文件中的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

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'),
# 加载 boxes,boxes_ignore,label 等信息
dict(type='LoadAnnotations', with_bbox=True),
# 重新定义图像大小、bbox 等区域的大小也会变换
dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
# 随机翻转,bbox 等内容也会变化
dict(type='RandomFlip', flip_ratio=0.5),
# 数据标准化
dict(type='Normalize', **img_norm_cfg),
# https://github.com/open-mmlab/mmdetection/blob/master/mmdet/datasets/pipelines/transforms.py
# https://mmcv.readthedocs.io/en/latest/api.html
# 填充除数,追踪源代码到 mmcv,意思是,填充后的图像边,是 32 的倍数
dict(type='Pad', size_divisor=32),
# https://github.com/open-mmlab/mmdetection/blob/master/mmdet/datasets/pipelines/formating.py
# 开始批处理。对于图片,处理、转为向量、收集到 batchsize。bbox,label 同理
dict(type='DefaultFormatBundle'),
dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),
]
test_pipeline = [
dict(type='LoadImageFromFile'),
dict(
type='MultiScaleFlipAug',
img_scale=(1333, 800),
# 禁止翻转
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='ImageToTensor', keys=['img']),
dict(type='Collect', keys=['img']),
])
]

而如果想扩展自己的数据处理流程 10 ,先自己定义一个数据处理的类,然后导入进来,加入到 train_pipeline 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from mmdet.datasets import PIPELINES

@PIPELINES.register_module()
class MyTransform:

def __call__(self, results):
results['dummy'] = True
return results

from .my_pipeline import MyTransform

train_pipeline = [
dict(type='LoadImageFromFile'),
dict(type='LoadAnnotations', with_bbox=True),
dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
dict(type='RandomFlip', flip_ratio=0.5),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
# 添加自己的
dict(type='MyTransform'),
dict(type='DefaultFormatBundle'),
dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),
]

而至于自定义模型的 backbone,neck,head 11 、学习率和优化方案 12、自定义损失函数 13 等,官网也给出了详细的解决方案。

references

感谢上学期间打赏我的朋友们。赛博乞讨:我,秦始皇,打钱。

欢迎订阅我的文章