YOLOv10n-EUCB-SC:铸件缺陷检测的轻量化实践 去年接手的项目中有一件事印象特别深。某发动机铸件车间的质检工序靠的是老师傅拿手电筒和打磨砂轮一件件翻检一个班次下来要过几百件大型铸件眼睛酸得厉害漏检率一直压不下来。车间想上自动检测试过传统图像处理方案反光、背景纹理一变就崩也换过精度很高的大模型推理速度又撑不住产线节拍。最后落地方案是以 yolov10n 这个轻量级基线为底加了一个自研的注意力与小目标补偿结构也就是标题里的 EUCB-SC把 4096×3072 的整图切成 640×640 的瓦片再做检测在保证气孔、缩孔、夹渣、裂纹等六类常见缺陷分类识别的同时把单工位平均判定时间控制在 1.2 秒左右。这篇文章就把这套从数据制备、结构改造、训练调参到产线部署的完整链路拆开讲适合正在做工业质检视觉项目、或者在轻量化模型上做嵌入式落地的朋友参考。1. 大型铸件缺陷检测的痛点与轻量化选型逻辑1.1 缺陷长什么样六类常见铸造缺陷的形态特征铸造件缺陷虽然种类多但产线上真正需要模型区分的就那么几类。我们项目里锚定的是六类气孔、缩孔缩松、夹渣、裂纹、冷隔、砂眼。气孔一般是单个或成串的圆形孔洞边缘比较光滑表面反光时经常形成高亮环传统算法最容易在这里翻车。缩孔和缩松主要出现在铸件最后凝固的区域形态不规则边界是锯齿状或者海绵状对比度低和背景铸件纹理混在一起。夹渣是浇注过程中混入的非金属夹杂物呈块状、片状颜色发暗经常伴随周边组织疏松。裂纹是细长的线条有的长达几厘米但宽度只有一两个像素是最难搞的一类。冷隔出现在两股金属液汇合处像一条弧形接缝常见于薄壁大平面。砂眼是砂芯或型砂脱落形成的孔洞内部粗糙往往有砂粒残留的颗粒感。这些缺陷有个共同点尺寸小、对比度低、形态多变。在 4096×3072 的整图中很多缺陷只占二三十个像素肉眼都要凑近看。这决定了后续所有技术选型——模型必须能在小目标上保持高召回同时不能太慢。1.2 为什么最终选了 yolov10n而不是精度更高的中大模型当时参与选型的候选很多yolov5n、yolov8n、yolov10n以及几个 heavier 的版本。产线给出的硬指标是单个工位的图像采集、推理、判读、汇总总耗时不能超过 1.5 秒算力设备不能上高功耗服务器只能放一台普通工控机和一块入门级显卡。在这个约束下s 和 m 级别的模型单瓦片推理或许够快但切图数量一多就hold不住节拍。yolov10n 有几个天生的优势适合这个场景。首先是 NMS-freeYOLOv10 设计了一对多的训练头和一对一推理头训练时用一对多学得更充分推理时直接输出最终预测省掉了非极大值抑制这一步。在切片多、目标多的工业图上NMS 省下的几毫秒累加起来非常可观。第二是整体结构干净C2f 模块、SCDown 下采样、PSA 注意力都是现成的改造空间大不像一些老版本结构里堆砌严重。第三是 nano 版本只有约 2 到 3 兆参数量显存占用低可以在低功耗设备上稳定跑。当然nano 的问题也明显特征表达能力弱深层特征对小目标的保留不够。这正是后面加 EUCB-SC 的原因先用轻量级的注意力把召回提上来再守住速度优势。1.3 大图小目标的死结切图策略与推理速度的妥协大型铸件图不能直接缩放到 640 输入模型。缺陷本身就小整图缩放会把 20 像素的裂纹变成 3 像素的短线模型根本学不到有效特征。解决思路是把整图切成瓦片每个瓦片独立过模型再把结果合并回整图坐标系。切图参数是按铸件结构定的瓦片 640×640滑窗步长 544也就是重叠 96 像素。这个重叠量经过实测很关键——缺陷如果恰好落在切图边界上不重叠或者重叠太少一个完整的裂纹会被切成两段两边都只剩半截模型识别率急剧下降。重叠 96 像素后边界缺陷至少在某个瓦片里保留 15% 以上的上下文情况好很多。每张 4096×3072 的整图大约产生 40 个瓦片。这意味着每个铸件要跑 40 次推理必须用 nano 级别的小模型才能压进 1.5 秒的节拍。这也是我后来反复跟人强调的工业检测里的模型选型不是先选精度而是先算账——你的算力、节拍、切图数量决定模型上限模型再准跑不动就等于零。2. 数据集制备从现场图到可训练样本的完整链路2.1 现场采集与清洗光照条件比相机分辨率更关键很多团队在做工业检测时把大量精力花在模型结构上实际上数据才是决定上限的那个变量。我们第一阶段在产线采集了 9000 多张原始图像采集时特意覆盖了不同工况环形光源低角度照射、条形光侧打、自然光补光、铸件翻砂前后不同状态等。同一种缺陷在不同光照下的外观差异比不同缺陷在同一光照下的差异还要大。采集后的清洗也有一套标准图像模糊的剔除严重过曝或欠曝的剔除同一个缺陷重复出现在连续帧里的只保留一张避免模型过拟合到重复背景。清洗后约剩 7500 张有效图进入标注流程。这一步不能省脏数据对模型的伤害会在后面成倍放大。2.2 标注规范多人标注前先定一起标注规则标注是工业检测项目里最容易失控的环节。我们一开始用了通用的目标检测标注方式结果三个人标出来的结果口径完全不一样有人把整片缩松区域框成大矩形有人只框最严重的核心区域有人把表层氧化皮当成气孔标了。后来定了一个详细的标注规范表缺陷类别位置规则边界规则最小标注歧义处理气孔只标内部空腔区域框住孔洞边缘不括高亮环直径 ≥ 12 像素油污/漆点类圆形缺陷不标缩孔缩松标整体疏松区域锯齿边界取灰度突变处面积 ≥ 60 像素与暗背景混杂时咨询质检师傅夹渣标夹杂物主体不框周边色晕面积 ≥ 40 像素疑似氧化皮不标裂纹单条裂纹整体一个框端到端覆盖完整长度 ≥ 25 像素打磨划痕人工剔除冷隔标接缝全段沿缝隙走向的长矩形长度 ≥ 30 像素浅色纹理不标砂眼标孔洞内部框内壁颗粒区域直径 ≥ 10 像素缩松伴生时不单独标标注工具用的是开源的多边形标注工具导出 COCO 格式。关键点是每批标注完成后由车间质检师傅抽检 20% 的图复核——师傅虽然不会写代码但对缺陷的判定经验是模型训练数据的黄金标准。整个标注周期约三周最终拿到约 2.8 万个实例框标注一致性明显比第一版好。2.3 数据增强与难例挖掘别把缺陷形态增强没了工业场景的数据增强要克制。通用检测里常用的 heavy augmentation 在缺陷图上有副作用马赛克增强会把不同铸件的背景拼在一起形成假的缺陷边界随机旋转 90 度对长条裂纹是灾难一条竖裂纹旋转 45 度后形态完全失真。我们最终使用的增强组合是小角度旋转±10 度、水平翻转、轻微亮度对比度扰动、局部高斯噪声、模拟反光的高光斑、随机遮挡。马赛克也用了但只在前 200 个 epoch 用后面关闭这一点后面训练章节还会细说。难例挖掘单独讲。工业现场最大的干扰不是缺陷本身而是长得像缺陷的正常物防锈油污渍、焊渣、打磨划痕、水渍。这些噪声在训练集里几乎没有推理时会大量误报。我们的做法是跑一版初版模型把所有误报区域裁剪出来作为负样本背景图放进训练集并标注为background。这样模型逐步学会区分缺陷和缺陷样干扰误报下降非常明显。2.4 切图与标签同步边界缺陷的归属要提前定切图的代码逻辑不复杂但标签同步有几个坑。滑窗生成瓦片后标签框要和瓦片坐标做对齐变换如果缺陷框跨切图边界我们采用两种策略大于 30% 面积落在瓦片内的保留小于 30% 的丢弃。这样既避免切出来的半截目标过少导致模型学不到也避免同一目标被重复框。还有一个容易忽略的细节切图后要按类别统计实例数。我们发现气孔、夹渣的样本占比超过 70%而裂纹只有 8% 左右。这种不平衡直接导致后续训练中裂纹的召回率特别低后面通过采样权重和阈值调整才拉回来。所以在切图完成后第一件事就是看各类别分布别急着开训练。3. yolov10n-EUCB-SC 结构拆解加在哪、为什么这么加3.1 基线 YOLOv10n 哪里不够用先看 YOLOv10n 的薄弱点。nano 模型为了轻量化骨干和 Neck 都比较薄特征图通道数少。缺陷这种小目标在 P3 层8 倍下采样还有一定响应到 P4、P5 层基本被背景淹没。而铸件缺陷检测很依赖上下文——气孔周围的组织、裂纹两端的延伸方向都是判别的关键信息。另一个问题是缺少有效的跨通道注意力。COCO 这类自然图像数据集里模型靠强的类别先验就能出成绩但工业缺陷类别间外观差异小通道关系敏感纯卷积结构很难自动聚焦到反光高亮环 暗色孔洞这种组合特征。3.2 EUCB 模块设计思路坐标注意力 通道压缩EUCB 全称我起的是 Efficient Unified Coordinate Block一个统一的轻量坐标注意力增强模块插在骨干 P3、P4、P5 三个输出之后、进入 Neck 之前。结构上它做三件事坐标分解聚合、通道关系建模、残差融合。坐标分解聚合是核心。普通全局池化把空间信息压成单点缺陷的位置信息全丢了。EUCB 把特征图分别沿水平方向和垂直方向做平均池化得到两个方向向量再把两个向量通过一个共享的 1×1 卷积压缩通道经过非线性激活后拆回两个方向恢复到原始特征图分辨率上做外积加权。这样每个位置都能感知自己离高响应行和列的距离对局部化的缺陷响应特别有效。通道关系建模借鉴了 SE 的思路但为了控制参数先通过瓶颈结构把通道压到四分之一再扩回来用简单 sigmoid 生成通道权重。最后残差连接让原特征和新加权特征相加。一个 EUCB 模块的参数增量大约只有 1.8 万个三个模块加起来不到 6 万个参数对整个 nano 模型来说几乎可以忽略。3.3 SC 结构小目标补偿分支与跨尺度融合SC 指的是 Small-object Compensation专门针对 stride 8 特征上的小缺陷响应不足。YOLOv10n 的检测头从 P3 开始也就是最小下采样倍率是 8一个 640 瓦片输入下特征图是 80×80。对直径 12 像素的气孔在 P3 上只有 1.5 个像素响应确实太少了。SC 模块的做法是从骨干第二个阶段引出一条 stride 4 的分支也就是 P2 特征分辨率 160×160。这个分支先过一个轻量卷积把通道数对齐到 P3 的通道数再与 P3 特征做逐元素相加。相加后的特征进入共享的检测头相当于在原有 P3 分支上叠加了更高分辨率的细节信息。这里有一个取舍P2 分支会带来约 4 倍的额外计算量如果直接加一道完整的检测头nano 的速度优势就没了。我们只在 P3 这一个分支上加 P2 补偿P4、P5 不加因为缩孔、夹渣这类目标在 stride 8 上已经足够。这样控制在 640 输入下GFLOPs 只上升约 0.6换来的是小目标召回率明显提升。3.4 参数与计算量账本模型改造最怕看着加了没多少实际算力爆炸。我习惯每一步都记录参数和计算量模型版本参数量640 输入计算量说明yolov10n 基线约 2.3M6.8 GFLOPs官方结构裸跑 EUCB约 2.35M6.9 GFLOPs三个轻量注意力模块 EUCB SC约 2.6M7.4 GFLOPs加 P2 补偿分支参数量的增长可以忽略计算量增加约 9%换来的是后续消融实验里 mAP50 提升 2 个点以上。这个性价比在工业项目里是划算的——要知道很多团队为了 1 个点精度会去换大一个量级的模型算力代价高一个数量级。4. 训练配置与调参实录4.1 环境、硬件与训练开销完整训练环境用的是 PyTorch 训练框架CUDA 版本对应驱动环境单张 24G 显存的显卡。数据集划分按 7:2:1 切成训练、验证、测试三份注意按整图切而不是按瓦片切否则同一张整图切出的瓦片会同时落在训练集和测试集造成数据泄漏指标虚高。训练前用 YOLOv10n 的官方预训练权重初始化再在自己的铸件数据上微调。对比过从头训练的效果差距在 mAP50 上大约 6 到 8 个点所以强烈建议保留预训练权重复用别省这一步。单卡训练 300 个 epoch 大约耗时 18 小时不算贵。4.2 超参数选择与实际效果曲线下面的超参数不是拍脑袋定的是在前两轮实验对比之后确定的超参数数值调整原因输入尺寸640×640与切图一致避免二次缩放batch size32单卡显存上限附近梯度稳定优化器SGDmomentum 0.937小数据集上比 AdamW 泛化更好初始学习率0.01warmup 3 epochs太高则 loss 震荡太低则收敛慢weight decay5e-4抑制过拟合mosaic前 200 epoch 开启之后关闭后期关掉稳定训练mixup0.05极低强度防止背景假缺陷EMA0.9999提升稳定性和最终精度训练曲线上有两个值得注意的现象。第一个是训练 loss 持续下降但验证 loss 在 180 个 epoch 附近开始回升这是过拟合信号。此时我们把 mosaic 关闭验证 loss 立刻回落。第二个是前 100 个 epoch 的 mAP50 涨得很慢卡在 0.4 左右急得不行后来发现是学习率 warmup 阶段还没过P2 分支的梯度比主体网络大需要更长的稳定期。耐心等到 200 epoch 后指标才开始快速爬升。4.3 损失函数与类别不平衡处理YOLOv10 本身用的是分类损失加框回归损失加分布焦点损失的组合这套组合计算高效不需要额外改造。真正需要手工处理的是类别不平衡。前面提到裂纹只占样本的 8%直接训练时裂纹类召回率只有 0.5 左右。最简单的办法是按类别逆频率给采样权重裂纹类的采样权重是气孔类的 4 倍。另一个办法是给损失函数加权重把裂纹的 cls 损失乘 1.5。两个办法我们都试了效果差不多最后用的是采样权重因为实现更简单不需要动损失函数代码。类别权重调整后裂纹召回率从 0.5 提升到 0.74总体 mAP50 也涨了 1 个多点。4.4 一个容易被忽略的评价指标问题工业项目里我最不建议只看 mAP50。mAP50 的 IoU 阈值是 0.5对框位置的要求很宽松——缺陷框偏了三分之一都不算错误。但产线上后续要有抓取或者定位动作框不准等于白检。我们在训练中就盯着 mAP50-95 看这个指标对框位置敏感得多。实测下来同一个模型 mAP50 可能只差 0.5 个点mAP50-95 拉开 3 个点。后来的部署效果也验证了mAP50-95 高的版本在产线误框率明显更低。如果你也在做类似项目建议把 mAP50-95 当作主指标mAP50 只是辅助参考。5. 消融实验与性能对比数据说话5.1 消融实验EUCB 和 SC 各自的贡献做消融实验是确认每个模块有效性的基本功。我们固定所有训练超参数不变只分别加载不同结构的模型在同一个验证集上比较模型mAP50mAP50-95召回率平均推理耗时单瓦片yolov10n 基线82.437.60.782.8 ms EUCB84.140.20.813.0 ms EUCB SC86.942.80.853.4 msEUCB 对 mAP50-95 的提升比 mAP50 更明显说明注意力模块确实让框定位更准了。SC 分支单独提召回尤其对小尺寸缺陷有效果。两个模块合在一起没有冲突提升可以累加。5.2 与其他常见检测模型的横向对比同样在我们的数据集上用一样的训练配置跑了几个常见基线模型mAP50mAP50-95单瓦片耗时参数量YOLOv5n80.134.22.2 ms1.9MYOLOv8n81.836.52.7 ms3.2Myolov10n 基线82.437.62.8 ms2.3Myolov10n-EUCB-SC86.942.83.4 ms2.6MYOLOv8n 参数量比 yolov10n 大但精度反而没有明显优势主要原因是 v8 的检测流程依赖 NMS在密集多目标场景下容易漏掉低置信度的真缺陷。v5n 精度偏低瓶颈在特征提取结构偏老。EUCB-SC 版本在 mAP50-95 上比最好的基线高了 5 个点以上而推理耗时仍在可接受范围。5.3 算总账整图推理时间是否满足节拍单瓦片 3.4 毫秒看起来很漂亮但要意识到这是纯 GPU 推理时间。实际产线流程还包括切图、前处理、后处理合并、通信开销。我们做了一版线程池流水线采集线程负责切图推理线程分批跑 GPU合并线程做结果汇总。按每批 4 个瓦片的 batch 推理40 个瓦片大约跑 10 批加上前后处理一张整图的完整流程平均 1.2 秒达到产线要求的 1.5 秒以内。省下的 0.3 秒余量是刻意留的。现场工况和实验室不同GPU 温度升高后推理速度会下降 5% 到 10%如果卡在临界点上线后面有的痛苦。6. 部署产线的那些坑6.1 模型导出与算子兼容问题训练完的 PyTorch 模型需要导出成 ONNX 格式再转换到推理引擎。自定义的 EUCB 模块在这里最容易出问题——坐标注意力里的方向池化和外积操作依赖某些动态索引算子导出时经常报错。处理办法是把坐标注意力拆成标准算子组合方向池化用自适应平均池化替代外积用逐元素乘加替代避免在图上做动态索引。SC 分支没有特殊算子导出很顺利。经验就是在开发阶段就把自定义模块用标准算子写后面省掉大量排错时间。另外导出时 opset 版本要选兼容目标引擎的版本不要贪新。我们实测新版 opset 导出后在某些边缘推理引擎上跑不了回退两个版本就正常了。6.2 推理精度与量化小目标在 INT8 下容易掉点部署时做 FP16 基本没有精度损失单瓦片耗时从 3.4 毫秒降到 2.7 毫秒。接下来尝试 INT8 量化用 500 张现场图做校准集量化后 mAP50-95 掉了 6 个点主要掉在小尺寸的气孔和裂纹上。分析原因有三个坐标注意力的输出值分布比较宽INT8 量化误差放大P2 补偿分支的高分辨率特征对量化更敏感校准集和现场光照差异导致动态范围估不准。最终部署方案是主体网络用 FP16 推理P2 分支单独保留浮点。整体耗时只比全 FP16 慢 0.2 毫秒小目标掉点几乎为零。这里我的建议是小目标缺陷检测场景慎用激进量化能用 FP16 就先用 FP16。6.3 现场误报与漏检的治理不是纯模型问题上线第一周误报主要集中在两块铸件表面的防锈油膜被识别为气孔打磨划痕被识别为裂纹。这两类人工看很容易区分但模型没见过足够多的负样本。治理分三步走第一步把所有误报图像收集归类确认确实没有真缺陷后作为负样本加入训练集第二步在后处理加规则检测框面积小于设定的最小面积阈值直接丢弃裂纹框的长宽比小于 5 时降低置信度评分第三步对 P2 分支单独设置较低置信度阈值因为小目标容易漏检宁可多召回再靠后处理过滤也不让真缺陷溜走。这套组合拳上线两周后误报率从每件 3.2 次降到 0.8 次漏检率从 1.1% 降到 0.4%基本达到人工质检的水平。关键认知是产线问题永远不能只靠模型结构解决后处理规则和负样本回流是同等重要的组成部分。6.4 上线后的版本迭代机制模型上线不是终点产线的铸件批次、模具状态、涂料更换都会改变缺陷形态分布。我们建立了一套轻量迭代流程现场每天收集检测有分歧的图像每周打一批标签每两周用旧权重加新数据增量训练一版验证集上 mAP50-95 不降反升才允许替换线上版本。同时保留一个金标准测试集精选的 1000 张各类缺陷图任何新版本必须在这个集上全类召回率达到阈值才能发布。这个机制看起来简单却避免了最常见的问题“模型上线后从来没更新过半年后产线换了砂型材质漏检率悄悄上升没人发现。” 定期重训和不定期回归是工业视觉项目里最容易被砍掉但最不该砍掉的环节。最后说点个人体会。这套项目里真正花时间的不是模型结构而是数据。EUCB-SC 带来的精度提升也就三个点上下但数据清洗、标注规范、负样本回流带来的提升是八个点以上。如果看完这篇文章想先做一件事我建议先把现场照片采集规范和标注一致性定下来模型再小也能用数据乱了再大的模型也白搭。另外一个小技巧训练时把 mAP50-95 当成主要指标mAP50 当辅助参考——在小目标缺陷上mAP50 会把定位不准的问题掩盖掉等上线才暴露就晚了。