AI-深度学习

从零搭建视觉模型 19:目标检测与 YOLO:从类别到目标框

展开合集目录
  1. 从零搭建视觉模型 01:图片进入模型后发生了什么
  2. 从零搭建视觉模型 02:Python 起步,写给 C/C++ 开发者
  3. 从零搭建视觉模型 03:从像素到张量的基础概念
  4. 从零搭建视觉模型 04:图片如何变成张量
  5. 从零搭建视觉模型 05:MLP 入门,从手算到图片输入
  6. 从零搭建视觉模型 06:亲手搭建一个小型 CNN
  7. 从零搭建视觉模型 07:模型怎样预测类别:分数、概率与决策
  8. 从零搭建视觉模型 08:让模型开始学习,损失、梯度与训练循环
  9. 从零搭建视觉模型 09:数据集、标签与数据划分
  10. 从零搭建视觉模型 10:损失函数与优化器:怎样把错误变成更新
  11. 从零搭建视觉模型 11:批次、训练轮次与学习率
  12. 从零搭建视觉模型 12:训练第一个真实图片分类器
  13. 从零搭建视觉模型 13:过拟合、数据增强与正则化
  14. 从零搭建视觉模型 14:验证、测试与错误分析
  15. 从零搭建视觉模型 15:保存模型、恢复训练与独立推理
  16. 从零搭建视觉模型 · 综合实践:实现 MLP,从损失计算、训练到 ONNX 推理
  17. 从零搭建视觉模型 16:ResNet 与残差连接
  18. 从零搭建视觉模型 17:预训练、迁移学习与微调
  19. 从零搭建视觉模型 18:轻量模型与计算成本
  20. 从零搭建视觉模型 19:目标检测与 YOLO:从类别到目标框
  21. 从零搭建视觉模型 20:图像分割与 U-Net:给每个像素一个判断
  22. 从零搭建视觉模型 21:注意力与视觉 Transformer
  23. 从零搭建视觉模型 22:从训练模型到 ONNX
  24. 从零搭建视觉模型 23:量化与精度对比
  25. 从零搭建视觉模型 24:从摄像头输入到部署验收
  26. 从零搭建视觉模型 25:发展历史、主流分类与当前格局
从零搭建视觉模型 19:目标检测与 YOLO:从类别到目标框的封面

学习初稿 · 第 19 课。 本篇已开放网页阅读,用于先建立完整认识。文中手算值和示意记录不代表真实训练成绩;真实数据训练、预训练权重和板端测试以本篇具体说明为准,本篇已附代码练习,实际验证范围见文末与包内记录。

分类模型缺少哪一部分信息

整图分类回答“主要是什么”,却无法告诉我们图中两辆汽车的位置。目标检测为每个目标输出位置、类别和分数,同一图片可以有零个、一个或多个目标。

训练标签因此从一个整数变成目标列表。一个目标框常用左上右下坐标 x1,y1,x2,y2,也可能用中心点与宽高。是否归一化、坐标基于原图还是缩放后输入,都属于必须明确的接口。

框不是像素掩码

框描述外接矩形,矩形内部可能包含背景,也可能包含其他目标。若需要知道每个像素是否属于汽车,就要使用下一课的分割任务。目标检测与分割有联系,但不能仅通过把框画得细一点相互替代。

用图宽 W、图高 H 归一化时,横向坐标除 W、纵向除 H。若统一除以较大边,会破坏非正方形图片的含义。标注软件和训练工具可能采用不同格式,转换后最好重新绘制一遍确认。

用 IoU 比较两个框

IoU 是交集面积除以并集面积。无重叠时为 0,完全一致时为 1。本例采用连续坐标或半开区间几何,不额外加 1;不要与某些历史像素坐标实现混用。

def iou(a, b):
    x1, y1 = max(a[0], b[0]), max(a[1], b[1])
    x2, y2 = min(a[2], b[2]), min(a[3], b[3])
    intersection = max(0, x2-x1) * max(0, y2-y1)
    area_a = max(0, a[2]-a[0]) * max(0, a[3]-a[1])
    area_b = max(0, b[2]-b[0]) * max(0, b[3]-b[1])
    union = area_a + area_b - intersection
    return intersection / union if union else 0.0

first = [0, 0, 10, 10]
second = [5, 0, 15, 10]
print(iou(first, second))
assert abs(iou(first, second) - 1/3) < 1e-12

这里每框面积为 100,交集为 50,并集为 150。IoU 只衡量几何重合,类别错了或把两个目标混在一个框里,仍然不算正确检测。

YOLO 在检测链路中的位置

