集装箱缺陷检测数据集全解析:从VOC/YOLO格式到YOLOv8训练
简介面向计算机视觉目标检测任务的数据集资源适用于集装箱表面缺陷识别模型的训练与验证主要服务于从事工业质检智能化研究的学生、算法工程师及相关项目开发者。数据集中共包含4127张真实场景图片每张图片均提供Pascal VOC格式的xml标注文件与YOLO格式的txt标注文件覆盖凹陷、孔洞、锈蚀三类常见缺陷总标注框数超过1万个标注由labelImg工具完成规则统一为矩形框标记。压缩包内共2000个文件其中以xml标注文件为主辅以使用说明txt文档整体体积约163.61MB结构简洁便于直接接入常见深度学习框架。数据集标注类别均衡Dent类约4943框、Rust类约3956框、Hole类约1218框可供目标检测、缺陷分类等任务开展训练与评测适合需要标准化标注数据的算法验证场景使用。目前已有348人学习下载。1. 集装箱缺陷检测数据集4127张双格式标注先别急着训练集装箱表面缺陷检测这些年一直是视觉方案的老大难箱体反光、锈迹和划伤混在一起现场光照又杂。这个数据集抓的对象就是三类常见缺陷——凹陷Dent、孔洞Hole、锈蚀Rust总共4127张实拍图片每张同时带Pascal VOC的xml和YOLO格式的txt标注总共10117个框。看上去开箱即用真正上手你会发现几个容易翻车的点类别严重不平衡Hole只有1218框Dent有4943框、双格式坐标换算认知偏差、还有“数据没划分”这件事。这篇就当拆箱记录把结构、校验、训练和踩坑一次讲完适合正打算拿集装箱缺陷做检测模型、又不想在数据环节浪费时间的从业者。2. 数据集结构拆解VOC 与 YOLO 两种格式的对应和换算拿到数据集先别急着配环境先看目录结构。这个包解压后是典型的 labelImg 双导出结果图片是 jpgVOC 标注是 firc_jizhuangxiang_647.xml 这种命名YOLO 标注是同名 .txt。文件名完全一一对应例如 firc_jizhuangxiang_647.jpg、firc_jizhuangxiang_647.xml、firc_jizhuangxiang_647.txt 是同一条样本。2.1 文件组织方式jpg、xml、txt 三者如何一一对应拆过太多“标注数量对不上”的数据集我拿到手的第一件事永远是跑一遍计数脚本而不是直接开始配环境。用下面这个脚本做基线检查import os from collections import Counter jpg_dir images # 按实际解压路径改 xml_dir annotations/voc txt_dir annotations/yolo def count_files(path, ext): return {os.path.splitext(f)[0] for f in os.listdir(path) if f.endswith(ext)} jpg_names count_files(jpg_dir, .jpg) xml_names count_files(xml_dir, .xml) txt_names count_files(txt_dir, .txt) print(jpg:, len(jpg_names), xml:, len(xml_names), txt:, len(txt_names)) print(jpg - xml:, len(jpg_names - xml_names), xml - jpg:, len(xml_names - jpg_names)) print(txt - xml:, len(txt_names - xml_names))逻辑很简单把三个目录里去掉扩展名的文件名分别收集成集合再做差集。差集为空说明一一对应不为空就说明有孤儿文件——通常是标注没保存全、复制文件时漏了几个或者解压中断。这一步花 30 秒能省后面训练时一整天的排错时间。参数上唯一要注意的是路径别写死绝对路径直接解压后改成你自己的目录即可。2.2 同一个目标两种写法坐标体系换算的根很多人以为 VOC 转 YOLO 就是写个脚本的事儿其实核心是坐标参照系不同。下面是一份典型 xml 中单个 object 的内容annotation filenamefirc_jizhuangxiang_647.jpg/filename size width1920/width height1080/height depth3/depth /size object nameDent/name bndbox xmin612/xmin ymin340/ymin xmax803/xmax ymax451/ymax /bndbox /object /annotationVOC 的 bndbox 记录的是左上角点 (xmin, ymin) 和右下角点 (xmax, ymax)单位是像素YOLO 格式的 txt 一行是“类别索引 cx cy w h”其中 cx、cy 是归一化后的中心点坐标w、h 是归一化后的框宽高。换算公式固定如下w (xmax - xmin) / width h (ymax - ymin) / height cx (xmin xmax) / 2 / width cy (ymin ymax) / 2 / height举刚才的数据width1920xmin612xmax803则 w(803-612)/19200.0995cx(612803)/2/19200.3685对应 txt 里“0 0.3685 0.3662 0.0995 0.1028”这样一行。这里最容易踩的坑是把 ymin、ymax 顺序写反或者把 w 写成 (xmax - xmin) 后除以 height。三角形换算看起来很入门数据集里 10117 个框任何一处坐标顺序错误都会让训练结果直接崩坏。2.3 三类缺陷的框数分布拿到统计先做预期管理预处理之前先用一句话总结这份数据的统计总图片 4127 张总框数 10117平均每张 2.45 个框三类缺陷框数差异非常明显。下表是原始统计类别框数占总框数比例大致每几张图出现一次Dent494348.9%约每张 1.2 框Rust395639.1%约每张 0.96 框Hole121812.0%约每张 0.3 框注意这个分布带来的不是一个“有没有标注”的问题而是训练策略问题。Hole 只有 1218 个框如果直接用默认配置训练模型对小样本类别的召回率通常会垫底后期要么靠数据增强把 Hole 的样本权重提上去要么在损失函数里做类别均衡。此外Dent 平均每张图超过 1 个框说明同一张图里经常有多个凹陷目标这也意味着模型在推理时的 NMS 阈值需要根据实际误检情况调而不是闭眼用默认值。3. 训练前的校验与划分类别核对、train/val 分割与 data.yaml3.1 类别名校验错一个字母训练直接白给这个数据集三类标注名是 “Dent”“Hole”“Rust”但很多训练框架如 YOLOv5 系的 data.yaml只认小写或特定顺序的类别列表。常见事故是txt 里的类别索引写的是 0、1、2而 data.yaml 里 names 顺序写成了 [“dent”, “rust”, “hole”]结果模型把 Rust 框当成 Hole 来学。训练不会报错mAP 也不会为 0但分类准确率永远上不去属于最阴间的错误。建议动手写一个扫描脚本把每个 txt 的类别索引和对应 xml 中的 name 做一次交叉核对import os, xml.etree.ElementTree as ET xml_root annotations/voc yolo_root annotations/yolo xmls [f for f in os.listdir(xml_root) if f.endswith(.xml)] errors 0 for xml_file in xmls: tree ET.parse(os.path.join(xml_root, xml_file)) names [obj.findtext(name) for obj in tree.findall(object)] base os.path.splitext(xml_file)[0] txt_path os.path.join(yolo_root, base .txt) if not os.path.exists(txt_path): print(缺txt:, base) errors 1 continue with open(txt_path) as f: idxs [int(line.split()[0]) for line in f if line.strip()] # 最小检查txt中出现的最大索引必须小于类别数3 if max(idxs) 3 or min(idxs) 0: print(索引越界:, base, idxs) errors 1这段脚本关心两件事txt 是否缺失、类别索引是否越界。索引 0/1/2 分别对应 Dent/Hole/Rust顺序必须在 data.yaml 里保持一致。任何一条越界都说明标注文件在导出或复制过程中被损坏需要重新解压原包而不是自己硬修因为人工改索引很容易把坐标也改错。3.2 训练集与验证集划分随机抽取 8 比 2固定随机种子这份资源明确说了“不包含分割路径的 txt 文件”也就是说它没有给你现成的 train.txt 或 val.txt 划分需要自己分。我一般习惯按 8:2 随机划分图片而不是按目录分因为集装箱图片采集时往往连续拍摄按目录划分容易把同一批光照环境全部塞进验证集导致验证结果虚高或虚低。import os, random, shutil random.seed(42) jpg_dir images train_dir split/train_images val_dir split/val_images os.makedirs(train_dir, exist_okTrue) os.makedirs(val_dir, exist_okTrue) jpgs [f for f in os.listdir(jpg_dir) if f.endswith(.jpg)] random.shuffle(jpgs) val_count int(len(jpgs) * 0.2) for i, f in enumerate(jpgs): dest val_dir if i val_count else train_dir shutil.copy(os.path.join(jpg_dir, f), os.path.join(dest, f))固定 seed42 是非常实用的习惯同一份数据和同一套划分训练结果才能复现也好和别人对比。val_count 这里按总图片数取整4127 张图会得到大约 826 张验证图和 3301 张训练图。如果你更倾向用硬链接而不是复制Linux 下可以把 shutil.copy 换成 os.link省磁盘空间注意跨文件系统时硬链接会失效。3.3 data.yaml 到底怎么写路径、类别顺序和框架要求YOLO 系框架训练前必须写一个 data.yaml告诉框架图片在哪、标签在哪、类别有哪些。这个文件写错是最常见的“看起来没问题但训练报错”来源。下面这份是基于本数据集的推荐写法path: /home/yourname/container_defect train: split/train_images val: split/val_images nc: 3 names: 0: Dent 1: Hole 2: Rustpath 是数据集根目录的绝对路径train 和 val 写相对于 path 的子路径。这里要特别注意两点一是 names 的索引顺序必须和 txt 标注中类别索引一致二是不要自作主张在 train 里同时写 images 和 labels 两级路径Ultralytics 框架是根据 train 路径自动寻找同级 labels 目录的即 split/train_images 的同级 split/train_labels如果想要自定义标签目录需要显式配置。有人会问VOC 那边不是也有标注吗为什么不直接用 xml两个原因一是 YOLO 系列框架原生读取 txt效率更高二是这份数据集的 txt 和 xml 本就同源于 labelImg既然 txt 已经存在且核对过坐标换算直接用 txt 最省事。到这个阶段数据侧的工作其实已经完成大半剩下就是训练。4. YOLOv8 实操训练命令、超参数与日志判读4.1 选哪个模型尺寸先 yolov8n 跑通再谈精度这一步的目标不是“一步到位训出最好的模型”而是先把流程跑通确认数据和代码链路没毛病。Ultralytics YOLOv8 的预训练权重从 n、s、m、l、x 五档递增参数量和推理速度差别很大。本项目缺陷框相对稀疏箱体表面缺陷的特征不算精细我一般直接建议从 yolov8n 或 yolov8s 起步。模型参数量约速度对本数据集的建议yolov8n3.2M最快第一轮跑通首选yolov8s11.2M快第二轮精度对比yolov8m25.9M中等精度上不去再试yolov8l/x43.7M/慢显存充足且追求极限环境安装不复杂常见做法是 pip install ultralytics然后下载预训练权重。如果机器是 Windows 且只有 CPUn 模型也能跑只是慢建议用带 GPU 的 Linux 机器或云 GPU这一步不必在环境上花太多时间。4.2 训练命令与超参数设置epochs、imgsz、batch 的取舍给本数据集的第一版训练命令我建议这样写yolo detect train \ modelyolov8n.pt \ datacontainer_defect.yaml \ epochs200 \ imgsz1280 \ batch16 \ patience30 \ projectrun_container \ nameexp1逐个参数说epochs200 给足收敛空间Dent 和 Rust 样本量大Hole 样本量少轮数太少 Hole 根本学不动imgsz1280 是为了保留小目标的细节集装箱锈蚀和凹陷经常是几十像素级别的局部特征640 输入会损失大量边缘信息这一项对这个数据集非常关键batch16 是 24GB 显存下的稳妥值显存小就降到 8batch 太小会导致 BatchNorm 统计不稳定patience30 是早停轮数超过 30 轮验证集 mAP 没有提升就自动停止避免空烧算力。有人喜欢把 mosaic 数据增强关掉理由是集装箱图片背景单一、拼接后语义混乱。我的经验是保留默认增强但把 close_mosaic10 设为最后 10 轮关闭 mosaic让模型在训练后期看到干净的原始图这能明显减少推理阶段对拼接伪影的依赖。4.3 日志怎么看三个类别分开盯尤其 Hole训练开始后终端会输出每轮的 P、R、mAP50、mAP50-95。重点不是总 mAP而是三个类别各自的指标。YOLOv8 训练结束后会在 project/name 目录下生成 confusion_matrix.png 和 results.csv我只说两个最值得关注的现象第一如果整体 mAP50 在 0.75 以上但 Hole 的 R 值才 0.4说明模型对孔洞漏检严重处理方法不是盲目加大 epochs而是先检查是不是类别不平衡导致决策边界偏向 Dent。第二如果训练 loss 持续下降但 val loss 在 30 轮后开始回升这是过拟合信号优先把 epochs 降下来或加正则项而不是加数据。import pandas as pd df pd.read_csv(run_container/exp1/results.csv) hole_cols [c for c in df.columns if Hole in c] print(df[hole_cols].tail(5))这段代码用 pandas 直接读 results.csv把 Hole 相关的列抓出来看最后五轮。Ultralytics 默认会把每个类别的指标也写进 csv字段名类似 metrics/precision(B)、metrics/recall(B)多加一个类别过滤列就能单独观察。别只在终端盯数字训练完把 csv 拉出来做曲线能发现很多终端滚动日志里注意不到的问题。5. 避坑与排查五条用训练时间换来的硬经验5.1 现象解压后文件缺失标注数量对不上解压 7z 包后jpg、xml、txt 数量不都是 4127有的数据集包在 Windows 下用老版本解压工具会中断或者文件名里的路径导致解压失败。原因7z 压缩包内目录层级与解压软件默认设置不一致部分文件在解压中途被跳过或解压出的路径嵌套了一层额外目录。解决用 7-Zip 或 Linux 下的 p7zip 命令直接解压到工作盘根目录不要套一层中文文件夹。解压完立刻跑一遍 2.1 节的统计脚本看到 4127/4127/4127 再继续。5.2 现象训练刚开始就报 labels 相关错误训练日志里出现 “Assertion labels are not correct failed”或 label 读取阶段报 shape 不匹配。原因大多是 txt 中存在空行、负坐标或类别索引越界。这份数据集本身标注质量是可信的但来源包的复制过程可能损坏个别文件。解决先跑 3.1 节的交叉核对脚本把越界文件筛出来如果只有一两个文件异常直接删掉对应 jpg/xml/txt 三元组而不是保留无标注图片保留无标注图片会干扰训练时的背景采样。5.3 现象模型把集装箱边缘误检成 Dent预测阶段发现箱子边框、吊角这些位置频繁出现 Dent 框精确率被拉低。原因箱体边缘和真实凹陷在灰度纹理上相近小尺寸输入下细节混淆另外训练集里边缘区域的负样本上下文不足误检难以被抑制。解决第一优先把 imgsz 从 640 提到 1280其次在验证阶段降低 conf 阈值到 0.15 看是误检还是漏检最后考虑裁剪训练把单张图按 2x2 切块给模型更充分的边缘上下文。这个数据集没有切块版本需要自己写脚本生成常见做法是把原图切块后按坐标平移 bndbox。5.4 现象Hole 类 mAP 始终比其他两类低一截训练结束后 Hole 的 AP50 明显低于 Dent 和 Rust甚至低于 0.3。原因Hole 只有 1218 框占总框数 12%类别不平衡让模型天然偏向样本多的类别且孔洞目标通常小、对比度低。解决按 3.2 节脚本固定 seed 后把训练集中 Hole 相关图片复制增强一份比如水平翻转亮度抖动再重训一版或者换用带类别权重平衡的损失配置。不要指望加 epochs 能解决不平衡加轮数只是让模型在多数类上更过度自信。5.5 现象验证集 mAP 很高现场测试完全不能看本地验证 mAP50 有 0.85拿到现场拍摄的集装箱图片上框全乱。原因验证集和训练集来自同一批拍摄序列场景分布太相近集装箱表面状态、光照、拍摄距离稍有变化模型泛化能力就露馅。解决严格按随机划分并把验证集单独隔离训练阶段不要看验证结果调参最终评估时留出约 200 张现场异域图片做盲测。数据集的特别声明里写得很清楚它不对训练后的模型精度作任何保证数据能给的是准确标注泛化要靠你自己做迁移验证。6. 可视化验证不可省训练后如何快速确认模型真的在学训练命令跑完并不是结束我强烈建议做完一轮可视化验证再收工。做法是用训练好的权重对验证集图片做批量预测并把预测框叠到原图上保存肉眼抽查 30-50 张。这一步不是走形式它能暴露日志和指标里看不到的问题比如框偏移半截、同一缺陷出两个框、小孔漏检。from ultralytics import YOLO model YOLO(run_container/exp1/weights/best.pt) results model.predict( sourcesplit/val_images, conf0.25, iou0.5, imgsz1280, saveTrue, projectvis_check, nameval_pred ) print(预测图保存至:, vis_check/val_pred)这段代码用训练好的 best.pt 对验证集全部图片做推理参数里 conf0.25 是比较低的置信度阈值目的是宁可多出框也不能漏掉真实缺陷iou0.5 是标准 NMS 交并比imgsz1280 必须和训练时一致否则特征尺度不一致会导致精度下降。saveTrue 会把带预测框的结果图片存到 vis_check/val_pred 文件夹下面。看完可视化结果之后再回过头对比 results.csv 里的指标基本就能判断模型是能部署还是需要返工如果整体质量不错但 Hole 不到位回到 5.4 做增强再训一版如果边缘误检多按 5.3 提到的方法换推理尺寸或裁剪方案。从那以后我每次拿到新数据集都强制先跑一遍完整校验流程再花十分钟做可视化抽查——数据不对训练再久也是白费。这个习惯帮我躲过了不少返工希望帮到你。本文还有配套的精品资源点击获取