ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

飞机表面缺陷检测的YOLO实战:从数据标注到产线部署全流程

飞机表面缺陷检测的YOLO实战:从数据标注到产线部署全流程

1. 飞机表面缺陷检测:为什么这项质检非做不可

1.1 缺陷类型与失效风险

飞机表面的缺陷和普通制造业的划痕、脏污完全不是一个量级的问题。机身蒙皮直接承受客舱压力、气动载荷和环境腐蚀,任何一条细微裂纹都可能发展成灾难性的结构失效。典型的飞机表面缺陷大致有这几类:

缺陷类型典型表现主要风险
疲劳裂纹蒙皮表面细长线状,多发生在铆钉孔、应力集中区结构强度下降,可能快速扩展
腐蚀蒙皮氧化、点蚀、层状剥蚀,常见于尾部、舱门周边蒙皮变薄,隐蔽性强
漆面鼓包/剥落涂层起泡、脱落,往往伴随底层腐蚀暴露金属基体,加速腐蚀
凹坑变形冰雹、地勤碰撞等造成的局部凹陷影响气动外形,产生应力集中
铆钉缺陷铆钉松动、裂纹、脱出连接强度丧失,裂纹源
烧蚀/雷击点雷击后的熔融坑、烧蚀痕迹表面防护层破坏

这里尤其要注意腐蚀的隐蔽性——很多时候漆面看起来只是微微鼓包,撬开以后底下已经大片剥蚀了。这类缺陷靠传统图像处理很难识别,因为它的边缘模糊、形状不规则、对比度低。

1.2 人工目视质检的三大硬伤

我近距离跟过机务维修的定检流程,一架窄体机绕机目视检查下来,少说要一两个小时。人工目视检测的问题集中在三点。

第一是疲劳。机库光线复杂、蒙皮反光强,检查员连续工作四十分钟以后注意力明显下降,细长裂纹在银色蒙皮上本来就不显眼,眼睛一花就过去了。第二是主观性。同一个缺陷,老师傅和新手判断的标准完全不同,有人觉得要报,有人觉得还能飞。这种标准不统一在质量体系里非常致命。第三是效率,整机表面几百平方米,人眼逐寸扫过去,成本太高,定检周期被迫拉长。

也因此,“先用视觉算法做一遍全表面预筛,再让检测人员聚焦预警区域复核”成了很自然的解决思路。这也是飞机表面缺陷识别这类工业零部件自动化质检项目存在的根本原因。

1.3 为什么选目标检测而不是传统图像处理

可能有人会问:缺陷不就是边缘、纹理异常吗,用OpenCV阈值分割或者边缘检测不行吗?我一开始也这么想过,直到拿真实蒙皮照片跑了一遍才发现完全行不通。

飞机蒙皮上本身就有铆钉排、蒙皮接缝、打磨痕迹、舷窗轮廓,甚至反光都会形成边缘,传统的边缘检测和阈值分割会把大量正常结构误判成缺陷,你得为每种机型、每种光照条件手工调一堆参数,换一个拍摄角度又废了。

而目标检测模型,尤其是YOLO这类端到端方案,直接把“框出缺陷”“判断缺陷类型”两步合在一起,模型的感受野能自动学习“铆钉孔周围的细线是裂纹”“蒙皮接缝是正常结构”这样的上下文。训练数据够的情况下,泛化能力远好于手工特征。再加上YOLO在工业部署上的生态成熟,训练、导出、推理都有标准链路,正适合机务和产线场景。

2. 数据集底子决定模型上限:类别设计、标注标准与清洗

2.1 缺陷类别设计:粒度太粗犯迷糊,太细没法标

很多做数据集的朋友第一个坑就是类别设计。飞机表面缺陷识别数据集,最忌讳两种做法:一种是把所有不正常的东西都标成defect,模型倒是能框出来,但完全没法指导后续处置;另一种是把裂纹按形态分成直裂纹、分叉裂纹、网状裂纹、微裂纹……标着标着就发现过渡形态太多,连标注员自己都拿不准该归哪一类,标签噪声爆炸。

