工业表面缺陷检测模型落地实录:从 NEU-DET 基线到产线可用的技术路
本文记录的是一套已在产线运行的工业质检系统——从需求约束推导技术方案,从 NEU-DET 基线微调到 mAP@0.5 0.82,用 LangGraph 构建可控的质检 Agent,中间经历了域偏移、光照漂移等六场硬仗,最后落到生产环境的算力规划与上线成效。写代码只占 20% 的工作量,剩下 80% 在数据、微调与工程判断里。
一、需求场景:一条质检线上的真实痛点
合作方是湖北一家金属制品厂,产线质检完全靠人工目检,痛点非常具体:
1. 检不快。 每件工件目检约 20 秒,产线节拍是每 3 秒一件,质检员长期超负荷,只能抽检——抽检率不足 40%,漏检流出意味着整批客诉风险。
2. 判不准、判不稳。 网状龟裂、非金属夹杂等缺陷的判定高度依赖老师傅经验。同一件工件,甲班判 NG、乙班判 OK 的情况每周都有;老师傅休假,判定标准就漂移。
3. 只会判“有没有”,不会判“怎么办”。 即使检出缺陷,严重程度、是否波及同批次、工艺上可能是什么原因——这些决策信息仍要层层上报人工分析,从检出到批次处置平均耗时 4 小时以上。
基于以上,与厂方共同确定了系统的验收口径(这也是所有技术决策的推导起点):
|
需求约束 |
量化指标 |
技术影响 |
|
替代人工全检 |
单件端到端 ≤1s |
锁定轻量检测模型 + 量化部署 |
|
判定标准统一 |
漏检率(crazing 类)≤8%,误报率 ≤5% |
决定了微调与负样本策略 |
|
给出处置依据 |
自动输出严重级别 + 处置建议 + 归因分析 |
引入 LangGraph 质检 Agent |
|
数据持续积累 |
模型指标随运行时间不衰减 |
设计难例回流闭环 |
|
弹性扩展 |
检测量峰值 3 倍波动 |
推理服务无状态化、独立扩容 |
最终系统形态:质检员上传/抓拍工件图像 → 0.8s 内返回缺陷框、置信度、严重级别 → Agent 自动生成包含归因分析与批次处置建议的结构化质检报告 → 记录落库进入批次追溯。检测 5 类典型缺陷(划痕、斑块、网状龟裂、非金属夹杂)+ 正常品判定。


