C#工业机器视觉Recipe配方系统设计:多型号、多相机、多工位参数如何统一管理

摘要

在非标机器视觉项目中,产品型号一旦从单一型号扩展到多个型号,软件复杂度往往会快速上升。相机曝光、视觉参数、ROI、检测阈值、PLC地址、存图规则等配置如果散落在代码和界面控件中,后期维护会非常困难。本文结合工业视觉软件实际开发场景,系统介绍一套基于 C# 的 Recipe 配方管理设计思路,包括配方数据结构、运行时快照、参数切换、版本管理、导入导出及异常回滚等内容。


1. 为什么视觉软件一定要设计Recipe

很多视觉软件第一个版本通常只有一个产品。

程序里直接写:

复制代码
Exposure = 3500;
Threshold = 80;
Score = 0.75;

或者:

复制代码
if (productType == "A")
{
    score = 0.75;
}
else if (productType == "B")
{
    score = 0.82;
}

产品只有两个的时候似乎问题不大。

但实际产线很容易变成:

复制代码
A01
A02
A03
B01
B02
C01
C02
...

而且不同型号之间改变的并不只是一个阈值。

可能同时涉及:

复制代码
相机曝光
相机增益
触发模式
ROI
模板文件
测量参数
深度学习模型
PLC地址
结果映射
存图路径
检测项启用状态

如果这些参数全部散落在:

复制代码
Form控件
配置文件
代码常量
VisionPro ToolBlock
HALCON程序
PLC逻辑

项目后期基本一定会失控。

所以我现在更倾向于把:

一个产品型号运行所需要的全部参数

统一定义成一个:

复制代码
Recipe

2. Recipe到底应该保存什么

Recipe并不只是:

算法参数。

对于一套完整的工业视觉系统,我更建议把它理解成:

一个产品型号的完整运行环境。

例如:

复制代码
Recipe
│
├─ Product
│
├─ Camera
│
├─ Vision
│
├─ Communication
│
├─ Save
│
└─ Runtime

C#可以设计成:

复制代码
public class Recipe
{
    public string Name { get; set; }

    public string ProductCode { get; set; }

    public int Version { get; set; }

    public CameraRecipe Camera { get; set; }

    public VisionRecipe Vision { get; set; }

    public CommunicationRecipe Communication { get; set; }

    public SaveRecipe Save { get; set; }
}

不要把所有字段全部塞进一个类。

否则最后会出现:

复制代码
public class Recipe
{
    public double Camera1Exposure;
    public double Camera2Exposure;
    public double Camera3Exposure;

    public double Score1;
    public double Score2;

    public int PlcAddress1;

    public string SavePath;

    // 几百个参数......
}

这种结构后期非常难维护。


3. 相机参数应该独立管理

例如:

复制代码
public class CameraRecipe
{
    public List<CameraParameter> Cameras { get; set; }

    public CameraRecipe()
    {
        Cameras = new List<CameraParameter>();
    }
}

单个相机:

复制代码
public class CameraParameter
{
    public string CameraName { get; set; }

    public double ExposureTime { get; set; }

    public double Gain { get; set; }

    public bool TriggerMode { get; set; }

    public int TriggerDelay { get; set; }
}

这样以后不管项目是:

复制代码
1台相机
4台相机
9台相机

Recipe结构都不需要重新设计。


4. 视觉参数也不要直接绑定UI控件

这是一个比较常见的问题。

例如:

复制代码
double score =
    Convert.ToDouble(txtScore.Text);

然后算法直接读取:

复制代码
txtScore

这相当于:

视觉算法依赖UI。

后期很容易出现问题。

正确关系应该是:

复制代码
UI
 ↓
Recipe
 ↓
VisionEngine

而不是:

复制代码
VisionEngine
 ↓
UI Control

例如:

复制代码
public class VisionRecipe
{
    public double MatchScore { get; set; }

    public double Threshold { get; set; }

    public bool EnableOCR { get; set; }

    public bool EnableMeasurement { get; set; }

    public string ModelFile { get; set; }
}

算法只读取:

复制代码
recipe.Vision.MatchScore;

而不是:

复制代码
txtMatchScore.Text;

5. Recipe文件建议使用什么格式

对于中小型视觉项目,我一般更喜欢:

复制代码
JSON

原因很简单。

可读。

可复制。

可比较。

可版本管理。

也方便人工检查。

例如:

复制代码
{
  "Name": "Product_A",
  "ProductCode": "A001",
  "Version": 3,
  "Vision": {
    "MatchScore": 0.82,
    "Threshold": 75,
    "EnableOCR": true,
    "EnableMeasurement": true,
    "ModelFile": "Models/A001.vpp"
  }
}

如果配方数量特别多,或者需要:

复制代码
复杂查询
权限
历史版本
修改记录

