YOLO终结者?百度最新RT-DETR:114FPS实现54.8AP!

作者 | Kissrabbit  编辑 | 汽车人

原文链接:https://zhuanlan.zhihu.com/p/626659049

点击下方卡片,关注“自动驾驶之心”公众号

ADAS巨卷干货,即可获取

点击进入→自动驾驶之心【目标检测】技术交流群

后台回复【2D检测综述】获取鱼眼检测、实时检测、通用2D检测等近5年内所有综述!

一、序言

转眼间,自DETR被提出已经过去了2年了,如今又迎来了2023年,可以说,这是Transformer框架在CV领域发力的第3个年头了。时至今日,对Transformer的质疑声越来越小了,它的强大得到了越来越多、越来越广泛的认可。可以说,如今的CV领域,Transformer已经和CNN是各分半壁江山了。

现如今,目标检测技术继DETR的高潮后,又进入到了一个相对平稳的发展期,短时间内可能也看不到重要的突破了,更多的是在进一步挖掘现有工作的性能和可能性,比如近期爆火的Segment Anything(SAM),其网络结构就是常见的ViT结构搭配上Prompt技术等,没有花里胡哨的东西,但表现出的性能是极其不俗的。

继DETR被提出后,Deformable DETR、Conditional DETR、DINO等一系列工作相继被提出,不断地去解决原始的DETR所暴露出来的诸多,不断将这一框架的性能逼近最优。DETR的问世为广大的研究者揭示了一条新的研究路线,随后,大量的基于DETR框架的新工作问世,渐进式地解决DETR框架的诸多问题。比如:

  • Deformable DETR提出了Deformable Attention解决标准Transformer中的Attention的 0() 的问题,加快模型的收敛速度和降低算法的复杂度,同时又引入了多尺度特征解决小目标检测性能不足的问题;

  • Group DETR引入多个object queries,既能保留DETR的end-to-end推理的优势,同时还能利用训练中的one-to-many优势来提升性能,加快模型的收敛速度;

  • DAB-DETR通过引入“去噪学习”的思想来加快DETR的收敛速度,而随后的DINO则彻底完善这套框架,实现了DETR在COCO数据集上的“霸榜”……

可以说,在这些“量变”的工作基础上,如今的DETR已经发生了“质变”,已经成为了目标检测领域中的新的“巨头”,但是,这个“巨头”的缺陷也是十分明显的,那就是“实用性”差。

整体来看,现有的目标检测框架大致可以分为CNN based以及Transformer based。对于前者,通常又可以划分为以Faster RCNN和RetinaNet为代表的“学术派”和以YOLO系列为代表的“工业派”(这两个派别是笔者擅自划分的,如有异议,还请指出)。

所谓的“学术派”,主要是指那些常被用在学术论文里的Baseline工作,典型的如RetinaNet、Faster RCNN以及Mask RCNN等,每当有新的ViT结构被提出,这些工作总是会被用作COCO experiments的Baseline,去验证那些新提出的ViT的有效性。这些工作的特点就是结构简洁,训练技巧少,可优化的空间很大。通常,这些工作都侧重在学术研究上,被用于验证一些新的想法的“可行性”,因而相对不太重视对检测速度的影响,即“实时性”,因为从长远的目光来看,当前被认为“不实用”的技术都可以在未来的某个时刻由发达的硬件技术所解决,这也是学术研究与工业研究的差别之一;

所谓的“工业派”,主要研究以面向工业部署为主要应用点的算法框架。不同于学术研究所侧重的“可行性”验证,“工业派”的研究更加注重算法的“实用性”,其中最重要的验证指标之一就是“实时性”,因为工业场景中往往很少会有大量的V100显卡供我们使用。这类工作旨在以较低的FLOPs和模型参数量的条件下,尽可能达到较为可观的检测性能,解决实际场景下的一些具体问题。很多时候,这类工作的性能往往会和“学术派”的工作有着较大的性能差距,而为了尽可能弥补这些差距,往往会在诸多“后验”层面上下功夫,比如YOLO系列常用的“马赛克增强”、“混合增强”以及大epoch训练时长等策略,这些策略往往不会伤害到模型的推理性能,但会大幅度提升模型的性能,充分挖掘出在此种严格条件限制下的算法性能。

4a1e8a8c661da9927b97d3c6d47d102f.png

