ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

海康相机调料包正反检测:YOLO数据集制作与训练实战

海康相机调料包正反检测:YOLO数据集制作与训练实战 简介在方便面生产质检场景中调料包的正反放置常需自动识别传统人工目检方式效率较低且易漏检。这份由海康设备拍摄并手动标注的目标检测数据集面向工业视觉、缺陷检测以及YOLO系列算法开发者可用于训练区分正常放置one与反向异常two两类目标解决产线上调料包方向自动判别的核心需求。数据集共904个文件包含441张jpg原图、442个txt格式的YOLO标签以及21个xml辅助标注文件压缩包大小约758.95MB图片与标签一一对应txt文件可直接用于YOLOv8等模型训练xml文件便于其他检测框架的格式转换与二次标注。标注质量扎实作者实测mAP可达90%左右适合作为基线数据集、迁移学习素材或算法对比基准也可用于模型鲁棒性测试与部署前的可行性评估。目前已有109人学习下载特别适合需要真实工业场景数据开展目标检测实验的开发者。1. 海康拍摄的调料正反目标检测数据集one/two 到底在检什么一条方便面产线每分钟过几百个调料包反放的料包没被挑出来走到喷码工序就是喷码打在错误位置走到包装工序就是袋面朝向不一致。海康相机拍摄的调料包正反目标检测数据集就是把这类现场画面整理成一套可训练的监督数据画面里正常放置的料包标成 one异常反方向放置的标成 two。它服务的是目标检测里的姿态判别需求适合做 YOLO 系列模型训练、产线质检验证的人直接拿来做基底数据。你不用再纠结“正反算不算分类问题”检测模型天然会通过框内纹理学方向。这篇从采集、标注、训练到验证全局拆一遍。2. 先立住判定逻辑one 和 two 的边界怎么定模型才不翻车2.1 调料包正反面在图像里到底差在哪目标检测的矩形框是不带方向的。一个轴对齐的包围框料包正放还是反放给出的坐标几乎一样区别只在框里装的内容不同。所以单类检测器只能回答“画面上有没有料包”回答不了“现在是正是反”。one/two 两个类的设计本质是把方向信息交给分类分支在同一个特征层上模型既要区分料包和背景又要区分两种方向姿态。再看实物差异。正放的调料包通常印刷面朝上能看到品牌名、口味标识和完整文字反放时出现在画面里的是背面印刷或者素面文字方向颠倒图案也可能镜像。封口纹路是第二个线索很多调料包的封口锯齿、压纹在正反两面的视觉密度不一样光靠印刷主体都能分但补上封口特征能提高弱光样本的鲁棒性。麻烦的是双面印刷的料包。正面印品牌背面印配料表两面都有字这时“方向”主要靠文字的阅读方向和封口朝向判断。如果采集时没有刻意收集这批“两面都有字”的样本模型很容易把它们当成一类正反混淆率直接上去。做数据集之前先把这些差异点列成表比拍脑袋标注靠谱得多。2.2 标注规范什么时候标 one什么时候标 two实际动手前要写一份标注规范定死正向基准。最常见做法是以印刷文字的阅读方向为正向文字从左到右、从下到上可正常阅读就是 one文字倒置、镜像或背面朝上就是 two。没有文字印刷的素面包以封口压纹朝向或喷码位置定义。这一条不写清楚换一个标注员标签就漂了。判断顺序也要固定先看有没有印刷面再看文字方向再看封口朝向最后看喷码。规则冲突时以第一条为准。比如一个料包印刷面朝上但文字转了 180 度按规则它还是 two不能因为“看起来正面朝上”就标成 one。实际产线里这种“朝上但方向反”的样本恰恰是最常见的异常形态。边界情况提前约定倾斜不超过 30 度按 one 处理严重遮挡看不清文字方向的不标或者单独放进测试集当干扰项模糊、运动拖影的帧先跳过后期模型迭代需要再加。标注时框要贴着目标外轮廓不要把背景收进来否则模型学到的是背景块而不是料包本身。有人会问既然方向判别这么绕为什么不只标一个类靠后处理判断方向端到端训练误差最小产线部署时直接读 class_id 就够了不用额外维护一个方向判别脚本。one/two 两类的数据集就是给检测模型一个明确的学习目标。2.3 样本规划做一套能用的正反检测集最少要多少图经验值one 和 two 各 400 个框起步。如果产线光照、角度、线速差异大按工况分层采样不能只在一个时段拍完。比如上午自然光、晚上灯光、不同批次料包各收集一批保证每个工况下两类都有样本。一张图不要只有一个目标。全画幅里有十几个料包比单包特写更接近产线真实输入模型在推理时也更容易适应小目标。如果担心目标太小训练时提高 imgsz 或者做图像分块不能指望用单包特写训出来的模型直接扛全画幅。数据集切分按批次或场景来不要 random.shuffle 全局乱切。同一批料包、同一条传送带拍的连续帧一部分进训练一部分进验证等于变相作弊验证集 mAP 虚高换现场就现原形。这个坑在标题里看不出来实际做数据的人基本都会踩一次。3. 素材采集用海康相机把现场画面变成可标注图片3.1 海康 RTSP 取流主码流和子码流怎么选海康相机基本都支持 RTSP 取流不用额外软件就能拉流。地址格式是固定的rtsp://用户名:密码IP:554/Streaming/Channels/101101 表示主码流102 表示子码流。训练素材必须用主码流分辨率够高印刷细节才保留得住子码流只适合人工预览和程序自检直接拿子码流图标注文字边缘全糊了后面的正反检测别想做起来。拉流脚本用 OpenCV 就能写先确认能把画面拉通再扩展import cv2 rtsp_url rtsp://admin:passwd192.168.1.64:554/Streaming/Channels/101 cap cv2.VideoCapture(rtsp_url) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) frame_count 0 while True: ret, frame cap.read() if not ret: break frame_count 1 if frame_count % 30 0: cv2.imwrite(fframes/{frame_count:06d}.jpg, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release()设置 CAP_PROP_BUFFERSIZE 为 1避免拉流时把旧帧堆在缓存里读出来全是延迟几秒的过期画面。按帧间隔保存而不是按时间保存因为 RTSP 传输过程可能丢帧按固定时间间隔会漏采。用户名密码里如果带 或冒号要做 URL 编码否则整个地址解析会中断。3.2 拍摄环境角度、光照和运动模糊的坑相机推荐垂直俯拍或者固定一个侧俯视角同时让料包长边与画面水平方向基本一致。检测框是水平的目标如果随机旋转分类分支学到的方向特征会和拍摄角度耦合现场角度一变模型就失效。机位架好以后用胶带固定不要每天调整。光照是最大的变量。调料包大多有铝箔或镀铝材质强光下直接镜面反光把印刷文字盖掉一片正反差异就被抹平了。常见做法是条形光源或者环形光源放在相机同侧的前上方角度微调让反光偏离镜头方向。投光灯从正上方往下打是翻车重灾区正面反光一片白怎么标都没救。曝光和快门也要手动控制。自动曝光在料包经过时画面忽明忽暗同一批图的亮度不一致模型会拿亮度做分类特征。快门设置在 1/500s 以上配合足够光照先保证采集阶段能冻结料包运动文字边缘清晰再谈分类精度。运动模糊是导致 two 被错判为 one 的常见原因因为字符边缘被糊掉以后模型只能靠轮廓猜。3.3 定时抓帧与批量采集记录批次、光照和场景采集要考虑样本多样性不同批次料包、不同线速、不同光源场景都要覆盖。改动光源或机位后单独建文件夹文件名带批次前缀留一份采集记录 CSV方便后面追溯“这批图是改了灯光拍的”还是“这批正反件数量比较少”。定时抓帧脚本这样组织import cv2, csv, time from pathlib import Path out_dir Path(frames) out_dir.mkdir(exist_okTrue) cap cv2.VideoCapture(rtsp://admin:passwd192.168.1.64:554/Streaming/Channels/101) gap 0.5 # 抓帧间隔秒 next_shot time.time() idx 0 with open(out_dir / capture_log.csv, w) as fp: writer csv.writer(fp) writer.writerow([index, time, batch, note]) while True: ret, frame cap.read() if not ret: continue now time.time() if now next_shot: idx 1 fname fbatchA_{idx:05d}.jpg cv2.imwrite(str(out_dir / fname), frame) writer.writerow([fname, time.strftime(%Y-%m-%d %H:%M:%S), batchA, ]) next_shot now gap if cv2.waitKey(1) 0xFF ord(q): break0.5 秒抓一帧产线正常线速下相邻帧的料包位置会有差异获得的样本比连拍再抽帧更均匀。CSV 里的 batch 字段很实用后面划分训练集和验证集时直接按 batch 分避免同一批连续帧污染验证结果。如果以后要升级成产线级正式方案常见做法是走海康 SDK 的取流接口或者用 VisionMasterVM搭图像方案。但前期造数据集阶段RTSP 加 OpenCV 就够用工具越轻量越不容易打断采集节奏。4. 标注与格式转换把图像变成 YOLO 能直接吃的数据集4.1 标注工具选型与统一规范标注工具用 labelme、CVAT、x-anylabeling 都行唯一要求是多人协作时导出格式统一。我习惯让标注员导出 VOC XML后面统一转 YOLO TXT这样换标注工具不用改训练流程。标注规范再强调一遍框贴着目标留 1 到 2 个像素不要把相邻料包或背景包进来one 和 two 都要标漏标一个框训练时那个区域就变成背景负样本严重遮挡、模糊到无法判断方向的图跳过不硬标。多人标注前做一个一致性抽检挑 30 张图让两个标注员标同一批对比框重叠和标签一致率。不一致超过 5%说明判定规则还没讲清楚先回去统一规范再正式开标。正反边界是语义问题不是训练问题数据源头错了后面全是白搭。4.2 VOC/JSON 转 YOLO TXT转换脚本与四个边界坑YOLO 训练需要的是归一化坐标的 TXT一行一个目标类别ID 中心点x 中心点y 宽度 高度全部归一化到 0 到 1。VOC 的 XML 里存的是像素坐标必须转换。import xml.etree.ElementTree as ET from pathlib import Path import cv2 CLASSES {one: 0, two: 1} def convert_xml(xml_path, out_txt): tree ET.parse(xml_path) root tree.getroot() img_file root.find(filename).text img cv2.imread(str(Path(xml_path).parent / img_file)) h, w img.shape[:2] lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) bw min(max(bw, 0.0), 1.0) bh min(max(bh, 0.0), 1.0) lines.append(f{CLASSES[name]} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) Path(out_txt).write_text(\n.join(lines))用 cv2.imread 读图取宽高而不是信任 XML 里存的尺寸。很多标注工具在图片被预处理缩放后XML 里的原始尺寸没同步更新坐标转换出来全部偏移。转换完抽几张图叠加画框检查这一步别省。四个边界坑坐标越界要裁剪到 0 到 1类别名写错会让类别和 ID 错位训出来的模型 one/two 互换都不知道一张图多个目标要循环写多行不能只写最后一行TXT 必须和图片同名不同后缀YOLO 按文件名自动匹配标签。用 labelme 的 JSON 导出时同理要取 polygon 或 rectangle 的最小外接矩形坐标本身相对图片不需要额外缩放。4.3 目录组织、数据集划分与类别统计YOLO 标准目录长这样dataset/ images/ train/ val/ labels/ train/ val/ data.yamlimages 里放图片labels 里放同名 TXTtrain 和 val 按批次划开。写一个统计脚本检查标签数量from pathlib import Path from collections import Counter for split in [train, val]: cnt Counter() for txt in Path(fdataset/labels/{split}).glob(*.txt): for line in txt.read_text().strip().splitlines(): cls line.split()[0] cnt[cls] 1 print(split, cnt)统计完看两件事一是有没有空标签文件空文件会导致训练时那个样本没有目标框报错或收敛慢二是 one 和 two 的框数量是否悬殊。如果 two 只有 one 的十分之一先补采真实异常样本不要用镜像翻转去凑数。镜像翻转会把方向语义搞乱one 的镜像可能恰好接近 two 的视觉形态合成出来的数据反而干扰模型。真实产线里 two 的比例本来就低训练集做成 1:1 是方便收敛但部署时要记住现场先验概率和训练分布不一样误报率会偏高。5. YOLO 训练与避坑记录从环境配置到能用的权重5.1 ultralytics 环境配置与 data.yaml零基础从没配过深度学习环境也能跑通装 Anaconda 建 Python 3.10 环境pip install ultralytics装完验证一下 torch 和 CUDA 能否对上能跑就不追求最新版本。数据集挂出来的训练配置长这样path: D:/datasets/condiment train: images/train val: images/val nc: 2 names: [one, two]names 的顺序和标签文件里的类 ID 一一对应one 是 0、two 是 1写反了就是正反互换训练时还看不出问题验证时才暴露。path 建议用绝对路径相对路径在不同操作系统下的解析行为不一致报错排查浪费时间。5.2 训练命令与关键参数解读训练命令最小可复现版本yolo detect train \ datadataset/condiment.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ patience20 \ cacheTrue分批讲参数。n 系列迭代快适合先跑通流程正反检测的差异是印刷纹理级别的n 精度不够就换 s 或 m不要一上来就上 x重训一轮成本太高。预训练权重必须用调料包数据集规模不可能从零训起。epochs 给到 120 配合 patience 早停loss 不再下降就自动停不会浪费算力。imgsz 从 640 起步显存够就试 832 或 960小印刷文字在这个参数上收益非常明显。batch 按显存调8G 显存开到 16 比较稳再大会爆显存。cacheTrue 把所有图片读进内存加速训练数据量大或内存不够就关掉。中间断了不用从头来训练目录下会生成 last.pt用 yolo detect train resumeTrue 接着跑。这条能省掉大量重训时间但要注意换了机器或数据路径后不能直接 resume会报路径错。5.3 训练里的五条踩坑记录现象、原因、解决记录一loss 正常下降但 two 几乎检不出来。原因是 two 样本少且训练时用了默认的水平翻转增强。YOLO 默认开启 fliplr0.5翻转会把 one 变成镜像而部分 two 形态恰好也是镜像或倒置模型把翻转当成类别特征方向语义被彻底打乱。解决训练时设置 fliplr0、flipud0保留 mosaic、scale、hsv 增强不影响方向语义。记录二换一批不同印刷样式的调料包直接失效。原因是训练集只来自一个批次、一种光源模型学到的是这个批次的底色和纹理不是普适的正反规律。解决采集阶段至少覆盖两到三个批次刻意混入光照变化样本让模型看到同类不同外观。记录三训练中途报错或 loss 出现 nan。原因是学习率偏大或数据里有损坏图片、空标签文件。解决先扫一遍 labels 目录把空 txt 删掉lr0 从默认降一半再不行降到 0.002 左右。记录四标注一致性问题导致 one 类内部特征分裂模型对部分 one 的置信度极低。原因是两个标注员对“印刷面朝上但文字倒置”这类样本的判定不一致。解决一致性抽检不一致样本重新逐张过定规则后再决定归 one 还是 two。记录五验证集 mAP 很高现场接连误报。原因是验证集划分没按批次连续帧同时进了 train 和 val模型记的是场景背景而不是方向。解决train/val 按采集批次或日期切保证同一批料包的连续帧只在一个集合里。6. 验证与落地用混淆矩阵和特征图确认模型真的在分正反6.1 先看混淆矩阵区分漏检和错分ultralytics 训练完会在验证阶段自动输出混淆矩阵重点看 one→two 和 two→one 两个方向。一个方向高一个方向低说明方向特征学偏了大概率是标注或增强的问题两个方向都低但漏检多说明目标太小或运动模糊先处理图像质量再谈分类。把验证集的错误样本按“漏检”和“错分”分开人工复核。漏检是根本没框出来错分是框对了但类别反了。错分才是方向判别的问题漏检多半是采集和分辨率的问题两个方向的解决办法完全不同。6.2 特征图可视化别把模型当黑匣子用 hook 把 neck 层特征取出来看模型关注什么from ultralytics import YOLO import torch model YOLO(runs/detect/train/weights/best.pt) features {} def hook_fn(name): def fn(module, input, output): features[name] output.detach() return fn m model.model for name, layer in m.named_modules(): if name model.8: # 层号先打印结构确认 layer.register_forward_hook(hook_fn(neck)) _ m(torch.zeros(1, 3, 640, 640)) fm features[neck] # fm shape: [1, C, H, W]把前8个channel绘制成热力图对比把 one 和 two 各取一批图看热力图上印刷文字边缘是否激活。如果两类激活区域几乎一样模型学到的是轮廓而不是方向回训练阶段调 imgsz 或换更深网络如果 text 区域有明显激活差异说明方向特征真的学进去了。6.3 接到海康实时流的推理与后处理参数推理脚本直接吃海康 RTSP 流import cv2 from ultralytics import YOLO model YOLO(best.pt) cap cv2.VideoCapture(rtsp://admin:passwd192.168.1.64:554/Streaming/Channels/102) while True: ret, frame cap.read() if not ret: continue results model(frame, conf0.35, imgsz640)[0] for b in results.boxes: cls_id int(b.cls[0].item()) conf b.conf[0].item() x1, y1, x2, y2 map(int, b.xyxy[0].tolist()) if (x2 - x1) * (y2 - y1) 400: continue label results.names[cls_id] if label two and conf 0.35: # 触发剔除信号或通知翻转机构 passconf 阈值不要拍脑袋定 0.5。跑一批历史视频画出 two 类的置信度直方图看 one 和 two 的分布重叠情况重叠大就是数据问题重叠小阈值定在谷底附近。最小框面积过滤能挡住远处背景里的误检连续三帧都报 two 再动作能滤掉单帧抖动造成的误触发。我现在接手这类项目一定先把采集脚本、标注规范和验证集划分定好再开始训练。模型结构能抄数据规范抄不了这条习惯帮我省了很多次重训。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进