尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于Halcon与C#的视觉检测平台源码解析:从模板匹配到运动控制

基于Halcon与C#的视觉检测平台源码解析:从模板匹配到运动控制 简介这份基于Halcon与C#开发的视觉检测平台源码面向工业视觉检测开发者封装了图像获取、处理、识别、结果可视化等基础流程适合想要借助C# WinForm落地视觉项目的初中级工程师。压缩包共两千个文件约200.64MB其中534个cs主程序文件构成核心逻辑441个txt可用于说明与数据参考284个png、68个bmp集中存放界面图样与视觉模板146个resx承载多语言/控件资源dll、pdb、config等则清楚标出依赖、调试与运行配置整体目录结构贴近Visual Studio标准组织。目前已有三千九百四十二人学习下载。平台v0.0.1.2虽属初期版本但工程可编译通过内部已包含主程序、图像显示窗口、数据图表、代码编辑控件、自定义按钮及停靠窗体等模块结合源码可重点理解Halcon形状匹配、OCR识别与C#界面联动的方法也能在此基础上扩展定位测量、读码等具体检测功能是一份合适的工程化入门与二次开发参考。1. 从一次产线停线说起VisionAndMotionPro 在解决什么问题年初帮一家汽配件厂调试视觉定位工位现场反馈“扫码枪偶尔读不到批次号一停就是半小时”。排查到最后问题不在扫码枪而是前面工位的OCR识别结果没有和运动控制联动——图像拿来看了但判断结果没有真正决定“放行还是拦截”。这不是设备不行是视觉检测平台没有把图像处理和业务逻辑串成闭环。VisionAndMotionPro 这套基于 Halcon 与 C# 的视觉检测平台源码版本 v0.0.1.2正是解决这类问题的典型骨架Halcon 负责图像算法C# 负责界面、流程和运动控制接口源码可编译模块划分清楚适合二开也适合当教学案例。它适合三类人刚接触机器视觉、想在 Windows 上快速搭一套检测界面的上位机工程师需要把 Halcon 算法集成进 .NET 项目的工控开发者以及想拆解“图像采集—模板匹配—结果输出”全链路的学生。本文会按工程结构、显示封装、模板匹配、部署排错这条线把这套源码能复用的部分逐层拆开。2. 工程骨架与 Halcon/C# 的互操作方式2.1 源码包的目录结构说明了什么拿到压缩包先别急着双击 .sln。这套 v0.0.1.2 的源码第一眼看上去文件很多但分类有规律。主工程VisionAndMotion是核心里面按功能拆成了多个 Form 或 UserControl 文件ImageWindow管图像显示MyChart管结果曲线CodeEdit是内嵌代码编辑器NewButton、OPTLight、DockForm则是控件和布局辅助。.vs文件夹是 Visual Studio 的本地配置缓存里面藏着.suo文件记录了最近打开的窗口位置和断点设置如果开发机和打包机 Visual Studio 版本一致保留它省不少事但如果你用的是 VS2022打开这个 2015/2017 年代建的工程建议直接删掉.vs和*.suo让它重新生成否则容易出现加载缓存冲突。下表是这套源码里你后续二开最常改的几个文件建议先在心里建一张地图。文件/模块名职责二开时改哪里VisionAndMotion主工程主流程、界面容器、线程调度流程状态机的切换条件ImageWindowHalcon 图像显示与 ROI 交互显示倍率、ROI 回调MyChart检测结果趋势曲线/数据可视化数据源类型、刷新频率CodeEdit自定义代码编辑/脚本执行语法高亮、脚本接口OPTLight光源控制模拟组件串口或 TCP 指令映射2.2 Halcon 集成进 C# 的两种路线v0.0.1.2 用的是哪种Halcon 和 C# 集成有两条主流路线。第一种是 HDevelop 里把算子导出成 C# 代码然后复制进你的 WinForm 或 WPF 工程调HOperatorSet的静态方法。这种方式最直观新手也最容易上手但每次改算法都要回到 HDevelop 改、再导出、再编译响应慢而且算子导出的代码大量使用HTuple可读性一般。第二种是运行时通过HDevEngine加载.hdev脚本文件C# 工程只负责传参和取结果算法修改不用重新编译 C# 工程。VisionAndMotionPro 这版源码的主流写法是第一种也就是把 Halcon 算子用 C# 直接调用的方式固化在工程里好处是单 exe 部署相对容易坏处是算法和界面耦合度偏高这也是二开时最值得重构的点。不管选哪条路第一步都是正确引用 Halcon 的 .NET 接口。安装 Halcon 后在 Visual Studio 里添加引用路径一般在C:\Program Files\MVTec\HALCON-xx.x\bin\dotnet35或dotnet4下。以 .NET Framework 4.x 为例需要添加的依赖核心是halcondotnet.dll。如果你用第二种方式跑脚本代码骨架长这样using HalconDotNet; // 初始化 HDevEngine加载 hdev 脚本 HDevEngine engine new HDevEngine(); engine.SetProcedurePath(D:\VisionAndMotion\halcon\procedures); // 加载并执行主脚本 HDevProcedure procedure new HDevProcedure(find_screw_hole); HDevProcedureCall call procedure.CreateCall(); // 传输入参数图像路径与匹配分数阈值 call.SetInputCTuple(ImagePath, D:\images\5.bmp); call.SetInputCTuple(MinScore, 0.6); // 执行算法 call.Execute(); // 取出结果抓到的行、列、角度 HTuple row, col, angle; call.GetOutputCTuple(Row, out row); call.GetOutputCTuple(Col, out col); call.GetOutputCTuple(Angle, out angle); Console.WriteLine($找到 {row.Length} 个目标首个位置:({row[0].D}, {col[0].D}));这段代码里HDevEngine的好处是让算法文件独立于主程序生产现场调参时你只需要替换脚本目录里的.hdev文件不用重新编译整个上位机。注意SetInputCTuple里传字符串路径时Halcon 内部会把字符串当成图像文件名去读取所以路径里不要带中文否则在部分语言环境下会吃到编码坑。GetOutputCTuple取出的HTuple如果拿不到值程序不会立即崩但后续.D转 double 时会抛HALCONEXCEPTION调试时看到这个异常先回去检查脚本里的set_origin或测量结果是否真的写进了输出变量。2.3 为什么源码里大量使用 HTuple 而不是原生类型C# 里你习惯用double、int、string但在 Halcon 的 .NET 接口里几乎每个算子都绕不开HTuple。它的本质是一个动态数组可以同时容纳不同类型的数据。在 VisionAndMotionPro 的检测流程里你经常能看到一个HTuple同时装了十几个点的 XY 坐标这在 Halcon 里叫“元组”。实操里我用HTuple的习惯是所有从 Halcon 算子返回的值一律先用HTuple接住等确定要落库或显示时再通过.D、.I、.S转成 C# 原生类型。这样虽然多一步转换但能极大减少类型不匹配导致的偶发异常尤其是当 Halcon 算子返回空元组时直接转 double 会崩而拿到HTuple后可以先判Length 0。3. 图像采集线程与 HWindowControl 显示把检测结果画到界面上3.1 从文件名解析到图像输入的完整路径源码包根目录里躺着一批 BMP 测试图StandardImage1.bmp、StandardImage2.bmp、Pic_2018_07_20_145230_blockId#11411.bmp、Pic_2018_07_20_145658_blockId#15805.bmp。从这些命名能看出这是一套带批次追溯的视觉工位测试数据blockId后面的数字是批次号Pic_2018_07_20_145230是日期加毫秒级时间戳。实际调试视觉项目时这种图片命名习惯非常值得保留因为现场出了问题你能反查是哪一秒拍的哪一批料。在这些测试图上你能验证模板匹配在不同光照、不同旋转角度下的响应。图像输入的常见做法是由采集线程监听相机回调拿到HObject后交给检测线程同时用Control.BeginInvoke更新界面显示。VisionAndMotionPro 里显示控件用的是HWindowControl这是 Halcon 官方提供的 Windows 控件内部封装了窗口句柄。显示的代码一般长这样// 从文件或相机读取图像到 HObject HOperatorSet.ReadImage(out HObject image, D:\images\5.bmp); // 在 HWindowControl 上显示 hWindowControl.HalconWindow.DispObj(image); // 在图像上画十字线标记中心点 HTuple centerRow 256, centerCol 256; HOperatorSet.DispCross(hWindowControl.HalconWindow, centerRow, centerCol, 60, 0);DispObj是直接把 HObject 刷到窗口DispCross是在已有显示层上叠加一个 60 像素长的十字线0代表十字线的角度方向。这里有个容易踩的坑HWindowControl.HalconWindow在窗体还没完全显示出来时调用DispObj会抛异常所以要在Form_Shown事件里做首次显示而不是Form_Load。3.2 UI 刷新卡顿的解法别在采集循环里直接 Invoke很多 C# 上位机工程师都遇到过这个经典问题“循环数据采集时 UI 刷新卡顿”。在 VisionAndMotionPro 这类视觉平台里采集帧率假设是 30fps如果你每一帧都直接BeginInvoke刷图像UI 线程会被塞爆界面拖拽都会掉帧。我的处理方式是加“显示节流器”只在上一帧显示完成后才请求下一帧或者干脆定频刷新比如每秒只刷新 15 帧图像但检测结果全量记录。// 节流式刷新避免 UI 线程堆积 private bool _isDisplayBusy false; private void OnFrameCaptured(HObject frame) { if (_isDisplayBusy) return; // 上一帧还没显示完丢弃当前帧 _isDisplayBusy true; hWindowControl.BeginInvoke(new Action(() { try { hWindowControl.HalconWindow.DispObj(frame); } finally { _isDisplayBusy false; } })); }这段代码的关键逻辑是采集回调进来时先判断_isDisplayBusy如果 UI 线程还没来得及处理上一帧这一帧的直接丢弃。视觉检测场景下算法处理速度决定了节拍显示慢一拍无伤大雅但 UI 线程卡死会导致整个上位机假死。BeginInvoke前记得检查hWindowControl.IsHandleCreated否则窗口销毁后回调线程再往里丢委托会抛ObjectDisposedException。3.3 Halcon 图像类型与 C# 内存释放Halcon 的 .NET 接口里图像对象有几种常见类型新手容易混淆列一张对照表类型含义典型使用HObject图像数据实体通道区域传给算子做处理HImage带语义的图像对象保存、显示HTuple通用数据元组传参、取结果HRegion区域/ROI模板匹配的搜索区内存释放这块C# 里有 GC但HObject底层封装的是 Halcon 的 C 指针。GC 只回收托管对象不保证立即释放非托管内存在长运行的视觉程序里每帧都 new 一个HObject再丢给 GC内存占用会一路爬升。推荐用using块或手动Dispose()尤其在高帧率采集场景不要等到 GC 来救你。源码里如果看到HOperatorSet.ReadImage之后没有Dispose二开时建议补上这属于我拿到源码第一轮就会做的优化。4. 模板匹配与检测核心从形状匹配到参数调优4.1 0.0.1.2 版本能做什么模板匹配主流程拆解VisionAndMotionPro 这版虽然版本号是早期阶段但主流程已经跑通了“加载模板—搜索目标—输出坐标角度”的闭环。核心算法用的是 Halcon 的形状匹配对应算子主要是create_shape_model和find_shape_model在 C# 里就是HOperatorSet.CreateShapeModel和HOperatorSet.FindShapeModel。形状匹配比灰度匹配鲁棒得多它对光照变化、遮挡、噪点都不敏感适合金属件定位这也是工业视觉里的标配算法。下面这段代码是从 VisionAndMotionPro 的定位逻辑里抽象出来的核心调用方式// 创建形状模板 HOperatorSet.ReadImage(out HObject modelImage, D:\images\StandardImage1.bmp); HOperatorSet.ReduceDomain(modelImage, out HObject modelRegion, out HObject reducedImage); HTuple modelID; HOperatorSet.CreateShapeModel(reducedImage, auto, -0.39, 0.79, auto, auto, ignore_local_polarity, auto, out modelID); // 在测试图中查找模板 HOperatorSet.ReadImage(out HObject testImage, D:\images\5.bmp); HTuple row, col, angle, score; HOperatorSet.FindShapeModel(testImage, modelID, -0.39, 0.79, 0.5, 1, 0.5, least_squares, 0, 0, out row, out col, out angle, out score);CreateShapeModel的第二个参数auto让 Halcon 自动决定金字塔层数-0.39和0.79是起始角和角度范围单位是弧度约等于正负 22.5 度。第六个参数ignore_local_polarity表示忽略局部极性适用于目标在暗背景和亮背景里都可能出现的情况。FindShapeModel里的0.5是 MinScore低于这个分数的匹配结果会被丢弃0.5是 Greediness值越大搜索越快但越容易漏检。4.2 这几个参数决定成败MinScore 与 Greediness很多新手拿到find_shape_model的十几个参数直接懵其实日常调参真正动的就四个MinScore、Greediness、NumLevels、角度范围。MinScore 默认 0.5实际项目里我一般从 0.6 起步。如果现场误检多就往 0.7、0.8 抬如果漏检多就降到 0.4但降太狠模板只要有一半被遮挡就能匹配上误检率直线上升。Greediness是贪婪度0 表示最严格、最慢但最稳1 表示最快但容易跳过真目标。打磨产线上我常用 0.7 作为起点调试时除非帧率不够否则不建议低于 0.5。角度范围直接决定搜索耗时。如果工件只在 0 度和 180 度两个装夹方向就设成大概率的三角展开别给足 360 度范围搜索时间会成倍增加。那什么时候用find_scaled_shape_model当来料尺寸有波动、镜头高度有小幅变化时用缩放匹配多出四个缩放范围参数C# 侧调用它的逻辑和上面这段几乎一样只是把FindScaledShapeModel替换进去再额外传入ScaleMin0.8, ScaleMax1.2。4.3 检测结果的数据可视化与导出源码里的 MyChart 模块就是把每次检测的分数、位置偏差画成折线或柱状图。实际项目中我建议同时记录三样东西时间戳、检测分数、XY 偏移量。MyChart 的刷新不需要每次检测都重绘而是每 100ms 或每 10 次检测批量添加一次数据点这样既能看到趋势又不拖累主线程。常见做法是把检测结果写进一个ConcurrentQueueDetectionResult再由定时器批量取出喂给图表组件这一层的解耦会让整台上位机的稳定性上一个台阶。除了图表结果也要落盘可追溯典型代码是把它导出成 CSV。// 检测结果落盘CSV 追加写入 using (StreamWriter sw new StreamWriter(D:\VisionAndMotion\results.csv, true)) { sw.WriteLine(${DateTime.Now:yyyy-MM-dd HH:mm:ss.fff},{row.D:F3},{col.D:F3},{angle.D:F2},{score.D:F3}); }这套写法的好处是现场不用装数据库Excel 或 WPS 就能直接打开分析班次结束拿 CSV 拉个透视表哪台设备良率波动一眼就能看出来。row.D和col.D是把 HTuple 转成 double 的写法注意这里的下标索引问题FindShapeModel返回的 row、col 是元组如果你的模板可能找到多个目标必须用row[0].D拿第一个直接row.D在元组长度大于 1 时会抛异常。4.4 定位精度不够时优先排查的不是算法而是标定这是我在视觉项目里踩过最大的坑。模板匹配返回的行列坐标是像素坐标要让机械手去抓必须转换成机器人坐标系。VisionAndMotionPro 里如果要接运动控制你需要一个九点标定流程让机械手走九个已知位置每个位置拍一张图记录像素坐标和机器人坐标然后用 Halcon 的vector_to_hom_mat2d计算仿射变换矩阵。// 九九点标定计算像素坐标到机器人坐标的仿射变换 HTuple px new HTuple(100, 200, 300, 100, 200, 300, 100, 200, 300); HTuple py new HTuple(100, 100, 100, 200, 200, 200, 300, 300, 300); HTuple rx new HTuple(10, 20, 30, 10, 20, 30, 10, 20, 30); HTuple ry new HTuple(10, 10, 10, 20, 20, 20, 30, 30, 30); HOperatorSet.VectorToHomMat2d(px, py, rx, ry, out HTuple homMat2D); // 后续每次匹配得到像素坐标后转成机器人坐标 HOperatorSet.AffineTransPoint2d(homMat2D, row[0].D, col[0].D, out HTuple robotX, out HTuple robotY);九点标定要覆盖整个视野范围而且机械手末端走的是方格路线而不是 Z 字这样能减少机械误差对仿射变换求解的影响。标定做完之后验证方法很简单让机械手回到第一个点看看视觉算出来的坐标和实际坐标差多少1 毫米以内的误差属于正常超过就要怀疑是相机安装角度、镜头畸变或标定板贴合度的问题而不是匹配算法的锅。5. 部署排错与 ROI 交互的进阶技巧5.1 换电脑跑不起来十有八九是运行时和位数不匹配v0.0.1.2 的源码在开发机上能编译拷到现场工控机上就报Halcon can not find feature in ...这是最经典的部署问题。报这个错几乎都是 Halcon 运行时版本和程序引用版本对不上。最简单可靠的检查顺序先看工程的“目标平台”是 x64 还是 x86再确认halcondotnet.dll是从哪个目录引用的最后检查工控机安装了对应版本的 Halcon Runtime。如果机器上没有装完整版 Halcon需要单独装 Runtime 并设置系统环境变量HALCONROOT指向安装目录。另一个高频坑是 CPU 指令集老工控机用老版本 Halcon 通常没问题但有些新 Halcon 版本要求 SSE4.1 以上跑匹配算法直接非法指令崩溃。5.2 交互式 ROI如何在图像窗口上手动拖出检测区域VisionAndMotionPro 里有HWindowControl但 ROI 交互是否能直接用取决于源码有没有封装HWindowControl的HMessage回调。以我这个 Halcon 工程师的习惯我更倾向于用 Halcon 提供的高级控件HSmartWindowControl来替换它内部集成了鼠标缩放/拖拽下面这段是任意手绘 ROI 后获取区域并用于模板匹配的思路// 在 HSmartWindowControl 上开启 ROI 绘制模式 hSmart.HalconWindow.SetDraw(margin); hSmart.HalconWindow.SetColor(red); // 用户拖拽结束后得到 HRegion HRegion roi hSmart.HalconWindow.DrawRegion(); HOperatorSet.ReduceDomain(image, roi, out HObject reduced); // 用 ROI 内的图像去创建模板 HOperatorSet.CreateShapeModel(reduced, auto, 0, 0, auto, auto, ignore_local_polarity, auto, out HTuple modelID);生产现场换产时操作工不需要重新学模板算法——直接在屏幕上拖一个框点“学习模板”就完成新产品注册。DrawRegion是 Halcon 窗口的交互式方法会阻塞直到用户双击完成绘制所以要放在后台任务里调用不能用它直接卡 UI 线程。线上环境模板管理至少要做三件事模板文件按产品型号命名、保存对应的 ROI 坐标、存储建模板时用的参数。这样换产时一键加载不依赖老师傅的记忆。到这为止从工程结构、显示刷新、模板匹配到部署排错已经覆盖了 VisionAndMotionPro 源码里最值得花时间的四个落点。最后一个实操建议拿到源码后第一件事不是跑通主流程而是用5.bmp和6.bmp做一次完整的图像差分——把标准图和你现场拍的暗光图放进同一个流程里跑先量化差异再动算法参数。这样做能帮你最快摸清这套平台在你自己的工况下极限在哪。本文还有配套的精品资源点击获取
返回列表