Files
2026-10-05 18:24:57 +08:00

10 KiB
Raw Permalink Blame History

检测方案的写法

中文 | English

检测方案是 recipes/zh/、recipes/en/ 下的 JSON 文件,一个文件对应「视觉检测」页上的一个按钮。直接放在 recipes/ 下的文件在两种语言下都显示;在页面上「另存为我的方案」会存成 recipes/my-*.json。

以下划线开头的字段(_title 按钮名、_note 说明)只给页面用。

{
  "_title": "钢管质检",
  "_note": "一句话说明",
  "id": "steel-pipe",
  "subject": "无缝钢管外表面",
  "max_side": 640,
  "calibration": {"mm_per_px": 0.2},
  "quality": {"min_sharpness": 5, "brightness": [40, 230]},
  "model": "multilingual",
  "pipeline": [ ... ],
  "draw": [ ... ],
  "measurements": { ... },
  "rules": { ... },
  "questions": { ... },
  "bindings": { ... }
}
字段 作用
subject 检测对象的一句话描述,会写进交给模型的素材
max_side 处理尺寸:长边超过它的图先等比缩小。pipeline 里的像素参数都按这个尺寸算。默认 1280
calibration.mm_per_px 原图上每个像素代表多少毫米。写了它,长度按 mm、面积按 mm² 输出;不写就是像素
quality 图像质量门槛:min_sharpness(拉普拉斯方差)和 brightness(平均亮度的上下限,0~255)。不合格时结论标成建议复核
model 默认的判定模型名;页面顶部填了模型名时以页面为准
demo / train 内置方案用:示例图的场景,以及「训练示例模型」用的参数

pipeline:一步一步处理图像

每一步有一个 op,产出一个有名字的结果,后面的步骤和测量值按名字引用。

mask:分割出一张掩膜

{"op": "mask", "name": "rust", "method": "hsv", "ranges": [{"h": [5, 42], "s": [35, 100], "v": [20, 100]}], "within": "pipe", "open": 3, "close": 7, "min_area": 40}
method 做什么 主要参数
hsv 按颜色范围取 ranges:色相 h 用 0360 度,饱和度 s、亮度 v 用 0100。h 写成 [340, 20] 表示跨过 0 度(红色)
exg 超绿指数,从土壤背景里分出绿色植物 threshold:"otsu" 或一个数(如 0.1)
gray 按亮度取 below / above(0~100),或 "otsu": "dark" / "bright";channel 可选 gray、s、v、l
anomaly 和局部背景差得多的地方(划痕、破洞、断纱) pre_blur 先抹掉细纹理;sigma 背景模糊半径,或 "background": "median" + ksize;k 稳健标准差的倍数;min_delta 最小差值;polarity:dark / bright / both
chroma 颜色偏离主色调的地方(污渍) pre_blur、k、min_delta
rows 从植被掩膜里找出作物行的条带 from;min_spacing 行距占画面的最小比例;axis:vertical / horizontal;angle 行的倾角
combine 合并掩膜 of:掩膜名的列表;how:or / and / sub / not

所有方法都可以加后处理,按这个顺序执行:invert、within(只保留某个掩膜内的部分)、exclude、open、close、dilate、erode(核的直径,像素)、fill(填洞)、min_area、keep_largest(只留最大的 n 块)。

regions:从掩膜里取出一个个区域

{"op": "regions", "name": "defects", "from": "defects", "min_area": 12, "max_area": 5000, "where": {"elongation": [">=", 3]}}

每个区域有这些属性:area(面积)、length(长)、width(宽)、diameter(等效直径)、elongation(长宽比)、circularity(圆度,01)、solidity(实心度,01)、bbox。写了 calibration 时,前四个的单位是 mm² 和 mm。

where 用来按属性筛选:{"属性": [">=", 3]}、{"属性": {"min": 1, "max": 5}}、{"label": "scratch"}。测量值里也可以用。

classify:用训练出的模型分类

{"op": "classify", "model": "steel-defects", "on": "regions:defects", "pad": 0.25, "min_prob": 0.5}

on 可以是 regions:区域名(给每个区域标上 label 和 prob)、image(整图分类,结果存进 into)或 tiles(把图切成 tile 大小的块逐块分类,normal 是正常类别的名字,其余类别的块合并成区域)。

模型还没训练时这一步会被跳过,依赖识别结果的测量值没有值,规则裁决不受影响。

detect:外部训练好的检测模型

{"op": "detect", "model_file": "data/onnx/pests.onnx", "classes": ["aphid", "whitefly"], "size": 640, "conf": 0.25, "iou": 0.45, "into": "insects"}

支持 YOLO 系列导出的 ONNX(v5 和 v8 / v11 两种输出排布)。classes 是训练时的类别名,顺序要一致。模型文件不存在时,依赖它的测量值记为「没有测到」,绑定的规则会提示复核,而不是按 0 处理。

measurements:测量值

