第6讲:检测大模型——从DETR到RT-DETR/R-DETR

一、从一个"找猫"游戏开始

1.1 传统目标检测的痛点

想象你要在一张复杂照片里找到所有的猫:

照片里有:
  - 3只猫(大小不一,有的被遮挡)
  - 2只狗
  - 1辆汽车
  - 背景:沙发、窗户、植物

传统方法(Anchor-based)怎么做?

步骤1:在图片上撒"锚框"(Anchor Boxes)
  像撒网捕鱼一样,在图片每个位置放几百个不同大小的框
  小框(检测小猫)、中框(检测中猫)、大框(检测大猫)

步骤2:每个锚框做两件事
  - 分类:这个框里是猫?狗?还是背景?
  - 回归:调整框的位置和大小,让它更贴合目标

步骤3:非极大值抑制(NMS)
  同一个猫可能被多个框检测到,只保留最好的那个

痛点

痛点 说明 后果
锚框设计复杂 需要预设大小、比例、数量 不同数据集要调参
正负样本不平衡 99%的锚框是背景(负样本) 训练效率低,模型偏向背景
NMS后处理 需要手动调阈值 实时性差,容易漏检或重复
非端到端 分类、回归、NMS是分开的 梯度无法统一优化

就像钓鱼时撒了1000个网,99%是空的,还要人工挑哪个网里有鱼。


二、DETR的革命:把检测变成"集合预测"

2.1 核心思想:像翻译一样做检测

机器翻译:
  输入:一段中文句子(词序列)
  输出:一段英文句子(词序列)
  模型:Transformer(Encoder-Decoder)

目标检测:
  输入:一张图片(像素网格)
  输出:一组边界框 + 类别(框序列)
  模型:???

DETR的洞察:
  "检测和翻译本质一样:都是序列到序列的转换!"

DETR(DEtection TRansformer)

输入图像 → CNN提取特征 → Transformer编码器 → Transformer解码器
                                                    ↓
                                              100个"查询框"
                                                    ↓
                                              每个查询输出:
                                                - 框坐标 (x, y, w, h)
                                                - 类别概率(包括"无对象"类)

2.2 DETR的架构详解

整体流程:

原始图像 [3, H, W]
    ↓
┌─────────────────────────┐
│  CNN Backbone(ResNet-50)│  ← 提取视觉特征
│  输出: [2048, H/32, W/32] │
└─────────────────────────┘
    ↓
┌─────────────────────────┐
│  1×1卷积降维              │
│  输出: [256, H/32, W/32]  │
└─────────────────────────┘
    ↓
展平 + 加位置编码
    ↓
┌─────────────────────────┐
│  Transformer Encoder      │  ← 全局特征交互
│  (6层,自注意力)         │
└─────────────────────────┘
    ↓
┌─────────────────────────┐
│  Transformer Decoder      │  ← 并行生成100个预测框
│  (6层,交叉注意力)       │
│  输入: 100个可学习查询 +    │
│       Encoder输出          │
└─────────────────────────┘
    ↓
100个预测结果(每个:框坐标 + 类别)
    ↓
匈牙利匹配 → 与真实框配对 → 计算损失

关键创新点

组件 作用 类比
Object Queries 100个可学习的"查询向量" 像100个侦探,各自负责找不同目标
Encoder 全局理解图像内容 侦探们先看完整张地图
Decoder 每个Query去图像中"查案" 侦探们分头行动,用交叉注意力找目标
匈牙利匹配 预测框和真实框的最优配对 给每个侦探分配最合理的任务

2.3 Object Queries:100个"侦探"

传统检测:每个位置预设锚框(被动等待)

DETR:100个可学习的Object Queries(主动查询)

初始化:随机向量 [100, 256]
训练后:
  Query 1 → 学会找"大图中央的对象"
  Query 2 → 学会找"左上角的小对象"
  Query 3 → 学会找"被遮挡的对象"
  ...
  Query 100 → 学会判断"这里没有对象"(背景)