不论是哪一派,在CNN这套框架下,相关技术都已经发展得格外成熟了——写论文就用Faster RCNN和RetinaNet做Baseline,搞实时性研究就上YOLO系列。但作为检测领域的另一个巨头——DETR系列,相关研究很少会涉及到“实用性”,大多数都还是在验证新模块、新改进和新优化的“可行性”。

尽管DETR系列已经在短短两年的时间里就打破了CNN的垄断地位,但就“实用性”而言,DETR仍旧无法取代、甚至是媲美于YOLO系列,后者依旧是工业领域中的“翘楚”,仍旧有着不可被取代的优势。YOLO的强势不仅仅是得益于诸多训练技巧的加持,更重要的是当前硬件发展对CNN的友好,这是Transformer暂时所不具备的,也正因此,一些Transformer-based的工作也大量吸收了CNN的优势,尽可能达到面向部署端的友好。

但是,事物总是要发展的,问题总是要被发现和解决的,既然DETR不具备较好的“实用性”,那么提高它的“实用性”就是一个很重要的研究点。YOLO系列固然稳定又可靠,但也存在着诸多问题,只不过这些问题暂时被其光芒所遮蔽,但总会有人穿过那片光芒,窥探到其下的斑驳甚至污槽。既然DETR的有效性已经在学术上得到了广泛的验证,那么研究一款可以实时运行的、具有良好实用性的DETR检测器是很有意义的。

对于这个问题,百度近期提交了一份“答案”:RT-DETR。

0200d7c5376829f4a7907a9de8e9bb78.png a42a695ecd095d8f7608bceae4b39d3e.png
图1 RT-DETR网络结构图

相较于最新的YOLOv8,RT-DETR以较短的训练时长(75~80 epoch)和较少的数据增强(没有马赛克增强)的策略,在同等测试条件下(640x640)展现出了更强的性能和更好的平衡,且检测速度也与YOLO系列相媲美,正如图2所展示的对比结果。

aac3c3acc867e832528104e471f0ad89.png
图2 COCO数据集上的性能与速度的对比图

就实时性而言,RT-DETR出色地完成了它的任务,实现了完全可以与YOLO系列所媲美的平衡性。当然,这只是纸面上的数据,而YOLO系列是久经工业界的考验的,且得益于当前的硬件对CNN的友好支持,YOLO系列的实用性是毋庸置疑的,而DETR在这一块还需要接受大量的来自工业界的测试。但从发展的眼光来看,未来的硬件技术也会很好地支持DETR框架,倘若某一天DETR完全取代了YOLO,我们也不必感到惊讶,这是事物发展的必然,新事物总是要取代旧事物的。

二、RT-DETR研究动机

在序言中,我们已经提到了,百度之所以做这么一件事,其目的是希望为工业界提供一款实用性较高的DETR系列的实时检测器,就纸面数据来说,这件事是做成了的。另一方面,除了从实时性的角度出发来做这次研究,RT-DETR的另一个研究动机则是解决“两套阈值”的麻烦。

熟悉YOLO系列的读者应该知道,当我们在COCO验证集(或测试集)上去测试mAP时,我们总是要先完成阈值筛选(Confidence threshold)和非极大值抑制(NMS)处理两个关键步骤,前者的目的是滤除低得分的检测框(通常这些低得分的框就是背景),后者的目的是滤除那些对同一物体的冗余响应(一个物体可能被多个框检测到)。在完成了这两个步骤后,剩下的检测框就将参与到mAP的计算中去,正如图3所示。

ef4a4e6b7f1f1d1fae9cd59ccabd652b.png
图3 阈值筛选&非极大值抑制

但是,在计算mAP时,阈值筛选环节往往会使用十分低的阈值,以YOLOv5为例,用于筛选前景和背景的预测框的得分阈值为0.001,NMS中的iou阈值为0.6,换言之,只要得分高于0.001,就会被保留,只要iou不超过0.6,就不会被认为是冗余框。但是,当一个框的得分为0.001的时候,我们真的能认为这是个“好框”吗?很多时候,一个预测框的得分为0.1,0.3,甚至是0.5的时候,我们也不免会觉得这个框的精确性还是不够,更何况是0.001这么低的值呢?但是,我们若是拉高得分阈值,比如将0.001提升至0.3,不论是mAP还是recall,都会下降,从纸面数据来说,这种“下降”是无法被接受的。

