风电叶片缺陷检测YOLO标注数据集实战:从格式解析到训练部署避坑指南

发布时间:2026/9/27 23:07:57
风电叶片缺陷检测YOLO标注数据集实战:从格式解析到训练部署避坑指南 简介面向风电叶片智能巡检与目标检测算法研究场景该数据集提供约1200张已标注的风电叶片缺陷图像采用YOLO标注格式涵盖10个缺陷类别如blade、drenaj、erozyon、etiket等具体以classes文件为准并完成训练集、验证集、测试集划分可直接用于YOLOv5/YOLOv8等模型训练与改进实验。资源包共2000个文件其中724张jpg原图对应1275个txt标注文件另有1个Python可视化脚本可快速校验检测框与标签的匹配效果整体打包为7z压缩包大小约45.2MB数据轻量、便于下载和调试。目前已有83人学习使用适合电力巡检、无人机视觉检测及计算机视觉方向的学生和工程师。作者同步提供YOLOv5改进实战教程与主页更多相关项目便于在真实工业缺陷数据上验证算法效果也可延伸至图像分类、医学图像分割等任务。1. 风电叶片缺陷目标检测先别急着训练把这份YOLO标注数据吃透再说风电叶片动辄几十米长高空巡检拍回来的图像里裂纹、砂眼、雷击损伤往往只有几十个像素大小。做缺陷检测模型再先进喂进去的数据标注格式不对训练直接就崩。这套资源是约1,200张已标注的风电叶片缺陷图像统一压成YOLO标注格式说白了就是每个缺陷用一个txt文件里的归一化坐标框住配上类别ID拿过来就能进YOLOv5、YOLOv8乃至YOLOv11的训练管线。它的价值在于省掉最耗时的标注环节适合正在做工业质检、巡检目标检测项目的工程人员也适合刚入目标检测门、想拿真实工业数据跑通整套流程的新手。我拆过不少所谓带标注的数据集教训是标注格式是否规范、类别分布是否均匀、图像分辨率是否统一这三件事决定你是花一下午还是花一星期踩坑。2. YOLO标注格式拆解从txt坐标到真实缺陷框的换算2.1 YOLO标注文件怎么读类别ID、归一化坐标与对应关系YOLO系列的标注格式早就是工业界的准标准了一套标注文件由两部分组成图像本体和同名txt文件。每个txt文件里每一行代表一个缺陷框五个字段依次是类别ID、x_center、y_center、width、height其中四个坐标全是归一化值取值在0到1之间。所谓归一化就是拿真实像素坐标除以图像的宽和高——假如一张图是4000x3000像素一个缺陷框中心点在图像正中间宽度占800像素那x_center就是0.5width就是0.2。这套资源里1,200张图之所以说已标注本质就是每张jpg旁边躺着一个同名的txt文件。拿到手第一件事不是急着训练而是先抽几组图核对坐标是否落在图像范围内。我一般写一段小脚本把标注框画回原图眼见为实。import cv2 image_path data/images/wind_turbine_001.jpg label_path data/labels/wind_turbine_001.txt img cv2.imread(image_path) img_h, img_w img.shape[:2] with open(label_path, r, encodingutf-8) as f: lines f.readlines() for line in lines: parts line.strip().split() cls_id int(parts[0]) x_center float(parts[1]) * img_w y_center float(parts[2]) * img_h box_w float(parts[3]) * img_w box_h float(parts[4]) * img_h x1 int(x_center - box_w / 2) y1 int(y_center - box_h / 2) x2 int(x_center box_w / 2) y2 int(y_center box_h / 2) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, fclass_{cls_id}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(check_001.jpg, img) print(f共绘制 {len(lines)} 个标注框图像尺寸 {img_w}x{img_h})这段代码的核心逻辑就是做一次坐标逆变换把归一化坐标乘回图像宽高得到像素坐标再把中心点加宽高换成左上角与右下角坐标画框看一眼标注位置准不准。参数上要注意cv2.rectangle的坐标必须是整数所以做了int()转换类别ID在数据集里通常从0开始跟names列表索引对齐看到class_3不要慌去查看类别清单确认对应的是雷击损伤还是表面裂纹。2.2 数据集的完整目录结构与文件清单资源解压后建议按下面的结构复核一遍缺文件要尽早发现。标准的YOLO数据集目录分images和labels两个兄弟目录这是YOLOv5以来的约定很多训练框架默认按路径前缀自动匹配images和labels。dataset/ ├── images/ │ ├── wind_turbine_001.jpg │ ├── wind_turbine_002.jpg │ └── ... (约1,200张) ├── labels/ │ ├── wind_turbine_001.txt │ ├── wind_turbine_002.txt │ └── ... (与图片同名同数量) ├── classes.txt ├── data.yaml └── README.txt关键在labels目录下每张图片必须有同名txt文件哪怕图像里没有缺陷也要留一个空文件否则训练时找不齐标注会报错。classes.txt里是一行一个类别名顺序严格对应txt里的类别IDdata.yaml里记录了类别数nc、类别名names和图像路径。这套资源还有一个常见坑图像尺寸不是统一分辨率——有的巡检图是4000x3000的大图有的是1920x1080的局部裁剪图。YOLO格式对分辨率本身不敏感因为坐标归一化掉了但训练时如果你不做resize策略模型可能对大目标和小目标的学习权重失衡这一点到训练参数部分还会再讲。2.3 用Python把YOLO标注批量转成COCO或VOC格式虽然YOLO格式能直接训练但有些场景必须转格式比如要用MMDetection的现成配置、要跑Mask R-CNN做实例分割或者想用Roboflow做二次清洗。转换的本质是把归一化框换算成像素坐标的[x1, y1, x2, y2]或[x, y, w, h]。import os import json img_dir dataset/images label_dir dataset/labels coco_output dataset/annotations.json annotations [] images [] categories [{id: 0, name: crack}, {id: 1, name: corrosion}, {id: 2, name: lightning_strike}] ann_id 1 for img_id, filename in enumerate(os.listdir(img_dir)): if not filename.endswith(.jpg): continue img_path os.path.join(img_dir, filename) img_w, img_h 1920, 1080 # 手动指定或读图获取 images.append({id: img_id, file_name: filename, width: img_w, height: img_h}) label_path os.path.join(label_dir, filename.replace(.jpg, .txt)) if not os.path.exists(label_path): continue with open(label_path, r) as f: for line in f.readlines(): parts line.strip().split() cls_id int(parts[0]) cx float(parts[1]) * img_w cy float(parts[2]) * img_h w float(parts[3]) * img_w h float(parts[4]) * img_h x1, y1 cx - w / 2, cy - h / 2 annotations.append({ id: ann_id, image_id: img_id, category_id: cls_id, bbox: [x1, y1, w, h], area: w * h, iscrowd: 0 }) ann_id 1 coco_data {images: images, annotations: annotations, categories: categories} with open(coco_output, w) as f: json.dump(coco_data, f) print(f转换完成: {len(images)} 张图片, {len(annotations)} 个标注框)这段脚本里最需要注意的是img_w和img_h不能拍脑袋写死——如果这套风电图像混合了多种分辨率保险做法是用OpenCV读每一张图的shape。我上面示例故意留了手动指定的注释提醒你因为这是最容易翻车的地方。另外COCO的bbox格式是[x, y, width, height]像素值不是[x1, y1, x2, y2]不少人转完才发现坐标对不上。3. 数据集落地数据划分、YOLOv8配置与训练参数设置3.1 目录改造与训练/验证集的随机划分脚本拿到数据集第一步永远是划分训练集和验证集这一步做得好后面少跑冤枉路。大多数YOLO框架目录结构长这样dataset下分train和val各自再套images和labels。如果资源本身没帮你划分就写脚本按下图思路处理照片按7:2:1分成train、val、test三批test集专门留到最后做盲测绝不参与训练和验证调参。12张这种量级的缺陷数据集所有图都拿来训、只切一次验证模型过拟合几乎必然所以划分策略要提前想好。import os import random from shutil import copy2 random.seed(42) img_dir dataset/images label_dir dataset/labels output_root dataset_split os.makedirs(output_root /train/images, exist_okTrue) os.makedirs(output_root /train/labels, exist_okTrue) os.makedirs(output_root /val/images, exist_okTrue) os.makedirs(output_root /val/labels, exist_okTrue) os.makedirs(output_root /test/images, exist_okTrue) os.makedirs(output_root /test/labels, exist_okTrue) files [f for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(files) total len(files) train_cut int(total * 0.7) val_cut int(total * 0.9) for f in files[:train_cut]: copy2(os.path.join(img_dir, f), output_root /train/images/ f) copy2(os.path.join(label_dir, f.replace(.jpg, .txt)), output_root /train/labels/ f.replace(.jpg, .txt)) for f in files[train_cut:val_cut]: copy2(os.path.join(img_dir, f), output_root /val/images/ f) copy2(os.path.join(label_dir, f.replace(.jpg, .txt)), output_root /val/labels/ f.replace(.jpg, .txt)) for f in files[val_cut:]: copy2(os.path.join(img_dir, f), output_root /test/images/ f) copy2(os.path.join(label_dir, f.replace(.jpg, .txt)), output_root /test/labels/ f.replace(.jpg, .txt)) print(f总数 {total} - train {train_cut} / val {val_cut - train_cut} / test {total - val_cut})随机种子random.seed(42)是强迫自己复现划分结果免得同一批图跑两个版本训练后对比不公。copy2只保留了图像和标注文件classes.txt和data.yaml单独复制到输出根目录。一般来说1,200张图的划分比例不必太激进7:2:1够用如果缺陷小目标特别多可以把val比例抬高到20%以上保证每种缺陷类别在验证集里都出现。3.2 data.yaml与模型配置参数怎么填才不白调YOLOv8系列的训练启动靠两个文件data.yaml描述数据路径和类别模型yaml描述网络结构。data.yaml本质是给训练器看的数据配置单三行搞定一类参数train: dataset_split/train/images val: dataset_split/val/images test: dataset_split/test/images nc: 3 names: [crack, corrosion, lightning_strike]三个路径里test是可选字段但建议在data.yaml里写上这样训练结束后框架会自动在测试集上跑一轮推理输出指标省得再手动调。nc要严格等于txt标注里最大类别ID加1比如类别ID是0到2nc就是3填成4会直接报错。names列表的顺序必须与标注txt文件中的ID一一对应我曾经因为names里把corrosion和crack对调训练结果里类别标签全反了预测框画出来明明是腐蚀输出的类别名却写着裂纹排查了整整一个下午。模型配置文件一般不用改结构只需要在命令行指定用哪个规格。1,200张图、3类缺陷yolov8n或yolov8s起步足够图多质量好的话上yolov8m。别一上来直接yolov8x工业缺陷检测的场景里训练速度和显存限制往往比精度上限更现实。3.3 训练启动命令与损失曲线观察YOLOv8训练是命令行一站式操作Ultralytics这种封装使得训练、验证、导出一条龙。命令看着短但里面藏着很多门道普通人跑出来的模型不理想八成是参数里的小细节没抠。yolo train datadataset/data.yaml \ modelyolov8s.pt \ epochs120 \ imgsz1280 \ batch8 \ device0 \ workers4 \ patience20 \ projectrun_wind_turbine \ namedefect_v1解释几个关键参数。imgsz设1280是因为风电叶片缺陷多为小目标输入分辨率太低缺陷会直接被下采样丢掉原图如果是4000x3000就跑训练时的letterbox resize等比例缩放到1280宽高不足处补灰边。batch设为8取决于显存12G显存跑yolov8s配1280输入batch调到8已经接近上限。patience是早停轮数20轮内验证集指标不涨就停防止过拟合。pretrained权重yolov8s.pt直接续训优势是COCO预训练权重里已经学到的纹理特征能迁移到叶片表面缺陷上。训练开始后盯着终端里每轮输出的box_loss、cls_loss和metrics/mAP50。正常情况是前20轮loss快速下降后面平缓震荡如果cls_loss降不下去基本是标注类别本身有歧义如果train loss降而val loss显著回升就是过拟合信号先停训练调增强参数。4. 训练与验证从mAP到置信度阈值设置4.1 训练结果解析PR曲线、mAP50与F1分数怎么看训练完会生成一个weights文件夹best.pt按验证集mAP选最优权重last.pt是最后epoch的权重。别小看这两个文件的区别——如果patience早停触发best.pt可能比last.pt早20轮两者效果差不少。results.png里画了每轮的指标曲线重点先看mAP50和mAP50-95两条线mAP50是IoU阈值0.5下的平均精度mAP50-95是在0.5到0.95区间每隔0.05算一次再平均。缺陷检测场景工业界普遍以mAP50为主排标准因为叶片缺陷要的是找得到位置精确度要求次之。如果mAP50能到0.85以上这个模型在现场够用了。PR曲线Precision-Recall Curve更能说明问题——曲线越贴右上角越好但风电缺陷的PR曲线往往有一个明显拐点召回率到0.9附近时精度会跳水这说明模型为了不漏检把一些背景误判成了缺陷。这时候需要做一个现实决策叶片巡检宁愿多报假阳性让人工复核也不能漏判真缺陷所以阈值调整思路要偏向召回率。4.2 推理测试与预测框过滤参数验证集指标再好看也要拿没见过的图像试推理效果。YOLOv8推理命令同样简洁但conf和iou两个参数直接决定输出框的多少与质量。yolo detect predict modelrun_wind_turbine/defect_v1/weights/best.pt \ sourcedataset_split/test/images/wind_turbine_015.jpg \ conf0.25 \ iou0.5 \ saveTrue \ projectrun_wind_turbine \ nameinference_testconf是置信度阈值过滤掉低置信度的预测框iou是NMS的IoU阈值同一目标的重叠框会被合并。刚才说到面向召回率调策略实际做法是把conf从默认0.25降到0.15让更多低置信度的缺陷框冒出来——代价是假阳性增多但对叶片缺陷来说宁可多框几个背景也不放过真裂纹。iou保持0.5即可调太高会让重叠的框合并不彻底调太低则可能把同一缺陷拆成两个框。跑完打开save的推理图先肉眼检查三类里面哪一类最弱再决定要不要补样本。4.3 模型导出ONNX与TensorRT部署注意事项实验室验证完最终要落到现场工控机或边缘盒子。YOLOv8导出ONNX只要一条命令yolo export modelrun_wind_turbine/defect_v1/weights/best.pt formatonnx opset12导出时opset版本要和推理端兼容onnxruntime默认支持opset 12以上老设备需要调低到11。风电现场部署常见两类设备NVIDIA Jetson系列用TensorRTCPU工控机用ONNX Runtime。导出的onnx里输入输出节点名是框架默认的做集成时要先打印节点名确认import onnx model onnx.load(run_wind_turbine/defect_v1/weights/best.onnx) for inp in model.graph.input: print(input:, inp.name, [d.dim_value for d in inp.type.tensor_type.shape.dim]) for out in model.graph.output: print(output:, out.name)这段代码帮你拿到实际节点名和尺寸输出格式一般是1x84x8400的tensor等推理时解析检测结果别硬套教程上的老格式。部署时还要注意输入图像要执行与训练时相同的letterbox处理即等比缩放加灰边许多上线的模型精度掉点就是部署端直接resize拉伸图像变形导致坐标偏移。5. 避坑指南风电叶片缺陷检测的五条踩坑记录5.1 标注框错位归一化坐标乘错了宽度和高度现象把标注框画回图像框的位置和缺陷本体有明显偏移有的框偏左上有的框偏右下不是系统性偏移而是各错各的。原因归一化坐标的x_center、y_center是基于原始图像宽度和高度分别归一化的有的工具把宽高对调了有的是图像被旋转后坐标没有同步变换。这种数据往往不是这套资源的问题而是自己用标注工具二次导出时设置错了。解决花十分钟写一遍文章开头那种可视化脚本全量抽查20到30张图发现错位就用OpenCV重新读取宽高重新计算归一化值生成新的label文件。从那以后我每次拿到新数据集都会先强制走一遍这个流程宁可慢一点不等训练跑完才回头怀疑数据。5.2 训练时标签文件缺失报错“Image not found”现象训练到中途突然崩溃报某个jpg图片在labels里找不到对应txt或者反过来。原因数据集图片里有几张是PNG或JPEG不同后缀名划分脚本只匹配了.jpg就会漏掉还有的标签文件在标注时就没有生成。解决用统一脚本把images目录下所有图片后缀检查一遍凡是没有对应txt的图片如果是jpg就补一个空txt如果是其他格式就转成jpg再建空txt。注意完全没缺陷的图像确实应该是空txt不要误删。5.3 小目标缺陷在训练后几乎检测不到现象训练loss降得很好mAP50也不低但在验证图上单独看小裂纹模型常常漏检或者把裂纹附近的高光区域误判成缺陷。原因1,200张图里缺陷尺寸分布极不均匀大块腐蚀区域占多数小裂纹样本少而且标注框像素面积小模型梯度被大目标主导。解决两个办法叠加使用。一是把imgsz从640提到1280小目标分辨率信息保留更多二是用YOLOv8自带的mosaic增强把目标拼图混合或者对只有小缺陷的样本做离线过采样复制。如果还不行就得考虑补充小目标样本数据量不够时调参天花板就在这里。5.4 置信度阈值过低导致假阳性泛滥现象conf调到0.1以下画出来的推理图满屏红框叶片边缘、天空云彩都被框成缺陷。原因叶片表面纹理复杂某些区域的梯度变化和真实缺陷的特征高度相似模型输出高置信度背景框阈值降太狠就把噪声全放进来了。解决用valid集做一次阈值扫描画F1-confidence曲线取F1最高的点作为当前业务下的最佳阈值。对漏检严重的类别单独看它的confidence分布针对性地调它的抑制系数——具体做法是训练时把类别损失权重提高1,200张这种规模的数据集类别不平衡增益通常比调阈值更明显。5.5 部署端与训练端图像预处理不一致导致精度掉点现象本地验证mAP50有0.87部署到工控机后现场实测只有0.7而且目标框明显偏小。原因部署端直接对图像做普通resize把1280x1280的输入拉伸成960x960没有执行训练时的letterbox补边导致目标比例失真。解决在部署代码里复刻训练预处理流程——等比缩放、计算padding、中心对齐填灰边推理之后再对坐标做逆变换映射回原图。这个逆变换逻辑没有统一库自己写时最容易算错padding值建议在本地先用测试图验证画框位置一致再上现场设备。6. 进阶验证技巧用混淆矩阵和热力图检验缺陷识别质量6.1 混淆矩阵别只盯着mAP看类别之间怎么混的验证集指标跑完我习惯再做一遍细看。YOLOv8训练完会生成confusion_matrix.png行列代表真实类别和预测类别对角线越亮越好——但风电缺陷数据里最值得看的是背景类那一行。如果背景行上有明显亮点说明模型把叶片表面纹理当成目标更麻烦的是缺陷类别之间的混淆比如雷击损伤和表面裂纹在视觉上本身就相似两者互相串这种混淆mAP看不太出来但现场会非常尴尬漏报裂纹报了一堆雷击。6.2 用Grad-CAM看模型注意力放在图像哪里模型判断一个区域有没有缺陷依据到底对不对用Grad-CAM做个可视化的类激活图能直接看到模型在哪些像素上做出决策。常见做法是用pytorch_grad_cam库对onnx或pth模型注册钩子取出最后一层卷积特征图做加权。import cv2 import torch from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import preprocess_image from ultralytics import YOLO model YOLO(run_wind_turbine/defect_v1/weights/best.pt) img cv2.imread(dataset_split/test/images/wind_turbine_021.jpg) input_tensor preprocess_image(img, mean[0, 0, 0], std[255, 255, 255]) cam GradCAM(modelmodel.model, target_layers[model.model.model[-2]]) cam_map cam(input_tensorinput_tensor, target_category0) cam_map cv2.resize(cam_map, (img.shape[1], img.shape[0])) heatmap cv2.applyColorMap(cam_map.astype(uint8), cv2.COLORMAP_JET) overlay cv2.addWeighted(img, 0.6, heatmap, 0.4, 0) cv2.imwrite(gradcam_defect.jpg, overlay)这里target_layers选取模型倒数第二层特征层target_category设为0代表想看裂纹这一类别的注意力分布。跑出来的热力图如果高亮区域集中在缺陷边缘甚至叶片高光纹理上那模型学到的特征就有偏差得回到数据层面重新检查样本质量。目前这套1,200张的数据集通过热力图能明显看到模型对小裂纹的注意力发散——热力图扩散成一片而不是聚焦在裂纹本体上。针对这个现象我后续的处理是手动裁剪局部小区域做二次训练效果比盲目调超参数来得快。从那以后我每个风电缺陷模型交付前都强制走一遍混淆矩阵加Grad-CAM双验证这一套组合能暴露80%指标掩盖的问题。1,200张的规模不算大但用好了足够跑通从数据清洗、训练评估到部署落地的完整闭环希望这份笔记帮你在自己的项目里少踩几个已知的坑。本文还有配套的精品资源点击获取