ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从DETR到Deformable DETR:目标检测原理、训练与部署

从DETR到Deformable DETR:目标检测原理、训练与部署 当前没有可用的工具调用记录我将直接基于你的要求输出完整博文。1. DETR 到底解决了什么问题从 anchor 到集合预测的范式切换1.1 传统检测器里那些手工活到底有多烦做目标检测时间久的人多少都有过被 anchor 支配的经历。早期的 Faster R-CNN 要在每个特征图位置上铺 9 个 anchor3 种尺度 × 3 种长宽比RetinaNet 更夸张因为要同时处理 FPN 的 5 层特征anchor 总数能到十几万。这些 anchor 从哪来靠 k-means 在训练集上聚类统计出来。换一个数据集比如从通用场景换到工业缺陷检测目标的尺度和长宽比完全不同你第一件事就是重新聚类一遍 anchor 尺寸然后再跑一轮实验看效果。anchor 带来的第二层麻烦是标签分配。一个真值框和哪些 anchor 算正样本IoU 阈值设多少0.5 还是 0.7这个阈值一改正负样本比例就变focal loss 的 alpha 和 gamma 又得重新调。我见过太多项目在这几个超参上反复横跳一周时间全花在调阈值的网格搜索上。第三层麻烦是 NMS。因为一个目标会被多个 anchor 同时预测必须靠非极大值抑制把重复框压掉。NMS 本身有两个问题一是它不可微训练和推理逻辑割裂二是它在密集场景下很容易误杀。两个挨得很近的人、一摞叠在一起的零件NMS 的 IoU 阈值稍微低一点就漏检。而且 NMS 的阈值必须放到 CPU 上跑或者用一些 tricky 的 CUDA 实现部署时又是一堆额外的算子适配工作。DETR 的出发点非常干脆这三件事我全都不想要。它要做的是一种集合预测——直接输出 N 个框N 是固定的通常是 100每个框要么对应一个真实目标要么被标成无目标。因为预测框和真值框通过一对一匹配绑定理论上不会出现重复框NMS 自然就不需要了。1.2 三句话讲清 DETR 的数据流DETR CNN 骨干 Transformer 编解码器 二分图匹配损失。整个流程拆开是这样的。一张 800×1066 的图送进 ResNet-50取最后那一层 C5 特征尺寸是 H/32 × W/32通道数 2048用一个 1×1 卷积压到 256 通道。然后把它拉平成 (H/32)×(W/32) 长度的序列加上位置编码送进 6 层 Transformer Encoder。Encoder 的作用是让每个位置都能看到全图信息建立起全局的上下文关联。Encoder 输出之后进入 Decoder。Decoder 的输入有两部分一部分是 Encoder 的全局特征作为 key 和 value另一部分是 100 个可学习的向量叫 object queries作为 query。这 100 个 query 你可以理解成 100 个提问者每个提问者专门负责问一句图像里有没有属于我的那个目标在哪里训练过程中它们会逐渐分化各自负责不同的位置和尺度。Decoder 输出 100 个 256 维的向量每个向量分别过两个并列的小预测头一个三层的 MLP 分类头输出 92 维COCO 的 91 类加一个无目标一个同样的 MLP 回归头输出 4 维也就是归一化的 (cx, cy, w, h)。这 100 个结果就是最终的预测集合推理时把分数低于阈值的直接扔掉剩下的就是检测结果。1.3 这套范式适合谁又不适合谁DETR 的代码干净到令人感动。整个模型核心逻辑不到 300 行没有 anchor 生成、没有标签分配、没有 NMS。如果你是做算法研究的想快速搭一个端到端检测基线或者想改结构做实验DETR 是最省心的起点。如果你的场景是画面里目标数量少、尺度偏大比如道路上的车、室内的人、货架上的大件商品DETR 的表现完全够用而且后处理链路短部署时省掉一堆算子适配。但它也有明显的短板。第一是收敛慢原论文要在 COCO 上训 500 个 epoch 才能完全收敛而 Faster R-CNN 只要 12 到 36 个 epoch。这意味着同样的机器训练成本翻十倍以上。第二是小目标效果差因为它只用了 C5 这一层 stride 32 的特征图一个 16×16 像素的小目标在这张图上只剩半个像素点。第三是显存吃紧100 个 query 走 6 层 decoder 自注意力序列长度 100 虽然不长但 encoder 的注意力矩阵是 (H/32 × W/32) 的平方800×1066 的输入算下来大概 850 个 token注意力矩阵是 850×850好像还行但 batch size 稍微大一点就爆显存。所以后面才有 Deformable DETR 那一系列工作。这条路线的演进逻辑是这篇文章要重点讲清的部分。2. 拆开 DETR 的每一层结构细节与设计取舍2.1 Backbone 与位置编码为什么 PE 要加在拉平后的特征图上Backbone 用的是标准的 ResNet-50但有两点修改。一是把最后一个 stage 的 stride 从 32 里的下采样保留同时去掉最后的全局池化和全连接层二是为了配合小目标官方实现里在 C5 之后加了一个 dilation 处理只在某些配置里实际主要还是用标准的 C5 输出。特征图进入 Transformer 之前先做一次 1×1 卷积降到 256 维这一步的意义是统一通道数让后续所有模块的 hidden dim 都是 256参数量可控。位置编码用的是固定正弦编码不是什么可学习参数。原因在于 Transformer 的自注意力本身是置换不变的——你把输入序列打乱输出的集合内容一样只是顺序变了。但图像的空间关系是必须保留的所以要把位置信息显式注入。正弦编码的好处是长度可以任意外推输入分辨率变了也不用重新学。关于加在哪这里有个容易被忽略的细节。原论文做过对比实验位置编码加在 Encoder 的输入上和内容特征相加效果最好加到 attention 的 query/key 上、或者干脆不加都掉点。而 Decoder 那边query 本身已经是可学习的 embedding再给它加位置编码反而会掉点所以官方实现里 decoder 的 query 位置编码是全零。实操提示如果你自己要改结构记住一句话——encoder 里 PE 加在输入上decoder 里 query 不加 PEcross-attention 的 key 用 encoder 输出加 PE。有个额外细节DETR 的注意力 mask 处理。Encoder 里没有 mask所有位置互相可见。Decoder 的自注意力也没有因果 mask因为这里不是语言模型那种自回归生成100 个 query 之间是平权的。2.2 Transformer Encoder把二维图当一维序列处理的代价Encoder 是标准的 6 层结构每层包含多头自注意力8 头head dim 32加 FFN256 → 2048 → 256前后各有一个 LayerNorm残差连接。这套配置和原始 Transformer 基本一致没什么特别的改动。真正的代价在于把二维特征图拉平成一维序列。拉平之后图像原本的二维邻域结构丢失了每个 token 只能靠位置编码来知道自己在哪。这带来的问题是注意力计算量。对于 800×1066 输入C5 特征图是 25×34一共 850 个 token自注意力复杂度是 O(n²)850² 大约是 72 万次操作单看不大但这是 6 层、8 头、batch 16 相乘显存涨幅就很可观了。更麻烦的是分辨率。如果你想提高小目标性能把特征图换成 stride 16C4token 数直接变成 3400复杂度变成 O(n²) 的 1156 万是原来的 16 倍。这就是为什么 DETR 不敢用多尺度特征——多尺度意味着 token 数暴涨标准的全局注意力根本扛不住。Deformable DETR 的切入点正在这里。它把每个 token 的注意力范围从全部 token限制到固定数量的采样点复杂度从 O(n²) 降到 O(nK)K 通常取 4。这样多尺度就变得可行了。2.3 Decoder 与 object queries100 个提问者怎么分工Decoder 也是 6 层但结构上比 Encoder 多了一个 cross-attention 模块。每层的顺序是自注意力query 之间互相看→ cross-attentionquery 去看 encoder 输出的全局特征→ FFN。三个模块都是残差加 LayerNorm。Object queries 是 100 个 256 维的可学习向量初始值是随机初始化的。它们是整个模型里最有意思的部分。训练结束后你把它们可视化出来会发现每个 query 都形成了自己的专长有的专门盯画面左下角的中等目标有的专门找横向宽扁的物体有的负责右下角的大目标。这种分化完全是靠损失函数反向传播自然形成的没有任何人工设计。但这也带来一个问题100 这个数字是超参。如果你的场景里单张图目标特别多比如密集人群、货架商品100 个 query 不够用会漏检。改大了训练又变慢而且匹配的难度上升。我见过有人把它改成 300在密集场景下确实涨点但训练时间也涨了将近一倍。这个参数没有普适值得看你的数据里单图目标的 P99 分位数是多少往上留 20% 到 30% 的余量比较稳。2.4 预测头为什么用 FFN 而不是卷积预测头是 3 层 MLP 加 ReLU中间维度 256分类和回归各一套但结构相同。为什么用 MLP 而不是卷积因为在这一步每个 query 已经是一个独立的、包含了全局信息的向量了它代表的不是某个空间位置而是一个可能的目标。卷积需要空间邻域结构而这里根本没有二维结构可卷。MLP 是最自然的选择。分类头的最后一层是 Linear(256, 92)92 COCO 的 91 类 1 个无目标类。回归头是 Linear(256, 4)输出归一化的 cxcywh。注意这里输出的是 cxcywh 而不是 xyxy因为 L1 损失在 cxcywh 上对小框更友好而且后面算 GIoU 时需要转换。这里有个隐蔽的坑DETR 输出的坐标是相对图像尺寸归一化到 [0,1] 的但训练时真值框也要做同样的归一化。如果你在数据集预处理里忘了这一步或者用了不同的归一化基准比如按 padding 前的原图 vs padding 后的图匹配会完全错乱loss 从一开始就卡在一个很高的值下不来。2.5 匈牙利匹配与损失函数一对一分配的完整逻辑这是 DETR 最核心也最容易被讲糊的部分。模型输出 100 个预测真值框假设有 3 个。要算 loss得先决定这 100 个预测中哪 3 个去对应哪 3 个真值。DETR 的做法是找一种最优的一对一匹配使得总的匹配代价最小。这是个典型的二分图匹配问题用匈牙利算法scipy 里的 linear_sum_assignment求解。匹配代价由三部分组成分类代价预测为真值类别的概率的负值也就是 -p̂(c_i)。预测得越准代价越小。L1 代价预测框和真值框的 L1 距离权重 λ_L1 5。GIoU 代价1 减去 GIoU权重 λ_giou 2。数学形式就是 cost -λ_cls·p̂ λ_L1·||b - b̂||₁ λ_giou·(1 - GIoU)。注意在匹配阶段分类部分用的是 softmax 概率论文里用的是 softmax不是 sigmoid但到了算最终 loss 的时候分类损失换成 focal loss因为 100 个预测里绝大多数是背景类别极度不平衡。最终的损失函数是L λ_cls · L_focal λ_L1 · L_L1 λ_giou · L_GIoU其中 focal loss 的 alpha0.25、gamma2L1 和 GIoU 只在匹配上的那些对上计算。另外Decoder 的每一层输出都会算一遍 loss这叫辅助损失auxiliary loss每一层后接的预测头参数是共享的。辅助损失的作用是给浅层提供更直接的梯度加快收敛。训练完之后推理只取最后一层的输出。注意事项匹配是一对一的不是一对多。一个真值框只会匹配到唯一一个预测框剩下 99 个全部算背景。这是 DETR 不需要 NMS 的根本原因也是它收敛慢的直接原因——每个真值只有一份梯度监督信号太稀疏了。3. 从 DETR 到 Deformable DETR收敛慢的根因与解法3.1 DETR 训 500 epoch 的真实原因原论文里明确写了DETR 需要 500 个 epoch 才完全收敛而 300 epoch 时还能看到明显的提升空间。很多人第一反应是因为一对一匹配监督信号少。这个说法对但只是一半。Deformable DETR 的论文给出了更细的三条分析。第一条是注意力权重的初始化问题。Transformer 的注意力是 softmax 归一化的训练初期所有位置的注意力权重接近均匀分布梯度非常小。而目标检测需要的是聚焦到目标所在的少数位置这种从均匀到聚焦的转变需要大量迭代。第二条是匈牙利匹配的不稳定性。同一张图在不同 epoch 可能匹配出完全不同的对应关系尤其是训练早期预测框乱飘每个 epoch 的监督目标都在变。这种震荡让模型很难稳定地学到东西。第三条才是一对一匹配导致的监督稀疏。100 个预测里只有 3 个拿到正样本梯度其余 97 个都在学我是背景。理解了这三条后面所有改进工作的思路就清楚了DAB-DETR 和 DN-DETR 针对第二条Deformable DETR 针对第一条和计算效率RT-DETR 那类工作则是把整个链路重新设计一遍。3.2 可变形注意力参考点加采样偏移的核心机制Deformable Attention 的想法其实很直觉。既然全局注意力要算 n²那就别算全局了每个 query 只需要关注少数几个自己感兴趣的位置就行。具体做法是每个 query 先预测一组采样偏移量 Δp然后在特征图上用双线性插值取出 K 个点的特征K 默认 4再对这 K 个特征做加权求和权重也是预测出来的。注意这个权重不需要 softmax 归一化到和为 1而是各自过一遍 sigmoid然后除以 K。这个细节很重要因为它让梯度更稳定。多头的情况下每个头有自己的偏移量和权重K 个采样点乘 8 个头一共 32 个采样位置。关键点在于采样偏移是从 query 特征直接预测出来的也就是说模型自己学会我应该往哪看。这种学习到的稀疏采样让注意力复杂度从 O(n²) 降到 O(nK)K4 时基本是线性的。还有一个隐藏的好处可变形注意力天然支持多尺度。每个 query 可以同时在不同层级的特征图上采样比如 C3、C4、C5 各采几个点而且每层的采样点数和偏移量都是独立预测的。这就解决了 DETR 只用单层特征导致的小目标问题。3.3 多尺度特征与两阶段变体Deformable DETR 里的多尺度配置是 C3、C4、C5 加上一个 stride 1 的 C6。C6 这一层比较特殊它不是靠卷积下采样得到的而是直接在 C5 上做一个 3×3 stride 2 的卷积。加它的目的是补上最大的尺度让大目标也有对应的特征层。每个层级采样点数是 4三层加一起是 12 个点乘 8 个头就是 96 个采样位置。即使这样计算量也比全局注意力低得多。两阶段变体是另一个重要的改进。它在 encoder 输出后面接一个候选框生成模块用 encoder 特征预测一批候选框每个位置一个多尺度的话就是所有位置的框然后按分类分数取 top-kk300把这 300 个框的位置编码作为 decoder 的 query 输入。这个设计带来两个好处。一是 query 有了明确的语义起点不再是随机初始化的向量收敛快了非常多。二是可以给 encoder 也加监督信号让编码器学到的特征更有区分度。实测下来Deformable DETR 在 COCO 上大概 50 个 epoch 就能接近 DETR 500 epoch 的效果训练时间从一周降到一天这个量级。这是我推荐任何人入门 DETR 系列时首选 Deformable 版本的原因。3.4 后续改进路线速览后面这条线上的工作基本都是在解决具体的痛点。DAB-DETR 把 object query 显式地建模成 4D anchor box (x, y, w, h)每一层 decoder 都对它做一次 refine。这样 query 就有了明确的几何含义不再是黑盒向量收敛速度进一步加快。DN-DETRDenoising DETR的思路非常巧妙。它借鉴了扩散模型里的去噪训练在训练时给真值框加噪声然后让模型去还原。因为加噪的真值框和真值之间是已知的对应关系不需要匈牙利匹配监督信号直接、稳定。这一招把匹配的不稳定性问题基本解决了。DINO 把上面几个思路合到一起再加上对比去噪正样本加小噪声负样本加大噪声但压到背景类和 look forward twice 的梯度更新策略在 COCO 上首次把 DETR 系列推到了 63 mAP 的水平。不过 DINO 训练成本也高配置复杂度不低。RT-DETR 是另一个方向——实时。它把 encoder 换成高效混合编码器把尺度内交互和跨尺度融合拆开做同时用 IoU-aware 的 query 选择。在 T4 上跑 640 分辨率能做到 100 FPS精度还能保持。如果你的落地场景对速度有硬要求RT-DETR 值得优先试。4. 上手训练用自己的数据跑通 DETR / Deformable DETR4.1 数据集准备与那个最经典的坑数据格式是 COCO 格式。目录结构长这样custom_data/ ├── train/ │ ├── img_0001.jpg │ └── ... ├── val/ │ ── ... └── annotations/ ├── instances_train.json └── instances_val.jsonJSON 里需要 images、annotations、categories 三个字段。annotations 里每个框用 bbox [x, y, w, h]左上角加宽高绝对像素坐标category_id 关联到 categories 里的 id。这里有个坑我踩过不止一次DETR 官方实现要求类别 id 从 0 开始连续编号。而 labelme、CVAT、以及大部分标注工具导出的 COCO JSONcategory_id 默认是从 1 开始的。为什么这个坑这么隐蔽因为如果你的数据恰好是 3 个类id 是 1、2、3DETR 内部会把它当作索引 1、2、3 去取类别而模型的分类头只开了 num_classes 个输出。结果就是类别 0 永远学不到类别 3 越界。表现在 loss 上分类 loss 会卡住不动mAP 一直是 0 或者极低。修法很简单写个脚本把 category_id 重新映射成 0 到 N-1import json with open(annotations/instances_train.json, r) as f: data json.load(f) # 建立旧 id 到新 id 的映射 old_ids sorted([c[id] for c in data[categories]]) id_map {old: new for new, old in enumerate(old_ids)} for c in data[categories]: c[id] id_map[c[id]] for ann in data[annotations]: ann[category_id] id_map[ann[category_id]] with open(annotations/instances_train_fixed.json, w) as f: json.dump(data, f)同时注意 categories 里不要保留任何未使用的类别占位。有些数据集是从别的格式转过来的categories 里塞了 20 个类实际只标了 5 个剩下 15 个没有对应的标注。这种情况下模型的分类头会留出多余的输出空间影响不大但会让类别索引混乱建议直接清掉。实操心得改完 JSON 之后先跑一个小脚本统计一下每类的框数量确认没有 0 样本的类也没有 id 断档。这一步花 5 分钟能省掉后面几小时的 debug。4.2 配置文件关键参数与显存换算DETR 官方仓库主要是命令行参数没有一个集中的 yaml。核心参数大概这几个python -m torch.distributed.launch --nproc_per_node8 --use_env main.py \ --coco_path /path/to/custom_data \ --output_dir ./outputs/exp01 \ --batch_size 2 \ --epochs 100 \ --lr 1e-4 \ --lr_backbone 1e-5 \ --num_classes 5 \ --resume ./detr-r50-e632da11.pth关于显存我实测过几组数字可以参考。ResNet-50 backbone输入短边 800单张 12G 显卡配置batch_size显存占用备注DETR R501约 8.5G可跑慢DETR R502约 13G12G 卡不够需要 16GDeformable DETR2约 9G多尺度但采样稀疏Deformable DETR4约 15G16G 卡刚好如果你的卡不够首选降低输入分辨率比如从短边 800 降到 600显存大概能省 40%。次选是冻结 backbone 的前两个 stage用--freeze_backbone_stages 2这类参数。最后才是降 batch因为 DETR 对 batch size 挺敏感的太小了 BatchNorm 统计不准而且匹配的稳定性会下降。关于 num_classes 的传法不同版本不一样。较新的版本有--num_classes参数老的版本需要手动改datasets/coco.py里的num_classes91这一行。建议先grep -r num_classes .看一眼当前版本是怎么处理的别想当然。4.3 微调策略学习率、backbone lr 与数据增强微调自己的数据最重要的原则是差异化学习率。DETR 官方用的是 transformer 部分 lr1e-4、backbone lr1e-5差了 10 倍。道理很简单backbone 是 ImageNet 预训练的学到的边缘、纹理特征对你的任务基本都适用动它太多反而破坏而 transformer 部分是随机初始化的需要快学。如果你的数据量很小比如 2000 张以内我建议把 backbone lr 再降一档到 5e-6甚至前 10 个 epoch 直接冻结 backbone。等 transformer 部分稍微收敛了再解冻一起训。这个策略我用过好几次比全程统一 lr 稳定得多。数据增强方面官方配置是随机 resize最短边从 480 到 800 之间随机、随机裁剪scale 从 0.5 到 2.0、随机水平翻转。这套增强比较保守适合通用场景。如果你做的是工业质检或者医学图像水平翻转可能不适用缺陷方向有语义建议关掉。随机裁剪也要慎用容易把完整目标裁成半个反而制造噪声。epoch 数方面如果你是从 COCO 预训练权重微调50 到 100 epoch 通常就够。从零开始训的话DETR 至少 300 epochDeformable DETR 建议 50 epoch 起步。别指望 20 个 epoch 就能出好结果这不是 YOLO。4.4 训练过程监控与日志解读DETR 的日志输出比其他框架要多需要会看。Epoch: [10] Total time: 0:05:23 Test: [ 0/50] eta: 0:01:20 loss: 8.2341 Average Precision (AP) [ IoU0.50:0.95 | area all | maxDets100 ] 0.213 Average Precision (AP) [ IoU0.50 | area all | maxDets100 ] 0.401 Average Precision (AP) [ IoU0.75 | area all | maxDets100 ] 0.187 Average Precision (AP) [ IoU0.50:0.95 | area small | maxDets100 ] 0.031重点看三个指标。第一个是分类 loss如果它长时间不降八成是类别 id 那个坑。第二个是 AP50 和 AP75 的差距如果 AP50 有 0.6 而 AP75 只有 0.1说明框的位置回归很差可能是学习率太大或者回归头的权重没调好。第三个是 small 那一行的 APDETR 在这个指标上非常弱能到 0.05 就算不错了如果你需要小目标直接上 Deformable DETR 或者加多尺度输入。还有一个观察DETR 的初始 loss 值通常很大10 以上前 5 到 10 个 epoch 会掉得很快之后进入缓慢下降。如果前 5 个 epoch loss 还卡在 8 以上不动基本可以确定数据或者配置有问题不用继续等了。5. 部署落地ONNX 导出、推理后处理与性能调优5.1 导出 ONNX 的常见报错与修法DETR 导出 ONNX 最大的障碍是它接受变长输入而 ONNX 在某些 opset 下对动态尺寸的注意力矩阵支持不够好。最稳妥的做法是固定输入尺寸或者至少固定 batch 和序列长度。用 transformers 库的DetrForObjectDetection导出大致是这样import torch from transformers import DetrForObjectDetection model DetrForObjectDetection.from_pretrained(./outputs/best).eval() dummy torch.randn(1, 3, 800, 1066) torch.onnx.export( model, dummy, detr.onnx, opset_version16, input_names[pixel_values], output_names[logits, pred_boxes], dynamic_axes{ pixel_values: {0: batch, 2: height, 3: width}, logits: {0: batch}, pred_boxes: {0: batch}, }, )常见的报错和对应处理RuntimeError: Output 0 of ... is a view and is being modified inplace这类一般是 opset 版本太低换到 16 或 17 通常能解决。Unsupported operator aten::unflatten这种是 torch 版本和 opset 不匹配最新版的 torch 有些算子要 opset 17 以上才支持。如果遇到grid_sampler相关的报错Deformable DETR 里的双线性采样会用到需要确认你的推理引擎支持这个算子。ONNX Runtime 支持但一些轻量级推理框架可能要自己实现。还有一点必须注意DETR 里有些实现用了torch.where或者动态 shape 操作来加位置编码导出时容易变成一长串动态算子图会变得非常臃肿。建议导之前先把位置编码用固定尺寸重写一遍。5.2 后处理不需要 NMS但要 sigmoid 加阈值DETR 的输出是两个张量logits 形状是 [B, 100, num_classes1]pred_boxes 形状是 [B, 100, 4]。后处理步骤和传统检测器完全不同。第一步对 logits 做 sigmoid 得到每个预测属于每个类别的概率。注意是 sigmoid 不是 softmax因为每个预测在类别维度是独立二分类。第二步从 num_classes 个真实类别里取最大概率忽略最后一维的无目标类得到每个预测的类别和分数。第三步按分数阈值过滤。这里的阈值选择很关键和传统检测器的手感不一样。因为是一对一预测理论上不会有重复框所以阈值可以设得比 YOLO 低得多0.3 甚至 0.2 都行。我一般先用 0.5 看效果如果发现漏检多就往下降。第四步坐标转换。pred_boxes 是归一化的 cxcywh需要转成 xyxy 再乘以图像的实际宽高。import torch def postprocess(logits, boxes, img_hw, score_thr0.5): # logits: [B, 100, C1] prob logits.sigmoid() # 去掉最后一维的 no-object cls_prob, cls_idx prob[..., :-1].max(dim-1) # [B, 100] keep cls_prob score_thr results [] for b in range(logits.shape[0]): b_keep keep[b] p cls_prob[b][b_keep] c cls_idx[b][b_keep] bx boxes[b][b_keep] # cxcywh - xyxy cx, cy, w, h bx.unbind(-1) x1 (cx - w / 2) * img_hw[1] y1 (cy - h / 2) * img_hw[0] x2 (cx w / 2) * img_hw[1] y2 (cy h / 2) * img_hw[0] results.append({ scores: p, labels: c, boxes: torch.stack([x1, y1, x2, y2], dim-1) }) return results注意 img_hw 的顺序很多 bug 都出在这里。如果你的输入经过 letterbox 填充还得先减掉 padding 再缩放回原图尺寸。注意DETR 的 100 个输出里同一类别的框理论上不会重叠但实际中偶尔会出现两个框重叠度 0.7 左右的情况这是匹配没学好的表现。如果这种情况多可以在后处理里加一个宽松的 NMSIoU 阈值 0.8 以上兜底代价很小但能救回一些精度。5.3 推理速度优化DETR 的推理速度瓶颈和 YOLO 不一样。YOLO 慢在 NMS 和一些后处理的 kernel launchDETR 慢在 Transformer 的注意力计算。ResNet-50 版本的 DETR在 V100 上跑 800×1066 大约是 20 到 25 FPS也就是 40 到 50 毫秒一帧。这个速度在离线场景完全够实时场景就吃紧了。优化路径有几条。最直接的转 TensorRTFP16 精度下大概能提速 1.5 到 2 倍到达 40 FPS 左右。注意转 TensorRT 时要处理grid_sampler在 Deformable DETR 里的支持TRT 8 以上是支持的。第二条路是换小 backbone。把 ResNet-50 换成 ResNet-18 或者 MobileNetV3速度能到 60 FPS 以上但 mAP 会掉 3 到 5 个点。如果你的场景目标比较显著这个代价可以接受。第三条路是直接换 RT-DETR。它的设计目标就是实时在 T4 上跑 640 分辨率能做到 100 FPS 以上精度也不差多少。如果项目从零开始我建议直接上 RT-DETR省掉后面所有优化工作。5.4 实际项目中的取舍建议选哪个版本我一般按这个逻辑走。如果你的项目是离线批处理比如对一批图片做标注辅助、做数据清洗那速度和精度都不是瓶颈直接用 DINO 或者 Deformable DETR 的高配版本精度优先。如果是边缘设备算力有限比如 Jetson 系列直接上 RT-DETR 或者 DETR 的轻量 backbone 版本别硬扛 ResNet-50。如果是云端在线服务有 GPU 且并发不高Deformable DETR 是性价比最高的选择50 epoch 训完速度也还行精度也够用。如果场景里目标尺度变化极大比如航拍图里同时有小车和大楼那多尺度是必须的只能选 Deformable DETR 系列原版 DETR 别考虑。6. 常见问题速查与避坑清单6.1 训练过程中的典型问题loss 从第一个 epoch 开始就卡住不动按出现概率排最可能是类别 id 没有从 0 开始。检查 JSON 里 categories 的 id 是不是 0、1、2 这样连续。第二可能是 num_classes 参数没传对模型开出的输出维度和你数据里的类别数不匹配。第三是标注格式有问题比如 bbox 用了 xyxy 而不是 xywh或者坐标超界负值、超过图像宽高。建议先跑一个可视化脚本把标注画到图上确认一遍。loss 前期下降正常中期突然爆掉变成 nan大概率是梯度爆炸。DETR 官方的梯度裁剪是 0.1这个值很小如果你改大了就容易出问题。先确认--clip_max_norm是 0.1再把 lr 降一半试试。如果还爆检查一下有没有异常样本比如宽高为 0 的框、或者归一化后坐标是 nan 的情况。训练几十个 epochmAP 一直低于 0.05这种情况多半是评估环节的问题不一定是模型没学好。先手动跑一次推理把预测框画出来看如果框的位置大致对但类别全错那是分类头的问题如果框完全乱飞那是回归头或者匹配的问题。还有一种情况是评估用的 annotation 文件和训练用的不是同一份类别映射不一致。6.2 精度与评估的疑难杂症AP50 很高但 AP75 很低典型的位置回归精度不够。可以试试把 λ_L1 从 5 提到 8或者给回归头单独调一个更小的 lr。另一个方向是检查输入分辨率如果训练时短边是 600测试时用 800尺度不一致也会导致这个问题。小目标 AP 是个位数这是 DETR 的固有短板只能靠结构改。最有效的办法是用 Deformable DETR 的多尺度版本让模型能在 C3 这种高分辨率特征上采样。次选是在输入端提高分辨率比如短边从 800 提到 1200代价是显存和速度。同一张图上目标漏检严重先统计一下你的数据里单图目标的分布。如果 P99 超过 80 个那 100 个 query 确实不够了改成 200 或 300 试试。如果目标数不多但依然漏检检查一下分数阈值是不是设太高DETR 的分数分布比 YOLO 偏保守阈值 0.5 可能已经过滤掉了不少正确预测。6.3 部署阶段的常见坑DETR 系列的部署问题集中在这几类我整理成表格方便对照现象可能原因排查与修法ONNX 导出报 view/inplace 错opset 版本过低换到 opset 16 或 17导出成功但推理结果全乱位置编码被优化掉了检查 position embedding 是否被常量折叠改成显式计算TensorRT 报 grid_sampler 不支持TRT 版本旧升级到 8.4 以上推理框坐标偏移letterbox padding 没还原后处理里先减 padding 再按比例缩放批量推理结果和单张不一致BatchNorm 或者动态 shape 问题固定 batch 尺寸导出时不要开动态 batch速度远低于预期注意力算子没融合用 FP16检查是否有算子回落到 CPU实操心得ONNX 导出后一定要做数值对齐验证。用同一张图分别跑 PyTorch 和 ONNX Runtime对比输出的 logits 和 boxes误差应该在 1e-4 量级以内。如果差得远说明导出过程丢了关键信息别急着往下做 TensorRT先把这个对齐问题解决了。最后再分享一个小技巧。DETR 系列模型的权重文件通常不小ResNet-50 版本大概 160MB。如果部署环境对包体积敏感可以把 backbone 换成 MobileNetV3权重能压到 60MB 左右或者用 FP16 存储直接减半。我做过的项目里FP16 量化在 DETR 上几乎不掉点但文件大小和推理速度的收益都很明显性价比很高。另外一个观察是DETR 的调参空间比 YOLO 小很多。它没有 anchor 尺寸、没有 IoU 阈值、没有 NMS 阈值真正能调的就那么几个学习率、query 数量、输入分辨率、损失权重。这既是好处也是限制——好处是少了很多试错成本限制是当你发现精度上不去的时候能动的旋钮不多往往得从数据和结构层面想办法。习惯之后我反而更喜欢这种方式因为它把精力从调参炼金拉回到了真正重要的事情上数据质量、标注一致性、还有场景本身的难度评估。
返回列表