学习初稿 · 第 19 课。 本篇已开放网页阅读,用于先建立完整认识。文中手算值和示意记录不代表真实训练成绩;真实数据训练、预训练权重和板端测试以本篇具体说明为准,本篇已附代码练习,实际验证范围见文末与包内记录。
分类模型缺少哪一部分信息
整图分类回答“主要是什么”,却无法告诉我们图中两辆汽车的位置。目标检测为每个目标输出位置、类别和分数,同一图片可以有零个、一个或多个目标。
训练标签因此从一个整数变成目标列表。一个目标框常用左上右下坐标 x1,y1,x2,y2,也可能用中心点与宽高。是否归一化、坐标基于原图还是缩放后输入,都属于必须明确的接口。
框不是像素掩码
框描述外接矩形,矩形内部可能包含背景,也可能包含其他目标。若需要知道每个像素是否属于汽车,就要使用下一课的分割任务。目标检测与分割有联系,但不能仅通过把框画得细一点相互替代。
用图宽 W、图高 H 归一化时,横向坐标除 W、纵向除 H。若统一除以较大边,会破坏非正方形图片的含义。标注软件和训练工具可能采用不同格式,转换后最好重新绘制一遍确认。
用 IoU 比较两个框
IoU 是交集面积除以并集面积。无重叠时为 0,完全一致时为 1。本例采用连续坐标或半开区间几何,不额外加 1;不要与某些历史像素坐标实现混用。
|
这里每框面积为 100,交集为 50,并集为 150。IoU 只衡量几何重合,类别错了或把两个目标混在一个框里,仍然不算正确检测。
YOLO 在检测链路中的位置
YOLO 是以实时检测发展起来的模型家族与生态,不是一个永远固定的输出张量格式。不同版本可能在检测头、框表示、分数计算和是否需要 NMS 上不同,必须查看具体模型的输出约定。Ultralytics 检测任务说明
典型处理包括图像预处理、网络前向、解析输出、筛选候选、必要的重复框处理与坐标还原。很多传统检测器使用 NMS:优先保留高分框,再抑制与其高度重合的同类候选。部分端到端模型不需要这一步,不能对所有导出模型重复加 NMS。
letterbox 后怎样回到原图
假设原图 640×480,等比例放到 320×320 输入,缩放倍率为 0.5,图像变成 320×240,上下各填充 40。输入坐标中的框 [50,70,150,170],还原时先减填充,再除倍率:
|
实际还要记录取整后的缩放尺寸与左右上下填充值,并裁剪到原图边界。最稳妥的办法是预处理时保存变换参数,后处理使用同一份信息。
本课的小实验与真实实验分开
几何实验可以手写三个同类候选框:第一个高分、第二个与它高度重合、第三个在远处;逐步执行阈值筛选和 NMS,解释为什么第二个被抑制。它验证后处理逻辑,不涉及训练出的识别能力。
真实实验则需固定一个受支持的 YOLO 权重和工具版本,准备本篇独立的图片样例,保存原图、模型输入、框与类别,再分别检查空场景、单目标、多目标和遮挡。若继续训练自定义检测器,还要补完整框标注、独立来源划分与 AP 评测,不能复用分类标签冒充框标签。
本课不下载权重、不声称已完成检测训练。后续实测应记录模型来源与许可证、输入大小、输出结构和后处理参数,特别检查模型本身是否已内置 NMS。
练习
- 一个框位置完全正确但类别错误,能否算正确检测?
- 原图与网络输入比例不同,为什么不能只把坐标乘一个比例?
- 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。