也可以使用 SQLite。

但不要为了"高级"而强行上数据库。

如果项目只有:

复制代码
10~30个型号

JSON往往已经足够。


6. Recipe目录怎么设计

我比较推荐:

复制代码
Recipes
│
├─ Product_A
│   ├─ Recipe.json
│   ├─ Vision.vpp
│   ├─ Model.shm
│   └─ Template.bmp
│
├─ Product_B
│   ├─ Recipe.json
│   ├─ Vision.vpp
│   └─ Template.bmp
│
└─ Product_C

这样一个产品需要的文件全部放在一起。

而不是:

复制代码
Config
Models
Images
VPP
Halcon

然后不同型号的文件全部混在一起。

项目一多以后非常容易拿错文件。


7. 配方切换最危险的问题是什么

很多程序切换型号的时候直接:

复制代码
_currentRecipe =
    LoadRecipe(name);

然后立刻开始生产。

但一个完整配方切换可能包含:

复制代码
停止检测
停止触发
加载Recipe
加载模板
加载VPP
加载DL模型
更新相机曝光
更新PLC参数
初始化检测工具
检查结果
重新允许生产

这其实是一个:

事务过程。

只要中间任何一步失败,都不应该让设备继续生产。


8. 配方切换建议采用状态机

例如:

复制代码
Idle
 ↓
Loading
 ↓
ApplyingCamera
 ↓
LoadingVision
 ↓
Validating
 ↓
Ready

失败则:

复制代码
Error

伪代码:

复制代码
public bool SwitchRecipe(string recipeName)
{
    Recipe newRecipe = null;

    try
    {
        SetState("Loading");

        newRecipe =
            _recipeManager.Load(recipeName);

        SetState("ApplyingCamera");

        ApplyCameraParameters(newRecipe);

        SetState("LoadingVision");

        LoadVisionProgram(newRecipe);

        SetState("Validating");

        ValidateRecipe(newRecipe);

        _currentRecipe = newRecipe;

        SetState("Ready");

        return true;
    }
    catch (Exception ex)
    {
        WriteLog(ex.ToString());

        SetState("Error");

        return false;
    }
}

9. 不建议直接修改正在运行的Recipe

例如程序正在检测产品A。

操作员打开设置页面,把:

复制代码
MatchScore

从:

复制代码
0.75

改成:

复制代码
0.85

如果算法直接读取同一个对象,就可能出现:

同一批产品前一半使用0.75,后一半使用0.85。

这对于追溯来说非常麻烦。

所以我更推荐:

编辑Recipe和运行Recipe分开。


10. 使用Runtime Snapshot

运行时可以复制一份:

复制代码
public class RuntimeRecipe
{
    public string RecipeName { get; set; }

    public int Version { get; set; }

    public double MatchScore { get; set; }

    public double Threshold { get; set; }
}

当开始运行一个产品时:

复制代码
Recipe
 ↓
Create Snapshot
 ↓
Inspection

后面用户即使修改配置,也不会影响当前产品。

只有下一次重新应用Recipe后才生效。


11. Recipe一定要有版本号

例如:

复制代码
Product_A

Version 1
Version 2
Version 3

为什么?

因为客户经常会问:

上周这个产品为什么检测OK,今天为什么NG?

如果只保存:

复制代码
Product_A

你根本不知道当时使用的是哪套参数。

建议每一次保存Recipe都增加:

复制代码
Version

同时日志记录:

复制代码
Recipe = Product_A

Version = 12

这样以后追溯非常方便。


12. 推荐保存修改记录

可以定义:

复制代码
public class RecipeChangeRecord
{
    public DateTime Time { get; set; }

    public string User { get; set; }

    public string RecipeName { get; set; }

    public int OldVersion { get; set; }

    public int NewVersion { get; set; }

    public string Description { get; set; }
}

例如:

复制代码
2026-09-12 10:35

User: Engineer

Recipe: Product_A

V12 → V13

MatchScore:
0.78 → 0.82

真正到了量产阶段,这种功能的价值非常高。


13. Recipe参数最好分权限

不是所有人都应该能修改全部参数。

例如:

复制代码
Operator

只允许:

复制代码
切换型号
启动
停止

Engineer:

复制代码
修改阈值
修改ROI
重新训练模板

Administrator:

复制代码
修改通讯
删除Recipe
系统设置

这不是为了把软件做复杂。

而是因为工业现场最怕:

不知道谁改了一个参数。


14. Recipe保存前必须校验

不要用户点击:

复制代码
保存

就直接写文件。

应该先验证:

复制代码
private bool ValidateRecipe(
    Recipe recipe,
    out string error)
{
    if (recipe.Vision.MatchScore < 0 ||
        recipe.Vision.MatchScore > 1)
    {
        error = "MatchScore必须位于0~1。";

        return false;
    }

    if (recipe.Camera == null)
    {
        error = "Camera配置不存在。";

        return false;
    }

    error = null;

    return true;
}

