深度学习---目标检测网络YoloX

发布时间 2023-10-08 11:06:42作者: 半夜打老虎

一、网络介绍

YoloX由旷视科技开源,以YoloV3(Darknet53作为backbone)作为基线,最大的区别在于 Decoupled Head,Data Aug,Anchor Free 和样本匹配(SimOTA)这几个方面,另外还提供了完善的代码,并很贴心的提供了部署脚本,真的很友好了。
PDF: https://arxiv.org/pdf/2107.08430.pdf
Code: https://github.com/Megvii-BaseDetection/YOLOX
参考:https://www.zhihu.com/question/473350307

1. Decoupled Head

将检测头解耦无疑会增加运算的复杂度,但经过权衡速度和性能上的得失,最终使用 1个1x1 的卷积先进行降维,并在分类和回归分支里各使用了 2个3x3 卷积,最终仅仅增加一点点参数,YOLOX 在 s,m,l,x 模型速度上的轻微下降也全源自于此。表面上看,解耦检测头提升了 YOLOX 的性能和收敛速度,但更深层次的,它为 YOLO 与检测下游任务的一体化带来可能。如:

  • YOLOX + Yolact/CondInst/SOLO ,实现端侧的实例分割
  • YOLOX + 34 层输出,实现端侧人体的 17 个关键点检测

2. Data Aug

数据增强方法Mosaic (主要思想是将四张图片进行随机裁剪,再拼接到一张图上作为训练数据,丰富背景的同时变相的增加了BatchSize)经过 YOLOv5 和 v4 的验证,已经证明了其能带来显著涨点。

YoloX为 Mosaic 配上 Copypaste,依然有不小的提升。这样做的原因是:当模型容量足够大的时候,相对于先验知识(各种 tricks,hand-crafted rules ),更多的后验(数据/数据增强)才会产生本质影响。可 Copypaste 的实现依赖于目标的 mask 标注,而 mask 标注在常规的检测业务上是稀缺的资源。而由于 MixUp 和 Copypaste 有着类似的贴图的行为,还不需要 mask 标注,因此可以让 YOLOX 在没有 mask 标注的情况下吃到 Copypaste 的涨点。不过YoloX中的 Mixup,没有原始 Mixup 里的 Bernoulli Distribution 和 Soft Label ,有的仅是 0.5 的常数透明度和 Copypaste 里提到的尺度缩放 ( scale jittering )。 YOLOX 里的 Mixup 有如此明显的涨点,大概是因为它在实现和涨点原理上更接近 Copypaste,而不是原版 Mixup。

注意:要在训练结束前的15个 epoch 关掉 Mosaic 和Mixup ,这可以避免让 YOLOX 训练结果偏离真实分布(Mosaic+Mixup 生成的训练图片,远远脱离自然图片的真实分布,并且 Mosaic 大量的 crop 操作会带来很多不准确的标注框)。
参考:https://github.com/ultralytics/yolov5/issues/2151

3. Anchor Free 与 Label Assignment

至于为什么 YoloX作为Anchor Free但性能不降反升,这与样本匹配有密不可分的联系

  • loss/quality/prediction aware
    基于网络自身的预测来计算 anchor box 或者 anchor point 与 gt 的匹配关系,充分考虑到了不同结构/复杂度的模型可能会有不同行为,是一种真正的 dynamic 样本匹配

  • center prior
    目标的质心都与目标的几何中心有一定的联系,将正样本限定在目标中心的一定区域内做 loss/quality aware 样本匹配能很好地解决收敛不稳定的问题

  • 不同目标设定不同的正样本数量( dynamic k )
    Dynamic k 的关键在于如何确定k,有些方法通过其他方式间接实现了动态 k ,比如 ATSS、PAA ,甚至 RetinaNet ,同时,k的估计依然可以是 prediction aware 的,我们具体的做法是首先计算每个目标最接近的10个预测,然后把这个 10 个预测与 gt 的 iou 加起来求得最终的k,很简单有效,对 10 这个数字也不是很敏感,在 5~15 调整几乎没有影响

二、网络实现

参照提供的代码:https://github.com/Megvii-BaseDetection/YOLOX

  • 主要网络结构
    在yolox目录下的models中找到yolox.py文件,

    主要包含YOLOXHead和YOLOPAFPN两块
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Copyright (c) Megvii Inc. All rights reserved.

import torch.nn as nn

from .yolo_head import YOLOXHead
from .yolo_pafpn import YOLOPAFPN