Decoder的工作方式(交叉注意力):

每个Query(侦探):
  "我在图像的哪个区域找目标?"
    ↓
  与Encoder输出的全局特征做交叉注意力
    ↓
  聚焦到相关区域
    ↓
  输出:框坐标 + 类别

100个Query是并行的,不像GPT那样自回归。DETR的Decoder一次输出所有预测。


三、匈牙利匹配:给侦探分配任务

3.1 问题:预测框和真实框怎么配对?

真实标签(Ground Truth):
  猫1: (x=100, y=200, w=50, h=60)
  猫2: (x=300, y=150, w=80, h=70)
  猫3: (x=500, y=400, w=40, h=50)

DETR预测(100个框):
  框1: (x=105, y=205, w=48, h=58)  ← 很像猫1!
  框2: (x=500, y=100, w=30, h=30)  ← 不太像任何猫
  框3: (x=305, y=155, w=75, h=68)  ← 很像猫2!
  ...
  框100: (x=0, y=0, w=0, h=0)     ← "无对象"类

挑战

  • 预测数(100)≠ 真实数(3)
  • 需要找到最优配对,让总损失最小

3.2 匈牙利算法:最优分配的数学

类比:4个工人做4个任务

成本矩阵(工人i做任务j的成本):

        任务A  任务B  任务C  任务D
工人1    9      2      7      8
工人2    6      4      3      7
工人3    5      8      1      8
工人4    7      6      9      4

目标:每个工人分配一个任务,总成本最小

匈牙利算法步骤(简化版):

Step 1: 每行减去最小值
Step 2: 每列减去最小值  
Step 3: 用最少的线覆盖所有0
Step 4: 如果线数=矩阵维度,找到最优分配
        否则,调整矩阵,重复Step 3-4

在DETR中的应用

成本 = 分类损失 + 边界框损失(L1 + GIoU)

目标:找到预测框和真实框的配对,使总成本最小

多余预测(100 > 真实数)→ 配对给"无对象"类(背景)

代码实现

from scipy.optimize import linear_sum_assignment

# 成本矩阵:预测框 vs 真实框
# 形状: [num_predictions, num_targets]
cost_matrix = compute_cost(predictions, targets)  
# cost = λ_cls * L_cls + λ_bbox * L1 + λ_giou * L_giou

# 匈牙利算法求解最优分配
row_ind, col_ind = linear_sum_assignment(cost_matrix)

# row_ind[i] 的预测框 匹配到 col_ind[i] 的真实框
matched_predictions = predictions[row_ind]
matched_targets = targets[col_ind]

四、RT-DETR:实时检测的优化

4.1 DETR的瓶颈

问题 原因 影响
训练慢 匈牙利匹配 + Transformer全局注意力 需要500 epoch才能收敛
推理慢 Encoder的自注意力O(n²),n=特征图尺寸 无法满足实时需求(30FPS)
小目标差 高分辨率特征图计算量太大 小物体检测精度低

4.2 RT-DETR的三大优化

优化1:Hybrid Encoder(混合编码器)
DETR的Encoder:所有尺度特征都过Transformer → 计算量大

RT-DETR的Encoder:
  ┌─────────────────────────────────────┐
  │  AIFI(Attention-based Intra-scale) │  ← 单尺度自注意力(轻量)
  │  处理最高分辨率特征                  │
  └─────────────────────────────────────┘
              ↓
  ┌─────────────────────────────────────┐
  │  CCFF(CNN-based Cross-scale)      │  ← 跨尺度融合(高效)
  │  用CNN融合多尺度特征                │
  └─────────────────────────────────────┘

效果:保留全局建模能力,大幅降低计算量

通俗理解

像看地图:DETR用放大镜看每个细节(慢),RT-DETR先看整体轮廓(AIFI),再用CNN快速拼接细节(CCFF)。


