AI 工业质检真正难在哪:从缺陷模型到 RT-Thread 产线闭环

2026-09-08 37 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:12 分钟

划痕、裂纹、漏装、错装等缺陷很适合用机器视觉识别,但“模型能识别一张图片”和“系统能在产线上连续运行”是两件事。工业质检的成本往往分散在数据采集、样本标注、节拍控制、设备联动、异常处置和版本追溯中,而不只是模型训练。

真正的最后一公里,是把一次推理变成可重复、可审计、可回退的生产流程。RT-Thread 这类实时操作系统可以承担设备控制、任务调度和故障保护;视觉模型则可以运行在带 NPU/GPU 的边缘计算单元上。二者不必部署在同一颗芯片上,但必须形成清晰、稳定的接口。

不要从模型选型开始,先定义什么叫检得准

每一种工件、工艺和缺陷都有不同的判定边界。即使都是“划痕”,喷涂件、玻璃和金属件在长度、深度、方向以及可接受程度上也不一样。因此,项目启动时应先把业务标准转换为可计算的验收指标。

至少需要明确以下内容:

项目 需要回答的问题
缺陷分类 哪些必须拦截,哪些允许放行,哪些需要人工复核?
漏检率 对关键缺陷最多允许漏掉多少?
误检率 错误剔除会造成多少停线或材料损失?
推理延迟 从触发拍照到输出结果,必须控制在多少毫秒内?
环境范围 光照、温度、振动、工件速度变化到什么程度仍需正常工作?
追溯信息 是否保存图片、工位、批次、模型版本和判定结果?

准确率通常不是最重要的单一指标。假设 99.5% 的产品都是良品,一个始终输出“良品”的模型也能得到 99.5% 的准确率,却完全没有质检价值。关键缺陷应单独统计漏检率,并结合误检率、P95 延迟和人工复核比例一起验收。

数据集也不能只做随机切分。相邻时间拍摄的图片往往拥有相同光照、背景和工件批次,随机分配到训练集和测试集后,会产生看似很高的测试成绩。更稳妥的方式是按日期、产线、批次或供应商切分,并保留真实生产环境中的独立验收集。

把推理节点放进确定性的产线链路

一种可以实践的分层架构如下:

相机/光源
    │ 触发与图像
    ▼
边缘计算单元(预处理、模型推理、置信度计算)
    │ request_id + verdict + model_version + latency
    ▼
RT-Thread 控制节点(状态机、超时、队列、看门狗、I/O)
    │
    ├── PLC/机械执行机构:放行、剔除、停线
    └── 本地缓存/工厂系统:图片索引、批次、结果、告警

这套分工的重点不是限定硬件,而是隔离职责:模型负责给出判断,实时控制节点负责在规定时间内执行安全动作。即使推理运行在同一台设备上,也建议保留逻辑边界。

推理结果至少应包含:

  • request_id:关联触发信号、图片和工件;
  • verdict:例如 OKDEFECTREVIEW
  • defect_type:缺陷类别;
  • confidence:用于复核分流,而不是直接代表真实概率;
  • model_version:支持追溯和回滚;
  • latency_ms:检查是否满足产线节拍;
  • error_code:区分超时、图片损坏、模型不可用等异常。

控制逻辑不能把“没有收到结果”解释成“良品”。当消息队列已满、相机断连、推理超时或模型进程失效时,应进入预先约定的安全状态,例如转人工复核、暂停进料或停线告警。具体选择取决于漏检风险和停线成本。

用可执行的质量门禁阻止不合格模型上线

下面是一个可以直接运行的最小验收脚本。假设离线验收结果使用 CSV 保存,标签只有 OKDEFECT。实际项目可以按缺陷类别扩展,并把阈值改成工厂确认过的标准。

将以下内容保存为 quality_gate.py

#!/usr/bin/env python3
import argparse
import csv
import math
import sys


def ratio(numerator, denominator):
    return numerator / denominator if denominator else 0.0


def percentile(values, p):
    if not values:
        return 0.0
    values = sorted(values)
    index = max(0, math.ceil(len(values) * p) - 1)
    return values[index]


parser = argparse.ArgumentParser()
parser.add_argument('csv_file')
parser.add_argument('--max-fn-rate', type=float, default=0.02)
parser.add_argument('--max-fp-rate', type=float, default=0.03)
parser.add_argument('--max-p95-ms', type=float, default=120.0)
args = parser.parse_args()

with open(args.csv_file, newline='', encoding='utf-8') as file:
    rows = list(csv.DictReader(file))

