作者,略微@知乎(已授权)
来源丨https://zhuanlan.zhihu.com/p/358441134
极市平台编辑
文章开头直接放上自己的项目代码:
https://github.com/hukaixuan19970627/YOLOv5_DOTA_OBBgithub.com/hukaixuan19970627/YOLOv5_DOTA_OBB
(以下是最初版本的代码,最新代码是GitHub为准)
star?请多多益善。
现成的YOLOv5代码真的很香。不管口碑如何,反正我用起来挺爽的。毕竟一个开源项目的学术价值和工程应用价值只要占其中之一就值得称赞,v5在项目上手真的很友好,建议大家自己去体验一下。
本文默认读者对YOLOv对5的原理和代码结构有了基本的了解,如果从未接触过,请参考本文:
深眼:后浪进攻yolov5深度可视化分析:https://zhuanlan.zhihu.com/p/183838757
。原始YOLOv5项目的应用场景是自然场景中的目标,目标检测框是水平矩形框(Horizontal Bounding Box,HBB),毕竟我们的视角是水平视角。
当视角发生变化时,二维图像中物体的形状特征会发生变化。为了更好地匹配图像特征,人们提出了各种边框标记方法,如:

视角继续上升到无人机/卫星的高度,俯视角下物体的形状特征继续改变。此时,框架标记方法有更多选择:
至于选择合适的边框标记方法有什么作用,我个人的理解如下:
;先验越充分,网络的学习计划就越少,有利于约束网络的训练方向,减少网络的收敛时间;
当目标对象太紧时,可以避免准确的标记NMS错杀已检出的目标。
以本图为例,准确的标记可以确保物体之间的紧密性IOU为0;如果将标记方法改为水平目标边框,检测效果将是可怕的。
以上文章的主要思想是其实这种边界问题可以用一句话来概括:这句话可以参考下图来理解:
长边定义法以180度回归θ为例,θ ∈[-90,90);正常训练时,网络预测θ值为88,目标真实θ值为89,网络学习的角度距离为1,真实情况下两者之间的差异为1;在边界条件下,网络预测θ值为89,目标真实θ值为-90,网络学习的角度距离为179,真实情况下两者的差值为1.
那么如何处理边界问题呢?θ以边界问题为例)
(Anchor free/mask的思路,PolarDet、P-RSDet基于极坐标系表示任何四边形物体,BBA-Vectors、O^2-DNet以向量为基础表示有向矩形,ROPDet、Beyond Bounding Box、Oriented Reppoints用点集来表示任何形状的物体,)
使用损失函数Smooth L单独考虑每个参数时,,使得边界处θ差值可以很大,但是loss实际上变化很小;或者综合考虑所有回归参数的影响,使用旋转IoU然而,损失函数也可以避免边界问题RIoU不可导,近似可导的相关工作可以参考KLD、GWD,工程上实现RIoU可指导工作可参考:https://github.com/csuhan/s2anet/blob/master/configs/rotated_iou/README.md
θ从回归问题到分类问题离散连续问题,避免边界)
其中2,3yangxue大佬们都有相应的解决方案,可以去他的主页参考。CSL是3思想的体现,只是CSL因为当θ成为分类问题后,网络无法学习角度距离信息。例如,真实的角度是-90。我们期望网络预测89和-89造成的损失值是相同的,因为角度距离实际上是1。
所以让我们移动上面的文章。我们直接使用结果。基于180度回归的长边定义法中的参数仅为θ有边界问题,但是CSL刚好又可以处理了θ边界问题,那我们“CSL 长边定义法的组合相对较好。暂时的原因是yangxue在最新的文章中,老板提出了这种方法的缺点:
当时心情如下,还是方法1。anchor free一劳永逸一劳永逸;
但是这篇文章的一部分我还没有彻底理解,我们还是只用CSL 长边定义法就够了,后期的升级工作交给大家。
标注方案确定后,可开始一系列改造工作。
所有基于深度学习的目标检测器项目的结构都分为:
一、数据加载部分
首先,在进入网络之前,我们必须知道我们的数据形式是什么,因为我们使用长边定义法,所以我们的注释文件格式是:
[ classid x_c y_c longside shortside Θ ] Θ∈[0, 180) * longside: 旋转矩形框的最长边 * shortside: 对应最长边的另一边 * Θ: x轴顺时针旋转遇到最长边所经过的角度
至于如何转换数据形式,充分利用它 总结规则就可以了。我的另一篇文章说得很清楚。你可以搬家:
略略略:DOTA数据格式转YOLO数据格式工具(cv2.minAreaRect踩坑记录):https://zhuanlan.zhihu.com/p/356416158)
接下来是图像数据和label我们必须熟悉数据进入程序backbone以前,在数据加载器过程中labels数据的维度变化。
原始yolov5中,labels数据维度一直是(X_LT, Y_LT, X_RB, Y_RB)左上角右下角两点坐标表示水平矩形框存在,并一直在进行归一化和反归一化操作
因为我们采用的边框定义法是[x_c y_c longside shortside Θ],边框的角度信息只存在于θ我们完全可以做到 [x_c y_c longside shortside] 视为水平目标框架,所以我们只需要在数据加载部分labels原始数据的基础上添加一个θ维度,只要不涉及,就会引起labels角度变化的代码不需要改变其处理逻辑。
数据加载器中有大量的归一化和反一化操作,以及大量涉及图像宽度和高度的数据变化,,在长边定义法中longside和shorside与图像的宽度和高度没有严格的对应关系。
数据加载器涉及三种数据增强方法:。
其中Mosaic,仿射矩阵增强的目的是(X_LT, Y_LT, X_RB, Y_RB)增强数据格式,修改时添加θ维度是可以的,但是仿射矩阵增强函数 Translation、Shear、Rotation、Scale、Perspective、Center 6种据增强方式,其中。
所以只要超参数中的 ['perspective']=0,['degrees']=0 ,这块函数代码就不需要修改逻辑部分,为了方便我们直接把涉及到角度的增强放在最后的普通数据增强方式中。
二、Backbone部分、Neck部分
提取图像特征层的结构都不需要改动。
三、Head部分
head部分也就是yolo.py文件中的Detect类,由于我们将θ转为分类问题,因此每个anchor负责预测的参数数量为 (x_c y_c longside shortside score)+num_classes+angle_classes。修改Detect类的构造函数即可。
四、损失函数部分
损失函数共有四个部分:
(1)计算损失前的准备工作
损失的计算需要 targets 与 predicts,每个数据的维度都要有所对应,因此需要general.py文件中的build_targets函数生成目标真实GT的
其中Anchor索引列表用于检索网络预测结果中对应的anchor,从而将其标记为正样本。yolov5为了保证正样本的数量,在正样本标记策略中采用了比较暴力的策略:
这种处理逻辑个人暂不评价好坏,但是yolov5源码在代码实现上显然考虑不够周全,。这种bug表现在训练中就是某个时刻yolov5的训练就会中断:
Traceback (most recent call last):
File "train.py", line 457, in <module>
train(hyp, opt, device, tb_writer)
File "train.py", line 270, in train
loss, loss_items = compute_loss(pred, targets.to(device), model) # loss scaled by batch_size
File "/mnt/G/1125/rotation-yolov5-master/utils/general.py", line 530, in compute_loss
tobj[b, a, gj, gi] = (1.0 - model.gr) + model.gr * iou.detach().clamp(0).type(tobj.dtype) # iou ratio
RuntimeError: CUDA error: device-side assert triggered
上述报错显然是索引时超出数组取值范围的问题,解决方法也很简单,先查询是哪些参数超出了索引范围,当运行出错时,进入pdb调试,打印当前所有索引参数:
然后就发现(举例:featuremap大小为32×32,网格索引范围为0-31,但是build_targets函数偶尔会输出索引值32,此时出现bug,训练中断)
然而我当时在yolov5项目源码的Issues中却发现没人提交这种问题,原因也很简单, 如下图所示:
这个BUG属于yolov5源码build_targets函数生成anchor索引时考虑不周全导致的,解决办法也很简单,在生成的索引处加上数值范围限制():
重复利用就重复利用呗(~破罐破摔!~),本来yolov5的跨网格正负样本标记方式就会产生,这个地方感觉还有很多地方可以优化,但就是想不明白这样子回归明明会产生二义性问题为什么效果还是很好?
之后的改建部分也比较机械,在compute_loss函数和build_targets函数中添加θ角度信息的处理即可,主要注意数据索引的代码块就可以,由于添加了‘θ’ 180个通道,所以函数中所有的索引部分都要更改。
我又去yolov5的issues上看了看,似乎20年11月份修复了这个问题。我这个是基于20年10月11日的代码改建的,要是晚下载几天就好了,兴许能避开这个坑。
又看了下新的yolov5源码,很多地方大换血...... 改建的速度还没人家更新的速度快。
(2)计算损失
无需更改,注意数据索引部分即可。
由于我们添加的θ是分类任务,照葫芦画瓢,添加分类损失就可以了,值得注意的是θ部分的损失我们有两种方案:
一种就是正常的分类损失,同类别损失一样:BCEWithLogitsLoss;
先将GT的θ label经CSL处理后,再计算类别损失:BCEWithLogitsLoss。
项目代码中同时实现了两种方案,由csl_label_flag进行控制,csl_label_flag为True则进行CSL处理,否则计算正常分类损失,方便大家查看CSL在自己数据集上的提升效果:
yolov5源码中边框损失函数采用的是IOU/GIOU/CIOU/DIOU,适用于水平矩形边框之间计算IOU,原本是不适用于旋转框之间计算IOU的。由于框会旋转等原因,计算两个旋转框之间的IOU公式通常都不可导,如果θ为回归任务,势必要通过旋转IOU损失函数进行反向传播从而调整自身参数,大多数旋转检测器的处理办法都是将不可导的旋转IOU损失函数进行近似,使得网络可以正常进行训练。
不过因为我们将θ视为分类任务来处理,相当于将角度信息与边框参数信息解耦,所以旋转框的损失计算部分也分为角度损失和水平边框损失两个部分,因此源码部分可以不进行改动,边框回归损失部分依旧采用IOU/GIOU/CIOU/DIOU损失函数。
这一部分我们需要考虑清楚,yolov5源码是将GT水平边框与预测水平边框的IOU/GIOU/CIOU/DIOU值作为该预测框的置信度分支的权重系数,由于改建的情况特殊(水平边框+角度),我们有两种选择:
置信度分支的权重系数依然选择水平边框的之间的IOU/GIOU/CIOU/DIOU;
方案1相当于完全解耦预测角度与预测置信度之间的关联,置信度只与边框参数有关联,但事实上角度的一点偏差对旋转框IOU的影响是很大的,。
方案1速度比方案2训练快很多,gpu利用率也更稳定,而且预测出来的框的置信度相比来说会更高,就是可能错检的情况会多一点(θloss收敛正常,置信度loss收敛正常的话该情况会得到明显缓解)
方案2除了错检情况少一点以外,其余都是缺点,大家可以自行对比尝试。不过缺点后期可以通过cuda加速来改善,毕竟DOTA_devkit提供的C++库计算效率确实不高。再加上代码不是自己写的,想直接套用别的旋转IoU代码就只能用时间效率贼低的for循环来做。
方案2自然是为了避免上述情况的产生,此外也是对将角度解耦出去的一种”补偿“。(——detach的参数不会参与网络训练)
不会计算旋转IOU也没关系,DOTA数据集的作者额外提供了一个DOTA_devkit工具,里面有现成的C++库,我们直接调用即可。
五、其他修改部分
数据加载器(图像预处理)--> BackBone(提取目标特征) --> Neck(收集组合目标特征) --> Head(预测部分) --> 损失函数部分
以上部分基本修改完毕,接下来就是可视化的部分,利用好Opencv的三个函数即可:
# rect = cv2.minAreaRect(poly) # 得到poly最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
# box = np.float32(cv2.boxPoints(rect)) # 返回最小外接矩形rect的四个点的坐标
# cv2.drawContours(image=img, contours=[poly], contourIdx=-1, color=color, thickness=2*tl)
大家可以参考我上传的项目代码,里面基本每段代码都会有我的注解(主要是当时自己刚开始看yolov5源码,每句话都有注释)。
改建部分完结撒花,欢迎讨论!
本文仅做学术分享,如有侵权,请联系删文。
后台回复:
后台回复:
后台回复:
1.面向自动驾驶领域的多传感器数据融合技术
2.面向自动驾驶领域的3D点云目标检测全栈学习路线!(单模态+多模态/数据+代码)3.彻底搞透视觉三维重建:原理剖析、代码讲解、及优化改进4.国内首个面向工业级实战的点云处理课程5.激光-视觉-IMU-GPS融合SLAM算法梳理和代码讲解6.彻底搞懂视觉-惯性SLAM:基于VINS-Fusion正式开课啦7.彻底搞懂基于LOAM框架的3D激光SLAM: 源码剖析到算法优化8.彻底剖析室内、室外激光SLAM关键算法原理、代码和实战(cartographer+LOAM +LIO-SAM)
9.从零搭建一套结构光3D重建系统[理论+源码+实践]
10.单目深度估计方法:算法梳理与代码实现
11.自动驾驶中的深度学习模型部署实战
12.相机模型与标定(单目+双目+鱼眼)
13.重磅!四旋翼飞行器:算法与实战
14.ROS2从入门到精通:理论与实战
15.国内首个3D缺陷检测教程:理论、源码与实战
扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在
▲长按加微信群或投稿
▲长按关注公众号
学习3D视觉核心技术,扫描查看介绍,3天内无条件退款
圈里有高质量教程资料、答疑解惑、助你高效解决问题