简介面向风力叶片巡检与缺陷检测任务这套数据集基于5113张图像构建压缩包内提供2000份PASCAL VOC格式的XML标注文件覆盖排水孔受损、雷击、污垢、漏油、PU胶带、表面裂纹、侵蚀等常见缺陷类型可支撑多类别缺陷定位与分类模型的训练需求。整个压缩包采用zip格式大小175.83MB共2000个XML文件每份XML均按VOC规范记录图像尺寸、缺陷类别名称及边界框坐标纯文本结构便于脚本批量解析与格式转换。文件名中保留GA419、WTG-20、DJI拍摄时间等来源标识方便与原始图像逐一对应降低数据预处理成本。目前已有1909人学习下载适合计算机视觉研究者、风电运维工程师及算法学习者直接用作模型训练数据。拿到后可省去人工标注环节快速转换为YOLO、COCO等格式用于风力叶片缺陷检测模型的训练、验证与对比实验也可结合图像增强方法提升巡检算法在复杂光照、遮挡等场景下的鲁棒性。1. 风力叶片缺陷检测数据集5113张图能撑起什么水平的检测模型风力叶片缺陷检测数据集——5113张带PASCAL VOC XML标注的现场图像放在风机巡检场景里不算海量但足够把一套缺陷检测方案从“能跑demo”推到“能上线试运行”的状态。七类缺陷覆盖排水孔受损、雷击、污垢、漏油、PU胶带、表面裂纹、侵蚀类与类之间形态差异极大个别类别又存在天然混淆处理不好会让模型收敛困难、漏检率居高不下。这类数据集的直接价值不在“刷精度榜”而在于标注格式标准、解析成本低能快速完成数据画像、格式转换和增强实验。想在风机叶片方向验证YOLO、RT-DETR这类主流检测器拿它当基准非常合理想自建风力巡检私有数据的团队也能从它的标注口径里学到可复用的字段设计。适合工业视觉落地工程师、做缺陷检测算法的学生以及准备自建数据集但还没定标注规范的人。2. PASCAL VOC XML标注字段结构、批量解析与写回修正拿到一批PASCAL VOC XML标注很多人第一反应是扔给标注工具或写个暴力字符串替换。XML解析说到底是三个动作读字段、做统计、改标注。XML文件怎么打开和编辑这类问题在项目里其实经常被问用VS Code或记事本直接打开看结构没问题真要改框或补标签必须靠脚本批量处理因为500多个XML里可能只有几十个有问题手动改是给自己挖坑。这章先把字段结构讲清楚再给一套能直接复用的解析与写回代码。2.1 XML层级结构与关键字段坐标、类别与尺寸从哪里取一个标准的VOC XML以annotation为根节点下面挂folder、filename、source、size、segmented以及若干个object节点。size里记录width、height、depthobject里记录name、pose、truncated、difficult和bndbox。多个object平铺存在即一张图有几个目标就有几个object叶片巡检图里经常一片区域叠着污垢和漏油两个框这种时候就是两段object。写解析脚本前我习惯先打印一份原始XML确认字段是否齐全annotation folderwind_blade/folder filenameBLADE_001_20230415_103245.jpg/filename sourcedatabaseCustom/database/source size width4000/width height3000/height depth3/depth /size segmented0/segmented object namedirt/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin1250/xmin ymin800/ymin xmax1850/xmax ymax1330/ymax /bndbox /object /annotation这里有两点我会在拿到任何VOC数据集时立刻核验。第一filename字段经常跟图片真实文件名不一致因为标注人员在目录间移动过图片XML里保留的是旧文件名处理时不能信任它应该用XML文件名stem作为关联键。第二size是否跟图片实际尺寸一致如果width、height与图像文件头不符后续转YOLO格式时归一化坐标会整体偏移。稳妥做法是转换时用PIL或OpenCV读图后取真实尺寸覆盖XML里的size。坐标系约定也要注意bndbox的xmin/ymin/xmax/ymax是像素坐标以左上角为原点同一张图所有object共用同一坐标系。类别名建议用下划线分隔的英文避免XML特殊字符转义。解析手段上Python标准库的xml.etree.ElementTree足够不需要引入专门的VOC工具库也不用装BeautifulSoup。2.2 用Python批量解析XML类别分布、目标面积与越界检测解析XML的核心需求不是“读出来”而是“读完能指导决策”。我用下面这个脚本做数据画像拿到的信息包括图片总数、各类目标数量、越界框数量和面积分布。import xml.etree.ElementTree as ET from pathlib import Path from collections import Counter xml_dir Path(./annotations) counter Counter() image_count 0 invalid_box_count 0 areas [] for xml_path in xml_dir.glob(*.xml): image_count 1 root ET.parse(str(xml_path)).getroot() width int(root.findtext(size/width)) height int(root.findtext(size/height)) for obj in root.findall(object): name obj.findtext(name).strip() counter[name] 1 box obj.find(bndbox) xmin int(box.findtext(xmin)) ymin int(box.findtext(ymin)) xmax int(box.findtext(xmax)) ymax int(box.findtext(ymax)) if xmax xmin or ymax ymin: invalid_box_count 1 continue if xmin 0 or ymin 0 or xmax width or ymax height: invalid_box_count 1 continue areas.append((xmax - xmin) * (ymax - ymin)) print(图片数量:, image_count) print(各类目标数量:, dict(counter)) print(无效或越界框数量:, invalid_box_count) print(目标面积均值:, sum(areas) / len(areas) if areas else 0)这段代码的思路很直接findtext(size/width)用XPath路径直达size子节点obj.find(bndbox)只取当前object内的框。循环里同时统计类别、面积和异常框第一轮画像打完哪些类需要做增强、哪些XML需要人工复核基本就有数了。面积分布我会建议单独画直方图而不是只看均值。原因是风机叶片拍摄高度、焦距差异大同一类“表面裂纹”在不同图像里的面积能差几十倍。“目标面积均值”在混合分布下没有决策意义分布直方图才有。另外当原始图像是4K、5K分辨率时一个裂纹框可能只有几百像素宽面积占比极低。这时用解析脚本算出的面积分布判断“是否需要切图tiling”把面积占比低于阈值的图像挑出来统一切成小图再进训练是风力发电大图训练的常用做法。2.3 批量生成与写回XML修正标注与补充标签的脚本解析之外写回XML是实际项目中躲不掉的动作。例如发现某个类别标签排序错了、某张图漏标了缺陷需要改或补。两条路改原XML或者生成新XML。无论哪条用ElementTree构造节点不要用字符串拼接字符串拼接在中文路径、特殊字符和编码上很容易翻车。补标签时最常见的需求是“给指定图片补充一组框”脚本骨架如下import xml.etree.ElementTree as ET from pathlib import Path def create_voc_xml(img_path: Path, boxes: list[dict], out_path: Path): root ET.Element(annotation) folder ET.SubElement(root, folder) folder.text wind_blade filename ET.SubElement(root, filename) filename.text img_path.name size ET.SubElement(root, size) # 宽高应从图片真实尺寸读取不要手写 width_el ET.SubElement(size, width) width_el.text str(4000) height_el ET.SubElement(size, height) height_el.text str(3000) depth_el ET.SubElement(size, depth) depth_el.text 3 segmented ET.SubElement(root, segmented) segmented.text 0 for box in boxes: obj ET.SubElement(root, object) name ET.SubElement(obj, name) name.text box[name] bndbox ET.SubElement(obj, bndbox) xmin ET.SubElement(bndbox, xmin) xmin.text str(box[xmin]) ymin ET.SubElement(bndbox, ymin) ymin.text str(box[ymin]) xmax ET.SubElement(bndbox, xmax) xmax.text str(box[xmax]) ymax ET.SubElement(bndbox, ymax) ymax.text str(box[ymax]) tree ET.ElementTree(root) ET.indent(tree, space , level0) tree.write(out_path, encodingutf-8, xml_declarationTrue)ET.SubElement逐层建节点顺序与传统VOC格式保持一致ET.indent用于格式化缩进避免生成的XML挤成一堆。需要说明的是这个函数里我用的是写入参数4000/3000占位实际项目必须用图片真实尺寸否则转换阶段会出坐标漂移。注意写回XML时不要依赖人肉传宽高。用cv2.imread或PIL.Image.open读图后取shape填进去这个习惯能免掉至少一次“尺寸写错导致mAP倒挂”的排查。3. 七类缺陷的标注特性从排水孔受损到表面裂纹的差异处理模型精度上限是由标注口径决定的这句话在风力叶片数据集上体现得尤其明显。七类缺陷有的面积大、边缘清晰有的只占几十个像素且细长如果统一用一种标注思路去理解训练出来的模型一定会偏科。这章讲清楚每类缺陷的视觉规律以及它们对检测器训练的影响。3.1 七类缺陷视觉特征与边界框标注建议结合这类巡检图像的普遍规律七类缺陷的差异可以按下表理解缺陷类别视觉特征边界框标注建议排水孔受损排水孔周围开裂、变色、变形面积通常较小框住整个受损区域而不是只框裂缝线雷击深色烧蚀带、碳化痕迹常伴随黑色纤维外露框住整个烧蚀区域纵向覆盖完整污垢深色块状或不规则条状边缘模糊按可见污染区域框取避免吞进边缘高光漏油条带状或滴状深色流痕轮廓相对清晰框住整条流痕不要只框末端PU胶带前缘保护胶带起翘、破损边缘清晰框起翘或破损部分胶带完好区域不标表面裂纹细长线条宽度可能只有1到5像素框裂纹所在区域不追求逐像素贴合侵蚀表面粗糙、麻点状、颜色渐变框住纹理变化区域区域过大时允许分框这里必须强调一个容易误会的点边界框不是像素分割。表面裂纹在图像里是一条曲线但VOC格式的bndbox只能给轴对齐矩形所以标注时拉一个覆盖裂纹的最小外接矩形即可。裂纹有多长矩形就拉多长但宽度不要刻意收紧到1像素否则后续数据增强里的旋转和缩放会把框“扭出”目标区域。PU胶带和污垢是另一对需要现场判断的类。PU胶带损坏区域常在叶片前缘边缘锐利、颜色对比明显污垢则是自然沉积边缘过渡柔和。如果一张图里两者同时出现且位置相邻先看边缘清晰度再定性不要按颜色深浅一刀切。3.2 类不均衡与小目标问题为什么裂纹和排水孔容易漏检在巡检数据集里类不均衡几乎是必然现象。雷击、污垢这类大面积缺陷往往样本充足排水孔受损受制于孔位数量和拍摄角度样本量天然偏少表面裂纹虽然绝对数量不少但因为目标细长在常规训练配置下很容易被当成背景。小目标问题在叶片场景里更麻烦。以一张4000×3000的巡检图为例裂纹框可能是12像素宽、300像素长面积占比约万分之三。模型输入缩放成640×640后这个目标只剩1到2像素的有效信息检测头基本感知不到。这也是为什么很多项目直接用默认imgsz640训练叶片裂纹得到的recall惨不忍睹。要让小目标类有实际召回要么把输入分辨率提到1280甚至1536要么先切图再训练没有第三条捷径。类不均衡的另一个表现是混淆对。漏油和污垢在视觉上都表现为深色区域模型容易把漏油误判成污垢排水孔受损与侵蚀都带表面纹理变化也容易混。如果做推理评测时发现某个类别的误检集中在另一个类别上先别急着加参数回看标注样本里这两个类的边界是否清晰、标注口径是否稳定。3.3 用坐标分布复查标注找出错框和异常框与其相信“标注质量没问题”不如用脚本把可疑样本筛出来。我常用下面这段代码检查两类问题宽高比异常的框以及中心点位置异常的框。import xml.etree.ElementTree as ET from pathlib import Path xml_dir Path(./annotations) suspicious [] for xml_path in xml_dir.glob(*.xml): root ET.parse(str(xml_path)).getroot() for obj in root.findall(object): name obj.findtext(name).strip() box obj.find(bndbox) xmin int(box.findtext(xmin)) ymin int(box.findtext(ymin)) xmax int(box.findtext(xmax)) ymax int(box.findtext(ymax)) w xmax - xmin h ymax - ymin if w 0 or h 0: suspicious.append((xml_path.name, name, 无效尺寸)) continue if w / h 30 or h / w 30: suspicious.append((xml_path.name, name, 宽高比异常)) # 中心点贴近图像边缘的框 cx (xmin xmax) / 2 cy (ymin ymax) / 2 if cx 10 or cy 10: suspicious.append((xml_path.name, name, 中心点贴边)) for item in suspicious[:50]: print(item)宽高比大于30的框在叶片场景里多为细长裂纹少量存在是正常的但如果一张图里大量普通缺陷整体宽高比异常大概率是标注时把旋转矩形的概念混进了轴对齐框。中心点贴边的框则可能是漏标了一部分目标也可能是标注软件在图像边缘的绘制限制需要人工确认。这种做法跟轴承缺陷检测、布匹缺陷检测不太一样。轴承和布匹的缺陷大多分布均匀中心点热力图应该比较分散风力叶片缺陷多集中在叶片边缘和前缘中心点位置天然有偏向。所以复查时重点不是“分布是否均匀”而是“有没有违反该类物理规律的异常点”。4. 把5113张图做成可训练的检测集VOC转YOLO与YOLOv8参数VOC XML不能直接喂给YOLO系模型YOLOv8训练自己的数据集要求标签是归一化TXT格式。转换、划分、配参这三步每步都有细节顺序错一个后面全乱。这章给出我常用的完整流程。4.1 VOC XML转YOLO TXT归一化坐标与越界框处理转换脚本的核心逻辑不复杂把VOC的xmin/ymin/xmax/ymax变成归一化的cx cy w h并按类别名称映射成数字ID。import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image CLASS_NAMES [ drain_hole_damage, lightning_strike, dirt, oil_leak, pu_tape, surface_crack, erosion ] xml_dir Path(./annotations) img_dir Path(./images) out_dir Path(./labels) out_dir.mkdir(exist_okTrue) for xml_path in xml_dir.glob(*.xml): root ET.parse(str(xml_path)).getroot() img_path img_dir / (xml_path.stem .jpg) if not img_path.exists(): continue # 用图片真实尺寸做分母忽略XML中的size字段 w, h Image.open(img_path).size lines [] for obj in root.findall(object): name obj.findtext(name).strip() if name not in CLASS_NAMES: continue cls_idx CLASS_NAMES.index(name) box obj.find(bndbox) x1 int(box.findtext(xmin)) y1 int(box.findtext(ymin)) x2 int(box.findtext(xmax)) y2 int(box.findtext(ymax)) # 裁剪越界坐标避免训练阶段报错 x1, y1 max(0, x1), max(0, y1) x2, y2 min(w, x2), min(h, y2) if x2 x1 or y2 y1: continue cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cls_idx} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: (out_dir / (xml_path.stem .txt)).write_text( \n.join(lines), encodingutf-8 )CLASS_NAMES的顺序就是后续YAML文件里names的顺序也就是模型输出类别的索引。这个顺序一旦定下来YAML必须跟着走不能中途调整。坐标公式里有个常见误解cx (xmin xmax) / 2 / w不是xmin / w也不是(xmax - xmin) / w后者是宽度。刚转格式时我在这上面栽过跟头出来的标签全部偏移半个框。越界处理做成“裁剪后判定”而不是直接丢弃原因在于很多标注框只是边缘超出了几个像素目标主体还在图像内。裁剪后面积占比若低于原框的某个阈值再丢弃按经验阈值取0.3比较稳。4.2 分层划分训练集与验证集低频类别不能全进训练5113张图按常规比例划分例如训练、验证、测试取80/10/10能跑通但不严谨。七类缺陷里如果排水孔受损只有少量样本随机划分很容易把它们全部挤进训练集验证集里压根没有这个类最终的验证mAP就成了假指标。更稳的做法是按“图片的类别组合”分组组内再随机划分import random from pathlib import Path from collections import defaultdict import xml.etree.ElementTree as ET xml_dir Path(./annotations) img2labels defaultdict(list) for xml_path in xml_dir.glob(*.xml): root ET.parse(str(xml_path)).getroot() for obj in root.findall(object): img2labels[xml_path.stem].append(obj.findtext(name).strip()) unique_labels sorted({lb for labels in img2labels.values() for lb in labels}) # 给每张图生成一个类别组合向量 feature_vec {} for im_id, labels in img2labels.items(): feature_vec[im_id] tuple( 1 if lb in labels else 0 for lb in unique_labels ) # 相同类别组合的图分到同一组再在组内做随机划分 groups defaultdict(list) for im_id, vec in feature_vec.items(): groups[vec].append(im_id) train_ids, val_ids, test_ids [], [], [] for im_ids in groups.values(): random.shuffle(im_ids) n_val max(1, int(len(im_ids) * 0.1)) n_test max(1, int(len(im_ids) * 0.1)) val_ids.extend(im_ids[:n_val]) test_ids.extend(im_ids[n_val:n_val n_test]) train_ids.extend(im_ids[n_val n_test:])逻辑解释把“同时包含污垢和漏油”的图、以及“单独包含裂纹”的图分别放进不同分组组内再随机划分。这样一来验证集里不会因为随机性丢掉某个类别组合模式。脚本输出的unique_labels顺序和4.1节的CLASS_NAMES顺序是一致的因为来源相同。划分完成后我会顺手打印每个集合里的类别计数确认低频类在三个集合中都存在。4.3 YOLOv8训练参数imgsz、mosaic和early stopping怎么设数据目录整理好之后训练配置写在一个YAML文件里path: ./wind_blade_dataset train: images/train val: images/val test: images/test names: 0: drain_hole_damage 1: lightning_strike 2: dirt 3: oil_leak 4: pu_tape 5: surface_crack 6: erosion训练命令行我一般这样起yolo detect train \ modelyolov8m.pt \ datawind_blade.yaml \ imgsz1280 \ epochs80 \ batch8 \ mosaic0.5 \ close_mosaic10 \ patience15imgsz1280是本场景最关键的参数。前面分析过裂纹和排水孔受损都是小目标640尺度下信息量严重不足。显存允许就上1280不允许就退回960再配合切图方案。mosaic0.5配合close_mosaic10mosaic增强对小目标有抑制作用把概率降到0.5并且在最后10个epoch彻底关闭让模型在接近真实分布的数据上收敛。patience15表示验证集指标连续15轮不涨就早停。风电巡检数据不是超大样本80轮基本够用设置早停是为了防止过拟合后的指标倒挂。modelyolov8m.pt我一般只作为起点如果验证集显示某类recall偏低再考虑换yolov8l对比收益不建议一上来就上最大模型复杂度提升在叶片这类低纹理背景上往往换不来等比例精度。注意训练前把names顺序和转换脚本里的CLASS_NAMES逐项对照一遍。基于RT-DETR做缺陷检测的方案数据集准备路径完全一样只是在模型配置和COCO格式接口上有差异标签层面的坑是共通的。5. 避坑指南格式转换、类别映射与小目标预处理中的五个坑数据准备阶段的坑往往不是马上爆出来的而是训练两三天后mAP不合预期时才被人想起来。下面五条都是实际项目里反复遇到的每条按“现象、原因、解决”说清楚。5.1 filename和真实图片名不一致训练时图片加载失败现象训练日志里偶尔出现图片读取警告或某几张图从头到尾没有参与计算最后验证集mAP偏低且不稳定。原因XML里的filename字段是标注时记录的旧文件名图片后来被移动或重命名过。训练脚本如果按filename去目录里找图就会扑空。解决转换脚本一律忽略XML里的filename改用XML文件名去配对图片。比如BLADE_001.xml对应BLADE_001.jpg如果图片是PNG后缀再换BLADE_001.png。配对前先打印缺失清单from pathlib import Path xml_dir Path(./annotations) img_dir Path(./images) for xml_path in xml_dir.glob(*.xml): candidates [ img_dir / (xml_path.stem ext) for ext in [.jpg, .jpeg, .png] ] if not any(p.exists() for p in candidates): print(缺失图片:, xml_path.stem)这条检查应该在转换的一开始做而不是等trainer跑起来再报错。5.2 bbox越界与负尺寸框mosaic阶段报错或精度倒挂现象YOLO训练过程中出现类似assert bw 0的异常或者正常跑完但小目标检测精度异常地低。原因部分标注框的xmax、ymax超出了图像边界个别框甚至出现左边界大于右边界的情况。负尺寸框在转换时如果不拦截生成的TXT里会出现负宽度或负高度模型加载标签时就会出问题。解决转换脚本里对坐标做clamp裁剪到图像范围内之后再计算中心点和宽高同时用条件判断丢弃无效框x1, y1 max(0, x1), max(0, y1) x2, y2 min(w, x2), min(h, y2) if x2 x1 or y2 y1: continue # 过滤过小的框宽或高小于原图0.5%的目标直接丢弃 if (x2 - x1) w * 0.005 or (y2 - y1) h * 0.005: continue阈值取0.005是一个经验值既能清掉误标噪声又不会误伤真实的裂纹目标。如果后续做切图训练这个过滤可以放宽因为切图后的小目标会重新变大。5.3 类别列表顺序错位推理结果固定串类现象训练流程一切正常但推理时“漏油”总是被识别成“污垢”而且混淆矩阵里这两列长期不正确。原因最常见的不是模型没学好而是类别顺序在不同脚本间不一致。例如转换脚本里CLASS_NAMES用oil_leakYAML里写成oil_leak_01或者某个脚本里把两个类的位置换了一下。模型输出的数字ID没有变但映射到名称时全错位了。解决转换和训练配置共用同一个类别清单。我现在的做法是转换脚本里把类别清单导出成JSON训练前由脚本自动读取并生成YAML的names字段python convert_voc2yolo.py --class-list classes.json python build_yaml.py --class-list classes.json --out wind_blade.yaml这样人肉抄错的可能性基本归零。训练完第一次推理时拿两三张典型图先看一眼类别名称是否合理再进入批量验证。5.4 imgsz640把表面裂纹压没了小目标几乎零召回现象训练完成后表面裂纹类的recall接近0漏检集中在这个类别上其他类表现尚可。原因640×640的输入尺寸下原始图里宽度只有几个像素的裂纹在缩放后几乎不可见。YOLO的特征图最小步长是8目标尺寸若小于特征图一个网格的有效感受野检测头很难给出稳定响应。解决三条路。第一imgsz提到1280或1536显存不够就减小batch。第二把含裂纹的原始图按1280×1280切块切成小块后再训练推理时同样按切块做滑窗。第三专门针对细长目标调整增强策略把旋转角度范围收窄到±15度以内避免裂纹框在增强后变形太严重。先做第一条见效最快如果recall还是不够再叠加切图方案。5.5 随机划分把低频类全部留在训练集验证结果不可信现象训练损失正常下降但验证集mAP波动剧烈某一次跑和另一次跑差距能到10个点以上。检查发现某个低频类在验证集里完全没有图像。原因直接random.shuffle再按比例切分低频类别样本少随机性很容易把其中一类全部切进训练集验证集里自然没有代表。解决用4.2节的分层划分方案按图片的类别组合先分组组内再随机划分。划分完成后做一步检查把验证集的类别分布打出来from collections import Counter val_labels Counter() for im_id in val_ids: val_labels.update(img2labels[im_id]) print(验证集类别分布:, dict(val_labels))任何一个类别在验证集里是0就不要开始训练。数据划分阶段的返工成本最低等模型训完再发现问题前面几天全白费。6. 验证与进阶评估指标、部署形态与高分辨率大图的滑窗推理训练跑完只算完成一半风电巡检场景的上线要求比学术指标更苛刻。验证阶段我会盯两类数据一是测试集上的mAP50和mAP50-95二是按图片统计的误检密度。mAP50-95能反映框定位质量但叶片缺陷的漏检和误检直接影响人工复核工时误检密度这个指标更接近业务成本。做法是统计每100张巡检图产生多少个假阳性框若超过每张0.5个先做误检分析再谈上线。高分辨率图像的推理是另一个常被忽略的环节。训练时已经把图缩放到1280推理时如果直接把4000×3000的原图缩成1280送进模型细裂纹大概率丢。常见做法是滑窗推理把原图切成1024×1024的块块与块之间重叠128像素逐块推理后用NMS合并重叠框。def slide_infer(image, model, crop1024, overlap128, conf0.25): h, w image.shape[:2] boxes [] for y0 in range(0, h - crop 1, crop - overlap): for x0 in range(0, w - crop 1, crop - overlap): crop_img image[y0:y0 crop, x0:x0 crop] result model(crop_img, imgszcrop, confconf)[0] for box in result.boxes: x1, y1, x2, y2 map(float, box.xyxy[0].tolist()) boxes.append( [x1 x0, y1 y0, x2 x0, y2 y0, float(box.conf), int(box.cls)] ) return nms_boxes(boxes, iou_thr0.5)这块逻辑的参数含义很直接crop决定切块大小与模型输入尺寸保持一致时不做二次缩放overlap128保证裂纹骑在切块边缘时不会因为截断而丢框NMS按类别分开合并不同类别的重叠框不互相抑制。如果推理耗时超标把重叠降到64误检率会有少量上升属于可以接受的取舍。我在自己的风机巡检项目里经历过一次“mAP看着不错、上线后密集误检”的翻车定位到最后是训练时没有关闭mosaic、推理时又没有做滑窗两个问题叠加导致小目标漏检和背景误检同时放大。那之后我固定一条流程先统计标签分布再核对转换脚本的类别映射最后才调训练参数。数据这块做扎实了模型只是流水线最后一环。希望帮到你。本文还有配套的精品资源点击获取