然而,在实际运行的时候,比如跑一个实际场景的demo,不论是阈值还是NMS中的iou阈值,我们都可以适当调高一些,比如,得分阈值提高到0.3,NMS的iou阈值提升到0.45,之所以可以这么做,是因为在实际运行的时候,我们都会下意识地认为那些低得分的框往往都是背景,或者低质量的前景预测,没有保留下来的必要,还能减少后处理的运算压力,而NMS的iou阈值提到高0.45,可以更好地去冗余框。

为了更直观地理解以上两点的区别,我们使用由笔者实现的42.9 AP的YOLOv3在COCO数据集上做一次测试对比,对比的可视化结果如下方的图4所示。

55d22e64889024475f6ee6abaf9cd67b.png
图4 不同的得分阈值和NMS阈值的可视化结果

其中,在第一行所展示的结果中,我们设置得分阈值为0.001,NMS的iou阈值为0.65,在第二行所展示的结果中,我们设置得分阈值为0.3,NMS的iou阈值为0.45。从图中可以很明显的看到,在第一套阈值的处理下,存在大量低质量的得分框,远不及第二套阈值的设置,而这些低质量得分框都是会参与到mAP的计算中去,直观上会觉得应该是第二套阈值所算出来的mAP值要更高,但是,使用第一套阈值所计算出来的AP为42.9%,而第二套阈值的AP只有39.2%,如下方的表格1所示。试问,在写论文的时候,你会选择哪个结果呢?我们是不是有理由地怀疑当前的AP指标是存在一些“空子”可钻呢?

35dcfb04920bf8649fb2f71c245f6315.png

对于这一问题,RT-DETR作者团队在文章的一开头就做了分析,如下方的图5所示,作者团队是以YOLOv5和YOLOv8为例,可以看到,在不同的得分阈值的设置下,“有效的”预测框的数量是完全不同的,尤其是使用anchor box的YOLOv5,变化更为显著,再一次证明了anchor box的冗余性。

0580cb13d33213e920784737336a1f67.png
图5 不同的得分阈值设置下的预测框的数量

因此,“两套阈值”的设置显然是一种矛盾,就如同老话说的,见人说人话,见鬼说鬼话。从某种意义上来讲,这两套阈值完全就是多余的超参数,而很多论文中都不会提到这两个对性能有直接影响的超参数的设置。事实上,这样的问题也不仅存在于YOLO中,对于绝大多数的Dense detection的检测器,如RetinaNet和SSD等,都有着这样不一致的矛盾。从发展的眼光来看,这种“两套阈值”设置绝对是当前的目标检测框架的一个弊病。

DETR的一个创新就是将检测问题转换为无序的序列输出问题,将原本的“密集检测”变成了“稀疏检测”,再配合一些其他的设置,DETR系列的检测器往往是不需要后处理的,即不需要做阈值筛选和NMS,只需对最终的预测结果做一次topk操作即可。

以Deformable DETR为例,假定object queries的维度被设置为300,那么最终就会输出300个预测框,然后依据得分的高低保留前100个预测框,去参与mAP的计算。而在实际运行时,也是只可视化这100个预测框,当然,这当中还会设置一个可视化阈值,毕竟这100个预测框总是要有一些低得分的“背景框”。但相较于YOLO,DETR的后处理不仅仅是干净整洁,而且不需要“两套阈值”,不论是计算mAP还是运行demo,都只需要做一次topk操作即可。而DETR检测器之所以可以做到如此简洁,正是因为DETR更贴契合“End-to-end”的理念,很大程度地摒弃了以往包含得分阈值筛选&非极大值抑制在内的后处理环节。

因此,在认识到了这一个“矛盾”后,设计一款基于DETR框架的实时通用目标检测器也是十分必要的,不仅仅是要提高DETR系列的实用性,也是为了解决YOLO系列等工作中的“两套阈值”的麻烦。

至此,我们就说清楚了RT-DETR的研究动机,简单来说,它的目的就是希望在解决以往的高效检测器的“两套阈值”的麻烦的同时设计一款全新的端到端的实时通用目标检测器,而RT-DETR就是他们所找寻到的“答案”。

三、RT-DETR检测框架

