MMDetection代码课
MMDetection版本变化
MMDetection3.0通过更细粒度的模块解耦,进一步拆解出了数据、模型、评测等抽象,并对这些接口进行了统一设计。通过对MMEngine和MMCV的重新适配,使得MMDetection的速度和精度得到提高。
环境配置
# Check nvcc version
!nvcc -V
# Check GCC version
!gcc --version
# # 安装 mmengine 和 mmcv 依赖
# # 为了防止后续版本变更导致的代码无法运行,我们暂时锁死版本
# !pip install -U "openmim==0.3.7"
# !mim install "mmengine==0.7.1"
# !mim install "mmcv==2.0.0"
# # Install mmdetection
# !rm -rf mmdetection
# # 为了防止后续更新导致的可能无法运行,特意新建了 tutorials 分支
# 之前的课程配置过内容,所以就不重新配置环境了,重新clone一下代码库
!git clone -b tutorials https://github.com/open-mmlab/mmdetection.git
%cd mmdetection
%pip install -e .
打印环境信息
from mmengine.utils import get_git_hash
from mmengine.utils.dl_utils import collect_env as collect_base_env
import mmdet
# 环境信息收集和打印
def collect_env():
"""Collect the information of the running environments."""
env_info = collect_base_env()
env_info['MMDetection'] = f'{mmdet.__version__}+{get_git_hash()[:7]}'
return env_info
if __name__ == '__main__':
for name, val in collect_env().items():
print(f'{name}: {val}')
准备数据集
! rm -rf cat_dataset*
! wget https://download.openmmlab.com/mmyolo/data/cat_dataset.zip
! unzip cat_dataset.zip -d cat_dataset && rm cat_dataset.zip
下载好的数据集已经是COCO格式的了,可以对数据进行可视化
import os
import matplotlib.pyplot as plt
from PIL import Image
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
original_images = []
images = []
texts = []
plt.figure(figsize=(16, 5))
image_paths= [filename for filename in os.listdir('cat_dataset/images')][:8]
for i,filename in enumerate(image_paths):
name = os.path.splitext(filename)[0]
image = Image.open('cat_dataset/images/'+filename).convert("RGB")
plt.subplot(2, 4, i+1)
plt.imshow(image)
plt.title(f"{filename}")
plt.xticks([])
plt.yticks([])
plt.tight_layout()
得到以下结果
from pycocotools.coco import COCO
from PIL import Image
import numpy as np
import os.path as osp
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Polygon
def apply_exif_orientation(image):
_EXIF_ORIENT = 274
if not hasattr(image, 'getexif'):
return image
try:
exif = image.getexif()
except Exception:
exif = None
if exif is None:
return image
orientation = exif.get(_EXIF_ORIENT)
method = {
2: Image.FLIP_LEFT_RIGHT,
3: Image.ROTATE_180,
4: Image.FLIP_TOP_BOTTOM,
5: Image.TRANSPOSE,
6: Image.ROTATE_270,
7: Image.TRANSVERSE,
8: Image.ROTATE_90,
}.get(orientation)
if method is not None:
return image.transpose(method)
return image
def show_bbox_only(coco, anns, show_label_bbox=True, is_filling=True):
"""Show bounding box of annotations Only."""
if len(anns) == 0:
return
ax = plt.gca()
ax.set_autoscale_on(False)
image2color = dict()
for cat in coco.getCatIds():
image2color[cat] = (np.random.random((1, 3)) * 0.7 + 0.3).tolist()[0]
polygons = []
colors = []
for ann in anns:
color = image2color[ann['category_id']]
bbox_x, bbox_y, bbox_w, bbox_h = ann['bbox']
poly = [[bbox_x, bbox_y], [bbox_x, bbox_y + bbox_h],
[bbox_x + bbox_w, bbox_y + bbox_h], [bbox_x + bbox_w, bbox_y]]
polygons.append(Polygon(np.array(poly).reshape((4, 2))))
colors.append(color)
if show_label_bbox:
label_bbox = dict(facecolor=color)
else:
label_bbox = None
ax.text(
bbox_x,
bbox_y,
'%s' % (coco.loadCats(ann['category_id'])[0]['name']),
color='white',
bbox=label_bbox)
if is_filling:
p = PatchCollection(
polygons, facecolor=colors, linewidths=0, alpha=0.4)
ax.add_collection(p)
p = PatchCollection(
polygons, facecolor='none', edgecolors=colors, linewidths=2)
ax.add_collection(p)
coco = COCO('./cat_dataset/annotations/test.json')
image_ids = coco.getImgIds()
np.random.shuffle(image_ids)
plt.figure(figsize=(16, 5))
# 只可视化 8 张图片
for i in range(8):
image_data = coco.loadImgs(image_ids[i])[0]
image_path = osp.join('./cat_dataset/images/',image_data['file_name'])
annotation_ids = coco.getAnnIds(
imgIds=image_data['id'], catIds=[], iscrowd=0)
annotations = coco.loadAnns(annotation_ids)
ax = plt.subplot(2, 4, i+1)
image = Image.open(image_path).convert("RGB")
# 这行代码很关键,否则可能图片和标签对不上
image=apply_exif_orientation(image)
ax.imshow(image)
show_bbox_only(coco, annotations)
plt.title(f"{filename}")
plt.xticks([])
plt.yticks([])
plt.tight_layout()
修改配置文件
主要是修改类别数量、学习率等等。
修改配置文件需要注意几个问题:
自定义数据集中最重要的是 metainfo 字段,用户在配置完成后要记得将其传给 dataset,否则不生效(有些用户在自定义数据集时候喜欢去 直接修改 coco.py 源码,这个是强烈不推荐的做法,正确做法是配置 metainfo 并传给 dataset)
如果用户 metainfo 配置不正确,通常会出现几种情况:(1) 出现 num_classes 不匹配错误 (2) loss_bbox 始终为 0 (3) 出现训练后评估结果为空等典型情况
MMDetection 提供的学习率大部分都是基于 8 卡,如果你的总 bs 不同,一定要记得缩放学习率,否则有些算法很容易出现 NAN。
在配置完成之后,可以先进行可视化来测试整个数据流,从而判断这个配置文件是否是有效的。
from mmdet.registry import DATASETS, VISUALIZERS
from mmengine.config import Config
from mmengine.registry import init_default_scope
import matplotlib.pyplot as plt
import os.path as osp
cfg = Config.fromfile('./cat_dataset/cat_rtmdet_config.py')
init_default_scope(cfg.get('default_scope', 'mmdet'))
dataset = DATASETS.build(cfg.train_dataloader.dataset)
visualizer = VISUALIZERS.build(cfg.visualizer)
visualizer.dataset_meta = dataset.metainfo
plt.figure(figsize=(16, 5))
# 只可视化前 8 张图片
for i in range(8):
item=dataset[i]
img = item['inputs'].permute(1, 2, 0).numpy()
data_sample = item['data_samples'].numpy()
gt_instances = data_sample.gt_instances
img_path = osp.basename(item['data_samples'].img_path)
gt_bboxes = gt_instances.get('bboxes', None)
gt_instances.bboxes = gt_bboxes.tensor
data_sample.gt_instances = gt_instances
visualizer.add_datasample(
osp.basename(img_path),
img,
data_sample,
draw_pred=False,
show=False)
drawed_image=visualizer.get_image()
plt.subplot(2, 4, i+1)
plt.imshow(drawed_image[..., [2, 1, 0]])
plt.title(f"{osp.basename(img_path)}")
plt.xticks([])
plt.yticks([])
plt.tight_layout()
得到以下结果
这是通过马赛克数据增强后的可视化结果。
模型训练和测试推理
训练
python tools/train.py cat_dataset/cat_rtmdet_config.py
测试和推理
# 测试并保存结果
python tools/test.py ipynb/cat_dataset/cat_rtmdet_config.py work_dirs/cat_rtmdet_config/best_coco_bbox_mAP_epoch_30.pth --show-dir results
会在 work_dir/cat_rtmdet_config/当前时间戳/results/
下生成测试图片,下面对前 8 张图片进行可视化。
import os
import matplotlib.pyplot as plt
from PIL import Image
%matplotlib inline
plt.figure(figsize=(20, 20))
# 你如果重新跑,这个时间戳是不一样的,需要自己修改
root_path='../work_dirs/cat_rtmdet_config/20230613_210036/results/'
image_paths= [filename for filename in os.listdir(root_path)][:4]
for i,filename in enumerate(image_paths):
name = os.path.splitext(filename)[0]
image = Image.open(root_path+filename).convert("RGB")
plt.subplot(4, 1, i+1)
plt.imshow(image)
plt.title(f"{filename}")
plt.xticks([])
plt.yticks([])
plt.tight_layout()
左边是GT,右边是预测值。
推理过程
python demo/image_demo.py ipynb/cat_dataset/cat.jpg ipynb/cat_dataset/cat_rtmdet_config.py --weights work_dirs/cat_rtmdet_config/best_coco_bbox_mAP_epoch_30.pth
可视化分析
这里需要用到MMYOLO中提供的脚本和功能,因为MMYOLO是基于MMDetection开发的,所以不存在迁移的问题。
可视化分析包括特征图可视化以及类似Grad CAM等可视化分析手段。
安装MMYOLO
git clone -b tutorials https://github.com/open-mmlab/mmyolo.git
mim install mmyolo
cd mmyolo
pip install -e .
特征图可视化
对上面的cat的图片进行可视化分析,但是这张图片分辨率太大,会导致程序崩溃,所以先进行缩放处理。
import cv2
img = cv2.imread('mmdetection/ipynb/cat_dataset/cat.jpg')
# print(img.shape)
h,w=img.shape[:2]
resized_img = cv2.resize(img, (640, 640))
cv2.imwrite('mmdetection/ipynb/cat_dataset/cat_resize.jpg', resized_img)
1.可视化backbone输出的3个通道
!python demo/featmap_vis_demo.py \
../mmdetection/ipynb/cat_dataset/cat_resize.jpg \
../mmdetection/ipynb/cat_dataset/cat_rtmdet_config.py \
../mmdetection/work_dirs/cat_rtmdet_config/best_coco_bbox_mAP_epoch_30.pth \
--target-layers backbone \
--channel-reduction squeeze_mean
上图中绘制的3个输出特征图对应大中小输出特征图。由于本次训练的backbone实际上没有参与训练,从上图可以看到,大物体cat是在小特征图进行预测,这符合目标检测分层检测思想。
2.可视化neck输出的3个通道
!python demo/featmap_vis_demo.py \
../mmdetection/ipynb/cat_dataset/cat_resize.jpg \
../mmdetection/ipynb/cat_dataset/cat_rtmdet_config.py \
../mmdetection/work_dirs/cat_rtmdet_config/best_coco_bbox_mAP_epoch_30.pth \
--target-layers neck \
--channel-reduction squeeze_mean
从上图可以看出,由于neck是参与训练的,并且FPN间的信息融合导致输出特征图更加聚集
Grad-Based CAM可视化
由于目标检测的特殊性,这里实际上可视化的并不是CAM而是Grad Box AM。使用前需要先安装grad-cam库
pip install grad-cam
1.查看neck输出的最小输出特征图的Grad CAM
!python demo/boxam_vis_demo.py \
../mmdetection/ipynb/cat_dataset/cat_resize.jpg \
../mmdetection/ipynb/cat_dataset/cat_rtmdet_config.py \
../mmdetection/work_dirs/cat_rtmdet_config/best_coco_bbox_mAP_epoch_30.pth \
--target-layers neck.out_convs[2]
2.查看neck输出的最大输出特征图的Grad CAM
!python demo/boxam_vis_demo.py \
../mmdetection/ipynb/cat_dataset/cat_resize.jpg \
../mmdetection/ipynb/cat_dataset/cat_rtmdet_config.py \
../mmdetection/work_dirs/cat_rtmdet_config/best_coco_bbox_mAP_epoch_30.pth \
--target-layers neck.out_convs[0]
这一层是空白的原因是,因为cat是大物体,而这一层是训练小目标的,所以没有对猫进行训练,所以梯度为0。