优化2:IoU-aware Query Selection
DETR的Query:100个随机初始化,训练后学会分工

RT-DETR的Query:从Encoder输出中"精选"高质量候选

步骤:
1. Encoder输出特征图,预测初步的类别和框
2. 计算每个位置的IoU(与真实框的重叠度)
3. 选IoU最高的Top-K个位置 → 作为Decoder的初始Query

效果:Query起点更好,收敛更快,精度更高

类比

DETR:100个侦探随机分散在城市里找线索
RT-DETR:先根据报警记录(IoU),把侦探派到最可能出事的地方


优化3:高效的Decoder设计
DETR Decoder:6层Transformer,每层都过交叉注意力

RT-DETR Decoder:
  - 减少层数(如3层)
  - 优化交叉注意力的计算
  - 引入多尺度特征融合

效果:在精度几乎不降的情况下,速度提升3-5倍

4.3 性能对比

模型 骨干网络 AP(COCO) FPS(T4 GPU) 延迟
DETR-R50 ResNet-50 42.0 28 36ms
RT-DETR-R50 ResNet-50 53.1 108 9.2ms
RT-DETR-R101 ResNet-101 54.3 74 13.5ms
YOLOv8-X - 53.9 60 16.7ms

RT-DETR首次实现了Transformer检测器的实时性,且精度超越YOLO系列!


五、DINO/R-DETR的改进:对比学习与去噪训练

5.1 DINO(DETR with Improved deNoising anchOr boxes)

核心改进

技术 作用 效果
对比去噪训练(CDN) 同时训练"干净查询"和"带噪查询" 加速收敛,提升稳定性
混合查询选择 部分Query用IoU选,部分随机 兼顾精度和多样性
Look Forward Twice 解码器层间信息传递优化 提升小目标检测

去噪训练详解

正常训练:
  输入图像 → Encoder → Query → Decoder → 预测框
                                    ↓
                              与真实框算损失

去噪训练:
  输入图像 → Encoder → 干净Query → Decoder → 预测框1
                      ↓
                    加噪声Query(故意扰动坐标)→ Decoder → 预测框2
                      ↓
                    目标:框2也要接近真实框!

效果:
  - 模型学会"即使查询不太准,也能修正"
  - 类似数据增强,提升鲁棒性
  - 收敛速度提升2-3倍

六、动手实验:用Ultralytics训练RT-DETR

6.1 环境准备

# 安装ultralytics(包含RT-DETR)
pip install ultralytics

# 验证安装
yolo checks

6.2 实验1:用预训练RT-DETR做推理

from ultralytics import RTDETR
import cv2
import matplotlib.pyplot as plt
import numpy as np

print("=" * 60)
print("【实验1】RT-DETR预训练模型推理")
print("=" * 60)

# 加载预训练模型(自动下载)
model = RTDETR('rtdetr-l.pt')  # 'rtdetr-l' 或 'rtdetr-x'
print("✅ 模型加载完成")

# 用COCO预训练权重检测图片
# 可以替换为你的图片路径
image_path = 'https://ultralytics.com/images/bus.jpg'  # 示例图片

print(f"\n检测图片: {image_path}")
results = model(image_path)

# 可视化结果
for result in results:
    # 获取带标注的图片
    annotated_frame = result.plot()
    
    # 显示
    plt.figure(figsize=(12, 8))
    plt.imshow(cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB))
    plt.title('RT-DETR Detection Result')
    plt.axis('off')
    plt.tight_layout()
    plt.savefig('/mnt/agents/output/rtdetr_detection_result.png', dpi=150)
    plt.show()
    
    # 打印检测信息
    boxes = result.boxes
    print(f"\n检测到 {len(boxes)} 个对象:")
    for i, box in enumerate(boxes):
        cls_id = int(box.cls)
        conf = float(box.conf)
        xyxy = box.xyxy[0].tolist()  # [x1, y1, x2, y2]
        class_name = result.names[cls_id]
        print(f"  {i+1}. {class_name}: 置信度={conf:.2%}, 位置=({xyxy[0]:.1f}, {xyxy[1]:.1f}, {xyxy[2]:.1f}, {xyxy[3]:.1f})")

