简介:面向集装箱表面缺陷检测任务,这份数据集包含1476张真实场景图片,覆盖Deframe、Dent、Hole、Rusty、Scratch五类常见缺陷,共计4227个矩形标注框。所有图片已使用labelImg工具完成Pascal VOC与YOLO两种格式的标注,可直接用于训练YOLO系列、Faster R-CNN等目标检测模型,免去人工标注和格式转换的重复工作。资源包为zip压缩格式,共2000个文件,包含1476个xml标注文件与524个txt文本文件,压缩包大小约163.76MB。目前已有494人浏览学习,适合计算机视觉入门者熟悉标注规范,也适合工业质检场景下快速验证缺陷检测方案。下载即可获得带标签的完整数据集,便于开展模型训练、精度对比与算法复现,有效缩短项目前期数据准备周期。
1. 集装箱缺陷检测数据集:1476张5类缺陷,能省掉采集标注的整段路
港口堆场里的集装箱,锈蚀、凹陷、变形这类缺陷,过去基本靠人拿着巡检单挨个核对。一个人一天看几百个箱子,漏检和疲劳是常态,缺陷照片存不下来,后续追溯也很麻烦。这个目标检测集装箱缺陷检测数据集就是为这类场景准备的:1476张真实箱体图像,标注了5类常见缺陷,同时提供VOC和YOLO两种格式。解压之后,你既可以用Annotations配合ImageSets下的清单跑Faster R-CNN那套流程,也可以直接把labels目录交给YOLOv8甚至yolov26去训练,不用自己写标注转换脚本。对正在做缺陷检测算法验证、被数据标注折腾过的工程师来说,这份资源能直接省掉两天采集和格式转换的时间。1476张不算海量,但配合预训练权重和数据增强,跑通一个能用于原型评估的检测模型绰绰有余。
2. 拆解压缩包:VOC与YOLO两套标注的目录结构和坐标换算逻辑
2.1 解压后的目录组织:XML与TXT各归其位
拿到zip先解压,用tree看一眼目录结构就能确认这份资源是「双格式并存」而不是「单一格式转来转去」。这种VOC+YOLO双格式打包的标注数据集,解压后大概率是下面这种布局:
container_defect/ ├── JPEGImages/ # VOC格式:原始图像 │ ├── img_0001.jpg │ └── ... ├── Annotations/ # VOC格式:每张图对应的XML标注 │ ├── img_0001.xml │ └── ... ├── ImageSets/ │ └── Main/ # VOC格式:划分好的训练/验证清单 │ ├── train.txt │ └── val.txt ├── images/ # YOLO格式:与labels同名的原图 │ ├── img_0001.jpg │ └── ... ├── labels/ # YOLO格式:每张图对应的TXT标注 │ ├── img_0001.txt │ └── ... ├── classes.txt # 类别清单,一行一个类别名 └── data.yaml # YOLO训练入口配置各目录的用途整理成一张表,训练时对照着用:
| 目录/文件 | 归属格式 | 作用 |
|---|---|---|
| JPEGImages/ | VOC | 存放原始箱体图像,XML里通过filename引用 |
| Annotations/ | VOC | 每张图对应一个XML,记录类别名和像素坐标 |
| ImageSets/Main/ | VOC | train.txt/val.txt按行列出图片文件名,是VOC训练的划分依据 |
| images/ | YOLO | YOLO训练时直接读取的原图目录 |
| labels/ | YOLO | 与images同名的TXT,内容是归一化后的坐标 |
| classes.txt | 通用 | 类别名清单,YOLO的data.yaml和VOC的class_names都靠它对齐 |
| data.yaml | YOLO | 指向images/labels路径,声明nc和names |
为什么厂商愿意同时给两套格式?因为VOC格式走的是以像素为单位的绝对坐标,适合Faster R-CNN、SSD这类两阶段或基于区域的目标检测框架;YOLO格式则用归一化相对坐标,直接喂给单阶段检测器。很多转换脚本在换算时容易把中心点坐标算错半个像素,或者类别索引从1开始导致训练对不上,这份资源直接给两套,省掉了中间转换的出错环节。
2.2 XML标注文件逐字段解析:object节点与bndbox坐标
打开Annotations目录下任意一个XML,核心结构就是annotation根节点下挂filename、size和一个或多个object节点。下面是一个典型结构,真实类别名以classes.txt为准:
<annotation> <folder>JPEGImages</folder> <filename>img_0001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>defect_class_1</name> <difficult>0</difficult> <bndbox> <xmin>320</xmin> <ymin>128</ymin> <xmax>580</xmax> <ymax>420</ymax> </bndbox> </object> </annotation>这个XML透露的信息比表面看起来多。filename决定了它和JPEGImages里哪张图对应,size里的宽高是整个坐标换算的基准,任何大于宽高的xmax/ymax都说明标注出了问题。object节点代表一个缺陷框,一张图有多个缺陷就挂多个object。bndbox里的四个值分别是左上角x、左上角y、右下角x、右下角y,单位是像素,且xmin < xmax、ymin < ymax是基本约束。
difficult字段值得留意,值为0表示这个缺陷框是清晰可辨认的,值为1表示模糊或争议样本。用这份数据集训练时,是否保留difficult等于1的样本会直接影响模型对难样本的召回,我一般会在转换脚本里加一个开关,先保留全部样本训练一版,再对比去掉难样本后的效果。
2.3 YOLO格式TXT的换算逻辑:中心点坐标与归一化
labels目录下的TXT,每行五个数字:class_id x_center y_center width height,全部是相对于图像宽高的比例值,范围在0到1之间。这段Python展示了从XML到TXT的完整换算过程,排查坐标越界时也能靠它定位问题:
import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, class_names): """把VOC XML转成YOLO TXT行,返回每行字符串列表""" tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find('size/width').text) # XML里的图像宽度 img_h = int(root.find('size/height').text) # XML里的图像高度 lines = [] for obj in root.iter('object'): name = obj.find('name').text.strip() if name not in class_names: continue # 跳过没登记的类别 cls_id = class_names.index(name) # 类别索引,YOLO必须从0开始 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) x_center = (xmin + xmax) / 2 / img_w # 中心点x归一化 y_center = (ymin + ymax) / 2 / img_h # 中心点y归一化 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}") return lines这段脚本的关键点在于除法算的是比例,不是像素差本身。x_center取的是左右边界的中点到图左边缘的比例,w是框宽占整图宽度的比例,所有值都在0到1之间才算合理。class_names.index(name)要求类别清单的顺序和data.yaml里的names严格一致,一旦两边的顺序错位,训练出来的模型预测的类别就是张冠李戴。
拿到这份数据集时,你真正要用的其实是labels目录下已经算好的TXT,上面的脚本只是帮你搞明白坐标是怎么来的,以及越界时该怀疑哪个环节。
3. 训练前的工程准备:数据体检、目录规整与data.yaml编写
3.1 先做一次数据体检:统计类别分布与空标签
我见过太多人拿到数据集直接开训,结果跑了两百个epoch才发现某个类别的样本只有十几张,模型对那类缺陷基本瞎猜。1476张图分5类,平均下来每类不到300张,但真实分布一定不平均。所以第一步是统计类别分布和空标签文件:
import os from collections import Counter labels_dir = 'labels' cls_counter = Counter() empty_files = [] for file in os.listdir(labels_dir): if not file.endswith('.txt'): continue path = os.path.join(labels_dir, file) with open(path) as f: lines = [ln.strip() for ln in f if ln.strip()] if not lines: empty_files.append(file) # 空标签文件会导致训练时样本被丢弃 continue for line in lines: parts = line.split() if len(parts) == 5: cls_counter[int(parts[0])] += 1 else: print(f"{file} 有格式异常的行: {line}") print("类别分布:", dict(cls_counter)) print("空标签文件数:", len(empty_files), empty_files[:10])这段脚本干了两件事:统计每个类别的标注框数量,找出空标签和格式异常的文件。cls_counter的键是类别索引,如果发现索引5出现但nc只设了5,说明labels里混入了越界索引,训练会直接报错。parts的长度必须等于5,多一个少一个都说明该行数据损坏。
顺便用PIL确认一下图片格式和尺寸分布,避免有灰度图和RGB图混在一起。集装箱图片多数是RGB三通道,但偶尔会混入某些设备输出的单通道灰度图,YOLO训练时对通道数不一致的输入会报错。
3.2 目录规整:把图片和标签按train/val归档
YOLO训练推荐的结构是images/train配labels/train,文件名一一对应。如果压缩包里已经排好了,直接跳过这步;如果没有,就按ImageSets/Main里的清单把文件分拣开。常见的做法是写一个bash循环:
# 创建训练和验证的图片、标签目录 mkdir -p container_defect/images/train container_defect/images/val mkdir -p container_defect/labels/train container_defect/labels/val # 按ImageSets/Main/train.txt清单拷贝训练集 while read img; do cp "$img" container_defect/images/train/ base=$(basename "$img" .jpg) cp "labels/${base}.txt" container_defect/labels/train/ done < ImageSets/Main/train.txt # 验证集同理,把ImageSets换成val.txt,目标目录换成val这段命令的核心是$(basename "$img" .jpg),它把JPEGImages/img_0001.jpg转成img_0001,这样labels/img_0001.txt才能精确匹配。注意图片后缀可能是.JPG或.png,basename时后缀要写对,否则找不到对应的标签文件。拷贝完成之后检查一下labels/train里的文件数量是否和images/train一致,差一个都要回去查。
3.3 编写data.yaml:路径、nc与names一个都不能错
YOLO训练全靠data.yaml指路,路径错了报错信息还特别迷惑。以这份数据集为例,data.yaml长这样:
# 数据集根目录,建议写绝对路径 path: /home/yourname/container_defect train: images/train # 训练图像目录,相对于path val: images/val # 验证图像目录,相对于path nc: 5 # 类别数,必须和labels里的最大索引+1相等 names: 0: defect_class_1 1: defect_class_2 2: defect_class_3 3: defect_class_4 4: defect_class_5nc和names是这里最容易出错的两个字段。nc一定要等于labels里实际出现的最大类别索引加1,如果labels里出现了索引5但nc只写了5,训练时加载标签会直接崩。names的顺序必须和转换脚本里的class_names.index(name)顺序一致,否则模型推理输出的类别名全是错的。我习惯把classes.txt放在data.yaml旁边,每次训练前用一行Python对比两边内容,确保没有遗漏。
4. YOLO训练实操:从命令参数到训练曲线判断
4.1 训练命令与关键超参数:epoch、imgsz、batch怎么设
环境配好之后,训练命令本身不复杂,一行就能起来:
cd container_defect yolo detect train data=data.yaml model=yolov8n.pt epochs=120 imgsz=640 batch=16 device=0model=yolov8n.pt表示用COCO预训练权重做迁移学习初始化。这份数据集只有1476张图,从零训练肯定不现实,用预训练权重可以大幅缩短收敛时间。epochs我先给120,数据集小,一般到不了120就会收敛,配合早停回调可以自动截断。imgsz=640是YOLOv8的默认推理尺寸,集装箱缺陷尺寸跨度大,640能兼顾速度和精度。batch=16在显存足够的前提下比较稳妥,如果你只有8G显存,降到8也能跑。
各参数的调整原则整理成表:
| 参数 | 建议值 | 说明 |
|---|---|---|
| model | yolov8n.pt | 先用nano验证流程,再换s/m提升精度 |
| epochs | 100-150 | 小数据集,关注验证集mAP是否先升后平 |
| imgsz | 640 | 默认值,缺陷小就改736,但显存占用增加 |
| batch | 16或32 | 受显存限制,16G显存以下用16 |
| device | 0 | 单卡GPU训练,CPU训练慢到不可用 |
用yolov26相关项目的源码跑这份数据也一样,代码结构里data.yaml、labels目录这些入口都是通用的,区别只在于配置文件字段可能有细微差异,具体以你用的那份源码为准。
4.2 观察训练曲线:loss下降和mAP走势说明什么
训练开始后,我会盯着两个东西:训练日志里的box_loss、cls_loss,以及每个epoch结束打印的mAP50。正常情况下box_loss应该从1.5附近稳步下降到0.5以下,mAP50从0开始爬坡,在早期epoch提升最快,后期逐渐走平。
如果box_loss出现断崖式下跌后长期不降,大概率是学习率设置导致模型陷入局部最优,常见做法是把学习率从默认的0.01降到0.001重新跑。如果mAP50一直上不来且低于0.3,先别急着调参,回头检查labels里的坐标是否越界,或者是否存在空标签文件拖了后腿。训练日志不是玄学,每条曲线的走势都对应数据或参数的一个具体问题。
另外,建议在训练配置里开启save=True和plots=True,这样每个epoch结束会把验证集的预测图存到runs/detect/train目录。直接翻预测图比看数字直观得多,看到集装箱表面被画上乱七八糟的框,基本可以判断是标注坐标的问题而不是模型的问题。
4.3 推理验证与导出:用训练好的权重跑没有见过的图片
训练结束后,runs/detect/train/weights/下会有best.pt和last.pt两个权重。best.pt是在验证集上mAP最高那轮的权重,推理验证用它:
yolo detect predict model=runs/detect/train/weights/best.pt source=JPEGImages/img_0100.jpg conf=0.25 save=Trueconf=0.25表示置信度阈值,低于25%的框会被过滤掉。集装箱场景我一般先用0.25看整体效果,再降到0.1检视被漏掉的低置信度框,判断是模型置信度不足还是标注本身就漏了。save=True会把画了框的结果图存到runs/detect/predict目录下。
推理结果里除了图片,还会看到一个labels.txt,里面记录了每张图检出的类别和置信度。我会把这些结果和Annotations里的真值做一次逐图对比,挑出那些模型明显检错或漏检的图,作为后续数据清洗和补充标注的依据。
5. 避坑:数据集落地过程中最常见的5个坑
5.1 现象:训练日志里nc=10,明明数据集只有5类
第一次用这份数据集训练时,日志里显示的类别数翻了一倍,还伴随大量WARNING: class not found之类的提示。原因几乎都是类别名大小写不一致,比如XML里同一类缺陷有时写Rust,有时写rust,转换成TXT时被当成了两个独立类别。解决方法是写一个脚本统一类名大小写,以classes.txt为准做一次全局替换,然后重新生成labels。从那以后我拿到任何数据集,都会先统计labels里的类别索引集合,确保和期望值完全一致再开训。
5.2 现象:YOLO格式坐标出现大于1或小于0的越界值
训练时弹出Box coordinates out of bounds的警告,用脚本扫了一遍labels目录,发现个别TXT行里出现了x_center=1.0342这样的越界值。原因是XML里标注框的xmax大于图片宽度,转换时没有做截断处理,导致归一化后坐标超出0到1的范围。解决方法是写脚本过滤并修正越界行,把坐标clip到0到1之间,同时回去检查对应的XML和图像是否错位。更稳妥的做法是直接在转换函数里加一个if xmax > img_w: xmax = img_w的边界保护。
5.3 现象:训练时提示Found 0 images,路径指向不存在
最常见的情况是data.yaml里的path用了相对路径,而训练命令的执行目录和数据目录不在同一层。YOLO在找不到图片时只给一句笼统的Found 0 images,非常迷惑。解决方法是把path改成数据集根目录的绝对路径,并且确认train和val字段是相对于path的相对路径,不是从根目录开始的绝对路径。我在配环境时发现,Windows机器上还要额外注意路径分隔符和盘符写法,Linux上直接/home/user/...最省心。
5.4 现象:换模型后推理类别与标签对不上
同一个数据集换了个模型架构训练,推理结果里类别名全是错位的。原因是不同框架的类别索引规则不一致,YOLO从0开始,某些框架从1开始,而data.yaml或classes文件没有跟着调整。解决方法是冻结类别定义文件,数据集的classes.txt作为唯一事实来源,任何转换脚本都从它读取类名和顺序。我在团队里的习惯是:每个数据集包自带一份classes.txt,建模时所有人引用同一个文件,谁都不许手写类别列表。
5.5 现象:某一类缺陷mAP50始终很低甚至为0
训练完查看mAP50按类别的明细,发现某一类缺陷几乎完全检不出。打开标签统计发现只有十几个样本,且这类缺陷的框特别小,模型在640分辨率下根本学不到有效特征。解决方法是针对性地做数据增强,把包含该类缺陷的样本做随机裁剪放大再复制进训练集,或者直接对该类样本加权训练。1476张图分5类,天然存在不均衡,应该在一开始的数据体检阶段就标记出来,不要等训练完再追悔。
6. 验证数据集质量的实用技巧:用物理解释反向核对标注与模型输出
拿到一份数据集,除了信它的标注,我更相信数据和物理规律可以互相印证。集装箱缺陷的类型不同,在图像上的几何特征也不同。比如大面积的锈蚀在标注框里应该占到一个相对大的区域,裂纹类缺陷的框则应该是高宽比极大的细条。如果统计出来的面积分布违背这个直觉,就要警惕标注质量或类别语义混淆。
我先用下面的脚本统计每个类别标注框的面积占比分布,把异常的框筛出来:
import os import numpy as np labels_dir = 'labels' area_ratio = {i: [] for i in range(5)} # 5类,按实际类别数调整 for file in os.listdir(labels_dir): if not file.endswith('.txt'): continue with open(os.path.join(labels_dir, file)) as f: for line in f: parts = line.split() if len(parts) != 5: continue cid = int(parts[0]) w = float(parts[3]) # 归一化宽度 h = float(parts[4]) # 归一化高度 area_ratio[cid].append(w * h) # 框面积占整图面积比例 for cid, ratios in area_ratio.items(): arr = np.array(ratios) print(f"类别{cid}: 样本数={len(arr)}, 平均面积占比={arr.mean():.4f}, " f"最小={arr.min():.4f}, 最大={arr.max():.4f}")框面积占比能暴露两类问题:一类是面积占比过小,比如平均值小于0.01,说明标注的缺陷在整图中只占极小区域,训练时容易被下采样吞掉;另一类是某一类别面积占比分布特别散,可能把不同物理形态的缺陷混成了同一类。统计之后,我会挑出面积占比最小的几十个框,把对应的原图裁剪出来逐张看,确认是本就微小的缺陷还是标注漏了周边区域。
模型训练完之后,还有一步验证值得做:用conf=0.1跑一遍验证集,把低置信度的预测框和真值框叠在同一张图上输出。低置信度框集中出现在哪类缺陷、漏检集中在哪张图,这些信息比mAP数字更能指导数据集优化。如果低置信度框都堆在某些光照暗的角落,说明数据集的成像条件单一,需要补充这类样本;如果漏检全集中在一个类别,说明该类别特征没学好或者样本太少。
从那以后我每次拿到数据集,都强制走一遍面积统计和推理可视化,再开始调参。这个习惯帮我挡住了好几套标注质量有问题的数据,省下的时间远超多花的半小时。希望帮到你。
本文还有配套的精品资源,点击获取