资讯详情

资讯详情

C#联合HALCON编程:HDevEngine与HDVP脚本集成开发指南

简介在工业机器视觉系统中图像算法库与上位机软件的集成是一项常见技术挑战。HALCON作为功能强大的图像处理算法库提供丰富的算子和脚本机制C#则以其高效的界面开发与业务逻辑整合能力成为上位机编程的主流选择。二者联合开发时通过HDevEngine在C#进程内动态加载HDVP脚本可实现视觉算法与业务代码的灵活解耦算法调整无需重新编译上位机程序显著提升现场调试效率。同时正确管理HImage图像对象、处理跨线程显示、配置DLL引用与许可环境是保证程序稳定运行的关键。这一技术路线广泛应用于视觉检测、定位测量、缺陷识别等工业自动化场景。本文围绕C#联合HALCON编程的完整链路从环境配置、核心对象数据流转到二次开发思路给出可落地的工程实践方案。1. 项目解析C#联合HALCON编程能解决什么痛点做过视觉项目的人应该都有体会算法在HALCON里跑得通一到C#上位机里就各种别扭。要么是把HDevelop里调好的算子导出成C#代码后发现窗体线程卡死要么是图像数据显示不出来要么是HDVP脚本加载后参数传不进去。这个示例项目的核心价值就是走通一条“HALCON引擎在C#进程内被调用”的完整链路——从相机取流、图像显示、HDVP脚本加载执行到结果回传你拿到手能直接跑起来再照着改自己的业务流程。适合正在做视觉检测上位机、工业视觉软件二次开发的人也适合刚接触C#和HALCON联合开发、想少走弯路的初学者。这份资源不是给你看算子的是给你看“桥接层”怎么设计的。2. 技术选型HALCON引擎集成方式与C#开发环境准备2.1 HALCON联合开发的三种集成方式与取舍C#项目里集成HALCON常见做法有三种这个示例工程采用的是第二种和第三种结合的方式我先说清楚三者的区别你再决定自己的项目用哪种。第一种是直接导出C#代码。在HDevelop里把写好的图像处理流程通过“文件→导出”生成C#文件然后在Visual Studio里调用。这种方式上手最快但问题也明显每改一次流程就要重新导出图像处理逻辑和业务代码混在一起而且导出的代码大量依赖HALCON的Window控件界面层被绑架得厉害。第二种是引用HALCON的.NET接口程序集也就是halcondotnet.dll在C#里直接用HImage、HObject这些托管类。这种方式的优点是类型完整HDevelop里能用的算子基本都有对应的方法而且能和C#的泛型、事件机制结合做封装比较顺手。本示例的核心调用方式就是这种。第三种是使用HDevEngine动态加载HDVP脚本。HDevelop写好的视觉流程保存为HDVP后缀的脚本C#通过HDevEngine类在运行时加载并执行参数通过SetInputCtrlParam和SetInputIconicParam传入。好处是视觉算法改动时不需要重新编译C#程序改脚本就行这种“算法与流程分离”的方式非常适合现场调试。这三种方式不是互斥的。这个示例项目里图像采集和显示走的是.NET接口方式二视觉流程执行走的是HDevEngine方式三两者并行不冲突。实际做项目时我建议你也这样组合底层图像采集和处理用接口方式保证类型可控业务层流程用脚本方式保证柔性。2.2 开发环境配置DLL引用、运行库与许可拿到这份资源先在Visual Studio里打开解决方案你会看到项目引用了几个关键程序集。最核心的是halcondotnet.dll它位于HALCON安装目录下的dotnet文件夹里面分x86和x64两个版本。注意这里有个血泪经验HALCON的.NET接口程序集版本必须要和你执行环境的目标位数一致。你现在项目里如果是x64平台就不要去引用x86目录下的那份否则运行时报BadImageFormatException概率极高。除了halcondotnet.dllHALCON运行时还需要依赖大量的底层原生DLL比如halcon.dll、hdevengine.dll这些。这些不需要你在C#项目里逐个引用它们都在安装目录的bin文件夹里。让程序能找到它们有两种常见做法一是把这些bin目录添加到系统PATH环境变量里二是把整个bin目录的内容复制到程序输出目录。第二种方式虽然笨但最省心我一般会写个后期生成事件脚本把DLL都拷贝过来。许可方面HALCON运行时会按照环境变量HALCONROOT或安装时的注册表信息去查找许可文件。如果程序部署到别的机器上记得要把许可证文件和runtime一起带上并且设置好HALCONROOT环境变量指向安装目录的根路径。很多C#程序在开发机器上跑得好好的拷到现场就报许可证错误多半是环境变量丢了。还有一个容易忽略的配置是.NET版本。这个示例工程目标是.NET Framework 4.6.1原因在于HALCON的托管接口对.NET Standard和.NET Core的支持一直没有做到完全成熟尤其是HDevEngine依赖的原生运行时在.NET Core下跨平台时会出现崩溃。如果不是有特殊需求建议沿用.NET Framework配合Visual Studio的“平台目标”设置为x64。2.3 示例工程文件清单与运行前置条件拿到资源后先看下工程里的文件结构我把核心文件列一下文件/目录类型作用说明FormMain.cs源码主窗体承载相机控制、图像显示与流程执行HalconEngineManager.cs源码HDevEngine封装类负责HDVP脚本加载与参数传递CameraService.cs源码相机采集服务统一了设备枚举、开启与帧回调HWindowDisplay.cs源码显示控件封装包装HWindowControl的画布操作Scripts/目录存放HDVP脚本文件本示例带了一个定位测量示例Resources/目录测试图像及相机配置文件bin/MVSDKDir.dll等原生DLL相机厂商SDK依赖库用于演示外部设备接入运行前要确认三件事HALCON已正确安装并激活许可Visual Studio引用路径里的halcondotnet.dll指向了正确的dotnet目录相机驱动和SDK库已装好。如果手头没有相机也没关系示例里做了模拟数据源可以读本地图片代替相机帧这在调试阶段非常实用。2.4 常见集成架构误区把HALCON对象直接塞进UI层有两点值得单独说一下。第一很多人写第一个C#调用HALCON的Demo时把HWindowControl当成普通PictureBox用在窗体里直接new一个出来然后把自己写的一堆HImage处理逻辑全堆在窗体事件里。这种写法跑通没问题但是一旦业务变复杂几十个算子挤在一个方法里后面基本改不动。这个示例里特意把显示控件封装成了HWindowDisplay把相机采集封装成了CameraService就是为了让你看到分层的价值。第二HALCON的托管对象虽然继承了IDisposable但不等于你可以忽略手动释放。HImage、HRegion这些对象内部都持有原生内存交给GC自动回收会有明显延迟在连续采集图像时内存会暴涨。正确姿势是用using语句或者及时调用Dispose()本示例在相机帧回调里对每帧图像处理完就释放我在后面对应小节会给出代码。这条如果不注意程序跑半小时后就会因为内存回收不及时出现明显的掉帧和卡顿。3. 项目结构拆解示例工程的目录设计、文件职责与核心对象3.1 三层结构界面、服务、算法如何分家先看这个示例工程的目录和命名空间划分它对标的是一种通用的工业视觉软件分层思路。结构上分成三层界面层、服务层、算法层。界面层只放窗体负责把数据显示出来并接收用户的交互操作服务层封装相机、显示这些和具体硬件打交道的部分提供事件和接口给界面调用算法层则是一个个独立的处理类接收图像对象输出结果。具体到这个工程里FormMain.cs就是界面层的入口。它不直接调用HDevelop导出代码也不直接操作HALCON的HImage而是通过CameraService拿到图像帧再交给HalconEngineManager去执行脚本。这样分的一个直接好处是如果项目要换相机品牌你只需要改CameraService内部实现FormMain和HalconEngineManager一概不动。HalconEngineManager是算法层的代表。它封装了对HDevEngine的完整调用暴露出来的方法只有LoadScript、SetInputImage、SetInputParam、Execute和GetResult几个。外部调用方不关心脚本内部是阈值分割还是模板匹配也不关心有几个中间变量这种设计让上位机开发人员能把精力集中在业务逻辑上而不是去读视觉工程师的HDVP脚本细节。显示层这里要专门说说HWindowDisplay这个封装类。HALCON自带的HWindowControl控件可以直接拖到窗体上但它有几个天生问题不能在后台线程里刷新画面、不能跨窗体共享、释放时容易导致GDI句柄泄漏。示例里把它重新包装了一层对外只暴露DisplayImage(HImage image)和SetPart(double row1, double col1, double row2, double col2)这两个方法内部自行处理线程切换和控件刷新这样UI层永远不需要关心Display调用是在哪个线程发起的。3.2 核心对象HImage、HDevEngine、HWindowControl之间的数据流转很多第一次接触C#联合HALCON编程的人会被对象关系绕晕我直接按数据流来梳理一遍。第一个对象是HImage这是图像数据的载体。它有两种来源一种是从相机SDK里拿到的Byte数组通过GenImage1算子构造另一种是从文件直接读入。在上位机程序里HImage是各种算子的输入比如阈值分割要基于它模板匹配也要基于它。注意HImage本身不持有显示能力它只是数据。第二个对象是HDevEngine这是引擎执行体。它在C#里对应一个脚本解释器实例你可以给它指定脚本路径调用Execute方法把整个HDVP跑一遍。它的输入输出很灵活图像通过SetInputIconicParam传进去数值型参数通过SetInputCtrlParam传进去。执行完之后通过GetOutputIconicParam和GetOutputCtrlParam把结果取回来。第三个对象是HWindowControl这是专门用来显示的控件。HALCON官网提供的显示方式有两种一种是在窗体上直接拖控件再调HImage.Display(WindowHandle)另一种是通过OpenWindow创建一个独立窗口句柄。示例里用的是前者因为它能自然嵌入WinForms界面。这个控件内部维护的HDrawingObject归属原始显示上下文的拿到句柄后可以反复使用不需要每次显示都重新创建。这三个对象流转的完整链路是相机SDK采集Byte数组→HImage载入→可选预处理算子处理→SetInputIconicParam交给HDevEngine→引擎执行HDVP脚本→GetOutputIconicParam拿回结果HImage和结果Region→HWindowDisplay在窗体上把结果图像显示出来。整个链路你对照着示例工程的FormMain里的事件处理流程去看会比较直观。3.3 HDevEngine封装类的设计要点与易错点这里给出HalconEngineManager的骨架代码重点看它是怎样设计出稳接口的。public class HalconEngineManager : IDisposable { private HDevEngine _engine; private HDevProcedure _procedure; private string _scriptPath; public HalconEngineManager() { // 创建引擎实例并关闭外部调试窗口避免脚本执行中弹出HDevelop调试框 _engine new HDevEngine(); _engine.SetEngineAttr(do_low_error_handling, false); _engine.StopOnError true; } public void LoadScript(string procedureName) { _procedure new HDevProcedure(procedureName); _scriptPath procedureName; // 从外部文件加载HDVP的方式见下面的示例 } public void LoadProcedureFromFile(string hdvpPath) { _engine.SetProcedurePath(Path.GetDirectoryName(hdvpPath)); _procedure new HDevProcedure(Path.GetFileNameWithoutExtension(hdvpPath)); } public bool Execute(HImage inputImage, out HRegion resultRegion, out double score) { resultRegion null; score 0; try { HDevProcedureCall call _procedure.CreateCall(); call.SetInputIconicParam(Image, inputImage); call.SetInputCtrlParam(MinScore, 0.6); call.Execute(); resultRegion call.GetOutputIconicParam(ResultRegion) as HRegion; score call.GetOutputCtrlParam(Score); return true; } catch (HDevEngineException ex) { // 记录日志并返回false不让异常直接抛出到UI层 return false; } } public void Dispose() { _procedure?.Dispose(); _engine?.Dispose(); } }逻辑说明这个类把HDevEngine的底层调用全部挡在了外部。LoadProcedureFromFile先设置脚本查找路径再创建具体流程对象Execute通过HDevProcedureCall实例传参并执行最后从输出参数中取回Region和Score。这样做之后主程序里调用方只需要关心三件事脚本路径、图像输入、期望的返回类型。参数说明SetInputIconicParam的Image要和HDVP脚本里声明的输入参数名严格一致否则引擎会在运行时抛出找不到参数的异常SetInputCtrlParam的MinScore同理。GetOutputIconicParam返回的是HObject类型需要强转成HRegion才能使用。这里有个常见坑如果你在脚本里没有声明某个输出参数但运行时强行Get引擎会抛HDevEngineException所以调用方要做好异常捕获。4. 核心代码逐步讲解图像采集、显示、HDVP调用的完整链路4.1 相机初始化与采集线程从设备枚举到帧回调先看CameraService里相机初始化的部分这段代码是走通整条链路的前置条件。它抽象出一个统一的接口任何相机只要实现了AcquireFrame和Start/Stop就能被上位机调度这个思路对于不同品牌相机的替换非常关键。public class CameraService : IDisposable { private CameraDevice _device; private Thread _captureThread; private volatile bool _isRunning; public event ActionHImage FrameArrived; public bool Start() { _device new CameraDevice(); bool ok _device.OpenDevice(0, 640, 480); if (!ok) { // 打开失败时回退到模拟模式用定时器读本地图片当帧 _device new SimulatedCameraDevice(Resources/test.png); _isRunning false; return false; } _isRunning true; _captureThread new Thread(CaptureLoop); _captureThread.IsBackground true; _captureThread.Start(); return true; } private void CaptureLoop() { while (_isRunning) { byte[] raw _device.ReadFrame(); if (raw null) continue; HImage img new HImage(byte, 640, 480, raw); FrameArrived?.Invoke(img); // 注意这里不释放img由订阅方FormMain在显示完成后释放 } } public void Stop() { _isRunning false; _captureThread?.Join(200); _device?.CloseDevice(); } }逻辑说明CaptureLoop是一个后台线程它循环从设备读取原始数据把Byte数组构造成HImage后通过FrameArrived事件抛给订阅者。事件参数类型用的是HImage而不是Bitmap就是为了让后续的处理流程能够直接对接HALCON算子省去转换的损耗。FrameArrived的订阅者是FormMain它拿到HImage后执行显示和流程调用。参数说明构造函数里byte表示图像类型为灰度图640和480是宽高。如果相机的像素格式是RGB要改成rgb并把宽高乘以3来计算Byte数组长度。SetPart这个方法要重点说它设置了显示控件的感兴趣区域只在窗口初始化和分辨率改变时需要调用每次刷新时不要重复设置否则会有额外开销。4.2 图像显示HWindowControl控件的线程安全刷新图像显示是新手翻车最多的环节核心难点在于Windows窗体控件禁止在非UI线程直接访问。HALCON的HWindowControl继承自NativeWindow机制它比普通控件稍微宽容一点但仍然强烈建议在UI线程里执行绘制操作。看一下HWindowDisplay的封装实现。public class HWindowDisplay { private HWindowControl _ctrl; public HWindowDisplay(HWindowControl control) { _ctrl control; } public void DisplayImage(HImage image) { if (_ctrl.InvokeRequired) { _ctrl.Invoke(new ActionHImage(DisplayImage), image); return; } // 将HALCON窗口的活动区域铺满图像尺寸避免显示被拉伸 HOperatorSet.SetPart(_ctrl.HalconWindow, 0, 0, image.Height - 1, image.Width - 1); image.Display(_ctrl.HalconWindow); } public void DisplayRegion(HRegion region, HImage background) { if (_ctrl.InvokeRequired) { _ctrl.Invoke(new ActionHRegion, HImage(DisplayRegion), region, background); return; } background.Display(_ctrl.HalconWindow); region.Display(_ctrl.HalconWindow); } }逻辑说明DisplayImage先检查当前线程是否为UI线程如果不是就通过Invoke转回去。SetPart把显示窗口的坐标系统与图像的宽高对齐这样显示出来是1:1比例的不会被拉伸变形。DisplayRegion用于把检测结果Region叠加在背景图像上这是做视觉检测结果可视化最常用的方式。参数说明HWindowControl.HalconWindow是一个HTuple类型它代表原生窗口句柄的封装。这个句柄一旦由窗体创建在整个生命周期内通常保持不变所以你可以安全地把_ctrl.HalconWindow传给HALCON算子使用。需要留意的是如果在窗体设计器里修改了控件大小句柄不会变但需要重新设置SetPart来匹配新尺寸。4.3 HDevEngine加载HDVP并执行视觉流程这是整个示例最核心的部分。在HDevelop里写的脚本保存为HDVP文件后C#这边通过HDevEngine在运行时加载它并执行。执行时先把当前帧图像作为输入参数传进去等脚本跑完再取回处理结果。这个过程的优势在于算法改动不用重编C#程序。public bool ExecuteVision(HImage frame, out string resultText, out HRegion defectRegion) { string hdvpPath Path.Combine(Application.StartupPath, Scripts, defect_detect.hdvp); bool scriptLoaded _engineManager.LoadProcedureFromFile(hdvpPath); if (!scriptLoaded) { resultText 脚本加载失败; defectRegion null; return false; } HTuple score, classId; HObject regionObj; bool ok _engineManager.ExecuteFishEyeProcedure(frame, out regionObj, out score, out classId); defectRegion regionObj as HRegion; resultText ok ? $缺陷评分:{score.D}}类别:{classId} : 执行异常; return ok; }逻辑说明ExecuteVision是对外暴露的界面层方法它先指定HDVP脚本路径再调用引擎管理类执行。脚本内部做的事情可以是任何视觉流程这里给出的是缺陷检测读出图像中每个缺陷区域的灰度均值和面积然后用一个简单的规则来判定类别。执行过程中C#这边完全不需要管算子的细节只要接收输出。参数说明score和classId都是HTuple类型HDevEngine执行后返回的值可能存在多个通道里。注意取HTuple的数值时要调用score[0].D或者强化转换为(double)score[0]直接对HTuple做算术运算在某些重载情况下会出现隐式转换问题。GetOutputIconicParam返回的HObject基类实例需要按照脚本里定义的类型转型成HRegion或HImage类型不一致时引擎会抛出预期的转换异常。4.4 帧处理后的内存释放策略与用后释放习惯这条经验是我排查现场内存泄漏问题时总结出来的。在上位机视觉软件里图像处理产生的临时对象数量极大如果不显式释放垃圾回收机制根本跟不上采集速度。HALCON的托管对象虽然实现了IDisposable但很多人不知道Dispose的具体时机该怎么定。private void OnFrameArrived(HImage frame) { // 注意frame在这里是由CameraService创建的使用完毕由本方法释放 try { _display.DisplayImage(frame); // 示例场景对当前帧做一次单通道提取再做阈值这里演示临时对象释放 HImage single frame.Rgb1ToGray(); HRegion threshold single.Threshold(0, 128); _display.DisplayRegion(threshold, single); threshold.Dispose(); single.Dispose(); } finally { frame.Dispose(); // 这一行很关键一定要释放相机传来的帧 } }逻辑说明这是一个典型的帧处理回调关键点在finally块里的frame.Dispose()。因为frame的所有权已经由CameraService转移到了这里所以当前帧处理完毕必须主动释放否则帧数据在后台线程里越积越多内存占用会线性增长。中间的single和threshold作为中间产物用完也立即释放。参数说明Rgb1ToGray()会返回一个新的HImage实例不会改动原图所以原图仍然可以继续显示或另作他用。Threshold(0, 128)是对灰度图做阈值分割返回HRegion在调用DisplayRegion之后这个Region就不再需要了。还有一个细节释放顺序也有讲究先释放依赖其他对象的Region再释放图像本身避免释放过程中HALCON内部句柄引用失效导致崩溃。5. 常见问题与避坑记录DLL冲突、HDevEngine执行异常排查5.1 BadImageFormatExceptionx86与x64引用的经典翻车现象程序编译通过运行时双击主窗体就抛出BadImageFormatException提示“未能加载文件或程序集halcondotnet.dll或其某一依赖项”有的机器上还会伴随“试图加载格式不正确的程序”的提示。原因这个异常几乎都是因为项目平台的位数与HALCON安装版本不匹配。比如你的程序集目标是x86但引用的是dotnet/x64下的halcondotnet.dll或者反过来那么运行时无法加载原生DLL。另一种情况是Visual Studio中“平台目标”设置为x86但HALCON只有x64的许可程序在64位系统上运行时会先被ALM拉去以32位进程加载进而触发这个错误。解决把项目的“生成→平台目标”改为x64并确保引用的halcondotnet.dll来源于HALCON安装目录的dotnet/x64下。如果你不得不以x86模式运行比如需要和32位的旧相机SDK交互那就要重新安装对应的x86运行时而不是简单切换平台。5.2 跨线程访问HWindowControl导致窗口卡死或闪退现象程序启动后相机画面非常卡点击按钮后界面直接无响应或者操作几次后窗体突然闪退系统日志里能看到和GDI相关的错误记录。原因CameraService的采集线程在后台触发FrameArrived事件处理器里直接调用了HWindowControl.HalconWindow进行绘制。Windows窗体的控件绘制必须发生在创建它的线程上一旦跨线程访问轻则错乱重则直接崩溃。这个问题的隐蔽性在于它不一定会稳定复现取决于线程调度时机。解决我是在HWindowDisplay封装里统一处理的检查InvokeRequired并转回UI线程再执行显示。需要注意用Invoke同步等待UI线程响应比BeginInvoke更适合图像显示场景因为异步调用可能会让多帧绘制请求在UI线程上排队看起来就像相机画面有晚到帧。5.3 HDevEngine执行报“未找到流程XXX”现象HDevEngine加载HDVP脚本时抛出HDevEngineException提示“Procedure xxx not found”但脚本文件明明就在指定的目录里。原因这个异常几乎都是因为脚本查找路径配置不对。HDevEngine在加载脚本前需要先用SetProcedurePath设置脚本所在目录或者把脚本放到引擎的默认查找路径下。如果你把HDVP文件放在工程的Scripts目录但程序当前工作目录变了查找就自然失败。解决启动程序时先通过Path.Combine(Application.StartupPath, Scripts)拼出绝对路径再调用SetProcedurePath。如果脚本里还调用了其他HDVP或依赖外部资源这个设置必须覆盖到所有依赖文件的根目录我的做法是在启动时统一把项目根目录下的所有子目录都追加到SetProcedurePath里。5.4 脚本执行时弹出HDevelop调试窗口现象运行程序时每执行一次视觉流程就弹出一个HDevelop的调试窗口界面还会暂停点继续才往下跑。原因HDevEngine默认允许打开调试器。在开发阶段弹窗能帮助查看中间变量但部署后绝对不能让这种情况发生。遇到弹窗多半是引擎创建时没有把engine attribute的调试开关关掉。解决在构建HDevEngine实例后立即调用SetEngineAttr(debug_enable, false)。同时把StopOnError属性设为true这样脚本抛出算子异常时引擎会直接向上抛错而不是在一个不显眼的地方悄悄截断方便你从C#端拿到错误信息定位问题。示例工程里已经把这两行放进了构造函数你只要照抄这个配置就行。5.5 程序退出时进程残留现象点击关闭按钮后程序窗体消失了但在任务管理器里进程不退出或者再次启动时提示“相机被占用”。这种现象在多路相机场景里最明显。原因相机采集线程没有在窗体关闭事件里正确停掉或者HDevEngine持有的原生资源没有被释放。界面线程已经退出了后台线程被设置为IsBackground后虽然会被系统强制终止但当相机SDK持有硬件句柄时强制终止会导致句柄释放不完整下次启动时设备还处于占用状态。解决在窗体的FormClosing事件里调用CameraService.Stop并在Stop里先置位退出标志再Join等待线程退出然后调用设备的CloseDevice。同时HalconEngineManager实现IDisposable在窗体Dispose时一并释放。示例工程的FormMain里已经有三行代码实现了这个过程注意不要把顺序弄反先停相机线程再释放引擎资源。6. 二次开发思路从固定流程到流程配置化的演进把示例跑通之后如果要做真正的项目我建议你先做一件事把相机类型抽成接口。示例里只是写了一个CameraService但如果你的现场要用不同品牌的相机每个相机的SDK差异非常大直接在CameraService里写死设备启动逻辑会导致后续维护成本很高。正确做法是把OpenDevice、ReadFrame、CloseDevice定义成接口各自品牌写成独立实现类然后用一个工厂类根据配置文件的CameraType字段动态创建实例。这样换相机时只加一个实现类主流程完全不用动。第二个值得投入的改造点是HDevEngine的参数配置化。目前示例里的MinScore、RegionWidth这些参数是硬编码在C#代码里的这违背了用HDVP脚本做视觉流程的初衷。我建议你把视觉参数全部抽出来写到一个Config配置文件里每次执行前从配置中心读取并设置进去。如果做的是多工位项目配一个工位对应一组参数的表格会更好。这样一来视觉工程师调HDevelop脚本时只需要改参数文件完全不用碰编译后的程序现场调整响应的效率会大幅提升。第三个改造方向是把显示层的功能扩出来。示例里的HWindowDisplay只能显示图像和单个Region实际项目里往往需要多ROI叠加、灰度直方图曲线、测量点到点距离的标尺绘制。可以在这个封装类里增加一个DrawOverlay方法合理利用HALCON的SetColor、SetLineWidth、DispArrow这类显示算子把测量结果可视化出来。这些事情放在显示封装类里比散落在各业务窗体里要清晰得多。最后说我自己的教训。以前我给一个项目做模板匹配定位现场反馈定位不准我先把特征分数阈值从0.6改到0.85结果过检率直线上升客户投诉误检变高。后来养成了习惯每次改完HDVP里的参数都在C#侧把对应配置项、修改时间、当帧图像一起存到日志目录。从那以后我每次做视觉上位机联调都强制走一遍这个流程脚本参数不直接改代码统一走配置文件并且把每一次执行的关键输入输出都落盘这样任何问题都能回溯。希望这个习惯和这份资源能帮到正在做C#联合HALCON集成开发的你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →