最近喜欢上了听音乐,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,之后就是确定安装版本。
首先查看自己的 CUDA
和 pytorch
版本,然后查阅官方提供的表格,找到对应的安装命令。综上,我本地服务器的安装指令就是(我现在写代码都默认 Linux 系统了,本教程不知道适不适合 windows)
1 | pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu92/torch1.7.0/index.html |
而后安装 mmdetection,继续掏出官方文档 3,按着步骤一步一步来:
1 | git clone https://github.com/open-mmlab/mmdetection.git |
也就是安装完成后,当前目录会保留一个 mmdetection
的文件夹,一些模型的配置文件都在里面,不要删除这个文件夹。在安装完成后,执行一段代码查看是否安装成功
1 | from mmdet.apis import init_detector, inference_detector |
简单推理
在网上查阅相关用法时,发现绝大多数教程已经过时,软件迭代重构、接口更新很正常。即使某一天本文被骂陈旧过时,我也不会感觉到任何意外。接口问题多查阅官方文档,其余问题可以多用谷歌搜索。但官方文档 4 写的实在是烂,甚至连返回类型都没写,是 tensor,还是 list。不仅没写类型,输出是什么也没写,是得分,是窗口还是类别,一个字都不肯多说,以上结论仅限博客发布的日期。所以只能自己一个一个打印了,然后自己分析了下,结果的形式是多个列表,因为有多个目标。每个列表的形式是盒子 xmin, ymin, xmax, ymax
以及得分。
1 | from mmdet.apis import init_detector, inference_detector |
在我打印 result 后,发现了一个震惊的消息,只有盒子、概率,没有类别,也就时说代码只能预测当前目标在哪,目标的概率,但不能预测目标是什么 5,没有类别输出。来看一下官方的回答。我当时属实没看懂,数据集有类别信息?既然有了类别信息还推理干啥…
没办法接着去翻 github,自己提了个问 6。才知道 result
的长度就是类别长度,而 print(model.CLASSES)
可以打印类别。比如 model.ClASSES
的长度是 80,那么 result
的长度也就是 80,result[0]
就对应 model.CLASSES
的第一个类别,表示第一个类别的盒子、概率。这样一切就解释通了。
- 如果一个图片里只有一个目标,那么
result
类别索引对应的元素中,只有一组数据。 - 如果一个图片里有多个目标,那么
result
类别索引对应的元素中,就有多组数据。
因为我的服务器没有 GUI,而官方画图程序需要调用 matplotlib 和 tkiner,所以无法显示图片,只能读取结果信息,自己用 PIL 画一下了。
1 | from PIL import Image, ImageDraw |
一个类对应一个目标: 一个类有多个目标:
自定义推理
准备数据
将数据的软链接挂到 mmdetection
文件夹下,目录结构如下。 ln -s minidata /mmdetection/data/
,创建软链时需要注意,使用绝对路径。因为之后会沿着软链访问数据,相对路径可能找不到数据。此时的目录结构如下:
1 | mmdetection |
annotations
是对图片信息的说明,test_data
就是要推理的图片。其实这里的结构也不太重要,重点是后面的配置文件要把路径给指对了。而后下载预训练的模型,对目标数据集进行推理。模型都可以在官方文档的 Model Zoo
中下载到。
修改配置文件(不建议)
因为源代码中指定的配置文件是 configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py
,而打开这个文件,我们会发现以下几行信息:
1 | _base_ = [ |
也就是,配置文件是继承自 _base_
文件夹下面的模型、数据、学习率、运行时设置这四个文件。所以要按照自己想要的方式运行,就需要修改这四个配置文件。
比如以数据为例,因为目标数据集位于 data/mini_data
下,所以要修改配置文件,使配置文件找到正确的数据路径。打开 configs/_base_/datasets/coco_detection.py
,修改 data
字典中的 test
。类别数量修改同理,在models文件夹下。
1 | test=dict( |
- 而后在
mmdetection/mmdet/core/evaluation/classes_names
中的coco_classes
函数中修改类别名称信息。(这个我没找到如何在配置文件中修改,只能在源文件中修改了) - 也在
mmdetection/mmdet/datasets/coco.py
中的class CocoDataset(CustomDataset):
类中的CLASSES
字段修改为自己需要的类别信息。
开始推理
而最后执行推理的代码如下:
1 | python tools/test.py \ |
RESULT_FILE
结果序列化输出到指定文件中,必须是.pkl
文件EVAL_METRICS
结果要评估的项目,结果会打印到屏幕,对于 COCO 数据接而言,有proposal, bbox, segm
等。但是需要注意的是,faster rcnn 没有 segm,mask rcnn 才有。
比如:
1 | # mmdetection 文件夹下执行 |
打开 test.py
,一步步追踪源码,发现推理时调用的东西和 inference_detector
接口调用的一样,所以推理阶段不输出类别,类别需要自己从 model.CLASSES
中获取。
1 | with torch.no_grad(): |
因为最后输出的结果保存到了 out.pkl
文件中,所以给一份精简代码,关于如何读取序列化数据。其实跟 json 的读取挺像的。
1 | import pickle |
推理结束后可以删除数据的软链,以备下次使用。删除软件时需要注意,可能一不小心删除原始数据。正确的删除软链方式是 rm minidata
,注意不要加斜杠。
理论上应该是按着上面描述的那样执行,但其实我这里报错了:KeyError: "CocoDataset: 'categories'"
。定位到报错代码,发现是因为使用 pycocotools
读取 json 文件时,没有读到分类这个属性。所以这里需要注意的是,推理要用的 json,自己要添加一下类别的信息。
通过配置文件推理
当我本能的以为又要修改源代码时,我意识到一个问题。以任何学过『设计模式』而言的人来说,初衷绝对不是让用户去修改源代码,每次修改来修改去,程序到最后可能都没法用了。而是应该通过添加额外配置文件,来达到用户想要的目的。对修改封闭,对扩展开放。所以以上修改我又还原了,决定自己写脚本进行推理。
自定义脚本推理
1 | import json |
配置文件推理
之前说过,配置文件继承自以下四个文件:
1 | _base_ = [ |
所以我们自己定义一个配置文件,称为 mynet.py
,位于 mmdetection/configs/faster_rcnn/
文件夹下,把以上四个文件的内容全部拷贝到 mynet.py
中,并修改自己需要改的地方即可。mynet.py
文件如下:
1 | _base_ = [ |
最终,python tools/test.py configs/faster_rcnn/mynet.py ......
就可以运行自己的配置文件,完成推理。尽量避免了破坏原有的代码结构。如果想加速执行,就需要多显卡,而 mmdetection
对这方面支持的很好:
1 | bash tools/dist_test.sh \ |
推理程序
然后,我把一些简单的程序传到 colab
了,可以打开去实际执行一下,虽然也有一些分类的任务,但思想是一样的。
训练
Finetune
这个恐怕是最简单但也是最常用的一种方案,和前文一样,也是基于配置文件、命令行启动的方式进行训练。修改类别同前文。同样以 Faster RCNN 为例,先写好自己的配置文件,执行方式为:
1 | python tools/train.py \ |
optional arguments 是可选参数,这里需要加上模型的 log 输出文件目录和预加载模型的地址:
--work-dir work_dirs/
,将日志、模型输出到work_dirs
文件夹下--load-from model.pth
,只加载模型参数,从第 0 个 epoch 开始训练,但是我试了一下,这个参数不太行--resume-from model.pth
,加载模型参数与优化器状态,继承上次的 epoch,通常用于恢复意外中断的训练过程
当然如果想在多个 GPU 上训练也是可以的:
1 | bash ./tools/dist_train.sh \ |
配置文件训练
官方文档给出了两种修改配置的方案 7:
- 一种是通过
python tools/train.py
时添加--cfg-options
参数,例如--cfg-options model.backbone.norm_eval=False
会将模型的 BN 层调为运行时 8 状态。但是这样参数会很多,且很容易出错,个人不建议这么修改。 - 第二种方案就是自定义配置文件,并继承
_base_
,具体操作可以见上文。模型、数据、学习率等都可以轻松设置。官网也给出了 Mask RCNN 的具体配置文件与解释 9。因为官方支持可以只在配置文件中写入修改部分,不修改的部分可以不必声明。而在子文件中修改配置文件时,如果重新定义了自己的变量,一定记得传入,否则默认还是原来的配置。如下:
1 | # 新的修改 |
数据处理流程
这个在程序中的字段是 pipeline
,也就是数据处理的一个管道。来看默认配置文件中的内容:
1 |
|
而如果想扩展自己的数据处理流程 10 ,先自己定义一个数据处理的类,然后导入进来,加入到 train_pipeline
即可。
1 | from mmdet.datasets import PIPELINES |
而至于自定义模型的 backbone,neck,head 11 、学习率和优化方案 12、自定义损失函数 13 等,官网也给出了详细的解决方案。