【七班】OpenMMLab AI实战营第七课——MMDetection代码

枫叶随樱花 2023-06-09 22:18:16

目录

环境配置与安装

数据集准备与可视化

自定义配置文件

训练前可视化验证

模型训练

模型测试与推理

可视化分析

特征图可视化

梯度可视化

检测新趋势

问题与思考


环境配置与安装

环境及MMDetection安装详见MMDetection官方文档

环境打印脚本,用于后续排查错误

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}')

数据集准备与可视化

数据集:下载链接

数据集可视化代码

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
import numpy as np
import os.path as osp
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()

自定义配置文件

# 当前路径位于 mmdetection/tutorials, 配置将写到 mmdetection/tutorials 路径下
#config_cat

_base_ = 'configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py'

data_root = 'cat_dataset/'

# 非常重要
metainfo = {
    # 类别名,注意 classes 需要是一个 tuple,因此即使是单类,
    # 你应该写成 `cat,` 很多初学者经常会在这犯错
    'classes': ('cat',), #存放类别名
    'palette': [  #存放调色板
        (220, 20, 60),
    ]
}
num_classes = 1 #类别数量

# 训练 40 epoch
max_epochs = 40
# 训练单卡 bs= 12
train_batch_size_per_gpu = 12
# 可以根据自己的电脑修改
train_num_workers = 4

# 验证集 batch size 为 1
val_batch_size_per_gpu = 1
val_num_workers = 2

# RTMDet 训练过程分成 2 个 stage,第二个 stage 会切换数据增强 pipeline
num_epochs_stage2 = 5

# batch 改变了,学习率也要跟着改变, 0.004 是 8卡x32 的学习率
base_lr = 12 * 0.004 / (32*8) #线性缩放

# 采用 COCO 预训练权重
load_from = 'https://download.openmmlab.com/mmdetection/v3.0/rtmdet/rtmdet_tiny_8xb32-300e_coco/rtmdet_tiny_8xb32-300e_coco_20220902_112414-78e30dcc.pth'  

model = dict(
    # 考虑到数据集太小,且训练时间很短,我们把 backbone 完全固定
    # 用户自己的数据集可能需要解冻 backbone
    backbone=dict(frozen_stages=4),
    # 不要忘记修改 num_classes
    bbox_head=dict(dict(num_classes=num_classes)))

# 数据集不同,dataset 输入参数也不一样
train_dataloader = dict(
    batch_size=train_batch_size_per_gpu,
    num_workers=train_num_workers,
    pin_memory=False,
    dataset=dict(
        data_root=data_root,
        metainfo=metainfo,
        ann_file='annotations/trainval.json',
        data_prefix=dict(img='images/')))

val_dataloader = dict(
    batch_size=val_batch_size_per_gpu,
    num_workers=val_num_workers,
    dataset=dict(
        metainfo=metainfo,
        data_root=data_root,
        ann_file='annotations/test.json',
        data_prefix=dict(img='images/')))

test_dataloader = val_dataloader

# 默认的学习率调度器是 warmup 1000,但是 cat 数据集太小了,需要修改为 30 iter
param_scheduler = [
    dict(
        type='LinearLR',
        start_factor=1.0e-5,
        by_epoch=False,
        begin=0,
        end=30),
    dict(
        type='CosineAnnealingLR',
        eta_min=base_lr * 0.05,
        begin=max_epochs // 2,  # max_epoch 也改变了
        end=max_epochs,
        T_max=max_epochs // 2,
        by_epoch=True,
        convert_to_iter_based=True),
]
optim_wrapper = dict(optimizer=dict(lr=base_lr))

# 第二 stage 切换 pipeline 的 epoch 时刻也改变了
_base_.custom_hooks[1].switch_epoch = max_epochs - num_epochs_stage2

val_evaluator = dict(ann_file=data_root + 'annotations/test.json')
test_evaluator = val_evaluator

# 一些打印设置修改
default_hooks = dict(
    checkpoint=dict(interval=10, max_keep_ckpts=2, save_best='auto'),  # 同时保存最好性能权重
    logger=dict(type='LoggerHook', interval=5))
