简介:数据集围绕行李箱外观缺陷检测任务构建,适合目标检测模型训练、算法对比及工业质检场景验证。压缩包内图片清晰,标注信息完整,共包含2个类别:damaged(破损)与good_condition(良好箱面),合计936个矩形标注框,其中damaged框199个、good_condition框737个,可直接用于训练缺陷定位与分类模型。文件共1953个,主要由650张jpg图像、650个xml标注文件和YOLO格式txt文件构成,可兼容YOLO与VOC训练流程;资源整体打包为zip,仅25.16MB,下载与解压均很轻便。数据集未做图像增强,分辨率清晰,适合希望基于真实原始样本开展实验的初学者或工程人员。当前已有48人学习/下载,可作为轻量级公开数据基准,支撑行李箱缺陷检测相关课程设计、论文实验或原型验证。
1. 行李箱缺陷检测数据集的真实价值:小模型也可以快速起步
拿到一个650张、2类的YOLO+VOC格式行李箱缺陷检测数据集时,别嫌它量小。缺陷检测在质检场景里最现实的问题是“缺陷样本本来就不多”,真实工厂要攒几千张划痕、凹陷照片往往需要几条产线连续跑几周;而一个650张的小数据集反而适合用来把漏检逻辑、训练链路和部署验证先跑通。这个数据集适合两类人:一是刚学目标检测,想拿自己的数据而不是COCO练手的开发者,照着YOLOv8的标准流程能完整走一遍数据校验、预训练权重加载、训练和测试;二是做行李箱/箱包质检方案评估的工程师,用小样本验证打光、拍摄角度和模型选型,再决定要不要投入产线。下面按我实际做过的质检项目顺序,把这个数据集的每个使用环节和容易翻车的点都过一遍。
2. 拆包后的第一件事:YOLO标签和VOC标注到底差在哪
数据包名字里同时写着YOLO和VOC,这其实是最容易让人忽略的坑。YOLO格式是每个txt文件里存归一化坐标,VOC格式是每个xml文件里存像素坐标,两者虽然描述同一个目标,但坐标基准完全不同。如果你不做一次字段级交叉校验就直接训练,轻则某些图片的标签被静默跳过,重则训练时loss忽高忽低,最后模型在验证集上看起来不错,一到真机就漏检。
2.1 同一张行李箱照片在两种标注格式里的样子
先看VOC的xml,它把坐标存成了像素值:
<annotation> <filename>IMG_0012.jpg</filename> <size> <width>1920</width> <height>1280</height> </size> <object> <name>scratch</name> <bndbox> <xmin>612</xmin> <ymin>780</ymin> <xmax>1090</xmax> <ymax>1180</ymax> </bndbox> </object> </annotation>xmin、ymin是左上角,xmax、ymax是右下角,单位是像素。这张图里scratch这个类别对应的就是缺缺陷框,如果同一张图还有凹陷,就会有第二个object节点。
对应同一个目标的YOLO标签文件,一行文本:
0 0.4432 0.7656 0.2489 0.3125第一个数字是类别ID,0对应scratch,1对应dent;后四个数分别是归一化后的中心点x、中心点y、宽、高。计算方式很简单:
- x_center = (xmin + xmax) / 2 / image_width
- y_center = (ymin + ymax) / 2 / image_height
- width = (xmax - xmin) / image_width
- height = (ymax - ymin) / image_height
YOLO系列从v5到v8,标签文件格式基本保持同一套规则,所以这个txt对YOLOv8、YOLOv5、YOLOX等主流检测框架都通用。需要特别注意的是:如果xml里的目标宽度为0,或者坐标越界,转换后就会生成异常标签;这类问题在650张的小数据集里可能只有几张,但足以让训练不稳定。
2.2 用Python把VOC转成YOLO格式的可靠脚本
很多标注平台导出时只给VOC,训练时又需要YOLO格式。即使这个zip里两种都给了,我也会自己写脚本转一次,因为同一批数据在两个文件夹之间不同步的情况太常见。下面这个脚本可以直接复用:
import xml.etree.ElementTree as ET import os # 类别顺序必须和训练时一致,先确定好,别中途乱改 CLASS_NAMES = ["scratch", "dent"] def convert_voc_to_yolo(xml_file, out_txt, img_w, img_h): tree = ET.parse(xml_file) root = tree.getroot() lines = [] for obj in root.iter("object"): name = obj.find("name").text.strip() if name not in CLASS_NAMES: # 遇到没见过的类别,宁可打印出来,不要静默跳过 print(f"[skip] {os.path.basename(xml_file)} -> unknown class: {name}") continue cls_id = CLASS_NAMES.index(name) bndbox = obj.find("bndbox") xmin = float(bndbox.find("xmin").text) ymin = float(bndbox.find("ymin").text) xmax = float(bndbox.find("xmax").text) ymax = float(bndbox.find("ymax").text) # 防御性检查,非法框直接忽略 if xmax <= xmin or ymax <= ymin: print(f"[warning] invalid bbox in {xml_file}: {xmin},{ymin},{xmax},{ymax}") continue if xmin < 0 or ymin < 0 or xmax > img_w or ymax > img_h: print(f"[warning] bbox out of range in {xml_file}: {xmin},{ymin},{xmax},{ymax}") continue x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") if lines: with open(out_txt, "w") as fp: fp.write("\n".join(lines))这段脚本有几个参数值得注意:CLASS_NAMES的顺序直接决定类别ID,如果你把dent写在scratch前面,整个模型的预测结果就全反了;img_w和img_h必须和真实图片分辨率一致,很多标注平台导出xml时不会带上图片尺寸,你读取时要二次核对。防御性检查不是多余的,我遇到过某个数据集里一张图片只有1KB但xml里标注了1000多个长宽坐标,那种数据进训练就是血泪。
2.3 可视化矩形框,把标注错误扼杀在训练前
光看txt数值很难发现问题。我习惯把YOLO标签直接画到图上,一张一张快速翻,比任何统计脚本都直观。下面这个可视化脚本可以少写很多重复代码:
import cv2 import os CLASS_NAMES = ["scratch", "dent"] def draw_yolo_boxes(image_path, label_path): img = cv2.imread(image_path) h, w = img.shape[:2] with open(label_path, "r") as f: for line in f: data = line.strip().split() if len(data) != 5: continue cls_id = int(data[0]) xc, yc, bw, bh = map(float, data[1:]) x1 = int((xc - bw / 2) * w) x2 = int((xc + bw / 2) * w) y1 = int((yc - bh / 2) * h) y2 = int((yc + bh / 2) * h) color = (0, 0, 255) if cls_id == 0 else (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, CLASS_NAMES[cls_id], (x1, max(0, y1 - 8)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imshow("check", img) cv2.waitKey(0) # 使用示例 draw_yolo_boxes("datasets/luggage650/images/train/IMG_0012.jpg", "datasets/luggage650/labels/train/IMG_0012.txt")重点检查三种情况:框是否明显偏离行李箱轮廓;一个划痕是否被拆成了两三个互相重叠的框;标签里是否存在两个类别ID混乱的目标。这种视觉检查花不了二十分钟,但它能避免你浪费几小时在错误的标注上训练。
3. 把650张行李箱图变成YOLOv8认得的训练集:划分与增强
拿到一个650张的小数据集,最忌讳的就是把所有图片直接丢进去训练,然后跑完看个mAP就说效果好。小数据集的训练集、验证集和测试集划分,比大数据的划分更影响最终指标,因为每一张图都可能改变几个百分点的分数。
3.1 固定随机种子,做train/val/test划分并同步复制标签
我一般按8:1:1的比例划分,并且固定随机种子。这样你做对比实验时,每次改变的不是数据划分,而只是模型或参数。
import os import random import shutil random.seed(42) src_images = "datasets/luggage650/images" src_labels = "datasets/luggage650/labels" out_root = "datasets/luggage650_split" all_images = [f for f in os.listdir(src_images) if f.endswith((".jpg", ".png"))] all_images.sort() random.shuffle(all_images) n_total = len(all_images) n_train = int(n_total * 0.8) n_val = int(n_total * 0.1) # 剩下的自然就是 test splits = { "train": all_images[:n_train], "val": all_images[n_train:n_train + n_val], "test": all_images[n_train + n_val:], } for split, img_list in splits.items(): img_out = os.path.join(out_root, "images", split) label_out = os.path.join(out_root, "labels", split) os.makedirs(img_out, exist_ok=True) os.makedirs(label_out, exist_ok=True) for img_name in img_list: shutil.copy(os.path.join(src_images, img_name), img_out) base_name = os.path.splitext(img_name)[0] label_name = base_name + ".txt" label_path = os.path.join(src_labels, label_name) if os.path.exists(label_path): shutil.copy(label_path, label_out) else: print(f"[missing] {label_name}")这段脚本有一个容易被忽略的点:图片文件名和标签文件名必须严格一一对应。如果IMG_0012.jpg没有IMG_0012.txt,训练时会报空标签警告,一般在YOLOv8里该图会跳过或者当作背景图处理。小数据集只要缺三五个标签,训练就会出问题,所以最好把[missing]打印出来人工处理。
3.2 增强参数不要全开,小样本更看重适度
650张小数据集的增强思路是:让模型看到更多变化,但不要破坏缺陷本身的形状。像行李箱这类刚性物体,过度旋转增强会让划痕变形失去物理意义,YOLOv8的fliplr可以开,但degrees不建议超过15。
我常用的增强参数值如下:
| 参数 | 推荐范围 | 说明 |
|---|---|---|
hsv_h | 0.01-0.02 | 色相变化,行李箱颜色多样,可以稍微放宽 |
hsv_s | 0.5-0.8 | 饱和度变化,帮助适应不同塑胶材质反光 |
hsv_v | 0.3-0.5 | 亮度变化,模拟不同产线打光 |
degrees | 5-15 | 旋转角度,太大缺陷形态不自然 |
translate | 0.1-0.2 | 平移,当前小数据集常见 |
scale | 0.3-0.5 | 缩放模拟不同拍摄距离 |
fliplr | 0.5 | 水平翻转,行李箱对称,可以开 |
mosaic | 1.0或0.5 | 小数据集开mosaic可以增加样本,但缺陷微小目标会被缩小,建议先试1.0再看效果 |
注意:这些参数是通过YOLOv8命令行传入的,例如hsv_h=0.015 hsv_s=0.7 hsv_v=0.4 degrees=10 fliplr=0.5。它们对最终效果的影响很大,我建议把mosaic和scale先单独拉开实验,因为行李箱表面上有些纹理本身就很像划痕,增强过头会让模型把正常纹理也当成缺陷。
3.3 写一个不会出错的dataset.yaml
YOLOv8的配置文件和YOLOv5兼容,直接写一个简单的yaml:
# datasets/luggage650/data.yaml path: /home/user/datasets/luggage650_split # 这个根目录建议写绝对路径 train: images/train val: images/val test: images/test nc: 2 names: 0: scratch 1: dentpath字段是根目录,train和val是相对path的路径。我遇到过最典型的问题是:把path写成相对路径,然后在别的目录下跑yolo train,系统拼接出来的路径不对,训练报错。另一个坑是names的ID顺序必须和标签文件里的cls_id一致。标签文件里0代表scratch,yaml里0就必须是scratch,如果你把dent写在0位置,训练不会报错,但预测结果会互相换掉,这种错误非常隐蔽。
4. 用YOLOv8训练行李箱缺陷检测模型:预训练权重、命令行参数与日志判断
数据准备完成之后,就到了整个流程里最容易产生成就感也最容易自我麻痹的阶段:训练。YOLOv8的封装程度很高,一条命令就能跑起来,但命令里的参数怎么设定,日志怎么判断,才是真正决定这个模型能不能用的地方。
4.1 环境安装与预训练权重选择
先装ultralytics包,这个动作是所有训练的前提:
pip install ultralyticsYOLOv8在首次运行时会自动下载预训练模型权重,比如yolov8n.pt,这就是常说的yolo预训练模型下载。如果你网络受限,也可以手动把权重文件放到当前目录下。对于650张、2类的小数据集,我强烈建议用yolov8n或者yolov8s,而不是一上来就用yolov8x。原因是缺陷特征相对简单,大模型在小数据上很容易过拟合,训练时间还长,收益微乎其微。
4.2 训练命令:epochs、batch、imgsz怎么定
下面是我在这个数据集上会用到的命令:
cd /home/user yolo detect train \ model=yolov8n.pt \ data=datasets/luggage650_split/data.yaml \ epochs=200 \ batch=16 \ imgsz=640 \ patience=30 \ hsv_h=0.015 hsv_s=0.7 hsv_v=0.4 \ degrees=10 \ fliplr=0.5 \ project=results/luggage_experiment \ name=run1参数解释如下:
model=yolov8n.pt:使用预训练权重初始化网络。650张小数据集的训练一定要加载预训练权重,否则收敛很慢,最终精度也会差不少。epochs=200:小数据集跑200轮足够,配合patience做早停。batch=16:在8GB显存上这个值比较稳,如果显存不够降到8。imgsz=640:行李箱是整体目标,缺陷较细,640分辨率是通用选择。你的图片如果是1920的大图,建议先离线resize到1280或640,不要直接用原尺寸训练,否则显存和速度都会失控。patience=30:30轮内验证集指标不升就自动停止,小数据集的训练一般到不了200轮,可能在80-120轮就停了。
注意project和name这两个参数,如果你每次跑实验都换一个name,结果会单独存文件,方便后续做对比。如果不指定,默认会写到当前目录的runs/detect/train里。
4.3 判断训练收敛:yolo损失函数、mAP50和混淆矩阵
训练过程中终端会持续输出训练损失和验证指标。你要重点看三个损失:box_loss(边界框回归损失)、cls_loss(分类损失)、dfl_loss(分布焦点损失)。当这三个损失在第30轮后还有明显波动,说明学习率太大;如果验证集指标五轮都不变,也先别急着打断,因为小数据集经常出现平台期,然后突然涨一点。
训练结束后,验证集上会给出mAP50和mAP50-95。小数据集上mAP50达到80以上比较正常,但更关键的是看混淆矩阵。用下面的代码读取:
from ultralytics import YOLO model = YOLO("results/luggage_experiment/run1/weights/best.pt") metrics = model.val(data="datasets/luggage650_split/data.yaml") # 打印混淆矩阵 print(metrics.confusion_matrix.matrix)混淆矩阵里最常见的坑是:scratch和dent两个类别互相混淆,或者大量真实目标被检测成背景。读到混淆矩阵后,不要急着改网络结构,先回去看对应图片的标注是不是有误。我在实际项目中曾经因为一个类别在标注时框得过大,把行李箱滚轮也包进去了,导致模型把滚轮误检成划痕。
5. 行李箱缺陷检测中的避坑指南:这5个问题实战里几乎一定会碰到
训练不是跑通了就万事大吉。下面这几个问题我几乎每次用类似小数据集都会碰到,每一个都是“现象→原因→解决”的固定套路。
5.1 类别名对不上,转换脚本静默丢标签
现象:训练时的cls_loss下降很快,但验证集mAP50一直为零。翻训练日志看到每张图有标注数量极少,有的图一个目标都没有。
原因:标注平台导出xml时类别名可能是scratch "Scratch"带上空格或大小写差异,你脚本里的CLASS_NAMES没有匹配上,转换时全部被跳过;更隐蔽的是,一个txt文件里只有4、5行标签正常,最后一行类别ID超出nc,YOLOv8读取时直接忽略整张图。
解决:转换脚本里遇到未知类别不要跳过,要打印完整类别名和出现次数。训练前用一个小脚本扫描所有标签,把类别ID超出0和1的文件挑出来:
import os label_dir = "datasets/luggage650_split/labels/train" for f in os.listdir(label_dir): path = os.path.join(label_dir, f) with open(path, "r") as fp: for line in fp: cls_id = line.split()[0] if int(cls_id) > 1: print(f"{f}: class_id {cls_id} out of range")这一步能省下后面排查的绝大部分时间。
5.2 小样本过拟合:训练loss下降,验证loss回升
现象:训练到第40轮,训练损失持续下降,但验证损失在30轮后开始反弹,precision和recall在验证集上震荡,最终模型对没见过的图表现很差。
原因:650张数据太少了,模型在后期记住了训练集中缺陷的具体纹理和背景颜色,而不是学习“行李箱上异常纹路”这一抽象概念。
解决:第一个手段是加patience早停,让模型停在验证集最好的位置;第二个手段是减小模型尺寸,从yolov8s换成yolov8n;第三个手段是适度加大增强,特别是scale和hsv_v,让模型看到更多亮度变化。如果以上都不明显,就需要人工筛选最像缺陷的难例,补充标注到训练集里。
5.3 长条划痕被拆成两段,NMS把结果过滤掉
现象:置信度阈值调到0.1才能看到完整划痕,默认0.25时只检测到划痕的一小段,或者同一道划痕出现两个重合框。
原因:行李箱划痕长宽比常常大于5:1,模型预测出的框会有多个候选重叠,默认NMS的iou=0.45会把两段框合并或者删除其中一个。另外,长条目标的中心点定位本身就容易漂移。
解决:训练和推理时把iou阈值调高到0.7,保留更多重叠候选框;推理时降低conf到0.15,用“宁可错检也不漏检”的策略,然后在后处理里把高度重叠的长条框做加权合并。这个调整对长划痕特别有效。
5.4 行李箱拉链、滚轮边缘被当成缺陷
现象:正常箱体的栅格纹理、拉链边缘,在模型预测框里被标成scratch,而且置信度还不低。
原因:缺陷分类本质上是在找异常纹理,但正常纹理中也含有类似划痕的强边缘,尤其拉链和滚轮在打光下会出现高反光细线。
解决:这类问题看混淆矩阵里背景列的数字就能判断。处理办法是收集几十张没有任何缺陷的“负样本”图片加入训练集,并给它们一个空标签文件。训练时模型会碰到大量背景,自然会把单纯的边缘特征权重降低。另外一个办法是后处理时按标注框位置过滤——行李箱滚轮通常在图像角落,拉链一般在箱体中线附近,可以根据先验规则去掉一部分误检。
5.5 换一个行李箱型号,召回率断崖式下降
现象:当前测试集上mAP50有0.85,换到另一批不同颜色、不同纹理的行李箱实拍图上,召回率从80%掉到30%,却没有任何训练报错。
原因:数据集本身只有650张,大概率只覆盖了少数几种箱体材质。棕色全PC箱和银色铝框箱在纹理细节上差异巨大,模型从650张里学到的特征不够泛化。
解决:这是小数据集的本质限制,只能靠数据补齐。常见做法是在产线打光固定的前提下,有意识地加入不同材质、不同使用程度的样本,做一次“分批次微调”——把新采集的真机照片做成一个小的验证集,然后跑30个epoch的增量训练。关键是不要手动复制旧标签,而是先让旧模型自动标注,再人工修正一批,不然微调会加剧过拟合。
6. 验证和打包流程:拿训练好的模型去碰一碰真机照片
模型训练完成后,先别急着导出部署,一个值得养成的习惯是做一次真机照片验证。用训练和验证都见过的数据评估模型是自我感动,拍几张没有被清洗过的现场照片,立刻能看到模型的真实水平。
from ultralytics import YOLO model = YOLO("results/luggage_experiment/run1/weights/best.pt") results = model.predict( source="live_photos/20240315_phone_capture.jpg", conf=0.2, iou=0.7, save_txt=True, save_conf=True, ) print(results[0].boxes)conf=0.2是故意调低一点,先看模型能检出哪些目标;如果0.2时误检太多,再往高调到0.35。不要直接按默认0.25定结论,因为真机照片的曝光和训练集差异很大,一次阈值判断不够。
验证没问题后,导出为ONNX格式,方便后续去掉PyTorch依赖、在工控机上跑推理:
yolo export model=results/luggage_experiment/run1/weights/best.pt format=onnx imgsz=640导出后用ONNXRuntime做一个最简单的批量推理脚本,检查导出后的模型和PyTorch结果是否一致。如果你的产线用的是TensorRT或OpenVINO,也可以在这个节点换上对应后端,但导入流程和超参选择是相同的。
最后一个小建议:每次新到一个行李箱批次,不要把新样本一股脑合并进旧数据重训。更稳妥的做法是把新样本收集成单独的验证集,让旧模型先标一遍,你再人工纠正几十张,再利用这些难例做5-20个epoch的微调,观察新验证集的召回率变化。我这里十次有八次会在这个环节发现模型在新批次上的缺陷形态和旧训练集差距很大,暴露问题后,再去决定是补采集还是改打光。这条流程走顺了,650张的数据集也能帮你快速判断整个质检方案的可行性。希望我的这些踩坑记录能帮你少走一点弯路。
本文还有配套的精品资源,点击获取