还可以检查:

复制代码
模板文件是否存在
VPP是否存在
模型是否加载成功
相机参数是否合法
路径是否存在

防止保存一套根本不能运行的Recipe。


15. 保存时不要直接覆盖原文件

更可靠的流程:

复制代码
Recipe.json
 ↓
Recipe.tmp
 ↓
验证写入成功
 ↓
替换Recipe.json

甚至可以保留:

复制代码
Recipe.bak

这样软件突然断电时,不容易造成:

复制代码
JSON只写了一半

最终整个配方损坏。


16. Recipe导入导出非常值得做

客户经常会有:

复制代码
设备A

设备B

设备C

如果同一个型号需要复制过去,不应该让工程师重新调一遍参数。

可以支持:

复制代码
Export Recipe

导出:

复制代码
Product_A.recipe

内部其实可以是ZIP:

复制代码
Recipe.json
Vision.vpp
Template.bmp
Model文件

导入时:

复制代码
检查版本
检查文件完整性
解压
注册Recipe

这对于设备复制和售后维护非常方便。


17. Recipe和算法版本最好分开

例如:

复制代码
RecipeVersion = 15

并不代表:

复制代码
AlgorithmVersion = 15

因为可能只是:

复制代码
曝光修改

并没有改算法。

所以建议记录:

复制代码
RecipeVersion

VisionProgramVersion

SoftwareVersion

日志例如:

复制代码
Software = 2.3.1

Recipe = A001 V15

Vision = VPP 3.2

以后出了问题,可以准确还原当时的软件环境。


18. 一个比较完整的Recipe架构

最终可以设计成:

复制代码
RecipeManager
│
├─ Load
├─ Save
├─ Delete
├─ Copy
├─ Import
├─ Export
├─ Validate
└─ VersionControl
       │
       ↓
     Recipe
       │
       ├─ Camera
       ├─ Vision
       ├─ PLC
       ├─ Save
       └─ Product
       │
       ↓
RuntimeRecipe
       │
       ↓
Inspection

UI永远只操作:

复制代码
RecipeManager

而不要自己到处:

复制代码
File.ReadAllText()

19. 一个好的Recipe系统解决的不是"保存参数"

Recipe真正解决的是:

复制代码
产品管理

参数一致性

版本追溯

软件维护

设备复制

权限管理

异常恢复

所以我现在认为:

Recipe不是视觉软件的附加功能,而是工业视觉软件的核心模块之一。

很多项目刚开始为了赶进度觉得:

以后再做。

最后往往会发现,后面补Recipe比一开始设计好困难很多。


总结

对于只有一个产品的Demo来说:

复制代码
配置文件 + 几个TextBox

完全够用。

但只要项目进入:

复制代码
多型号

长期运行

多人维护

多设备复制

参数追溯

阶段,就非常有必要建立一个完整的Recipe系统。

真正成熟的Recipe系统应该具备:

复制代码
结构化参数

版本管理

切换状态机

运行快照

合法性检查

权限控制

修改记录

导入导出

异常回滚

这些功能看起来和视觉算法没有直接关系。

但它们恰恰决定了一套视觉软件:

是一个调试程序,还是一个真正可以长期维护的工业软件。

相关推荐
fujisheng6613 小时前
FUI 导航实践:拆开 Layer、History、Coverage 与 Cache 的组合语义
c#·unity3d
淡海水4 小时前
10-05-高级-模式匹配-CSharp如何改变与数据结构的交互方式
数据结构·算法·c#·模式匹配
XiaoZhenHua985 小时前
C# WinForms + 西门子S7 + SQLite + EPPlus生产数据采集系统架构设计
系统架构·sqlite·c#
Lost of 程序猿6 小时前
船岸数据同步链路实战:SendFlag、单调版本号与断网三天后的续传
后端·c#·asp.net
jonyleek16 小时前
离散制造插单重排实战:从4.2小时到90秒的技术实现路径
离散制造·aps·生产排程·工业软件·约束求解·jvs·jvs-aps
捷米特网关模块通讯17 小时前
开关量无线传输模块配对即使用,无需编程快速搭建控水系统
数据采集·工业自动化·工业智能网关·无线数传模块·工业无线网桥
cjp5601 天前
012.UG二次开发,UG管道服务器与WPF管道通讯,传输JSON数据
c#
jiushidt1 天前
ArcGIS Pro Add‑in 打包流程详解
arcgis·c#·.net·arcgispro
geovindu2 天前
CSharp: Observer Pattern
开发语言·后端·观察者模式·设计模式·c#·.netcore·行为模式