
简介面向计算机视觉与风电运维场景的风力涡轮机缺陷检测数据集收录18,912张涡轮机叶片与机舱表面图像标注同时支持YOLO、PASCAL VOC XML、COCO JSON三种主流格式模型识别准确率达91.4%可用于YOLO系、Faster R-CNN等目标检测任务的训练与评测覆盖缺陷定位、损伤分类等工业巡检需求。压缩包整体约584.4MB内置图片及配套标注文件三种格式相互对应便于直接接入PyTorch、TensorFlow及主流标注工具减少格式转换与适配成本。已有354人学习下载适合高校科研、风电设备智能化巡检及工业视觉算法调优场景。利用该数据集可快速搭建从数据加载、模型训练到指标评估的完整实验链路大幅节省数据准备环节的时间投入帮助算法工程师与科研人员更快完成风电缺陷检测方案的迭代验证。1. 91.4%背后的工业视觉问题这个数据集到底解决什么风力涡轮机的巡检一直是运维成本的大头。传统人工巡检靠无人机拍完照片再拉回办公室一帧帧看一个风场几百台机组光看图就能耗掉一个工程师两三天。更麻烦的是叶片上的前缘腐蚀、雷击损伤、塔筒焊缝裂纹这些缺陷在不同光照和角度下表现差异极大人眼漏检是常态漏检一次叶片开裂后续可能就是整支叶片更换的代价。所以这两年大家开始把目标检测模型往这个场景里搬但模型要落地第一道坎不是网络结构而是标注数据。这个风力涡轮机缺陷检测数据集18912张图片配上COCO JSON格式的标注把“无人机巡检图→缺陷框”这条路补上了最关键的一环。91.4%的识别准确率并不是一个实验室数字它是在叶片、塔筒、机舱这些真实巡检视角下对前景缺陷的整体识别水平。这个数据集的价值不在“量”上——18912张在工业数据集里不算大而在于它踩中了两个最容易被忽视的点一是图片来自真实巡检而非网络爬图背景里有天空、草地、海面也有其他叶片虚化干扰模型在这个分布上练出来才敢上机二是标注统一走COCO JSON格式省去了团队内部来回转换标注格式的功夫。它的主要服务对象是两类人一类是风电场运维团队里想试点视觉检测的算法工程师另一类是做无人机巡检服务、需要给客户交付缺陷报告的系统集成商。2. 读懂COCO JSON字段结构、语义与18912张图的组织方式2.1 先用三个命令摸清数据集的底细拿到数据集后我一般不会急着开训练先花十分钟做三件事确认图片和标注是否对得上、统计类别分布是否失衡、抽查边界框是否落在图片有效区域内。这三件事直接决定后面训练是顺风还是逆风。以下是我常用的三段式检查脚本第一步先看整体规模。import json from pathlib import Path # 读取COCO格式标注文件先验证JSON目录存在 ann_file Path(annotations/instances_train.json) with open(ann_file, r, encodingutf-8) as f: coco json.load(f) # 统计图片数、标注数、类别数 print(图片数量:, len(coco[images])) print(标注数量:, len(coco[annotations])) print(类别数量:, len(coco[categories])) # 检查图片ID和文件名对应关系 img_id_to_name {img[id]: img[file_name] for img in coco[images]} print(前5个图片ID:, list(img_id_to_name.items())[:5]) # 检查是否存在悬空标注annotation引用了不存在的图片ID ann_img_ids {ann[image_id] for ann in coco[annotations]} missing ann_img_ids - set(img_id_to_name.keys()) print(悬空标注数:, len(missing))这段脚本的逻辑很直白COCO的JSON顶层包含images、annotations、categories三个主要数组annotations里的每个条目通过image_id关联到images里的某张图。悬空标注检查是所有检查里最容易发现问题的因为标注软件导出时偶尔会把原图删掉但留下标注记录这种脏数据会让模型在训练时一直报错或静默丢样本。如果missing数量不为0直接找数据集发布方补图别自己硬扛。第二步看类别维度。这个数据集是工业缺陷检测类目通常比COCO的80类少得多但每类的样本量差距可能很大。用一段简单的类别统计就能看出哪些类是小样本。from collections import Counter # 统计每个类别出现的标注次数 cat_id_to_name {cat[id]: cat[name] for cat in coco[categories]} cat_counter Counter() for ann in coco[annotations]: cat_counter[ann[category_id]] 1 for cat_id, cnt in sorted(cat_counter.items(), keylambda x: x[1]): print(f{cat_id_to_name[cat_id]}: {cnt}条标注)执行完这段统计你大概率会看到典型的工业缺陷分布一类叶片裂缝或剥落占了大头另一类比如塔筒焊缝裂纹或机舱罩雷击点只有零星几十条。这直接影响后面的训练策略——如果按默认的类别均衡去采样小样本类基本学不到特征推理时全都漏检。看到这种分布后不要急着开训先做好两类预案给小样本类配置更高的loss权重或者用数据增强对欠拟合类别做过采样。这个操作后面第4章会给出参数级别的做法。2.2 COCO JSON里那些容易看走眼的字段语义COCO格式的坑不在于大结构而在于细字段。先看images数组里的width和height这两个值必须和图片实际像素一致。我遇到过一个数据集图片是1920×1080但JSON里写的是1600×900训练时数据加载器一按比例缩放所有边界框整体偏移mAP直接掉十几个点。这个错误的隐蔽性在于训练过程不报错只有验证集上误差越看越不对劲才发现。检查方式就是随机挑5张图用下述代码验证标注和图片尺寸匹配情况。再看annotations里的bbox字段。COCO的bbox格式是[x, y, width, height]注意这是左上角坐标加宽高不是中心点格式也不是[x1, y1, x2, y2]格式。很多从LabelImg转过来的同学会把坐标习惯带进来在转换脚本里忘了改。另外COCO里area字段的单位是像素平方不是归一化值如果后续做按面积过滤的增强策略要注意单位换算。最后一个容易踩的字段是iscrowd。如果标注里存在iscrowd1的实例它表示该区域是一整片密集目标不能按普通实例去算loss。风力涡轮机叶片上密集的雨蚀坑群有时候会被标注员标成crowd区域训练时如果不对这类标注做排除或特殊处理损失函数会被带偏。2.3 数据划分先分后训别让模型偷看验证集数据集的18912张图看起来数量不小但如果划分不严谨验证集的分数就是虚高。常见做法是按风场或按拍摄任务划分而不是按文件名单纯随机切。因为同一个风场同一天拍摄的照片光照风格接近随机切分会导致模型实际上“见过”验证集的拍摄条件推理时当然准。我的习惯是先看JSON里有没有license或date_captured字段有的话就按拍摄时间切片没有的话就按文件名前缀分组再划分。以下是我常用的划分脚本import json import random from pathlib import Path random.seed(42) with open(annotations/instances_all.json, r, encodingutf-8) as f: coco json.load(f) images coco[images] random.shuffle(images) # 训练集80%验证集20%。更严格的做法是按拍摄地点分组再随机 split_idx int(len(images) * 0.8) train_imgs images[:split_idx] val_imgs images[split_idx:] def build_subset(img_list): subset_ids {img[id] for img in img_list} anns [a for a in coco[annotations] if a[image_id] in subset_ids] return { info: coco.get(info, {}), licenses: coco.get(licenses, []), categories: coco[categories], images: img_list, annotations: anns, } train_coco build_subset(train_imgs) val_coco build_subset(val_imgs) with open(annotations/instances_train.json, w, encodingutf-8) as f: json.dump(train_coco, f, ensure_asciiFalse) with open(annotations/instances_val.json, w, encodingutf-8) as f: json.dump(val_coco, f, ensure_asciiFalse) print(训练集图片:, len(train_imgs), 标注:, len(train_coco[annotations])) print(验证集图片:, len(val_imgs), 标注:, len(val_coco[annotations]))这个脚本用集合过滤的方式找出对应图片ID的标注思路比遍历全表高效得多。注意random.seed(42)固定随机数保证两次运行的划分结果一致这对对比实验很重要。如果发现某个缺陷类别的样本全落在了验证集里而训练集没有说明随机划分不可靠必须手动回填。做法是把该类别的图片ID拿出来强制将其中的60%放回训练集再重新切分。3. 把COCO转成YOLO转换脚本与四个边界坑3.1 为什么要转YOLO格式很多人拿到COCO JSON后会问数据集不是已经标注好了吗为什么还要转格式原因是训练生态不一样。COCO JSON是检测数据集的通用交换格式但实际训练时大家用的框架未必直接消费COCO。Ultralytics YOLOv8、YOLO11这类轻量级检测框架的默认标注格式是每张图一个同名txt文件每行分别记录class_id x_center y_center width height五列坐标是归一化到0-1的相对值。如果你的目标是快速验证这个数据集的效果把COCO转成YOLO格式是最省事的路径——不需要装额外依赖ultralytics包自带加载器直接读txt目录就能训练。另外还有一些框架接收的是CSV或Pascal VOC的XML格式但YOLO txt格式是目前社区生态最广、工具链最全的。做转换时最核心的事情只有一个把COCO的像素坐标[x, y, w, h]转换成归一化中心点坐标同时保证类别ID从0开始连续编号。3.2 通用转换脚本bbox坐标归一化COCO到YOLO的坐标转换公式是固定套路x_center (x w / 2) / img_widthy_center (y h / 2) / img_heightbox_width w / img_widthbox_height h / img_height。这里最容易犯的错误是没有读图片真实尺寸直接用JSON里的width和height算归一化前面说过如果标注字段里的尺寸和实际图片不一致这一步就会把错误放大。import json from pathlib import Path from PIL import Image # COCO标注路径和输出目录 coco_path annotations/instances_train.json label_dir Path(labels/train) label_dir.mkdir(parentsTrue, exist_okTrue) with open(coco_path, r, encodingutf-8) as f: coco json.load(f) # 建立图片ID到文件名的映射同时读取真实尺寸 img_info {} for img in coco[images]: img_path Path(img[file_name]) with Image.open(img_path) as im: real_w, real_h im.size img_info[img[id]] { file_name: img[file_name], width: real_w, height: real_h, } # 类别ID需要重映射到0开始连续编号 cat_ids sorted(cat[id] for cat in coco[categories]) cat_id_map {old_id: new_id for new_id, old_id in enumerate(cat_ids)} # 按图片聚合标注 from collections import defaultdict anns_by_img defaultdict(list) for ann in coco[annotations]: anns_by_img[ann[image_id]].append(ann) # 逐图写入YOLO txt for img_id, anns in anns_by_img.items(): img img_info[img_id] txt_path label_dir / (Path(img[file_name]).stem .txt) lines [] for ann in anns: x, y, w, h ann[bbox] # 边界保护框坐标不能超出图片范围 x max(0, min(x, img[width] - 1)) y max(0, min(y, img[height] - 1)) w min(w, img[width] - x) h min(h, img[height] - y) # 过滤掉宽高为0的无效框 if w 0 or h 0: continue new_id cat_id_map[ann[category_id]] x_center (x w / 2) / img[width] y_center (y h / 2) / img[height] box_w w / img[width] box_h h / img[height] lines.append(f{new_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) print(转换完成共生成, len(anns_by_img), 个标注文件)这段脚本做了三件容易被忽略的事一是用PIL读取图片的真实宽高而不是直接相信JSON里的字段二是加入边界保护逻辑把超出图片范围的框裁剪回来三是过滤掉宽高为零的无效标注。这些防御性写法在人工标注的数据集里非常有用因为标注软件偶尔会导出畸形框。转换后随机抽一个txt文件检查内容格式应该类似2 0.482501 0.315000 0.126000 0.084000最后四个数值全在0到1之间才是健康的。3.3 如果标注里有掩码segmentation字段的取舍标题里说这个数据集支持COCO JSON格式标注很多COCO格式的数据集除了边界框外还带着segmentation多边形掩码。如果有这个字段那么技术上有两个选择只用bbox做检测或者把掩码转成YOLO分割格式做实例分割。做风力涡轮机缺陷检测时我的判断是优先做目标检测而非分割——缺陷的形状不规则但运维关心的核心是“哪里有缺陷、有多严重”而不是缺陷的像素级轮廓分割标注的标注成本高、训练耗时大、推理速度慢换来的收益对缺陷定级帮助有限。如果坚持要转掩码格式YOLO分割格式是每行class_id x1 y1 x2 y2 ...坐标同样归一化。有一个转换细节必须要注意COCO的segmentation是JSON数组内部嵌套多层列表。当标注的是一个多边形时segmentation是[[x1, y1, x2, y2, ...]]当iscrowd1时segmentation变成了{counts: ..., size: [...]}这种RLE编码格式这两种结构不能同一套代码解析。# 掩码转换时读COCO segmentation多边形坐标 import json from pathlib import Path import numpy as np def coco_poly_to_yolo_seg(poly, img_w, img_h): # COCO多边形是扁平列表成对解析 points np.array(poly, dtypenp.float64).reshape(-1, 2) points[:, 0] points[:, 0] / img_w points[:, 1] points[:, 1] / img_h # 归一化后钳制到[0,1]范围 points np.clip(points, 0.0, 1.0) return points.flatten().tolist()这段代码只处理多边形格式遇到RLE格式的要跳过或单独写解码器。我一般建议标注里如果有RLE掩码直接放弃掩码转换只走bbox因为RLE解码需要用到pycocotools的maskUtils增加了依赖不说解码出来的密集掩码在YOLO训练里也未必带来多少精度收益。3.4 四类边界问题的优先级判断第一优先级是类别ID偏移。COCO的类别ID通常不是0开始的连续整数比如原始标注里可能是第1类、第3类、第7类中间有空洞。YOLO文本格式要求类别ID必须从0连续排列如果不重映射训练时类别数对不上模型loss计算直接报错或类别错位。解决方式就是上面脚本里的cat_id_map。第二优先级是坐标越界。标注框的右下角坐标可能等于图片宽高值这在COCO的坐标语义里是合法的但转YOLO时如果不做钳制框的右边界会落在1.0以上数据加载器在mosaic增强里拉伸时会算出负的IoU值损失函数就飘了。上面的脚本里x max(0, min(x, img[width] - 1))就是把坐标钳制到有效像素范围。第三优先级是宽高为负的框。倒置坐标在深度学习数据增强里的行为不可预期必须过滤。第四优先级是文件名与图片内容不一致。这个在数据集的file_name字段里可能是相对路径也可能是纯文件名转换脚本用Path(img[file_name]).stem取主文件名时能兼容两种情况但如果JSON里的file_name带有子目录路径且实际图片也放在对应子目录里直接取stem会导致图片目录和标签目录对不上。检查方式就是看转换后生成的txt总数和images数组长度是否一致。4. 用18912张图复现91.4%训练配置与验收清单4.1 数据集目录结构和data.yaml配置在跑训练之前先定标准目录布局。Ultralytics YOLO的加载器默认按images/和labels/两个目录来识别图片和标注文件图片的train和val子集要拆开放YOLO的data.yaml通过路径指向两个集合的图片目录标签目录则自动由图片路径替换images为labels得到。这种隐式关联意味着如果你的图片放在datasets/wind_turbine/images/train标签就必须放在datasets/wind_turbine/labels/train否则数据加载器会报“Label not found”。我常用的目录结构是这样datasets/wind_turbine/ ├── images/ │ ├── train/ # 约15130张 │ ├── val/ # 约3782张 ├── labels/ │ ├── train/ # 每张图对应一个txt │ ├── val/ └── data.yamldata.yaml里的names字段必须和COCO里categories的原始顺序保持一致因为模型输出的类别索引就是txt文件里的第一列数字。不要自己去重排类别顺序否则标注的类别ID和names列表错位训练时类别标签张冠李戴。# data.yaml —— 放在datasets/wind_turbine/data.yaml path: ./datasets/wind_turbine train: images/train val: images/val nc: 5 # 根据实际类别数修改 names: 0: leading_edge_erosion 1: lightning_strike 2: surface_crack 3: coating_peeling 4: weld_defect这里的nc值必须与JSON里统计出的类别数一致names的排列顺序要与转换脚本重映射后的新ID对应。如果你用的是预训练权重做迁移学习但类别数不同最终预测层的输出通道会不匹配YOLO会自动裁剪重新初始化最后一层此时日志里会出现一条警告正常现象不用慌。4.2 训练参数配置imgsz、batch、epochs怎么定图像分辨率是工业检测里最先要拍的板。风力涡轮机的图片大多来自无人机挂载的相机原始分辨率可能是4000×3000或者更夸张的5472×3648。这么大的图直接丢进网络是不现实的GPU显存扛不住而且缺陷区域在原始图里往往只占几十到几百像素下采样后容易丢失细节。常见的做法是把输入尺寸设为1280或1536在分辨率和显存占用之间取平衡。如果你用YOLOv8x这个规格的模型1280分辨率训练时的batch只能开到8或16得实测显存再调。yolo detect train \ modelyolov8x.pt \ datadatasets/wind_turbine/data.yaml \ imgsz1280 \ batch8 \ epochs100 \ patience20 \ optimizerAdamW \ lr00.0005 \ weight_decay0.0005 \ seed42参数为什么这么设简单说三点imgsz1280是为了保住叶片前缘腐蚀这类小目标的像素信息降采样到640时细节全丢lr00.0005比默认的0.01低一个数量级因为预训练权重在COCO上训过直接用大学习率会把已有的特征破坏掉patience20是早停策略验证集mAP连续20个epoch不涨就停止可以省掉大量无效训练时间。如果你的显卡只有12G显存batch降为4同时把cacheTrue关掉硬盘缓存标注数据会额外占内存。模型的选型上我不会一上来就堆最大的模型而是先用YOLOv8m或YOLOv8l跑30个epoch看验证集mAP50的趋势。如果mAP50能从0.5稳步爬到0.7以上说明数据质量没问题如果一直卡在0.3以下大概率是标注和图片的对应关系出了岔子排查方向在数据而不是模型。4.3 复现91.4%准确率的三个关键习惯准确率91.4%不是随便跑就跑得出来的。第一个关键习惯是评估指标里必须同时关注mAP50和mAP50-95前者代表目标框位置命中率后者代表定位精细度。工业缺陷检测场景下mAP50比mAP50-95更重要——运维人员只关心“框有没有框中缺陷”对像素级IoU要求没那么苛刻。第二个关键习惯是验证集里单独抽出一个“恶劣天气”子集来评估。风力涡轮机巡检图里有大量逆光、雾霾、叶片旋转模糊的照片这类图片在随机划分的验证集里占比不高模型的泛化能力容易被高估。第三个关键习惯是用混淆矩阵而不是只盯mAP。训练结束后执行下面这段代码能看到每个缺陷类别之间的误检情况。from ultralytics import YOLO # 加载训练好的权重跑验证集 model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datadatasets/wind_turbine/data.yaml, imgsz1280) # 打印每个类别的mAP和召回率 print(metrics.box.map) # mAP50-95 print(metrics.box.map50) # mAP50 print(metrics.box.maps) # 每个类别的mAP50-95列表 print(metrics.box.mp) # 平均精确率 print(metrics.box.mr) # 平均召回率如果某个类别的平均召回率显著低于其他类说明这个类存在漏检。最直接的解法就是回到数据层面做增强对该类别的图片做旋转、亮度扰动和裁剪放大而不是加正则化或调阈值。4.4 用TTA和置信度阈值控制漏检率实际部署时91.4%这个数字还要再往前推一步——测试时增强TTA。YOLO的TTA默认对输入图做水平翻转和尺度缩放推理时对多个增强结果做平均能把mAP往上抬0.5到1个百分点代价是推理时间膨胀到原来的2到4倍。如果部署在边缘端设备上TTA可能不现实此时优先降低置信度阈值来控制漏检。默认的conf0.25对工业缺陷来说偏高很多真实缺陷的置信度只有0.15到0.2建议先设conf0.05跑一遍验证集看召回率再用精度-召回率曲线找最佳阈值。yolo predict modelruns/detect/train/weights/best.pt \ sourcedatasets/wind_turbine/images/test \ conf0.1 \ iou0.5 \ imgsz1280 \ save_txtTrue注意这里iou指NMS的IoU阈值0.5意味着两个框重叠超过50%就合并缺陷目标重叠严重的图片不建议调太低否则一个缺陷会被NMS拆成多个框。5. 避坑合集从标注噪声到类别不平衡的实测教训5.1 JSON合并冲突多人同时标注导致数据丢失现象标注团队用多人协作方式导出COCO JSON合并时发现总标注数比预期少了上千条训练后部分缺陷类别完全学不到特征。原因多人的JSON在合并时如果直接按文件覆盖后写的人会把先写的人的标注覆盖掉。更隐蔽的是有些标注工具导出的JSON的image_id是局部递增的——两个标注员各自的图都从1开始编号合到一起后后者的图片ID发生冲突图片张数对不上标注也挂错图。解决合并操作不要手动操作JSON文件用脚本把图片和标注都追加到一个新文件里同时对image_id做重编号把第二人的图片ID整体偏移到第一人的最大ID之后。养成每次合并后重新跑一遍图片数和标注数统计的习惯数字对不上就回滚重来。这类问题不好排查但只要数据规模对不上号就往ID冲突方向想。5.2 小样本缺陷类别的loss被大类别淹没现象训练时总mAP在涨但塔筒焊缝裂纹这类小样本缺陷的mAP始终在0.1以下看起来像模型完全检测不到这个类别。原因缺陷类别分布高度不均衡大类别比如叶片前缘腐蚀的样本数是小类别的几十倍默认的loss计算方式按样本数加权小类别的梯度占比微乎其微模型自然偏向学大类别特征。解决调整类别loss权重给小样本类别分配更高的权重值。YOLOv8里可以在data.yaml的同级目录放一个自定义的损失函数配置文件或者在训练参数里用cls权重参数调节。实际项目里更有效的做法是离线对小样本类别做裁剪过采样——把包含焊缝裂纹的图片复制多份并做随机光照变化人为平衡类别分布。还有一种做法是换用focal loss让模型关注难分类样本。5.3 标注框偏离真实缺陷边缘现象训练过程正常但推理时框的位置总是比特标注的位置偏左或偏上几个像素看起来像系统误差。原因标注员在标注时没有严格贴合缺陷边缘习惯性地从缺陷左上角偏外的位置开始画框。这种系统性偏差在数据量小的时候会被模型学进去表现为预测框整体偏移。解决把标注框按一定比例收缩或者向外扩展对训练数据做微调。做法是先对每个框的坐标做x x 0.05 * w、y y 0.05 * h重新生成一遍训练标注再对比验证集表现。我通常是在第一次训练后发现预测框偏移用这个办法能直接把mAP50拉回两个点以上。5.4 预训练权重类别数不匹配现象加载deim的COCO预训练权重或YOLOv8的COCO预训练权重时报错提示分类头的输出维度不匹配模型直接初始化失败。原因COCO预训练权重是80类输出风力涡轮机缺陷数据集只有几个类别分类头全连接层的输出维度不一致。解决这种报错不用管框架会自动丢弃最后一层并重新初始化。真正需要留意的是特征提取层是否被冻结。如果为了省显存把backbone冻结COCO上学的底层特征会保留但它们不擅长提取叶片纹理和表面细密裂纹这类工业视觉特征冻结反而拖累精度。我的习惯是全部层开放训练只是backbone部分用很低的学习率让预训练特征在微调初期不被破坏太多。5.5 JSON文件打不开或格式异常现象用json.load读标注文件时报错Expecting value: line 1 column 1或者读出来是字符串而不是字典。原因有些标注工具导出时会把JSON写成一个对象数组而不是标准的COCO格式有些数据集的标注文件被人手动编辑过逗号或括号不完整。少数情况是标注文件实际是Excel另存成的伪JSON——全电发票PDF伪装成JSON标注文件也可能出现类似问题。解决先看文件开头几个字符判断是不是BOM头导致的解析异常用with open(ann_file, rb) as f: f.read(3)检查然后确认JSON顶层结构是否包含images和annotations。如果顶层是一个列表每个元素是一张图的标注信息说明它是另一种COCO变体需要写一个适配器把列表转成标准格式再继续。6. 用confusion matrix反推误检根因胜过盲目调参训练结束后不要直接拿91.4%的准确率去交付先算一次验证集的混淆矩阵。这个矩阵能直接告诉你当前模型最大的问题在哪是叶片前缘腐蚀被误检成涂层剥落还是塔筒背景被误检成焊缝缺陷。我用pycocotools或者YOLO自带的ConfusionMatrix都能算但更轻量的方法是直接跑一段推理把预测结果和真值框做IoU匹配。from ultralytics import YOLO import torch model YOLO(runs/detect/train/weights/best.pt) results model.val(datadatasets/wind_turbine/data.yaml, imgsz1280) # 读取验证集每类的召回率跟损失函数加权前的类别统计对比 recalls results.box.mr # 平均召回率, 一维数组顺序与data.yaml的names一致 maps results.box.maps # 每类mAP50-95 # 找出召回率低于0.5的类别优先处理 for i, (r, m) in enumerate(zip(recalls, maps)): if r 0.5: print(f类别{i}召回率{r:.3f} mAP{m:.3f} —— 需要补充样本或增强)这个诊断脚本的价值在于把问题量化而不是靠感觉。如果节点腐蚀召回率0.42此时去加大epoch数量或换更强的backbone意义有限——问题出在数据层面或标注层面。我一般会回到第2章的类别统计脚本查看这个类别的样本数量和框尺寸分布。如果框普遍小于32×32像素说明小目标太多导致下采样后特征消失解法不是换模型而是提高imgsz到1536或2240。如果召回率正常但精确率低说明模型存在大量误检此时看置信度阈值是否太低或某个背景类别和缺陷类别的特征太接近。风力涡轮机场景里最常见的就是把塔筒表面的阴影误检成裂缝这类误检靠调阈值压不下去得在训练集里补充大量负样本——纯背景但纹理与缺陷相似的塔筒表面图片。负样本放到图片目录但不加标注文件YOLO的加载器会把它当作背景图参与训练效果立竿见影。最后一招是看推理结果图。我习惯在runs/detect/predict目录里逐张翻看近两百张包含误检和漏检的图片看看它们的光照条件、拍摄角度、图片模糊程度是否有共通点。有一次我发现漏检集中在背光拍摄的图片中叶片边缘和天空背景融合人眼看着都费劲。这个现象靠调模型是没有后悔药的必须在数据侧增加背光条件下的标注样本而不是靠trick。从那以后我每次接数据集都会先做一轮“人工试图发现规律”的排查这个习惯帮我省下的调参时间比任何自动化工具都多。希望这次的拆解和配置思路能帮你在自己的数据上少走一段弯路。本文还有配套的精品资源点击获取