【YOLO系列】YOLOX(含代码解析)

news/2024/11/7 17:59:39/

文章目录

    • 环境配置
      • demo测试
      • 转换成onnx
    • YOLOX
      • 数据增广
      • decoupled head
      • Anchor-free
      • 标签分配
        • get_geometry_constraint
        • SimOTA
    • 总结
    • 参考

【YOLO系列】YOLO v3(网络结构图+代码)
【YOLO 系列】YOLO v4-v5先验知识
【YOLO系列】YOLO v4(网络结构图+代码)
【YOLO系列】YOLO v5(网络结构图+代码)

环境配置

购买的腾讯云服务器,配置为GPU计算型GN7/8核/32GB/5Mbps。conda创建虚拟环境,python版本选择的是3.7。

conda create -n yolox python=3.7
conda activate yolox
git clone git@github.com:Megvii-BaseDetection/YOLOX.git
cd YOLOX
pip install -v -e .

然而在pip 安装时,出现下述错误。issues/1368中存在同样的问题,需要在安装yolox之前,手动安装torch。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ht41Lcsc-1687770183365)(./1687142812458.png)]

在pytorch官方网站上找到GPU版本torch的安装命令。

conda install pytorch1.13.0 torchvision0.14.0 torchaudio==0.13.0 pytorch-cuda=11.6 -c pytorch -c nvidia

然后再运行pip install -v -e . 命令即可安装成功。我的环境配置如下所示,这个配置没有出现pycocotools的安装错误。

conda 23.3.1
python 3.7
torch 1.13.0
cuda 11.6
cudnn 8302

demo测试

下载yolox_tiny的预训练模型,然后执行如下命令。

python tools/demo.py image -f exps/default/yolox_tiny.py -c yolox_tiny.pth --path assets/dog.jpg --conf 0.25 --nms 0.45 --tsize 640 --save_result --device gpu

在这里插入图片描述

转换成onnx

tools目录下的export_onnx文件可以将pth文件转换成onnx文件,这样就可以清晰地查看网络结构。转换onnx模型时,需要检测是否下载了onnxruntime库文件。onnx的算子版本默认是11。

python tools/export_onnx.py --output-name yolox_tiny.onnx -n yolox-tiny -c yolox_tiny.pth

YOLOX

YOLOX的基础模型是YOLOv3-SPP(backbone为DarkNet53+SPP),相比于YOLOv3-SPP,YOLOX添加了EMA权重更新,cosine lr schedule,IoU loss和IoU-aware分支。

YOLOX的backbone和Neck与YOLOv3-v4相比,无太大改动,此篇文章不再赘述YOLOX的backbone和Neck部分。

数据增广

YOLOX的数据增广方式有RandomHorizontalFlip、ColorJitter、multi-scale 、Mosaic和MixUP。舍弃了RandomResizedCrop增广方式,因为官方发现RandomResizedCrop与Mosaic的功能有些重叠。

Mosaic和Mixup需要再训练结束前的15个epoch关掉。官方给出的解释是,Mosaic+Mixup生成的训练图片,脱离自然图片的真实分布,而且Mosaic大量的crop操作会带来很多不准确的标注框。提前关闭Mosaic和Mixup,能够使检测器避开不准确标注框的影响,在自然图片的数据分布下完成最终的收敛。

decoupled head

在YOLO v3-v5版本中,head头同时预测输出目标位置和类别,每个特征图的预测张量为 N × N × [ 3 ∗ ( 4 + 1 + c l a s s e s _ n u m ) ] N \times N \times [ 3*\left( 4 + 1 + classes\_num \right) ] N×N×[3(4+1+classes_num)],其中 N N N是特征图的大小。这种被称为耦合头(Coupled head),它的设计思路简单,仅需要几个全连接层或者卷积层即可。

YOLO中使用的解耦合头(Decoupled head),使用不同的分支分别预测目标位置和类别信息。首先先使用一个 1 × 1 1 \times 1 1×1的卷积减少通道维数,然后并行分类和回归两个分支,每个分支堆叠两个 3 × 3 3 \times 3 3×3的卷积。分类分支用来预测类别信息;回归分支用来输出bbox的位置信息。在回归分支中,添加了IoU 分支。

在这里插入图片描述

在训练过程中,解耦合头相比于耦合头,能够更快地收敛,并且能够学习地更精确。
在这里插入图片描述

Anchor-free

