一、目标检测基础概念
目标检测(Object Detection)是计算机视觉中最核心的任务之一,目标是在图像中定位并识别感兴趣的物体。与图像分类不同,目标检测不仅要回答"图像中有什么",还要回答"物体在哪里"。一个完整的目标检测系统需要输出每个检测到的物体的类别标签和边界框坐标。
目标检测的核心挑战:
- 多尺度问题: 同一类物体可能在图像中呈现不同的大小
- 遮挡处理: 目标被部分遮挡时仍需准确检测
- 密集场景: 小物体密集排列时的区分和定位
- 速度与精度权衡: 实时检测要求高帧率,但精度不能牺牲太多
1.1 边界框(Bounding Box)
边界框是目标检测中最基本的表示形式,通常用两种方式表示:
- (x, y, w, h): 中心点坐标 + 宽度 + 高度
- (x1, y1, x2, y2): 左上角坐标 + 右下角坐标
Python# 边界框格式转换
def xywh_to_xyxy(bbox):
"""将 (cx, cy, w, h) 转换为 (x1, y1, x2, y2)"""
cx, cy, w, h = bbox
x1 = cx - w / 2
y1 = cy - h / 2
x2 = cx + w / 2
y2 = cy + h / 2
return [x1, y1, x2, y2]
def xyxy_to_xywh(bbox):
"""将 (x1, y1, x2, y2) 转换为 (cx, cy, w, h)"""
x1, y1, x2, y2 = bbox
cx = (x1 + x2) / 2
cy = (y1 + y2) / 2
w = x2 - x1
h = y2 - y1
return [cx, cy, w, h]
1.2 交并比(IoU)
IoU(Intersection over Union)衡量预测边界框与真实边界框的重叠程度,是目标检测中最核心的评估指标之一。IoU 的计算公式为两个框交集面积与并集面积的比值。
IoU = (A ∩ B) / (A ∪ B)
Pythondef compute_iou(box1, box2):
"""计算两个边界框的 IoU
box 格式: [x1, y1, x2, y2]
"""
# 计算交集区域坐标
x1_inter = max(box1[0], box2[0])
y1_inter = max(box1[1], box2[1])
x2_inter = min(box1[2], box2[2])
y2_inter = min(box1[3], box2[3])
# 交集面积
inter_area = max(0, x2_inter - x1_inter) * max(0, y2_inter - y1_inter)
# 并集面积 = A面积 + B面积 - 交集面积
area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
union_area = area1 + area2 - inter_area
# IoU
iou = inter_area / union_area if union_area > 0 else 0
return iou
1.3 锚框(Anchor Box)
锚框(Anchor Box)是目标检测中预先定义的一组固定尺寸和比例的参考框。模型不是在图像中任意搜索物体,而是在每个特征图位置上预测锚框的调整偏移量。锚框的设计直接决定了检测器对小物体、细长物体等的检测能力。
锚框设计的关键参数:
- 尺度(Scale): 锚框的大小,通常使用多个尺度(如 32x32, 64x64, 128x128)来覆盖不同大小的物体
- 长宽比(Aspect Ratio): 常见的比值有 1:1, 1:2, 2:1,用于覆盖不同形状的物体
- 特征图位置: 每个特征图网格位置生成 K 个锚框(K = 尺度数 × 长宽比数)
1.4 非极大值抑制(NMS)
NMS(Non-Maximum Suppression)是目标检测后处理的关键步骤。由于检测器可能对同一物体生成多个重叠的检测框,NMS 通过保留置信度最高的框并抑制与其重叠度高的其他框,确保每个物体只有一个检测结果。
Pythondef nms(boxes, scores, iou_threshold=0.5):
"""非极大值抑制
boxes: [[x1,y1,x2,y2], ...]
scores: [score1, score2, ...]
"""
if len(boxes) == 0:
return []
# 按置信度降序排序
indices = scores.argsort()[::-1]
keep = []
while len(indices) > 0:
# 保留当前最高分框
current = indices[0]
keep.append(current)
if len(indices) == 1:
break
# 计算当前框与其余框的 IoU
rest = indices[1:]
ious = [compute_iou(boxes[current], boxes[i]) for i in rest]
# 保留 IoU 小于阈值的框
indices = [rest[i] for i in range(len(rest)) if ious[i] < iou_threshold]
return keep
1.5 mAP(平均精度均值)
mAP(mean Average Precision)是目标检测领域最常用的评估指标。对于每个类别,计算 PR 曲线(Precision-Recall Curve)下的面积得到 AP,再对所有类别的 AP 求平均得到 mAP。计算过程中,预测框与真实框的匹配基于 IoU 阈值(通常为 0.5 或 0.5:0.95)。
AP = ∫01 P(r) dr mAP = (1/C) ∑i=1C APi
二、两阶段检测器(R-CNN 系列)
两阶段检测器将目标检测分解为两个步骤:第一步生成候选区域(Region Proposals),第二步对每个候选区域进行分类和回归。这类方法精度较高,但速度相对较慢。
R-CNN 系列演进路线
R-CNN (2014) → Fast R-CNN (2015) → Faster R-CNN (2015)
↓
Mask R-CNN (2017) | Cascade R-CNN (2018)
2.1 R-CNN:基于候选区域的卷积神经网络
R-CNN(Region-based CNN)由 Ross Girshick 于 2014 年提出,是深度学习目标检测的开创性工作。其核心思想是使用选择性搜索(Selective Search)算法生成约 2000 个候选区域,然后对每个候选区域进行 CNN 特征提取和 SVM 分类。
R-CNN 流程:
- 候选区域生成: 使用 Selective Search 从图像中提取约 2000 个可能包含物体的区域
- 特征提取: 将每个候选区域缩放到固定大小(227x227),输入 CNN(AlexNet)提取特征
- 分类: 对每个候选区域的特征使用 SVM 分类器判断所属类别
- 回归: 使用边界框回归器修正候选区域的位置
R-CNN 的缺点: 需要对每个候选区域分别进行 CNN 前向传播,2000 个候选区域导致推理速度极慢(每张图约 47 秒),且训练过程需要多阶段流水线(CNN 预训练 + SVM 训练 + 回归器训练),无法端到端优化。
2.2 Fast R-CNN:共享卷积计算
Fast R-CNN 的关键创新在于共享卷积计算:先对整个图像进行一次 CNN 前向传播得到整张特征图,然后通过 RoI Pooling 层从特征图中为每个候选区域提取固定大小的特征向量。此外,Fast R-CNN 将分类器统一为 Softmax 层,并与边界框回归器联合训练,实现了多任务学习。
PyTorch# RoI Pooling 简化实现
import torch
import torch.nn as nn
import torch.nn.functional as F
class RoIPool(nn.Module):
def __init__(self, output_size, spatial_scale=1.0):
super().__init__()
self.output_size = output_size # (H, W)
self.spatial_scale = spatial_scale
def forward(self, features, rois):
"""
features: 特征图 [B, C, H, W]
rois: 候选区域 [N, 5] (batch_idx, x1, y1, x2, y2)
"""
output = []
for i in range(rois.shape[0]):
batch_idx = int(rois[i, 0])
x1 = int(rois[i, 1] * self.spatial_scale)
y1 = int(rois[i, 2] * self.spatial_scale)
x2 = int(rois[i, 3] * self.spatial_scale)
y2 = int(rois[i, 4] * self.spatial_scale)
# 从特征图中裁剪候选区域
roi_feat = features[batch_idx, :, y1:y2, x1:x2]
# 缩放至固定大小
roi_feat = F.interpolate(
roi_feat.unsqueeze(0),
size=self.output_size,
mode='bilinear',
align_corners=False
)
output.append(roi_feat)
return torch.cat(output, dim=0)
Fast R-CNN 的改进: 相比于 R-CNN,Fast R-CNN 的训练时间从 84 小时降至 9.5 小时(约 9 倍加速),推理时间从 47 秒降至 0.32 秒(约 150 倍加速)。更重要的是,分类和回归可以在统一的网络中进行训练。
2.3 Faster R-CNN:端到端目标检测
Faster R-CNN 在 Fast R-CNN 的基础上引入了 区域建议网络(RPN,Region Proposal Network),将候选区域生成也融入神经网络,实现了真正的端到端训练。RPN 在特征图的每个位置生成锚框,并通过二分类(前景/背景)和粗略边界框回归来生成高质量的区域建议。
Faster R-CNN 架构
输入图像
↓
[骨干网络:VGG16 / ResNet / FPN]
↓
┌──────────────────────────────────┐
│ RPN(区域建议网络) │
│ 3x3 Conv → 1x1 Conv (cls) │
│ → 1x1 Conv (reg) │
│ 输出:前景分数 + 锚框偏移 │
└──────────────┬───────────────────┘
↓ NMS 筛选候选区域
┌──────────────────────────────────┐
│ RoI Pooling + 检测头 │
│ FC → Softmax (类别) │
│ FC → BBox Reg (位置) │
└──────────────────────────────────┘
↓
最终检测结果
PyTorch# RPN 简化实现
class RegionProposalNetwork(nn.Module):
def __init__(self, in_channels, mid_channels=512,
anchor_scales=[8, 16, 32],
anchor_ratios=[0.5, 1.0, 2.0]):
super().__init__()
num_anchors = len(anchor_scales) * len(anchor_ratios)
# 中间卷积层
self.conv = nn.Conv2d(in_channels, mid_channels,
kernel_size=3, padding=1)
self.relu = nn.ReLU(True)
# 分类分支:二分类(前景/背景)
self.cls_head = nn.Conv2d(mid_channels, num_anchors * 2,
kernel_size=1)
# 回归分支:每个锚框4个偏移量 (dx, dy, dw, dh)
self.reg_head = nn.Conv2d(mid_channels, num_anchors * 4,
kernel_size=1)
def forward(self, x):
# x: 特征图 [B, C, H, W]
x = self.relu(self.conv(x))
# 分类 logits: [B, num_anchors*2, H, W]
cls_logits = self.cls_head(x)
# 回归偏移: [B, num_anchors*4, H, W]
bbox_deltas = self.reg_head(x)
# 调整形状
N, _, H, W = cls_logits.shape
cls_logits = cls_logits.view(N, -1, 2, H, W)
bbox_deltas = bbox_deltas.view(N, -1, 4, H, W)
return cls_logits, bbox_deltas
Faster R-CNN 核心优势:
- 端到端训练: RPN + Fast R-CNN 可以联合训练,共享特征提取网络
- 高质量候选区域: RPN 生成的候选区域比 Selective Search 质量更高,数量更少(~300 vs ~2000)
- 速度飞跃: 推理速度提升至接近实时(约 5-10 FPS on GPU)
- 统一框架: 为后续 Mask R-CNN(实例分割)、Cascade R-CNN(级联检测)等奠定了基础
2.4 FPN(特征金字塔网络)
FPN(Feature Pyramid Network)是 Faster R-CNN 的重要增强组件。它通过自顶向下的路径和横向连接,构建了具有丰富语义信息的多尺度特征金字塔,使得网络可以更好地检测不同尺度的物体,尤其是小物体。
三、单阶段检测器
单阶段检测器(One-Stage Detector)直接回归物体的类别和位置,不依赖独立的候选区域生成步骤,以速度优势著称。代表性工作包括 YOLO 系列和 SSD。
3.1 YOLO 系列
YOLO(You Only Look Once)由 Joseph Redmon 于 2016 年提出,将目标检测建模为一个统一的回归问题——单次前向传播即可预测所有物体的边界框和类别概率。
YOLOv1:开创性的统一检测框架
YOLOv1 将输入图像划分为 S x S 的网格。每个网格负责检测中心点落在该网格内的物体。每个网格预测 B 个边界框(每个框包含 x, y, w, h, confidence)和 C 个类别概率。输出张量形状为 S x S x (B*5 + C)。
YOLOv1 检测流程
输入图像 (448x448)
↓
[GoogLeNet 风格 CNN]
↓
输出张量 7x7x30
↓
┌─────────────────────────────────────┐
│ 每格 2个边界框 (x,y,w,h,conf) │
│ 每格 20个类别概率 (PASCAL VOC) │
│ 总输出: 7x7x(2*5+20) = 7x7x30 │
└─────────────────────────────────────┘
↓ NMS 后处理
最终检测结果
PyTorch# YOLOv1 损失函数简化实现
class YOLOv1Loss(nn.Module):
def __init__(self, S=7, B=2, C=20,
coord_scale=5, noobj_scale=0.5):
super().__init__()
self.S = S
self.B = B
self.C = C
self.coord_scale = coord_scale
self.noobj_scale = noobj_scale
self.mse = nn.MSELoss(reduction='sum')
def forward(self, pred, target):
"""
pred: [B, S*S*(B*5+C)] 预测张量
target: [B, S, S, B*5+C] 真实标签
"""
pred = pred.view(-1, self.S, self.S, self.B * 5 + self.C)
# 分离坐标、置信度、类别
pred_boxes = pred[..., :self.B * 4]
pred_conf = pred[..., self.B * 4:self.B * 5]
pred_cls = pred[..., self.B * 5:]
target_boxes = target[..., :self.B * 4]
target_conf = target[..., self.B * 4:self.B * 5]
target_cls = target[..., self.B * 5:]
# 计算损失
loss_coord = self.coord_scale * self.mse(pred_boxes, target_boxes)
loss_obj = self.mse(pred_conf, target_conf)
loss_noobj = self.noobj_scale * self.mse(
pred_conf[target_conf < 0.5],
target_conf[target_conf < 0.5]
)
loss_cls = self.mse(pred_cls, target_cls)
return loss_coord + loss_obj + loss_noobj + loss_cls
YOLOv3:多尺度预测
YOLOv3 引入了三个关键改进:① 使用 Darknet-53 作为骨干网络(借鉴残差连接);② 在 3 个不同尺度的特征图上进行预测(对应大、中、小物体);③ 使用逻辑回归替代 Softmax 进行多标签分类。每个尺度使用 3 个锚框,对于 COCO 数据集共使用 9 个锚框。
YOLOv5:工程化实践的集大成者
YOLOv5 并非严格的学术论文,而是由 Ultralytics 公司基于 YOLOv4(CSPDarknet + PANet + Mish 激活)思想实现的工程化框架。其核心改进包括:
YOLOv5 工程化改进:
- Mosaic 数据增强: 将 4 张图像拼接为一张,丰富小物体样本
- 自适应锚框计算: 自动从训练数据中聚类出最优锚框尺寸
- CSP 架构: 跨阶段局部网络(Cross Stage Partial)减少计算量
- PANet 颈部网络: 增强特征金字塔的信息流动
- CIoU 损失函数: 更精确的边界框回归损失
YOLO 系列速度优势对比:
| 模型 | 输入尺寸 | mAP@0.5 (COCO) | FPS (Titan X) |
| YOLOv1 | 448x448 | 63.4% | 45 |
| YOLOv2 | 416x416 | 78.6% | 67 |
| YOLOv3 | 416x416 | 55.3% | 35 |
| YOLOv5s | 640x640 | 56.8% | ~120 |
| YOLOv5l | 640x640 | 67.3% | ~45 |
注:不同版本使用的评估标准和数据集版本不同,上表仅为参考对比。
3.2 SSD:单次多框检测器
SSD(Single Shot MultiBox Detector)由 Wei Liu 于 2016 年提出,是另一种重要的单阶段检测器。SSD 的核心思想是在多个不同尺度的特征图上进行密集采样和预测。
SSD 的核心设计:
- 多尺度特征图: 在 VGG16 骨干网络的不同层(Conv4_3, Conv7, Conv8_2, Conv9_2, Conv10_2, Conv11_2)上设置检测头,低层检测小物体,高层检测大物体
- 默认框设计: 每个特征图位置生成 4-6 个不同尺度和长宽比的默认框(相当于锚框),共计 8732 个默认框
- 难例挖掘(Hard Negative Mining): 训练时控制正负样本比例(约 1:3),确保训练稳定
PyTorch# SSD 多尺度检测头简化实现
class SSDHead(nn.Module):
def __init__(self, in_channels, num_classes, num_boxes):
super().__init__()
self.num_classes = num_classes
# 分类分支
self.cls_head = nn.Conv2d(in_channels, num_boxes * num_classes,
kernel_size=3, padding=1)
# 定位分支
self.reg_head = nn.Conv2d(in_channels, num_boxes * 4,
kernel_size=3, padding=1)
def forward(self, x):
# x: 特征图 [B, C, H, W]
B, _, H, W = x.shape
cls_out = self.cls_head(x) # [B, num_boxes*C, H, W]
reg_out = self.reg_head(x) # [B, num_boxes*4, H, W]
# 展平并调整形状
cls_out = cls_out.permute(0, 2, 3, 1).reshape(B, -1, self.num_classes)
reg_out = reg_out.permute(0, 2, 3, 1).reshape(B, -1, 4)
return cls_out, reg_out
class SSD(nn.Module):
def __init__(self, backbone, num_classes):
super().__init__()
self.backbone = backbone
# 多尺度检测头配置 [通道数, 默认框数量]
self.heads = nn.ModuleList([
SSDHead(512, num_classes, 4), # Conv4_3
SSDHead(1024, num_classes, 6), # Conv7
SSDHead(512, num_classes, 6), # Conv8_2
SSDHead(256, num_classes, 6), # Conv9_2
SSDHead(256, num_classes, 4), # Conv10_2
SSDHead(256, num_classes, 4), # Conv11_2
])
def forward(self, x):
features = self.backbone(x)
cls_list, reg_list = [], []
for feat, head in zip(features, self.heads):
cls_out, reg_out = head(feat)
cls_list.append(cls_out)
reg_list.append(reg_out)
# 沿空间维度拼接所有尺度的预测
cls_all = torch.cat(cls_list, dim=1)
reg_all = torch.cat(reg_list, dim=1)
return cls_all, reg_all
3.3 两阶段与单阶段对比
| 特性 | 两阶段(Faster R-CNN) | 单阶段(YOLO/SSD) |
| 推理速度 | 较慢(5-10 FPS) | 快(30-120+ FPS) |
| 检测精度 | 更高,尤其小物体 | 略低,但差距逐渐缩小 |
| 训练难度 | 需要多阶段训练技巧 | 训练相对简单 |
| 正负样本平衡 | RPN 过滤了大量负样本 | 需额外处理(Focal Loss / OHEM) |
| 部署友好度 | 模型较大 | 轻量级,适合移动端 |
| 典型应用 | 自动驾驶感知、医学影像 | 实时视频分析、边缘设备 |
四、损失函数
目标检测的损失函数通常包含分类损失和定位损失两个部分。分类损失衡量预测类别与真实类别的差异,定位损失衡量边界框位置的精确度。
4.1 分类损失:交叉熵(CE)
Lcls = -∑i=1C yi log(pi)
对于多类别分类,使用 Softmax 交叉熵损失。对于多标签分类(一个物体可属于多个类别),使用 Sigmoid + 二元交叉熵损失。在目标检测中,Focal Loss 是在交叉熵基础上的重要改进,通过引入调制因子 (1-pt)γ 来降低易分类样本的权重,聚焦于难分类样本。
PyTorch# Focal Loss 实现
class FocalLoss(nn.Module):
def __init__(self, alpha=0.25, gamma=2.0):
super().__init__()
self.alpha = alpha
self.gamma = gamma
def forward(self, pred, target):
# pred: [N, C] logits, target: [N] 类别索引
ce_loss = F.cross_entropy(pred, target, reduction='none')
pt = torch.exp(-ce_loss) # 预测正确概率
# alpha: 类别平衡权重, (1-pt)^gamma: 难易样本调制因子
focal_loss = self.alpha * (1 - pt) ** self.gamma * ce_loss
return focal_loss.mean()
4.2 定位损失:Smooth L1
Smooth L1 损失是 Fast R-CNN 中引入的边界框回归损失,相比 L2 损失对异常值更鲁棒。
Smooth L1(x) = { 0.5x2 if |x| < 1; |x| - 0.5 otherwise }
PyTorch# Smooth L1 Loss
smooth_l1 = nn.SmoothL1Loss()
# 或手动实现
def smooth_l1_loss(pred, target, beta=1.0):
diff = (pred - target).abs()
loss = torch.where(diff < beta,
0.5 * diff ** 2 / beta,
diff - 0.5 * beta)
return loss.mean()
4.3 IoU 系列损失函数
Smooth L1 损失对尺度敏感,且无法直接优化 IoU 指标。近年来一系列基于 IoU 的损失函数被提出:
IoU 损失函数演进:
- GIoU(Generalized IoU): 在 IoU 基础上引入最小外接矩形(C),解决 IoU 在无重叠时梯度为 0 的问题。LGIoU = 1 - IoU + |C \ (A ∪ B)| / |C|
- DIoU(Distance IoU): 引入两框中心点距离的惩罚项,使预测框更快向真实框中心靠拢。LDIoU = 1 - IoU + ρ2(b, bgt) / c2
- CIoU(Complete IoU): 在 DIoU 基础上加入长宽比一致性惩罚,同时考虑重叠面积、中心点距离和长宽比三个几何因素。LCIoU = 1 - IoU + ρ2(b, bgt) / c2 + αv
PyTorch# CIoU Loss 实现
def ciou_loss(pred_boxes, target_boxes):
"""
pred_boxes, target_boxes: [N, 4] (x1, y1, x2, y2)
"""
# 计算 IoU
iou = compute_iou(pred_boxes, target_boxes)
# 计算中心点距离
p_x1, p_y1, p_x2, p_y2 = pred_boxes.unbind(dim=-1)
t_x1, t_y1, t_x2, t_y2 = target_boxes.unbind(dim=-1)
p_cx = (p_x1 + p_x2) / 2
p_cy = (p_y1 + p_y2) / 2
t_cx = (t_x1 + t_x2) / 2
t_cy = (t_y1 + t_y2) / 2
rho2 = (p_cx - t_cx) ** 2 + (p_cy - t_cy) ** 2
# 最小外接矩形对角线距离
c_x1 = torch.min(p_x1, t_x1)
c_y1 = torch.min(p_y1, t_y1)
c_x2 = torch.max(p_x2, t_x2)
c_y2 = torch.max(p_y2, t_y2)
c2 = (c_x2 - c_x1) ** 2 + (c_y2 - c_y1) ** 2 + 1e-7
# 长宽比一致性
v = (4 / (torch.pi ** 2)) * (
torch.atan(torch.abs(t_x2 - t_x1) / (torch.abs(t_y2 - t_y1) + 1e-7))
- torch.atan(torch.abs(p_x2 - p_x1) / (torch.abs(p_y2 - p_y1) + 1e-7))
) ** 2
alpha = v / (1 - iou.detach() + v + 1e-7)
ciou = iou - rho2 / c2 - alpha * v
return (1 - ciou).mean()
五、PyTorch 实现简化版目标检测器
下面我们实现一个简化但完整的目标检测训练流程,包含数据准备、模型定义和训练循环。为了便于演示,我们使用合成的简单数据集。
PyTorch# 完整的目标检测训练流程演示
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import numpy as np
# ========== 1. 简易数据集 ==========
class SimpleDetDataset(Dataset):
def __init__(self, num_samples=1000, img_size=224):
self.num_samples = num_samples
self.img_size = img_size
def __len__(self):
return self.num_samples
def __getitem__(self, idx):
# 生成随机图像和边界框
img = torch.randn(3, self.img_size, self.img_size)
# 生成一个随机边界框
x1 = torch.rand(1).item() * 0.5
y1 = torch.rand(1).item() * 0.5
w = torch.rand(1).item() * 0.3 + 0.1
h = torch.rand(1).item() * 0.3 + 0.1
boxes = torch.tensor([[x1, y1, x1 + w, y1 + h]])
labels = torch.tensor([0]) # 类别 0
return img, boxes, labels
# ========== 2. 简易检测模型 ==========
class SimpleDetector(nn.Module):
def __init__(self, num_classes=1, num_anchors=3):
super().__init__()
# 骨干网络(简化版)
self.backbone = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, stride=2, padding=1),
nn.ReLU(),
nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),
nn.ReLU(),
nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),
nn.ReLU(),
)
# 检测头
self.cls_head = nn.Conv2d(128, num_anchors * num_classes, kernel_size=1)
self.reg_head = nn.Conv2d(128, num_anchors * 4, kernel_size=1)
def forward(self, x):
feat = self.backbone(x)
cls_out = self.cls_head(feat)
reg_out = self.reg_head(feat)
B, _, H, W = cls_out.shape
cls_out = cls_out.view(B, -1, 1, H, W)
reg_out = reg_out.view(B, -1, 4, H, W)
return cls_out, reg_out
# ========== 3. 训练循环 ==========
def train_one_epoch(model, dataloader, optimizer, device):
model.train()
total_loss = 0
for imgs, boxes, labels in dataloader:
imgs = imgs.to(device)
# 前向传播
cls_logits, reg_deltas = model(imgs)
# 简化损失计算(实际需匹配锚框与真实框)
# 此处使用占位损失函数演示流程
loss_cls = cls_logits.sum() * 0.001
loss_reg = reg_deltas.sum() * 0.001
loss = loss_cls + loss_reg
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
return total_loss / len(dataloader)
# ========== 4. 推理与后处理 ==========
def predict(model, image, conf_threshold=0.5):
model.eval()
with torch.no_grad():
cls_out, reg_out = model(image.unsqueeze(0))
# 应用 Softmax 获取概率
probs = torch.softmax(cls_out, dim=2)
# 筛选高置信度检测结果
scores, labels = probs[..., 1:].max(dim=2)
mask = scores > conf_threshold
if mask.sum() == 0:
return [], [], []
# 解析边界框
keep_scores = scores[mask]
keep_labels = labels[mask] + 1
keep_boxes = reg_out[0][mask]
# 应用 NMS
keep_indices = nms(keep_boxes, keep_scores)
return keep_boxes[keep_indices], keep_scores[keep_indices], keep_labels[keep_indices]
# ========== 5. 启动训练 ==========
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = SimpleDetector().to(device)
optimizer = optim.Adam(model.parameters(), lr=1e-3)
dataset = SimpleDetDataset(num_samples=100)
dataloader = DataLoader(dataset, batch_size=8, shuffle=True)
for epoch in range(5):
loss = train_one_epoch(model, dataloader, optimizer, device)
print(f"Epoch {epoch+1}, Loss: {loss:.4f}")
以上代码展示了目标检测器从数据生成、模型定义、训练到推理的完整流程。在实际项目中,需要使用真实数据集(如 COCO、PASCAL VOC),并实现锚框匹配、正负样本采样等关键逻辑。
实际项目中的关键实现细节:
- 锚框匹配策略: 计算每个锚框与所有真实框的 IoU,IoU > 0.5 为正样本,< 0.4 为负样本
- 正负样本平衡: RPN 中采样 256 个锚框(正负比 1:1),检测头中使用 OHEM 或 Focal Loss
- 数据增强: 随机翻转、颜色抖动、多尺度训练(MST)、马赛克增强
- 学习率调度: 多步衰减(StepLR)或余弦退火(CosineAnnealing)
- 同步批归一化(SyncBN): 多 GPU 训练时的关键技巧
六、评估指标与基准
6.1 mAP 计算详解
mAP 的计算过程可分为以下步骤:
- 对所有检测结果按置信度降序排列
- 遍历每个检测结果,判断是否与某个真实框匹配(IoU > 阈值)
- 计算 Precision 和 Recall,绘制 PR 曲线
- 计算 AP:PR 曲线下的面积(使用插值法)
- 对所有类别平均:mAP = mean(AP1, AP2, ..., APC)
Python# mAP 计算简化实现
def compute_ap(precision, recall):
"""计算 PR 曲线下面积(插值法)"""
# 在 recall 轴上插入 101 个点
ap = 0.0
for t in np.arange(0, 1.01, 0.01):
# 取 recall >= t 时的最大 precision
p = np.max(precision[recall >= t]) if np.any(recall >= t) else 0
ap += p / 101
return ap
def compute_map(all_detections, all_groundtruths, iou_threshold=0.5):
"""
计算 mAP
all_detections: {class_id: [{bbox, score}, ...]}
all_groundtruths: {class_id: [{bbox, difficult}, ...]}
"""
aps = []
for class_id in all_detections:
dets = all_detections[class_id]
gts = all_groundtruths[class_id]
# 按置信度排序
dets = sorted(dets, key=lambda x: x['score'], reverse=True)
# 标记每个真实框是否已被匹配
detected = [False] * len(gts)
tp = np.zeros(len(dets))
fp = np.zeros(len(dets))
for i, det in enumerate(dets):
# 找到 IoU 最大的真实框
best_iou = 0
best_idx = -1
for j, gt in enumerate(gts):
iou = compute_iou(det['bbox'], gt['bbox'])
if iou > best_iou:
best_iou = iou
best_idx = j
if best_iou >= iou_threshold and not detected[best_idx]:
tp[i] = 1
detected[best_idx] = True
else:
fp[i] = 1
# 累计计算 Precision 和 Recall
tp_cumsum = np.cumsum(tp)
fp_cumsum = np.cumsum(fp)
num_gts = len(gts)
precision = tp_cumsum / (tp_cumsum + fp_cumsum + 1e-7)
recall = tp_cumsum / (num_gts + 1e-7)
ap = compute_ap(precision, recall)
aps.append(ap)
return np.mean(aps)
6.2 主流基准数据集
| 数据集 | 类别数 | 图像数 | 标注数 | 特点 |
| PASCAL VOC | 20 | ~11K | ~27K | 经典小规模数据集,mAP@0.5 标准 |
| MS COCO | 80 | ~200K | ~500K | 大规模数据集,mAP@0.5:0.95 标准,小物体丰富 |
| ImageNet Det | 200 | ~500K | ~534K | 类别最多,但每类实例较少 |
| Objects365 | 365 | ~600K | ~10M | 大规模数据集,面向通用检测 |
| LVIS | ~1200 | ~164K | ~2M | 长尾分布,稀有类别检测挑战 |
6.3 实时检测速度评估
在实际应用中,检测速度通常使用 FPS(Frames Per Second)衡量。影响 FPS 的关键因素包括:骨干网络大小、输入分辨率、后处理效率(NMS)、推理框架优化(TensorRT / ONNX 等)。
七、前沿发展与总结
7.1 DETR:基于 Transformer 的目标检测
DETR(Detection Transformer)由 Facebook AI 于 2020 年提出,将目标检测建模为集合预测问题,使用 Transformer 架构替代了传统的锚框设计和 NMS 后处理。DETR 通过二分图匹配(Hungarian Algorithm)直接预测一组物体集合,大幅简化了检测流程。
DETR 核心创新:
- Transformer 编码器-解码器: CNN 提取特征后,Transformer 捕获全局上下文信息
- 物体查询(Object Queries): 一组可学习的嵌入向量,解码器将其转换为检测结果
- 二分图匹配损失: 使用匈牙利算法将预测与真实框进行最优匹配,避免 NMS
- 集预测范式: 直接输出固定数量的预测(通常为 100 个),无需锚框设计
7.2 当前最佳实践
截至 2026 年,目标检测领域的发展呈现出以下趋势:
- 端到端范式: DETR 及其变体(Deformable DETR, DINO)正在取代传统锚框方法
- 基础模型: 基于大规模预训练的基础模型(如 Grounding DINO)实现开放词汇检测
- 多模态融合: 结合文本描述的语言引导检测(GLIP, OWL-ViT)
- 高效架构: 轻量级检测网络(NanoDet, YOLOX-Nano)在移动端广泛部署
- 4D 检测: 视频目标检测(VID)和多视角 3D 检测(BEVFormer)
八、核心要点总结
- 目标检测两范式: 两阶段(Faster R-CNN 代表)精度高但速度慢;单阶段(YOLO/SSD 代表)速度快,两者差距正逐步缩小
- 基础组件: 锚框(Anchor)是连接特征图与检测结果的桥梁;NMS 是后处理去重的标准方案;IoU 是衡量检测精度的核心指标
- 损失函数: 从 Smooth L1 到 CIoU 的演进反映了对定位精度的持续追求;Focal Loss 解决了单阶段检测器的正负样本失衡问题
- 多尺度检测: FPN(特征金字塔)和 SSD 的多尺度特征图策略是处理不同大小物体的关键
- 评估标准: mAP@0.5(PASCAL 标准)和 mAP@0.5:0.95(COCO 标准)是衡量检测器性能的行业标准
- 工程实践: YOLOv5/YOLOv8 系列的成功证明了工程优化(数据增强、模型量化、部署框架)在工业界的巨大价值
- 发展趋势: Transformer(DETR 系列)正在重塑目标检测范式,开放词汇和基础模型成为新方向
九、进一步思考
目标检测作为计算机视觉的核心任务,其发展深刻反映了深度学习技术的演进脉络。从 R-CNN 的"区域分类"到 YOLO 的"统一回归",再到 DETR 的"集预测",每一次范式转变都带来了性能的重大突破。
实践建议:
- 入门学习: 从 YOLOv5 入手,理解数据准备、训练、部署的完整流程
- 学术研究: 关注 DETR 系列和开放词汇检测的最新进展
- 工业部署: 考虑模型量化(FP16/INT8)、TensorRT 加速、NMS 优化等工程手段
- 领域应用: 结合具体场景(医学影像、遥感、自动驾驶)设计定制化的检测方案
从更宏观的视角看,目标检测的发展正在从"检测已知类别的物体"向"理解和推理视觉场景中的任意物体"演进。多模态大模型的兴起为这一目标提供了新的可能性——未来的检测系统将不再局限于固定类别,而是能够通过语言交互灵活理解用户需求,实现真正的通用视觉感知。