资讯动态

方便面调料包正反检测数据集与YOLO训练避坑指南

发布时间:2026/10/27 17:01:52 来源:尧图企业网站定制
简介面向方便面调料包正反面识别与产线质检场景的目标检测数据集图像由海康设备拍摄按正常放置与反方向放置分别标注为 one、two适合训练和验证 YOLO 系列等检测模型。压缩包共 904 个文件包含 441 张 jpg 图像、442 个 txt 标注文件及 21 个 xml 辅助标注文件整体约 758.95MB便于直接组织训练集、验证集与复核标注。数据为手动标注描述中给出 YOLOv8 检测 mAP 约 90% 的参考结果可用于课程实验、算法对比与工业视觉原型验证包内样本覆盖不同拍摄角度与摆放状态标注与图像对应便于做数据清洗、标签核对和模型迭代。已有 110 人学习适合计算机视觉初学者、目标检测实践者及需要正反面识别样本的开发者参考。1. 方便面调料包的正反朝向值得单独做一个检测数据集产线上投料工位最容易被低估的一类异常是调料包躺反了。正面朝上和背面朝上在俯视图里轮廓几乎一模一样人眼要盯着图案方向、撕口位置才分得清连看八小时必然走神。这份方便面调料正反目标检测数据集就是冲着这个场景整理的工业面阵相机俯拍正常放置标为 one反向放置标为 two标注直接是检测框格式拿来就能进训练流程。它适合做食品包装产线视觉的工程师也适合手头有工位相机、想快速验证「方向类缺陷能不能用目标检测兜住」的团队。真正难的地方不在模型而在增强策略和标签一致性——这两点决定了这个数据集是帮你省两周还是让你在产线上翻一次车。2. 拆开数据集one 与 two 的标注边界和采集口径2.1 为什么把正反做成两个检测类而不是「检测分类」两段式工业上处理方向异常常见两条路线。第一条是先检出一个通用的「调料包」类再把框裁出来送进一个二分类网络判断正反第二条就是这份数据集采用的路线直接把 one 和 two 当成两个检测类别一次前向出结果。两段式看起来更「模块化」实际问题不少。裁剪框只要有几像素抖动送进分类器的图就带上了不同比例的背景分类器很容易把传送带纹理、阴影边缘当成判别依据换个光照就崩。而且调料包的正反差异是全局的——整幅包装图案旋转了 180 度撕口从左上跑到右下——这种差异在整图上判断比在裁剪块上判断更稳。单阶段两类检测还有个隐性好处延迟低。产线节拍通常几十毫秒一个工位两段式多一次推理和一次内存拷贝节拍紧的时候就是硬伤。代价也要说清楚。两类检测在目标外观高度相似时容易出现同一包上出两个框、类别还打架的情况后处理必须显式处理「一包只能有一个结论」。这一点在第 5 章会单独展开。2.2 文件结构、标注格式与类别序数据集按检测任务的通用组织方式给到图片和同名 txt 标签一一对应标签是 YOLO 的归一化格式每行五个数class_id cx cy w h其中 cx、cy、w、h 都是除以图宽图高之后的 0~1 浮点数class_id 从 0 开始。这个数据集里类别序必须钉死为class_id类别名含义现场语义0one正常放置正面朝上可直接投料1two反向放置方向相反需要翻包或剔除目录上通常是这样的结构划分脚本跑完就是这个样子seasoning/ ├── images/ │ ├── train/ *.jpg │ └── val/ *.jpg ├── labels/ │ ├── train/ *.txt │ └── val/ *.txt └── seasoning.yaml这里有个血泪经验类别序一旦定下来转换脚本、data.yaml 的 names、后处理里的类别映射三处必须同步任何一处写反都不会报错只会安静地把 one 判成 two。所以我一般会把类别映射抽成一个常量模块所有脚本都从它 import改一处全改。2.3 工业面阵相机拍出来的图对标注意味着什么采集端是一台 GigE 接口的工业面阵相机固定俯拍、配条形光源快门和曝光锁死避免频闪。这种条件下图像有三个特征会直接影响标注和训练。第一个是反光。调料包外层是复合膜铝箔层在条光下会出一条高亮带位置随包体摆放角度漂移。高亮带压在撕口或图案上时标注员会犹豫框边界该画到哪我给的规则是框贴着包体最外轮廓画不因为反光去缩框反光交给增强和更多样本覆盖。第二个是背景单一。传送带底色固定模型很容易退化成「找背景里的异物」而不是「看包体方向」。所以采集时刻意保留了一些背景变化——皮带接头、边缘挡板、不同批次的色差——虽然样本变脏但泛化会好一截。第三个是分辨率。原图分辨率通常明显高于训练输入直接缩到 640 会让撕口这种小结构糊掉。我的做法是训练输入不低于 960并且关掉矩形推理保证 letterbox 的缩放比例一致。3. 从原始素材到可训练目录划分、转换与自检3.1 划分策略按批次和时间段切别用随机数切这一步看着最简单翻车率却最高。连续抽帧的图片放进随机划分训练集和验证集里会出现几乎一样的画面mAP 能刷到 0.98上线一塌糊涂。我的规则是按采集时段切分同一个上午、同一批包材、同一次光源调整拍出来的图整段进同一个集合绝不拆散。训练集覆盖多种光照和包材批次验证集单独留一到两个批次做「没见过」的检验。比例上 8:2 够用但验证集必须至少包含一批 two 类样本否则你根本不知道反向包检得怎么样。如果数据里 two 类明显偏少现场反向放置本来就是小概率事件不要靠在验证集里多放 two 来「平衡」那会把评估指标做虚。正确做法是在训练集里对 two 做过采样或复制粘贴增强验证集保持真实分布这样指标才对应产线真实表现。3.2 标注转换脚本如果原始标注是 Labelme 的 json或者是一堆混在一起的 txt用下面这段统一转成 YOLO 目录。转之前先把每张图对应的标注找齐缺标注的图直接剔除不要让空标签图混进训练集。# convert_to_yolo.py import json import random import shutil from pathlib import Path # 类别序写死禁止在其他脚本里重复定义 CLASS_MAP {one: 0, two: 1} SRC_IMG Path(raw/images) SRC_ANN Path(raw/annotations) # labelme json 目录 DST Path(seasoning) VAL_RATIO 0.2 SEED 42 def labelme_to_lines(json_path: Path, img_w: int, img_h: int) - list[str]: data json.loads(json_path.read_text(encodingutf-8)) lines [] for shape in data.get(shapes, []): label shape[label] if label not in CLASS_MAP: continue # 未知类别直接跳过不猜 pts shape[points] xs [p[0] for p in pts] ys [p[1] for p in pts] # 越界坐标夹到图内避免负值或 1 导致训练报错 x1, x2 max(0, min(xs)), min(img_w, max(xs)) y1, y2 max(0, min(ys)), min(img_h, max(ys)) if x2 - x1 4 or y2 - y1 4: continue # 极小的误标框丢弃 cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{CLASS_MAP[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) return lines def main(): pairs [] for img_path in sorted(SRC_IMG.glob(*.jpg)): json_path SRC_ANN / f{img_path.stem}.json if not json_path.exists(): print(f[skip] 无标注: {img_path.name}) continue pairs.append((img_path, json_path)) random.Random(SEED).shuffle(pairs) n_val max(1, int(len(pairs) * VAL_RATIO)) split {val: pairs[:n_val], train: pairs[n_val:]} for subset, items in split.items(): (DST / images / subset).mkdir(parentsTrue, exist_okTrue) (DST / labels / subset).mkdir(parentsTrue, exist_okTrue) for img_path, json_path in items: from PIL import Image with Image.open(img_path) as im: img_w, img_h im.size lines labelme_to_lines(json_path, img_w, img_h) if not lines: continue # 转完没框的图不进数据集 shutil.copy2(img_path, DST / images / subset / img_path.name) (DST / labels / subset / f{img_path.stem}.txt).write_text( \n.join(lines), encodingutf-8 ) if __name__ __main__: main()逻辑上分四步建立图片和标注的一一对应按固定种子打乱后切分逐张读原始尺寸做归一化最后落盘。几个参数需要按你的实际情况改。VAL_RATIO控制验证集比例样本量小于 500 张时建议提到 0.25否则 two 类在验证集里可能只有十几张指标抖动大得没法看。SEED固定住是为了复现别每次跑都换。坐标夹取那两行是必须的Labelme 手画的框越过图像边界很常见不夹会在训练时抛归一化越界。要注意random.shuffle是按文件切的。如果你的数据是连续抽帧这样做仍然会泄漏必须改成按时间段分组后整组切分具体见 3.1。3.3 转换后自检三个统计量足够发现大部分问题转完不检查等于把雷埋进训练。我固定跑下面这段统计看三个数每类框数量、每类框的面积中位数、有没有空标签。# check_labels.py import statistics from collections import Counter, defaultdict from pathlib import Path NAMES {0: one, 1: two} root Path(seasoning/labels) for subset in (train, val): cnt Counter() areas defaultdict(list) empty 0 for txt in (root / subset).glob(*.txt): lines [l for l in txt.read_text().splitlines() if l.strip()] if not lines: empty 1 continue for line in lines: c, cx, cy, w, h line.split() c int(c) cnt[NAMES[c]] 1 areas[NAMES[c]].append(float(w) * float(h)) print(f--- {subset} ---) print(空标签文件:, empty) for k in (one, two): a areas[k] med statistics.median(a) if a else 0 print(f{k}: 框数{cnt[k]}, 面积中位数{med:.4f})判断标准很直接。第一one 和 two 的框数量比例如果超过 10:1说明反向样本严重不足训练时会明显偏向 one需要过采样。第二两类框的面积中位数应该接近因为都是同一个包如果 two 的面积中位数只有 one 的一半多半是标注时把反向包框小了或者混进了别的物体。第三空标签文件数必须是 0出现就说明有图漏标回去补。4. 训练配置把翻转类增强全部关掉4.1 方向敏感任务的增强白名单与黑名单这是整个流程里最容易翻车的地方也是这类数据集和普通检测数据集最大的区别。调料包的正反本质是 180 度旋转关系。也就是说你把一张 one 的图做一次水平翻转画面看起来就是 two 的样子但标签还写着 one。模型学到的就是「长得像 two 的东西其实是 one」。这种脏标签不会让 loss 爆炸只会让模型在现场表现得像玄学——训练集指标漂亮实际一到反向包就犹豫。所以增强必须分类处理增强项建议值原因fliplr水平翻转0.0等价于把 one 翻成 two标签失效flipud垂直翻转0.0同上180 度旋转的另一种实现degrees随机旋转0.0 或 ±10大角度旋转等价于改变朝向小角度只用来模拟摆放歪斜mosaic0.0~0.3四图拼接不改变朝向但会缩小目标工位场景收益有限scale0.3~0.5模拟相机高度差异和包体鼓包安全translate0.1模拟包体在视野内偏移安全hsv_h / hsv_s / hsv_v0.015 / 0.5 / 0.3覆盖不同批次包材色差和光源衰减安全copy_paste视 two 数量定对 two 做过采样时很有用注意粘贴位置别叠到已有包上表里前两行是硬性约束不管你的框架默认值是什么都必须显式改成 0。4.2 data.yaml 与训练命令先把数据集描述文件写对。names 的顺序必须和转换脚本里的 CLASS_MAP 完全一致这是唯一一处不能靠记忆的地方。# seasoning.yaml path: ./seasoning train: images/train val: images/val names: 0: one 1: two训练命令把增强参数一并显式传入不要依赖默认值。不同框架版本的默认 fliplr 通常是 0.5不改就是给自己埋雷。yolo detect train \ modelyolov8s.pt \ dataseasoning.yaml \ imgsz960 \ epochs200 \ batch16 \ workers8 \ fliplr0.0 \ flipud0.0 \ degrees0.0 \ mosaic0.2 \ scale0.4 \ translate0.1 \ hsv_h0.015 hsv_s0.5 hsv_v0.3 \ patience40 \ projectruns/seasoning逐项说一下为什么这么给。imgsz960是为了保住撕口和图案细节代价是显存占用约为 640 的 2.2 倍单卡 16G 上 batch 大概只能到 16如果显存吃紧就降到 8 配合梯度累积。epochs200配patience40是因为这个任务收敛其实很快通常 60~80 轮就基本平了后面留着是为了等 two 类的召回慢慢爬上来。scale0.4不算激进产线上包体鼓包程度不同尺度抖动是真实存在的这个值能覆盖住。mosaic0.2是个折中。完全关掉模型对背景变化的鲁棒性会差一点开到 1.0四图拼接会把单包压得很小反而削弱方向特征。0.2 到 0.3 之间是我试下来比较稳的区间。4.3 类别不平衡与置信度校准如果自检发现 two 只有 one 的三分之一以下光靠增强不够得在采样上下功夫。最简单的是复制 two 的图片并改名让它在训练集里重复出现两到三次注意复制时要连带标签一起复制并且验证集不能这么做。稍微讲究一点的做法是用 copy_paste把 two 的包体抠出来贴到不同背景上同时生成新标签这样既增加数量又增加多样性。另一个常被忽略的点是置信度校准。两类检测在不平衡数据上训练完two 的置信度分布整体会比 one 低一截因为模型见得少、把握小。如果直接拿 0.5 当阈值one 检得挺好two 大面积漏。所以阈值不能拍脑袋定必须看分布具体做法放在第 6 章。5. 避坑与排查五条现场记录5.1 训练指标 0.97上线反向包漏检一半现象验证集 mAP50 很高混淆矩阵里 one 和 two 都干净但现场连续跑一天反向包检出率不到一半。原因增强配置里 fliplr 没有显式关闭用的是框架默认的 0.5。每一轮都有一半样本被水平翻转one 的图被翻成 two 的样子却仍标 one模型被迫学到「这种朝向也可能是 one」判断边界被抹平。解决把 fliplr、flipud、degrees 全部显式置 0 写入训练命令不用默认值重训后在验证集上单独看 two 的召回正常应该比之前提升十几个点。5.2 推理结果全是 two一个 one 都没有现象模型训完直接拿现场图测框画出来了但类别清一色是 two置信度还不低。原因data.yaml 里的 names 顺序和转换脚本的 CLASS_MAP 对不上或者后处理里有另一份独立的类别映射写反了。这类错误框架不会报因为类别数是对的。解决把类别映射收敛到一个常量模块data.yaml 从它生成后处理也 import 它转换完成后抽十张图用可视化脚本画框肉眼确认 one 的图上标的是 one。5.3 换了批包材指标断崖式下跌现象同一套权重上一批包材测着挺好换了一批印刷色偏深的two 的检出率掉了三成。原因训练集里所有样本都来自同一两个批次模型实际上记住了那一批的颜色和反光模式没学到方向本身。解决按批次分层采样训练集至少覆盖三批以上包材hsv_s 和 hsv_v 适当加大到 0.5 和 0.3模拟色差验证集单独留一个没进训练的批次作为上线前的真实门槛。5.4 同一包上出两个框类别还互相打架现象反向包上同时出现一个 one 框和一个 two 框NMS 之后两个都留着下游逻辑不知道该信谁。原因one 和 two 外观高度相似模型在边界样本上会同时激活两类。默认 NMS 是类内抑制不同类别之间不互相压制所以两个框都能活下来。解决这个场景有个业务前提——每个工位视野里一包只有一个结论。所以后处理改成先做类别无关的 NMS再对剩下的框按置信度取 top-1如果 top-1 的置信度低于阈值输出「未识别」交给人工复核不要硬判。5.5 标注框画得过紧增强后目标被裁掉一半现象训练正常但现场包体靠近视野边缘时经常漏检。原因标注时框贴着包体轮廓画scale 和 translate 增强一作用包体被推到图像边界外只剩半个目标模型学到的是残缺特征。解决标注时框外扩 2~3 像素留余量训练时把 translate 控制在 0.1 以内或者开启边界裁剪保护验证集里专门放一批包体靠边的图确认不塌。6. 上线前的验证拿混淆矩阵和置信度分布定阈值模型训完不等于能用最后一道关是把阈值定下来并且证明这个阈值在现场站得住。先跑一次完整验证把混淆矩阵和每类的 P/R 曲线拉出来from ultralytics import YOLO model YOLO(runs/seasoning/weights/best.pt) metrics model.val( dataseasoning.yaml, imgsz960, conf0.001, # 压低阈值拿到完整置信度分布 iou0.6, plotsTrue, ) print(metrics.box.maps) # 每类 mAP50-95 print(metrics.confusion_matrix) # 看 one-two、two-one 的错分conf0.001是为了让所有候选框都吐出来方便统计分布正常推理不会用这个值。跑完重点看混淆矩阵的对角线和非对角线如果 one 被大量判成 two说明模型对正向样本太保守如果 two 被大量判成 one就是漏检反向包这是产线上更不能接受的方向优先级更高。接着按类别统计置信度分位数用验证集的结果反推阈值类别P50 置信度P10 置信度建议阈值one0.930.780.5two0.810.420.35表里的数字是自己跑出来填的不是固定值。取值思路是让 two 的阈值落在它 P10 附近偏上保证真实反向包九成以上能过one 的阈值可以高一点因为正向包数量多误判成 two 的代价是误剔除比漏检轻。两个阈值分开设比全局一个阈值靠谱得多。最后一步别省从验证集里挑三十张图用最终阈值跑一遍人工核对每个框尤其核对被压到阈值边缘的那些。我吃过一次亏验证集指标好看实际是模型把传送带接缝当成了包体的一部分框大了半圈。从那以后每次定完阈值我都强制走一遍人工抽检不看指标只看框。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