YOLOv1-v5版本皆基于anchor的模型。为了能够取得更好的性能,在训练之前,通常都会通过聚类分析训练数据集的方式确定anchor box的大小,确定的anchor box一般适用于该数据集,通用性不强。而且Anchor的存在增加了检测头的复杂度以及生成结果的数量。YOLOX是Anchor Free的模型,Anchor-Free机制显著减少了需要启发式微调的参数设计的数量和许多其他技巧,比如anchor聚类和grid sensitive。Anchor-Free的机制使得检测在训练和解码阶段,变得相对简单。

将YOLO模型转换成anchor-free的形式是很简单的。将每个位置的预测bbox由3个减少到1个,并且直接预测bbox的四个值,即网格左上角的两个偏移量和预测bbox的宽高。

下图是YOLOX-tiny模型的head部分,图像的输入尺寸是 416 × 416 × 3 416 \times 416 \times 3 416×416×3。YOLOX的基础模型是YOLOv3-SPP,Neck是FPN,有三个预测head头,每个head的输入特征尺寸分别是 52 × 52 52 \times 52 52×52 26 × 26 26 \times 26 26×26 13 × 13 13 \times 13 13×13,相比于原输入图像尺寸,下采样分别是 8 8 8 16 16 16 32 32 32倍。三个head的输出合并转置之后,特征图大小为 1 × 3549 × 85 1 \times 3549 \times 85 1×3549×85,其中 85 85 85是COCO数据集中80个类别预测概率+4个坐标信息+1个IoU值;YOLOX每个网格仅仅预测一个bbox,那么就有 3549 3549 3549个预测bbox。

如果是anchor-base的模型,同样的图像输入,同样的模型结构,则有 3 × ( 13 × 13 + 26 × 26 + 52 × 52 ) × 85 = 904995 3\times \left( 13 \times 13+26 \times 26+52 \times 52\right) \times 85=904995 3×(13×13+26×26+52×52)×85=904995个预测结果。Anchor-Free的预测结果个数为 3549 × 85 = 301665 3549 \times 85 = 301665 3549×85=301665,约等于anchor-base模型的 1 / 3 1/3 1/3

在这 3549 3549 3549个预测框中,其中有2704个预测框所对应的锚框的大小为 8 × 8 8\times 8 8×8; 有676个预测框所对应的锚框的大小为 16 × 16 16\times 16 16×16;有169个预测框所对应的锚框的大小为 32 × 32 32 \times 32 32×32。这 3549 3549 3549个预测框中,只有少部分是正样本,绝大多数是负样本,那么哪些是正样本?如何将正样本预测框挑选出来呢?思路就是将这 3549 3549 3549个预测框和图片上所有的gt框进行关联,从而挑选出正样本,这种关联方式,被称为标签分配。

在这里插入图片描述

标签分配

在YOLOX的官方代码中models/yolo_head.py中get_assignments函数定义了如何进行标签分配,调用了两个主要的函数get_geometry_constraintsimota_matching。get_geometry_constraint函数的主要目的就是:提取落在gt bboxes 一定范围内的所有候选框。simota_matching函数是SimOTA求解。标签分配问题可以转换为标准的OTA问题,但是Sinkhorn-Knopp算法需要多次迭代才能求得最优解,官方发现该算法会导致25%的额外训练时间,因此采用简化版的OTA方法,SimOTA,求解近似最优解。

get_geometry_constraint

get_geometry_constraint的代码如下所示。首先根据偏移量(x_shifts, y_shifts)恢复预测锚框在图片的中心位置;接下来以gt锚框的中心点gt_bboxes_per_image[:, 0:2]为中心,设置边长为 2 × c e n t e r _ d i s t 2 \times center\_dist 2×center_dist的正方形,计算出正方形的左上角(gt_l, gt_t)和右下角(gt_r, gt_b);然后计算预测锚框的中心点与正方形左上角(gt_l, gt_t)和右下角(gt_r, gt_b)的距离c_l, c_t, c_r, c_b,判断c_l, c_t, c_r, c_b是否都大于0,这样就可以将落在正方形范围之内的候选框提取出来了。

