
简介面向行李箱缺陷检测任务的目标检测数据集包含650张清晰日常场景图片标注了damaged与good_condition两类共936个矩形框适用于训练YOLO、Faster R-CNN等模型也可作为工业质检方向的入门练习数据。压缩包共1953个文件涵盖650张jpg原图、650个xml标注文件与650个txt标签文件同时提供VOC与YOLO两种格式无需自行转换即可直接接入常用训练流程整体约25.16MB体积轻量便于下载和快速迭代。已有34人浏览学习。数据已做准确合理标注并对同一图片提供对应格式文件方便使用者对照查看标注结果压缩包内按JPEGImages、Annotations、labels三个文件夹分类存放目录结构直观适合需要一批带标签数据来验证检测算法或开展课程设计的学生、研究人员。1. 行李箱缺陷检测数据集650 张、2 类、YOLO/VOC 双格式拿到手先别急着训这份名为“行李箱缺陷检测数据集”的压缩包一共 650 张图片、两类缺陷、同时带了 YOLO 和 VOC 两种标注格式。第一次遇到这种包的人容易犯两个错要么觉得 650 张太少直接放弃要么解压完不校验就丢进 YOLO 训练。我拿到这类少样本工业缺陷数据第一步永远是先算账训练验证切完之后还剩多少两个类别的框数量差几倍坐标格式能不能交叉对上。这套流程跑完你才能判断这份数据集到底是直接能用、还是需要清洗。它适合刚接手行李箱外观质检的算法工程师也适合想用真实缺陷数据练手 YOLO 训练流程的入门者。后面讲的每一步都是按我自己从解压到验收、训练到排障的完整路径展开的。2. 理解数据包双格式标注、少样本规模与压缩包内部结构2.1 650 张不是数据量少而是“少样本”设计先认清边界再定策略一份几百张的缺陷检测数据集在工业视觉里是常态不是凑数。行李箱表面有划痕、凹陷、裂纹、碰伤采集时要覆盖不同箱体颜色、正面侧面光线、拉链区域能稳定整理出 650 张带框标注背后是不少人工成本。别拿它跟 ImageNet1k、COCO、DOTA、CrowdHuman 这种几十万上百万张的通用数据集比规模那不是一个赛道。和轴承缺陷检测、机油盖缺陷检测这些任务类似行李箱缺陷的难点在于同一个缺陷在不同箱面材质下形态差异大少量样本必须配合预训练权重和增强策略才能压住过拟合。我的常见做法是把 650 张按 85% 和 15% 切大约 550 张训练、100 张验证。训练类别只有 2 个batch size 设在 8 到 16epoch 设 100 左右观察 loss 是否收敛不需要一上来就堆 300 epoch。这里还有一个容易被忽略的假设2 类不代表两个类别各占一半。多数行李箱数据集里划痕类占大头、凹陷类占小头比例差到 4 倍以上很常见。训练前先统计类别分布比训完再猜问题定位要快得多。如果传统方案里你是用 Halcon、LabVIEW 或小波特征去设计规则检测箱面换个颜色就要重新调参YOLO 的优势是端到端但代价是你得把“类别不平衡”这笔账自己管好。2.2 YOLO 格式与 VOC 格式到底差在哪为什么同一份数据要发两遍很多入门者拿到双格式会懵既然有两个标注文件夹是不是内容不一样实际上两者描述的是同一批框只是坐标系和载体不同。VOC 格式每张图对应一个 XML 文件框坐标是像素绝对值形如xmin, ymin, xmax, ymaxYOLO 格式每张图对应一个 TXT 文件每行是class, x_center, y_center, width, height后四个值都用 0 到 1 归一化。项目VOC XMLYOLO TXT单图标注载体一个 .xml 文件一个 .txt 文件坐标含义xmin/ymin/xmax/ymax 像素坐标class xc/yc/w/h 归一化坐标常见消费方labelImg、mmdetection、Faster R-CNNUltralytics YOLO、多数部署 SDK人工查看成本可以直接打开看结构需要换算回像素坐标供应商给双格式通常是标注工具导出了 VOC转训练格式时顺手另存了一份 YOLO TXT。这不仅方便两种框架的用户还给了你做质量校验的机会用图片宽高把 YOLO 归一化坐标反推回像素坐标如果和 XML 里的值对不上说明两份标注来自不同版本训练前必须锁定其中一份重转。换算公式很简单设图片宽 W、高 H则 xc (xmin xmax) / (2W)w (xmax - xmin) / W。如果你在搜索时误把 CCPD、HRSC2016、PHM2012 这类数据集当成同类资源要注意它们的坐标体系完全不同CCPD 把坐标信息编码在文件名里HRSC2016 用的是旋转框或多边形PHM2012 则是退化趋势数据不是检测框。这些都不能直接套 YOLO/VOC 双格式的处理脚本。2.3 先从压缩包目录树判断排版而不是直接解压开训拿到 zip 先不急着unzip先用unzip -l看一眼内部结构这一步只读取压缩包目录信息不解压速度很快。它能帮你判断三件事图片和标注是平铺在根目录还是按 images/、labels/ 分了子目录是否存在__MACOSX/这类打包残留有没有classes.txt、README之类的辅助文件。# 列出 zip 内部前 30 条路径确认目录层级 unzip -l luggage_defect_650.zip | head -30 # 如果目录项太长直接把所有文件名列到文件里再 grep unzip -l luggage_defect_650.zip | grep -E \.(jpg|png|jpeg|xml|txt)$ | head -40head -30是只看开头部分避免刷屏grep后面的扩展名过滤是为了只关注图片和标注文件把目录项和说明文件滤掉。看到路径里出现annotations/voc/和labels/yolo/这类分层时后续清洗脚本就要按这套结构去写如果图片和标注混在同一个目录就得先分类再加后缀这一步决定后面所有代码的路径。还有一点经验很多从网盘转存的 zip文件名字段是 GBK 编码在 Linux 下解压会出现乱码这属于常见翻车点第 5 节会单独讲。3. 把 zip 变成可训练数据解压、类别统计与双格式坐标交叉验证3.1 先做完整性测试再解压到独立目录解压数据集最忌讳的就是缺文件。650 张图缺了几张训练时 YOLO 会报图片路径不存在如果缺的是验证集里的关键样本mAP 对比就失真了。所以解压前先做一次标准完整性校验。# 1. -t 是 test 模式只校验 zip 的 CRC不释放文件 unzip -t luggage_defect_650.zip | tail -20 # 2. 通过校验后解压到一个独立项目目录 mkdir -p ~/datasets/luggage650 unzip -q luggage_defect_650.zip -d ~/datasets/luggage650 # 3. 统计三种关键文件的数量核对是否和标题里的 650 张对得上 find ~/datasets/luggage650 -type f \( -name *.jpg -o -name *.png -o -name *.jpeg \) | wc -l find ~/datasets/luggage650 -type f -name *.xml | wc -l find ~/datasets/luggage650 -type f -name *.txt | wc -l这里tail -20只取末尾 20 行因为 zip 文件较多时unzip -t输出很长报错信息通常在结尾。解压路径建议放在~/datasets/luggage650把数据集和工程代码目录分开避免 labels 目录被误删。第三步的图片数量如果明显少于 650说明包本身不完整先别继续往下做如果图片是 650、XML 也是 650、TXT 只有 600说明有 50 张图只有 VOC 标注没有 YOLO 标注训练前要查清楚。3.2 统计类别和框数确认“2 类”的真实分布标题写“2 类”具体类别名和数量分布要到解压后才知道。我一般会先跑一段简单的统计脚本把每类框数量、带框图片数、空标签图片数打出来。这个数字直接影响后边 data.yaml 的类别顺序和要不要做样本均衡。# count_labels.py # 统计 YOLO txt 中每个类别的框数量、带框图片数和空标签数 from collections import Counter from pathlib import Path label_dir Path(~/datasets/luggage650/labels).expanduser() # 按实际解压路径改 cls_counter Counter() img_with_box 0 empty_label 0 for txt_path in sorted(label_dir.glob(*.txt)): if txt_path.stem classes: # 有些包会带 classes.txt 汇总文件跳过 continue text txt_path.read_text(encodingutf-8, errorsignore) lines [ln for ln in text.splitlines() if ln.strip()] if len(lines) 0: empty_label 1 # 空 txt 说明该图无缺陷属于背景样本 continue img_with_box 1 for ln in lines: parts ln.split() if len(parts) 5: # 标准格式 class xc yc w h cls_counter[int(parts[0])] 1 print(f带框图片数: {img_with_box}) print(f空标签图片数: {empty_label}) print(f各类别框数: {dict(sorted(cls_counter.items()))})这里errorsignore是为了防止个别标签文件里出现非 UTF-8 字符导致整个脚本中断len(parts) 5是防御性写法某一行如果多了坐标点或少了类别就先跳过而不是让程序崩掉。输出的类别编号很重要比如结果是“{0: 421, 1: 187}”那说明类别 0 是多数类、类别 1 约是它的一半。记下这个映射写 data.yaml 的 names 时按同样顺序填写。空标签图片数是关键指标如果 650 张图里有 100 张空标签说明包的负样本比例约 15%训练时要保留这些背景图不要为了省事把它们删掉。3.3 双格式交叉验证VOC 坐标和 YOLO 坐标对得上吗这是我最建议在训练前做的一步。双格式数据包最常见的问题是两个格式不是同一版本生成坐标差几个像素甚至十几像素。如果直接信任某一侧标注训练框位置偏差会让 box_loss 在一个错误的位置上反复震荡。交叉校验脚本的思路是读 VOC XML 里的像素框读同名 YOLO txt 里的归一化框用图片真实宽高反推像素然后累加四个顶点坐标的差值。# verify_dual_format.py # 抽查若干张图把 YOLO 归一化坐标反推回像素与 VOC XML 对比 import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image root Path(~/datasets/luggage650).expanduser() img_dir root / images voc_dir root / annotations / voc yolo_dir root / annotations / yolo xml_list sorted(voc_dir.glob(*.xml))[:5] # 先抽查前 5 张 for xml_file in xml_list: img_file None for ext in (*.jpg, *.jpeg, *.png, *.bmp): cands list(img_dir.glob(f{xml_file.stem}{ext})) if cands: img_file cands[0] break if img_file is None: print(f{xml_file.stem}: 缺少对应图片) continue width, height Image.open(img_file).size yolo_txt yolo_dir / f{xml_file.stem}.txt xml_boxes [] for obj in ET.parse(xml_file).getroot().findall(object): bb obj.find(bndbox) xml_boxes.append([float(bb.findtext(k)) for k in (xmin, ymin, xmax, ymax)]) if not yolo_txt.exists(): print(f{xml_file.stem}: yolo txt 缺失) continue yolo_lines yolo_txt.read_text().strip().splitlines() if len(yolo_lines) ! len(xml_boxes): print(f{xml_file.stem}: 框数量不一致 xml{len(xml_boxes)} yolo{len(yolo_lines)}) continue for idx, (xml_box, yolo_line) in enumerate(zip(xml_boxes, yolo_lines)): _, xc, yc, bw, bh map(float, yolo_line.split()) px_min (xc - bw / 2) * width py_min (yc - bh / 2) * height px_max (xc bw / 2) * width py_max (yc bh / 2) * height diff (abs(px_min - xml_box[0]) abs(py_min - xml_box[1]) abs(px_max - xml_box[2]) abs(py_max - xml_box[3])) print(f{xml_file.stem} 框{idx}: 坐标差{diff:.1f}px)[:5]是只取前 5 个 XML 做快速抽查时间允许的话可以改成全部文件坐标差累计值是四个顶点差的加总如果小于 2px 说明两份标注一致如果出现几十像素的偏差说明 txt 和 xml 不是同一版本。遇到不一致我的原则是以 VOC XML 为基准因为 XML 通常是标注工具直接导出、人工可读可改YOLO txt 大概率是后来脚本转出来的。下一步从 XML 统一重新生成 YOLO txt避免训练时两个目录混着用。4. 把 650 张送进 YOLOv8data.yaml、分组划分与训练参数选择4.1 类别顺序决定一切data.yaml 的两处必改项YOLO 训练启动后最先加载的就是数据配置文件。少样本数据集的 data.yaml 比大数据集更容易写错因为类别少、命名随意类别名和 txt 下标一旦错位训练照样跑但推理结果全是错的。下面是我常用的一份模板。# luggage650.yaml —— 放在数据集根目录或工程 configs/ 下均可 path: /home/you/datasets/luggage650 # 改成你自己的数据集绝对路径 train: images/train val: images/val nc: 2 names: 0: scratch 1: dent两个最容易错的地方第一names的下标必须和 YOLO txt 第一列的类别编号严格一致。前面统计脚本输出“类别 0 有 421 个框”那 0 就对应实际缺陷名中最多的那一类如果你把names的 0 和 1 写反模型没有报错但推理时会把划痕全部标成凹陷。第二path建议写绝对路径不要写相对路径因为 YOLO 训练时工作目录经常切换train/val 是相对path的子目录。labels 目录不需要在这个文件里指定Ultralytics 会自动在 images 同级目录找同名 labels 目录比如images/train对应labels/train。4.2 基于同源分组的划分脚本不要随机打散再切650 张图通常不是一个物体只拍一张而是同一个箱子从不同角度拍多张。如果按文件名随机划分同一个行李箱的照片会同时出现在训练集和验证集验证 mAP 虚高到了产线上换一只新箱子立刻掉点。解决方法是按同源标识分组文件名类似bag001_01.jpg、bag001_02.jpg的视为同一组划分时整组进入训练侧或验证侧。# split_trainval.py # 按文件名前段分组划分避免同一箱子同时出现在 train 和 val import random, shutil from pathlib import Path random.seed(2024) root Path(/home/you/datasets/luggage650) img_dir root / images label_dir root / labels val_ratio 0.15 # 650 张里约 100 张做验证 imgs sorted(img_dir.glob(*.jpg)) sorted(img_dir.glob(*.png)) groups {} for img in imgs: key img.name.rsplit(_, 1)[0] # 下划线前部分是同一只箱子的标识 groups.setdefault(key, []).append(img) group_keys list(groups.keys()) random.shuffle(group_keys) val_n max(1, round(len(group_keys) * val_ratio)) val_keys set(group_keys[:val_n]) for split in (train, val): (root / images / split).mkdir(parentsTrue, exist_okTrue) (root / labels / split).mkdir(parentsTrue, exist_okTrue) for key, imgs in groups.items(): target val if key in val_keys else train for img in imgs: label label_dir / f{img.stem}.txt shutil.copy2(img, root / images / target / img.name) if label.exists(): shutil.copy2(label, root / labels / target / label.name)这个脚本的rsplit(_, 1)[0]是取最后一个下划线之前的部分作为分组键如果你的文件名里没有下划线它会把整个文件名作为键退化成单张随机划分。遇到这种情况只能靠目录信息手动分组。random.seed(2024)是固定随机种子保证每次划分结果一致方便复现实验。copy2保留原文件的修改时间属性对后续做数据集版本管理有帮助。4.3 一条训练命令与 YOLO 损失函数的读法数据准备好之后用 YOLOv8 训练是第一轮实验最快的方式。650 张图不需要大模型yolov8n足够因为少样本场景下模型越大越容易过拟合。cd ~/projects/luggage_check yolo detect train \ modelyolov8n.pt \ dataluggage650.yaml \ epochs100 \ imgsz640 \ batch16 \ patience20 \ seed0 \ cacheTruemodelyolov8n.pt是预训练权重不要从yolov8n.yaml开始空训练否则 550 张训练图根本学不出通用特征patience20表示验证损失连续 20 个 epoch 不下降就提前停止少样本任务过拟合来得早这个参数能帮你节省时间imgsz640是默认分辨率如果行李箱占画面比例大可以下探到 512 提升速度。训练开始后看 loss 曲线不要只盯总 loss。YOLO 的损失函数由三部分构成box_loss衡量预测框和标注框的位置回归误差cls_loss衡量类别分类误差dfl_loss是分布焦点损失负责框边界的精细度。这三条曲线训练侧应该在 20 到 30 个 epoch 内快速下降然后变平如果验证侧先降后升说明过拟合已经开始了这时候不是加 epoch而是回头检查增强或者权重衰减。5. 避坑650 张行李箱数据训练中最常见的五个踩坑点5.1 同箱多角度图被切到两边训练指标虚高产线全漏现象验证集 mAP50 到了 0.9 以上部署后用新行李箱测试漏检率完全不可接受。原因划分脚本没有按同源分组同一个箱子不同角度的照片一部分进了训练、一部分进了验证模型等于靠“背图”获得了高分。解决按第 4.2 节的思路改成同名分组划分验证指标会老实掉下来。掉下来的数值才是真实水平。如果文件名没有分组规律只能人工建 group 表或者按拍摄时间间隔聚类无论如何不能让同一个物理对象横跨两个集合。5.2 双格式标注不一致坐标差几像素训练却不报错现象训练 loss 正常下降推理画框时框总是往右上偏一点或者验证 loss 在低位反复抖动。原因VOC XML 是后来人工修正过的版本YOLO txt 没同步更新两份标注偏移了几个像素。解决训练前跑第 3.3 节的交叉校验发现偏差后以 XML 为基准重新生成 YOLO txt可以用下面的脚本批量重建。# rebuild_yolo_from_voc.py # 以 VOC XML 为准重新生成 YOLO txt避免双格式版本不一致 import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image voc_dir Path(~/datasets/luggage650/annotations/voc).expanduser() img_dir Path(~/datasets/luggage650/images).expanduser() yolo_out Path(~/datasets/luggage650/labels_fixed).expanduser() yolo_out.mkdir(exist_okTrue) for xml_file in voc_dir.glob(*.xml): img_file next(img_dir.glob(f{xml_file.stem}.*), None) if img_file is None: continue w, h Image.open(img_file).size lines [] for obj in ET.parse(xml_file).getroot().findall(object): # cls_id 这里需要按真实的 name 到 id 映射填或先用 0 占位 cls_id 0 bb obj.find(bndbox) xmin, ymin, xmax, ymax [float(bb.findtext(k)) for k in (xmin, ymin, xmax, ymax)] xc (xmin xmax) / 2 / w yc (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) (yolo_out / f{xml_file.stem}.txt).write_text(\n.join(lines), encodingutf-8)注意这里cls_id 0只是占位实际使用时必须根据 XML 里的类别名映射到编号比如划痕是 0、凹陷是 1。还有一个常见误用VOC 的xmin有的标注工具从 1 开始按公式转成归一化坐标时不要额外减 1减了会引入半像素系统偏移。5.3 解压出来文件名乱码还多出 __MACOSX 目录现象Windows 下用自带解压工具解压图片名和 xml 名变成“锟斤拷”或者“______”YOLO 训练时图片和标签对不上。原因zip 里的文件名用的是 GBK 编码Windows 自带工具按本地编码解析时出现错位反之 Linux 的 unzip 按 UTF-8 解析也会认出乱码。解决用 Python 的 zipfile 读取文件头判断文件名编码后修正。zip 文件规范里flag_bits 0x800表示文件名已经用 UTF-8 编码没这个标志的按 GBK 处理。# 演示文件名编码判断不处理具体解压动作 import zipfile with zipfile.ZipFile(luggage_defect_650.zip) as zf: for info in zf.infolist(): raw info.filename if info.flag_bits 0x800: # zip 已标记 UTF-8 编码 fixed raw else: fixed raw.encode(cp437).decode(gbk, errorsignore) print(fixed)解压完成后如果发现__MACOSX/隐藏目录做工控项目时建议直接清理掉这类目录里往往是 macOS 的元数据文件不仅占空间还会干扰文件数量统计。如果你用 7-Zip 解压右键菜单里通常有“文件名编码修正”的选项比写脚本更省事。5.4 两类缺陷不均衡划痕多凹陷少先调权重还是先补数据现象训练侧 loss 下降正常但验证时几乎只检出划痕凹陷类总 mAP 不到 0.2。原因类别框数量差距超过 4 倍时默认的 cls_loss 被多数类主导少类样本梯度被淹没。解决第一步先看类别比例如果少类不到多类的 30%不要直接调损失函数权重。我的偏好是先在数据层面过采样把少类标注的 txt 复制几份放回训练集本质上就是让每个 epoch 看到更多次少类样本。Ultralytics YOLOv8 没有直接在 yaml 里暴露 per-class loss 权重所以数据过采样是更稳的可复现方案。第二步检查增强配置里 mosaic 的开关小缺陷在 mosaic 拼图时容易被切成碎片可以先关掉 mosaic 或少类样本占比极低时单独加一块。这一步是轴承缺陷检测、机油盖缺陷检测这些同类工业小样本任务里都绕不过的。5.5 训练日志一切正常推理把两个类别互相认错现象loss 收敛、mAP 数值也不低但部署到现场后把划痕输出成 dent或者反过来。原因data.yaml 的 names 顺序和 txt 类别编号错位。很多人从别处复制 yaml只改了路径和类别名没注意到原来的包类别顺序是反的。解决训练前把统计脚本的结果打印出来比如“0 类 421 框、1 类 187 框”再打开 yaml 比对 names 是否按同一个顺序排列。怀疑顺序错的时候只需要改 yaml 里的 names 顺序重新训练即可不需要改标注文件。6. 少样本模型怎么“验货”盲测组、PR 曲线与增强参数微调6.1 先用 20 张同环境照片做盲测再上报指标验证集指标只能说明模型在已见数据附近的表现少样本任务尤其容易被验证集“骗”。我习惯在模型训练完后用手机或产线相机在相同光照下补拍 20 到 30 张该场景的新图单独放一个blind/目录用训练好的权重跑一遍yolo detect predict modelruns/detect/train/weights/best.pt \ sourceblind/ imgsz640 save_txtTrue然后把识别结果翻出来数漏检。对行李箱缺陷检测来说漏一个缺陷比误报一个背景严重得多所以盲测时我只看 recall不看 precision。如果 20 张里有 3 个缺陷框没画出来先别调阈值而是把这几张图并回训练数据里补一轮这比反复调 confidence 阈值更有效。6.2 增强参数在小样本场景下的调整方向650 张规模下增强是一把双刃剑。Mosaic 能缓解数据量不足但小缺陷区域在拼接后容易变形HSV 扰动对箱面颜色有帮助但幅度太大可能让划痕纹理失真。我的实际参数是mosaic 降到 0.5hsv_h 保持 0.015 以下scale 保持默认但关闭 translate。同时可以尝试对缺陷区域做复制粘贴增强把标注框内的缺陷小块裁出来随机贴到正常箱面图上这是在类别不均衡时提升少类 recall 的常用手段比单纯调 loss 权重更直接。6.3 把每一次实验的 PR 曲线留档别只看一个 mAP我现在的习惯是每个训练实验单独用projectrun_luggage命名跑完先把 results.csv 里的 PR 曲线截图翻一遍。少样本模型的 mAP 波动大单看某次实验胜出可能只是随机种子好把三次实验的 PR 曲线放在一起对比某一类别的召回率是否稳定一眼就能看出来。踩过几次“验证集调参调得很漂亮、盲测翻车”的亏之后我现在把盲测组作为验收模型的第一道关卡过不了盲测组的权重宁可重训也不推上线。这个思路不一定适合所有项目但对这类少样本缺陷检测方向能把你的时间和算力花在真正值得补的数据上。希望帮到你。本文还有配套的精品资源点击获取