
简介本数据集面向工业质检与线缆缺陷检测方向的算法工程师、研究生及计算机视觉学习者提供电缆钢丝绳表面缺陷的标注数据可用于目标检测模型的训练与验证。数据采用Pascal VOC与YOLO双格式组织包含1800张jpg图片及一一对应的1800个xml标注文件和1800个txt标注文件标注类别为break、thunderbolt、wear三类对应框数分别为1235、750、1055总框数3040均使用labelImg按矩形框规则标注且部分图片经过增强处理以提升样本多样性。压缩包为7z格式共2000个文件以xml与txt标注文件为主整体约118.59MB目录结构清晰便于直接接入主流检测框架。目前已有1268人学习下载适合需要快速构建线缆缺陷检测基线、验证标注质量或开展对比实验的读者参考使用。1. 电缆钢丝绳线缆缺陷检测数据集1800 张 VOCYOLO 双格式到底怎么用手上拿到一个电缆钢丝绳线缆缺陷检测数据集VOC 和 YOLO 两种标注格式1800 张图3 个类别。很多人第一反应是直接丢进 YOLOv8 开训结果 mAP 卡在 0.5 上不去回头查半天才发现是类别定义没对齐、VOC 的 XML 里 bndbox 坐标越界、或者训练集里混进了几乎无缺陷的负样本。这个数据集解决的就是“从零搭建线缆表面缺陷检测模型”时最耗时的数据准备环节——它把 Pascal VOC 的 XML 标注和 YOLO 的 TXT 标注同时打包省去格式转换的麻烦适合做工业质检方向的目标检测入门和基线验证。3 个类别通常对应断股、磨损、锈蚀这类典型缺陷1800 张的规模不算大但足够跑通一个可用的基线模型也适合用来验证数据增强策略和损失函数调参的效果。如果你正在做电缆、钢丝绳、线缆相关的缺陷检测项目或者想找一个工业场景的小规模数据集练手 YOLO 全流程这个数据集是一个务实的起点。2. 先搞清楚 VOC 和 YOLO 两套标注到底差在哪2.1 Pascal VOC 的 XML 结构与字段含义Pascal VOC 格式是目标检测领域最早的标注规范之一每张图对应一个 XML 文件文件名与图片名一致。核心结构是annotation根节点下包含filename、size、object等子节点。每个object里记录类别名name和边界框bndboxbndbox 里有xmin、ymin、xmax、ymax四个值单位是像素原点在图片左上角。annotation folderimages/folder filenamecable_001.jpg/filename size width640/width height480/height depth3/depth /size object namebroken_strand/name !-- 类别名需与训练配置一致 -- poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin120/xmin !-- 左上角 x像素坐标 -- ymin85/ymin !-- 左上角 y -- xmax310/xmax !-- 右下角 x -- ymax240/ymax !-- 右下角 y -- /bndbox /object /annotation这里有几个容易翻车的点size里的宽高必须和实际图片一致否则转换 YOLO 格式时归一化会出错name的大小写和拼写必须和你的data.yaml里names列表完全一致差一个字母就会导致该类训练时被忽略difficult标记为 1 的目标在评估时通常会被跳过但训练时是否忽略取决于框架实现YOLO 系列默认不区分 difficult。2.2 YOLO 的 TXT 格式与归一化坐标YOLO 格式每张图对应一个 TXT 文件每行一个目标格式为class_id x_center y_center width height所有坐标都是相对于图片宽高的归一化值范围 0 到 1。class_id 从 0 开始按类别列表顺序编号。0 0.335938 0.338542 0.296875 0.322917 1 0.712500 0.550000 0.150000 0.200000第一行表示类别 0中心点 x 为图片宽度的 33.59%中心点 y 为高度的 33.85%框宽占图片宽度的 29.69%框高占高度的 32.29%。这种归一化设计让模型对输入尺寸不敏感训练时可以统一 resize 到 640×640 而不需要重新计算标注。VOC 转 YOLO 的公式是x_center (xmin xmax) / 2 / widthy_center (ymin ymax) / 2 / heightw (xmax - xmin) / widthh (ymax - ymin) / height。转换时如果出现坐标小于 0 或大于 1说明原始 XML 里的 bndbox 越界了需要裁剪到图片边界内否则训练时 YOLO 的 dataloader 会直接报错或者静默丢弃该目标。2.3 两套格式的选型建议与转换时机拿到双格式数据集不要两个都用。我的习惯是如果后续用 YOLOv5/v8/v11 系列训练直接用 TXT 格式省去转换步骤如果要做格式对比实验、或者需要把标注导入 LabelImg 做二次修正用 XML 格式更方便因为 LabelImg 原生支持 VOC。转换时机上建议在划分训练集/验证集之前就统一成一种格式避免划分后还要对两套文件做同步操作容易漏文件。注意转换前先备份原始 XML转换脚本跑完后抽查 10 张图的 TXT 内容用cat看几行确认 class_id 和坐标没有明显异常。3. 用脚本把 VOC 批量转成 YOLO 并做完整性校验3.1 转换脚本的完整实现下面这个脚本处理整个数据集目录遍历所有 XML输出对应的 TXT 到指定目录同时记录转换日志。import os import xml.etree.ElementTree as ET from pathlib import Path # 类别列表顺序决定 class_id必须与后续训练配置一致 CLASSES [broken_strand, wear, corrosion] def convert_bbox(size, box): 将 VOC 的 xmin,ymin,xmax,ymax 转为 YOLO 归一化格式 dw 1.0 / size[0] dh 1.0 / size[1] x_center (box[0] box[1]) / 2.0 * dw y_center (box[2] box[3]) / 2.0 * dh w (box[1] - box[0]) * dw h (box[3] - box[2]) * dh # 裁剪到 [0,1]防止越界坐标导致训练报错 x_center max(0, min(1, x_center)) y_center max(0, min(1, y_center)) w max(0, min(1, w)) h max(0, min(1, h)) return x_center, y_center, w, h def convert_xml_to_txt(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in CLASSES: print(f[跳过] 未知类别 {name} in {xml_path}) continue cls_id CLASSES.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 边界裁剪防止标注超出图片范围 xmin max(0, min(w, xmin)) xmax max(0, min(w, xmax)) ymin max(0, min(h, ymin)) ymax max(0, min(h, ymax)) if xmax xmin or ymax ymin: print(f[跳过] 无效框 in {xml_path}) continue xc, yc, bw, bh convert_bbox((w, h), (xmin, xmax, ymin, ymax)) lines.append(f{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) if lines: with open(txt_path, w) as f: f.write(\n.join(lines)) else: print(f[警告] 无有效目标: {xml_path}) def batch_convert(xml_dir, txt_dir): os.makedirs(txt_dir, exist_okTrue) xml_files list(Path(xml_dir).glob(*.xml)) print(f共找到 {len(xml_files)} 个 XML 文件) for xml_file in xml_files: txt_file Path(txt_dir) / (xml_file.stem .txt) convert_xml_to_txt(str(xml_file), str(txt_file)) print(转换完成) if __name__ __main__: batch_convert(./annotations, ./labels)脚本逻辑说明CLASSES列表的顺序直接决定 class_id必须和训练时的data.yaml里names顺序一致。convert_bbox里做了归一化和裁剪防止个别标注越界导致后续训练报错。batch_convert遍历目录下所有 XML输出同名 TXT。参数方面xml_dir指向 VOC 标注所在目录txt_dir是输出目录脚本会自动创建。3.2 转换后的校验用 Python 检查标注一致性转换完不能直接开训先跑一个校验脚本检查图片和标注文件是否一一对应、坐标是否合法、类别分布是否严重不均衡。import os from pathlib import Path from collections import Counter IMG_DIR ./images LBL_DIR ./labels CLASSES [broken_strand, wear, corrosion] def validate(img_dir, lbl_dir): img_files {p.stem for p in Path(img_dir).glob(*.jpg)} lbl_files {p.stem for p in Path(lbl_dir).glob(*.txt)} # 检查缺失 missing_lbl img_files - lbl_files missing_img lbl_files - img_files if missing_lbl: print(f[缺失标注] {len(missing_lbl)} 张图无对应 TXT: {list(missing_lbl)[:5]}) if missing_img: print(f[缺失图片] {len(missing_img)} 个 TXT 无对应图: {list(missing_img)[:5]}) # 统计类别分布 counter Counter() for lbl in Path(lbl_dir).glob(*.txt): with open(lbl) as f: for line in f: parts line.strip().split() if len(parts) 5: counter[int(parts[0])] 1 print(类别分布:) for cls_id, count in sorted(counter.items()): print(f {CLASSES[cls_id]}: {count} 个目标) # 检查空标注文件 empty [p.name for p in Path(lbl_dir).glob(*.txt) if p.stat().st_size 0] if empty: print(f[空标注] {len(empty)} 个文件为空: {empty[:5]}) if __name__ __main__: validate(IMG_DIR, LBL_DIR)校验脚本输出三类信息图片和标注的对应关系、每个类别的目标数量、空标注文件列表。如果某个类别目标数极少比如少于 50训练时该类几乎学不到东西需要考虑过采样或者数据增强。空标注文件如果是负样本可以保留少量但比例不要超过 10%否则模型会偏向预测背景。3.3 划分训练集和验证集的正确姿势1800 张图按 8:2 划分训练集 1440 张验证集 360 张。不要用随机划分因为同一根线缆的连续帧可能同时出现在训练集和验证集里导致验证指标虚高。我的做法是按文件名前缀分组同一组只出现在一个集合里。import random from pathlib import Path import shutil random.seed(42) # 固定随机种子保证可复现 IMG_DIR Path(./images) LBL_DIR Path(./labels) OUT_TRAIN_IMG Path(./dataset/images/train) OUT_VAL_IMG Path(./dataset/images/val) OUT_TRAIN_LBL Path(./dataset/labels/train) OUT_VAL_LBL Path(./dataset/labels/val) for d in [OUT_TRAIN_IMG, OUT_VAL_IMG, OUT_TRAIN_LBL, OUT_VAL_LBL]: d.mkdir(parentsTrue, exist_okTrue) imgs sorted(IMG_DIR.glob(*.jpg)) random.shuffle(imgs) split int(len(imgs) * 0.8) train_imgs imgs[:split] val_imgs imgs[split:] for img_list, img_out, lbl_out in [(train_imgs, OUT_TRAIN_IMG, OUT_TRAIN_LBL), (val_imgs, OUT_VAL_IMG, OUT_VAL_LBL)]: for img in img_list: shutil.copy(img, img_out / img.name) lbl LBL_DIR / (img.stem .txt) if lbl.exists(): shutil.copy(lbl, lbl_out / lbl.name) print(f训练集 {len(train_imgs)} 张验证集 {len(val_imgs)} 张)划分完检查一下两个集合的类别分布是否接近如果验证集里某个类别一个目标都没有说明划分有问题需要调整随机种子或改用分层抽样。4. 用 YOLOv8 跑通第一个基线模型4.1 data.yaml 的写法与三个必调参数YOLOv8 训练需要一份data.yaml指定路径、类别数和类别名。path: ./dataset train: images/train val: images/val nc: 3 names: 0: broken_strand 1: wear 2: corrosionpath是数据集根目录train和val是相对路径。nc必须等于names的长度写错会直接报错。names的顺序必须和转换脚本里的CLASSES一致否则类别会错位。训练命令yolo detect train data./dataset/data.yaml modelyolov8n.pt epochs100 imgsz640 batch16 lr00.01三个必调参数imgsz默认 640如果原始图片分辨率远大于 640可以试 1280但显存占用会翻倍batch根据显存调整16 是 8GB 显存的保守值lr0初始学习率0.01 是 YOLOv8 的默认值小数据集可以降到 0.001 防止过拟合。4.2 训练日志怎么看loss 曲线和 mAP 的解读训练启动后控制台会输出每个 epoch 的 box_loss、cls_loss、dfl_loss 和 mAP50、mAP50-95。box_loss 下降说明边界框回归在收敛cls_loss 下降说明分类在改善。如果 box_loss 震荡不降检查标注框是否有大量越界或宽高为 0 的情况。mAP50 在 30 个 epoch 后应该开始明显上升如果 50 个 epoch 还趴在 0.1 以下大概率是类别配置错了或者标注格式有问题。验证集 mAP 和训练集 mAP 差距超过 0.2说明过拟合需要加数据增强或者减少模型参数量。YOLOv8 默认开启 mosaic 和 mixup小数据集上 mosaic 可能让模型学到不真实的拼接边界可以试mosaic0.5降低概率。4.3 推理测试用训练好的权重跑单张图训练完成后权重保存在runs/detect/train/weights/best.pt。用下面命令跑单张图推理yolo detect predict modelruns/detect/train/weights/best.pt source./test.jpg conf0.25 saveTrueconf是置信度阈值默认 0.25漏检多就降到 0.1误检多就升到 0.5。推理结果会保存在runs/detect/predict/下打开图片看框的位置和类别标签是否正确。如果框的位置明显偏移检查推理时的imgsz是否和训练时一致。5. 避坑与排查1800 张小数据集训练最常见的 5 个翻车点5.1 类别名大小写不一致导致某类完全不学现象训练日志里某个类别的 mAP 始终为 0其他类别正常。原因XML 里类别名是Broken_Strand但data.yaml里写的是broken_strandYOLO 匹配不到该类目标被当作背景。解决统一用转换脚本里的CLASSES列表转换和训练配置从同一个变量生成不要手写两遍。5.2 标注框越界导致训练中途报错现象训练到某个 epoch 突然报AssertionError: x1 x2或类似错误。原因个别 XML 里 bndbox 的 xmax 小于 xmin或者坐标超出图片尺寸。解决转换脚本里已经做了裁剪和有效性检查如果还有问题用校验脚本定位具体文件手动修正或删除该标注。5.3 验证集 mAP 虚高同一根线缆的图泄漏到两个集合现象验证集 mAP 0.95但实际推理时漏检严重。原因连续帧或同一根线缆的不同角度图被随机分到了训练集和验证集模型记住了背景而不是缺陷特征。解决按文件名前缀或拍摄批次分组划分确保同一组只出现在一个集合。如果数据集没有分组信息用感知哈希做图片去重把相似图分到同一侧。5.4 负样本比例过高导致模型偏向预测背景现象推理时大量缺陷没框出来置信度普遍偏低。原因数据集中混入了大量无缺陷的线缆图且都给了空 TXT模型学到“大部分图没有缺陷”的先验。解决负样本比例控制在 5% 到 10% 之间超出的部分从训练集移除只保留少量在验证集里评估误检率。5.5 图片格式不统一导致部分图读取失败现象训练时提示cannot identify image file或某些图被跳过。原因数据集里混了.png、.bmp和.jpg而data.yaml或 dataloader 只匹配了.jpg。解决统一转成.jpg用 PIL 批量转换同时更新标注文件名与图片名一致。from PIL import Image from pathlib import Path for img_path in Path(./images).glob(*): if img_path.suffix.lower() in [.png, .bmp, .tiff]: img Image.open(img_path).convert(RGB) img.save(img_path.with_suffix(.jpg), JPEG, quality95) img_path.unlink() # 删除原文件6. 小数据集提点技巧从 0.6 到 0.8 的几个实操手段1800 张 3 类别基线模型 mAP50 大概在 0.55 到 0.65 之间。想再往上提靠的不是换更大的模型而是把数据用透。第一个手段是离线增强对训练集里的缺陷区域做随机旋转、亮度抖动、高斯噪声每张图生成 2 到 3 个变体把训练集扩到 4000 张左右。注意增强只对训练集做验证集保持原样。第二个手段是 copy-paste 增强把缺陷区域裁剪出来随机粘贴到正常线缆图的合理位置模拟更多缺陷形态。这个操作需要保证粘贴后的框仍然有效粘贴位置不要超出图片边界。import cv2 import random import numpy as np def copy_paste_aug(img, bboxes, defect_patches, n3): 将缺陷 patch 随机粘贴到图片上返回新图和新增框 h, w img.shape[:2] new_boxes [] for _ in range(n): patch, (px1, py1, px2, py2) random.choice(defect_patches) ph, pw patch.shape[:2] if ph h or pw w: continue x random.randint(0, w - pw) y random.randint(0, h - ph) # 简单混合避免边缘生硬 roi img[y:yph, x:xpw] img[y:yph, x:xpw] cv2.addWeighted(roi, 0.3, patch, 0.7, 0) new_boxes.append([x, y, xpw, yph]) return img, bboxes new_boxes第三个手段是调整损失函数权重。YOLOv8 默认的 box_loss 和 cls_loss 权重是 7.5 和 0.5小数据集上可以试box10让模型更关注框的回归精度。如果某个类别目标特别小开close_mosaic10在最后 10 个 epoch 关闭 mosaic让模型在真实分布上微调。第四个手段是测试时增强TTA推理时对同一张图做水平翻转和不同尺度缩放合并检测结果mAP 通常能涨 1 到 2 个点代价是推理速度慢 3 倍。yolo detect predict modelbest.pt source./test.jpg augmentTrue conf0.2augmentTrue开启 TTA。这个参数在验证阶段也可以开但会拉长验证时间。我的习惯是训练完先用默认参数跑一遍验证如果 mAP 离预期差得不多再开 TTA 看能补多少补得多就说明模型对尺度或翻转敏感回头在训练时加对应的数据增强比推理时补救更划算。最后说一个血泪经验小数据集上不要一上来就调模型结构先把标注质量查三遍。我遇到过 mAP 死活上不去最后发现是 200 多张图的标注框把整根线缆都框进去了模型学的是“整图都是缺陷”推理时自然框不准。标注框应该紧贴缺陷区域留 2 到 3 个像素的边距就够了。每次改完标注重新训练前用校验脚本再跑一遍类别分布确认没有类别目标数突然掉到个位数。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。