Appearance
第二章 目标检测算法
课程导航
| 项目 | 内容 |
|---|---|
| 课时 | 2 小时 |
| 类型 | 理论 + 代码实战 |
| 前置知识 | CNN 基础、PyTorch 基本使用、第一章 OpenCV |
一、学习目标
- 理解目标检测的核心评估指标:IoU、mAP、NMS
- 掌握 Faster R-CNN 的两阶段检测流程(RPN + ROI Pooling)
- 理解 YOLO 系列从 v1 到 v5 的核心演进思想
- 能使用 YOLOv8/v9 训练自定义数据集
- 了解小目标检测的优化方法(多尺度训练、FPN)
二、核心知识点
2.1 目标检测评估指标
2.1.1 定义
| 指标 | 全称 | 作用 |
|---|---|---|
| IoU | Intersection over Union | 预测框与真实框的交并比,衡量定位精度 |
| mAP | mean Average Precision | 综合召回率与精确率的全局指标 |
| NMS | Non-Maximum Suppression | 后处理去冗余框,保留最优检测 |
2.1.2 IoU 计算
python
import numpy as np
def compute_iou(box1, box2):
"""
box: [x1, y1, x2, y2] 左上 + 右下坐标
"""
x1 = max(box1[0], box2[0])
y1 = max(box1[1], box2[1])
x2 = min(box1[2], box2[2])
y2 = min(box1[3], box2[3])
inter = max(0, x2 - x1) * max(0, y2 - y1)
area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
union = area1 + area2 - inter
return inter / union if union > 0 else 0.0
# 示例
gt = [50, 50, 150, 150]
pd = [60, 60, 140, 140]
print(f"IoU = {compute_iou(gt, pd):.4f}") # 结果 ≈ 0.642.1.3 NMS 算法
python
def nms(boxes, scores, iou_threshold=0.5):
"""
boxes: [[x1,y1,x2,y2], ...]
scores: [score, ...]
返回保留的框索引
"""
indices = np.argsort(scores)[::-1] # 按得分降序
keep = []
while len(indices) > 0:
i = indices[0]
keep.append(i)
rest = indices[1:]
ious = np.array([compute_iou(boxes[i], boxes[j]) for j in rest])
indices = rest[ious <= iou_threshold] # 移除与当前框 IoU 过大的
return keep
# 测试
boxes = np.array([[10,10,50,50], [12,12,48,48], [100,100,150,150]])
scores = np.array([0.9, 0.8, 0.95])
kept = nms(boxes, scores, 0.5)
print(f"NMS 保留索引: {kept}")2.1.4 mAP 计算流程
- 对每个类别,按置信度降序排列所有检测框
- 依次计算 Precision 和 Recall,绘制 PR 曲线
- AP = PR 曲线下面积(通常用 11 点插值或积分法)
- mAP = 所有类别 AP 的均值
- COCO 标准:
mAP@0.5(IoU=0.5)、mAP@0.5:0.95(IoU 从 0.5 到 0.95 步长 0.05 取均值)
2.2 Faster R-CNN — 两阶段检测器
2.2.1 定义
Faster R-CNN 是两阶段(Two-Stage)目标检测的代表,通过**区域建议网络(RPN)**替代 Selective Search,实现端到端训练。
输入图像 → 共享 CNN 骨干 → 特征图
├── RPN → 区域建议(Region Proposals)
└── ROI Pooling → 分类 + 回归核心组件:
| 组件 | 作用 |
|---|---|
| Backbone | CNN 提取特征(VGG16 / ResNet-50 / ResNet-101) |
| RPN | 在特征图上滑动窗口,输出 anchor 的前景/背景得分 + 坐标偏移 |
| ROI Pooling | 将不同尺寸的 proposal 池化为固定尺寸(如 7×7)送入全连接层 |
| Head | 全连接层输出类别概率 + 边界框回归偏移 |
2.2.2 Anchor 机制
- 每个滑动窗口位置预设
k个 anchor box(不同比例 + 不同尺度) - 通常 k=9(3 种面积 × 3 种长宽比)
- RPN 输出:2k 个类别得分 + 4k 个坐标偏移
2.2.3 代码骨架(使用 Detectron2 / Torchvision)
python
# ---------- 使用 torchvision 内置 Faster R-CNN ----------
import torch
import torchvision
from torchvision.models.detection import fasterrcnn_resnet50_fpn
# 加载预训练模型
model = fasterrcnn_resnet50_fpn(pretrained=True)
model.eval()
# 推理
from PIL import Image
import torchvision.transforms as T
img = Image.open('test.jpg').convert('RGB')
transform = T.Compose([T.ToTensor()])
img_tensor = transform(img).unsqueeze(0)
with torch.no_grad():
predictions = model(img_tensor)
# predictions[0] 包含 'boxes', 'labels', 'scores'
boxes = predictions[0]['boxes'].numpy()
scores = predictions[0]['scores'].numpy()
labels = predictions[0]['labels'].numpy()
# 过滤低置信度预测
keep = scores > 0.5
filtered_boxes = boxes[keep]
print(f"检测到 {len(filtered_boxes)} 个目标")2.3 YOLO 系列演进
2.3.1 定义
YOLO(You Only Look Once)将目标检测视为回归问题,单次前向即可输出类别与位置,实现实时检测。
| 版本 | 年份 | 核心贡献 |
|---|---|---|
| v1 | 2016 | 首个单阶段检测器;将图像分为 S×S 网格,每个网格预测 B 个框 |
| v2 | 2017 | Batch Normalization、Anchor Box、DarkNet-19、多尺度训练 |
| v3 | 2018 | DarkNet-53、FPN 多尺度预测、9 个 anchor、Logistic 分类器 |
| v4 | 2020 | CSPDarkNet-53、Mish 激活、MOSAIC 数据增强、CIoU Loss |
| v5 | 2020 | Focus 模块、自适应 anchor、GIOU Loss、四种模型尺寸(n/s/m/l/x) |
2.3.2 YOLOv1 核心思想
- 输入 448×448,输出 S×S×(B×5 + C) 张量
- S=7, B=2, C=类别数(PASCAL VOC 为 20)
- 每个框预测:
[x, y, w, h, confidence] - Loss = 坐标误差 + 置信度误差 + 分类误差
2.3.3 YOLOv3 关键改进
python
# YOLOv3 输出解析核心逻辑
def decode_yolo_output(pred, anchors, num_classes):
"""
pred: (batch, grid_h, grid_w, num_anchors, 5+num_classes)
anchors: [[w,h], ...]
"""
batch, h, w, num_anchors, _ = pred.shape
grid_x, grid_y = np.meshgrid(np.arange(w), np.arange(h))
# 解析中心坐标偏移
x = (grid_x + sigmoid(pred[..., 0])) / w
y = (grid_y + sigmoid(pred[..., 1])) / h
# 解析宽高(指数缩放)
w_box = anchors[:, 0] * np.exp(pred[..., 2]) / w
h_box = anchors[:, 1] * np.exp(pred[..., 3]) / h
# 置信度 + 分类
conf = sigmoid(pred[..., 4])
cls_probs = softmax(pred[..., 5:], axis=-1)
return x, y, w_box, h_box, conf, cls_probsYOLOv3 多尺度预测:在 3 个不同尺度的特征图上分别预测(13×13、26×26、52×52),每个尺度 3 个 anchor,共 9 个 anchor。
2.4 YOLOv8 / v9 自定义训练
2.4.1 YOLOv8 简介
Ultralytics YOLOv8 是目前最流行的 YOLO 实现之一,统一了目标检测、分割、姿态估计、分类等任务的训练框架。
2.4.2 数据集准备(COCO 格式)
dataset/
├── images/
│ ├── train/ # 训练图像
│ └── val/ # 验证图像
├── labels/
│ ├── train/ # 每个图像对应一个 .txt
│ └── val/
└── data.yaml # 数据集配置data.yaml 示例:
yaml
train: ./dataset/images/train
val: ./dataset/images/val
nc: 3
names: ['defect', 'scratch', 'dent']标签格式(每行一个目标):
<class_id> <x_center> <y_center> <width> <height>
# 坐标均归一化到 [0, 1]2.4.3 训练代码(YOLOv8)
python
from ultralytics import YOLO
# ---------- 加载预训练模型 ----------
model = YOLO('yolov8n.pt') # n/s/m/l/x 可选
# ---------- 训练 ----------
results = model.train(
data='data.yaml',
epochs=100,
imgsz=640,
batch=16,
device='cuda',
lr0=0.01,
augment=True, # 自动数据增强
patience=20, # Early Stopping
)
# ---------- 验证 ----------
metrics = model.val()
print(f"mAP@0.5: {metrics.box.map50:.4f}")
print(f"mAP@0.5:0.95: {metrics.box.map:.4f}")
# ---------- 推理 ----------
results = model('test.jpg', conf=0.5)
results[0].show() # 显示检测结果
results[0].save('result.jpg')
# ---------- 导出 ----------
model.export(format='onnx') # 导出 ONNX2.4.4 YOLOv9 亮点
- PGI(Programmable Gradient Information):解决深层网络梯度信息丢失问题
- GELAN(Generalized Efficient Layer Aggregation Network):轻量高效的特征融合
- 相比 v8 在同等参数下精度更高
python
from ultralytics import YOLO
# YOLOv9 训练方式与 v8 高度一致
model = YOLO('yolov9c.pt') # yolov9t / yolov9c / yolov9e
model.train(data='data.yaml', epochs=100, imgsz=640)2.5 小目标检测优化
2.5.1 定义
小目标(通常 < 32×32 像素)在特征图中信息量极少,是检测中的难点。
2.5.2 优化策略
| 策略 | 说明 |
|---|---|
| 多尺度训练 | 每次迭代随机选择不同输入尺寸(如 320→640),增强尺度鲁棒性 |
| 特征金字塔 (FPN) | 自顶向下传递语义信息,每个尺度独立预测 |
| 高分辨率输入 | 保持输入分辨率(如 1280),但增加计算量 |
| 数据增强 | Mosaic、Copy-Paste、Random Crop 增加小目标多样性 |
| 专门的小目标检测头 | 在浅层高分辨率特征图上添加额外的检测头 |
| Swin Transformer 骨干 | 利用全局注意力捕获小目标上下文 |
2.5.3 FPN 核心思想
P7 ──↑ (1/128)
P6 ──↑ (1/64)
P5 ──↑ (1/32) ← 自顶向下语义融合
P4 ──↑ (1/16)
P3 (1/8) ← 原始底层特征(高分辨率)python
# FPN 简化示意
class FPN(nn.Module):
def __init__(self, in_channels_list, out_channels=256):
super().__init__()
self.lateral_convs = nn.ModuleList([
nn.Conv2d(c, out_channels, 1) for c in in_channels_list
])
self.output_convs = nn.ModuleList([
nn.Conv2d(out_channels, out_channels, 3, padding=1)
for _ in in_channels_list
])
def forward(self, features):
# features: [C3, C4, C5] 从骨干网络
laterals = [conv(f) for f, conv in zip(features, self.lateral_convs)]
# 自顶向下融合(从最高层开始)
for i in range(len(laterals)-1, 0, -1):
laterals[i-1] += F.interpolate(laterals[i],
scale_factor=2,
mode='nearest')
# 输出卷积平滑
outputs = [conv(l) for l, conv in zip(laterals, self.output_convs)]
return outputs # [P3, P4, P5]2.5.4 YOLOv8 开启多尺度训练
python
model.train(
data='data.yaml',
epochs=100,
imgsz=640,
multi_scale=True, # 开启多尺度训练
scale=0.5, # 尺度抖动范围 [1-scale, 1+scale]
mosaic=1.0, # Mosaic 增强概率
copy_paste=0.1, # Copy-Paste 增强
)三、小结
| 知识点 | 掌握程度 | 关键理解 |
|---|---|---|
| IoU / mAP / NMS | 必须掌握 | 评价和过滤检测结果的三大工具 |
| Faster R-CNN | 理解流程 | RPN 生成 proposal + ROI Pooling 分类回归 |
| YOLO 演进 | 必须掌握 | 单阶段回归 => 多尺度预测 => CSP 结构 |
| YOLOv8/v9 训练 | 必须掌握 | 数据准备 → model.train() → 导出 |
| 小目标检测优化 | 理解策略 | 多尺度 + FPN + 数据增强 + 高分辨率 |
四、课后练习
基础题
- 手写 NMS 函数,并用
numpy实现完整的 mAP 计算流程(给定预测框和真实框列表)。 - 使用
torchvision.models.detection.fasterrcnn_resnet50_fpn对一张包含多个物体的图片进行检测,输出所有置信度 > 0.5 的目标类别和坐标。
进阶题
- 收集 100 张自定义数据集(如交通标志),标注为 YOLO 格式,使用 YOLOv8 训练并评估 mAP。
- 对比 YOLOv8n / YOLOv8s / YOLOv8m 在相同数据集上的训练速度、模型大小和 mAP 表现。
- 使用 YOLOv8-seg 实例分割模型训练一个自定义分割数据集。
思考题
- 为什么 YOLOv3 在三个不同尺度的特征图上做预测?这对小目标检测有什么帮助?
- 两阶段(Faster R-CNN)和单阶段(YOLO)检测器各自的优缺点是什么?在什么场景下该选谁?