我自己的实践是控制在6类左右:crack(所有裂纹形态)、corrosion(腐蚀)、paint_peeling(漆面鼓包剥落)、dent(凹坑)、rivet_defect(铆钉缺陷)、burn(烧蚀点)。这样分类的好处在于:

  • 每个类别的视觉特征相对稳定,标注一致性高;
  • 下游处置逻辑清晰——看到腐蚀和铆钉缺陷优先级最高,漆面剥落可以暂缓处理;
  • 类别之间不易混淆,模型收敛快。

如果你的实际场景中发现“裂纹”和“划痕”在视觉上确实高度重叠,宁可单独加一个scratch类给模型一个学习区分的机会,也不要硬把两者合并成一类。合并只会让模型无所适从,精确率崩掉。

2.2 边界框标注红线:紧致框、极小目标与遮挡

标注质量对YOLO系模型的影响往往比模型结构本身还大。我踩过的坑集中在这几条红线。

第一是紧致框。很多人习惯给缺陷留一圈“安全边距”,框得松一点,觉得这样好标、不容易漏。但在计算IoU的时候,松框和紧框的差距会被平方放大,一条裂纹本来占框面积的5%,你外扩一倍,正样本质量大幅下降,mAP直接被稀释。裂纹这种细长目标,标注时要尽量贴合外接矩形,宁可框得窄,也别为了省事框一个大方块。

第二是极小目标。对于在输入分辨率下小于10×10像素的目标,我建议直接过滤掉。你强行标进去,模型在640分辨率下根本学不到足够特征,反而会给训练带来噪声。真想覆盖这类缺陷,应该靠提高输入分辨率或切块推理,而不是硬标。

第三是模糊样本。凡是人眼需要纠结3秒以上才能确定类别的样本,建议直接删掉。工业质检的标注原则是“宁缺毋滥”,模糊标签比缺失标签危害更大。一个标错的样本,和一百个正确样本对梯度的破坏力相当。

2.3 数据清洗与YOLO格式转换

数据来源通常很杂,真实维修场景拍摄的、实验室样板拍的、合成渲染的,混合在一起。我习惯在标注前先跑一遍清洗脚本:

  • 用感知哈希去掉近似重复图片;
  • 检查图片能否正常解码、分辨率是否达标;
  • 检查标注框是否越界、宽度高度是否为0或负数;
  • 检查类别ID是否超出nc范围。

YOLO格式要求每张图片一个同名txt文件,每行是class_id x_center y_center width height,坐标全部归一化。数据划分建议按场景而不是按文件名随机打散——同一个机身、同一个机位拍的图片容易高度相似,如果训练集和验证集都包含同一场景,验证指标会虚高。按拍摄批次或机型划分才能测出真实泛化能力。

3. YOLO训练实战:模型选型、参数配置与增强策略

3.1 模型选型:不是越新越好,要看算力和产线节拍

这几年轻松点的局面是YOLO系列版本迭代特别快,从v5到v8、v9、v10,还有RT-DETR,选择很多。但要记住一个原则:算法选型不只看精度榜单,还要看团队熟不熟、部署链条顺不顺、产线节拍能不能跟上。

我的建议是默认从YOLOv8s或YOLOv8m开始。理由很实际:

  • ultralytics训练、导出生态成熟,踩坑资料多;
  • v8s在640分辨率下推理延迟低,边缘设备也能跑;
  • v8m在小目标和细长形态上比v8s有明显提升,适合裂纹这类小目标较多的场景。

如果算力非常紧张,甚至可以退回YOLOv5s配ONNX Runtime,精度差一些但胜在CPU上跑得动。没必要一上来就追YOLOv10或者RT-DETR——新模型结构不代表在你的缺陷数据集上必然更好,很多情况下v8已经够用,把精力留给数据和部署更划算。

3.2 训练参数怎么设:从YAML到命令行

数据集准备好之后,第一步是写YAML:

path: /data/aircraft_defect/ train: images/train val: images/val test: images/test nc: 6 names: ['crack', 'corrosion', 'paint_peeling', 'dent', 'rivet_defect', 'burn']

训练命令我习惯写成这样:

