集装箱缺陷检测数据集VOC转YOLO全流程:从格式校验到切块推理

📅 发布时间:2026/10/14 9:21:31
集装箱缺陷检测数据集VOC转YOLO全流程:从格式校验到切块推理
简介集装箱缺陷检测数据集围绕Dent凹痕、Hole孔洞、Rust锈蚀3类缺陷提供4127张图像对应的标注数据可直接用于YOLO、Faster R-CNN等目标检测模型训练适用于工业质检、港口设备巡检及运输安全监控等场景。压缩包采用7z格式共包含2000个文件其中1999个为VOC格式的xml标注文件另有1个txt使用说明文件整体大小约163.61MB。三类缺陷累计10117个目标框Dent、Hole、Rust分别包含4943个、1218个和3956个框均由labelImg以矩形框形式完成标注。数据同时提供Pascal VOC和YOLO两种格式用户无需额外转换即可接入常用训练脚本提高数据准备工作效率。已有348人学习下载这份数据适合从事集装箱外观检测、目标识别方向的开发者与研究人员参考使用。1. 集装箱缺陷检测数据集VOCYOLO格式4127张3类别.7z下载之后先别急着训练集装箱表面缺陷检测在码头、堆场和铁路运输里都很常见。一个集装箱长期日晒雨淋锈蚀、变形、破损会逐渐冒出来人工巡检效率低而且容易漏。做数据驱动方案最头疼的是标准数据要拍不同光照下的箱体还要逐张框出缺陷。所以看到“集装箱缺陷检测数据集VOCYOLO格式4127张3类别.7z”这样的文件第一反应都是下载、解压、开训。我在某公司的模拟项目X里碰到过几乎一样的数据包当时没看类别顺序就直接丢进训练脚本一晚上 loss 都不降。后来发现是 VOC 标注转 YOLO 时坐标归一化和类别 id 全乱了和模型本身没关系。这篇笔记就把这个数据集的完整链路拆开讲文件结构怎么认、VOC 怎么转 YOLO、训练前要校验什么、常见坑在哪。适合正在做集装箱或钢结构表面缺陷检测的工程师也适合刚入门 YOLO 但被数据格式反复折腾的从业者。4127 张图不算大但对缺陷检测场景足够做一次完整的迁移学习前提是数据管道先走对。2. 拆开 .7z 之后集装箱缺陷数据集的文件结构、类别口径与适用模型先花 10 分钟把目录结构看明白能省掉后面一整天的排错时间。多数公开缺陷检测数据集会按下面这种常见布局组织文件解压后先不要急着跑训练脚本。2.1 文件结构一个 7z 压缩包里到底装了什么用 7-Zip 解压后通常能看到 Annotations、JPEGImages、labels、ImageSets 这几个目录以及 classes.txt 和 data.yaml。它们各管一摊Annotations 里是 VOC 风格的 xml 标注JPEGImages 是原始图像labels 是 YOLO 训练实际读取的 txt 标注。ImageSets/Main 里一般放着 train.txt、val.txt 这类划分文件data.yaml 是给 YOLO 用的数据配置入口。. ├── Annotations/ # VOC 风格的 xml 标注 ├── JPEGImages/ # 原始图像jpg 或 png ├── labels/ # YOLO 风格的 txt 标注 ├── ImageSets/ │ └── Main/ # train.txt / val.txt 划分 ├── classes.txt # 类别顺序 └── data.yaml # 给 YOLO 用的数据配置看到这个结构先做一件事统计三个目录里的文件数量是否一致。因为我遇到过好几次数据集打包时 labels 目录是空的需要用户自己转换不检查就直接训练程序会一直报错找不到对应标注。find Annotations -name *.xml | wc -l find JPEGImages -name *.jpg -o -name *.png | wc -l find labels -name *.txt | wc -l三行命令分别数出 xml、图片、txt 的数量。如果数量对不上基本可以断定 labels 缺失或格式不统一需要走第 3 章的转换流程。data.yaml 里的 nc 和 names 也要重点看不少数据集 labels 里的类别编号不是从 0 开始而是从 1 开始这种细节最容易被忽略。2.2 三个类别到底代表什么先读 classes.txt 再做任何事集装箱缺陷怎么分类不同数据集的粒度完全不同。有的把锈蚀、变形、破损分三类有的把裂纹、脱落、异物分三类。拿到压缩包后第一件事是执行下面这条命令看清楚三类缺陷在数据提供方那里的语义。cat classes.txt看到输出后要确认的不是类别名好不好记而是这个顺序会不会影响后面的 id 映射。YOLO 的标签 txt 里每一行第一个数字就是类别 id它直接对应 classes.txt 的下标 0/1/2。只要之前有人动过类别顺序txt 和 xml 里的 name 就对不上训练出来的模型再怎么调参也没用。训练之前我会写一行小脚本把 xml 里的 name 全部抓出来和 classes.txt 对比10 秒出结果能省掉很多后续麻烦grep -h name Annotations/*.xml | sort | uniq -c-h表示不打印文件名输出到屏幕就是纯类别数量sort是为了让相同的 name 聚在一起uniq -c才能正确计数。如果输出的类别名和 classes.txt 里的列表对不上说明标注噪声大或转换脚本有问题得先处理再往下走。2.3 4127 张图适合接哪一种检测网络4127 张对目标检测来说属于中等偏小规模。工业缺陷和自然场景不一样同一类缺陷长得很像箱体表面的纹理又特别丰富模型很容易把纹理当成缺陷所以网络选择上有讲究。我一般会做这样的选型对比方案适用情况对集装箱缺陷的说明YOLO 轻量版快速验证、显存紧张收敛快小目标表现一般可用数据增强补YOLO 中型追求 mAP、显存够用对小缺陷更友好训练时间长一点Faster R-CNN已有 VOC 标注、想对比小目标检测稳但推理慢部署麻烦SSD不推荐对小缺陷明显吃亏边框回归弱4127 张图完全可以先用 YOLO 轻量版跑通因为这个规模的数据撑不起大模型大模型反而容易过拟合。但如果显卡显存允许把输入分辨率从 640 提到 960 会比换更大模型更有效。集装箱缺陷里不少是小锈斑和细裂纹图像分辨率才是真正的瓶颈。3. 把 VOC 转成 YOLO 格式转换脚本、坐标归一化与类别 id 映射VOC 的 xml 标注是给人看的YOLO 的 txt 是给训练循环吃的两者完全不兼容。这是整个数据集使用中最容易翻车的一步值得单独花一章说透。3.1 为什么 VOC 里的 xml 不能直接喂给 YOLOVOC 的标注是绝对像素坐标xmin、ymin、xmax、ymax单位是像素。YOLO 要的是归一化相对坐标中心点 cx、cy 以及宽高 bw、bh取值都在 0 到 1 之间。假设一张图宽 w1920高 h1080标注框是 xmin100, ymin120, xmax700, ymax800。转换公式是cx 等于 xmin 加 xmax 的和除以 2 再除以图宽cy 同理用图高框宽等于 xmax 减 xmin 除以图宽框高用 ymax 减 ymin 除以图高。这几个换算必须写对否则训练时检测框会全部错位。更隐蔽的问题在 txt 格式YOLO 要求每行是“class_id cx cy bw bh”中间用空格分隔一旦混入逗号训练程序直接报错。3.2 一个不依赖外部库的 VOC2YOLO 转换脚本转换脚本用 Python 标准库就能写不需要装额外依赖。下面的脚本只依赖xml.etree.ElementTree和pathlib在任何 Python 3 环境都能跑import os import xml.etree.ElementTree as ET from pathlib import Path def voc2yolo_one(xml_path, out_dir, class_list): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_list: continue class_id class_list.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h lines.append(f{class_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) txt_name Path(xml_path).stem .txt out_path Path(out_dir) / txt_name out_path.write_text(\n.join(lines), encodingutf-8)核心逻辑是读取 xml 里的原始图像宽高把 bndbox 的四个像素坐标换算成归一化的中心点坐标和宽高。每个对象生成一行写入和 xml 同名的 txt。class_list.index(name)这一步决定了类别 id所以 class_list 的顺序必须和 classes.txt 完全一致不要按自己的习惯排序。参数说明:.6f是保留 6 位小数对归一化坐标够用。框很小时精度不够会导致过小目标被训练程序忽略后面避坑章会详细讲。continue分支用于跳过 class_list 里没有的类别避免把数据提供方遗漏的脏标签带进训练。3.3 先跑通一张再批量转换整个目录不要一上来就全量转换。我习惯先挑一张 xml 手动跑输出一个 txt 后立刻打开看内容确认格式正确再上批量脚本。cat labels/xxx.txt看到类似“0 0.312 0.544 0.215 0.093”的一行再批量。批量时建议把异常单独收集避免中途退出跑一半# 批量转换并记录失败的 xml failed [] for xml_file in sorted(Path(Annotations).glob(*.xml)): try: voc2yolo_one(xml_file, Path(labels), class_list) except Exception as e: failed.append((xml_file.name, str(e))) print(ffailed: {len(failed)}) if failed: print(failed[:10])try/except 的作用是让单个坏 xml 不至于中断整个转换流程。失败的 xml 名和错误信息会打印前 10 条方便快速定位是命名空间问题还是缺字段。3.4 反向验证把 YOLO 的 txt 坐标画回图片转完不是结束。随机抽 3 到 5 张图做反向验证把归一化坐标换算回像素并画框框应该紧紧贴住缺陷区域。import cv2 def check_txt(image_path, txt_path, class_list): img cv2.imread(str(image_path)) h, w img.shape[:2] with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls, cx, cy, bw, bh parts cx, cy, bw, bh map(float, (cx, cy, bw, bh)) xmin int((cx - bw / 2) * w) ymin int((cy - bh / 2) * h) xmax int((cx bw / 2) * w) ymax int((cy bh / 2) * h) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2) cv2.imwrite(check_output.jpg, img) check_txt(JPEGImages/xxx.jpg, labels/xxx.txt, class_list)这段把归一化坐标反算回像素坐标再画出框。如果所有框都偏到左上角或右下角多半是除以宽高时用错了尺寸。这里用了 OpenCV 做可视化验证转换脚本本身只依赖标准库。4. 训练前的数据划分与标签校验让 YOLO 吃上干净标签很多人拿到数据集直接开训其实还差一步划分和校验。这一步决定训练集和验证集之间有没有污染也决定 loss 曲线是不是真的在学东西。4.1 正确的划分方式按图像划分不要按框划分4127 张图如果按标注框数量去随机划分同一张图的几个缺陷框可能一部分进训练集、一部分进验证集。模型相当于在考场上见过原题验证集的 mAP 会虚高上线后立刻现原形。正确做法是先把图像文件名列表打乱再按比例切三份ls JPEGImages | sed s/\.[^.]*$// | shuf all_ids.txt wc -l all_ids.txt head -n 3300 all_ids.txt train_ids.txt sed -n 3301,3800p all_ids.txt val_ids.txt sed -n 3801,4127p all_ids.txt test_ids.txt第一行把 JPEGImages 里的文件名后缀去掉得到纯 id 列表并打乱。后面三行按数量切成训练、验证、测试三份。3300 张训练、500 张验证、327 张测试是按 8:1:1 左右的示例比例可以按实际需求调整。切好 id 后对 YOLO 训练还要生成图片路径列表。很多 YOLO 版本支持在 data.yaml 里给一个 txt里面每行是图片绝对路径sed s|^|/path/to/JPEGImages/|; s|$|.jpg| train_ids.txt train_paths.txt注意图片后缀到底是 jpg 还是 png以 JPEGImages 目录里的实际文件为准写错了路径程序不会报错只是加载的图片全部失败。4.2 小缺陷目标的一图多框先统计目标尺寸再定训练参数集装箱缺陷里常见一图多框比如一整块锈蚀被切成几个邻近框。训练时很容易遇到两种情况目标太小被忽略框太密导致 NMS 后大量重叠。YOLO 训练默认会忽略在输入分辨率下过小的真实目标如果缺陷框平均面积很小要先看数据统计import numpy as np from pathlib import Path sizes [] for txt in Path(labels).glob(*.txt): for line in txt.read_text().strip().splitlines(): parts line.split() if len(parts) 5: continue _, _, _, w, h parts sizes.append(float(w) * float(h)) arr np.array(sizes) print(fmin{arr.min():.6f} median{np.median(arr):.6f})这个脚本统计归一化面积的分布。在 640×640 分辨率下一个 0.005 归一化面积的框大约是 45×45 像素还能处理低于 16×16 像素的框默认超参数会直接忽略。遇到这种情况优先提高输入分辨率而不是盲目加深模型。4.3 三件套校验图片、xml、txt 三向一致转换完并不能保证每个 xml 都正确生成了 txt也不能保证每张图都有标注。训练前我习惯跑一个校验脚本确保三套文件一一对应from pathlib import Path img_dir Path(JPEGImages) xml_dir Path(Annotations) txt_dir Path(labels) imgs {p.stem for p in img_dir.iterdir() if p.suffix.lower() in {.jpg, .png}} xmls {p.stem for p in xml_dir.iterdir() if p.suffix .xml} txts {p.stem for p in txt_dir.iterdir() if p.suffix .txt} print(missing xml:, len(imgs - xmls)) print(missing txt:, len(imgs - txts)) print(txt without img:, len(txts - imgs)) empty_txts [p for p in txt_dir.iterdir() if p.stat().st_size 0] print(empty txt:, len(empty_txts))集合差imgs - xmls表示图片里有但 xml 里没有的文件名。空 txt 出现的原因通常是 xml 里所有 object 的 name 都不在 class_list 中被转换脚本里的 continue 跳过了需要回看 xml 标注内容。5. 集装箱缺陷检测训练避坑5 条常见翻车记录与排查顺序5.1 训练程序启动后卡在数据集加载路径与 data.yaml 的坑现象启动训练后日志一直提示数据集找不到或者直接退出界面上没有任何 loss 输出。原因多半是 data.yaml 里的路径是相对路径而训练脚本是在项目根目录执行的不同系统下路径分隔符不兼容也会导致加载失败。解决data.yaml 里的路径尽量写绝对路径并检查文件名后缀是否和解压目录一致。快速验证一条路径python -c from pathlib import Path; pPath(/path/to/JPEGImages/xxx.jpg); print(p.exists())能打印 True 再开机训练这一步 30 秒排除主要路径问题。5.2 loss 始终不下降在 7 附近徘徊类别 id 错位现象训练跑了几十个 epochloss 数值停在 7 左右验证集画出来的框完全乱套。原因训练是一个多类别分类加回归的任务。当 txt 里的类别 id 和 data.yaml 的 names 错位模型一直在学错误映射loss 就压不下去。常见场景是别人给的 txt 类别从 1 开始计数而你用的是从 0 开始的 names。解决检查 labels 里第一个数字的分布awk {print $1} labels/*.txt | sort -n | uniq如果输出里出现 3 或负数说明标签生成时类别编号越界了。重新走一遍第 3 章的转换脚本确保 class_list 的顺序和 classes.txt 一致。5.3 训练集能拟合验证集 mAP 特别低小目标直接消失现象训练 loss 正常下降但 mAP 始终是个位数画出来的预测框全部集中在大目标上。原因集装箱缺陷里的小锈斑和细裂纹在 640×640 输入下可能只有几个像素宽。YOLO 对小于特征图步长的目标不敏感训练默认又会忽略过小的真实框。解决把输入分辨率提到 960 或 1280先不要叠模型深度。同时参照第 4.2 节的目标尺寸统计结果如果归一化面积中位数小于 0.0001提升分辨率比换任何模型都有效。数据增强里也要谨慎使用过强的 mosaic因为它会进一步缩小目标。5.4 转换出来的 txt 部分为空文件xml 命名空间或字段缺失现象转换脚本跑完labels 目录里有一部分 txt 是 0 字节甚至有些 xml 解析直接失败。原因xml 可能带了命名空间用 find(object) 找不到节点或者 bndbox 里缺少某个坐标字段取值是空的。解决给转换脚本加一层容错解析节点时用 local_name 处理命名空间def local_name(tag): return tag.split(})[-1]转换前先快速找出不含object的 xmlgrep -L object Annotations/*.xml-L直接输出不含object的文件名这些文件要么是漏标要么是格式与预期不同先人工看一眼比脚本硬扛要快。5.5 类别不均衡严重某类缺陷一个都测不出来采样与增强策略现象验证集里多数类的 mAP 还可以少数类基本为 0训练集里两类样本数量差一个量级。原因集装箱缺陷数据集中不同类别数量天然不均衡。锈蚀类可能占七成变形或裂纹类只占很小一部分模型把训练注意力全放在多数类上。解决先统计类别分布cut -d -f1 labels/*.txt | sort | uniq -c | sort -rn多数类降采样会丢数据更稳的办法是对少数类做针对性增强。裂纹类对翻转不敏感可以多开水平垂直翻转锈蚀类不要加太多颜色抖动。也可以在训练配置里给少数类更高的类别权重。别指望普通随机采样能自动平衡数据集的类别分布不改变模型就学不会少数类。6. 进阶用切块推理提升小缺陷检出率一个值得立刻试的技巧集装箱缺陷检测项目经常卡在“大图上的小缺陷”。整张箱体照片可能有 4000×3000 像素缩到 640 输入后一个 20 像素的裂纹直接变成 3 像素后处理再强也救不回来。与其去调 anchor 参数不如先试切块推理推理时把原图切成若干重叠的小块每个块独立过模型最后把检测框映射回原图坐标再合并。效果经常比换模型还明显。def tiling_infer(image, model, tile_size960, overlap64): h, w image.shape[:2] dets [] for y0 in range(0, h - overlap, tile_size - overlap): for x0 in range(0, w - overlap, tile_size - overlap): tile image[y0:y0 tile_size, x0:x0 tile_size] result model(tile) for box in result.boxes: cls int(box.cls) cx, cy, bw, bh box.xywhn.tolist()[0] x1 (cx - bw / 2) * tile_size x0 y1 (cy - bh / 2) * tile_size y0 x2 (cx bw / 2) * tile_size x0 y2 (cy bh / 2) * tile_size y0 dets.append((x1, y1, x2, y2, cls, float(box.conf))) return dets外层两个循环负责在大图上按固定步长滑动步长是 tile_size 减 overlap保证相邻块有重叠区域。每个 tile 独立推理后把归一化坐标乘 tile 尺寸再加左上角偏移就得到原图坐标。最后对所有检测结果做一次 NMS把重叠边界上的重复框合并掉。tile_size 建议取 960太小会切碎大缺陷太大失去切块意义。overlap 取 64 到 128太小边缘目标会被截断太大推理重复计算多。映射回原图时注意 tile 左上角坐标 x0/y0 的基准这是切块推理最容易出错的点。为什么这个技巧值得先试它完全不改变训练数据也不需要重新训练模型只是把推理阶段的输入方式改一下。很多集装箱缺陷检测项目的落地难点不在模型结构而在缺陷尺寸和多尺度变化上切块推理正好能顺带解决这两点。训练时也可以配合使用把 4127 张图按切块方式扩增成更多小图样本但要注意避免同一切片在不同 epoch 被重复增强。我现在的习惯是每次拿到新数据集先跑一遍第 4.3 节的完整性校验再统计一次目标尺寸分布接着就用切块推理做一轮快速验证。这个顺序帮我少走了很多弯路至少避免了“模型换了好几个最后发现是数据问题”的尴尬。如果你正在跟集装箱缺陷检测数据集较劲先把这三件套做好再考虑调网络结构希望帮到你。本文还有配套的精品资源点击获取