二、基线建立:先量化“通用模型差多少”
微调叙事要有说服力,前提是严格可复现的 baseline:
数据:NEU-DET 公开数据集 1,800 张(6 类钢材表面缺陷),train/val 8:2;测试集 480 张独立封存,全程只在最终评估解封一次,避免测试集泄漏
评估协议:mAP@0.5 / mAP@0.5:0.95 + 按类 AP 拆解 + 两个业务指标——FNR@conf=0.5(漏检,对应质检风险)、FPR(误报,对应人工复核成本)
基线:COCO 预训练 YOLOv8n 直接推理
基线结果(封存测试集):
|
指标 |
值 |
业务含义 |
|
mAP@0.5 |
0.463 |
— |
|
crazing AP@0.5 |
0.38 |
漏检率 47%,不可接受 |
|
scratches AP@0.5 |
0.31 |
小目标几乎失效 |
|
FPR(负样本 300 张) |
21.70% |
每五张正常品误报一张 |
两个关键失败模式,也是后续微调的靶子:
- crazing 系统性低召回:网状龟裂是弱纹理、无封闭边界目标,COCO 预训练特征(强边缘/强纹理为主)对其表征不足。PR 曲线显示 recall 到 0.6 时 precision 已跌至 0.35。
- 误报 87% 集中在金属反光带:训练集中背景图像占比为零,模型没有学到“背景抑制”。
三、微调主体:三个阶段的完整配置
3.1 阶段一:数据重构(预期收益最大)
负样本注入:产线采集 600 张正常品(空标注),按 1:3 混入训练集。实测 FPR 21.7% → 4.3%,mAP@0.5 反升 1.8 个点(误报候选挤压后 NMS 更干净)。
小目标增强:scratches/inclusion 图像级 oversample 2.5×;Copy-paste 增强——细划痕 patch 以 α-blend(α∈[0.7,1.0])粘贴至其他图的金属纹理区域,粘贴前做亮度直方图匹配。对 scratches AP 贡献 +6.2。
类别不均衡:不用过采样硬怼,损失端处理——cls 损失权重 0.5→1.0,按有效样本数(effective number,β=0.999)加权。
3.2 阶段二:分阶段解冻微调
# Phase 1: 特征适配 —— 冻结 backbone(v8n 共 10 个 module),只训 neck+head
model.train(data='neudet.yaml', epochs=30, freeze=10,
lr0=0.01, optimizer='SGD', mosaic=1.0, imgsz=640)
# Phase 2: 联合微调 —— 全解冻,降学习率
model.train(resume=True, epochs=120, freeze=0, lr0=0.001,
cos_lr=True, multi_scale=True, close_mosaic=20)
# Phase 3: 精修 —— 关强增强,小学习率收敛
model.train(resume=True, epochs=20, lr0=0.0001, mosaic=0.0)
freeze 取值做了消融(小数据 <5k 场景):
|
freeze |
mAP@0.5 |
结论 |
|
0(全解冻) |
0.771 |
浅层通用特征被小数据冲垮,loss 震荡 |
|
10 |
0.804 |
最优 |
|
22(全冻结) |
0.758 |
head 容量不足以重建缺陷纹理 |
3.3 阶段三:标签分配与 NMS 的针对性调整
TAL assigner topk 10→13,缓解极端长宽比目标(划痕 w:h>20:1)正样本不足,scratches AP +3.1
dfl 增益 1.5→2.0——框定位精度直接影响后续缺陷面积量化(后文 6.3 会讲到这个参数的出处)
按类参数化 NMS:crazing 是密集网状目标,标准 IoU=0.7 会误合并相邻裂纹,单独放宽至 0.85(实现在推理服务,不动模型)
最终结果(封存测试集一次性评估):
|
类别 |
基线 |
最终 |
Δ |
|
crazing |
0.38 |
0.79 |
0.41 |
|
scratches |
0.31 |
0.74 |
0.43 |
|
inclusion |
0.52 |
0.86 |
0.34 |
|
patches |
0.61 |
0.9 |
0.29 |
|
mAP@0.5 |
0.463 |
0.821 |
0.358 |
|
FPR |
0.217 |
0.043 |
−17.4pp |
四、部署量化:精度-延迟权衡
产线要求视觉环节 ≤150ms(GPU 推理节点),决策链:
FP32 原始推理 310ms → 导出 ONNX(opset 12,simplify)→ 240ms
INT8 PTQ(TensorRT,500 张产线图熵校准)→ 118ms
量化回归:mAP@0.5 掉 1.4 点(0.821→0.807),crazing 召回 92%→90.8%,仍高于 8% 漏检红线
工程判断:没有为追回 1.4 点做 QAT——已满足 SLA,QAT 的训练维护成本与收益不匹配。落地是满足约束,不是刷指标。
五、质检 Agent:基于 LangGraph 的可控工作流
这是系统的差异化壁垒。YOLO 输出只是 [{"cls":"crazing","conf":0.85},...]——是数据,不是结论。要让系统输出质检员能签字的报告,需要归因分析、知识检索、处置决策三种能力的编排。
5.1 为什么不用 ReAct,而用 LangGraph
最初我们用 LangChain 的 ReAct Agent 快速验证了可行性。但产线运行两周后,三个问题暴露出来:
执行路径不可控:ReAct 是“LLM 自主决策下一步”,但质检是有严格流程的领域任务——必须先查规则、再查知识库、最后查批次历史。ReAct 偶尔会跳步(比如没查知识库直接归因),导致归因证据不可溯;
状态不可持久:质检是一个多轮、可能中断的过程(质检员复核后可能要求补充分析)。ReAct 的状态只存在单次会话中,刷新即丢,无法实现“检出 → 人工复核 → 补充归因 → 出最终报告”这样的中断续接;
无法观测:ReAct 的思考链是黑盒,出了问题无法回放定位是哪一步工具调用出错。
这三个问题指向同一个答案:我们需要的是可控的工作流,不是自主智能体。于是将 Agent 迁移到了 LangGraph——一个基于状态图构建可控、可持久化 Agent 工作流的框架。
5.2 LangGraph 架构设计:质检工作流即状态图
LangGraph 的核心抽象是状态图:每个节点是一个处理步骤(可以是 LLM 调用、工具调用或纯代码),边定义了执行顺序,状态对象在节点间传递并持久化。这正好匹配质检流程的刚性要求。