yolo train \ data=aircraft_defect.yaml \ model=yolov8m.pt \ epochs=300 \ imgsz=960 \ lr0=0.01 \ batch=16 \ mosaic=1.0 \ close_mosaic=10 \ --patience 50

几个参数的取舍值得单独说。

输入分辨率是飞机表面缺陷识别里最影响结果的一个旋钮。640分辨率下细裂纹只有几个像素,效果会很差;960或1024能明显提升小目标召回率。代价是显存和推理时长翻倍,除非有10GB以上显存,否则配合m模型容易爆显存。

close_mosaic=10是很多新手忽略的参数。YOLO默认持续开Mosaic增强,但训练后期Mosaic生成的伪目标会让模型对真实目标尺度产生错误认知。我习惯在最后10-20个epoch关掉Mosaic,让模型在接近真实分布的数据上微调。

学习率方面,在默认SGD下lr0=0.01是比较稳的起点。如果batch size压到8以下,lr同步调到0.005左右。训练过程中盯住loss曲线和验证集mAP,到平台期就可以early stop,不用傻跑满300轮。

3.3 针对金属反光与光照不均的数据增强

飞机蒙皮表面最大的视觉干扰是反光和光照不均。同样是裂纹,直射光下可能亮得刺眼,阴天散射光下可能几乎看不见。数据增强如果不做针对性处理,模型在机库里稍微变个角度就失灵。

我会在ultralytics的增强参数上做这些调整:

hsv_h: 0.02 hsv_s: 0.7 hsv_v: 0.6 degrees: 30 translate: 0.1 scale: 0.2 fliplr: 0.0 flipud: 0.0 mosaic: 1.0 mixup: 0.1

这里重点说一下hsv_v和翻转。hsv_v调到0.6是为了模拟不同光照强度下蒙皮明暗变化,让模型学会“只看缺陷的形态而非整体亮度”。fliplr和flipud我建议关掉——机身蒙皮上的铆钉排布、蒙皮接缝方向是有物理规律的,水平翻转会让模型学到错误的几何先验。很多默认训练模板开着水平翻转,在缺陷检测里反而帮倒忙。

如果小目标漏检严重,可以试试“缺陷复制粘贴”增强:从标注样本中裁出缺陷小图,经过随机缩放、旋转、亮度扰动后,粘贴到无缺陷的背景蒙皮图上。这个操作能将正常背景中的“负样本”转化为优质“正样本”,对小目标缺陷的提升非常明显,前提是做好边缘融合,别留下明显矩形接缝。

4. 三个反直觉的坑:小目标裂纹、易混类别与漏检权衡

4.1 裂纹是细长小目标:为什么AP一直上不去

我在复盘实验结果时最常碰到的现象是:整体mAP还行,VAE特别高,但crack类的AP总是拖后腿。原因很直白——裂纹天生就是细长小目标,极端情况下一条裂纹宽2像素、长80像素,边界框的长宽比达到40:1。YOLO的下采样结构对这类极端宽高比目标天然不友好,特征图从浅到深逐级下采样,细线的特征在深层特征图上几乎消失。

解决办法我验证过几条:

  • 提高输入分辨率,960起步,条件允许上1280;
  • 使用带P2输出层的模型结构,YOLOv8-P2能保留浅层高分辨率特征,对小目标更友好;
  • 大图推理用切块策略,把相机拍的6K高清图切成带重叠的768小块,分别推理再按坐标拼回全景;
  • 训练时对小目标做过采样或复制粘贴增强。

几条组合下来,crack类的召回率能从65%左右拉到85%以上。单靠某一条,提升有限。

4.2 裂纹与划痕傻傻分不清:误报怎么压

这类缺陷检测项目做到中期,最大的敌人不是“没检出”,而是“误报太多”。最常见的误报来自裂纹和划痕、打磨痕迹的视觉相似性。在二维图像里,裂纹是材料分离,划痕是表面磨损,但照片看起来都是细长暗线,神经网络很难靠单帧RGB区分。

我有几个实战有效的思路:

第一,在类别表里加上scratch类,让模型显式学习“这玩意儿是划伤不是裂纹”的分类边界,这个类未必是业务要报的缺陷,但能显著降低crack的误报。