print("\n📊 检测结果已保存!")

6.3 实验2:在自定义数据集上训练

from ultralytics import RTDETR
import yaml

print("\n" + "=" * 60)
print("【实验2】RT-DETR自定义数据集训练")
print("=" * 60)

# ============================================
# 步骤1:准备数据集(YOLO格式)
# ============================================

"""
YOLO格式目录结构:
dataset/
├── train/
│   ├── images/
│   │   ├── img001.jpg
│   │   └── ...
│   └── labels/
│       ├── img001.txt
│       └── ...
├── val/
│   ├── images/
│   └── labels/
└── data.yaml

标注文件格式(img001.txt):
  0 0.5 0.5 0.3 0.4   ← class x_center y_center width height(归一化0-1)
  1 0.2 0.3 0.1 0.2
"""

# 创建示例data.yaml
data_yaml = """
path: ./custom_dataset  # 数据集根目录
train: train/images
val: val/images

# 类别
names:
  0: cat
  1: dog
  2: person

nc: 3  # 类别数
"""

# 保存yaml(实际使用时修改路径)
with open('custom_data.yaml', 'w') as f:
    f.write(data_yaml.strip())

print("✅ 数据集配置文件已创建: custom_data.yaml")
print("请根据实际数据集修改路径和类别")

# ============================================
# 步骤2:加载模型并训练
# ============================================

# 加载预训练模型(迁移学习)
model = RTDETR('rtdetr-l.pt')

print("\n开始训练...")
print("参数说明:")
print("  data: 数据集配置文件")
print("  epochs: 训练轮数(RT-DETR通常需要100-300轮)")
print("  imgsz: 输入图像尺寸(640)")
print("  batch: 批次大小(根据GPU调整)")
print("  lr0: 初始学习率")
print("  lrf: 最终学习率因子")

# 训练(演示用,实际运行时取消注释)
"""
results = model.train(
    data='custom_data.yaml',
    epochs=100,
    imgsz=640,
    batch=8,
    lr0=1e-4,      # RT-DETR需要较小学习率
    lrf=0.01,
    optimizer='AdamW',
    patience=20,   # 早停耐心值
    save=True,
    device=0       # GPU设备号
)
"""

print("""
训练技巧:
1. 一定要用预训练权重!(rtdetr-l.pt)
   RT-DETR从头训练需要大量数据和算力

2. 学习率要小(1e-4到5e-4)
   Transformer对学习率敏感

3. 数据增强要充分
   RT-DETR没有CNN的归纳偏置,需要更多数据变化

4. 训练轮数要够
   DETR系列收敛慢,100轮起步,300轮更好
""")

# ============================================
# 步骤3:验证和导出
# ============================================

print("\n" + "=" * 60)
print("【步骤3】模型验证与导出")
print("=" * 60)

# 验证(训练后)
# metrics = model.val()

# 导出为ONNX(部署用)
# model.export(format='onnx', dynamic=True, simplify=True)

print("""
导出格式选项:
  ONNX: 通用格式,支持动态输入
  TensorRT: NVIDIA GPU加速(最快)
  OpenVINO: Intel CPU优化
  CoreML: Apple设备
""")

print("\n✅ 训练流程说明完成!")

6.4 实验3:理解匈牙利匹配(可视化)

import torch
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import linear_sum_assignment

print("\n" + "=" * 60)
print("【实验3】匈牙利匹配算法可视化")
print("=" * 60)