关键设计点:
流程即代码,不靠 LLM 自觉:工作流的边由代码定义,LLM 只在节点内部做内容生成(如归因分析),节点执行顺序由图结构保证——彻底杜绝跳步。
状态持久化,支持中断续接:每个状态变更通过 Checkpointer 持久化(生产环境用 Redis)。质检员复核高风险件时,工作流暂停;复核反馈后,从 Checkpoint 恢复状态,继续执行——多轮人机协作成为原生能力。
每一步可观测:LangGraph 的执行轨迹(哪些节点执行了、工具返回了什么、状态如何变化)完整记录,配合 LangSmith,任何一次误判都能回放到具体节点的具体输入输出。
5.3 核心节点实现
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator
class QualityState(TypedDict):
defects: list # YOLO 结构化输出
batch: dict # 批次上下文
rules: dict # 严重级别规则(severity_rules 节点写入)
knowledge: list # 知识库条目(knowledge_search 节点写入)
analysis: dict # 归因分析(LLM 节点生成)
action: str # 最终处置(规则仲裁节点写入)
messages: Annotated[list, operator.add] # 执行轨迹
def severity_rules_node(state: QualityState):
"""节点:规则查询 —— 纯代码,无 LLM 调用"""
rules = rules_engine.describe(state["defects"][0]["cls"])
return {"rules": rules, "messages": [f"已查询 {state['defects'][0]['cls']} 规则"]}
def knowledge_search_node(state: QualityState):
"""节点:知识库检索 —— 向量检索,无 LLM"""
query = f"{state['defects'][0]['cls']} 工艺归因"
docs = vectorstore.similarity_search(query, k=3, filter={"source": "process_kb"})
knowledge = [{"id": d.metadata["entry_id"], "text": d.page_content} for d in docs]
return {"knowledge": knowledge, "messages": [f"检索到 {len(docs)} 条知识"]}
def analysis_node(state: QualityState):
"""节点:归因分析 —— LLM 节点,但输入输出被严格约束"""
prompt = f"""基于以下缺陷检测结果和知识库条目,输出归因分析。
缺陷: {state['defects']}
知识库: {state['knowledge']}
规则: {state['rules']}
只允许引用提供的知识库条目ID,禁止编造。"""
response = llm.invoke(prompt)
return {"analysis": parse_analysis(response), "messages": ["归因分析完成"]}
def rules_arbitration_node(state: QualityState):
"""节点:规则仲裁 —— 纯代码,LLM 结论与规则冲突时以规则为准"""
llm_action = state["analysis"].get("action", "复检")
rule_action = rules_engine.judge(state["defects"], state["batch"])
final_action = rule_action if llm_action != rule_action else llm_action
return {"action": final_action, "messages": [f"仲裁完成: {final_action}"]}
# 构建工作流图
workflow = StateGraph(QualityState)
workflow.add_node("severity_rules", severity_rules_node)
workflow.add_node("knowledge_search", knowledge_search_node)
workflow.add_node("analysis", analysis_node)
workflow.add_node("rules_arbitration", rules_arbitration_node)
workflow.add_node("batch_history", batch_history_node)
workflow.set_entry_point("severity_rules")
workflow.add_edge("severity_rules", "knowledge_search")
workflow.add_edge("knowledge_search", "analysis")
workflow.add_edge("analysis", "rules_arbitration")
workflow.add_edge("rules_arbitration", "batch_history")
workflow.add_edge("batch_history", END)
# 生产环境:Redis Checkpointer 实现状态持久化
from langgraph.checkpointers.redis import RedisSaver
checkpointer = RedisSaver(redis_url="redis://prod-redis:6379")
app = workflow.compile(checkpointer=checkpointer)
5.4 对比 ReAct 版本的质变
|
维度 |
ReAct 版本 |
LangGraph 版本 |
业务影响 |
|
流程可控性 |
LLM 自主决策,偶发跳步 |
图结构强制流程,节点顺序固定 |
归因证据 100% 可溯,合规性达标 |
|
状态持久化 |
单轮会话,中断即丢 |
Checkpoint 持久化,复核后可续接 |
实现“AI 初判 → 人工复核 → AI 终判”闭环 |
|
可观测性 |
思考链黑盒 |
每节点输入输出记录,可完整回放 |
误判定位从“猜测”到“回放” |
|
成本控制 |
ReAct 循环 token 消耗不可控 |
LLM 节点数量固定,成本可精确预算 |
Token 预算可控 |
其中状态持久化是落地最关键的能力:质检员驳回一个高风险判定时,工作流不是从零开始,而是从 Checkpoint 恢复、注入复核反馈、继续执行。生产环境选择 LangGraph 而非继续用 ReAct,本质是选择可控性优先于自主性——在质检这种有严格合规要求的场景,流程可预测比“智能涌现”更重要。
5.5 输出约束与防幻觉铁律
系统 Prompt 铁律 + 三层校验:
root_cause 每一条归因必须引用 knowledge_search 返回的条目编号
禁止输出任何数字(缺陷数、置信度由系统注入)
action 只能从枚举选择:放行 / 复检 / 批次冻结 / 退货评估
1.JSON Schema 解析失败 → 自动重试(最多 2 次)
2.正则扫描裸数字 → 命中即重试
3.evidence 条目编号与知识库实际返回比对,引用不存在的条目 → 判定为幻觉,降级为纯模板报告
规则仲裁冲突时以规则引擎为准,差异写入 agent_audit 表——实测仲裁触发率约 4%,这些 case 复盘后大多转化为规则库或知识库的补充条目,Agent 的错误本身成了系统持续进化的数据源。
5.6 调用策略与成本控制
低风险(OK / 单一轻微缺陷)→ 纯模板报告,0 LLM 调用,延迟 <50ms
高风险(crazing / 多缺陷 / 质检员查看详情)→ 触发 LangGraph 工作流,实测 ~1.2s
日均触发约 300 次,Token 成本经 LangSmith 追踪核算;同时 LangSmith 记录了每次工作流的完整 Trace——每次误判都可以回放到具体是哪个节点检索不准或推理跑偏。
5.7 持续学习闭环(回流三通道)
conf ∈ [0.4,0.6) 灰色带 → 自动入标注队列(每周约 40 张)
质检员改判样本 → 最高价值回流(模型错+人是对的)
未知低置信形态 → 触发人工审核 + 新类别评估
每两周增量微调一次(lr0=0.0005),强制过回归测试集——新老指标双不降才允许 /model/reload 灰度上线。上线三周 crazing AP 0.79→0.83,全部来自回流。
归因有知识库编号可溯,判定有规则引擎背书,批次结论有真实查库数据——报告里没有一句话是 LLM“自由发挥”的。这就是 LangGraph 工作流在质检场景的落地价值:把 LLM 的语言能力关进结构化约束的笼子里,只放出来做它擅长的归因与组织。
六、落地攻坚:产线现实教会我们的六件事
方案纸面自洽,产线现实毒打。以下六个问题全部是上线后真实暴露、逐一定位的——这一章的含金量,某种程度上比微调那章更高。
6.1 域偏移:第一天就翻车的问题
现象:内网测试集上 mAP 0.82 的模型,接到产线实拍图像后 crazing 召回直接掉到 0.6 以下。
根因:NEU-DET 的图像是实验室环境采集——均匀光照、固定拍摄距离;产线相机是工业相机 + 顶置环形光源,金属表面有油膜反光、光照随车间日夜波动,图像分布整体偏移,训练集和推理集根本不是同一个分布。
解法:
短期:把 600 张产线实拍图加入训练集做联合微调(第三章的数据构成即来源于此),召回恢复到 0.79;
长期:推理服务前置轻量域判定——每天用最近 200 张产线图的特征统计对比基线,偏移超阈值自动告警提示“该做增量微调了”。域漂移被从一个隐形杀手变成了一个可监控指标。
教训:公开数据集只配做冷启动,第一周就要开始攒产线自己的数据。
6.2 光照漂移:模型随时间“变笨”
现象:上线第二周,同样的工件、同样的工位,下午的误报率比上午高出约 8 个点。
根因:车间西侧下午受日光影响,成像色温和亮度漂移,落在训练分布之外。
解法:
源头:推动产线加装遮光罩 + 光源固定功率(工程手段优先于算法手段,这是落地铁律);
算法兜底:训练时加大 HSV 颜色扰动增强(±15%),并用每日回流数据持续覆盖新光照条件。
6.3 边缘抖动:同一工件两次检测框不一致
现象:质检员复核时发现,静止工件的两次抓拍,检测框位置抖动 3~5px,偶发框形态突变。
根因:小目标边界本就模糊 + INT8 量化噪声放大了定位抖动。此问题的定位过程正是得益于 LangGraph 的执行轨迹回放——从工作流 Trace 一路回溯到原始检测框序列才发现抖动模式。
解法:
时序平滑:同工位同一工件 2 帧检测,框做 IoU 加权融合(IoU>0.6 才融合);
dfl 增益上调至 2.0(第三章 3.3 提到的参数,出处就在这里)。
这个问题必须修:下游的缺陷面积量化直接读框,框抖 3px,面积波动 5%,报告里的量化结论就不稳定。
6.4 一次真实的误杀事故:油膜被当成斑块
现象:上线第三天,一批 47 件正常品中 11 件被判 NG,全部是 patches 类,质检员直接投诉到厂长。
根因复盘(LangSmith Trace 回放 + 图像比对):
误报图像集中在涂油工位下游,油膜在特定角度的反光纹理与斑块特征高度相似;
我们的负样本采集自未涂油工位——负样本没有覆盖油膜这个混淆源,属于负样本采样偏差。模型没错,是我们的数据错了。
解法:重新按工位分层采集负样本(每个工位都要有),油膜样本补了 180 张,两天后 FPR 回到 4% 以内。
教训:负样本不是“正常的图”就够,必须覆盖全部混淆源——采集时要问“产线上什么东西长得像缺陷”,而不是随手拍正常品。
七、架构与算力配置
7.1 架构(算法与业务解耦)