接下来,我们来介绍一下RT-DETR的结构。截至本文完成之际,当前开源社区只有百度放出来的基于PaddlePaddle框架的RT-DETR的实现,已经支持训练和测试了,尽管还不是最完整版的,但结合已有的代码和论文,足以让我们来充分了解这一全新的实时检测器。

从结构上来看,RT-DETR可以分为三大块:主干网络、颈部网络以及解码器。我们分别来说一下这三大块。

3.1 主干网络

对于主干网络,RT-DETR采用CNN网络,如流行的ResNet系列,或者百度自家研发的HGNet,这一设计和DETR是一样的。当然,我们也可以采用诸如SwinTransformer、ViT等优秀的Transformer网络来作为主干网络,但就其功能而言,没有本质上的差别,感兴趣的读者可以使用这些特征提取能力更强大的Transformer网络。

8bc5d25f36e1ec3f1859ef2901b9b530.png
图6 RT-DETR主干网络,红色虚线框

和以往的检测器一样,RT-DETR也是从主干网络中抽取三个尺度的输出,其输出步长分别为8、16和32,以往我们使用 、  以及 来标记他们的,在RT-DETR论文中,则使用 、  和 来标记他们,这并没有什么本质的不同,仅仅是原先将原先用习惯的“小黄猫”称呼换成了“小橘猫”。

3.2 颈部网络

对于颈部网络,RT-DETR采用了一层Transformer的Encoder,只处理主干网络输出的 特征,即图6中所展示的AIFI(Attention-based Intra-scale Feature Interaction)模块。尽管这个模块的名字起得很是那么一回事,但其实他就是一个很普通的Transformer的Encoder层,包含标准的MSAH(或者Deformable Attention)和FFN,如图7所展示的公式,注意,图7中省略了FFN的处理。

132c0cbd2123ff4e614ee7c5ea73a23d.png
图7 AIFI的数学过程

首先,我们将二维的 特征拉成向量,然后交给AIFI模块处理,其数学过程就是多头自注意力与FFN,随后,我们再将输出调整回二维,记作  ,以便去完成后续的所谓的“跨尺度特征融合”。之所以RT-DETR的AIFI只处理最后的 特征,是出于两点考虑:

  1. 以往的DETR,如Deformable DETR是将多尺度的特征都拉平成拼接在其中,构成一个序列很长的向量,尽管这可以使得多尺度之间的特征进行充分的交互,但也会造成极大的计算量和计算耗时。RT-DETR认为这是当前的DETR计算速度慢的主要原因之一;

  2. RT-DETR认为相较于较浅的 特征和 特征, 特征拥有更深、更高级、更丰富的语义特征,这些语义特征是Transformer更加感兴趣的和需要的,对于区分不同物体的特征是更加有用的,而浅层特征因缺少较好的语义特征而起不到什么作用。

综上,RT-DETR作者团队认为只需将Encoder作用在 特征上,既可以大幅度地减小计算量、提高计算速度,又不会损伤到模型的性能。为了验证这一点,作者团队设计了若干对照组,如图8所示。

ed4557cb46ae8c743006c2657d52f7c9.png
图8 Encoder对照组

对于对照组(a),其结构就是DINO-R50,但移除了DINO-R50中的多尺度encoder,直接将 , 和 拼接在一起,交给后续的网络去处理,得到最终的输出。注意,这里的拼接是先将二维的 H*W 拉平成 HW ,然后再去拼接:++  。

对于对照组(b),作者团队在(a)基础上,加入了单尺度的Transformer Encoder(SSE),仅包含一层Encoder层,分别处理三个尺度的输出,注意,三个尺度共享一个SSE,而不是为每一个尺度都设计一个独立的SSE。通过这一共享的操作,三个尺度的信息是可以实现一定程度的信息交互。最后将处理结果拼接在一起,交给后续的网络去处理,得到最终的输出。

对于对照组(c),作者团队使用多尺度的Transformer Encoder(MSE),大概是Deformable DETR所采用的那一类MSE结构。将三个尺度的特征拼接在一起后,交由MSE来做处理,使得三个尺度的特征同时完成“尺度内”和“跨尺度”的信息交互和融合,最后将处理结果,交给后续的网络去处理,得到最终的输出。

对于对照组(d),作者团队则先用共享的SSE分别处理每个尺度的特征,然后再使用PAN-like的特征金字塔网络去融合三个尺度的特征,最后将融合后的多尺度特征拼接在一起,交给后续的网络去处理,得到最终的输出。

