10 KiB
检测方案的写法
中文 | 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 用 0s、亮度 v 用 0h 写成 [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)、1)、solidity(实心度,0bbox。写了 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 开头的注释里。
调参的顺序
- 固定拍摄条件:距离、角度、光照、背景。这比任何算法参数都重要
- 在画面里放一把尺,算出
mm_per_px - 先只写分割步骤,在页面上看标注图对不对;颜色范围用取色工具量出 H、S、V 再留一点余量
- 加上测量值,用几张已知结果的图核对数值
- 写规则和题目,阈值来自你的标准;给贴近阈值时不可靠的测量值设
guard - 需要区分类别时再训练识别模型
- 接上判定模型后跑一次「数字自检」