def get_geometry_constraint(self, gt_bboxes_per_image, expanded_strides, x_shifts, y_shifts):"""Calculate whether the center of an object is located in a fixed range of an anchor. This is used to avert inappropriate matching. It can also reduce the number of candidate anchors so that the GPU memory is saved."""expanded_strides_per_image = expanded_strides[0]# 锚框在图片上的的中心点(x,y)x_centers_per_image = ((x_shifts[0] + 0.5) * expanded_strides_per_image).unsqueeze(0)y_centers_per_image = ((y_shifts[0] + 0.5) * expanded_strides_per_image).unsqueeze(0)# in fixed centercenter_radius = 1.5center_dist = expanded_strides_per_image.unsqueeze(0) * center_radius# 左上角(gt_l, gt_t),右上角(gt_r, gt_b)gt_bboxes_per_image_l = (gt_bboxes_per_image[:, 0:1]) - center_distgt_bboxes_per_image_r = (gt_bboxes_per_image[:, 0:1]) + center_distgt_bboxes_per_image_t = (gt_bboxes_per_image[:, 1:2]) - center_distgt_bboxes_per_image_b = (gt_bboxes_per_image[:, 1:2]) + center_dist# 计算锚框中心点(x,y)与左上角(gt_l, gt_t)、右下角(gt_r, gt_b)两个角点的相应距离c_l = x_centers_per_image - gt_bboxes_per_image_lc_r = gt_bboxes_per_image_r - x_centers_per_imagec_t = y_centers_per_image - gt_bboxes_per_image_tc_b = gt_bboxes_per_image_b - y_centers_per_image# 堆叠center_deltas = torch.stack([c_l, c_t, c_r, c_b], 2)# 是否都大于0is_in_centers = center_deltas.min(dim=-1).values > 0.0anchor_filter = is_in_centers.sum(dim=0) > 0# 将落在gt矩形范围内的所有anchors,都提取出来了geometry_relation = is_in_centers[:, anchor_filter]return anchor_filter, geometry_relation

SimOTA

get_geometry_constraint函数提取落在gt bboxes 一定范围内的所有候选框之后,根据返回的候选框mask分别提取候选预测框bboxes,类别分数cls_preds和前景背景目标分数obj_preds,然后再分别进行回归和分类交叉熵Loss函数计算、计算cost值。相关代码和注释如下所示。

fg_mask, geometry_relation = self.get_geometry_constraint(gt_bboxes_per_image,expanded_strides,x_shifts,y_shifts)
# 依据候选框mask提取候选预测框
bboxes_preds_per_image = bboxes_preds_per_image[fg_mask]
# 依据候选框mask提取类别分数
cls_preds_ = cls_preds[batch_idx][fg_mask]
# 依据候选框mask提取前景背景目标分数
obj_preds_ = obj_preds[batch_idx][fg_mask]
num_in_boxes_anchor = bboxes_preds_per_image.shape[0]# 计算gt bboxes和预测bboxes之间的IoU
pair_wise_ious = bboxes_iou(gt_bboxes_per_image, bboxes_preds_per_image, False)
gt_cls_per_image = (F.one_hot(gt_classes.to(torch.int64),
self.num_classes).float())
# 回归loss
pair_wise_ious_loss = -torch.log(pair_wise_ious + 1e-8)with torch.cuda.amp.autocast(enabled=False):cls_preds_ = (cls_preds_.float().sigmoid_() * obj_preds_.float().sigmoid_()).sqrt()# 分类交叉熵losspair_wise_cls_loss = F.binary_cross_entropy(cls_preds_.unsqueeze(0).repeat(num_gt, 1, 1),gt_cls_per_image.unsqueeze(1).repeat(1, num_in_boxes_anchor, 1),reduction="none").sum(-1)
del cls_preds_
# pair-wise matching degree,cost计算
cost = (pair_wise_cls_loss + 3.0 * pair_wise_ious_loss + float(1e6) * (~geometry_relation))

SimOTA的代码和注释如下所示。经过get_geometry_constraint函数从原 3549 3549 3549个预测框初筛一部分预测框之后,SimOTA算是精细化筛选。首先设置候选框的最小数量,从pair_wise_ious中挑选出前k个IoU最大的候选框;接下来再通过topk_ious的信息,动态选择候选框;然后得到matching_matrix;最后过滤掉公用的候选框。

