简介一套面向集装箱表面缺陷检测的标注数据集为工业质检与计算机视觉目标检测场景提供基准数据。数据覆盖凹坑、孔洞、锈蚀三类缺陷对应4127张图片的标注信息标注框总数达10117个其中凹坑框4943个、孔洞框1218个、锈蚀框3956个每类缺陷均有独立标签便于分类训练与评估。压缩包共2000个文件核心为1999个XML标注文件另附TXT说明文档整体大小约163.61MB文件结构清晰。每个缺陷目标均采用labelImg工具以矩形框精确标注标注规则统一边界框坐标与类别信息完整、格式标准可直接转换为YOLO、Faster R-CNN等主流检测模型所需的训练样本。已有348人学习适合需要快速获取高质量表面缺陷数据集的算法研究者与工业视觉开发者可有效缩短数据采集与标注周期。1. 集装箱缺陷检测数据集压缩包里的双格式标注到底怎么用4127张图、3个类别、VOC和YOLO双格式、一个7z压缩包——这个数据集的标题已经把你要做的事基本写完了解压、看标签、喂给YOLO训练。集装箱缺陷检测在港口、物流园、堆场视觉项目里是典型的高频需求做安全检测、箱体损伤定损、自动化验箱的团队都绕不开它。这个包的价值在于同时给了VOC和YOLO两种标注意味着你既可以用YOLO系列快速出基线也能用VOC格式转成Faster R-CNN、SSD或mmdetection的输入不用自己重新标一张图。适合谁用手里没有集装箱图片的算法工程师想验证YOLO训练流程的学生以及需要快速给客户出demo的交付团队。要注意一点数据集的3个类别具体是哪三种缺陷解压后以labels目录里的类名或data.yaml为准不同打包者的命名习惯差别很大别拿到手就按经验猜。下面从格式解读开始一步步把它变成能正常跑训练的工程目录。2. 数据集里有什么VOC与YOLO标注格式的差异和选型2.1 4127张样本是什么规模单类还是多场景4127张在工业缺陷检测里属于中等偏小的规模。对比公开数据集COCO有十几万张但集装箱缺陷这种垂直场景能凑到四千多张已经能覆盖常见锈蚀、破损、变形等表现。关键是这个数量决定了训练策略直接随机初始化训练很难收敛必须用预训练权重做迁移学习数据增强的强度要比通用检测大一些。这个体量也暗示了类别分布大概率不均衡。工业采集中大面积锈蚀容易拍到小目标的开焊、凹坑往往只有几十个像素框的数量可能相差十倍。拿到包后第一件事不是训练而是统计每个类别的标签数量。常见做法是在labels目录下执行统计命令for i in 0 1 2; do count$(grep -h ^$i labels/*.txt | wc -l) echo class $i: $count done这段命令遍历0、1、2三个类别id在labels目录的所有TXT文件里统计以对应数字开头的行数。TXT每行代表一个标注框行首数字就是类别id。如果输出的三个数字差距超过5倍后面训练时要考虑类别加权否则样本多的类会垄断loss。执行前注意把labels路径换成你解压后的实际路径。2.2 VOC格式目录结构与XML标注字段解读VOC格式的结构固定解压后通常能看到JPEGImages、Annotations、ImageSets三个目录。JPEGImages放原图Annotations放同名XML标注文件ImageSets/Main里是train.txt、val.txt这类划分文件内容是图片文件名不带后缀的列表。这套结构从PASCAL VOC沿用至今很多检测框架可以直接读取。XML文件里最关键的是object节点每个object对应一个标注框。拿一个集装箱缺陷样本举例annotation size width1920/width height1080/height depth3/depth /size object namedent/name difficult0/difficult bndbox xmin152/xmin ymin410/ymin xmax640/xmax ymax720/ymax /bndbox /object /annotationsize节点下的width和height是图像原始尺寸bndbox里的四个值分别是框的左上角x、左上角y、右下角x、右下角y单位是像素。注意XML里没有归一化坐标值直接对应原图像素。判读时有一个常见误区xmin/ymin不一定是框的最小值如果标注工具输出不规范可能出现xminxmax的情况训练前要做一次交换或过滤。difficult字段表示该样本是否难以识别部分框架会忽略difficult1的样本。2.3 YOLO格式目录结构与TXT归一化坐标换算YOLO格式是另一个世界。目录结构通常是images和labels两个平级目录图片放在images同名TXT放在labels。TXT每行五个数字第一个是类别id从0开始后面四个是归一化坐标。一行代表一个框0 0.412 0.410 0.254 0.287四个数字依次是框中心x、中心y、框宽、框高全部除以图像宽高做了归一化取值在0到1之间。所以如果你的图像是1920×1080这个框对应的像素坐标是中心点x0.412×1920≈791中心点y0.410×1080≈443宽0.254×1920≈488高0.287×1080≈310。VOC和YOLO之间的换算关系可以记成一组公式cx (xmin xmax) / 2 / width cy (ymin ymax) / 2 / height w (xmax - xmin) / width h (ymax - ymin) / height反过来从YOLO转VOC也是同一组公式逆推。很多数据集打包者会同时提供两种格式但要注意两个目录如果分开存放训练时只喂YOLO格式即可VOC格式留作标注审查——因为XML可读性更好发现TXT有异常时对照XML更容易定位是哪张图、哪个框出了问题。不要同时把两种格式的标签都放进一个训练目录YOLO训练流程只会读取与图片同名的TXT文件多余的XML不会报错但会干扰你排查。3. 解压7z压缩包三平台落地命令与文件完整性校验3.1 Linux、macOS、Windows解压7z的最小命令7z不是系统自带的压缩格式三平台都需要先装工具。Linux下Debian/Ubuntu用apt装p7zip-fullCentOS/RHEL用yum装p7zip和p7zip-pluginssudo apt install p7zip-full # Debian / Ubuntu sudo yum install p7zip p7zip-plugins # CentOS / RHEL装完直接解压7z x container_defect_dataset.7zx命令会保留压缩包内的完整目录结构。如果你只需要看压缩包里有什么先用l命令列目录确认结构后再解压避免把一堆不知道哪来的文件散到当前目录7z l container_defect_dataset.7zmacOS用Homebrew装p7zip命令一样。Windows最简单的方式是装7-Zip右键“解压到当前文件夹”就行。但命令行方式在大批量处理时更可控PowerShell里这样调用 C:\Program Files\7-Zip\7z.exe x D:\data\container_defect_dataset.7z -oD:\data\container_output-o参数指定解压输出目录注意-o后面没有空格。命令行方式比右键多一个好处解压日志会逐条列出文件哪个文件报错可以直接看到。如果你的压缩包设了密码尽量不要在命令行里直接写-p密码——shell的历史记录会记住它。先不带密码执行等交互提示再输入。3.2 解压后先做三件事图片可读、标签可解析、文件一一对应解压完成不等于数据可用。我习惯先跑一个三连校验图片能不能被解码、XML/TXT能不能被解析、图片和标签是否一一对应。跳过这一步直接训练后面大概率会翻车在某个不知道哪来的损坏文件上。图片解码校验用Python最直接import os from PIL import Image img_dir JPEGImages broken [] for name in os.listdir(img_dir): if not name.lower().endswith((.jpg, .jpeg, .png)): continue try: img Image.open(os.path.join(img_dir, name)) img.verify() except Exception as e: broken.append((name, str(e))) print(broken images:, len(broken)) for item in broken[:10]: print(item)verify()方法只校验文件头和数据完整性不会真正解码速度很快。如果broken列表不为空优先重新下载对应文件别自己裁剪修复——一张损坏图在训练时会导致数据加载中断而且报错信息不直观。接着校验TXT标签的数值范围这一步直接决定训练会不会出nanimport os label_dir labels bad_files [] for name in os.listdir(label_dir): if not name.endswith(.txt): continue with open(os.path.join(label_dir, name)) as f: for line in f: parts line.strip().split() if len(parts) ! 5: bad_files.append((name, field count ! 5, line)) continue cls, cx, cy, w, h int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if cx 0 or cx 1 or cy 0 or cy 1 or w 0 or h 0: bad_files.append((name, out of range or zero size, line)) print(bad label files:, len(bad_files)) for item in bad_files[:20]: print(item)这个脚本逐行检查五个字段是否齐全、归一化坐标是否在0到1之间、框的宽高是否为正。w0或h0的框是YOLO训练的定时炸弹反向传播时梯度会变成非法值。最后做图片和标签的对应关系检查用集合差集一眼就能看出谁多谁少img_names {os.path.splitext(f)[0] for f in os.listdir(images) if f.lower().endswith((.jpg, .png))} label_names {os.path.splitext(f)[0] for f in os.listdir(labels) if f.endswith(.txt)} print(images without labels:, len(img_names - label_names)) print(labels without images:, len(label_names - img_names))正常情况两个差集都应为空。如果出现“labels without images”说明这张图的图片文件缺失训练时dataloader会报FileNotFoundError且错误信息可能指向完全不相关的文件排查起来很费劲。4. 把数据集接入YOLO训练目录调整、data.yaml与train/val划分4.1 目录结构重排YOLOv5/v8约定的数据组织方式YOLOv5和YOLOv8对数据目录的约定一致images和labels两个平级目录图片和标签文件名相同后缀不同然后在一个YAML文件里指定路径。如果压缩包里的YOLO格式目录已经是这种结构直接跳过这一步。如果不是需要把图片和标签各自归拢到一起。常见做法是用软链接而不是复制文件省磁盘且速度快mkdir -p datasets/container_defect ln -s /path/to/original_images datasets/container_defect/images ln -s /path/to/original_labels datasets/container_defect/labels软链接的坑在于如果路径写错YOLO训练时报错信息是“No such file or directory”但不会告诉你链接断了检查起来比较隐蔽。所以我一般倾向于复制而不是链接尤其当原始目录结构混杂的时候mkdir -p datasets/container_defect/images datasets/container_defect/labels cp JPEGImages/*.jpg datasets/container_defect/images/ cp labels/*.txt datasets/container_defect/labels/训练集和验证集的划分有两种方式一种是在YAML里分别指定train和val的目录或文件列表另一种是直接按目录划分。我建议用文件列表方式灵活且可复现——把划分结果写进txt跑多次实验时保证每次用的都是同一份列表。划分脚本import os import random base_dir datasets/container_defect image_dir os.path.join(base_dir, images) names [os.path.splitext(f)[0] for f in os.listdir(image_dir) if f.endswith(.jpg)] random.seed(42) random.shuffle(names) split int(len(names) * 0.8) train_names, val_names names[:split], names[split:] with open(os.path.join(base_dir, train.txt), w) as f: f.write(\n.join(f{base_dir}/images/{n}.jpg for n in train_names) \n) with open(os.path.join(base_dir, val.txt), w) as f: f.write(\n.join(f{base_dir}/images/{n}.jpg for n in val_names) \n) print(ftrain: {len(train_names)}, val: {len(val_names)})80/20划分在4127张的规模下是常见起点。如果某个类别的样本很少可以考虑按类别分层划分——先按类别id分组再从每组里按比例抽保证验证集里每个类都存在。随机划分在小样本下容易出现某个类别在验证集里只有几张的情况导致验证mAP波动很大。这个脚本里我把random.seed固定为42保证每次运行划分结果一致排查问题时不会因为数据顺序变化引入额外变量。4.2 data.yaml配置与预训练权重选择data.yaml是YOLO训练的总入口内容很简短但每个字段都关键path: /absolute/path/to/container_defect train: train.txt val: val.txt nc: 3 names: 0: dent 1: rust 2: crackpath必须是绝对路径或相对YOLO项目根目录的路径train和val填train.txt和val.txt时YOLO会基于path拼接。nc是类别数标题说3类别就填3。names列表的顺序就是模型输出类别的顺序这个顺序必须和标签TXT里第一列的数字完全一致——0对应names里第0个类1对应第1个类以此类推。names里的类名从哪里来解压后看labels里每个TXT第一列数字的统计再看VOC的XML里object节点的name字段两边对照。如果labels里数字1对应XML里的“rust”names[1]就必须写rust。写错不会报错但训练出来的模型预测结果全部错位而且loss曲线看起来完全正常——这类错位是最阴间的bug。预训练权重方面YOLOv5和v8在首次运行时会自动下载coco预训练权重。网络条件不允许的情况下在另一台机器上提前下载好pt文件拷到项目根目录再训练。YOLOv8的启动命令yolo train datadatasets/container_defect/data.yaml modelyolov8n.pt epochs100 imgsz640 batch16yolov8n是轻量级模型适合先跑通流程。确认数据没问题后换yolov8s或yolov8m提升精度。YOLOv5的对应命令是python train.py --data datasets/container_defect/data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100两个版本的数据集格式兼容同一份data.yaml可以直接复用只是命令行参数写法不同。batch16是在单张消费级显卡上比较稳的起点显存不够降到8不要为了凑batch把imgsz从640改到320——集装箱缺陷是小目标分辨率降低会直接丢掉小缺陷的召回。4.3 用统计脚本核对类别id与names顺序训练前再跑一遍这个检查它能发现你在names里写错类别名或漏了某个类别。把labels目录里所有TXT第一列的数字统计出来awk {print $1} labels/*.txt | sort | uniq -c输出示例2100 0 3500 1 640 2三个数字和nc3吻合且每个TXT第一列的最大值不超过2。如果出现数字3说明要么这个数据集不止3类要么某个标签文件写错了。此时先不要训练去VOC的XML里找那张图对应的object节点确认真实的类别名是什么。用uniq -c而不是直接数行数是因为这个统计能看到类别分布——如果数字分布极不均匀前面提到的类别加权策略就要安排上。5. 训练翻车排查这个数据集最容易踩的5个坑5.1 解压报“密码正确但数据错误”7z的编码玄学现象用7-Zip右键输入密码解压界面提示“数据错误文件已经损坏”但密码明明是从下载页面原样复制的。更有迷惑性的是同一个人发的其它压缩包能正常解压唯独这个包报错。原因分两种。第一种是压缩包在传输过程中不完整——网盘下载中断、迅雷拉取不全解压到某个文件时CRC校验失败。第二种是文件名包含中文或特殊字符7-Zip在非UTF-8区域设置下默认用系统编码解压文件名导致文件名乱码但文件内容其实是完整的。解决先用test命令确认压缩包本身是否完整再决定走哪条路7z t container_defect_dataset.7z如果test通过说明压缩包没坏问题在文件名编码解压时加UTF-8开关7z x container_defect_dataset.7z -scsUTF-8-scsUTF-8参数让7-Zip用UTF-8解析压缩包内的文件名。如果test报错说明文件确实不完整重新下载或换下载工具不要尝试修复。这里有个习惯值得养成下载完先test再解压尤其从网盘拉下来的大文件。另外7z的加密走AES-256密码忘记基本没有后悔药暴力破解不现实唯一出路是回到原始来源重新获取。所以密码要存在一个本地txt里跟压缩包放一起。5.2 lossnan与BatchNorm崩溃现象训练第一轮就出现nan或者前10轮正常、第11轮loss突然变成nan然后永远恢复不回来。YOLOv5的训练日志里表现为“nan”出现在loss列YOLOv8可能在验证阶段直接报“RuntimeError: value cannot be converted to type float”。原因通常有两个方向。一是标签里有非法框前面校验脚本查出来的w0或h0的框在loss计算时产生除零或对数运算越界。集装箱场景下这种错误多半来自标注工具导出时的坐标精度丢失——某个框的右下角坐标和左上角坐标相同算出来高就是0。二是学习率过大和batch过大叠加梯度的数值范围失控BatchNorm层的统计量崩掉这是yolo训练中bn崩溃最常见的诱因。解决先把标签里的零尺寸框过滤掉import os label_dir labels for name in os.listdir(label_dir): if not name.endswith(.txt): continue path os.path.join(label_dir, name) with open(path) as f: lines f.readlines() valid [] for line in lines: parts line.strip().split() if len(parts) ! 5: continue w, h float(parts[3]), float(parts[4]) if w 0 and h 0: valid.append(line) with open(path, w) as f: f.writelines(valid)然后调低学习率和batch重训。YOLOv8里yolo train datadatasets/container_defect/data.yaml modelyolov8n.pt imgsz640 batch8 lr00.001 ampFalseampFalse关闭混合精度训练这一步能排除很多fp16下的数值稳定性问题。如果关闭amp后正常说明你的数据和当前loss组合在fp16下不稳定保持关闭即可集装箱缺陷检测不需要靠混合精度那点显存节省。5.3 类别id错位标签看起来没事mAP全是0现象训练流程正常走完验证集mAP0.5显示0但loss曲线在正常下降。看预测结果图片模型输出的框位置基本合理但类别标签全部张冠李戴——比如把“dent”预测成“rust”。原因标签TXT里第一列的数字和data.yaml里names的顺序对不上。假设打包者在VOC格式里定义的顺序是dent0、rust1、crack2但他下载了别人的YOLO转换脚本脚本里写死了names顺序这3个类在YOLO标签里可能就变成了rust0、dent1、crack2。你只看到nc3就填了names实际类别对应已经完全错位。YOLO训练不会校验这个因为数字本身是合法的。解决训练前用VOC的标注做一次交叉核对。写个脚本读取所有XML里object的name统计每个名字出现次数再统计YOLO标签里每个数字出现次数两个列表必须是一一对应且数量接近import os import xml.etree.ElementTree as ET voc_dir Annotations label_dir labels voc_counts {} for name in os.listdir(voc_dir): if not name.endswith(.xml): continue tree ET.parse(os.path.join(voc_dir, name)) for obj in tree.getroot().findall(object): cls obj.find(name).text voc_counts[cls] voc_counts.get(cls, 0) 1 label_counts {} for name in os.listdir(label_dir): if not name.endswith(.txt): continue with open(os.path.join(label_dir, name)) as f: for line in f: cls int(line.strip().split()[0]) label_counts[cls] label_counts.get(cls, 0) 1 print(VOC:, voc_counts) print(YOLO:, label_counts)对照输出结果把data.yaml里的names按YOLO标签实际数字调整过来。这个坑不报错、不警告但直接废掉整个训练结果是数据集类项目最常见的隐蔽bug。5.4 类别不平衡导致某类mAP虚低现象三个类别的mAP差距悬殊样本最多的类mAP能达到0.85样本最少的类只有0.3。这不是模型能力问题是数据集本身分布决定的。另一个相关现象是yolo混淆矩阵总合不唯一——矩阵里横向加起来不等于100%很多人误以为是bug其实是归一化口径不同矩阵默认按行归一化看每一行应该约等于100%看列就没有这个约束。集装箱缺陷的场景里锈蚀往往大面积出现容易标注也容易学习开焊、裂缝这类缺陷面积小、对比度低标注框可能只占图像的百分之几模型很难提特征。加上样本数量本来就少双重不均衡叠加。解决先确认不平衡的程度用之前的awk统计看三个数字的比例。如果差距在3倍以内调整loss权重即可超过5倍要在数据层面做增强。YOLOv5可以用分类权重参数按样本量反比设置YOLOv8在配置文件里给每个类设置loss权重。更有效的是数据增强——把少样本类的缺陷区域从原图上裁下来粘贴到无缺陷的集装箱表面生成合成样本。这种增强在工业质检里很常用比翻转和裁剪有效得多因为它直接增加了关键正样本。5.5 中文路径和特殊字符导致的加载失败现象训练启动时报错找不到文件但路径明明存在。检查发现数据集存放在“D:\数据\集装箱缺陷\”或者图片文件名里有空格和“#”号。原因YOLO的数据加载对路径和文件名里的特殊字符很敏感Windows下中文路径和空格会让opencv读取失败或dataloader混淆。集装箱项目的数据通常来自现场拍摄文件名经常是“IMG_20240315_143200_#2.jpg”这种风格。解决统一规范。把数据集放到纯英文路径下文件名全部改成无特殊字符的编号格式for f in images/*.jpg; do newname$(echo $f | sed s/[^A-Za-z0-9._\/-]//g) if [ $f ! $newname ]; then mv $f $newname fi done对应的标签文件名也要同步修改否则图片和标签就失配了。改完后重新跑一遍图片与标签的对应关系检查。6. 验证与进阶用混淆矩阵和mAP反向检查数据质量6.1 混淆矩阵里三处值得警惕的异常训练完看results.png里的混淆矩阵很多人只会看对角线数值高不高。对角线当然要关注但更有价值的是三类异常。第一类是background列有明显数值——模型把集装箱表面的铆钉、锁杆、焊缝、阴影误检成了缺陷这说明你的负样本不够需要收集更多无缺陷集装箱的图片做背景样本。第二类是某一个类别的行里非对角线单元格明显高于其它行这说明两类缺陷外形相似模型分不清典型的处理方式是检查标注是否有边界模糊——同一个特征在一张图里标成锈蚀、另一张图里标成裂缝。第三类是行和列的分布总和差异大这基本就是类别不平衡的直接体现按前面说的加权方案处理。6.2 集装箱场景的数据增强边界集装箱外观有三个显著特点表面是重复的波纹纹理强反光会造成高光区域顶部和底部结构不同。这些特点直接限制了数据增强的选择。mosaic增强可以开它把四张图拼在一起对学习小目标有利。旋转设±10度足够集装箱的边框线是垂直的旋转角度太大会让箱体结构变得不自然。水平翻转可以开左右对称对集装箱成立但垂直翻转要关掉——箱顶有锁杆和角件箱底是平面翻转后特征完全错乱。色彩抖动要克制锈蚀的颜色特征是关键线索RGB扰动太大会把锈色洗没HSV空间里把saturation扰动适度调大一点模拟不同光照下的色彩衰减hue扰动保持很小。我拿这个数据集的习惯是先花二十分钟把标签当代码审一遍再做训练。只要发现一个XML和TXT对不上的框就停下来搞清楚原因再继续——这种数据问题不会自己消失只会以更隐蔽的方式在mAP上给你一刀。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?