界定问题
把模糊的产品构想转化为三行任务规格说明,并将业务规则保留在代码中,这样你无需重新训练就能修改规则。

许多停滞不前的计算机视觉项目,问题都在于一开始没有明确的问题。“检测不安全行为”听起来像一项需求,但标注人员无法为它标注,模型无法学习它,也没人能判断它何时有效。只需一个下午就能写出三行规格说明,解决这个问题——并确定你要训练什么任务、标注什么内容,以及报告哪项指标。
实践操作
为什么要先写规格说明#

计算机视觉项目的每个步骤,都从步骤 0 继承目标。问题模糊会导致数据集含糊不清、标注人员意见不一,指标也会衡量错了东西——而在步骤 2 再怎么训练,也无法弥补这一点。
三行内容#
任务规格说明由三句话组成,每行一句。每句话分别回答项目中不同角色会提出的问题:
| 行 | 回答的问题 | 较弱 | 较强 |
|---|---|---|---|
| 输入 | 模型能看到什么? | “摄像头画面” | “来自 12 个天花板摄像头的 1920×1080 帧,帧率为 5 fps” |
| 输出 | 会返回什么数据? | “检测安全问题” | “forklift 和 person 的框,每个框都带有置信度分数” |
| 决策 | 谁在什么情况下采取行动? | “提高安全性” | “当叉车和人员在 2 秒内相距不足 2 m 时,通知值班主管” |
输出行需要格外谨慎,因为它的数据类型决定了任务:

整张图像对应一个标签,意味着分类;框意味着检测;轮廓意味着分割;关节点意味着姿态;旋转框意味着 OBB;每像素对应一个类别或距离,则意味着语义分割或深度估计。下一课会介绍全部七项任务。
示例:将模型与规则分开#
“我们想知道叉车在仓库地面上何时做出了危险行为。”
这句话描述的是一个项目,而不是一个任务。它混合了两项工作:找到叉车和人员,这是像素层面的工作;判断什么算危险,这是策略层面的工作。把它们拆开:
只有检测器才是模型。菱形部分是普通代码。你现在就可以尝试这种拆分:bus.jpg 中没有叉车,但一辆公交车和四个人就能完成相同的测试。第一版规则是:“当某人的框与车辆框重叠超过 30% 时发出警报”:
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
result = model("https://ultralytics.com/images/bus.jpg", verbose=False)[0]
# The model's job ends here: a class, a box, and a confidence per object.
people = [b.xyxy[0].tolist() for b in result.boxes if result.names[int(b.cls)] == "person"]
vehicles = [b.xyxy[0].tolist() for b in result.boxes if result.names[int(b.cls)] == "bus"]
def share_inside(a, b):
"""Fraction of box a's area that lies inside box b."""
w = max(0, min(a[2], b[2]) - max(a[0], b[0]))
h = max(0, min(a[3], b[3]) - max(a[1], b[1]))
return w * h / ((a[2] - a[0]) * (a[3] - a[1]))
# The decision rule is plain code: change the policy without retraining.
ALERT_SHARE = 0.30
for person in people:
share = max(share_inside(person, v) for v in vehicles)
print(f"person at x={person[0]:3.0f}: {share:4.0%} inside the bus box -> {'ALERT' if share > ALERT_SHARE else 'ok'}")person at x= 49: 70% inside the bus box -> ALERT
person at x=223: 76% inside the bus box -> ALERT
person at x=670: 70% inside the bus box -> ALERT
person at x= 0: 62% inside the bus box -> ALERT四次警报,全都误报:这些人在人行道上,位于一辆停着的公交车前方。检测器对每个框的判断都正确;规则错了,因为 2D 图像中的重叠并不意味着现实中的两个物体距离很近。既然规则是代码,修复方法也就是改代码。比较每个人的脚部接地点与公交车的车轮线:
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
result = model("https://ultralytics.com/images/bus.jpg", verbose=False)[0]
people = [b.xyxy[0].tolist() for b in result.boxes if result.names[int(b.cls)] == "person"]
vehicles = [b.xyxy[0].tolist() for b in result.boxes if result.names[int(b.cls)] == "bus"]
def same_ground_line(person, vehicle, tolerance=40):
"""True when the person's feet are level with the vehicle's wheels and inside its width."""
feet_x, feet_y = (person[0] + person[2]) / 2, person[3]
return vehicle[0] < feet_x < vehicle[2] and abs(feet_y - vehicle[3]) < tolerance
for person in people:
alert = any(same_ground_line(person, v) for v in vehicles)
print(f"person at x={person[0]:3.0f}: feet y={person[3]:.0f}, bus wheels y={vehicles[0][3]:.0f} -> {'ALERT' if alert else 'ok'}")person at x= 49: feet y=902, bus wheels y=751 -> ok
person at x=223: feet y=861, bus wheels y=751 -> ok
person at x=670: feet y=880, bus wheels y=751 -> ok
person at x= 0: feet y=871, bus wheels y=751 -> ok每个人的脚都比车轮线低 110–150 像素,因此他们比公交车更靠近摄像头。无需重新训练,也无需重新标注:安全团队可以迭代规则,而检测器保持不变。生产环境版本会用经过标定的摄像头将脚部映射到地面坐标,并通过目标跟踪加入 2 秒持续时间判断——仍然是在相同的框上运行代码。
主观词语——“不安全”“有缺陷”“可疑”“VIP”——应该写在读取模型输出的代码里。模型回答的是关于像素的问题。如果两位专家会对某个标签争论不休,这场争论就应该放进一条你可以版本化、测试和修改的规则中。
常见问题#
症状:规范写着“检测安全问题”或“理解场景”,标注人员却不知道该画什么。解决方法:将输出改写为带有命名类别的数据类型:“forklift 和 person 的边界框”。
症状:某个类别(如 suspicious_behavior 或 dangerous),两位标注人员对同一帧给出了不同标签。解决方法:用可见证据替代它——例如,一个人处于限制区域内,或一扇门被撑开——并将判断移到决策规则中。
症状:“描述正在发生的事情”(图像描述)或“预测叉车是否会撞到人”(预测)。解决方法:先定位物体,再在此基础上添加跟踪和运动规则。先构建检测器,再做预测。
从规格说明到成功标准#
规格说明指出模型做什么,但没有说明做到什么程度才算足够好,也没有说明漏报与误报相比代价如何。从业务目标出发这一节摘自《构建高性能 YOLO 数据集》,它会将规格说明转化为可衡量的目标。本课程之后的每一课都会回到你的三行内容。
显示解答
货架摄像头的规格说明:
- 输入:固定摄像头面向一条过道,每 60 秒拍摄一张 1920×1080 静态图像。
- 输出:
product和empty_facing(没有商品的货架位置)的边界框,每个框都带有置信度分数。 - 决策:营业时间内,如果同一货架上的三个或更多
empty_facing框连续出现在五张静态图像中,就创建补货任务。
模型无法看到的词语是补货任务、同一货架、连续出现在五张图像中和营业时间内。把它写成规则:
if count(empty_facing on shelf S) >= 3 in each of the last 5 stills and store_is_open(now):
create_restock_task(shelf=S)商店可以更改“3”、“5”或营业时间,而无需重新标注任何图像。