文章目录

  • 前言
  • Focus
  • Decouple head
  • Strong data augmentation
  • Anchor Free
    • Multi positives
    • SimOTA
  • 代码实现
    • 输入预处理
    • 输出后处理
  • 参考

前言

在目标检测2022最新进展中提到YOLOX,YOLOX是由旷视工作,开源了源代码,在知乎的问题上并做出了详细的解答:如何评价旷视开源的YOLOX,效果超过YOLOv5,可谓是“长江后浪推前浪”。YOLOx创新在于使用Decoupled Head、SIMOTA等方式。通过阅读本篇博客,你可以了解yolox的这些创新点背后的原理以及相对应的出处,同时本篇博客也详细介绍YOLOX的实现以及部署(基于tensorflow2)。当然在阅读本博客的同时,你也可以阅读参考部分的引用资料来加深对YOLOX的理解。

YOLOX的base line是YOLOV3,通过添加各种trick,得到YOLOX-Darknet53,结构图如下所示。要注意一点是CBS模块与YOLOV3中的CBL模块差不多,只不过是激活函数不同而已,CBS使用的是SiLU激活函数,CBL使用的是LeakyRelu激活函数。

另一方面YOLOX还具备Yolox-s、Yolox-m、Yolox-l、Yolox-x系列,这些系列原理与YOLOV5的相同,根据各个网络的宽度、高度不同进行划分。最后对于轻量级网络,YOLOX设计了YOLOX-Nano以及YOLOX-Tiny轻量级网络。

Focus

Focus模块的第一次使用并不是在YOLOX,而是在YOLOv5上已经进行应用。由于先前没有对YOLOV5进行分析,因此在这部分介绍Focus模块。Focus模块对图像进行切片操作,在图像中每隔一个像素取一个值,这样就拿到四张图像,如下图所示。这使得W,H信息集中到了通道空间,输入通道扩充四倍。输入[Batch_size, 640, 640, 3]输出得到[Batch_size, 320, 320, 12]。


Focus模块作用可以参考以下链接:YOLOv5 Focus() Layer

 class Focus(nn.Module): # Focus wh information into c-space def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups super(Focus, self).__init__() self.conv = Conv(c1 * 4, c2, k, s, p, g, act) # self.contract = Contract(gain=2) def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2) return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))

Decouple head

关于Decouple的原理在Revisiting the sibling head in object detector和Rethinking classifification and localization for object detection做了详细的阐述。第一篇文章说明了之前的目标检测任务中,在同一个检测头检测物体的坐标以及物体分类对检测头无利,文章中把这种检测头成为sibling head。因为定位任务和分类任务关注部分不一样,这种失准(misalignment)可以通过简单的task-aware spatial disentanglement(TSD)来解决。这个方法原理是对某个实例进行观察,某些突出区域的特征有丰富的信息特征进行分类,而这些边界特征对边界回归有作用,如下图所示。因此TSD通过decouples两个任务而提高模型的效率。

在另一篇文章中,Rethinking classifification and localization for object detection经过分析也得出与第一篇文章的结论,即定位任务与分类任务的偏好是不一致,因此放在同一个检测头是不合理。除此之外,该文章分析FC层适合用于分类任务,而卷积头适合定位操作。因此该文章提出了双头检测这样一种结构,与当前YOLOX相似。

基于上面的背景,YOLOX提出以下的检测头。该结构除了能够提高检测性能,还可以提升收敛速度。但是将检测头解耦,会增加运算的复杂度。

YOLOX的检测头结构具体如下图所示

不同的分支对于FPN+PAN输出的不同尺度下的feature map的输出,这是目标检测的例牌了,结合不同的尺度对物体进行精确分类。输入为[640, 640, 3],经过8倍、16倍、32倍下采样,即得到 80 × 80 80\times 80 80×80, 40 × 40 40 \times 40 40×40, 20 × 20 20 \times 20 20×20的三个尺度,85的值即80类+4个坐标信息+1个分类置信度。通过conca得到 85 × 8400 85\times 8400 85×8400的结果,每一行即 1 × 8400 1\times8400 1×8400的结果中包含了不同尺度下预测框的结果。

Strong data augmentation