"scratch_len": {
  "label": "最长划痕", "unit": "mm", "decimals": 1, "guard": 1, "range": [0, 200],
  "compute": {"type": "max", "regions": "defects", "attr": "length", "where": {"elongation": [">=", 4]}}
}
字段 作用
label / unit 显示的名字和单位,会写进素材
decimals 小数位。测量值先四舍五入到这个位数,再拿去比较和显示
guard / guard_pct 临界带:测量值离阈值不超过它(绝对值,或阈值的百分比)时,这次比较算「临界」
bands 档位,从低到高:[{"max": 30, "label": "未熟"}, {"max": 70, "label": "转色"}, {"label": "成熟"}]
limit 这一项自己的限值 {"min": .., "max": ..},会写进素材(规则里用到的阈值会自动写,不用重复)
range / typical 自检生成用例时用的取值范围和典型值
hidden true = 不写进素材
compute 怎么算,见下表。不写 compute 的项只能由接口的 values 提供
compute.type 含义 参数
area_ratio 掩膜面积占比(%) mask;within(分母用另一个掩膜,不写就是整幅图)
area 掩膜面积 mask
count 区域个数 regions、where
sum / max / min / mean 区域某个属性的合计 / 最大 / 最小 / 平均 regions、attr、where
density 单位面积的个数 regions;per:m2 / dm2 / cm2;within
mean_channel 某个通道的平均值 channel、mask
class_count / class_ratio 识别为某一类的个数 / 占比 regions、label
top_label 数量最多的类别 regions
label 整图分类的结果 from
input 由请求里的 inputs 提供(其它传感器的读数) default
expr 用其它测量值算出来 expr:四则运算和 min max abs round sqrt,如 "defect_area / pipe_area * 100"

rules:硬规则

条件由比较和 all(且)、any(或)、not(非)组成。比较写成 {"m": "测量值名", "op": ">=", "value": 70},op 可以是 >= > <= < == != in between。

是非规则写 when:

"pick": {"label": "达到采摘标准", "when": {"all": [{"m": "ripe_ratio", "op": ">=", "value": 70}, {"m": "blemish_ratio", "op": "<=", "value": 3}]}}

分档规则写 cases,从上往下第一个成立的生效,最后一档不写 when 作为兜底:

"verdict": {"label": "质检结论", "cases": [
  {"value": "不合格", "when": {"any": [{"m": "scratch_len", "op": ">", "value": 20}, {"m": "pit_count", "op": ">=", "value": 2}]}},
  {"value": "让步接收", "when": {"m": "defect_count", "op": ">=", "value": 1}},
  {"value": "合格"}]}

questions 和 bindings

questions 和「文本判定」页的题目格式完全一样(noul 是非、choice 单选、score 打分)。

bindings 把题目绑到规则上:

"bindings": {"pick": {"rule": "pick"}, "verdict": {"rule": "verdict", "mode": "advise"}}
  • 是非题绑是非规则;单选题绑分档规则,规则的 value 必须是题目的选项;打分题绑分档规则,value 是等级的序号
  • mode 默认是 enforce:规则的结果就是结论,模型的答案只用来核对。advise:模型的答案是结论,规则的结果作为参照。先跑「数字自检」,一致率够高再考虑改成 advise
  • 绑定了规则的题目,发给模型时题干后面会自动附上判定标准。不想附的话加 "show_standard": false
  • 没有绑定的题目完全由模型回答

draw:标注图

"draw": [
  {"mask": "rust", "color": "#f59e0b", "alpha": 0.35, "label": "锈斑"},
  {"regions": "defects", "color": "#e11d48", "label": "缺陷", "colors": {"scratch": "#3b82f6"}}
]

不写 draw 时画出所有区域。图上的编号对应「测量与区域」页签里的表格。

接口

页面用到的接口都可以直接调用,请求头 X-WB-Target 指定判定模型走哪个接口(auto / local / 远程接口的 id)。

接口 作用
POST /v1/inspect {recipe, image 或 values, context, model, min_confidence, use_model, inputs} → 测量、素材、每题结论。recipe 可以是方案的 id,也可以是完整的方案对象
POST /v1/inspect/selftest {recipe, model, limit} → 数字自检报告
GET /v1/inspect/selftest?recipe= 上一次的自检报告
POST /v1/inspect/review {recipe, state, questions, gold} → 记下一条人工复核
GET /v1/inspect/export?recipe= 导出 {state, questions, gold} 的 JSONL
POST /v1/vision/analyze {recipe, image, inputs} → 只做图像分析,不调用判定模型
GET /v1/vision/datasets、POST /v1/vision/datasets/add 数据集
POST /v1/vision/train、GET /v1/vision/jobs?id= 训练和进度
GET /v1/vision/models、POST /v1/vision/predict 模型列表和单张图分类

完整的参数说明在 app/vision_server.py 和 app/launcher.py 开头的注释里。

调参的顺序

  1. 固定拍摄条件:距离、角度、光照、背景。这比任何算法参数都重要
  2. 在画面里放一把尺,算出 mm_per_px
  3. 先只写分割步骤,在页面上看标注图对不对;颜色范围用取色工具量出 H、S、V 再留一点余量
  4. 加上测量值,用几张已知结果的图核对数值
  5. 写规则和题目,阈值来自你的标准;给贴近阈值时不可靠的测量值设 guard
  6. 需要区分类别时再训练识别模型
  7. 接上判定模型后跑一次「数字自检」