
做农产品的质检第一条铁律是别让眼睛说了算。人工目检在大批量的苹果分选线上根本撑不住几小时的重复劳动之后漏检率稳步上升烂心混进好果箱这种事我见过太多次。后来我逐步把苹果缺陷检测交给深度学习模型来做这里写的就是其中一个完整落地案例基于 YOLO26 神经网络的训练代码、缺陷数据集、以及一套带 GUI 交互界面的推理工程。写这篇东西的初衷很直接很多做大田出身的朋友不是不会跑模型而是缺少一条从 数据集构造 → 训练调参 → 推理部署 → 界面打包 的完整链路。我把自己在项目里踩过的坑、重新梳理过的思路、最终能稳定跑工业产线的配置全部按顺序倒出来给那些正准备做果蔬外观质检、或者已经在分类识别上卡壳的人一份可以照着抄的作业。1. 苹果缺陷检测的落地场景与方案选型1.1 先分清楚你要解决的是哪种缺陷苹果的外观缺陷不是有伤没伤这么简单。我和某产区合作做分选时一线品控把缺陷分为好几级碰压伤、腐烂、果锈、霉心、黑斑、刺伤、还有套袋不良造成的花斑。不同缺陷对模型的压力差异极大——碰压伤在图像上往往只有一小片轻微褐变边界模糊到人眼都要凑近了看腐烂区域虽然颜色明显却经常带水光反射干扰特征提取果锈则可能覆盖半个果面又被误当正常底色。所以第一步动作是把你手头的缺陷类别清单固定下来并且每一类都要留下足够的影像样本。我在项目里最终定了六类缺陷加一个正常类分别是正常、碰压伤、腐烂、果锈、黑斑、刺伤、霉心。这里特意把霉心单独抽出来它表面往往看不出明显异常只能依靠碰撞声音和轻微变色的耦合信息这类样本非常稀缺放到后面数据部分再细说。1.2 传统视觉方案为什么被放弃早期我们试过纯传统算法路线颜色阈值分割 形态学开闭运算 连通域面积筛选用来挑腐烂果效果不错因为腐烂区域的颜色分布和正常果皮差距大但遇到碰压伤和细小黑斑就崩了。原因是这些缺陷的低对比度特征没办法靠固定阈值描述光照一变化同一颗果拍到不同位置的亮度差都能盖过真缺陷。后来试过手工特征 SVM 分类器对已经裁好的局部图像块效果尚可但难点落到了缺陷区域怎么定位上。滑动窗口扫全图计算量和误报率都不可控漏检率也高。直到换成 YOLO 系列目标检测网络才真正把检测 分类合并到一步完成——用边界框锁定缺陷位置同时给出类别标签。这次项目最终选定 YOLO26核心原因是几个维度都满足产线节奏单阶段检测天然适合实时分选轻量化主干让推理延迟控制在几十毫秒量级多尺度特征融合对大小差异极大的苹果缺陷从几毫米黑斑到半个果面的果锈有更好的召回表现。方案定位能力泛化能力实时性开发成本阈值分割形态学弱差高低手工特征SVM 滑窗中中低高YOLO26 目标检测强强高中1.3 检测和分割的取舍做缺陷识别时很多人会纠结到底用目标检测还是图像分割。我的建议比较简单如果你的任务只是把这颗有烂斑的苹果挑出来目标检测足够了如果老板还想知道缺陷面积占比、或者要对缺陷边缘做精确的几何测量那才需要切到分割模型。苹果上大部分缺陷的边缘语义本来就含糊分割结果的像素级精度未必能带来等价的商业回报反而提高标注成本项目周期也会被拖长。因此这篇项目用的边界框检测思路在落地上更有性价比。2. 苹果缺陷数据集从哪里来标注到什么粒度2.1 数据采集环节的三个硬性约束数据集是这类项目真正的地基地基歪了模型再先进也白搭。我在采集苹果缺陷图像时踩过三次明显的坑每次都让模型表现有肉眼可见的回退。一是背景干扰。一开始贪图方便在场地里一个普通光源下直接拍结果苹果高光区把绿色叶影映上去模型把叶影当成了缺陷区域。后面统一改成黑色哑光背景 顶部散射光源苹果放在旋转托盘上保证每一颗果都被多角度采集缺陷变化能被充分记录。二是分辨率匹配问题。产线相机拍出来的原始图像分辨率很高但模型训练输入通常缩到 640 × 640 或 1280 × 1280。如果缺陷很小缩图后细节全部丢失模型根本学不到特征。我的处理方式是在完整果图上标注大缺陷同时对局部缺陷区域做切片放大把切片也作为训练样本。等于让模型既看整体又能凑近看局部这个操作对黑斑这类小目标检出率提升非常明显。三是同一颗苹果的多状态记录。苹果在周转过程中磕碰会增多腐烂范围会变大。理想情况是同一样本分时段拍多次把轻重程度不同的影像都灌进训练集。实际操作中我用模拟产线的方式把苹果从离地 30 厘米的台面落下制造不同力度等级的碰压伤再分批次拍照。这样做出来的模型对产线真实工况的容忍度比只拍完好果或极端烂果的模型强很多。2.2 标注规范边界框多大才合适标注这件事看似无脑其实是技术活。规范不统一模型学到的边界就乱七八糟。我对标注工人只提四条要求每一条都是血泪教训换来的边界框要紧贴可见缺陷不放大到整个苹果。一块区域存在两种缺陷重叠时按更严重的那类标不重复打框。因光照造成的高光反光区域不标。缺陷面积过小导致人眼都难以判断时直接丢弃该样本不猜。为什么第一条规定很关键如果框圈到整个苹果模型就会误以为苹果形状也是缺陷特征训练后对完好的背景区域产生大量假阳性框。我在一次中期训练里就因为标注偷懒验证集 mAP 一直上不去后来把一百多张误标样本找出来修正指标立刻回升。2.3 数据增强与分配比例数据增强我用的是 Mosaic、随机翻转、HSV 扰动、随机遮挡这四件套。Mosaic 把四张图拼成一张等于变相扩大了 batch 的上下文信息随机遮挡对腐烂被叶子覆盖、黑斑被手指挡住的边缘场景特别有效。注意一点增强要适度苹果表面有自然纹理HSV 扰动参数调太猛会让苹果颜色失真模型学到假颜色关系反而干扰正常果皮的缺陷判断。训练集、验证集、测试集的划分比例我固定在 8:1:1并在划分时按缺陷类别做分层采样保证每个类别在三个集合里的比例基本一致否则验证集里某一类样本太少指标波动会大到失真。最终数据集规模大约是三万一千张标注图像其中正常果占比控制在四成左右、缺陷样本占了六成左右。正常果比例不能太低否则模型产生严重偏置会把所有果都当有缺陷来报警。3. YOLO26 训练的关键参数从配置到跑通的完整记录3.1 网络结构中真正影响效果的部分YOLO26 延续了单阶段检测器的整体框架由主干、颈部、检测头三部分组成。但项目里真正影响苹果缺陷检出效果的点集中体现在两个地方颈部采用了加权双向特征融合结构让高层语义信息和低层边缘信息在融合时不是简单相加而是通过可学习的权重自动调节检测头则输出更精细的边界框回归参数并引入显式的目标置信度分支。对苹果缺陷来说这个设计的意义很具体腐烂这种大范围缺陷在高层的语义特征里更明显黑斑这种小范围缺陷需要在浅层高分辨率特征图上响应。如果特征融合只是机械拼接两个尺度上的信息就会被互相稀释。在实测里我把模型从 YOLO26 的原始配置切到去掉权重融合的简化版做对比苹果缺陷数据上的平均精确率掉了大约 3 个百分点尤其小目标黑斑的召回率掉得更明显。3.2 核心训练超参是怎么定下来的训练配置直接给出来这是我在多次实验里反复调过的一版参数数值说明输入尺寸640 × 640兼顾速度与精度1280 用于小目标专项训练批次大小16单卡显存占满不影响梯度稳定性初始学习率0.01用预热策略前 3 轮从 0.001 渐升至 0.01优化器SGD动量 0.937比 Adam 在检测任务上收敛更稳损失函数分类损失 回归损失 置信度损失权重 0.5 : 1.0 : 1.0训练轮数120早停阈值设为验证集 10 轮无提升权重衰减0.0005防止过拟合的主防线之一学习率和批次大小的搭配是很多新手容易忽略的点。盲盒式调到 0.0001模型会半天不收敛调到 0.1 又直接梯度爆炸。我用的方法是先设 0.01 跑前 10 轮观察训练损失如果迟迟不下就按 2 倍递减直到损失出现稳定下降趋势为止。预热策略不能少模型前几轮的权重还是乱糟糟的突然给大学习率容易把初始化破坏掉预热让梯度更新平滑过渡。另外我用命令行的方式启动训练把配置固定在 YAML 文件里方便每次实验追溯python train.py --data apple_defect.yaml --weights yolov26_s.pt --img 640 --batch 16 --epochs 120 --project defects_trainingapple_defect.yaml 里面指定了三件事训练集和验证集的图像路径、类别名字列表、类别数量。路径一定要写绝对路径我吃过相对路径的亏——换电脑跑训练时图像目录找不到直接报错中断。3.3 损失权重调优的直观经验类别不平衡在苹果缺陷项目里非常普遍正常果占大头、碰压伤也不少但霉心样品可能只有两百张。单纯用默认权重模型会把难学的霉心类别彻底放弃。我在损失设置里动态调了类别权重给样本量少的类别更高的惩罚系数。以霉心为例类别权重大概是正常果的 3 到 4 倍。这里有个技巧值得单独说调权重不能只看数量比还得看类别的难易程度。比如果锈虽然样本数量比霉心多但果锈在表现上差异很大有的像一片褐色有的只是零散孔点模型学起来难也需要额外加权。我把类别权重做成可配置的数组每次训练前按样本统计和验证集表现微调一套权重管两三个版本迭代之后整体 mAP 会比初始状态提升五到八个点。4. 训练中踩过的坑误检、漏检和过拟合的排查思路4.1 误检高发场景与排查流程有一次中途训练结束测试集上正常果的误检率高达 12%模型隔三差五把完好的苹果框成碰压伤。我第一反应是数据问题于是按类别逐一检查验证集预测图发现误检集中发生在两个时间段拍的样本上一个是下午自然光透过窗户斜射进来的批次另一个是苹果表面残留水珠没擦干的批次。定位到根因后处理方式分两步先把有水珠的样本要么擦干重拍要么直接剔除因为真实产线上苹果是经过风干通道后才进相机的水珠场景本来不该出现再把下午斜射光、自然光条件下的样本单独抽样补充到训练集里做数据增强。处理后误检率降到 2% 以下。这条经验给我最大的教训是误检要先分场景、分批次的做聚类观察不要一上来就调模型参数问题百分之八十在数据侧。4.2 小目标漏检黑斑和刺伤为什么老丢黑斑和刺伤两类缺陷一开始的核心问题就是漏检。它们面积太小边界框可能只有十几个像素经过下采样后特征图上的响应弱到几乎消失。我的调整思路是三维并举对黑斑和刺伤类别的原始图像执行过采样复制多份后加不同强度的高斯噪声和旋转扰动提高它们在训练样本中的占比权重。训练时采用 1280 分辨率的输入重训练而不是基于 640 的权重做简单微调。分辨率提升对小目标候选框的匹配帮助显著代价是训练时间拉长近一倍。把浅层特征图的输出参与损失计算让模型的梯度在反向传播时更多流向高分辨率分支。这三步叠完黑斑的漏检率从拉胯的 40% 降到 9%刺伤降到 11%。虽然还是有提升空间但对产线分选来说黑斑漏掉几个并不会造成变质果混入已经满足客户验收门槛。4.3 过拟合的典型表现与抑制手段出现过拟合最典型的迹象是训练损失一路降到很低验证集 mAP 却在某轮之后开始掉头向下train-val 曲线之间的间隔越拉越大。苹果缺陷数据集虽然有三万多张但类别之间样本不均衡模型会倾向把样本量大的类别背下来而不是真正学会泛化特征。我采用的抑制手段包括加大权重衰减到 0.0005、在主干网络中等层加 Dropout概率 0.1、用标签平滑把分类目标的 hard label 改成 0.95/0.05 的软目标减少模型对训练集少数强特征的过度自信。效果比较好的是 10 轮未涨就早停配合保存最优权重省掉了手动盯训练的精力。5. GUI 交互界面让模型从上位机走向操作员5.1 操作员要的不是参数是结果一个模型训得再好如果配套界面是命令行黑框框一线操作员根本不会用。这个项目的 GUI 交互界面我定位成给非技术人员使用的检测工具台界面核心功能锁定在五个模块上单张图片检测面板拖入图片立刻显示缺陷框和置信度。相机实时检测面板接入 USB 摄像头或以太网工业相机画面实时叠加检测框。批量文件夹检测面板批量处理一个目录下所有图片并导出 CSV 统计结果。阈值设定面板操作员可以调整置信度阈值解决不同客户对过检率的要求差异。结果可视化面板把单张图片的缺陷类别、置信度、数量直接做成统计卡片。设计逻辑很简单上限要低操作员不用懂模型也知道怎么点扩展要高内部接口要留好后面接数据回传、分选联动才不返工。5.2 GUI 内部结构推理线程与主界面的协作图形界面用的是 PyQt5推理部分用 ONNX Runtime 加载导出的模型。关键点在于推理不能直接写在主线程里否则界面一卡操作员就会开始双击重启程序。我做了标准的三线程设计主线程处理界面交互、按钮点击、文件选择。推理线程执行模型推理推理完成后发出携带结果的结果信号。显示刷新线程从队列中取最新的帧把检测结果绘制到窗口上。这里给一个简化版的核心推理代码可以直接当脚手架用import onnxruntime as ort import numpy as np import cv2 class AppleDetector: def __init__(self, onnx_path): self.session ort.InferenceSession(onnx_path) self.input_name self.session.get_inputs()[0].name def preprocess(self, image, size(640, 640)): img cv2.resize(image, size) img img[:, :, ::-1].transpose(2, 0, 1) # BGR - RGB img np.ascontiguousarray(img, dtypenp.float32) / 255.0 img np.expand_dims(img, axis0) return img def infer(self, image, conf_thres0.35): x self.preprocess(image) outputs self.session.run(None, {self.input_name: x})[0] # 这里的 outputs 后续要接 NMS 逻辑 boxes, scores, classes self.nms(outputs, conf_thres) return boxes, scores, classesNMS 非极大值抑制这部分用 ONNX Runtime 自带的离线算子或者自己写都行。我的经验是直接沿用 PyTorch 时代训练时那套后处理实现转换成 ONNX 后把它固化成 Python 函数保证训练和部署两边的行为一致。5.3 界面与产线时序的匹配问题实时检测模式下相机帧率是 30 帧每秒但模型推理一帧要 35 到 50 毫秒做不到逐帧检测。解决办法是让推理线程跳过来不及处理的帧只处理最新的帧不会造成画面卡顿和检测结果排队堆积。显示线程则永远显示当前的最新帧和最新检测框。这样牺牲了一点点检测连续性换来了界面的长时间稳定运行。批量检测模式相对简单维护一个任务队列逐张读取图片、推理、保存结果并在界面上用进度条反馈。我在实际运行中注意到 Windows 上读取大量图片文件时打开速度很慢提前用 imdecode 解码代替 imread批量处理三千张图片的时间缩短了差不多四成。6. 实测效果与部署复盘6.1 评估指标怎么读目标检测项目的最终效果不能靠肉眼扫几张图来拍板。我用各不相同但互补的三组指标来衡量mAP0.5边界框与真实框交并比大于 0.5 的平均精度、漏检率、过检率。产线方更关心漏检率和过检率因为前者决定了烂果会不会混进好果后者决定了好果会不会被糟蹋掉。最终测试集上六类缺陷的 mAP0.5 数据如下缺陷类别mAP0.5漏检率备注碰压伤0.9214.1%轻度碰压伤仍有漏网腐烂0.9581.2%表现最最好果锈0.9036.3%边缘表现差异大黑斑0.8749.0%小目标依赖高清图刺伤0.86111.0%最浅小的伤难抓霉心0.84214.2%样本量少特征隐蔽综合 mAP0.5 在 0.9 左右640 分辨率下推理延迟约 42 毫秒转 1280 分辨率延迟约 95 毫秒。产线单相机 10 果每秒的速度完全能扛住。6.2 部署时最容易忽略的硬件点模型导出采用 Torch 的 ONNX 导出接口然后用 ONNX Runtime 的 CPU 模式先跑通。苹果产线现场通常没有高端 GPU我用过两种硬件路线一个小型工作站带一张中端显卡推理延迟稳定在 20 毫秒另一个是纯 CPU 工控机640 分辨率下延迟约 120 毫秒只能满足低产能需求。给预算有限的团队的建议优先买带 NPU 的嵌入式板卡或者入门级 GPUCPU 方案看着省钱但产线提速后立刻变成瓶颈。部署还有一个小坑导入 ONNX 模型后输入尺寸得和训练时保持一致否则输出张量形状错位轻则结果不对重则直接报错。我封装模型时会把输入尺寸写进配置文件界面加载模型时强制按该尺寸做预处理。6.3 数据回传让模型越用越准运行一两个月之后现场会积累大量新样本尤其是一些模型初始没见过的光照条件、苹果品种表皮纹理。我建议在 GUI 里额外加一个人工复核入口检测完成后操作员可以对结果进行纠错把漏检或误检的图片打上标签。每周把纠错结果汇入训练集重新增量训练不断提升模型在新工况下的表现。这是落地项目能不能长期稳定运行的关键比一次性调参重要得多。这个项目前前后后我跑了三轮完整的迭代从最初只能识别腐烂到现在六类缺陷都在产线上稳定工作最大的体会就是数据永远比模型更值得砸时间。你给模型看的场景越接近真实工况它回报给你的稳定度就越高。如果手头正准备启动类似项目先把相机、光照、输送线这些物理环节调明白再投入算力去训练别顺序搞反了——排错的时间省下来才是项目真正赚到的部分。