def visualize_hungarian_matching():
    """
    可视化匈牙利匹配过程
    """
    # 模拟:3个真实框,5个预测框
    np.random.seed(42)
    
    # 真实框 [x, y, w, h]
    gt_boxes = np.array([
        [50, 50, 100, 100],   # 猫1
        [200, 150, 80, 120],  # 猫2
        [350, 300, 60, 60],   # 猫3
    ])
    
    # 预测框(有些好,有些差)
    pred_boxes = np.array([
        [55, 55, 95, 95],     # 接近猫1 ✅
        [210, 160, 75, 110],  # 接近猫2 ✅
        [100, 100, 50, 50],   # 随机框 ❌
        [360, 310, 55, 55],   # 接近猫3 ✅
        [400, 400, 30, 30],   # 随机框 ❌
    ])
    
    # 计算IoU成本(1 - IoU,越小越好)
    def compute_iou(box1, box2):
        # 计算交集
        x1 = max(box1[0], box2[0])
        y1 = max(box1[1], box2[1])
        x2 = min(box1[0]+box1[2], box2[0]+box2[2])
        y2 = min(box1[1]+box1[3], box2[1]+box2[3])
        
        inter = max(0, x2-x1) * max(0, y2-y1)
        area1 = box1[2] * box1[3]
        area2 = box2[2] * box2[3]
        union = area1 + area2 - inter
        
        return inter / union if union > 0 else 0
    
    # 构建成本矩阵
    cost_matrix = np.zeros((len(pred_boxes), len(gt_boxes)))
    for i, pred in enumerate(pred_boxes):
        for j, gt in enumerate(gt_boxes):
            iou = compute_iou(pred, gt)
            cost_matrix[i, j] = 1 - iou  # 成本 = 1 - IoU
    
    print("成本矩阵(预测框 × 真实框):")
    print("      猫1    猫2    猫3")
    for i, row in enumerate(cost_matrix):
        print(f"框{i+1}: {row[0]:.3f}  {row[1]:.3f}  {row[2]:.3f}")
    
    # 匈牙利算法求解
    row_ind, col_ind = linear_sum_assignment(cost_matrix)
    
    print(f"\n匈牙利匹配结果:")
    print(f"最优配对(总成本最小):")
    total_cost = 0
    for r, c in zip(row_ind, col_ind):
        cost = cost_matrix[r, c]
        total_cost += cost
        match_type = "✅ 好匹配" if cost < 0.5 else "⚠️ 勉强匹配"
        print(f"  预测框{r+1} → 真实框{c+1}(猫{c+1}): 成本={cost:.3f} {match_type}")
    
    print(f"\n总成本: {total_cost:.3f}")
    
    # 未匹配的预测框 → 背景类
    unmatched = set(range(len(pred_boxes))) - set(row_ind)
    print(f"未匹配的预测框(视为背景): {[i+1 for i in unmatched]}")
    
    # 可视化
    fig, ax = plt.subplots(1, 1, figsize=(10, 8))
    
    # 画真实框(绿色)
    for i, (x, y, w, h) in enumerate(gt_boxes):
        rect = plt.Rectangle((x, y), w, h, fill=False, edgecolor='green', linewidth=2)
        ax.add_patch(rect)
        ax.text(x, y-5, f'GT: 猫{i+1}', color='green', fontsize=10, weight='bold')
    
    # 画预测框(红色),匹配的有连线
    colors = ['red'] * len(pred_boxes)
    for r in row_ind:
        colors[r] = 'blue'  # 匹配的变蓝色
    
    for i, (x, y, w, h) in enumerate(pred_boxes):
        rect = plt.Rectangle((x, y), w, h, fill=False, edgecolor=colors[i], linewidth=2)
        ax.add_patch(rect)
        ax.text(x+w, y+h+5, f'Pred{i+1}', color=colors[i], fontsize=9)
    
    # 画匹配连线
    for r, c in zip(row_ind, col_ind):
        gt = gt_boxes[c]
        pred = pred_boxes[r]
        ax.plot([gt[0]+gt[2]/2, pred[0]+pred[2]/2], 
                [gt[1]+gt[3]/2, pred[1]+pred[3]/2], 
                'b--', alpha=0.5, linewidth=1)
    
    ax.set_xlim(0, 500)
    ax.set_ylim(400, 0)  # Y轴翻转,符合图像坐标
    ax.set_aspect('equal')
    ax.set_title('Hungarian Matching: Predictions vs Ground Truth')
    plt.legend(['Matched', 'GT', 'Unmatched Pred', 'Matched Pred'], 
               loc='upper right')
    plt.tight_layout()
    plt.savefig('/mnt/agents/output/hungarian_matching_viz.png', dpi=150)
    plt.show()
    
    print("\n📊 匹配可视化图已保存!")