train_cfg = dict(max_epochs=max_epochs, val_interval=10)


with open('rtmdet_tiny_1xb12-40e_cat.py', 'w') as f:
    f.write(config_cat) #存储配置

注:MMDetection 提供的学习率大部分都是基于 8 卡,如果你的总 bs 不同,一定要记得缩放学习率,否则有些算法很容易出现 NAN(自己在训练YOLOv8是第一轮的loss为NAN可能也是学习率缩小不够多)

训练前可视化验证

主要用于检查配置是否正确

采用 mmdet 提供的 tools/analysis_tools/browse_dataset.py 脚本来对训练前的 dataloader 输出进行可视化,确保数据部分没有问题。

from mmdet.registry import DATASETS, VISUALIZERS
from mmengine.config import Config
from mmengine.registry import init_default_scope

cfg = Config.fromfile('rtmdet_tiny_1xb12-40e_cat.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 rtmdet_tiny_1xb12-40e_cat.py

注:在加载权重文件时出现unexpected key in source state_dict: data_preprocessor.mean, data_preprocessor.std不重要,mean和std都是定值,每次都会进行初始化

模型测试与推理

如果想离线测试,则可以使用 test.py 脚本

命令 python tools/test.py rtmdet_tiny_1xb12-40e_cat.py work_dirs/rtmdet_tiny_1xb12-40e_cat/best_coco/bbox_mAP_epoch_30.pth

测试可视化

# 数据集可视化

import os
import matplotlib.pyplot as plt
from PIL import Image

%matplotlib inline

plt.figure(figsize=(20, 20))

# 你如果重新跑,这个时间戳是不一样的,需要自己修改
root_path='work_dirs/rtmdet_tiny_1xb12-40e_cat/20230517_120933/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()

其中左边显示的是标注框,右边显示的是预测框。

MMDetection 支持多种可视化后端,例如 TensorBoard 和 WandB,默认情况下是将图片保存到本地,只需要修改可视化部分配置即可轻松切换。如下所示

以下配置只需要加到 rtmdet_tiny_1xb12-40e_cat.py 配置最后即可

(1) 同时使用本地和 WandB 后端

visualizer = dict(vis_backends = [dict(type='LocalVisBackend'), dict(type='WandbVisBackend')])

(2) 仅仅使用 TensorBoard 后端

visualizer = dict(vis_backends = [dict(type='TensorboardVisBackend')])

修改配置后重新运行 test.py 即可在 WandB 和 TensorBoard 前端界面查看 图片和 log 等。

如果想对单张图片进行推理,你可以直接使用 mmdetection/demo/image_demo.py 脚本

命令 python demo/image_demo.py cat_dataset/images/IMG_20211102_003429.jpg rtmdet_tiny_1xb12-40e_cat.py --weights work_dirs/rtmdet_tiny_1xb12-40e_cat/best_coco/bbox_mAP_epoch_30.pth

注:推理指令中图片路径既可以时单张图片也可以是一个文件夹

可视化分析

特征图可视化与梯度图可视化(Grad CAM)

MMYOLO环境配置与安装详见官方文档MMYOLO官方文档

特征图可视化

MMYOLO 中,使用 MMEngine 提供的 Visualizer 可视化器进行特征图可视化,其具备如下功能:

- 支持基础绘图接口以及特征图可视化。

- 支持选择模型中的不同层来得到特征图,包含 squeeze_meanselect_maxtopk 三种显示方式,用户还可以使用 arrangement 自定义特征图显示布局方式。

squeeze_mean:表示将所有通道取平均值

select_max:表示选择最大的通道进行显示

topk:选择topk的最大值

可以调用 demo/featmap_vis_demo.py 来简单快捷地得到可视化结果,为了方便理解,将其主要参数的功能梳理如下:

  • img:选择要用于特征图可视化的图片,支持单张图片或者图片路径列表。

  • config:选择算法的配置文件。

  • checkpoint:选择对应算法的权重文件。

  • --out-file:将得到的特征图保存到本地,并指定路径和文件名。

  • --device:指定用于推理图片的硬件,--device cuda:0 表示使用第 1 张 GPU 推理,--device cpu 表示用 CPU 推理。

  • --score-thr:设置检测框的置信度阈值,只有置信度高于这个值的框才会显示。

  • --preview-model:可以预览模型,方便用户理解模型的特征层结构。

  • --target-layers:对指定层获取可视化的特征图。

    • 可以单独输出某个层的特征图,例如: --target-layers backbone , --target-layers neck , --target-layers backbone.stage4 等。

    • 参数为列表时,也可以同时输出多个层的特征图,例如: --target-layers backbone.stage4 neck 表示同时输出 backbone 的 stage4 层和 neck 的三层一共四层特征图。

  • --channel-reduction:输入的 Tensor 一般是包括多个通道的,channel_reduction 参数可以将多个通道压缩为单通道,然后和图片进行叠加显示,有以下三个参数可以设置:

    • squeeze_mean:将输入的 C 维度采用 mean 函数压缩为一个通道,输出维度变成 (1, H, W)。

    • select_max:将输入先在空间维度 sum,维度变成 (C, ),然后选择值最大的通道。

    • None:表示不需要压缩,此时可以通过 topk 参数可选择激活度最高的 topk 个特征图显示。

  • --topk:只有在 channel_reduction 参数为 None 的情况下, topk 参数才会生效,其会按照激活度排序选择 topk 个通道,然后和图片进行叠加显示,并且此时会通过 --arrangement 参数指定显示的布局,该参数表示为一个数组,两个数字需要以空格分开,例如: --topk 5 --arrangement 2 3 表示以 2行 3列 显示激活度排序最高的 5 张特征图, --topk 7 --arrangement 3 3 表示以 3行 3列 显示激活度排序最高的 7 张特征图。

    • 如果 topk 不是 -1,则会按照激活度排序选择 topk 个通道显示。

    • 如果 topk = -1,此时通道 C 必须是 1 或者 3 表示输入数据是图片,否则报错提示用户应该设置 channel_reduction 来压缩通道。

  • 考虑到输入的特征图通常非常小,函数默认将特征图进行上采样后方便进行可视化。

注意:当图片和特征图尺度不一样时候,draw_featmap 函数会自动进行上采样对齐。如果你的图片在推理过程中前处理存在类似 Pad 的操作此时得到的特征图也是 Pad 过的,那么直接上采样就可能会出现不对齐问题。

注:在观察特征图时,需要遵循目标检测分层检测思想,即小特征图检测大物体,大特征图检测小物体

梯度可视化

CAM:带有类别的

由于目标检测的特殊性,这里实际上可视化的并不是 CAM 而是 Grad Box AM。使用前需要先安装 grad-cam 库

命令 pip install "grad-cam"

注:观察过程中,如果什么都没有则说明没有梯度,也就没参与训练,同样遵循小特征图检测大目标,大特征图检测小目标

检测新趋势

1. Open-Vocabulary Object Detection: 开放词汇目标检测,给定图片和类别词汇表,检测物体(类别可控)

2. Grounding Object Detection: 给定图片和文本描述,预测文本中所提到的在图片中的物体位置

经典算法Grounded Language-Image Pre-training(2021)

3. 视觉领域大一统模型,例如通用图像分割模型

经典算法Generalized Decoding for Pixel, Image, and Language(2022)

问题与思考

1. 本次课程是一次较为简单的调用模型的课程,可熟悉mmdetection的配置文件等相关训练操作,如果需要修改模型等需求,还需要看源码深度挖掘

2. 在可视化分析部分,squeeze_meanselect_maxtopk 三种显示方式的优缺点各是什么,在日常使用过程中如何根据任务类型先选择合适的方式?

 

...全文
685 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

535

社区成员

发帖
与我相关
我的任务
社区描述
构建国际领先的计算机视觉开源算法平台
社区管理员
  • OpenMMLab
  • jason_0615
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