class YOLOX(nn.Module):
    """
    YOLOX model module. The module list is defined by create_yolov3_modules function.
    The network returns loss values from three YOLO layers during training
    and detection results during test.
    """

    def __init__(self, backbone=None, head=None):
        super().__init__()
        if backbone is None:
            backbone = YOLOPAFPN()
        if head is None:
            head = YOLOXHead(80)

        self.backbone = backbone
        self.head = head

    def forward(self, x, targets=None):
        # fpn output content features of [dark3, dark4, dark5]
        fpn_outs = self.backbone(x)

        if self.training:
            assert targets is not None
            loss, iou_loss, conf_loss, cls_loss, l1_loss, num_fg = self.head(
                fpn_outs, targets, x
            )
            outputs = {
                "total_loss": loss,
                "iou_loss": iou_loss,
                "l1_loss": l1_loss,
                "conf_loss": conf_loss,
                "cls_loss": cls_loss,
                "num_fg": num_fg,
            }
        else:
            outputs = self.head(fpn_outs)

        return outputs
  • nano tiny s m l x 不同格式模型
    YOLOX提供不同版本的模型,主要区别在于修改网络的depth和width,其对应的大小分别为:
self.depth = 0.33    # nano: 0.33, tiny:0.33,   s: 0.33, m: 0.67, l: 1.0, x: 1.33  # nano和tiny需调整self.input_size = (416, 416)
self.width = 0.50    # nano: 0.25, tiny: 0.375, s: 0.50, m: 0.75, l: 1.0, x: 1.25

最终影响的是YOLOPAFPN模块

  • 部署
    在Demo目录下还提供了不同的部署方式,可以依据实际需求进行选择。

    以NCNN平台为例,其关键点在于自定义实现YoloV5Focus层:

  • 效果


三、训练自己数据

参照docs目录下的train_custom_data.md,训练自己数据主要包含以下几个步骤:

1. 准备数据集

采用labelme标注 利用labelme工程中自带的转换脚本将其转换成coco数据样式

2. 数据存放格式

dataDir

  • annotations
    • annotations_train.json
    • annotations_val.json
  • JPEGImages
    • *.jpg

备注:按coco标准来讲,JPEGImages目录下还应分成train和val两个目录,这里修改了datasets/coco.py文件,所以没有再区分

# img_file = os.path.join(self.data_dir, self.name, file_name)
img_file = os.path.join(self.data_dir, file_name)
# print("********", os.path.isfile(img_file))
img_file = img_file.replace("\\", "/")

3. 新建自己参数脚本

在根目录下的exps目录下新建Tooth.py,依据实际情况设置相应参数

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import os

from yolox.exp import Exp as MyExp


class Exp(MyExp):
    def __init__(self):
        super(Exp, self).__init__()
        self.depth = 0.33    # s: 0.33, m: 0.67, l: 1.0, x: 1.33
        self.width = 0.50    # s: 0.50, m: 0.75, l: 1.0, x: 1.25
        self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]

        # Define yourself dataset path
        self.data_dir = "/home/heygears/jinhai_zhou/data/2D_detect/tooth"
        self.train_ann = "annotations_train.json"
        self.val_ann = "annotations_val.json"

        self.num_classes = 2   # include background

        self.max_epoch = 300
        self.data_num_workers = 4
        self.eval_interval = 1

        self.cls_names = (
            "background",
            "tooth",
        )

4.训练

进入根目录下的tools目录
执行:

python train.py -f ../exps/Tooth.py -d 0 -b 64 --fp16 -o -c /path/to/yolox_s.pth

其中-d后面数字表示GPU编号, -b 后面数字表示批量数据大小, --fp16表示混合精度训练 后面的pth文件为预训练权重

5.测试

修改tools目录下demo.py,添加demo==images参数,支持图片批量测试

执行以下命令进行测试

python demo.py images -f ../exps/Tooth.py -n Tooth -c ../weights/Tooth_s.pth --path ../assets/tooth --conf 0.55 --nms 0.45 --tsize 640 --save_result --device cpu

6.导出模型

在tools目录下,运行export_onnx.py文件

python export_onnx.py -f ../exps/Tooth.py -n Tooth -c ../weights/Tooth_s.pth --output-name ./tooth.onnx

四、常见错误

训练中出现的问题

wget https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-linux.zip
sudo unzip ninja-linux.zip -d /usr/local/bin/
sudo update-alternatives --install /usr/bin/ninja ninja /usr/local/bin/ninja 1 --force