简介:一份面向YOLO系列算法的电缆损坏目标检测数据集,适合需要训练破损检测模型的开发者与研究人员直接使用。资源包共两千个文件,以可扩展标记语言和纯文本两种标签格式为主,分别为约一千零八十一个和九百一十九个,压缩包整体约一百零七兆字节。数据集已预先划分好训练集与验证集,内置数据集配置文件,可无缝适配多种主流版本的目标检测算法。标签内容包含类别索引、归一化的目标框中心坐标与宽高,两种标签格式分别存储于不同文件夹,便于在不同训练框架间切换,省去格式转换时间。目前已有七十九人学习使用,适合作为电缆损伤检测项目的起步数据,也方便在此基础上进行二次标注或算法迭代。
1. 电缆损坏数据集:1318 张带标签图像,到底能不能拿来直接训 YOLO?
这个数据集看起来就是一堆 txt 文件名,实际上是一个标准的 YOLO 目标检测数据集,1318 张电缆外观图像,每张图同时配 YOLO 格式 txt 和 VOC 格式 xml 标签。相比自己组织拍摄、标注、清洗,这个包把最耗时的工作省掉了。但拿到手千万别直接双击压缩包里的图片开始训练,第一件事是打开标签文件,确认类别索引、坐标是否归一化、train/val/test 划分是否干净,再决定用哪个版本的 YOLO。适合刚开始做目标检测的开发者,也适合要做电缆损坏识别但手里没有现成数据的从业人员。下面按我实际拆包的顺序,把数据集结构、训练流程和常见坑逐个过一遍。
2. 数据集结构与标签格式:txt/xml 双标注的对应关系与坐标换算
解压之后看到一堆img_0957_477.txt这样的文件,先不要慌,这只说明标签文件命名用的是图片名加后缀。整个数据集不是把图片和标签混在一个文件夹,而是按规范把 images、YOLO 标签、VOC 标签和 data.yaml 分开放置。为什么要分开放?因为训练框架加载标签时,会按扩展名自动过滤,混在一起容易导致读取错误。
2.1 文件清单:images、labels_yolo、labels_voc、data.yaml 的存放逻辑
常见做法是解压后得到这样几个部分:
- images 目录:1318 张电缆外观图像,包含 JPG 或 PNG 格式,文件名类似 img_0957_477.jpg。
- labels_yolo 目录:与图像同名的 txt 文件,每行一个目标,内容是 class x_center y_center width height。
- labels_voc 目录:同名 xml 文件,按 VOC 格式保存 object 节点,里面有 name 和 bndbox。
- data.yaml:训练配置文件,声明 train/val/test 路径、类别数量 nc、类别名称 names。
表格形式如下:
| 目录 | 格式 | 内容 | 用途 |
|---|---|---|---|
| images | jpg/png | 原始图像 | 输入图片 |
| labels_yolo | txt | 归一化框坐标 | YOLO 系列直接训练 |
| labels_voc | xml | 绝对像素坐标框 | VOC 转其他格式 |
| data.yaml | yaml | 路径/类别配置 | 训练入口 |
注意:这个数据集已经进行了划分,train/val/test 分别对应不同的子目录,data.yaml 里的 train/val 路径应该填写划分后的目录。如果没有子目录,只有一个 images 目录,需要自己划分。我拿到包后会先跑一个文件数量核对脚本,确认每个图片都有对应标签,且数量完全一致。用 bash 循环可以做这个检查:
for img in images/*.jpg; do base=$(basename "$img" .jpg) if [ ! -f "labels_yolo/$base.txt" ]; then echo "missing yolo label: $img" fi if [ ! -f "labels_voc/$base.xml" ]; then echo "missing voc label: $img" fi done这个脚本的逻辑是遍历每张 jpg 图片,取出不带后缀的文件名,然后去两个标签目录找同名文件。如果输出 missing 信息,说明该图片缺少对应标签,需要补标或者剔除;如果没有输出,就可以放心进入下一步。这里有个参数容易忽略:如果扩展名不是 jpg,需要把第一行.jpg改成实际后缀,否则basename提取出的名字不对,后面的判断就会误报。另一个常见情况是压缩包里既有 .jpg 也有 .png,循环只处理了一种,容易漏检,我一般用find images -name '*.jpg' -o -name '*.png'扩展。
2.2 YOLO 归一化坐标解析:从 txt 一行数值反推像素框
YOLO 标签格式描述里写得清楚: <x_center> <y_center> ,四个坐标值都是相对图片宽高的比例,范围 0~1。这意味着同样一行数值,在不同分辨率的图片上代表不同的像素区域。你可能看到 0.5 0.5 0.2 0.2,以为是一个居中的大方框,但如果图片是 1920×1080,实际框就是 384×216 像素;如果是 640×480,实际框只有 128×96 像素。所以解析标签前,必须先拿到图片的真实宽高。
我处理任何 YOLO 数据集,都会写脚本抽查几张图,把标签框画出来,确认没有跑偏。下面这段 Python 代码可以把归一化坐标转换成像素坐标并打印:
import cv2 img = cv2.imread('images/img_0957_477.jpg') h, w = img.shape[:2] with open('labels_yolo/img_0957_477.txt') 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) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) print(f'class={cls}, box=({x1}, {y1}, {x2}, {y2})') cv2.imwrite('check_img.jpg', img)代码里的关键点:先用img.shape[:2]取高度和宽度,然后用公式x_center ± width/2算出左右边界,y_center ± height/2算出上下边界。注意 OpenCV 的 shape 返回的是 (height, width, channels),不要把顺序搞反。打印出的坐标可以画在图上,如果框和实际损坏区域重叠,说明标签格式正常;如果框偏移很大,多半是标注时使用了绝对像素坐标但没有归一化,这时候训练出来的模型会很不稳定。
另外要留意 class 的值。txt 文件第一列是类别索引,从 0 开始,不是类别名称。数据集的 data.yaml 中的 names 列表才定义了索引对应的文字含义。如果训练时发现 loss 很高,也可以先看看是不是类别索引跨越了 nc 范围。
2.3 VOC 格式 xml 的读取与转 YOLO 时最常踩的边界错误
数据包里还提供了 VOC 格式的 xml 标签,文件内容大致是这样的结构:
<annotation> <object> <name>0</name> <bndbox> <xmin>120</xmin> <ymin>45</ymin> <xmax>160</xmax> <ymax>80</ymax> </bndbox> </object> </annotation>注意这里的 bndbox 是绝对像素坐标,不是归一化比例。如果你想用 XML 标签训练 YOLO,就要转换成 txt。这个转换脚本看起来不难,但有不少细节:
import xml.etree.ElementTree as ET def xml_to_yolo(xml_path, img_w, img_h): root = ET.parse(xml_path).getroot() lines = [] for obj in root.iter('object'): name = obj.find('name').text # 这里需要你的类别映射表,例如 names = ['broken', 'crack'] cls = 0 # 从 names.index(name) 得到真实索引 xml_box = obj.find('bndbox') xmin = float(xml_box.find('xmin').text) ymin = float(xml_box.find('ymin').text) xmax = float(xml_box.find('xmax').text) ymax = float(xml_box.find('ymax').text) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h lines.append(f'{cls} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}') return lines参数说明:img_w 和 img_h 必须来自实际读图得到的宽高,不能想当然用数据集默认分辨率。cls 需要通过names.index(name)动态算出,如果 name 本身就是索引字符串,也要先转 int 再写;如果不用 index 而直接给 0,所有类别都会变成同一类。另外输出时保留 6 位小数,是为了让宽度高度在小目标场景下不因为四舍五入产生明显偏移。
转换完成后如何验证?我会挑一张图,把 yolo 标签对应回去画框,再和 xml 里原始坐标画的框对比,如果两个框边缘完全重合,转换正确。最容易犯的错是中心点写成(xmin + width / 2),宽度写成xmax - xmin,却没有除以图像宽高,导致数值全部大于 1,训练时目标框超出图片边界。这也是我为什么一直强调「归一化」这个动作。
3. 直接训练 YOLOv5/v8/v11:data.yaml 配置与训练命令
数据集格式再标准,也要把 data.yaml 写对,训练命令才能跑起来。很多失败不是算法问题,而是配置文件里的路径和类别数不一致导致的。下面把这部分拆开说。
3.1 data.yaml 字段详解:path、train、val、nc、names 一个都不能少
data.yaml 是整个 YOLO 训练流程的入口,内容大致如下:
path: /absolute/path/to/cable_dataset train: images/train val: images/val test: images/test nc: 2 names: 0: broken 1: crackpath 必须写成绝对路径,因为在不同机器上训练,相对路径很容易因当前工作目录不同而找不到图片。train/val/test 可以写相对 path 的路径,框架会自动拼接成/absolute/path/to/cable_dataset/images/train。nc 是类别数量,必须等于 names 的长度。如果你看到的标签类别索引最大是 1,那 nc=2 是对的;如果索引最大是 2,那 nc 至少是 3。names 的顺序也要和 txt 中的索引一致,例如索引 0 对应 broken,1 对应 crack,不能调换。
很多人会把 train/val 路径写成绝对路径,但在不同环境间迁移就要改,不如统一用 path + 相对路径。另外,如果你的数据集目录里已经划分好了 train/val,不要把train写成整个 images 目录,否则验证集也会被当训练集读进去。这种配置错误不会立刻报错,只会让你的 mAP 虚高。
3.2 训练命令与参数设置:从 YOLOv5 到 v8/v11 的差异
YOLOv5 使用独立的 train.py,YOLOv8/v11 使用 ultralytics 包,但两者都能读同一份 data.yaml。以 640 分辨率为例:
# YOLOv5 python train.py --data data.yaml --weights yolov5s.pt --img 640 --epochs 100 --batch 16 # YOLOv8 / v11 yolo train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16参数说明:--img/imgsz 是输入训练分辨率,针对电缆损坏这种小目标,640 算基础,如果显存允许可以提到 960 或 1280。--batch/batch 是批量大小,一般 16 起步,显存不足就降到 8。epochs 建议在 100 左右,再搭配 early stopping。超参数里比较关键的是mosaic和hsv增强,电缆表面纹理复杂,过度色彩增强可能让模型关注颜色而不是结构,所以如果训练发现 mAP 不稳,可以尝试关闭部分增强。
在 ultralytics 的 Python 接口里,还可以这样写:
from ultralytics import YOLO model = YOLO('yolov8n.pt') model.train(data='data.yaml', epochs=100, imgsz=640, batch=16, workers=4, patience=20)最后一个参数 patience=20 表示如果连续 20 个 epoch 验证 mAP 没有提升,训练提前结束。这个参数在长训练中很实用,可以节省大量时间。workers=4 是读图线程数,Windows 上如果报 DataLoader worker 错误,把 workers 设为 0 最稳。
3.3 数据划分验证:train/val/test 互斥与标签完整性
数据包描述说已经划分好了,但是不是每个子目录都完整,仍然需要验证。最粗暴的办法是统计各目录图片数是否与描述数量级一致,再检查是否有重叠。用 bash 的 comm 命令可以快速找出两个目录的公共文件名:
ls images/train > /tmp/train.list ls images/val > /tmp/val.list comm -12 /tmp/train.list /tmp/val.list | head -20comm -12 的作用是输出两个文件都存在的行。如果这里显示了图片名,就说明 train 和 val 有重叠,需要重新划分。严格的数据集应当保证图片不重复,否则验证集失去意义。另一个检查是确认标签目录和图片目录的名称一一对应,可以沿用第 2 章的循环脚本。对比完成后,还要用head data.yaml确认路径指向的目录真实存在。常见的坑是复制数据集时只复制了 images,忘记复制 labels,训练时框架找不到同名 txt,就会跳过该图片,导致实际训练数量变少,但日志里不会明显提示。
3.4 从零训练还是迁移学习:增量训练时如何保留原有类别
如果你希望模型在电缆损坏之外还保持原有类的检测能力,需要在 data.yaml 里把原有类和新类一起列出来,而不是把 data.yaml 覆盖成只有电缆损坏。常见做法是新建一个 data_all.yaml,把 names 设置为原类别加新类别,然后在训练时加载官方预训练权重,model = YOLO('yolov8n.pt'),这样模型会从 COCO 预训练的知识开始,而不是从随机权重开始。对于 1318 张图的数据量,从随机权重训练 100 epoch 也能收敛,但迁移学习会让收敛更快,小目标检测的稳定性也更好。
注意迁移学习时不要把 nc 改小,否则最后一层输出的维度会和预训练不一致,加载权重时报错。改完 names 后,要检查原有标签中是否出现了不在 names 里的旧类别索引。如果原标签有 class 5 而新 names 只有 0~1,加载时会直接报 index out of range,这时需要做标签映射,把所有旧的类别索引重映射到新列表位置。
4. 验证与测试:mAP、混淆矩阵与标签质量检查
模型训练完成后,验证环节不能只看一张 loss 曲线图。电缆损坏目标尺度小、背景干扰强,需要针对性地看几个指标。
4.1 用验证集评估:mAP50 与 mAP50-95 的差异
在 ultralytics 里执行验证:
yolo val model=runs/train/exp/weights/best.pt data=data.yaml输出结果里最重要的两个数是 mAP50 和 mAP50-95。mAP50 是 IoU 阈值取 0.5 时的精度,mAP50-95 是在 0.5 到 0.95 每 0.05 取一个阈值再平均。如果 mAP50 很高但 mAP50-95 很低,说明你的模型虽然能找到目标,但框的边界不够准。对于电缆损坏这类需要定位到具体位置的场景,mAP50-95 更值得关注。
看到 mAP50-95 低,我一般先检查训练分辨率。640 分辨率下很多细纹目标只有 10 像素左右,自动下采样后特征已经丢失,此时把输入放大到 1280,或者用更大的模型,通常会有改善。另外可以看验证集图片上的预测框,如果框普遍偏大或偏小,可能是标签里宽度高度计算有问题。
4.2 推理时的 letterbox 与置信度调整
验证通过后,对测试图做推理可以使用 predict 接口:
from ultralytics import YOLO model = YOLO('runs/train/exp/weights/best.pt') results = model.predict(source='images/test', conf=0.25, imgsz=640, save_txt=True, save_conf=True)conf=0.25 是置信度阈值,低于这个值的预测会被丢弃;imgsz 必须和训练时一致,否则输入分辨率变化会导致精度漂移。save_txt=True 会输出预测结果 txt,save_conf=True 会额外把置信度写进 txt。如果你要统计所有可能的误检,可以把 conf 降到 0.1,再人工筛选。
这里有个容易忽略的点:ultralytics 在推理时会自动做 letterbox 填充,让图片长边缩放到 imgsz,短边填充灰色。如果你的数据集中电缆是细长形状,letterbox 后会产生大量无效填充区域,模型可能把填充边缘误检为目标。遇到这种情况,可以试试rect=True让推理时按图片原始宽高比缩放,不强制填充到正方形。
4.3 标签质量检查:空标签、过小框、越界框
输入标签的质量决定模型上限,所以训练前和训练后都要检查。下面脚本扫一下所有 txt:
import glob import os bad_files = [] for txt in glob.glob('labels_yolo/*.txt'): if os.path.getsize(txt) == 0: bad_files.append((txt, 'empty label')) continue with open(txt) as f: for line in f: parts = list(map(float, line.split())) if len(parts) != 5: bad_files.append((txt, 'field count error')) break cls, xc, yc, bw, bh = parts if cls < 0 or cls >= 10: bad_files.append((txt, f'class index {cls}')) break if bw <= 0 or bh <= 0 or bw > 1 or bh > 1: bad_files.append((txt, 'box out of range')) break print(f'bad files: {len(bad_files)}')这个脚本检查三类问题:空标签、字段数量不为 5、框宽高超出 0~1。更严格的越界检查需要结合原图宽高,如果xc + bw/2 > 1,说明框超出图片右边界,可能是标注时画到边缘外。对于 1318 张图的数据集,这类问题通常不多,但混入压缩包时偶尔会发生。检查到坏标签后,直接删除该图片和标签,或手动重标,不要带着问题训练。
4.4 混淆矩阵与类别间误检排查
验证集评估有时会隐藏一个关键问题:类别 A 的目标被误检成类别 B。只看 mAP 很难发现,需要看混淆矩阵。ultralytics 在验证结束后会生成confusion_matrix.png,横轴是真实类别,纵轴是预测类别。如果某个非背景类误检很多,矩阵里相应格子颜色就会偏深。
电缆损坏类别少时,尽量人工检查矩阵。类别 0 预测成类别 1,原因通常是两类目标外观接近,或者标注时类别顺序混乱。解决方向有两个:第一,确认训练标签里的类别索引没有整体平移一位,比如原本是 0,结果标成了 1;第二,如果确实是类间相似,可以用 focal loss 调整难例权重,但我更推荐合并相似类别,减少模型区分压力。类别越少,训练数据越集中,精度反而更容易做上去。
5. 避坑:电缆损坏数据集训练中的五个常见问题与排查
在实际操作中,新手经常会卡在奇怪的地方。这里挑五个我反复遇到的坑,每条都按现象、原因、解决三步写清楚。
5.1 现象:训练刚开始就报错Label class 2 exceeds nc=2 in data.yaml
原因:txt 标签里存在类别索引为 2 的标注,但 data.yaml 里 nc 只有 2,YOLO 类别索引从 0 开始,最大只允许 1。造成这种情况通常是多份数据合并时类别编号冲突,或者标注工具导出的类别没有从 0 重新编号。
解决:先用命令统计标签实际类别:
awk '{ print $1 }' labels_yolo/*.txt | sort -nuawk 利用空格把每行第一个字段打印出来,sort -nu 得到唯一且排序后的类别列表。如果输出是 0 1,说明类别索引正常,报错是 data.yaml 写错;如果输出 0 1 2,就把 nc 改为 3 并补上 names。如果出现 0 1 5,那很可能是某些标签文件本身就标错了,需要打开对应图片确认,而不是盲目扩大 nc 掩盖问题。
5.2 现象:验证集画框显示框偏移,训练 loss 长时间不降
原因:xml 转 txt 时没有按原图实际宽高做归一化,或者图像带 EXIF 旋转信息,读取后宽高互换。电缆照片经常来自不同设备,手机或相机拍摄时存储了方向信息,OpenCV 默认不处理 EXIF,导致最后标签和图像错位。
解决:在读取图像时用cv2.imread(path, cv2.IMREAD_IGNORE_ORIENTATION),禁止自动按 EXIF 旋转,保证和标注工具看到的一致。如果是转换问题,把转换前后的框画在同一张图上对比,确认中心点和宽高完全一致。我一般会在标注阶段就统一去旋转,并在转换脚本里打印每张图的宽高,防止某张异常图拖垮整个训练。
5.3 现象:模型 Precision 高但 Recall 低,小目标全漏掉
原因:YOLO 模型在多次下采样后,小尺寸目标可能缩小到几个像素甚至消失。电缆裂痕、断口等损坏区域在原图中占比小,训练时如果输入分辨率才 640,模型几乎没有足够特征去学习这些细节。
解决:先把训练分辨率提高到 960 或 1280,同时增大 batch 保持显存利用率。如果显存有限,可以改用模型输入尺寸自动调整,或者在预处理中把图片切块,让每个 patch 里的损坏目标占比变大。另一种做法是开启 mosaic 增强,在训练初期把四张图拼成一张,让每一层的特征图都能看到小目标,但最后一个 epoch 要关闭 mosaic,防止目标被切碎。
5.4 现象:训练到中途报错CUDA out of memory,调小 batch 仍然复现
原因:数据集图片尺寸差异大,训练时自动按 batch 内最大尺寸做 padding,导致显存占用不稳定。你把 batch 从 16 调到 8 之后,如果还有特别大的图,仍然会显存溢出。另一个因素是开启了 cache=True,图片被提前加载进显存。
解决:在 ultralytics 里设置rect=True,按宽高比把图片分组,同一个 batch 的尺寸接近,减少 padding 浪费;同时设置batch=-1让框架根据显存自动估算最大 batch。如果还不稳定,把cache=False,关闭显存缓存,换用内存缓存。对于 1318 张图的规模,内存缓存足够,速度也不会慢太多。
5.5 现象:混淆矩阵显示大量背景被误检成损坏类别
原因:部分图片的标签画得太松,把背景纹理也框进来,或者类别定义边界模糊。比如电缆本身有磨损痕迹,标注者把磨损也算成损坏,模型就会学习到「所有纹理复杂区域都输出框」。
解决:把训练集中所有标签画到图片上,逐个检查那些与背景相似的目标。如果确定是误标,删除坏标签并重新标注。如果类别本身模糊,可以增加一个「背景」负样本类别,或者在后处理里把 conf 阈值调高到 0.4,牺牲一定召回换取更干净的输出。更根本的做法是检查测试集是否包含足够多的负样本图片,如果测试集全是正样本,mAP 再高也不能反映实际落地效果。
6. 进阶:导出模型并批量分析预测框与真实框的重合度
训练做完了,别急着把模型塞进业务。我会再做一步批量验证:将测试集预测结果导出成 YOLO 格式,计算每个真实框匹配到的最大 IoU,按目标尺寸把失败样本分类,这样能看出模型在小目标上到底差多少。
6.1 批量预测并保存归一化坐标
用 Python 脚本遍历一张张图片,保存预测框:
import cv2 from pathlib import Path from ultralytics import YOLO model = YOLO('runs/train/exp/weights/best.pt') test_dir = Path('images/test') for img_path in test_dir.glob('*.jpg'): img = cv2.imread(str(img_path)) h, w = img.shape[:2] result = model.predict(str(img_path), conf=0.25, imgsz=640)[0] out_path = img_path.with_suffix('.txt').name with open(f'pred/{out_path}', 'w') as f: for box in result.boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() xc = ((x1 + x2) / 2) / w yc = ((y1 + y2) / 2) / h bw = (x2 - x1) / w bh = (y2 - y1) / h cls = int(box.cls[0]) f.write(f'{cls} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}\n')这段代码输出的是归一化坐标,方便和 ground truth 在同一空间比较。注意,预测结果需要除以当前图片的宽高,如果不除,后续 IoU 计算会因为图片尺寸差异而全错。conf=0.25 是一个起始阈值,使用时可以根据验证集情况调整。
6.2 计算 IoU 并定位失败样本
IoU 函数大家都会写,关键是匹配策略。我一般遍历每个真实框,在预测框里找最大 IoU,如果最大 IoU 低于 0.5,就把这张图和 label 转存到一个 hard_examples 目录,后期针对性增强。
def iou_boxes(box_a, box_b): xa = max(box_a[0], box_b[0]) ya = max(box_a[1], box_b[1]) xb = min(box_a[2], box_b[2]) yb = min(box_a[3], box_b[3]) inter = max(0, xb - xa) * max(0, yb - ya) area_a = (box_a[2] - box_a[0]) * (box_a[3] - box_a[1]) area_b = (box_b[2] - box_b[0]) * (box_b[3] - box_b[1]) return inter / (area_a + area_b - inter + 1e-6)参数说明:bndbox 是绝对像素坐标的 [x1, y1, x2, y2]。最后加 1e-6 是防止分母为 0。如果框坐标是归一化的,分母同样使用归一化面积,结果不变,因为同图缩放不影响 IoU。把每张图的平均最大 IoU 排序,排名靠后的那张大概率是模糊、遮挡或者目标太小,这些样本需要单独处理,而不是无脑调轮数。
从那以后,我每次拿到标注好的数据集,都会强制走一遍「标签审计 → 数据划分检查 → 训练 → 批量 IoU 分析」的流程。这套流程走下来,模型翻车的概率低了很多。希望帮到你。
本文还有配套的精品资源,点击获取