visualize_hungarian_matching()

七、核心总结

概念 一句话解释
Anchor-based 预设大量候选框,分类+回归+NMS,复杂且慢
DETR 用Transformer做端到端检测,100个Query直接预测
Object Query 可学习的"侦探",各自负责找不同目标
匈牙利匹配 最优分配预测框到真实框,最小化总成本
RT-DETR 实时DETR,Hybrid Encoder + IoU-aware Query
去噪训练 给Query加噪声,训练模型修正能力
端到端 不需要NMS后处理,梯度统一优化

八、面试高频题

Q1:DETR和传统检测器(YOLO/Faster R-CNN)的本质区别?

:传统检测器是"两阶段"或"多阶段":先产生候选框(Anchor/RPN),再分类和回归,最后NMS去重。DETR是端到端的:用Transformer直接输出固定数量的预测框,通过匈牙利匹配与真实框配对,无需NMS和Anchor设计。

Q2:为什么DETR训练慢?RT-DETR怎么解决的?

:DETR慢的原因:1)Transformer全局注意力O(n²)计算量大;2)匈牙利匹配的稀疏监督导致收敛慢;3)小目标检测需要高分辨率特征图。RT-DETR通过Hybrid Encoder(AIFI+CCFF)降低计算量,IoU-aware Query Selection提供更好的初始化,优化Decoder结构,实现实时推理。

Q3:匈牙利匹配的成本函数怎么设计?

:成本 = 分类成本 + 边界框成本。分类成本常用Focal Loss或交叉熵;边界框成本用L1 Loss(坐标差)+ GIoU Loss(考虑重叠度和尺度)。超参数平衡三者权重,通常 λcls=1,λL1=5,λgiou=2\lambda_{cls}=1, \lambda_{L1}=5, \lambda_{giou}=2λcls=1,λL1=5,λgiou=2

Q4:RT-DETR适合什么场景?

:需要高精度和端到端优化的场景:工业质检(小目标检测)、自动驾驶(多尺度目标)、医学影像(需要全局上下文)。如果极致速度优先(如手机端),YOLOv8-nano可能更合适;如果精度优先且算力充足,RT-DETR是更好的选择。


九、课后作业

作业1:对比实验

# 在同一数据集上,对比训练:
# 1. YOLOv8(Anchor-free但非Transformer)
# 2. RT-DETR(Transformer端到端)
# 观察:收敛速度、最终精度、推理速度

作业2:理解Query

# 可视化训练后的Object Query
# 看不同Query是否学会了关注不同位置/尺度
# 提示:提取Decoder交叉注意力的权重,画热力图

作业3:思考多模态检测

问题:如果要把文本描述(如"找到红色的车")加入检测,
      DETR架构怎么改?

提示:Query变成"文本条件化的查询",
      类似GLIP或Grounding DINO的做法。

十、下讲预告

第7讲:分割大模型——SAM(Segment Anything Model)

我们将:

  • 理解"任意分割"的提示机制(点、框、文本、掩码)
  • 探索SAM的编码器-解码器架构
  • 动手用SAM做自动分割和交互式分割
  • 了解SAM 2的视频分割能力
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