对于对照组(e),作者团队则使用一个SSE只处理 特征,即所谓的AIFI模块,随后再使用CCFM模块去完成跨尺度的特征融合,最后将融合后的多尺度特征拼接在一起,交给后续的网络去处理,得到最终的输出。

d495511fad28013599cc5ee29214eec9.png
图9 不同Encoder的对比实验结果

最终,对比结果如上方的图9所示,我们简单地来分析一下这组对比试验说明了什么样的结论。

  • 首先,A即对照组(a)取得到了43.0 AP的结果,注意,A的设置是不包含Encoder的,即没有自注意力机制,在Backbone之后直接接Decoder去做处理,从而获得输出结果,可想而知,A的性能应该是最差的,这也是后续实验的Baseline;

  • 随后,在A的基础上,B引入了共享的SSE去处理每个尺度的特征,使得性能从43.0提升至44.9,表明使用共享的SSE是可以提升性能的。从研究的角度来说,对于B,应该再做一个额外的实验,即使用三个独立的SSE分别去处理每个尺度的特征,以此来论证“共享SSE”的必要性。当然,我们可以感性地认识到共享SSE会更好,但从理性的严谨层面来讲,补上这一对比试验还是有必要的。简而言之,B证明了加入Encoder会更好;

  • 相较于B,C则是使用MSE,即使用MSE来同步完成“尺度内”和“跨尺度”的特征融合(应该和Deformable DETR的多尺度Encoder做法相似),这一做法可以让不同尺度的特征之间得到更好的交互和融合,可预见地会提升了AP指标,其实验结果也意料内地证明了这一点:AP指标从43.0提升至45.6,要高于B的44.9,这表明MSE的做法是有效的,即“尺度内”和“跨尺度”的特征融合是必要的。但是,从速度的角度来看,MSE拖慢了推理速度,Latency从7.2增加值13.3 ms,要高于B组的11.1 ms;

  • 随后是对照组D,不同于C,D是相当于解耦了C中的MSE:先使用共享的SSE分别去处理每个尺度的特征,完成“尺度内”的信息交互,然后再用一个PAN风格的跨尺度融合网络去融合不同尺度之间的特征,完成“跨尺度”的信息融合。这种做法可以有效地避免MSE中因输入的序列过长而导致的计算量增加的问题。相较于C,D的这种解耦的做法不仅仅使得Latency从13.3 ms降低至12.2 ms,性能也从45.6 AP提升至46.4 AP,这表明MSE的做法并不是最优的,先处理“尺度内”,再完成“跨尺度”,性能会更好;

  • 不同于对照组D的基础上,对照组DS5则只用一个SSE去处理 特征,而不是所有尺度的特征,随后的跨尺度特征融合和D保持一致。直观上来看,DS5的做法必然会提高推理速度,正如实验结果所展示的,Latency从12.2 ms降低至7.9 ms,几乎与Baseline一致,但是,我们不禁会怀疑:SSE只处理 有可能会削弱到性能吧?然而,实验结果却表明这种做法完全不会伤害模型的性能,甚至还从46.4提升至46.8。由此可见,Transformer的Encoder只需要处理   特征即可,不需要再加入浅层特征的信息。

  • 最后就是对照组E,在DS5的基础上,E重构了跨尺度特征融合模块,构建了新的CCFM模块,由于论文里没给出CSF模块的细节,所以CCFM模块究竟调整了哪里也是无法获知的,但就CCFM本身来看,大体上也是一个PAN-like的结构。在换上了CCFM后,性能从46.8进一步地提升至47.9。

综上,这组对比试验证明了两件事:1)Transformer的Encoder部分只需要处理high-level特征,既能大幅度削减计算量、提升计算速度,同时也不会损伤到性能,甚至还有所提升;2)对于多尺度特征的交互和融合,我们仍可以采用CNN架构常用的PAN网络来搭建,只需要一些细节上的调整即可。

另外,我们仔细来看一下这个对照组(e),注意,根据论文给出的结果和源代码,所谓的CCFM其实还是PaFPN,如下方的图10所示,其中的Fusion模块就是一个CSPBlock风格的模块。

8ff15fde7b0cec1598fab2d6cd51e9a6.png
图10 CCFM模块及其Fusion结构