这一部分主要与输入有关系,在YOLOX的论文中,使用马赛克增强,如下图所示:

以及mixup

图来自:参考1

但是要特别注意的是:

  1. 在训练的最后15个epoch,这两个数据增强会被关闭掉。
  2. 由于采取了更强的数据增强方式,作者在研究中发现,ImageNet预训练将毫无意义,因此,所有的模型均是从头开始训练的。

Anchor Free

关于Anchor Free可以参考以下论文:

  • Pp-yolov2: A practical object detector
  • CornerNet: Detecting Objects as Paired Keypoints
  • Objects as points

Anchor Free的方式是在Yolox-Darknet53进行应用。Anchor Free与Anchor Based的区别在于有没有anchor(感觉我在说废话),而anchor的作用是作为一个基准。回顾YOLOV3的方法,假设feature map某个网格中存在目标,则用基准的anchor去回归真实框,得到偏移量。而在YOLOX输出的feature map中,将模型中所有的预测框都囊括在85*8400的feature map中,里面包括不同尺度的预测框。


当有了这些预测框信息,每张图像也有标注框的信息,这时需要做的是将模型8400预测框和图片上的目标框进行关联,挑选出正样本锚框。这里采用的关联方式是标签分配。原理是Multi positives和SimOTA。

Multi positives

SimOTA

关于OTA的参考论文:OTA: Optimal Transport Assignment for Object Detection。在SimOTA中,不同目标设定不同的正样本数量(dynamick),以旷视科技​官方回答中的蚂蚁和西瓜为例子,传统的正样本分配方案常常为同一场景下的西瓜和蚂蚁分配同样的正样本数,那要么蚂蚁有很多低质量的正样本,要么西瓜仅仅只有一两个正样本。对于哪个分配方式都是不合适的。动态的正样本设置的关键在于如何确定k,SimOTA具体的做法是首先计算每个目标Cost最低的10特征点,然后把这十个特征点对应的预测框与真实框的IOU加起来求得最终的k。这一部分就是对框进行筛选。首先进行初步的框筛选:

  • 根据中心点判断:寻找anchor box中心点,落在gt_box矩形范围内的anchors
  • 根据目标框来预测:以gt中心点为基准,设置边长为5的正方形,挑选正方形内所有的锚框。

经过初步筛选后则可以精细化筛选:

  • 初筛正样本信息提取
  • Loss函数计算
  • cost成本计算
  • SimOTA求解

精细化筛选在此主要讲述SimOTA,假设当前图像有3个目标框,针对初步筛选出的1000个的正样本进行进一步计算。

  1. 首先计算1000个待处理的框以及3个gt_box计算分类损失cls_loss、位置损失iou_loss,置信度损失是将类别的条件概率和目标的先验概率做乘积。那么进一步根据cls_lossiou_loss得到cost成本计算,维度为[3, 1000],3表示的是3个预测框,1000表示待预测框。

  2. 得到1000个预测框,那么可以挑选k个iou最大的候选框,topk_ious,这里k取10。这时候需要进一步确定dynamic_k。

    通过上图,可以得知:目标框1和3,给他分配3个候选框,而目标框2,给它分配4个候选框。

  3. 利用前面计算的cost值,即[3,1000]的损失函数加权信息。在for循环中,针对每个目标框挑选,相应的cost值最低的一些候选框。

  4. 对于重复预测框对应不同的gt目标框,即第五列所对应的候选框,被目标检测框1和2,都进行关联。对这两个位置,还要使用cost值进行对比,选择较小的值,再进一步筛选。

  5. 对筛选预测框进行loss计算,要注意的是这里的iou_loss和cls_loss,只针对目标框和筛选出的正样本预测框进行计算。而obj_loss,则还是针对8400个预测框。

在这里如果看不明白可以参考:

  • YOLOX深度解析(二)-simOTA详解。
  • Anchor free系列网络之YOLOX源码逐行讲解篇(八)–simOTA标签匹配策略详解

代码实现

输入预处理

输入预处理主要是针对图像做一个Mosaic处理,具体代码如下:

def get_random_data_with_Mosaic(self, annotation_line, input_shape, max_boxes=500, jitter=0.3, hue=.1, sat=0.7, val=0.4):h, w = input_shapemin_offset_x = self.rand(0.3, 0.7)min_offset_y = self.rand(0.3, 0.7)image_datas = [] box_datas   = []index       = 0for line in annotation_line:line_content = line.split()image = Image.open(line_content[0])image = cvtColor(image)iw, ih = image.sizebox = np.array([np.array(list(map(int,box.split(',')))) for box in line_content[1:]])flip = self.rand()<.5if flip and len(box)>0:image = image.transpose(Image.FLIP_LEFT_RIGHT)box[:, [0,2]] = iw - box[:, [2,0]]new_ar = iw/ih * self.rand(1-jitter,1+jitter) / self.rand(1-jitter,1+jitter)scale = self.rand(.4, 1)if new_ar < 1:nh = int(scale*h)nw = int(nh*new_ar)else:nw = int(scale*w)nh = int(nw/new_ar)image = image.resize((nw, nh), Image.BICUBIC)if index == 0:dx = int(w*min_offset_x) - nwdy = int(h*min_offset_y) - nhelif index == 1:dx = int(w*min_offset_x) - nwdy = int(h*min_offset_y)elif index == 2:dx = int(w*min_offset_x)dy = int(h*min_offset_y)elif index == 3:dx = int(w*min_offset_x)dy = int(h*min_offset_y) - nhnew_image = Image.new('RGB', (w,h), (128,128,128))new_image.paste(image, (dx, dy))image_data = np.array(new_image)index = index + 1box_data = []if len(box)>0:np.random.shuffle(box)box[:, [0,2]] = box[:, [0,2]]*nw/iw + dxbox[:, [1,3]] = box[:, [1,3]]*nh/ih + dybox[:, 0:2][box[:, 0:2]<0] = 0box[:, 2][box[:, 2]>w] = wbox[:, 3][box[:, 3]>h] = hbox_w = box[:, 2] - box[:, 0]box_h = box[:, 3] - box[:, 1]box = box[np.logical_and(box_w>1, box_h>1)]box_data = np.zeros((len(box),5))box_data[:len(box)] = boximage_datas.append(image_data)box_datas.append(box_data)cutx = int(w * min_offset_x)cuty = int(h * min_offset_y)new_image = np.zeros([h, w, 3])new_image[:cuty, :cutx, :] = image_datas[0][:cuty, :cutx, :]new_image[cuty:, :cutx, :] = image_datas[1][cuty:, :cutx, :]new_image[cuty:, cutx:, :] = image_datas[2][cuty:, cutx:, :]new_image[:cuty, cutx:, :] = image_datas[3][:cuty, cutx:, :]new_image       = np.array(new_image, np.uint8)r               = np.random.uniform(-1, 1, 3) * [hue, sat, val] + 1#---------------------------------#hue, sat, val   = cv2.split(cv2.cvtColor(new_image, cv2.COLOR_RGB2HSV))dtype           = new_image.dtypex       = np.arange(0, 256, dtype=r.dtype)lut_hue = ((x * r[0]) % 180).astype(dtype)lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)lut_val = np.clip(x * r[2], 0, 255).astype(dtype)new_image = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val)))new_image = cv2.cvtColor(new_image, cv2.COLOR_HSV2RGB)new_boxes = self.merge_bboxes(box_datas, cutx, cuty)box_data = np.zeros((max_boxes, 5))if len(new_boxes)>0:if len(new_boxes)>max_boxes: new_boxes = new_boxes[:max_boxes]box_data[:len(new_boxes)] = new_boxesreturn new_image, box_data

输出后处理

在输出后处理主要是针对推理时候的处理。众所周知,YOLOX输出的三个不同尺度的feature map ( 20 × 20 , 40 × 40 , 80 × 80 ) (20\times20,40\times40,80\times80) (20×20,40×40,80×80),每个featuremap对应有三个分支分别为目标坐标分支、目标分类分支以及分类置信度分支。在推理的时候,假设在 20 × 20 20\times20 20×20的分支,如果 20 × 20 20\times20 20×20个特征点中某个特征点落在物体的对应框内,则进行预测。

