资讯动态

流水线包裹检测小样本数据集:VOC转YOLO与YOLOv8训练实践

发布时间:2026/9/28 13:11:18 来源:尧图企业网站定制
简介这份流水线包裹检测数据集同时提供Pascal VOC与YOLO两种标准格式面向工业物流包裹分拣、目标检测入门及迁移学习场景可直接用于YOLO系列、Faster R-CNN等常见检测网络的训练与评测。资源共437个文件包含145张jpg原图、145个xml标签和145个txt标签压缩包仅11.07MB轻量易下载。标注类别涵盖bag、bigbox、box、longbox四类共575个矩形框均由labelImg按统一规则绘制且不含分割路径配置数据只保留检测所需内容标注准确合理。样本中大小箱体与长条箱体尺度差异明显可用来评估模型对于多尺度目标的鲁棒性也适合作为数据增强、类别不均衡等实验的练习集。目前已有246人学习/下载便于快速上手VOC与YOLO格式差异并搭建小型检测流水线。1. 流水线包裹检测数据集145张双格式图像小样本项目的第一个样本145张图、4个类别、VOC和YOLO双格式一起打包进一个7z压缩包这是流水线包裹检测数据集最常见的交付形态。第一次拿到它的人第一反应往往是“145张够干嘛”但做过产线视觉的都知道真实流水线上能凑出145张高质量标注图已经很不容易机位要对、光照要调、异常样本要等还得一张张框选。这个数据集替你省掉了最费时的两个环节标注是现成的格式也从VOC转好了一份YOLO。接下来沿“解压核对、格式转换、训练调参、踩坑、提上限”这条线把这份小样本数据集用明白。适合刚接手产线视觉项目、手里只有少量标注数据的工程师也适合第一次跑YOLO但不想拿学术数据集练手的同学。新手按顺序走熟手可以直接跳到第5章和第6章看关键坑位。2. 拿到7z先别急着训练双格式目录结构、文件计数与三类核对动作标注格式再标准也不代表能直接喂给训练器。这一章先讲清楚VOC和YOLO两种格式在流水线场景里的分工再给出解压后必须做的三个核对动作。这三个动作是很多人跳过的也是后面训练翻车最集中的根源。2.1 VOC和YOLO两种格式在流水线场景里的分工VOC格式的目录结构通常是三件套JPEGImages放原图Annotations放同名xmlImageSets/Main下放划分文件。xml内部用bndbox节点记录xmin、ymin、xmax、ymax物体类别放在name节点里。这种格式最大的优势是人可读拿编辑器打开一个xml就能知道这张图里有几个包裹、每个框的像素坐标是多少。标注工具比如LabelImg默认导出的就是它所以VOC成了数据交付的通用语言。YOLO格式则完全面向训练器图片和txt标注分两个目录平铺每个txt对应一张图每行五个数字第一列是类别编号后四列是归一化后的中心点x、中心点y、宽、高。模型加载数据时不需要解析xml直接把txt映射到张量里训练循环更省事。缺点是不如VOC直观没打开看过的人光看数字感受不到标注长什么样。在包裹检测这种产线项目里两种格式经常并存。标注外包用VOC交付训练要用YOLO中间那道转换工序通常得自己写。这份数据集把两份都给了省掉了这一步但“省掉转换”不等于“不用核对”。目录结构、类别清单、标注质量这三关在拉训练命令之前都值得过一遍。2.2 解压后的第一个核对动作文件计数与类别分布Linux下解压7z常见做法是用p7zip7z x 流水线包裹检测数据集VOCYOLO格式145张4类别.7z系统没装7z的话Debian/Ubuntu系先装p7zip-fullCentOS/RHEL系装p7zip。Windows下建议直接装最新版7-Zip右键解压就行。命令行下包名带中文用Tab补全比手打安全。解压完先数文件心里有个底find . -type f | wc -l这个数字配合目录结构可以判断文件有没有缺。145张图对应145个jpgVOC格式有145个xmlYOLO格式有145个txt加上可能的classes.txt、data.yaml、train.txt、val.txt总数应该在440到600之间。如果差得远先怀疑解压中断或者文件丢失别急着往下走。接下来看类别。VOC格式没有统一类表但xml里每个目标的name就是类别名直接抓grep -rh name Annotations | sort | uniq -c输出里数字最大的类别是正样本最充足的数字最小的那个就是训练里最容易学不出来的。四类分布如果某一类只有十几个目标框后面训练就得单独处理类别不平衡。YOLO格式的类表通常在classes.txt里或者在配套的data.yaml里打开扫一眼确认是4个类别且顺序没有重复。这里有个容易误解的地方需要强调双格式不代表数据翻倍。同一张图的两套标注描述的是同一批目标145张图就是145个样本不是290个。数文件时发现数量对得上就对了别把“文件数很多”误当成“样本量很大”。2.3 第二个核对动作把YOLO标注画回图片上看一遍文件数对、类别对不代表标注能用。标注框画偏、画大、漏标这些靠数文件看不出来得把框画回图上眼见为实import cv2 from pathlib import Path img_root Path(images/train) label_root Path(labels/train) class_names Path(classes.txt).read_text().splitlines() for img_path in sorted(img_root.glob(*.jpg)): img cv2.imread(str(img_path)) h, w img.shape[:2] label_path label_root / (img_path.stem .txt) if not label_path.exists(): print(f缺少标注: {img_path.name}) continue for line in label_path.read_text().strip().splitlines(): cid, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[int(cid)], (x1, max(0, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(fcheck_{img_path.name}, img)这段脚本遍历YOLO格式的txt把归一化坐标换算回像素坐标画框并写上类别名后输出。换算公式是像素坐标等于归一化坐标乘以图像的宽或高框的左上角是中心点减去半个宽高右下角是中心点加上半个宽高。跑完把所有check_开头的图按顺序翻一遍重点看三类问题框有没有明显包住传送带或流水线背景、小目标框是不是整体偏大、同一个包裹有没有被重复画框。145张图全部翻一遍大约十几分钟换来的是训练阶段不返工这笔时间花得值。第三个核对动作是看压缩包里有没有带ImageSets/Main目录下的train.txt和val.txt。如果带了说明上一手处理人已经做过划分优先用它如果没有就按第4章的脚本自己划分。划分顺序放在后面讲是因为先确认标注质量再去划分能避免把坏标注带进训练集。3. 从VOC转YOLO的落地脚本归一化坐标计算与四个边界坑这份数据集已经把YOLO格式备好了但实际工作中你手上的其他数据大概率还是裸VOC。转格式这件事迟早躲不过这一章把脚本和四个边界坑位钉死。坐标归一化看着简单真正跑起来翻车的都是细节。3.1 一个能直接改改就用的VOC转YOLO脚本import xml.etree.ElementTree as ET from pathlib import Path XML_DIR Path(Annotations) OUT_DIR Path(labels) # 类别表顺序一旦确定就不要再改训练时data.yaml的names必须和它保持一致 CLASSES [package, label, tape, damaged] def convert_one(xml_path: Path) - str: tree ET.parse(xml_path) root tree.getroot() w int(root.findtext(size/width)) h int(root.findtext(size/height)) if w 0 or h 0: raise ValueError(f{xml_path.name}: 图像尺寸为0xml可能损坏) lines [] for obj in root.iter(object): name obj.findtext(name) if name not in CLASSES: print(f{xml_path.name}: 类别 {name} 不在CLASSES里跳过) continue cid CLASSES.index(name) b obj.find(bndbox) x1 float(b.findtext(xmin)); y1 float(b.findtext(ymin)) x2 float(b.findtext(xmax)); y2 float(b.findtext(ymax)) # 坐标越界统一裁剪到图像有效范围内 x1 min(max(x1, 0), w - 1) y1 min(max(y1, 0), h - 1) x2 min(max(x2, 0), w - 1) y2 min(max(y2, 0), h - 1) if x2 x1 or y2 y1: print(f{xml_path.name}: {name} 越界后宽高非正跳过这个框) continue cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cid} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) return \n.join(lines) def main(): OUT_DIR.mkdir(exist_okTrue) for xml_path in sorted(XML_DIR.glob(*.xml)): text convert_one(xml_path) (OUT_DIR / (xml_path.stem .txt)).write_text(text) if __name__ __main__: main()这个脚本把VOC的xmin、ymin、xmax、ymax转成YOLO的cx、cy、width、height四个坐标全部除以图像宽高做归一化。CLASSES列表的顺序决定txt里的类别编号这个顺序必须和后续data.yaml的names完全一致一个错位整个训练都是白跑。两个设计细节值得说。一是用findtext直接拿size里的width和height比先find(size)再find(width)少写两行也不容易漏节点。二是转换时没动xml文件本身输出的txt是新增的万一转错了xml还在随时能重新生成这就是后悔药。3.2 转换时的四个边界坑越界、空标注、类别名、坏尺寸坑一、坐标越界。现象是某张图的xml里xmax写的是680但图像实际宽度只有640。原因多半是标注工具在图像缩放或裁剪后没有同步更新坐标手标时鼠标稍微拖出边界也会这样。解决脚本里把坐标clamp到0到w-1和0到h-1之间clamp后如果x2小于等于x1或y2小于等于y1说明整个框都在画面外直接丢掉这个框并打日志不要硬写进txt。坑二、空标注xml。现象是Annotations下有一个xml打开只有size没有object转换后txt是0字节。原因可能是采集时拍到空画面或者被标注的人漏标。解决0字节txt可以保留YOLO训练会跳过空标注但最好单独列一份空xml清单回头人工确认是漏标还是真没有包裹。漏标的话补标比训练时莫名少一张图好排查。坑三、类别名大小写不一。现象是CLASSES列表里写的是Package转换时一半xml里的name是package全部被当成未知类别跳过。原因是标注时手滑或不同标注员习惯不一。解决转换前先跑一遍2.2里的grep命令把全部name统计出来把大小写、全角半角统一成一份清单再写进CLASSES。类别名不一致是静默的它不报错只是悄悄把你的正样本丢掉。坑四、xml里的size不可信。现象是转换完训练突然报图像读取错误或尺寸异常。原因是xml里的width和height与真实图片尺寸不一致通常是xml被编辑过或图片被替换过。解决不要用xml里的size用cv2或PIL读一次图片真实尺寸以真实尺寸为准做归一化。145张图读一遍耗时不到一秒值得写进脚本里。4. 用145张图训练YOLOv8数据划分、必调参数与最小训练命令格式和核对都过了开始训练。这一章给出一个能直接复制运行的完整流程先按9比1划分数据集再写data.yaml最后用yolov8n预训练权重拉起训练。145张图是小样本参数设置和大数据集不一样照搬大样本方案容易翻车。4.1 数据划分脚本与data.yaml路径、类别索引、目录名三者对齐训练前先划分数据集。划分的原则很简单按图片列表复制而不是随手把文件分成两堆。同一张图的jpg和txt必须一起去train或一起去val否则训练集缺图、验证集指标也算不对。import random, shutil from pathlib import Path random.seed(42) src_images Path(images) src_labels Path(labels) train_img_dir Path(images/train) val_img_dir Path(images/val) train_lbl_dir Path(labels/train) val_lbl_dir Path(labels/val) for d in (train_img_dir, val_img_dir, train_lbl_dir, val_lbl_dir): d.mkdir(parentsTrue, exist_okTrue) imgs sorted(src_images.glob(*)) imgs [p for p in imgs if p.suffix.lower() in (.jpg, .jpeg, .png)] random.shuffle(imgs) split int(len(imgs) * 0.9) for img in imgs[:split]: shutil.copy2(img, train_img_dir / img.name) shutil.copy2(src_labels / (img.stem .txt), train_lbl_dir / (img.stem .txt)) for img in imgs[split:]: shutil.copy2(img, val_img_dir / img.name) shutil.copy2(src_labels / (img.stem .txt), val_lbl_dir / (img.stem .txt))seed固定为42保证每次划分结果一致方便复现问题。按stem拼接txt文件名确保jpg和txt一一对应。只复制不移动原始目录保持不动这是后悔药后面想调整划分比例随时能重来。然后写data.yamlpath: /home/user/package_dataset train: images/train val: images/val nc: 4 names: 0: package 1: label 2: tape 3: damagedpath必须是绝对路径YOLO会拿它拼接train和val的相对路径路径写错会直接报错或者把空目录当数据集。names的索引顺序与txt里的class_id严格对应。校验方法随机打开一个txt看第一行第一个数字假设是0到classes.txt里找第0个名字再到yaml的names里找第0个名字三个一致就对了。4.2 小样本训练的必调参数imgsz、batch、epochs、patienceyolo train taskdetect \ modelyolov8n.pt \ datapackage.yaml \ epochs300 \ imgsz640 \ batch16 \ patience50 \ device0modelyolov8n.pt这个参数是小样本训练的关键。预训练权重一定带上145张图从零初始化训练几乎必炸yolov8n是nano版本对这份数据量最合适s和m的参数量上去了但小样本下收益极小只会增加过拟合风险。预训练权重第一次运行会自动下载也支持自己指定路径。epochs300配合patience50。小数据集通常在80到120轮基本收敛patience让训练在50轮没有进步时自动停不会傻等300轮跑完也不会正好卡在过拟合点上。如果训练到第40轮mAP还在涨说明这个数据集还有余量不用手动改。batch16。145张图分训练集约130张batch16意味着每个epoch只有8步更新batch再往上加BN层的统计量会非常抖。显存小用8显存大也建议保持16换来的不是精度而是稳定。小样本场景下batch和learning rate是配套的调了batch就要留意lr的变化。imgsz640。如果包裹在画面里普遍偏小试试960代价是显存和时间翻倍如果目标本身就大640够用。这个参数最后要靠验证集mAP说话不要人云亦云。4.3 训练起来后盯两个指标而不是盯loss训练开始后命令行和runs/detect/train目录下的日志会实时刷新。建议不看每步的train loss盯两个东西每轮结束的验证mAP50以及train曲线和val曲线的分离程度。mAP50在第5到第10轮还是0基本可以判定数据或类别编号有问题别等300轮跑完直接CtrlC回去查。train loss一路降、val mAP先升后跌是典型过拟合靠patience早停能挡一大半。训练完验证用这个命令yolo val taskdetect \ modelruns/detect/train/weights/best.pt \ datapackage.yaml建议顺手把last.pt也验证一遍。小样本训练的best.pt可能是某轮运气好跳出来的last.pt有时泛化反而更好两个都看一眼再决定部署哪个。验证输出里会带mAP50、mAP50-95、precision、recall四个核心数字以及一张混淆矩阵图。5. 避坑小样本目标检测最常见的5个翻车现场这一章的每条记录都来自真实项目里的踩坑按“现象、原因、解决”三段写。遇到类似问题直接对号入座能省下来回翻日志的时间。5.1 loss下降但mAP一直为0现象训练日志里box_loss从2.1降到0.6分类loss也在降可每轮结束的mAP50始终是0.0。模型看起来在学验证结果却完全没用。原因最常见的是类别编号错位。txt里class_id为0对应packagedata.yaml里0号却写成了label模型学到的类别和验证时对不上mAP自然算不出来。另一个常见原因是验证集目录为空yolo val找不到图片指标全是0。解决先看验证集目录下有没有jpg文件再随机打开一个训练txt打印每行第一个数字和data.yaml的names逐一对照。改完后重跑训练通常10轮内mAP50就会跳起来。这个坑排在第一位因为它的现象最隐蔽跑起来最像正常训练。5.2 混淆矩阵每行加起来不到100%现象val结束打印的混淆矩阵某一行四个格子的百分比加起来不到100%甚至只到六七十。原因YOLO的混淆矩阵里有一个隐性的background类代表“被正确判断为背景”的区域。每一行的百分比是把该类真实样本分到所有类别的比例包括background那一列所以行和不是100才是正常的。第一次看到的人基本都会以为自己算错了。解决不用管行和直接看对角线。对角线低的类别说明它经常被认成背景或者别的类去第2章的可视化脚本里翻这一类样本看是不是标注框太松、目标太小再去补样本。混淆矩阵不是给你看总数的是给你看哪两类互相混淆的。5.3 验证集mAP很高但流水线上不能用现象离线验证mAP0.5超过0.9模型部署到产线摄像头逆光、遮挡、新角度的漏检一大堆。现场测试和离线指标完全两个世界。原因145张图过拟合了训练集的拍摄风格。训练时的mosaic、mixup这类增强在学术数据集上是涨点利器在小样本产线数据下反而强化了对特定光照和背景的记忆。模型记住了训练图的样子而不是包裹的通用特征。解决训练参数里把增强强度调低典型做法是degrees5、scale0.2、hsv_v0.3mosaic开0.5。更靠谱的是留出10到20张真实现场图不参与训练单独当测试集。把现场图的mAP当作唯一上线指标离线指标只做参考。这个坑属于前期不容易发现、部署时才爆的类型预防比补救更划算。5.4 解压7z报“密码正确却一直报错”现象Windows下用第三方解压工具解压密码明明是对的提示密码错误或解到一半中断换一个工具又能解出来。原因压缩包文件名用了UTF-8编码老版工具按系统本地编码解析中文文件名文件名乱码导致完整性校验失败另外如果压缩时勾选了“加密文件列表”密码正确但列表解不出来表现也是反复报错。这两种情况都和文件内容无关纯粹是工具链的问题。解决Linux下用7z x直接解压Windows装最新版7-Zip再解。解压前先跑7z l -slt 文件名.7z看文件列表能列出中文文件名说明工具链正常。解压成功之后原始7z包留一份放旁边别删后续任何文件损坏都靠它兜底。如果密码真的忘了只能靠字典硬试那是另一个话题了。5.5 增强让训练集翻倍但泛化没提升现象开了翻转、旋转、mosaic之后训练集从145张变成两千多张训练loss很漂亮现场测试依旧漏检。原因增强是对同一批像素做几何和颜色变换不产生新的真实信息。它只能延缓过拟合不能替代真实样本。145张图的增强后样本一轮控制在300到400张就够再多只是增加训练耗时信息量并没有变。解决把省下来的时间用于补拍新光照、新角度的小批次数据哪怕一次只补30张对泛化的提升也比无限增强大。记录每次补拍后的现场测试mAP你会发现拐点不在增强强度上在新的真实样本上。增强参数本身是玄学真有那功夫多拍点图比什么都管用。6. 冻结骨干微调把145张图的训练上限再抬高一截145张图全量微调yolov8n通常会在50轮左右开始过拟合。一个有效做法是冻结骨干网络只训练检测头。YOLOv8的train命令支持freeze参数freeze10表示冻结前10层正好是nano版本的整个backbone。yolo train taskdetect \ modelyolov8n.pt \ datapackage.yaml \ epochs150 \ batch16 \ freeze10 \ patience30这样做的理由是预训练权重里的特征是通用视觉特征比如边缘、纹理、形状这些在包裹数据集上依然有效。只训练检测头让模型把“怎么框出包裹”这件事学好就够了。参数空间变小145张图足够撑住不严重过拟合mAP的抖动也明显小于全量微调。训练完不要只报一个mAP数字。跑一次val打开混淆矩阵图对着矩阵确定两件事哪两类互相混淆最严重哪一类对角线最低。如果流水线场景里“胶带”和“破损”反复串说明这两类外观太近要么补拍更能区分它们的样本要么合并成一类。类别合并是小样本下的常用操作四类并成三类只要业务允许检测稳定性的提升往往立竿见影。我现在的习惯是拿到任何小样本数据集第一件事不是拉训练命令而是先花半小时做可视化核对和类别编号检查。不要急着把训练拉起来再去看结果数据侧的一点不确定都会在训练结果里放大十倍。这个顺序帮我挡掉过很多次白跑的训练希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