YOLOv11 垃圾分类模型测试指标良好,实时检测效果差的原因是什么?
测试指标好、实时检测效果差时,先检查测试集能否代表实际场景,再核对实时程序加载的模型、图像处理流程和结果过滤设置。目前还无法确定具体原因,数据量偏小、图片来源与摄像头场景不同,都需要重点排查。单纯把训练从 50 epochs 加到 150 epochs,或反复调整 batch size,通常解决不了这些问题。
类别清单中实际有 11 类,与“6 个 class”的描述不一致。这可能是记录错误,也可能涉及类别配置,需要先核实。
为什么测试分数很好,摄像头效果仍然很差?
测试集可能太小,或与训练集过于相似
列出的数据合计有 2,057 个标注框。一张图片可以包含多个框,同一个实物也可以出现在多张图片里,因此这个数字不能代表独立图片或不同实物的数量。
如果数据按 train 70%、valid 20%、test 10% 划分,且各类大致按比例分配,部分少数类的标注框数量估算如下:
| 类别 | 划分前标注框数 | 训练集预计标注框数 | 测试集预计标注框数 |
|---|---|---|---|
| straw | 47 | 33 | 5 |
| plasticcup | 74 | 52 | 7 |
| milkcarton | 100 | 70 | 10 |
| mask | 136 | 95 | 14 |
| bag | 192 | 134 | 19 |
实际数量需要在划分后统计。按图片划分时,如果一张图中有多个目标,各类标注框的分配比例未必与图片比例一致。
测试集中某类只有几个目标时,多检出或漏掉一个,指标就可能明显变化。即使全部检出,也不足以说明模型能识别不同品牌、形状和拍摄条件下的同类垃圾。
还要检查是否存在数据泄漏,例如:
- 同一张网络图片的不同尺寸版本分别进入了训练集和测试集。
- 同一段视频的相邻帧被随机分到了不同集合。
- 同一个垃圾实物在相同背景下的连拍图片进入了不同集合。
- 先做数据增强,再将原图与增强图分别划入训练集和测试集。
这些情况会让测试任务过于容易。应先按原始图片、视频、拍摄批次或实物分组划分数据,再在训练阶段做增强。
网络图片与实际摄像头画面存在分布差异
可以使用 Kaggle 等来源的图片,但要检查它们与实际部署画面的差别。
训练图片里的垃圾可能完整、居中,背景也干净;摄像头画面中的目标可能很小,以俯视角度出现,或受到手持遮挡、反光和运动模糊的影响。同一类别的外观也会变化,例如折叠的口罩、揉成团的袋子和压扁的奶盒。
如果某一类别的图片主要来自固定来源,模型还可能利用背景、拍摄风格等线索。换成学校现场的背景后,这些线索就失效了。
吸管需要单独检查目标尺寸。它在原始画面中可能清楚可见,缩放到模型输入尺寸后,宽度却只剩很少的像素。增加相似图片或继续训练,未必能补回缩放时丢失的视觉信息。
测试指标与实时观察使用了不同条件
目标检测中的“accuracy”含义不明确,建议分别记录:
- Precision:预测出的目标中,有多少是正确的。
- Recall:实际存在的目标中,有多少被检出。
- F1:Precision 和 Recall 的调和平均。
- mAP50、mAP50-95:在相应 IoU 条件下衡量检测性能。
一次检测要被判定为正确,类别必须正确,预测框与标注框也必须达到匹配要求。
F1 会随置信度阈值变化。测试报告中的较高 F1,可能对应一个较合适的阈值,而实时程序使用的是另一个阈值。mAP 也不能直接当作某个部署阈值下的检出率。Ultralytics 的验证流程和预测流程各有阈值设置,因此仅凭汇总分数无法判断摄像头表现。Ultralytics 验证文档
先用同一张画面定位问题
从摄像头保存几张没有绘制检测框、没有额外缩放的原始帧,包含漏检、误检和正常检测的画面。用训练得到的同一份权重对这些图片运行独立推理,先缩小排查范围,避免每次都重新训练。
| 对照结果 | 优先检查方向 |
|---|---|
| 原测试图表现好,摄像头保存的图片表现差 | 场景差异、目标尺寸、训练数据覆盖 |
| 同一张摄像头图片,独立推理正常,实时程序异常 | 权重路径、预处理、类别过滤、后处理 |
| 降低置信度阈值后出现正确检测框 | 分数偏低、阈值设置,需要同时评估误检 |
.pt 模型正常,导出模型异常 | 导出与部署的输入输出处理、精度设置、NMS |
| 物体静止时正常,快速经过时容易漏检 | 运动模糊、处理速度、跳帧、端到端延迟 |
检查类别配置和标注导出
确认模型实际包含哪些类别
核对以下几处的类别配置是否一致:
- 数据配置文件中的
names,以及显式配置的nc。 - 标签文件中的 class ID。
- 加载权重后的
model.names。 - 实时程序中“类别 ID 到名称”的映射表。
只调整 names 的顺序、保留原标签 ID,会改变 ID 对应的类别含义。部署程序中的类别顺序也要核对,不能假定它与训练时相同。
检查程序是否设置了 classes=[...],或者只显示某些类别。被过滤掉的类别即使有模型输出,也不会显示在界面上。
把导出的标签重新画到原图上
Label Studio 中的标注看起来正确,导出后的坐标和类别仍可能出错。
标准 YOLO 检测标签每行是:
class_id x_center y_center width height
class ID 从 0 开始,坐标使用相对于图片宽高归一化后的值。检查是否把左上角坐标误当成中心坐标,或把像素、百分比直接当成归一化值。Ultralytics 检测数据格式
每个类别抽查一些图片,将导出的标签画回原图,确认:
- 框的位置、大小及类别名称正确。
- 同一张图中属于目标类别的可见物体没有漏标。
- 相同类别使用一致的标注规则。
- 图片旋转或方向信息没有造成框与图像错位。
还要明确类别边界,例如 bag 包括哪些袋子、cutlery 包括哪些餐具。标注规则含糊,模型也难以学好。
用实际摄像头建立验证集
在摄像头的实际安装位置采集并标注一批画面,覆盖使用中的距离、光照、遮挡和垃圾状态。采集时要更换实物,分批拍摄,避免把大量相邻帧当作独立样本。
现场数据应划分为不同集合,分别用于:
- 训练:补充模型没见过的外观和场景。
- 验证:选择模型、输入尺寸与阈值。
- 最终测试:保留独立拍摄批次,在方案确定后评估。
分别统计每类目标的数量、Precision、Recall 和混淆情况。对 can,重点看误检框落在什么物体上;对 mask、milkcarton、straw、bag,要区分是完全没有候选框、分数过低,还是被预测成了其他类别。
如果反复根据某个测试集中的错误改进模型,这个测试集就已经参与了开发,需要另外保留最终测试数据。
代码示例:固定权重,检查现场数据与阈值
运行以下示例需要使用 Ultralytics Python API,并安装支持 YOLO11 的 ultralytics。路径要替换为实际路径;640、0.001、0.05 等值仅用于建立排查基线,最终参数需要结合该系统确定。
先创建 live.yaml,沿用训练时完全一致的类别映射,将 val 指向已标注的现场验证图片。图片与标签按 YOLO 检测数据目录组织。
from ultralytics import YOLO
def main():
model = YOLO("runs/detect/train/weights/best.pt")
print("Model classes:", model.names)
metrics = model.val(
data="live.yaml",
split="val",
imgsz=640,
rect=False,
conf=0.001,
iou=0.7,
plots=True,
verbose=True,
)
print("mAP50:", metrics.box.map50)
print("mAP50-95:", metrics.box.map)
if __name__ == "__main__":
main()
查看终端输出的逐类指标和目标数量,以及生成的预测示例、PR 曲线和混淆矩阵。较低的置信度门槛便于评估 PR 曲线,实时系统的阈值仍需单独选择。Ultralytics 验证接口
接着,对同一张检测失败的画面比较不同阈值:
from ultralytics import YOLO
model = YOLO("runs/detect/train/weights/best.pt")
for conf in (0.05, 0.15, 0.25):
result = model.predict(
source="camera_failure.jpg",
imgsz=640,
rect=False,
conf=conf,
iou=0.7,
classes=None,
agnostic_nms=False,
verbose=False,
)[0]
print(f"\nconf={conf}")
for box in result.boxes:
class_id = int(box.cls.item())
score = float(box.conf.item())
print(model.names[class_id], f"{score:.3f}")
result.save(filename=f"camera_failure_conf_{conf:.2f}.jpg")
conf 控制最低置信度;classes=None 保留所有类别。这里的 iou 是 NMS 阈值,与计算 mAP 时判断预测框是否匹配标注框的 IoU 阈值用途不同。Ultralytics 预测参数
如果降低阈值后出现了正确框,同时也增加了大量误检,就要在现场验证集上权衡检出与误检,不能仅凭一张图降低全局阈值。
实时程序还应核对哪些地方?
独立推理正常、实时程序异常时,逐项核对两者的差别:
- 是否加载了同一份自定义权重,路径是否指向旧训练结果。
- 是否有额外的类别筛选、置信度过滤或显示条件。
- 是否重复缩放、重复归一化,或错误拉伸图像。
- 自定义后处理是否正确还原了 letterbox 的缩放和填充偏移。
- 是否错误解析了模型输出的坐标格式。
- 是否误用 class-agnostic NMS,抑制了不同类别的重叠目标。
颜色通道需要按接口要求处理。Ultralytics 高层接口接收 OpenCV 的 NumPy 图像时按 BGR 处理,传入张量时则有不同的输入约定。先核对接口要求,再决定是否需要 BGR/RGB 转换。Ultralytics 输入格式说明
如果使用 ONNX、TensorRT 或量化模型,先让原始 .pt 模型与导出模型处理同一批图片,再比较部署结果,以判断差异是否来自模型转换或运行环境。
数据应该怎么补,训练应该怎么改?
目前的数据适合做原型,但仅凭标注数量无法保证现场效果。也没有“每类达到某个固定数量就一定够用”的标准。
优先针对检测失败的情况补充数据或调整设置:
| 当前问题 | 优先补充或调整 |
|---|---|
| straw 漏检 | 不同颜色、背景和摆放方向;检查缩放后的宽度,必要时调整拍摄距离或输入分辨率 |
| mask、bag 漏检 | 展开、折叠、揉皱、部分遮挡等实际状态 |
| milkcarton 漏检 | 不同包装、朝向、压扁状态及现场光照 |
| can 误检 | 收集误检物体和背景,作为难负样本;属于已有目标类别的物体仍须正确标注 |
| 多类在手持时失效 | 增加手持、遮挡和实际投放姿态的样本 |
补充数据时,要增加实物和拍摄条件的多样性。同一根吸管连续拍摄几百帧,覆盖的情况仍然有限。确认不包含任何目标类别的背景图,可以用作负样本。
完成数据与部署检查后,再调整训练:
- 如果之前使用随机权重训练,可以改用预训练权重微调。
- 根据现场验证结果决定是否增加输入尺寸,同时测量推理速度。
- 使用符合现场条件的亮度、尺度、模糊和遮挡增强,避免增强后目标难以辨认。
- 根据训练与现场验证指标的变化决定训练时长。训练指标继续提高,而现场验证表现停滞或下降时,应检查过拟合。
- 每轮只改少量设置,并记录逐类结果,便于判断改进来自哪里。
还应记录物体经过检测区域时的成功检出率和端到端延迟。视频中偶尔出现一次检测框,未必足以支持后续分拣。单帧指标达标后,需要继续验证目标在实际停留时间内能否被稳定识别。
备注:内容仅供参考。