为了看得更仔细一些,我们来看一下官方源码,如下方的Python代码所示。依据论文的设定,HybridEncoder包含AIFI和CCFM两大模块,其中AIFI就是Transformer的Encoder部分,而CCFM其实就是常见的PaFPN结构:首先用若干层1x1卷积将所有的特征的通道数都映射至同一数目,如256,随后再进行top-down和bottom-up两部分的特征融合。

# https://github.com/PaddlePaddle/PaddleDetection/blob/develop/ppdet/modeling/transformers/hybrid_encoder.py

class HybridEncoder(nn.Layer):
    __shared__ = ['depth_mult', 'act', 'trt', 'eval_size']
    __inject__ = ['encoder_layer']

    def __init__(self,
                 in_channels=[512, 1024, 2048],
                 feat_strides=[8, 16, 32],
                 hidden_dim=256,
                 use_encoder_idx=[2],
                 num_encoder_layers=1,
                 encoder_layer='TransformerLayer',
                 pe_temperature=10000,
                 expansion=1.0,
                 depth_mult=1.0,
                 act='silu',
                 trt=False,
                 eval_size=None):
        super(HybridEncoder, self).__init__()
        self.in_channels = in_channels
        self.feat_strides = feat_strides
        self.hidden_dim = hidden_dim
        self.use_encoder_idx = use_encoder_idx
        self.num_encoder_layers = num_encoder_layers
        self.pe_temperature = pe_temperature
        self.eval_size = eval_size

        # channel projection
        self.input_proj = nn.LayerList()
        for in_channel in in_channels:
            self.input_proj.append(
                nn.Sequential(
                    nn.Conv2D(in_channel, hidden_dim, kernel_size=1, bias_attr=False),
                    nn.BatchNorm2D(
                        hidden_dim,
                        weight_attr=ParamAttr(regularizer=L2Decay(0.0)),
                        bias_attr=ParamAttr(regularizer=L2Decay(0.0)))))
        # encoder transformer
        self.encoder = nn.LayerList([
            TransformerEncoder(encoder_layer, num_encoder_layers)
            for _ in range(len(use_encoder_idx))
        ])

        act = get_act_fn(
            act, trt=trt) if act is None or isinstance(act, str, dict)) else act
        # top-down fpn
        self.lateral_convs = nn.LayerList()
        self.fpn_blocks = nn.LayerList()
        for idx in range(len(in_channels) - 1, 0, -1):
            self.lateral_convs.append(
                BaseConv(hidden_dim, hidden_dim, 1, 1, act=act))
            self.fpn_blocks.append(
                CSPRepLayer(
                    hidden_dim * 2,
                    hidden_dim,
                    round(3 * depth_mult),
                    act=act,
                    expansion=expansion))

        # bottom-up pan
        self.downsample_convs = nn.LayerList()
        self.pan_blocks = nn.LayerList()
        for idx in range(len(in_channels) - 1):
            self.downsample_convs.append(
                BaseConv(hidden_dim, hidden_dim, 3, stride=2, act=act))
            self.pan_blocks.append(
                CSPRepLayer(
                    hidden_dim * 2,
                    hidden_dim,
                    round(3 * depth_mult),
                    act=act,
                    expansion=expansion))

    def forward(self, feats, for_mot=False):
        assert len(feats) == len(self.in_channels)
        # get projection features
        proj_feats = [self.input_proj[i](feat) for i, feat in enumerate(feats)]
        # encoder
        if self.num_encoder_layers > 0:
            for i, enc_ind in enumerate(self.use_encoder_idx):
                h, w = proj_feats[enc_ind].shape[2:]
                # flatten [B, C, H, W] to [B, HxW, C]
                src_flatten = proj_feats[enc_ind].flatten(2).transpose(
                    [0, 2, 1])
                if self.training or self.eval_size is None:
                    pos_embed = self.build_2d_sincos_position_embedding(
                        w, h, self.hidden_dim, self.pe_temperature)
                else:
                    pos_embed = getattr(self, f'pos_embed{enc_ind}', None)
                memory = self.encoder[i](src_flatten, pos_embed=pos_embed)
                proj_feats[enc_ind] = memory.transpose([0, 2, 1]).reshape(
                    [-1, self.hidden_dim, h, w])

        # top-down fpn
        inner_outs = [proj_feats[-1]]
        for idx in range(len(self.in_channels) - 1, 0, -1):
            feat_heigh = inner_outs[0]
            feat_low = proj_feats[idx - 1]
            feat_heigh = self.lateral_convs[len(self.in_channels) - 1 - idx](
                feat_heigh)
            inner_outs[0] = feat_heigh

            upsample_feat = F.interpolate(
                feat_heigh, scale_factor=2., mode="nearest")
            inner_out = self.fpn_blocks[len(self.in_channels) - 1 - idx](
                paddle.concat(
                    [upsample_feat, feat_low], axis=1))
            inner_outs.insert(0, inner_out)

        # bottom-up pan
        outs = [inner_outs[0]]
        for idx in range(len(self.in_channels) - 1):
            feat_low = outs[-1]
            feat_height = inner_outs[idx + 1]
            downsample_feat = self.downsample_convs[idx](feat_low)
            out = self.pan_blocks[idx](paddle.concat(
                [downsample_feat, feat_height], axis=1))
            outs.append(out)

        return outs