required = {'ground_truth', 'predicted', 'latency_ms', 'model_version'}
if not rows or not required.issubset(rows[0]):
    sys.exit(f'CSV 缺少字段,必须包含: {sorted(required)}')

valid_labels = {'OK', 'DEFECT'}
for line, row in enumerate(rows, start=2):
    if row['ground_truth'] not in valid_labels or row['predicted'] not in valid_labels:
        sys.exit(f'第 {line} 行标签非法')

tp = sum(r['ground_truth'] == 'DEFECT' and r['predicted'] == 'DEFECT' for r in rows)
fn = sum(r['ground_truth'] == 'DEFECT' and r['predicted'] == 'OK' for r in rows)
fp = sum(r['ground_truth'] == 'OK' and r['predicted'] == 'DEFECT' for r in rows)
tn = sum(r['ground_truth'] == 'OK' and r['predicted'] == 'OK' for r in rows)
latencies = [float(r['latency_ms']) for r in rows]
versions = {r['model_version'] for r in rows}

fn_rate = ratio(fn, tp + fn)
fp_rate = ratio(fp, fp + tn)
p95_ms = percentile(latencies, 0.95)

print(f'samples={len(rows)} model_versions={sorted(versions)}')
print(f'TP={tp} FN={fn} FP={fp} TN={tn}')
print(f'false_negative_rate={fn_rate:.4%}')
print(f'false_positive_rate={fp_rate:.4%}')
print(f'p95_latency_ms={p95_ms:.2f}')

passed = (
    len(versions) == 1
    and fn_rate <= args.max_fn_rate
    and fp_rate <= args.max_fp_rate
    and p95_ms <= args.max_p95_ms
)

if not passed:
    sys.exit('QUALITY GATE: FAILED')

print('QUALITY GATE: PASSED')

准备一份示例数据并执行:

cat > inspection_results.csv <<'CSV'
ground_truth,predicted,latency_ms,model_version
OK,OK,71.2,scratch-detector-1.4.0
DEFECT,DEFECT,84.5,scratch-detector-1.4.0
OK,OK,69.8,scratch-detector-1.4.0
DEFECT,DEFECT,91.3,scratch-detector-1.4.0
OK,DEFECT,78.6,scratch-detector-1.4.0
CSV

python3 quality_gate.py inspection_results.csv \
  --max-fn-rate 0.02 \
  --max-fp-rate 0.40 \
  --max-p95-ms 120

示例为了使用少量数据,把误检率阈值放宽到了 40%,不能直接作为生产标准。实际验收集应覆盖不同批次、班次、光照、设备状态和典型缺陷,并由工艺与质量团队共同确定阈值。质量门禁可以接入 CI/CD:脚本返回非零退出码时,阻止模型包进入生产发布阶段。

上线之后,数据闭环比继续调参更重要

工业现场会不断产生训练阶段没有覆盖的变化:新供应商改变了材料纹理,相机支架发生轻微位移,灯具老化导致曝光变化,或者工艺改进改变了正常产品的外观。模型上线不是项目终点,而是新一轮数据采集的起点。

可以建立以下闭环:

  1. 将低置信度、人工改判和设备异常样本放入待复核队列;
  2. 优先标注模型意见与人工结论不一致的图片,而不是无差别保存全部图像;
  3. 为图片记录批次、工位、时间、相机参数和模型版本;
  4. 新模型先运行影子模式,只记录判断,不驱动剔除机构;
  5. 对比新旧模型在相同工件上的结果,通过门禁后再灰度切换;
  6. 保留上一版本模型和配置,确保现场可以快速回滚。

还应考虑存储和网络边界。生产图片可能包含产品设计或工艺信息,不宜默认上传到公网。网络中断时,控制链路必须继续工作;本地缓存达到上限后,应按风险策略删除旧良品图片、保留缺陷与异常记录,而不是阻塞实时任务。

落地前的检查清单

一个可上线的 AI 质检系统,至少应能回答这些问题:

  • 缺陷标准是否由工艺、质量和算法团队共同确认?
  • 测试集是否按批次或时间隔离,避免数据泄漏?
  • 推理超时、相机断连和消息丢失时,RT-Thread 控制节点进入什么状态?
  • 每次判定能否追溯到图片、工件、参数和模型版本?
  • 新模型是否经过离线门禁、影子运行和小范围灰度?
  • 是否存在无需联网的降级方案,以及可验证的回滚路径?

工业质检的竞争力并不只来自更复杂的网络结构。稳定的采集标准、可信的验收数据、确定性的实时控制和持续运行的数据闭环,才是把演示模型变成生产设备的关键。


相关推荐