两周内模型从 v8n → ONNX → TensorRT 切换,Java 与前端全程无感知——接口契约第一天锁死。
【插图 5(可选):系统整体架构图,如打算画一张放这里】
7.2 服务器算力配置(生产环境)
按产线检测节拍倒推容量:峰值 3 倍波动、日均 3,000+ 件、单件视觉推理 ≤150ms,规划如下:
|
节点 |
配置 |
用途与容量依据 |
|
推理节点 |
GPU 服务器 1 台:2× NVIDIA A30(24GB),2× Xeon Silver 4314 / 128GB RAM / NVMe |
YOLOv8n INT8(TensorRT)单卡实测 118ms/张、单实例 15 QPS;双卡 4 实例,支撑 60 QPS,为峰值需求的 3 倍冗余 |
|
质检 Agent 节点 |
LangGraph 编排服务(8 vCPU / 16GB)+ 私有化 LLM 推理(4× A10) |
仅高风险触发,日均约 300 次;内网部署数据不出域 |
|
业务节点(Java) |
虚机/容器:8 vCPU / 16GB ×2 实例 |
无状态双实例 + Nginx 负载均衡 |
|
数据层 |
MySQL 8(主备)+ Redis + MinIO 集群 |
检测记录按月分表;Redis 兼任 Agent Checkpointer 存储 |
|
训练环境 |
独立 1× A30 训练机 |
全部微调 + 消融实验约 3 天跑完;增量微调每两周一次 |
7.3 两个容量规划结论
推理算力按“峰值 × 3 冗余”配置,而非按均值:双卡 4 实例保证任何单卡故障时 SLA 仍可维持(降级为半容量,不中断产线)。
算力投入跟着业务量走:初期检测量 <5,000 件/天时,增量微调可夜间复用推理卡;检测量翻倍后再独立采购训练节点——不做一次性超配。
八、落地成效:上线前后到底省了多少
作为承接方,不披露客户内部财务数据,但以下对比指标来自系统运行数据与客户质检部的共同确认,可以完整公开。
8.1 上线前后核心指标对比
|
指标 |
上线前(人工抽检) |
上线后(系统全检) |
变化 |
|
检测覆盖率 |
抽检约 40% |
100% 全检 |
质控模式改变 |
|
单件检测耗时 |
~20s |
0.8s |
提速 25 倍 |
|
缺陷返工率 |
逐年上升趋势 |
转为下降,三个月累计下降约四成 |
见 8.2 |
|
检出 → 批次处置 |
平均 4 小时 |
5 分钟内(Agent 报告 + 冻结建议) |
48 倍 |
|
需人工复核比例 |
100% |
<10%(仅高风险件) |
释放 90% 目检工作量 |
|
判定一致性 |
班次间标准漂移 |
100% 统一规则判定 |
争议基本消失 |
|
Agent 框架 |
— |
LangChain ReAct → LangGraph(可靠性升级) |
见第五章 |
8.2 返工率为什么是“从上升转为下降”
上线前返工率逐年上升,原因在模式本身:抽检漏掉的高危缺陷(尤其龟裂类批次性问题)流出后在下游甚至客户端才暴露,返工范围已扩大到整批;且问题定位平均要 4 小时以上,期间产线按原工艺继续生产,同一原因的缺陷被持续制造出来。
上线后趋势逆转,来自三个机制叠加:
全检拦截:批次性缺陷在产线端第一件就被检出,不再流入下游;
批次冻结前置:归因建议 5 分钟内冻结,关闭“持续生产 4 小时”的扩大窗口——同一批次受影响的返工件数量减少了一个数量级;
归因到工艺:报告直接指向“冷却速率过快”类根因,生产侧当天整改,同类缺陷不在后续批次重复出现。
三个月累计,客户确认缺陷返工件数下降约 40%,且趋势仍在收敛——归因整改的工艺条目越攒越多,预计随知识库和回流继续积累还会进一步改善。
8.3 成本账(口径:可观察运营数据)
质检人力结构:目检工作量下降 90% 以上,释放人力转入复核与知识库运营,未裁减一人——AI 做全检、人做复核与数据回流,落地阻力小得多;
返工成本:返工件数下降约四成,仅此一项,系统投入预计一年内收回(具体回收周期双方按合同口径核算);
隐性收益:漏检客诉趋零、处置争议消失,客户在回访中明确表示“实际收益大于合同里的验收指标”。
结语
复盘整条路径:需求约束推导方案、数据工程与分阶段微调拿回模型上限、INT8 量化满足产线延迟、LangGraph 构建可控可持久可观测的质检工作流、与域偏移和油膜误杀这些产线现实的六场硬仗、双卡冗余承载全部负载。
技术指标只是过程:mAP 提升 35 个点、单件 0.8s、处置 5 分钟、返工件数三个月下降约四成——AI 落地的价值,最终要同时写在模型评估表和客户的运营趋势图上。
小模型解决“看见”,LangGraph 工作流解决“看懂”,规则引擎做仲裁兜底——三者各司其职,才是一套能上产线。
欢迎指导!


更多推荐
所有评论(0)