多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

高铁受电弓检测数据集:VOC与YOLO格式1245张2类目标检测

高铁受电弓检测数据集:VOC与YOLO格式1245张2类目标检测 简介面向高铁受电弓视觉检测任务的数据集同时提供Pascal VOC与YOLO两种标注格式可解决该场景公开样本稀缺的问题省去格式转换环节直接用于YOLO系列、Faster R-CNN等目标检测模型的训练与评估。资源共2000个文件压缩后大小48.8MB包含1245张jpg图像、1245个xml标注文件以及755个txt格式文件xml中记录类别与矩形框坐标txt存储YOLO格式的归一化标注信息两种文件配合使用便于主流检测框架直接读取。类别覆盖roi与sdg两类每类各含1245个矩形框总标注框数2490由labelImg工具人工绘制定位准确、边界清晰。已有819人学习/下载。数据集贴合高铁弓网检测场景适合轨道交通视觉运维、工业缺陷检测等方向的初学者与研究者快速开展模型训练也可作为目标检测基准数据用于算法对比。1. 高铁受电弓检测数据集 VOCYOLO 格式1245 张图的 2 类目标检测资源做高铁接触网巡检项目时最缺的往往不是模型结构而是带干净标注的受电弓图像。我最近拆了这个高铁受电弓检测数据集 VOCYOLO 格式的 7z 包1245 张 jpg 图片每张图同时配 Pascal VOC 的 xml 标注和 YOLO 的 txt 标注两个类别 roi 和 sdg每个类别 1245 个框总框数 2490。也就是说每张图都标了一个 roi 和一个 sdg拿来训练 YOLOv5、YOLOv8 这类目标检测模型正好。它适合两类人一是刚接触工业视觉、需要一份能直接跑通训练流程的标准数据来练手的新手二是我这种已经在做检测项目、需要拿第二份数据源来验证自己数据处理和训练配置的工程师。无论哪类这份数据都能帮你把精力花在模型和业务上而不是花一整晚修标注格式。2. 数据集结构解析VOC 和 YOLO 格式到底怎么对应拿到任何检测数据集我第一件事都是先拆标注格式。这份数据集的特别之处在于同时提供两种格式很多新手容易被“两种格式”绕晕以为要多做一次转换。其实这两种格式只是同一批标注的两种写法理解清对应关系后面才能放心用。2.1 Pascal VOC 的标注文件长什么样Pascal VOC 是目标检测领域的老牌标注格式labelImg 默认可以导出。这份数据集的 xml 文件与 jpg 同名内部用annotation做根节点记录文件夹名、文件名、图像尺寸、目标类别和边界框。我摘一段典型结构说明annotation folderfirc_shoudinggong/folder filenamefirc_shoudinggong_833.jpg/filename size width1920/width height1080/height depth3/depth /size object nameroi/name bndbox xmin100/xmin ymin200/ymin xmax300/xmax ymax400/ymax /bndbox /object object namesdg/name bndbox xmin150/xmin ymin220/ymin xmax280/xmax ymax380/ymax /bndbox /object /annotation这段 xml 里的字段都很直观size的 width、height 是图像的像素尺寸depth是通道数每个object对应一个目标name是类别字符串bndbox是矩形框的绝对像素坐标。关键点有两个一是size必须和实际 jpg 文件尺寸一致否则后续坐标转换会失真二是bndbox的四个值必须落在图像范围内超出边界会让 YOLO 训练直接报错。我在处理多份数据集时发现这些“看起来没问题”的细节恰恰是最容易藏雷的地方。解析 xml 的方式很简单用 Python 的xml.etree.ElementTree即可。需要注意的是同一个object在 xml 中出现顺序并不代表类别顺序可能这张图第一个 object 是 roi下一张图第一个 object 就变成 sdg。所以统计时一定要按name值做字典累计而不是按位置序号。2.2 YOLO 的 txt 标注格式与归一化坐标YOLO 全系列读取的训练标注是纯文本每行对应一个目标class x_center y_center width height。这里的中心坐标和宽高都是相对图像宽高的比值取值一般在 0 到 1 之间。对应上面那张图txt 内容大概是0 0.5000 0.3500 0.8000 0.9000 1 0.3500 0.4200 0.1200 0.3400第一列的 0 和 1 是类别 id它对应 data.yaml 里names列表的下标。也就是说如果 data.yaml 写的是names: [roi, sdg]那么 0 是 roi、1 是 sdg一旦顺序写反整个模型学到的语义都是反的。这种错误在训练日志里不会有任何提示只有到测试阶段才会发现预测框的类别标签张冠李戴是最典型的黑匣子问题。从 VOC 转成 YOLO 格式的公式是固定的我一般这样算x_center (xmin xmax) / 2 / width y_center (ymin ymax) / 2 / height bbox_width (xmax - xmin) / width bbox_height (ymax - ymin) / height这份数据集已经提供了转换后的 txt直接拿来用就行。但建议你用这个公式心算一遍某个框的坐标验证 txt 里的数值是否合理。比如一张 1920×1080 的图某个框 xmin960、xmax1920那 x_center 计算出来应该是 0.75。如果看到 0.5说明转换过程有 bug这种问题靠目检很难发现必须靠数值核对。另外摘要里专门写了一句“不包含分割路径的txt文件”意思是压缩包里没有 train.txt、val.txt 这种记录图片路径的列表文件。也就是说这份数据集只提供图片和标注没有官方划分好训练集与验证集。这对新手来说是个隐藏坑很多人解压后直接去找 train.txt 找不到然后以为数据集不完整。实际不是你需要按第 4 章的流程自己划分。VOC 与 YOLO 格式的核心差异可以用一张小表说明格式文件后缀坐标类型类别标识是否归一化Pascal VOC.xml绝对像素坐标 xmin,ymin,xmax,ymax字符串 name否YOLO.txt中心点宽高整数 class id是有了这张表再对照上面两个例子两种格式的对应关系就很清楚了。2.3 两个类别的业务含义roi 与 sdg这份数据集的说明文档并没有给出完整的类别定义我按受电弓检测场景的命名习惯来解读sdg是“受电弓”的拼音缩写shou dian gong框的应该是受电弓整体结构roi是 Region of Interest即感兴趣区域可能单独框出弓头、碳滑板或接触网关键部位。这种“先框整体、再框局部”的两级标注在工业缺陷检测里很常见上游可以先用 sdg 做目标定位再用 roi 做精细区域分析比如检测滑板磨损、异物入侵等。从训练角度看两个类别的框数均为 1245说明每张图片都恰好有这两个框。这种完美一致性看起来是好事但也意味着如果你发现某张图只有 sdg 没有 roi就应该怀疑标注遗漏。另外roi 如果是小目标它的框在整图中占比很小这会直接影响小目标检测精度。我拿到数据后会先可视化几张图确认 roi 框在受电弓上的具体位置再去决定是否需要调整输入分辨率或使用多尺度训练。这一步不是可有可无的仪式而是后续一切调参的前提。提示在确认类别含义之前不要急于修改类别名或合并类别。先跑通训练、再根据实际检测需求决定是否调整避免把自己的主观假设写进数据管道。2.4 用 labelImg 打开这份数据集做二次确认摘要里写这数据用 labelImg 标注那你可以直接用 labelImg 复查。打开 labelImg 后左侧选择“打开目录”指向图片目录右侧切换标注格式如果是看 xml就保持 PascalVOC如果是看 txt就切到 YOLO。它能同时把框、类别名显示在图上方便直观确认。我平时会随机抽十几张图重点看 roi 框和 sdg 框的位置。如果发现 sdg 框把整个受电弓都圈进去了而 roi 框只圈了弓头的一部分那我对训练的预期就有了底如果两个框几乎完全重合说明标注规则可能不是“整体局部”而是“大框套小框”甚至“重复标注”。这种视觉确认比看任何统计数字都来得快也更容易暴露异常。3. 解压 7z 包并核对数据集一步都不能省的检查清单这份资源是 .7z 格式和 zip、tar.gz 不一样默认的unzip命令解不了。我在 Linux 服务器上解压前会先装 p7zip-full然后执行下面的命令。3.1 在 Linux 下解压 .7z 文件apt-get update apt-get install -y p7zip-full 7z x 高铁受电弓检测数据集VOCYOLO格式1245张2类别.7z7z x会保留压缩包内的目录结构而不是像7z e那样把所有文件平铺到当前目录。平铺会导致成百上千个 jpg 和 xml 混在一起后续按目录分类时会非常痛苦。所以我从来不用e只用x。如果你在 Windows 上解压可以用 7-Zip 图形界面右键选择“提取到当前目录”效果等价。注意解压路径里如果有中文部分 Linux 系统下可能出现文件名乱码这就是后文避坑的典型场景。解压完成后先不要急着开训练先看一眼目录里都有什么。这份压缩包内文件主体是 jpg、xml、txt 各 1245 个另外还有一个“使用前必读.txt”。我个人习惯第一件事就是把“使用前必读.txt”移到一个独立目录避免后面统计 txt 文件数量时被它干扰。3.2 检查文件数量与文件名配对数据集常见的问题不是缺图而是图片与标注文件数量对不上或者文件名因为大小写、后缀不同导致配对失败。先做一轮基本统计find . -name *.jpg | wc -l find . -name *.xml | wc -l find . -name *.txt | wc -l预期三个数字都是 1245。如果 txt 的统计结果为 1246通常就是因为把“使用前必读.txt”也算进去了。这时候可以加过滤条件再统计一次或者用 Python 脚本排除掉说明文件。数量没问题还要验证文件名是否一一对应。我的做法是用 Python 集合做差集import os from pathlib import Path img_dir Path(解压后的图片目录) xml_dir Path(解压后的xml目录) txt_dir Path(解压后的txt目录) def stem_set(directory, suffix): return {f.stem for f in directory.glob(f*{suffix})} img_stems stem_set(img_dir, .jpg) xml_stems stem_set(xml_dir, .xml) txt_stems stem_set(txt_dir, .txt) print(缺xml的图片:, len(img_stems - xml_stems)) print(缺txt的图片:, len(img_stems - txt_stems)) print(多出的xml:, len(xml_stems - img_stems)) print(多出的txt:, len(txt_stems - img_stems))这段脚本的关键在于用Path.glob匹配后缀然后用集合差集找出缺失和多出的文件。如果“多出的txt”里包含“使用前必读”那正好说明说明文件已经被统计进来需要把它剔除。注意这里没有考虑.jpeg和.jpg混用的情况如果你遇到文件扩展名不统一先把所有图片统一成.jpg再做配对。3.3 核对标注类别与框数文件配对只是第一步标注内容也得复查。我习惯先统计 xml 里的类别和框数import glob import xml.etree.ElementTree as ET xml_list glob.glob(解压后的xml目录/*.xml) counts {} empty_files [] for xml_path in xml_list: tree ET.parse(xml_path) root tree.getroot() objects root.findall(object) if len(objects) 0: empty_files.append(xml_path) for obj in objects: name obj.find(name).text counts[name] counts.get(name, 0) 1 print(类别统计:, counts) print(空标注文件数:, len(empty_files))这段程序对每个 xml 统计object总数和各类别数量。期望输出是{roi: 1245, sdg: 1245}总计 2490 个框。如果 roi 数量比 sdg 少说明某些图漏标了 roi 或标错了名字如果计数比 1245 多说明有些 xml 里重复标了多余框这类数据在训练时会把模型带偏。空标注文件要单独记录因为 YOLO txt 为空意味着这张图没有正样本默认会被忽略。只查 xml 还不够txt 也应该扫一遍。因为两份格式虽然理论上一致但转换脚本可能存在 bug导致 txt 里的框数量与 xml 不一致。我通常会写一个类似的计数器按行读取 txt累计每行的 class id 和框数再和 xml 结果逐图对比。这种双重校验看起来麻烦但实际上非常值得——我在某次项目中就遇到 xml 中两个框、txt 中只有一个框的情况最终查明是转换脚本在遍历时被一张损坏图片中断导致。3.4 用脚本输出不一致的具体文件只报数量差异还不够得知道具体哪几张图有问题。在 3.3 的基础上我会再加上一段对比逻辑对每个图片从 xml 中数目标数再从 txt 中数目标数二者不等就 print 出来。这样可以直接把脏数据踢出去不用等训练时才发现。import glob from pathlib import Path xml_dir Path(解压后的xml目录) txt_dir Path(解压后的txt目录) def count_xml(xml_path): import xml.etree.ElementTree as ET tree ET.parse(xml_path) return len(tree.getroot().findall(object)) def count_txt(txt_path): with open(txt_path, r) as f: return sum(1 for line in f if line.strip()) for xml_file in sorted(xml_dir.glob(*.xml)): txt_file txt_dir / (xml_file.stem .txt) if not txt_file.exists(): print(缺txt:, xml_file.name) continue n_xml count_xml(xml_file) n_txt count_txt(txt_file) if n_xml ! n_txt: print(不一致:, xml_file.name, xml, n_xml, txt, n_txt)这个脚本输出的每一行都代表一个脏样本。对这份数据来说理想情况是什么都不输出。如果有输出说明资源在打包或转换时出了问题一定要先修再训。修复方式通常是以 xml 为准重写 txt因为 XML 是 labelImg 的原生格式可信度更高但如果 xml 的框本身也有问题就得回手工标注。4. 把数据集喂给 YOLO 训练目录组织与关键训练配置数据检查完下一步就是把它变成 YOLO 能直接用的目录结构。很多新手在解压后直接把整个文件夹填到 data.yaml 里结果训练报找不到标签。YOLO 不认识混合目录你必须把图片和标签分开并且按 train/val 组织。4.1 按 YOLO 官方格式重新组织目录YOLOv5 和 YOLOv8 推荐的目录结构是一致的yolodataset/ ├── images/ │ ├── train/ │ ├── val/ ├── labels/ │ ├── train/ │ └── val/图片目录与标签目录通过相同的文件名 stem 配对。也就是images/train/abc.jpg对应labels/train/abc.txt。这份数据集没有提供划分好的 train/val 列表所以我一般自己划分。1245 张图不算多用 8:2 划分比较稳妥训练集 996 张、验证集 249 张。import os import random import shutil from pathlib import Path src_img_dir Path(解压后的图片目录) src_label_dir Path(解压后的yolo标签目录) out_root Path(yolodataset) for split in [train, val]: (out_root / images / split).mkdir(parentsTrue, exist_okTrue) (out_root / labels / split).mkdir(parentsTrue, exist_okTrue) imgs sorted(src_img_dir.glob(*.jpg)) random.seed(42) random.shuffle(imgs) val_count int(len(imgs) * 0.2) for i, img_path in enumerate(imgs): split val if i len(imgs) - val_count else train label_path src_label_dir / (img_path.stem .txt) if not label_path.exists(): print(缺标签:, img_path.name) continue shutil.copy(img_path, out_root / images / split / img_path.name) shutil.copy(label_path, out_root / labels / split / label_path.name)脚本里random.seed(42)的目的是让每次划分结果可复现方便以后对比实验。划分逻辑是先随机打乱所有图片然后把最后 20% 划为验证集。注意这里我选择的是“复制”而不是“移动”因为原始 xml 还需要保留万一后面要做格式转换或人工检查不必重新解压压缩包。如果你磁盘空间紧张可以用shutil.move替换copy但建议先做完所有检查再移动。4.2 生成 data.yaml 文件YOLO 官方实现通过 data.yaml 定位数据集。内容很简单path: /absolute/path/to/yolodataset train: images/train val: images/val names: 0: roi 1: sdgpath必须写绝对路径。因为训练脚本可能从任意子目录启动相对路径很容易解析错误。train和val是相对path的目录名。如果你的项目目录里还有测试集可以加test字段但这份数据没有专门切测试集所以只需要 train 和 val。最关键的names顺序。它直接对应 txt 第一列的 class id。所以写 yaml 之前必须确认 txt 里 0 到底代表 roi 还是 sdg。我自己从来不猜而是直接抽查一个 txt 和它的 xml如果 txt 第一行第一列是 0对应 xml 中第一个 object 的 name 是 roi那就把 0 写成 roi。检查手法在避坑部分会再次强调。4.3 训练命令与参数说明目录和 yaml 就绪以 YOLOv8 为例启动训练yolo detect train datadata.yaml modelyolov8n.pt epochs150 imgsz640 batch16 patience20逐个参数说明datadata.yaml指向刚才的配置modelyolov8n.pt是官方预训练权重YOLO 会自动下载用它做迁移学习远比随机初始化收敛快epochs150对 1245 张图来说是合理的训练轮数时间紧张可以减到 100imgsz640是输入尺寸如果受电弓目标偏小建议升到 1280但显存占用会明显增大batch16根据显存调整12GB 显存跑这个配置基本没问题显存小就降到 8 或 4patience20表示连续 20 轮验证集 mAP 不上升就自动停止属于早停机制能省下不少等待时间。如果你用 YOLOv5命令是python train.py --data data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 150YOLOv5 参数的语义基本一样只不过入口脚本和变量命名不同。需要注意的是无论 v5 还是 v8都不要把验证集和训练集放在同一目录否则验证曲线会虚高到没有参考价值。我会在训练启动后看一眼日志里的 val 图片数量确认大约等于训练集的四分之一。4.4 数据增强与类别不平衡的取舍这份数据集的框数比例是 1:1不存在类别不平衡但受电弓图像有很强的工业场景特点。YOLO 默认的增强策略未必都适用左右翻转fliplr会改变弓头方向如果弓头结构不对称翻转后的样本会传递错误信号色彩抖动hsv_h对灰度有特殊要求的场景也可能有害。常见做法是先关掉增强训练一遍得到基线再开启增强和默认增强对比 mAP。比如yolo detect train datadata.yaml modelyolov8n.pt epochs150 imgsz640 batch16 augmentFalseaugmentFalse会关闭大多数在线增强利于判断数据本身的难度。如果基线结果已经不错再逐步开启增强来提升泛化能力。这一步本质上是控制变量法比直接堆增强参数要高效得多。5. 避坑7z 解压与标注不一致的常见问题这一章写下我实际踩过、也帮别人排查过的几个高频问题。每条都按“现象、原因、解决”写复制代码和操作都能直接复用。5.1 解压后中文文件名乱码现象解压后 jpg 文件名变成一串乱码或者扩展名消失导致图片和 xml 无法配对。原因压缩包在 Windows 下创建时文件名使用 GBK/GB2312 编码Linux 下 7z 默认按 UTF-8 解码编码错位就变成乱码。这类问题只要有中文文件名就会出现和资源本身质量无关。解决解压时指定编码或者解压后批量重命名。在 Linux 下如果文件名本身乱码但同时带有规律前缀比如firc_shoudinggong_*可以写脚本按规律重命名。最稳妥的方案是把所有文件名改为 ASCII 结构比如统一叫img_0001.jpg同时修改对应的 xml 里filename。我在做项目时都会在解压后立即执行一次find . -name *乱码*检查避免后续步骤被乱码文件名干扰。5.2 xml 里 filename 和实际 jpg 文件名不一致现象脚本配对时大量报缺图但目录里明明文件都在。原因labelImg 保存 xml 时会把当前打开的图片名写入filename但如果图片后来被重命名或者从别的目录复制过来xml 里的名字就不会自动更新。打包方采集数据时可能没有统一整理导致 xml 内部记录和文件名对不上。解决不要用 xml 里的 filename 去匹配图片。正确做法是以文件系统里的 jpg 文件名 stem 为标准去 labels 目录找同名 txt。如果 xml 只是用来做统计和可视化也可以直接忽略filename字段。这个坑教会我一条原则文件系统的真实文件名才是唯一事实xml 里的记录只能当参考。5.3 YOLO 类别顺序和 data.yaml 对不上现象训练时 loss 正常下降验证时类别标签完全错乱比如把受电弓整体识别成 roi把 roi 识别成 sdg而且两个类别的精度都很低。原因txt 第一列的 0/1 本身没有语义类别语义来自 data.yaml 的 names 顺序。如果 names 写反了模型学到的映射就是反的。这种情况在训练阶段不会有任何报错是最容易让人白费几周时间的问题。解决训练前写一个抽查脚本把 xml 的 object name 顺序和 txt 的 class id 打印出来人工核对。例如随机取 10 张图读取每张图的第一个 txt 行和第一个 xml object name确认对应关系后再写 data.yaml。从那以后我拿到任何 YOLO 格式数据集都会先做一次“标注语义核验”杜绝这类静默错误。5.4 图片尺寸不一致导致坐标越界现象训练中途抛出AssertionError: bbox must be in [0, 1]或者 loss 变成 NaN。原因xml 里的 width、height 是标注时用的尺寸但实际图片后来被缩放或压缩导致转换后的归一化坐标超过 1。有些标注工具在用户手动拖动图片时也会写入错误的 size 字段。解决不要完全信任 xml 的 size。训练前用 OpenCV 读取每张图的实际分辨率重新计算 txt 坐标。如果发现某张图尺寸异常就单独替换。可以用一行命令检查所有图片尺寸是否一致python -c from PIL import Image; import glob; print({Image.open(f).size for f in glob.glob(images/*.jpg)})输出应该只有一个元素比如{(1920, 1080)}。如果出现多个尺寸就需要按尺寸分组处理。5.5 把“使用前必读.txt”当成了标注文件现象统计 txt 数量是 1246训练时某个 batch 出现invalid class id或者 YOLO 在读取某行时因为中文解释出现字符转换错误。原因压缩包里有一个“使用前必读.txt”它后缀是 .txt和标注文件一模一样。如果不加过滤就把整个目录当 labels 目录用YOLO 会读取这个文件并尝试把内容解析成坐标自然就出错了。解决在划分训练集时强制校验标签文件 stem 必须存在于图片 stem 集合中不属于的全都排除。参考我在 4.1 的代码label_path.exists()已经隐含了文件名一致的前提但如果“使用前必读.txt”恰好存在于标签目录且同名图片不存在就会被跳过。如果它存在于 labels 根目录复制时会进入 train 或 val我们在脚本中只复制label_path而 label_path 是与当前 img 同名的所以不会复制无关的说明文件。所以脚本本身是安全的。建议在复制前再加一行过滤if label_path.stem in img_stems双重保险。6. 进阶检查用 20 行代码可视化标注并评估数据质量训练前最重要的一步是可视化。统计只能告诉你数量对不对只有把框画到图上才能看出位置准不准、范围贴不贴边。我常用的脚本很简单import cv2 import glob from pathlib import Path img_paths sorted(glob.glob(yolodataset/images/train/*.jpg)) for idx, img_path in enumerate(img_paths): img cv2.imread(img_path) h, w img.shape[:2] txt_path Path(img_path.replace(images, labels)).with_suffix(.txt) with open(txt_path) as f: for line in f: cls, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) color (0, 255, 0) if cls 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imwrite(f/tmp/check_{idx}.jpg, img) if idx 50: break脚本把 YOLO 归一化坐标还原到像素坐标然后画绿框和红框分别代表 cls 0 和 cls 1。跑完之后到 /tmp 目录打开这些图重点看两点框是否紧贴目标以及两个框的重叠关系是否稳定。如果某些图上 roi 框偏移了半个目标那这张图的标注大概率有问题需要修。这个脚本也很适合用来写自动质量检查如果检测到框宽或框高为 0直接报错。我还习惯统计所有框的宽高比。受电弓结构通常是长条形如果大量框的宽高比接近 1说明标注框可能圈了背景而不是目标这会影响 anchor 匹配。可以用一组 Python 循环把宽高比分桶统计再结合可视化决定是否需要手动修正。有了这一遍检查训练出来的模型才靠谱。我以前跳过可视化直接开训结果模型 mAP 很低回查发现标注框整体偏了 20 个像素最后只能删除一批脏数据重新训练。从那以后我每次拿新数据集都强制走完“解压检查—配对检查—坐标检查—画框可视化”这四步才算过关。虽然多花半小时但省下的返工时间远超这个投入。希望这份资源能让你绕过我走过的弯路直接落一个能跑的受电弓检测模型。本文还有配套的精品资源点击获取
返回列表