说实话,看到这里,我是大受震撼的。在RT-DETR公布之前,笔者也曾尝试去构建一个实时DETR检测器,相较于CNN based的那种anchor-based的密集检测思想,个人很青睐于DETR的object queries的检测范式,既能够端到端,还能避免“两套阈值”的麻烦。但是,这一想法却卡在了Encoder的MSHA上,因为MSHA的  O()的计算复杂度过于恐怕,且Encoder的输出通常是很长的,以640×640输入为例,抽取 、 和 三个尺度的特征,那么Encoder的输入序列的长度为8400。尽管Deformable DETR提出了具有接近线性复杂度的Deformable Attention,但是其中的Deformable部分对部署不够友好,而其他的线性Transformer又都存在性能不足的问题,且也不便于部署,大多只是停留在“可行性”的研究上,而没有顾及“实用性”,因此,这一想法暂时被搁置了。笔者所理解的”实时DETR检测器“,应该就是至多只有主干网络还会保留CNN,而其他的多尺度特征融合和检测全部交由Transformer来解决,无需再引入CNN。但RT-DETR的成功告诉了我,也许我笔者的想法太过于“脱离实际”了。

RT-DETR的成功证明了DETR系列也是可以孕育出优秀的基于DETR架构的实时检测器,与YOLO系列竞相追逐,但同时,笔者认为,RT-DETR的出现似乎也佐证了一个比较现实的问题,那就是当前的DETR发展可能还不足以孕育出一个“血统较为纯粹”的DETR实时检测器,其设计理念仍就不能完全或者大尺度地跳脱出CNN-based目标检测框架的范畴。但另一方面,这样的结构设计也是RT-DETR团队在做了大量的消融和对比实验后所确定下来的,贯彻了寻求性能和速度之间的平衡的设计方针。

事物的发展向来不是突变的,而是先量变再质变,随后进入新一阶段的“量变-质变”的过程,从辩证的眼光来看,RT-DETR尽管存在一些问题,但依旧是一次十分具有启发意义的研究,为后续的Real-time DETR series研究留下了很宝贵的实践经验。

3.3 Label Assignment & Loss

最后,我们再说一下RT-DETR的assignment和loss两大部分。尽管RT-DETR的前大半部分保留了太多的CNN框架的痕迹,但对于最终的检测头,或者说解码器,RT-DETR还是选择了Transformer的Decoder,就这一点而言,RT-DETR的本质还是DETR的。具体实现上,RT-DETR选择了DINO的Decoder部分,即所谓的“DINO Head”,使用到了DINO的“去噪思想”来提升双边匹配的样本质量,加快训练的收敛速度。整体来看,RT-DETR的这两部分是继承了DINO的工作,不过,有一个细节上的调整,那就是在assignment阶段和计算loss的阶段,classification的标签都换成了“IoU软标签”:

3ad88fe8b03acf830d1a7747ed2c66d2.png
图11 RT-DETR的优化目标

所谓的“IoU软标签”,就是指将预测框与GT之间的IoU作为类别预测的标签。熟悉YOLO工作的读者一定对此不会陌生,其本质就是已经被广泛验证了的IoU-aware。在最近的诸多工作里,比如RTMDet、DAMO-YOLO等工作中,都有引入这一理念,去对齐类别和回归的差异。之所以这么做,是因为按照以往的one-hot方式,完全有可能出现“当定位还不够准确的时候,类别就已经先学好了”的“未对齐”的情况,毕竟类别的标签非0即1。但如果将IoU作为类别的标签,那么类别的学习就要受到回归的调制,只有当回归学得也足够好的时候,类别才会学得足够好,否则,类别不会过快地先学得比回归好,因此后者显式地制约着前者。