YOLO 是以实时检测发展起来的模型家族与生态,不是一个永远固定的输出张量格式。不同版本可能在检测头、框表示、分数计算和是否需要 NMS 上不同,必须查看具体模型的输出约定。Ultralytics 检测任务说明

典型处理包括图像预处理、网络前向、解析输出、筛选候选、必要的重复框处理与坐标还原。很多传统检测器使用 NMS:优先保留高分框,再抑制与其高度重合的同类候选。部分端到端模型不需要这一步,不能对所有导出模型重复加 NMS。

letterbox 后怎样回到原图

假设原图 640×480,等比例放到 320×320 输入,缩放倍率为 0.5,图像变成 320×240,上下各填充 40。输入坐标中的框 [50,70,150,170],还原时先减填充,再除倍率:

x1 = 50 / 0.5 = 100
y1 = (70 - 40) / 0.5 = 60
x2 = 150 / 0.5 = 300
y2 = (170 - 40) / 0.5 = 260

实际还要记录取整后的缩放尺寸与左右上下填充值,并裁剪到原图边界。最稳妥的办法是预处理时保存变换参数,后处理使用同一份信息。

本课的小实验与真实实验分开

几何实验可以手写三个同类候选框:第一个高分、第二个与它高度重合、第三个在远处;逐步执行阈值筛选和 NMS,解释为什么第二个被抑制。它验证后处理逻辑,不涉及训练出的识别能力。

真实实验则需固定一个受支持的 YOLO 权重和工具版本,准备本篇独立的图片样例,保存原图、模型输入、框与类别,再分别检查空场景、单目标、多目标和遮挡。若继续训练自定义检测器,还要补完整框标注、独立来源划分与 AP 评测,不能复用分类标签冒充框标签。

本课不下载权重、不声称已完成检测训练。后续实测应记录模型来源与许可证、输入大小、输出结构和后处理参数,特别检查模型本身是否已内置 NMS。

练习

  1. 一个框位置完全正确但类别错误,能否算正确检测?
  2. 原图与网络输入比例不同,为什么不能只把坐标乘一个比例?
  3. NMS 阈值与分类分数阈值是不是一个含义?
参考答案

不能,检测匹配同时涉及类别和位置要求。有填充或非等比例缩放时,需要分别处理偏移及横纵缩放。分数阈值筛选候选可信程度,NMS 阈值控制框之间的重叠抑制,两者不能混用。

下一课把输出单位从目标框进一步细化到像素。

配套代码练习

实验:IoU、NMS 与坐标还原

查看完整代码:main.py
"""IoU、NMS 与坐标还原。运行方式与练习见 README.md。"""
def iou(a, b):
    x1, y1 = max(a[0], b[0]), max(a[1], b[1])
    x2, y2 = min(a[2], b[2]), min(a[3], b[3])
    intersection = max(0, x2-x1) * max(0, y2-y1)
    area_a = max(0, a[2]-a[0]) * max(0, a[3]-a[1])
    area_b = max(0, b[2]-b[0]) * max(0, b[3]-b[1])
    union = area_a + area_b - intersection
    return intersection / union if union else 0.0

first = [0, 0, 10, 10]
second = [5, 0, 15, 10]
print(iou(first, second))
assert abs(iou(first, second) - 1/3) < 1e-12

# 按类别执行贪心 NMS,输入为 (框, 分数, 类别)。
candidates = [(first,0.9,0),([1,0,11,10],0.8,0),(first,0.7,1)]
kept = []
for box,score,label in sorted(candidates,key=lambda row:row[1],reverse=True):
    if all(label!=other_label or iou(box,other)<=0.5 for other,_,other_label in kept):
        kept.append((box,score,label))
assert len(kept)==2
print('NMS kept:',kept)
# 640x360 -> 640x640:上下各补 140,缩放比例 1。
original = [100,50,200,150]
padded = [100,190,200,290]
restored = [padded[0],padded[1]-140,padded[2],padded[3]-140]
assert restored==original
print('restored:',restored)
下载代码

在项目根目录执行;下载包解压后,在解压目录执行 python3 main.py。

cd 'content/posts/AI-深度学习/从零搭建视觉模型/19-目标检测与YOLO/code' && python3 main.py

本篇已附独立实验包,在代码卡片中查看源码、下载或复制运行命令。解压后执行 python3 main.py;依赖安装见包内 README.md。

观察目标: 示例 IoU 为 1/3;同类重叠框被抑制,异类框保留。

动手修改: 把两个框设为完全重合和完全分离,分别核对 IoU;改变 NMS 阈值观察保留框。

所有输入均在本篇代码中构造;运行范围和验证记录见包内 verification.md。


完整阅读目录 · 上一篇 · 下一篇