ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

集装箱缺陷检测:VOC/YOLO数据集处理与YOLOv8训练全流程

集装箱缺陷检测:VOC/YOLO数据集处理与YOLOv8训练全流程 简介面向集装箱外观质检与目标检测模型训练这套数据集提供 4127 张集装箱表面图片的 Pascal VOC 与 YOLO 双格式标注覆盖 Dent、Hole、Rust 三类常见缺陷合计 10117 个矩形标注框其中 Dent 框 4943 个、Rust 框 3956 个、Hole 框 1218 个分布较完整。压缩包为 7z 格式共 2000 个文件以与图片同名的 xml 标注为主并附使用说明 txt整体约 163.61MB每张图片均有对应的 VOC xml 与 YOLO txt前者记录类别和矩形框坐标后者提供归一化坐标可直接供 YOLO 系列或 Faster R-CNN 等模型读取省去自行格式转换。压缩包内文件命名统一目录便于批量读取标注由 labelImg 工具人工画框完成能够直接用于划分训练、验证与测试集。目前已有 348 人浏览学习适合集装箱缺陷检测、工业外观质检算法验证及相关课题研究下载后可直接解压使用无需额外整理标注文件且缺陷类别贴近工业现场常见形态。1. 集装箱缺陷检测数据集VOCYOLO格式4127张3类别为什么说这份数据能直接进训练我前段时间在验证港口集装箱外观缺陷检测拆的是“集装箱缺陷检测数据集VOCYOLO格式4127张3类别”这套资源。最耗时间的不是选网络而是凑数据箱体表面的 Dent、Hole、Rust 三类缺陷受光照和反光影响大散图拼出来的训练集漏标错标比比皆是。这套数据给了 4127 张 jpg 和对应的 4127 个 VOC xml、4127 个 YOLO txt矩形框标注合计 10117 个框Dent 4943、Hole 1218、Rust 3956用 labelImg 画完框之后同时导出了两种格式。它适合暂时没有可靠箱体缺陷数据、想快速跑通 YOLO 训练流程的人作者也明说了不保证模型精度数据只保证标注合理准确这点比包装漂亮的资源更实在。下面从解压开始把它变成能直接训练的结构。2. 解压与目录规整把 7z 里的 jpg/xml/txt 配对成 YOLO 的 images/labels 结构2.1 Linux 下解压 7zp7zip 安装、命令与完整性核对数据集以 .7z 压包拿到生产服务器或 Ubuntu 上跑训练第一件事是装 p7zip。Windows 上用 7-Zip 图形界面直接解压Linux 下用命令行更顺手。命令如下sudo apt-get update sudo apt-get install -y p7zip-full 7z x 集装箱缺陷检测数据集VOCYOLO格式4127张3类别.7z7z 的 x 参数表示按包内原有目录结构解压和 unzip 的默认行为一致。如果不希望文件散落到当前目录加 -o 指定输出根目录7z x 包名.7z -o/data/defect注意 -o 和路径之间不要加空格这是 7z 命令行一个容易写错的地方。解压前先7z l 包名.7z列出包内容确认包内是否已经带了 images 或 annotations 目录再决定后边的规整脚本往哪扫。装 p7zip 时如果提示找不到包先 update 再装国内镜像源偶尔会出现缓存没刷新。解压完别急着打开图先做数量核对。这份数据集的硬指标是 jpg、xml、txt 三种文件各 4127 个用三条 find 就能查完find . -name *.jpg | wc -l find . -name *.xml | wc -l find . -name *.txt | wc -l三个结果必须完全一致。如果 jpg 是 4127 而 xml 只有 4126说明解压中断或原始包本身不完整这种损坏在 7z 固实压缩模式下特别隐蔽包里边一个文件坏了后面一串文件全解不出来但解压程序只报一次错。我的习惯是解压后把7z t 包名.7z也跑一遍输出末尾有 Everything is Ok 才继续往下走。提示解压出来三种文件数量不一致时不要自己手动补一个同名 xml优先怀疑压缩包损坏重新下载比修补标签省时间。2.2 通用配对脚本不管平铺还是子目录都归并成 images/labels解压后布局通常有两种可能jpg、xml、txt 全部平铺在一个目录里或者分别放在 images 与 annotations 子目录。无论哪一种YOLO 训练只认 images/train、images/val、labels/train、labels/val 这四条目录并且图片与标签必须同名。我一般不用手动整理而是写一个按文件名 stem 配对的脚本适配两种布局import os import random import shutil src_dir /data/defect # 解压后的根目录 out_root /data/container_defect # 规整后的目录 val_ratio 0.2 seed 42 imgs {} txts {} for root, _, files in os.walk(src_dir): for f in files: stem os.path.splitext(f)[0] if f.endswith(.jpg): imgs[stem] os.path.join(root, f) elif f.endswith(.txt): txts[stem] os.path.join(root, f) stems sorted(txts.keys()) random.seed(seed) random.shuffle(stems) val_count int(len(stems) * val_ratio) val_stems set(stems[:val_count]) for subset in [train, val]: os.makedirs(f{out_root}/images/{subset}, exist_okTrue) os.makedirs(f{out_root}/labels/{subset}, exist_okTrue) for stem in stems: if stem not in imgs: print(missing image for, stem) continue subset val if stem in val_stems else train img_src imgs[stem] txt_src txts[stem] shutil.copy2(img_src, f{out_root}/images/{subset}/{stem}.jpg) shutil.copy2(txt_src, f{out_root}/labels/{subset}/{stem}.txt) print(total stems:, len(stems), val:, len(val_stems))逻辑说明先用 os.walk 递归收集所有 jpg 和 txt建立文件主体名到绝对路径的映射再把主体名列表随机打乱后按比例切出验证集。这里以 txt 为主体而非 jpg是为了保证每一个验证样本都有标签文件。脚本里处理了“标签存在但图片缺失”的情况这类样本直接跳过比硬复制更安全不然训练时 YOLO 会在读取阶段直接报错。copy2 保留文件元数据不影响训练但后续可以用 md5 判断文件有没有被误改。参数说明val_ratio 取 0.24127 张里验证集约 825 张对 3 类缺陷来说验证集不算小。若 Hole 这类稀疏缺陷的框数太少可以把 val_ratio 降到 0.1后面检验模型时再用全量测试集。seed 固定为 42保证每次跑出来的划分一致——这点很重要后面调模型参数时如果 train/val 集合变了指标对比就没有意义了。2.3 规整后的自查数量、空标签、对应关系规整完要做一次快速验证。以下是三个常用命令find images/train -name *.jpg | wc -l find labels/train -name *.txt | wc -l find labels/val -name *.txt | wc -l图片与标签数量应该一致val 数量大约 825。接下来看有没有空 txt也就是没有任何标注的图片。空标签文件在 YOLO 里是合法的模型会把对应图片当负样本处理这能帮助降低误检但前提是训练集和验证集都包含一定比例的空标签图。如果全部为空或者全部非空需要自己去平衡。另一个自查点是 txt 内容格式每行 5 个数第一个是类别编号后四个是归一化坐标行数应与对应 xml 中 object 的数量一致。随机抽一个主体名把 xml 和 txt 对照读一眼比任何自动化检查都更能发现问题。3. 标注解析与格式换算VOC 框坐标转 YOLO 归一化坐标的公式与三个类别分布3.1 先看懂一个 xmllabelImg 保存的字段到底有什么数据集用 labelImg 标注标注规则是“对类别画矩形框”所以每个 xml 记录的就是一个标准的 Pascal VOC 检测框。解析这类文件不需要第三方库Python 自带的 ElementTree 就够用脚本如下import xml.etree.ElementTree as ET tree ET.parse(firc_jizhuangxiang_647.xml) root tree.getroot() filename root.find(filename).text width int(root.find(size).find(width).text) height int(root.find(size).find(height).text) print(image:, filename, size:, width, height) for obj in root.iter(object): name obj.find(name).text box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) print(name, xmin, ymin, xmax, ymax)输出形如firc_jizhuangxiang_647.jpg 1920 1080和若干行Dent 624 511 703 541。逻辑说明labelImg 把图片原始宽高存在 size 节点里框的坐标是绝对像素x 向右增大、y 向下增大这和 OpenCV 的习惯一致也和 YOLO 的坐标系一致转换时不需要翻转坐标。另一个细节是 xml 里没有多边形分割点只有矩形框的 bndbox这正对应摘要里那句“不包含分割路径的 txt 文件”——它指的是 YOLO 分割格式的多边形点不是目录路径。有新手把这句话理解成“没有划分 train/val 路径”以为还得自己补目录结构其实目录划分在第 2 章已经解决了。3.2 VOC 转 YOLO 公式归一化中心坐标怎么算四个边界坑YOLO 标签不是绝对坐标而是归一化到 0 到 1 的相对坐标。已知 xml 里是 xmin、ymin、xmax、ymax图片宽高为 w、h转换公式是class_names [Dent, Hole, Rust] cls_id class_names.index(name) x_center (xmin xmax) / 2.0 / width y_center (ymin ymax) / 2.0 / height box_w (xmax - xmin) / width box_h (ymax - ymin) / height line f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}参数说明x_center 是框中心点的水平相对位置由 xmin 和 xmax 取平均得到box_w 是框宽占整图宽的比例所以分母是图片宽而不是某个固定输入尺寸。有人会把分子分母一起除以 640或者按 416 归一化这在目标检测里是错误操作——归一化必须是相对原图尺寸否则训练时图片被缩放到 640标签也跟着错位。四个边界坑经常凑在一起出现。一是坐标越界labelImg 偶尔能画到图片边缘xmax 等于 width 是合法的归一化后 box_w 正好 1.0但 xmax 大于 width 就必须过滤。二是宽高为 0如果标注时手滑把框拖成一条线转换后 box_w 为 0计算损失时会出现除零或 nan表现是训练 loss 突然变成 nan。三是类别数字顺序txt 里 0 代表 Dent、1 代表 Hole、2 代表 Rust 还是别的顺序完全由上面 class_names 这个列表决定它必须与后面 data.yaml 的 names 一致。四是精度保留 6 位小数足够但不要用科学计数法Ultralytics 读取标签时对科学计数法兼容性较差看起来是 0.0051 写成 5.1e-3实际解析时容易出错。这份数据已经同时给了两种格式正常情况下不会再手动转换但理解公式仍然有实际价值。下载的资源可能被别人二次加工过txt 里的坐标与 xml 是否严格对应、是否夹带越界框都需要你用这组公式反向验证。我之前拿过一份声称是双格式的数据实际 txt 是用另一个脚本导出的中心点算错训练三天才发现问题。从那以后我拿到任何双格式数据都会先随机抽三个样本用公式把 xml 转一遍再和 txt 对比误差超过 0.001 就认为处理过程有问题。3.3 三个类别的框数分布Hole 为什么是最需要关注的那一类摘要里给了明确的统计数字整理成表更直观类别框数占比说明Dent494348.9%凹陷类缺陷样本最充足Hole121812.0%孔洞类缺陷占比最低Rust395639.1%锈蚀类缺陷样本量中等总框数 10117平均到 4127 张图上约 2.45 个框。这意味着大量图片只有单个缺陷少数图有多个类别不平衡真实存在。Hole 在集装箱表面通常是锈蚀穿孔或机械损伤面积小、对比度低比 Dent 和 Rust 都难学。训练时如果整体 mAP 上不去十有八九是 Hole 拖后腿。常见做法是给 Hole 增加过采样或提高 cls loss 权重这个放到第 6 章细说。另外Dent 是凹陷在箱体表面视觉上是光影变化而不是色差Rust 有明显颜色特征反而最好分。理解这三类缺陷在视觉上的差异比盲目调参数更省时间。4. 训练前的数据体检核对三类框数、坐标越界与 train/val 划分一致性4.1 全量扫 xml把 4127 个文件的框数和越界一次查完进入训练之前我会把数据当成待检查的原料先跑一个体检脚本。它的目的不是看单张图而是回答三个问题类别框数是否符合摘要给的 4943/1218/3956有没有坐标越界或宽高为 0 的框xml 与图片尺寸有没有不一致脚本如下放在原始解压根目录下运行import glob import xml.etree.ElementTree as ET expected {Dent: 4943, Hole: 1218, Rust: 3956} counts {} bad [] for xml_path in glob.glob(*.xml): root ET.parse(xml_path).getroot() img_w int(root.find(size).find(width).text) img_h int(root.find(size).find(height).text) for obj in root.iter(object): name obj.find(name).text box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) counts[name] counts.get(name, 0) 1 if xmin 0 or ymin 0 or xmax img_w or ymax img_h or xmax xmin or ymax ymin: bad.append((xml_path, name, (xmin, ymin, xmax, ymax), (img_w, img_h))) print(counts:, counts) print(expected:, expected) print(bad boxes:, len(bad)) for item in bad[:5]: print(item)逻辑说明counts 统计的是框数不是图数所以 Dent 4943 表示整份数据集中 Dent 这个类别共标了 4943 个框。判断越界的条件包括 xmax img_w 这种坐标超出图片尺寸的情况也包括 xmax xmin 这种“拖成一条线”的无效标注。bad 列表只打印前 5 条避免在终端刷屏。参数说明expected 字典的值来自摘要统计如果你拿到的版本框数对不上先别急着删数据优先查是不是解压不完整而不是直接怀疑标注质量。另一个检查点是图片尺寸分布。用 Pillow 扫一遍所有 jpg确认分辨率是否统一from PIL import Image import glob sizes {} for img_path in glob.glob(*.jpg): w, h Image.open(img_path).size sizes[(w, h)] sizes.get((w, h), 0) 1 print(sizes)如果尺寸集中在少数几种分辨率说明这批图基本是统一拍摄的如果出现大量不同尺寸训练时 imgsz 的缩放策略要更谨慎。集装箱缺陷检测中一个框覆盖几十到几百像素不等统一 imgsz640 通常够用但前提是原图尺寸不要低于 640 太多。4.2 train/val 划分固定 seed、比例选择与类别平衡第 2 章规整目录时已经按主体名做了随机划分那一版没有考虑类别。遇到 Hole 只有 1218 框的情况我会在体检阶段再做一次分层划分确保验证集里有足够多的 Hole 样本。最常见做法是用 scikit-learn 的 train_test_split 按“图内类别组合”分层from sklearn.model_selection import train_test_split xml_files sorted(glob.glob(*.xml)) y [] for xml_path in xml_files: root ET.parse(xml_path).getroot() classes sorted({o.find(name).text for o in root.iter(object)}) y.append(/.join(classes)) train_files, val_files train_test_split( xml_files, test_size0.2, random_state42, stratifyy ) print(len(train_files), len(val_files))参数说明stratify 接收的是每个样本的类别组合例如 Dent/Hole/Rust 或 Rust这样划分后每个子集里三类缺陷的样本比例与全量大致一致。random_state42 的作用和第 2 章 seed 一样都是为了让复现实验成为可能。如果再加细一点可以按框数而非图数分层但因为一张图可能有多个类别sklearn 的 stratify 不支持多标签所以用类别组合字符串是实际项目里最常见的近似方案。Hole 在验证集里大约只剩 240 框如果验证时 Hole AP 抖动明显可以把 test_size 从 0.2 降到 0.1把更多 Hole 留到训练侧。这和 COCO2017 那种 80 类、各类别分布相对均匀的通用数据集不是一回事工业缺陷数据天然不平衡划分策略要保守。4.3 划分后的映射检查空标签与孤立文件划分完成后最后一个体检项是目录一致性。每次完成新划分我都会跑三行检查comm -3 (ls images/train | sed s/.jpg//) (ls labels/train | sed s/.txt//) comm -3 (ls images/val | sed s/.jpg//) (ls labels/val | sed s/.txt//) find labels -name *.txt -empty | wc -lcomm 输出两边文件名集合的差异正常情况应该是空输出第三个命令统计空标签数量。关于空标签需要区分两种情况本来就没有目标的背景图空标签是合理负样本本来应该标注但漏标的图空标签会变成误导负样本。我会按空标签的主体名去关联 xml人为抽查 20 张如果空 xml 占比超过 5%说明标注阶段漏标严重这种数据要谨慎用来做精度评测训练倒是还能用。这个步骤看起来琐碎但孤立标签在 YOLO 里只会报警告不会中断风险隐藏得很深。5. 避坑与常见问题从 7z 解压到 BN 崩溃的五个高频事故5.1 数据侧7z 报 CRC 错、文件数量对不上、中文路径乱码现象解压到一半报 CRC Failed 或 Unexpected end of data解压完成后 jpg/xml/txt 数量不一致。原因这套数据集是 7z 固实压缩包内文件按块连续存储下载过程中任何字节错误都会影响后续整块数据磁盘余量不足也会让写入中断但 7-Zip 不一定立刻报错。解决先7z t 包名.7z测试完整性输出 Everything is Ok 才继续若测试本身报错直接重新下载不要用修复功能硬救。另一种常见报错是 7z 文件密码正确但解压一直报错这套数据本身没有设密码如果弹密码框说明下载到的文件不对或者压缩工具版本太老认不了新的 7z 头换最新 7-Zip 或 p7zip 16.02 以上版本就好。现象Windows 下用 YOLO 训练提示 FileNotFoundError路径里能看到中文。原因ultralytics 在 Windows 上走的是 OpenCV 和 Python 原生文件接口对中文目录的支持不稳定尤其是包含“集装箱”这种多字节字符的路径经常读图失败但不报错直到某个 epoch 才中断。解决解压后立刻复制到纯英文路径比如D:/datasets/container_defectyaml 中的 path、train、val 都用相对路径。这是工业数据交付里最常见的翻车点我一般拿到任何数据集都先改目录名避免后续玄学问题。5.2 数据侧txt 坐标越界、宽高为 0、或类别顺序错位现象训练 loss 在几十步内掉到 NaN或验证 mAP 恒定 0抽查 txt 发现某行坐标是 nan 或 1.000000。原因转换脚本把(xmin xmax) / 2.0 / width里的 width 误读成 0或 xml 的 size 与图片真实尺寸不一致导致归一化坐标全是 0 或 1。解决用第 4 章的体检脚本把越界框和宽高为 0 的框全部输出发现一个改一个批量改完后再跑一遍。如果坐标不算越界但模型总学不出来看类别顺序import glob, random for txt in random.sample(glob.glob(labels/train/*.txt), 3): with open(txt) as f: lines [line.strip().split() for line in f.readlines()] print(txt, [(int(x[0]), x[1:]) for x in lines])这段脚本随机抽三个 txt输出每行的类别编号和坐标。把它与对应 xml 的 name 字段对照能直接确认 0/1/2 分别对应 Dent/Hole/Rust 中的哪个。多数 mAP 为 0 的案例最后查到的是 txt 里类别编号和 yaml 的 names 顺序对不上而不是网络结构有问题。顺序错位的后果是两类缺陷互相学反val 指标永远上不去。现象图片明明存在但某个 txt 没有对应的图片或者反过来。原因第 2 章脚本跳过缺失图片后train/val 目录里出现孤立标签或孤立图片。解决用 4.3 的 comm 命令查差异把缺图样本从标签目录删掉或者补回对应图。YOLO 加载标签时遇到孤立标签只会警告不会中断这种隐性风险要在进训练前清掉不然验证集里多了一个没有对应图的标签会污染统计。5.3 训练侧BN 崩溃、混淆矩阵“总和不为 1”别慌现象损失曲线在前几个 epoch 突然冲高到数十随后一直 nan。原因标签里存在极端宽高比或坐标越界的框BN 统计量被污染也可能是学习率太高、batch 太小。解决先回到数据侧把越界框过滤干净训练起步参数用 lr00.001、batch16不要一上来就 0.01。如果仍崩溃跑一次带 plots 的训练打开生成的 labels.jpg框的落点全是乱的就是标签错位而不是网络问题。yolo 训练中 BN 崩溃这个关键词被讨论了无数次九成是标签问题一成是学习率问题。现象验证完打开 confusion_matrix.png数值相加怎么都对不上 1怀疑代码算错。原因这张混淆矩阵的纵轴是真实类别横轴是预测类别最后一列代表背景误检矩阵按样本计数而非按行归一化所以各行总和各不相同把整张矩阵加总不等于 100% 是正常现象。解决不要加总矩阵只看对角线。对角线高说明每个类别被正确识别背景列高说明误检多优先提高 confidence 阈值或调 cls 损失权重。遇到“sum 不为 1”先想清楚矩阵坐标轴含义再决定要不要慌张。现象训练到一半模型在 val 上 mAP 一直不动。原因验证集太小或太简单。Hole 只有 1218 框0.8/0.2 划分后 val 里可能只有约 240 个 Hole 框随机波动就能让 AP 跳动 0.05。解决val_ratio 降到 0.1或改用 5 折交叉验证评估最终报告精度时用全量数据跑一次推理人工复核。数据集这一侧能给的坑基本就是这些剩下的是模型侧但模型侧的问题大多能回溯到数据侧。6. 跑通 YOLOv8 训练yaml、增强与混淆矩阵验证的实用流程6.1 一份能直接用的 data.yaml规整后的目录放在工程下data.yaml 按如下写path: ./container_defect train: images/train val: images/val nc: 3 names: 0: Dent 1: Hole 2: Rust这里 names 的顺序必须和第 3 章转换公式里的 class_names 一致。只要 0 对应 Dent、1 对应 Hole、2 对应 Rust整条链路就对齐了。路径都写相对 path 的相对路径方便换机器也避免中文路径问题。6.2 训练命令与增强参数yolo detect train datacontainer_defect.yaml modelyolov8n.pt epochs150 imgsz640 batch16 lr00.001 mosaic0.8 close_mosaic10 cacheTrue参数说明mosaic0.8 表示开启马赛克增强的样本比例close_mosaic10 表示最后 10 个 epoch 关闭马赛克避免 BN 统计值不稳定cacheTrue 是把 4127 张图全部载入内存二次迭代速度提升明显。yolov8n.pt 首次运行会自动拉取预训练参数网络受限时把预训练权重提前放到工程目录即可。Hole 是小目标类可以再加hsv_h0.015 hsv_s0.7 scale0.5增强颜色和尺度多样性但不要调太大集装箱缺陷检测里过度调色会让 Dent 的光影特征失真。6.3 结果验证先看混淆矩阵对角线再人工复核训练结束后打开runs/detect/train/confusion_matrix.png。不要纠结矩阵加起来是不是 1直接看三类缺陷各自的对角线命中率重点观察 Hole 是不是明显低于 Dent 和 Rust。如果是用 best.pt 对 val 目录里的 Hole 样本单独推理一轮命令是yolo detect predict modelbest.pt sourceimages/val save_txtTrue然后读取预测 txt 和原始 xml把两类框画在同一张图上检查漏检是不是集中在暗部阴影区。从那以后我每次换数据集都强制先跑完第 4 章的体检脚本确认三类框数、越界框为空、划分 seed 固定这三件事再进训练这套流程多花十分钟能省掉排查 NaN 和 mAP 为 0 的半天希望帮到你。本文还有配套的精品资源点击获取
返回列表