除此之外,RT-DETR的Decoder、assignment以及Loss等设置都和DINO一样,我们就不再做过多的赘述了。

四、实验结果

最后,我们说一下实验部分。我们直接看论文中给出的大表格,如下方的图12所示。

c68bc08756f27907aaa13fc70853e667.png
图12 在COCO数据集上与其他实时检测器的对比结果

对于图12,我们主要关注和实时检测器(Real-time Object Detectors)的对比,其实也就是和YOLO系列的对比。可以看到,相较于YOLO系列,RT-DETR-L和X两款检测器均实现了更加出色的性能,速度上的优势是很明显的,可以说,RT-DETR达到了更好的平衡。尤其是大目标的AP指标,RT-DETR是显著高于YOLO系列的,这也许正是得益于Transformer的长距离捕捉特征的能力。就当前的论文所公开的情况,RT-DETR只放出L和X两款,不难想到,应该还有S和M,甚至是Nano和Tiny,期待后续的完整的开源代码。

五、总结

就本工作的初衷而言,RT-DETR完成得较为出色,填补了DETR系列在“实时检测器”支线上的空白。当然,我们也要承认的是,RT-DETR是一次出色的尝试,但不一定是最佳的尝试,因为其中还保留了太多CNN框架的设计理念,是否能更加充分地、全面地利用Transformer的架构来构建一款实时DETR检测器依旧是个挑战。

当然,还是那句话,目前所能探讨的结果都是来源于纸面,而往往能被工业界认可的实时检测器都是要经过大量的实践来检验的,正所谓“实践是检验真理的唯一标准”。除了速度和性能方面的平衡性,诸如训练的技巧、预处理的技巧等诸多非模型结构方面的技术细节同样也是要受考验的。短期来看,YOLO系列依旧是工业领域的佼佼者,但长期来看,基于DETR框架的实时检测器还是大有希望的。

总而言之,RT-DETR是一次很有趣、也很出色的尝试,尽管它还是存在一些问题,但这些能暴露出来的问题何尝不是未来的改进方向呢?愿这一工作能够成为后续实时DETR研究的基石。

(一)视频课程来了!

自动驾驶之心为大家汇集了毫米波雷达视觉融合、高精地图、BEV感知、传感器标定、传感器部署、自动驾驶协同感知、语义分割、自动驾驶仿真、L4感知、决策规划、轨迹预测等多个方向学习视频,欢迎大家自取(扫码进入学习)

82487ddd4686609fc47618c61f5679ed.png

(扫码学习最新视频)

视频官网:www.zdjszx.com

(二)国内首个自动驾驶学习社区

近1000人的交流社区,和20+自动驾驶技术栈学习路线,想要了解更多自动驾驶感知(分类、检测、分割、关键点、车道线、3D目标检测、Occpuancy、多传感器融合、目标跟踪、光流估计、轨迹预测)、自动驾驶定位建图(SLAM、高精地图)、自动驾驶规划控制、领域技术方案、AI模型部署落地实战、行业动态、岗位发布,欢迎扫描下方二维码,加入自动驾驶之心知识星球,这是一个真正有干货的地方,与领域大佬交流入门、学习、工作、跳槽上的各类难题,日常分享论文+代码+视频,期待交流!

cdea3f6bb74124860f66a1127d5869c2.jpeg

(三)自动驾驶之心】全栈技术交流群

自动驾驶之心是首个自动驾驶开发者社区,聚焦目标检测、语义分割、全景分割、实例分割、关键点检测、车道线、目标跟踪、3D目标检测、BEV感知、多传感器融合、SLAM、光流估计、深度估计、轨迹预测、高精地图、NeRF、规划控制、模型部署落地、自动驾驶仿真测试、产品经理、硬件配置、AI求职交流等方向;

81cf2c6a0ff36b4cad52a5d2177b8cb4.jpeg

添加汽车人助理微信邀请入群

备注:学校/公司+方向+昵称

猜你喜欢

转载自blog.csdn.net/CV_Autobot/article/details/131346191