资讯动态

风力叶片缺陷检测数据集解析:VOC标注转YOLO训练全流程

发布时间:2026/9/26 11:08:04 来源:尧图企业网站定制
简介风力叶片缺陷检测数据集专为风电运维与无人机巡检场景设计面向计算机视觉算法工程师、科研人员及相关专业学生可用于训练和评估目标检测模型识别排水孔受损、雷击、污垢、漏油、PU胶带、表面裂纹及侵蚀等多类型缺陷。压缩包共2000个XML标注文件整体大小约175.83MB全部采用PASCAL VOC标准格式便于直接解析或转换为YOLO、COCO等主流训练格式。该数据集整体涵盖5113张图像缺陷类别覆盖全面标注框位置信息完整适合作为叶片缺陷检测、智能巡检项目的训练与验证数据。目前已有1909人学习下载。利用这些XML标注用户可以快速搭建数据加载流程结合开源检测框架开展模型微调与性能对比也能针对不同缺陷类型进行统计分析和可视化支撑算法评测、论文实验及工程落地。1. 风力叶片缺陷检测数据集5113张VOC标注能解决哪一类巡检难题做风电巡检的算法工程师基本都遇到过这个局面飞一圈拍回上千张叶片照片甲方要求把裂纹、漏油、雷击点标出来但你手上连一个像样的训练集都没有。人工看片一天最多看两三百张高空拍摄的叶片缺陷又小又模糊肉眼都容易漏。这套风力叶片缺陷检测数据集就是为这个场景准备的——5113张图全部按 PASCAL VOC XML 格式做了目标级标注资源里的 PASICAL VOC 写法其实就是同一套 VOC XML 规范覆盖排水孔受损、雷击、污垢、漏油、PU 胶带、表面裂纹、侵蚀七类典型问题。适合三类人做风电运维视觉系统的工程师、需要工业缺陷数据的算法岗求职者、以及用 YOLO 系做毕设但苦于没有真实工业数据的学生。有它打底你省掉的不只是标图时间还有到处找数据碰壁的时间。2. 从 XML 标注到可训练样本VOC 格式解析与七类缺陷的边界判断拿到资源先别急着训模型先把 XML 读明白。很多人在这一步偷懒直接拿现成脚本转换结果坐标偏移、类别对不上回头查半天才发现是格式理解错了。这一章把 VOC 的结构、字段、七类缺陷的视觉差异讲透再给一段统计脚本让你在训练前就把数据集的底细摸清楚。2.1 VOC 目录结构与 XML 字段拆解下载解压后目录结构基本沿用 VOC 惯例JPEGImages放原图Annotations放同名 XMLImageSets/Main里是划分好的训练/验证列表。如果作者没提供ImageSets就按第 3 章的脚本自己划分。先打开一个 XML 看一眼字段这是所有后续转换的地基。字段含义使用注意folder图片所在目录名很多工具乱写代码里不要依赖它filename图片文件名要和 JPEGImages 里实际文件严格对应size / width,height图片宽高转 YOLO 格式时的归一化分母但后面有个大坑object / name缺陷类别名直接决定你训练时的类别字典object / truncated目标是否出界一般忽略但出界严重的框建议先过滤object / difficult难例标记VOC 惯例是 difficult1 不参与训练object / bndboxxmin,ymin,xmax,ymax 像素坐标左上角为原点注意有些工具会写成浮点这里有个容易翻车的点一张图往往有多个object同一个叶片上同时存在裂纹和污垢很常见解析时必须遍历全部object不能只取第一个。另一个是坐标越界问题——我遇到过ymax比height还大、xmin为负数的情况这种坏框不处理训练时 Loss 会莫名震荡。所以拿到数据集第一天先写脚本把这些框标红能删就删能裁就裁。2.2 七类缺陷的样本特征与标注边界这套数据集的类别不是随便拍的每一类在风电运维里都有明确指向。看表类别典型外观易混对象排水孔受损叶片后缘排水孔附近破裂、变形小目标为主表面裂纹雷击黑色烧蚀焦痕常伴随大面积结构损伤框通常很大污垢污垢表面灰尘、泥污块状边界模糊侵蚀漏油深色油渍从接缝处渗出条状或滴状污垢PU 胶带人为粘贴的修补胶带或保护膜长条状叶片边缘表面裂纹细线状裂缝低对比度极小目标排水孔受损侵蚀前缘或表面材料磨损、发白、砂面质感污垢注意一个反直觉的地方PU 胶带本身不是缺陷而是“正常但需要记录位置”的物体。风电运维里胶带翘边、脱落是事故前兆所以它也进了标注列表。但对检测模型来说它是个形状特征极强的类长条、规整、样本量大模型很容易过拟合到“只要长条就报 PU 胶带”这在第 4 章的避坑里专门讲。污垢和侵蚀的边界最模糊。我的经验是看纹理污垢是附着在表面的颗粒感侵蚀是材料本身变暗发白。但标注员不见得懂这个所以原始标注里这两类互相串是正常的训练前做好心理准备必要时直接合并。2.3 盘家底用脚本统计每类样本量与框尺寸分布训练前先跑一遍统计能省掉后面大量返工。我一般先看三件事每个类别有多少实例、框尺寸相对原图的比例、XML 里有没有类别名的拼写变体比如crack和surface_crack同时存在。这段脚本直接放在解压目录下跑import xml.etree.ElementTree as ET from pathlib import Path from collections import Counter, defaultdict ann_dir Path(Annotations) counts Counter() box_stats defaultdict(list) for xml_file in ann_dir.glob(*.xml): tree ET.parse(xml_file) root tree.getroot() # 注意这里先用 XML 里的 size真实分辨率校验后面再说 img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) for obj in root.findall(object): name obj.find(name).text.strip() counts[name] 1 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) w xmax - xmin h ymax - ymin # 记录绝对像素和相对原图比例 box_stats[name].append((w, h, w / img_w, h / img_h)) for name, n in counts.most_common(): print(f{name}: {n} instances) # 检查每个类别的框宽高比中位数判断是否适合直接训练 for name, stats in box_stats.items(): ratios [s[2] for s in stats] # 相对原图宽的比例 med sorted(ratios)[len(ratios) // 2] print(f{name}: median relative width {med:.3f})逻辑说明counts统计每个类别的实例总数box_stats记录每类框的绝对尺寸和相对原图比例。相对比例这个数很关键——如果某个类别的中位数宽度不到原图的 2%说明它是典型小目标后面训练必须加大输入尺寸或做切图否则直接训就是白烧显卡。参数说明img_w和img_h来自 XML 的size节点这一步只是快速摸底真要转格式必须用真实图片分辨率。另外这段脚本会把difficult1的框也统计进去如果想严格对齐训练分布加一行判断即可。3. 从 VOC 到 YOLO转换脚本、数据划分与训练前的最后一公里VOC XML 信息全但主流检测框架吃的是 YOLO 格式的 txt。这一章讲转换原理、给出可直接跑的脚本再讲一个很多人忽视的问题——怎么划分训练集和验证集才能让指标不虚高。3.1 为什么目标检测训练端口普遍选 YOLO 系格式VOC XML 里包含 pose、truncated、difficult 这些字段对训练来说大部分是冗余信息。YOLO 格式每行就五个数class_id cx cy w h全部归一化到 0~1简单干净。Ultralytics YOLOv8、YOLO11、mmyolo 都吃这套后续导出 ONNX 部署也顺畅。另外你从这套数据集训出来的权重大概率要接 HALCON 或者 OpenCV 的推理流程YOLO txt 转成部署脚本最省事。转换的核心是把像素坐标变成归一化中心点坐标先算cx (xmin xmax) / 2 / img_w再把框宽w (xmax - xmin) / img_w。有个细节归一化用的是图片宽高做分母所以图片分辨率必须准确如果错 10 个像素小目标框就会偏出去一个身位。3.2 VOC 转 YOLO 脚本与逐行说明我在这个数据集上用的转换脚本如下路径按你解压后的目录改import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image # 类别名和 id 的映射顺序必须与训练 yaml 完全一致 class_map { drain_hole_damage: 0, lightning_strike: 1, dirt: 2, oil_leak: 3, pu_tape: 4, surface_crack: 5, erosion: 6, } ann_dir Path(Annotations) img_dir Path(JPEGImages) out_dir Path(labels) out_dir.mkdir(exist_okTrue) for xml_path in ann_dir.glob(*.xml): # 用真实图片分辨率做归一化分母别信 XML 里的 size img_path img_dir / (xml_path.stem .jpg) if not img_path.exists(): img_path img_dir / (xml_path.stem .png) with Image.open(img_path) as img: img_w, img_h img.size tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_map: print(funknown class: {name} in {xml_path.name}) continue cls_id class_map[name] 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) # 坐标裁剪防止越界框导致训练崩溃 xmin max(0.0, min(xmin, img_w - 1)) xmax max(0.0, min(xmax, img_w - 1)) ymin max(0.0, min(ymin, img_h - 1)) ymax max(0.0, min(ymax, img_h - 1)) if xmax xmin or ymax ymin: continue cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) if lines: out_path out_dir / (xml_path.stem .txt) out_path.write_text(\n.join(lines), encodingutf-8)逻辑说明这段脚本先打开真实图片拿宽高再遍历 XML 里的所有object把像素坐标转成归一化中心点坐标最后写入与图片同名的 txt 文件。class_map是类别名到数字 id 的字典这一步的 id 顺序必须和第 5 章wind_blade.yaml里的names完全一致否则训练出来的结果全错这是最常见的翻车点。参数说明img_w、img_h来自 PIL 读取的真实分辨率不是 XML 里的size。坐标裁剪这段代码是我吃过大亏后加上去的——数据集里偶尔有越界框不裁剪的话归一化坐标会超过 1 或小于 0某些框架会直接报错有些框架不报错但 Loss 乱跳。xmax xmin的过滤则是丢弃宽度为 0 的坏框。3.3 数据划分按影像序列切别随机打散风电叶片巡检有个显著特点同一个巡检批次里连续拍的几十帧高度相似比如同一片叶片的裂纹在连续 5 张图里都存在只是角度稍微不同。如果随机划分同一批次的相似帧会同时落进 train 和 val验证集指标虚高部署到新场景立刻原形毕露。常见做法是先把图片按文件名前缀分组同一组的图作为整体切分。我用的是下面这个分组脚本import random from pathlib import Path images sorted(Path(JPEGImages).glob(*.*)) groups {} # 按文件名前两段分组实际前缀规则看你的文件名命名 for img in images: key _.join(img.stem.split(_)[:2]) groups.setdefault(key, []).append(img.stem) group_names list(groups.keys()) random.seed(42) random.shuffle(group_names) split int(len(group_names) * 0.8) train_groups set(group_names[:split]) val_groups set(group_names[split:]) with open(train.txt, w) as f: for g in train_groups: for stem in groups[g]: f.write(fimages/{stem}.jpg\n) with open(val.txt, w) as f: for g in val_groups: for stem in groups[g]: f.write(fimages/{stem}.jpg\n) print(ftrain images: {sum(len(groups[k]) for k in train_groups)}) print(fval images: {sum(len(groups[k]) for k in val_groups)})逻辑说明key _.join(img.stem.split(_)[:2])的意思是取文件名的前两段作为分组标识比如blade_20240115_001.jpg会归到blade_20240115组。实际命名规则取决于你下载到的文件先ls看一眼再定前缀规则我一般会手动验证几个分组确保同一个叶片的前后帧没有拆散。参数说明split 0.8是训练集比例先跑通流程够用。这套数据有 5113 张按 8:2 切是 4090 张训练、1023 张验证。如果后面要加数据增强或做交叉验证可以改成 90/10但别低于 8:2否则小目标类别的验证样本太少mAP 抖动会很大。提示train.txt 里的路径是相对路径取决于第 5 章 yaml 里path字段怎么配两者要对应上。4. 风力叶片缺陷检测常见问题与排查五个实测踩坑记录这一章写我在类似风电叶片数据上实际踩过的坑。有些坑是 VOC 格式本身带来的有些是这套七分类结构特有的。每一条都按「现象 → 原因 → 解决」写你训练时遇到类似问题可以直接对照。4.1 XML 的 size 与真实图片分辨率不一致框集体偏移现象训练 Loss 正常下降但验证时 mAP 很低画出来发现所有框向右下方偏移偏移量还不固定。原因数据集里部分 XML 的size写的是缩略图的尺寸比如 1240×768而JPEGImages里实际是 1920×1080。转换代码直接用了 XML 的 size 做归一化分母导致 cx、cy 被放大了。解决转换脚本里强制用 PIL 或 OpenCV 读取真实图片宽高忽略 XML 的 size 节点。虽然多了 IO 开销但换来坐标正确值得。如果已经用错 size 转换并训了一半别硬着头皮继续重新转一次再训模型很快能拉回来。4.2 污垢与侵蚀互相抢样本混淆矩阵一片红现象mAP50 看着有 0.6但单独看混淆矩阵dirt 和 erosion 两条竖线交叠严重互相误判率超过 30%。原因这两类在视觉上都是表面材料的灰度变化原始标注者判定标准不一致。同一块区域这张图标 dirt那张图标 erosion模型学到的是噪声。解决两个选择。要么合并成一类surface_degradation快速拿基线要么抽 500 张两个类别置信度接近的难例人工复核重新标注后加入训练。工业项目我建议先合并跑通交付基线再拆细类做迭代。别指望训练本身能学会两者的边界标注噪声不是模型能扛住的。4.3 裂纹、排水孔受损在 512 输入下几乎全漏现象总 mAP 还行但 surface_crack 类别的 AP 不到 0.2现场照片里细裂纹根本没框出来。原因裂纹的框宽度可能只占原图的 1%~3%缩放到 512 后只剩几个像素特征完全消失。解决训练输入尺寸提到 1024 或 1280推理时也保持同样尺寸。如果显存扛不住做切图推理——把 1920×1080 的原图切成 4 块重叠区域分别推理再合并这类做法在工业缺陷检测里很常见可以直接用 SAHI 库。切图时重叠率设 10%~20%防止裂纹正好被切刀切掉。4.4 PU 胶带样本多且长条规整模型学出一堆误报现象误报框里大量出现把叶片边缘、反光条当成 PU 胶带的情况且置信度还很高。原因PU 胶带本身不是缺陷它的视觉特征是长条、浅色、贴合叶片表面和叶片边缘结构相似。再加上它样本量大模型倾向于把“长条物”全归过去。解决别把它当普通缺陷类看待。训练完成后单独计算它的 AP和真实缺陷分开评估。推理时对长宽比大于 5、面积占比超过某个阈值的框降权。更彻底的方案是把 PU 胶带从目标检测里拆出去用分割模型单独建模因为它本质是区域问题不是物体问题。4.5 雷击大框压制旁边的小目标框NMS 一刀切现象雷击框动辄占原图三分之一当雷击和裂纹同时出现在一张图上时NMS 后小裂纹框被删掉现场复核时发现漏检。原因NMS 在同类别内按 IoU 和置信度压框。雷击框和旁边的小框虽然 IoU 不高但后处理在某些实现里会对所有框做全局抑制大框置信度又高小框就被连坐了。解决两个口子。训练时关闭 agnostic NMS只做类别内 NMS推理后对雷击类别单独用更低的 IoU 阈值比如 0.3其他类保持 0.5。如果项目允许拆模型把雷击单独用一个分支做不要和细小缺陷混在一起后处理。5. 把数据集跑起来YOLOv8 配置、增强策略与 mAP 验证前四章把数据和坑都盘完了这一章上实战。我以 Ultralytics YOLOv8 为例讲配置和训练这套流程换成 YOLO11 也成立。重点讲三个环节数据集 yaml 怎么写、增强参数怎么调、验证结果怎么看。5.1 数据集 yaml 与训练命令先把转换好的数据按 VOC 风格整理成训练目录。我的组织方式是images/train、images/val、labels/train、labels/val然后写一个 yamlpath: ./wind_blade train: images/train val: images/val nc: 7 names: 0: drain_hole_damage 1: lightning_strike 2: dirt 3: oil_leak 4: pu_tape 5: surface_crack 6: erosionnames的 id 顺序就是第 3 章class_map里定义的顺序两个地方不一致会出大问题——你的 loss 正常降但画出来的框全对应到错误类别上。path是相对路径train和val是子目录yaml 和wind_blade目录的相对位置要对上。训练命令yolo detect train datawind_blade.yaml modelyolov8m.pt \ imgsz1024 epochs120 batch8 patience20 \ projectruns/wind_blade nameexp1逻辑说明yolov8m是精度和速度的平衡点。这套数据里有裂纹这种小目标yolov8s容量偏低yolov8l如果没有大显存显卡训练时间翻倍。imgsz1024是特意选的——512 会把细裂纹直接抹掉1280 能进一步提升小目标召回但对显存要求高1024 先用起来后续有机器再往上加。参数说明batch8按 12G 显存估算如果是 8G 卡就降到 4或者开启amp混合精度。patience20是早停轮数mAP 连续 20 轮不涨就停省时间。epochs120对 4090 张训练图来说够用过多反而容易过拟合到 PU 胶带这类大样本上。5.2 小目标增强与推理设置风电叶片的背景相对单一增强方向不用太激进。我的习惯是保留 mosaic 和轻微翻转但不开太大的 copy-paste——细裂纹特征本身就弱拼接增强会把裂纹的走向打乱。推理阶段倒是可以激进一点工业场景宁多勿漏from ultralytics import YOLO model YOLO(runs/wind_blade/exp1/weights/best.pt) results model.predict( sourcetest_images, imgsz1280, conf0.15, iou0.5, augmentTrue, )逻辑说明conf0.15是故意放低的。缺陷检测的漏检代价远高于误报漏一个裂纹可能让叶片带病运行一个月多几个误报框只是让复核人多点两下鼠标。augmentTrue走 TTA通过翻转、缩放等操作融合多个推理结果小目标召回能稳定提升代价是推理时间涨一倍。参数说明imgsz1280推理时比训练时大这是允许的相当于把 1024 训练的模型用在 1280 分辨率上对小目标友好。如果显存不够把 1280 改成 1024别硬撑。5.3 验证阶段看什么mAP50、mAP50-95 与混淆矩阵训练结束后runs/wind_blade/exp1/目录下有一堆结果。我最先看两个文件results.csv和confusion_matrix.png。import pandas as pd df pd.read_csv(runs/wind_blade/exp1/results.csv) # 打印最后一条验证记录 print(df[[metrics/mAP50(B), metrics/mAP50-95(B)]].tail(1))逻辑说明mAP50-95远低于mAP50说明模型框的定位精度不够稳——缺陷检测和通用物体检测不一样通用场景框差几个像素没事缺陷框需要贴合实际损伤区域定位不准直接影响后续运维决策。这两个数差距如果超过 20 个点优先检查是不是某个类别的框标注本身就不准。参数说明混淆矩阵里重点看两个位置——主对角线是各类的正确率背景列上的点代表漏检。如果surface_crack大量落在背景列说明它没被检出这时候再去调增强和阈值如果dirt和erosion交叉回到 4.2 的合并方案。6. 进阶玩法用已训练权重做半自动标注回头再补一遍雷击与裂纹训完一轮不等于结束。真实项目里 5113 张图往往不够覆盖一个新风场的拍摄条件积累更多数据是常态。我的做法是拿这个模型当预标注工具把标注成本再压一档。用best.pt直接跑新拍回来的巡检图输出 YOLO 格式的 txtyolo predict modelruns/wind_blade/exp1/weights/best.pt \ sourcenew_images save_txtTrue save_confTruesave_txtTrue会在每个图片同目录下生成同名 txt 文件里面是class_id cx cy w h conf格式。把原图和 txt 放进一个目录用 LabelImg 或 CVAT 打开时选 YOLO 预标注目录标注员只需要改错框、补漏框。实际做过一次5000 张新图的标注时间能从一周压到两天多难点只在于模型漏检的那部分需要人工补。这套流程我跑下来最顺的方式是先让模型以conf0.15的阈值大批量出框宁可多给标注员一些候选框也不要一开始就漏掉。另外一个值得做的操作是难例回灌。从推理结果里抽出置信度在 0.2~0.5 之间的框——这类框大概率是模型犹豫不决的样本也是最容易刷高 AP 的部分。抽 100 个左右的裂纹和排水孔受损候选框给标注员精确复核补进训练集再训一轮往往能再拉 2~3 个点的 mAP。从那以后我每次拿到新数据集都强制先跑一遍第 2.3 节的统计脚本把类别分布、框尺寸、名字拼写这三个底摸完再决定转换和训练策略。顺序一旦颠倒后面所有指标都是在为标注噪声和划分泄漏买单返工起来非常被动。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