具体步骤如下:

  1. 进行中心预测点的计算,利用Regression预测结果前两个序号的内容对特征点坐标进行偏移,左图红色的三个特征点偏移后是右图绿色的三个点;
  2. 进行预测框宽高的计算,利用Regression预测结果后两个序号的内容求指数后获得预测框的宽高;
  3. 得出所有框后进行非极大值抑制,则可得到最终结果
    代码如下:
def get_output(outputs, num_classes, input_shape, max_boxes = 100, confidence=0.5, nms_iou=0.3, letterbox_image=True):image_shape = K.reshape(outputs[-1], [-1])batch_size = K.shape(outputs[0])[0]grids = []strides = []hw = [K.shape(x)[1:3] for x in outputs]'''outputs before:batch_size, 80, 80, 4+1+num_classesbatch_size, 40, 40, 4+1+num_classesbatch_size, 20, 20, 4+1+num_classesoutputs after:batch_size, 8400, 8400, 4+1+num_classes'''outputs = tf.concat([tf.reshape(x, [batch_size, -1, 5 + num_classes]) for x in outputs], axis = 1)for i in range(len(hw)):grid_x, grid_y  = tf.meshgrid(tf.range(hw[i][1]), tf.range(hw[i][0]))grid            = tf.reshape(tf.stack((grid_x, grid_y), 2), (1, -1, 2))shape           = tf.shape(grid)[:2]grids.append(tf.cast(grid, K.dtype(outputs)))strides.append(tf.ones((shape[0], shape[1], 1)) * input_shape[0] / tf.cast(hw[i][0], K.dtype(outputs)))grids = tf.concat(grids, axis=1)strides = tf.concat(strides, axis=1)box_xy = (outputs[..., :2] + grids) * strides / K.cast(input_shape[::-1], K.dtype(outputs))box_wh = tf.exp(outputs[..., 2:4]) * strides / K.cast(input_shape[::-1], K.dtype(outputs))box_confidence  = K.sigmoid(outputs[..., 4:5])box_class_probs = K.sigmoid(outputs[..., 5: ])boxes = yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape, letterbox_image)box_scores  = box_confidence * box_class_probsmask = box_scores >= confidencemax_boxes_tensor = K.constant(max_boxes, dtype='int32')boxes_out   = []scores_out  = []classes_out = []for c in range(num_classes):class_boxes      = tf.boolean_mask(boxes, mask[..., c])class_box_scores = tf.boolean_mask(box_scores[..., c], mask[..., c])nms_index = tf.image.non_max_suppression(class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=nms_iou)class_boxes         = K.gather(class_boxes, nms_index)class_box_scores    = K.gather(class_box_scores, nms_index)classes             = K.ones_like(class_box_scores, 'int32') * cboxes_out.append(class_boxes)scores_out.append(class_box_scores)classes_out.append(classes)boxes_out      = K.concatenate(boxes_out, axis=0)scores_out     = K.concatenate(scores_out, axis=0)classes_out    = K.concatenate(classes_out, axis=0)return boxes_out, scores_out, classes_out

hw变量是获取每个feature map的宽高,其实这里说的feature map是不对的,只能认为是特征的shape。

然后重点是gridsstrides两个变量。各自的shape如下所示:


在程序中有box_xy = (outputs[..., :2] + grids) * strides / np.array(input_shape[::-1], np.float32),在此把他分成两部分分别为:(outputs[..., :2] + grids)strides / np.array(input_shape[::-1], np.float32)。第一部分表示获取所有 8 × 8 8\times8 8×8feature map的x,y具体是8400里面的前6400个值。后部分即进行映射。

参考

  1. 深入浅出 Yolo 系列之 Yolox 核心基础完整讲解
  2. 如何评价旷视开源的YOLOX,效果超过YOLOv5