def simota_matching(self, cost, pair_wise_ious, gt_classes, num_gt, fg_mask):# 第一步:设置候选框的数量# 根据cost值的大小,新建一个全0的matching_matrixmatching_matrix = torch.zeros_like(cost, dtype=torch.uint8)# 设置候选框的最小数量n_candidate_k = min(10, pair_wise_ious.size(1))# 从pair_wise_ious中挑选n_candidate_k个IoU最大的候选框topk_ious, _ = torch.topk(pair_wise_ious, n_candidate_k, dim=1)# 第二步:通过topk_ious动态挑选候选框dynamic_ks = torch.clamp(topk_ious.sum(1).int(), min=1)# 第三步:得到matching_matrixfor gt_idx in range(num_gt):_, pos_idx = torch.topk(cost[gt_idx], k=dynamic_ks[gt_idx], largest=False)matching_matrix[gt_idx][pos_idx] = 1del topk_ious, dynamic_ks, pos_idx# 第四步:过滤公用的候选框anchor_matching_gt = matching_matrix.sum(0)# deal with the case that one anchor matches multiple ground-truthsif anchor_matching_gt.max() > 1:multiple_match_mask = anchor_matching_gt > 1_, cost_argmin = torch.min(cost[:, multiple_match_mask], dim=0)matching_matrix[:, multiple_match_mask] *= 0matching_matrix[cost_argmin, multiple_match_mask] = 1fg_mask_inboxes = anchor_matching_gt > 0num_fg = fg_mask_inboxes.sum().item()fg_mask[fg_mask.clone()] = fg_mask_inboxesmatched_gt_inds = matching_matrix[:, fg_mask_inboxes].argmax(0)gt_matched_classes = gt_classes[matched_gt_inds]pred_ious_this_matching = (matching_matrix * pair_wise_ious).sum(0)[fg_mask_inboxes]return num_fg, gt_matched_classes, pred_ious_this_matching, matched_gt_inds

总结

上述描述的anchor-free方法,每个物体仅仅只选择了一个正样本,这样会忽略掉一些高质量的预测框。参考FCOS算法,简单地赋予中心 3 × 3 3 \times 3 3×3区域为正样本,这种方法被成为multi positives。

YOLOX在YOLO v3 的基础上,增添了一些技巧。decoupled head提升了1.1%,Mosaic和MixUP更强的增强方式又提升了2.4%,anchor-free的方式提升了0.9%,multi positives也提升了2.1%,SimOTA的提出不仅减少了训练时间,AP还提升了2.3%,比ultralytics的YOLO v3的AP还增加3%。

在这里插入图片描述

参考

  1. Megvii-BaseDetection/YOLOX
  2. YOLOX: Exceeding YOLO Series in 2021
  3. 如何评价旷视开源的YOLOX,效果超过YOLOv5?

http://www.ppmy.cn/news/588416.html

相关文章

Directory Opus打不开除了zip的压缩包(打开错误)

Directory Opus只内置了Opus zip针对.zip后缀的文件来处理。 其它格式的压缩包需要通过其它压缩包软件来完成。 调整如下: 在电脑上可选择7z、WINRAR来安装。 参考文章 https://www.cnblogs.com/moonache/p/4871148.html

怎么用管理员方式打开压缩包

今天下载了安卓的源代码,解压时,报了"Cannot create symbolic link xxx" "You may need to run WinRAR as administrator"的问题。 该如何以管理员方式打开呢, 1、右键菜单中并没有"管理员打开"菜单&#xff…

rar压缩包的打开密码破解

压缩包密码设置了打开密码,解压压缩包的时候必须输入密码才能继续解压文件,忘记了密码或者不知道该如何解决问题呢? 想要破解压缩包打开密码,可以通过破解软件进行密码破解 可以破解rar、zip、7z格式压缩包的打开密码&#xff0…

ZipOutputStream 生成压缩文件,用winrar打开后报”不可预料的压缩文件末端”错误

问题产生的原因:可能是用到文件流未正确关闭 解决办法是:1.检查待压缩文件的流是否都正常关闭,且按顺序 2.生成压缩文件的过程中用到的流是否正常关闭,且按顺序 try {File zipFile new File(fileName);fileName zipFile.getNa…

压缩包打开密码解决办法

rar、zip、7z格式的压缩包有打开密码,如果不知道打开密码,我们可以通过 破解软件尝试破解打开密码。 打开软件把压缩包添加到软件中,选择一个找回方法,点击【下一步】跟着软件提示进行操作就可以开始破解密码了。 组合破解、掩码…

SpringBoot下载文件打不开的解决办法

吸取教训主要是是下载文件打不开,是因为当前页面输入流不能直接连接打开。需要新的连接进行打开点击下载。 http://localhost:8081/tm/corpus/infomation/downloadInfoTemplate 主要是分俩步骤进行,一是生产连接,然后弹出框然后进行下载就可以…

前端下载zip出现文件打不开

因为后端传递过来的是zip流的形式,在返回数据中data里面的数据为类似与base64的格式,我们平常处理的时候是将data里的数据转换成blob的形式。在此步我们就做错了,我们应当在请求的时候加上responseType: "blob",这样我们…

StringBuilder和StringBuffer

StringBuilder和StringBuffer 目录 StringBuilder和StringBuffer特点常见方法练习:测试字符串连接StringBuilder和StringBuffer的区别 特点 封装了char[]数组 是可变的字符序列 提供了一组可以对字符内容修改的方法 常用append()来代替字符串做字符串连接”” 内部…