第二,利用上下文信息做二次判定。裂纹出现在铆钉孔、铆钉连线附近有明确的工程意义,而划痕往往随机分布。可以在检测后处理里增加一个规则:检测框落在已知应力集中区域(铆钉排、舱门角隅)才保留为裂纹,否则降级为提示信息。

第三,如果算力允许,换成分割模型。YOLO-seg输出的轮廓信息比边界框丰富得多,对细长形态的判别比纯框检测更细腻。缺点是标注成本翻倍——分割标注一张裂纹图比框标注慢三到五倍。

4.3 漏检与误报的工业级权衡:调阈值不是拍脑袋

工业质检和学术竞赛最大的区别在于代价不对称。漏掉一条正在扩展的疲劳裂纹,可能危及飞行安全;误报一次,顶多多花两分钟人工复核。所以生产环境里的风险偏好一定是“宁可多报,不可漏检”。

具体到工程实现,我一般会把推理的conf阈值从默认的0.25降到0.15甚至0.10。别看只是阈值微调,召回率能涨好几个点。代价是误报数量可能翻倍,但反正下一步有人工复核兜底,这个代价是可以接受的。

更稳妥的做法是同时计算NMS IoU阈值对多目标重叠的影响。裂纹这种细长目标挨得很近(比如一条主裂纹派生出几条分叉),默认NMS 0.7会把相邻检测框合并或抑制掉一部分,我习惯调到0.5左右,保留更多候选框,把筛选的判断交给人工或后处理规则。

评估指标上,与其死盯mAP@0.5:0.95,不如单独看每个缺陷类别的召回率。生产报告里我会列出conf在0.1、0.15、0.2三档下的Recall和F1,供团队一起决定最终阈值,而不是我一个人拍脑袋。

5. 从模型到产线:成像方案、推理优化与复核流程

5.1 成像端:偏振光源和相机选型

模型再好,拍出来的照片不行也是白搭。飞机蒙皮是高反光曲面,普通室内照明下一条裂纹可能正好落在高光区,完全看不出来。成像方案上我有几个明确结论。

光源优先用低角度光配合偏振片。低角度光让裂纹、凹坑产生阴影,增强缺陷和正常表面的对比度;偏振片能滤掉金属表面的镜面反射眩光,只保留漫反射信息。光线方向很讲究,条形光沿铆钉排方向打,纵向裂纹会格外清晰;环形光则更适合检查凹坑和漆面鼓包这类圆形缺陷。

相机方面,静态定检场景用500万到1200万像素的全局快门工业相机就够了,镜头根据检测距离选焦距,景深要覆盖住机身弧面的起伏。如果将来要做地面巡检小车或机械臂自动扫查,帧率至少要15fps以上,否则运动模糊会毁掉图像。

还有一个很容易忽略的问题是曝光和自动增益。机库环境光不断变化,相机的自动曝光会让同一块蒙皮在上午和下午拍出的亮度差异巨大,模型在训练数据里见得少的光照情况下会劣化。建议固定曝光参数,配合遮光罩稳定环境光。

5.2 推理加速:ONNX/TensorRT落地实战

训练阶段用PyTorch没毛病,但到了产线,我强烈建议导出成ONNX再转TensorRT。实测下来一张640×640的YOLOv8m,在桌面级GPU上FP16推理只需3-5ms,CPU上的ONNX Runtime则要60-100ms。

TensorRT构建的简化流程是先把ultralytics模型导出为ONNX:

yolo export model=yolov8m.pt format=onnx opset=12

然后用TensorRT重新构建engine,开启FP16:

import tensorrt as trt logger = trt.Logger(trt.Logger.WARNING) builder = trt.Builder(logger) network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser = trt.OnnxParser(network, logger) with open("yolov8m.onnx", "rb") as f: parser.parse(f.read()) config = builder.create_builder_config() config.set_flag(trt.BuilderFlag.FP16) engine = builder.build_engine(network, config) with open("yolov8m_fp16.engine", "wb") as f: f.write(engine.serialize())

这里有个坑我要提醒一下:如果模型训练时用了960分辨率,导出和构建engine时的输入尺寸要固定为960×960,推理时也保持同样尺寸,否则精度会有明显回退。