YOLOX的深入理解相关推荐

  1. 简明扼要理解YOLOX

    由于疫情已经居家办公2周了,明天就可以正常通勤上班了,内心还有点小小的期待呢.趁着这三月暖暖的春风和屋外喳喳的鸟叫声,咱们来借鉴着大神的文章梳理一下YOLOX算法.如果对YOLO 系列算法还不了解的同 ...

  2. 我用YOLOX露了一手,记录一下模型部署、优化及训练的实现全过程

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 作者丨叶润源 来源丨https://www.yuque.com/ye ...

  3. 号称超过yolox和yolov5的PP-YoLoE

    PP-YoLoE | PP-YoLov2全面升级Anchor-Free,速度精度完美超越YoLoX和YoLov5 PP-YOLOE是基于PP-YOLOv2的卓越的单阶段Anchor-free模型,超越 ...

  4. 收藏 | YOLOX模型部署、优化及训练全过程

    点上方计算机视觉联盟获取更多干货 仅作学术分享,不代表本公众号立场,侵权联系删除 转载于:作者丨叶润源 来源丨https://www.yuque.com/yerunyuan/ar9831/tsm0id ...

  5. 简明扼要理解YOLO v3

    YOLO 系列目标检测算法在目标检测史上的具有里程碑式的意义,网上YOLO系列的文章也是数不胜数,今天我就结合几个比较好的文章以及我自己的理解,简明扼要记录一下YOLO的经典版本YOLO v3,虽然现 ...

  6. YOLOX源码解析--全网最详细,建议收藏!

    YOLOX源码解析 大家好,我是[豆干花生],这次我带来了YOLOX源码解析,与你分享~ 参考文献: https://blog.csdn.net/u011622208/article/details/ ...

  7. YOLOX的解耦头结构思考

    问题 YOLOX提出了一个Decoupled Head结构以代替YOLO Head,进而在YOLOv3 baseline的基础上提升了1.1个百分点的mAP,那为什么解耦头结构就能够提升检测效果呢? ...

  8. 从零深入理解Yolo系列理论v1-v8 + 目标检测面试提问

    目标检测-Yolo系列发展 先验框/锚框/候选框 Anchor机制 Anchors Base原理 Anchors Free原理 YOLO v1 Yolov1网络结构 Yolov1实现方法 Yolov1 ...

  9. YOLOX部署优化训练

    用给一个小不知名小板卡部署一下 不过这里用python 就当个玩具吧 YOLOX将近两年来目标检测领域的各个角度的优秀进展与YOLO进行了巧妙地集成组合并且重回Anchor Free的怀抱.本文详细的 ...

最新文章

  1. tp3.2.3实现后台的顶部栏目查询,不用拼接sql
  2. Struts2+Android (3) 多种方式向服务器发送信息
  3. linux进程看门狗使用方式,Linux系统中基于看门狗的精细化进程监控方法及系统的制作方法...
  4. Java最佳实践– Char到Byte和Byte到Char的转换
  5. 涉及子模块_COMSOL Multiphysics 5.6 RF模块更新详解
  6. python将数组传入mysql_通过python将文件中的数据传输到MySQL,传到,mysql
  7. Pantera Capital CEO:比特币有望在今年夏天达到11.5万美元
  8. redis 公网ip访问_怎样从公网访问内网Redis数据库
  9. Mybatis案例升级版——小案例大道理
  10. 【Django 2021年最新版教程27】数据库model 查询2个日期范围内的所有日期
  11. MSDN Windows XP Professional x64 Edition with SP2 +VL简体中文语言包+序列号
  12. 2020年如何写一个现代的JavaScript库
  13. 【ArcGIS】地表模型生成和显示
  14. 软件工程导论—总体设计
  15. 如何制定软件测试策略
  16. idt公司官网Alt-R HDR设计工具及模板使用说明
  17. 类与类之间的几种关系
  18. 在WinXP镜像中手工集成sata驱动。
  19. QT/C++标签文档报表工具
  20. 建立智慧的软件开发中心,第 5 部分: 智慧软件开发中心参考框架

热门文章

  1. Unicode编码是什么?
  2. Kivy之Button按钮
  3. java面向对象--超市购物程序
  4. 抽丝剥茧C语言(中阶)指针+练习
  5. 好用的行业和职业字典表
  6. SAT考试之SAT词汇记忆4步走
  7. emlog百度快速收录自动秒推送插件
  8. dialog 只能点击确认 取消和右上角的叉号才能关闭
  9. 基于APB与I2C的多主多从架构设计
  10. Mysql服务起不来