工业缺陷分割实战:航空发动机叶片数据集全流程解析

📅 发布时间:2026/10/19 13:22:04
工业缺陷分割实战:航空发动机叶片数据集全流程解析
做了几年工业视觉相关的项目我越来越认同一个判断真正卡进度的往往不是模型结构而是数据本身。前段时间完整跑了一个名叫AeBAD的航空发动机叶片异常识别分割数据集1149张图、labelme标注、4个类别光看这几个参数好像很平常但真上手之后才发现这类工业缺陷分割数据和自然场景数据集完全是两个物种。这篇文章不是名词科普而是把整个流程拆开讲一遍数据怎么清洗、labelme的JSON怎么转成训练能用的格式、模型怎么选、指标怎么读、坑踩在哪里希望对准备入坑工业缺陷分割或者正在做航发孔探图像检测的同学有参考价值。1. 这类数据集到底在解决什么问题1.1 航空发动机叶片检测的现场到底长什么样航空发动机叶片的工作环境相当苛刻高温、高压、高转速再加上吸入砂石、鸟击、振动疲劳等因素叶片表面很容易出现裂纹、烧蚀、腐蚀、缺损等异常。如果这些异常没有被及时发现轻则影响发动机性能重则可能造成安全事故。所以在日常维护中孔探检测是标准流程维修人员用内窥镜探头伸进发动机内部一张图一张图地看叶片表面凭肉眼和经验判断哪里有异常。这个模式最大的问题在于人。孔探图像动辄几千张细小的裂纹在屏幕上可能就是几条像素宽的暗线看久了眼睛疲劳漏检率会明显上升。而且缺陷判断标准比较依赖个人经验两个工程师对同一张图可能给出不同结论。AeBAD这种数据集的价值就是给AI辅助判读提供燃料——先用人工标注好的样本训练模型让模型自动在图像里把异常区域勾出来检修人员在模型输出的基础上复核。这比纯人工快很多也比纯人工更稳定至少在“初筛”这个环节能大幅减轻负担。1.2 为什么是分割而不是分类和检测接触过CV的人都知道图像任务可以按输出粒度分成几档图像分类告诉你是“正常”还是“异常”目标检测告诉你“哪里有异常”用框框出来图像分割更进一步把异常区域的每个像素都分出来。回到叶片检测这个场景分割几乎是必选项原因非常实际。第一缺陷形状不规则。裂纹是细长的弯曲线条烧蚀是一片边缘模糊的区域腐蚀可能是点状群目标检测的矩形框很难贴合这些形状框小了漏掉边缘框大了把大量正常纹理包进来后面的维修决策会受干扰。第二维修记录需要量化信息。检修人员不只想知道“这里有缺陷”还想知道缺陷面积多大、沿什么方向延伸、距离叶片边缘多远。这些信息只有像素级掩码能提供。第三叶片本身纹理复杂有些正常纹理和早期裂纹在视觉上很像检测框的语义粒度太粗分辨率不够分割模型被迫保留更多空间细节。所以把任务定义为分割不是炫技是场景需求决定的。1.3 4个类别到底该怎么定义标题里写了“4类别”这类工业数据集的类别定义通常围绕异常类型和维修策略来划分。基于我对类似项目的理解比较常见的4类划分是裂纹、烧蚀、腐蚀、材料缺损。具体到不同数据集类别名可能有差异但原则上都是“不同异常形态对应不同失效原因和处理方法”。裂纹是细长线状一定伴随走向变化有时候还有分叉危险程度通常最高烧蚀多由高温燃气冲刷导致表现为表面熔融、变色边缘比较模糊腐蚀常出现在有盐分或化学物质进入发动机的工况表面会呈现点状或片状的坑蚀伴随颜色变化材料缺损则指前缘缺口、掉块等明显轮廓缺失。模型把这4类分开输出维修人员一眼就能知道这架发动机是什么问题处置优先级也能按类别分配。反而如果把所有异常都标成同一个“defect”类别训练出来就是个只有正常/异常的二分类分割信息量缩水太多。2. Labelme格式解析与数据摸底2.1 Labelme的JSON里到底存了什么Labelme是目前很常用的多边形标注工具输出格式是JSON文件一个图像对应一个同名JSON。很多人拿到数据第一件事就是睁眼找训练脚本实际上应该先把这个JSON结构读懂。{ version: 5.2.0.post4, flags: {}, shapes: [ { label: crack, points: [[512, 341], [514, 365], [513, 389]], group_id: null, shape_type: polygon, flags: {} } ], imagePath: blade_001_left.jpg, imageData: null, imageHeight: 1080, imageWidth: 1920 }这里最关键的是shapes数组每个元素是一个标注对象。label是类别名points是多边形顶点坐标列表shape_type标注类型工业数据里绝大多数是polygon偶尔有rectangle和circle。imageData字段要特别注意如果标注时勾选了“自动保存图片数据”imageData会包含一张完整的Base64编码图片JSON体积直接膨胀到几十MB如果不勾选它是null训练前必须根据imagePath自己把原图读出来。我建议做项目时统一把imageData置空只保留路径节省空间别让一堆巨型JSON拖慢加载速度。2.2 1149张图片的数据量怎么看对分割任务来说1149张到底算多还是少说实话放在自然场景里这点量连零头都不够随便一个开源分割数据集都是几万张起步。但工业视觉完全是另一套逻辑能拍的样本有限异常本身就是小概率事件能收集到一千多张带标注的真实缺陷图已经是相当有分量的家底了。关键问题不是总张数而是每个类别的有效实例数。一张图上可能只有一个异常实例也可能有七八个。做数据摸底时我会统计每张图的标注数量、每个类别的实例总数、每个类别的像素面积总和。经验上4个类别至少每个类别要有200个以上独立实例如果某个类别只有几十个后面训练时大概率会被“淹没”。当时数据集中有一个类别实例数接近400另一个只有230左右还勉强能打。如果遇到更悬殊的情况别急着训练先做类别均衡的采样策略否则模型会直接把稀有类别预测成背景。2.3 拿到数据集后的第一件事统计抽样很多技巧在比赛和项目复盘里特别不起眼但实际特别重要。收到数据后不要急着开训先写一个解析脚本把所有JSON遍历一遍输出三张表类别实例数分布、图像尺寸分布、标注多边形面积分布。尤其是图像尺寸孔探视频抽帧出来的图片有横有竖有的分辨率更高如果直接统一resize到某个尺寸可能会导致小缺陷被压缩到几乎看不见。统计完之后我会按类别抽样每类随机挑5到10张图把原图和标注重叠画出来人工看一眼。这一步能发现很多隐藏问题某个类别的标注边界明显画歪了、某些多边形包含了大量正常纹理、还有的标注框其实画的是背景上的油污。这些问题在统计表里看不出来但会在训练时变成脏标签直接拖低指标。3. 从Labelme到训练数据工程全流程3.1 数据清洗先把坏的JSON挑出来Labelme格式看着简单实际运行过程中会产生很多脏数据。最典型的是多边形自交。标注员在勾裂纹时手一抖点位顺序不对多边形就成了一个自交叉的形状转mask时填充区域会变得很奇怪。另外还有的情况是JSON文件本身损坏少了一个逗号或者多了个换行Python的json.load都会直接报错。我的处理办法是写一个校验脚本先把所有JSON文件逐个load一遍load不了或字段缺失的直接标记出来人工复核然后对每个polygon做简单检查计算顶点数、多边形面积面积小于5像素的标注基本是无效标注要么删掉要么重新标最后再画重叠图人工抽检。这个过程听上去繁琐但能在后续省掉大量排查时间。别跳过我见过有人上来就转数据集转完发现某个类别mask全是错位的最后还要回到源JSON重新折腾。3.2 把JSON转成COCO/VOC/YOLO分割格式清洗完之后下一步就是把labelme的JSON转成深度学习框架常用的格式。主流的转换目标有三个COCO格式、VOC格式的PNG灰度掩码、YOLO分割格式。COCO适合通用分割框架VOC的PNG掩码适合直接喂给U-Net这类模型YOLO分割格式适合走YOLOv8-seg系列。我自己的经验是如果项目主要做语义分割直接转成PNG掩码最省事一张原图对应一张同尺寸的灰度图每个像素的取值是类别ID0是背景1到4是4个异常类别。转换的核心脚本逻辑不复杂大概这个样子import json from PIL import Image, ImageDraw def labelme_to_mask(json_path, height, width): with open(json_path, r, encodingutf-8) as f: data json.load(f) mask Image.new(L, (width, height), 0) draw ImageDraw.Draw(mask) for shape in data[shapes]: cat_id label_to_id.get(shape[label], 0) if cat_id 0: continue points [tuple(p) for p in shape[points]] draw.polygon(points, fillcat_id) return mask实际项目中要考虑的细节更多类别ID映射顺序必须固定不能每次启动脚本都重新生成映射真实图像尺寸和JSON里记录的imageHeight/imageWidth要核对否则掩码错位多边形顶点要转成整数再画否则某些库会报错如果一张图有多个多边形都属于同一个类要先合并再填充。还有一点是labelme的坐标是绝对像素坐标但有些标注员习惯用归一化坐标转换前必须弄清楚否则会发生所有掩码整体偏移。3.3 数据划分与增强不让模型考“原题”数据划分是工业分割项目里最容易被忽视的环节。普通分类任务随机切分就能用但孔探图像不一样同一台发动机、同一次拍摄、同一段叶片表面上抽出来的连续几帧画面上缺陷外观高度相似。如果这些图像一部分进了训练集一部分进了验证集验证集指标会虚高模型真正部署到全新图片上性能就露馅。我的做法是“按来源分组划分”。比如同一个发动机编号、同一次孔探记录的图像归为一组划分时整组进入训练集或验证集保证验证集里没有跟训练集同来源的“近亲”图片。比例上训练集约65%到70%验证集和测试集各15%左右。增强方面工业数据不能只靠随机翻转和旋转解决一切。裂纹方向是有物理意义的旋转角度过大反而会让模型学到不真实的形态。我会把增强控制在比较克制的范围内随机左右翻转、小角度旋转、亮度对比度扰动再加一点随机遮挡。对稀有类别还可以尝试复制粘贴增强把一个缺陷掩码从训练集某张图里扣出来粘贴到另一个背景图上这是工业小样本任务里提升稀有类别召回率的利器。4. 分割模型选型与训练经验4.1 模型怎么选从U-Net到SegFormer分割模型这几年迭代很快但工业落地选型不能只看排行榜。我按不同需求做了一个横评先说结论求稳用DeepLabV3配ResNet50求精度且推理资源充足可以上SegFormer数据量极少想快速验证用U-Net。模型精度表现推理速度显存占用推荐场景U-Net ResNet34编码器中等快低快速验证、原型跑通DeepLabV3 ResNet50中上中中工业落地的首选SegFormer-B2/B3高中下中高追求分割精度、资源充足Mask2Former高低高学术探索、不考虑实时性DeepLabV3胜在成熟稳定有大量预训练权重对中小数据集的收敛性比Transformer类模型好。SegFormer的优势是对细长结构的建模能力强但推理速度在工控机上可能吃紧。如果是部署到带GPU的服务器那SegFormer-B2可以试如果最终要上普通工控机甚至ARM设备老老实实走CNN路线用TensorRT或者ONNX Runtime做加速。别一上来就选最重的模型工业项目里90%的难题出在数据和部署模型结构只占很小一部分。4.2 输入尺寸和分块推理保住细小裂纹孔探图像原始分辨率往往很高直接整图resize到512x512再训练一条本来只占二三十像素宽度的裂纹压缩后可能只剩五六个像素模型很难抓住它的特征。我的经验做法是先统计目标的最小尺度让最小的异常目标在模型输入尺寸下也至少保留8到12个像素的宽度。如果这个条件不能满足就分块推理。分块其实不复杂推理时把原图按一定步幅切成若干patch比如1024x1024的patch每块分别进模型得到掩码后再按原坐标拼回去。切块之间的重叠区域可以用线性权重融合避免接缝处出现明显的预测断裂。训练时也可以按patch来训练不过要注意类别不平衡很多patch里可能完全没有异常全是背景样本训练占比太大会让模型有背景倾向。解决办法是按比例过滤出包含异常的patch混合训练。4.3 损失函数与类别不平衡处理类别不平衡是任何缺陷分割项目都躲不开的问题。叶片异常区域在整张图里的像素占比通常不到几个百分点如果直接用标准交叉熵模型会倾向把所有像素预测成背景因为背景占大头loss也能降得很低。我这里的标准配置是CE加Dice的组合损失。Dice Loss对前景和背景的像素数量不敏感专门解决前景占比低的问题CE快提供稳定的梯度信号两者配合效果比单用任何一种都好。类别权重也要做一点设计。统计每个类别在训练集中的像素占比把CE的class weight设置成像素占比的倒数再开根号避免某个特别大的类别压过稀有类别。训练超参数我用的是输入640x640batch size 8AdamW优化器初始学习率1e-4配合余弦退火训练70到90个epoch。监控指标是验证集mIoU设一个early stopping patience连续15个epoch不涨就停。这套参数在不同分割模型之间表现稳定可以作为基线先跑一版。5. 评估指标与后处理模型好不等于能用5.1 指标怎么读mIoU之外还要看什么很多项目汇报习惯性说“mIoU到了0.8”但工业场景里光看mIoU会骗人。mIoU是逐像素指标对细长裂纹非常不友好。一条宽度只有5像素的裂纹哪怕预测结果整体形状已经很接近GT只要位置偏了一两个像素IoU就可能掉到0.4以下。所以除了mIoU我还会搭配看Dice系数和“目标级命中率”。目标级命中率是这样定义的把GT掩码按连通域拆成一个个独立缺陷实例模型预测的掩码和某个GT实例的IoU超过0.5就认为这个缺陷被检出了。检修人员最关心的是“这一簇异常漏没漏”而不是逐像素对得多整齐。用0.5作为IoU阈值比较常见也可以按业务需求调整。目标级命中率比mIoU更贴近真实使用价值是我评估模型能不能用的首要指标。5.2 后处理把模型输出变成可用的检修结果模型输出的原始logits经过sigmoid之后是一个概率图不是最终结果。常规做法是取0.5作为阈值转成二值掩码但对强类别不平衡的缺陷分割0.5往往太低或太高需要单独调。我可以直接扫描验证集上0.3到0.8之间的阈值选让目标级命中率最高的值。裂纹类别的概率输出通常比烧蚀类别的低把裂纹阈值单独调低一点是常见操作。得到二值掩码后还要做形态学处理。孔探图像噪点多预测结果里经常出现单个像素的毛刺或小洞用开运算消除碎屑用闭运算填补孔洞。最后把掩码等比缩放回原图坐标在原始图像上叠加半透明色块输出给检修系统。这一步看着简单但如果直接拿模型的低分辨率输出就叠加缺陷边缘会和原图对不齐现场人员复核时会觉得“AI勾的区域跟实际位置有偏移”体验很差。我的处理习惯是记录原图尺寸和模型输入尺寸之间的缩放比例算好后再做双线性插值确保边界准确对位。6. 高频问题排查速查表把这段时间遇到的典型问题整理成一张表按现象、可能原因、解决方法对应着看后面再遇到类似问题可以少走弯路。现象可能原因解决思路json.load直接报错JSON文件损坏或编码问题写脚本批量解析坏文件单独挑出来重新标注转换出来的mask和原图错位imageWidth/imageHeight不一致或坐标类型混淆转换前校验JSON头部信息和原图size所有训练样本几乎都预测为背景类别严重不平衡前景像素占比过低用Dice Loss配合重采样过采样含目标的patch某一类别的mIoU明显低于其他类别该类别的实例数太少或标注质量差检查标注增加该类别的增强比例必要时补充标注细裂纹被断成几段输入分辨率不够特征丢失提高输入尺寸或者分块训练和推理验证集指标高但换一批图效果崩数据划分时有同源图片泄漏按发动机编号或拍摄批次分组划分模型推理时显存溢出原始图太大直接喂进模型分块推理或限制单张输入的最大边长同一条裂纹被预测成两个类别不同标注员对类别边界理解不一致统一标注规范重跑标注一致性检查部署后程序推理速度很慢模型权重过大或输入尺寸过大导出ONNX并用固定尺寸做加速必要时换轻量模型输出掩码有很多小碎块后处理缺形态学滤波增加开运算和闭运算去掉单像素噪声7. 踩过几次坑之后的真实体会如果你也是第一次碰这类数据集我最想强调的就一句话数据分析的时间一定要给足至少要占到整个项目的一半。我见过不少团队把JSON转成mask直接就开训结果损失函数压不下去来回折腾几个星期最后回头一查才发现category id映射表从头到尾就是错的。工业数据项目里模型和调参都没有你以为的那么玄真正容易翻车的地方全在数据链路里。另一个体会是做一个demo模型很快做成一个能用、敢用的模型很慢。这里说的“敢用”指的是检修人员愿意把模型输出的结果拿来做参考这需要交付的不只是一个神经网络权重还包括阈值方案、后处理逻辑、异常case说明甚至一个能直观叠加掩码的小工具。把这些补齐整个项目的价值才真正落得了地。最后再分享一个实操小技巧训练时每个epoch结束都固定存几张典型样本的预测可视化图比如裂纹、烧蚀、腐蚀、材料缺损各存一张。这样不需要每次打开终端盯指标只看这些图的变化就能快速判断模型是不是在往正确方向走T字过拟合也能更早发现。这个习惯我从跑这个数据集开始一直保持到现在节省的排查时间远比多花的那点存图时间划算。