边缘侧部署如果没有GPU,可以考虑自带NPU的嵌入式板卡,或者退而求其次用CPU方案。CPU上要重点优化预处理和后处理,图片缩放到模型输入尺寸的resize操作,用OpenCV的INTER_LINEAR就够,避免多余的色彩空间转换。

5.3 人机协同:AI预警+人工复核

把AI当成“自动判罚系统”是很多工业项目的认知误区。飞机表面缺陷识别的正确落地形态是“机器筛、人复核”。

具体流程是:相机采集图像后,模型以较低的置信度阈值输出所有候选缺陷;系统把这些候选框按类别和置信度分级,红色预警直接推到复核工位,黄色提示进入一般队列;检测人员只对预警框区域做二次目视确认,确认结果实时录入系统。

这套流程跑起来以后,我的体感是单个机位的目检工作量降低了六成以上,人的精力可以集中在有限数量的预警框上,漏检率反而比纯人工更低。更重要的是,复核人员每一次确认结果都在为数据集贡献新标签——误报的图像被标记为负样本,漏检的图像被补上标注框,再回流到训练集。

6. 数据闭环:新缺陷回流与主动学习实战

6.1 产线新缺陷回流机制

模型上线不是终点,而是数据收集的起点。真实产线上遇到的缺陷形态,一定会比训练集里见过的更杂。比如南方沿海地区的腐蚀形态,和北方干燥地区的很不一样;不同批次的蒙皮材料,漆面剥落的表现也不一样。

所以我坚持在部署系统里加一个“自动导出”功能:凡是模型预测置信度在0.2到0.5之间的样本,以及人工复核时判定为误报/漏检的样本,全部自动存进待标注池。每周固定一个迭代批次,把池子里的样本拿给人补标,追加进训练集,做增量训练。

这个闭环看起来简单,但很多团队做到这一步就停了。原因通常是增量训练流程太粗糙——新数据直接混进原训练集重训,没有版本管理,结果指标忽高忽低。我的做法是给数据集和模型都打上版本号,每次迭代固定数据快照,训练前先验证新模型在旧测试集上的指标没有倒退,再部署。

6.2 主动学习:让模型自己挑难例

数据回流如果全量让人工标注,成本依然很高。更聪明的做法是让模型自己挑“有价值的样本”。

我的标准做法是:用当前模型对产线新增图片做批量推理,把置信度记录排序。置信度极高(比如0.9以上)和极低(0.05以下)的样本价值最低——前者模型已经很确定了,直接采纳为伪标签或负样本;后者要么是背景要么是太难,标注员效率也低。最有价值的是置信度在0.3到0.6之间的“犹豫区”样本,这些就是模型没学明白的难例。

实际落地时,我还会加一个“类别不确定度排序”:同一张图,模型在crack和scratch之间反复横跳、两个类别置信度接近的样本,优先级最高。这类易混样本对模型的帮助,远超随机抽取的新图。

6.3 迭代节奏与团队协作

数据闭环这件事,最怕“三天打鱼两天晒网”。我自己的节奏是:

  • 首个版本从标注到训练,压到三周以内跑通,先让模型处理80%的简单场景;
  • 上线后每月做两轮增量训练,每轮追加500到1000张高质量标注图;
  • 每季度做一次全量重训,覆盖所有历史数据,刷新基线模型。

团队协作上,我强烈建议标注、算法、现场三方每周对一次“bad case”。标注员不理解缺陷工程背景,很容易把腐蚀标成漆面剥落;算法工程师不现场看,也不知道铆钉阴影才是误报主力。这个沟通成本省不得。

另外一个小提醒:数据集版本管理做扎实。我见过不止一次,同事在旧分支上改了标注,新分支又训练了新模型,最后对不上账,白白浪费两周时间。Git LFS管理图片和标注文件,训练前锁定commit,简单有效。

最后再说一个我自己很受益的小技巧:每次增量训练之后,不急着替换线上模型。先在产线上并线跑三天,对比新旧模型的误报率和漏检率,确认没有回归后再切换。AI质检这种场景,稳定性永远比峰值精度更重要。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进