C#与HALCON实战:构建工业视觉检测系统的完整链路
简介这份资源是面向机器视觉开发者与工控软件学习者的完整C#与HALCON视觉检测系统源代码聚焦工件表面质量检测场景可用于理解从图像采集、预处理、特征提取到决策反馈与界面展示的全流程架构适合具备一定C#基础、希望深入掌握HALCON算法调用与系统集成的中级开发者参考。压缩包共245个文件约45.48MB以80个cs源码文件为核心辅以bmp样例图像、dll与pdb运行库、resx与resources界面资源、csproj与sln工程文件及config配置等构成可直接研读的完整工程。资源已有7066人学习下载热度较高。读者可从中获得各功能模块的源码实现、HALCON算子调用方式、相机采集与缺陷判定逻辑以及可二次开发的项目结构便于对照学习并迁移到自己的检测项目中。1. 从一台产线相机说起这套 C# HALCON 视觉系统到底能干什么产线上相机拍下一张图几秒内要判断产品有没有划伤、孔位偏没偏、字符读没读对然后把 OK/NG 结果丢给 PLC。这套 C# HALCON 的完整视觉检测软件系统解决的就是这类活它把相机采集、图像预处理、模板匹配、测量、缺陷检测、结果判定和界面交互串成一条可运行的链路而不是丢给你一堆零散的算子示例。适合两类人一是手上接了检测项目、需要一套能直接改的框架的开发者二是学过 HALCON 但不知道怎么把它包成桌面软件的人。下面按“能干什么 → 怎么跑 → 怎么改”拆开讲源代码 C# 与 halcon 机器视觉软件系统的结合点会落在每个模块里。2. 工程结构与技术选型WinForm 壳 HALCON 算子 分层调用2.1 为什么是 C# WinForm 而不是 WPF这类视觉软件有个很现实的特点界面不追求炫追求稳和快。WinForm 在工业现场跑十几年不出问题的案例太多了控件拖拽快、相机 SDK 的 .NET 封装基本都优先给 WinForm 示例HALCON 的 HWindowControl 控件也是原生 WinForm 控件直接拖到窗体上就能显示图像省掉一大截图像渲染的适配工作。WPF 做视觉界面不是不行但 HWindowControl 的嵌入要绕一层 WindowsFormsHost缩放和刷新时序容易出玄学问题现场调试时不好排查。技术栈大致是这样一套组合层次技术作用界面层C# WinForm主界面、参数面板、结果显示图像显示HALCON HWindowControl图像渲染、ROI 绘制、结果叠加视觉算法HALCON (HDevelop 导出)匹配、测量、Blob、OCR相机采集相机厂商 .NET SDK取图、触发、曝光控制数据持久化本地文件 / 轻量数据库参数、配方、检测记录通信串口 / TCP / PLC 协议结果下发、信号握手选型上有个关键决策算法逻辑到底写在 C# 里还是留在 HDevelop 里。常见做法是先在 HDevelop 里把流程调通用 HDevelop 的导出功能生成 C# 代码再粘进工程里做封装。这样算法调试和界面开发能并行改参数也不用重新编译整个界面。代价是导出的代码变量名一坨坨的需要自己整理成方法。2.2 目录结构长什么样拿到源码包第一件事是看目录判断它是“能跑的工程”还是“半成品”。一个完整的视觉检测系统目录一般会呈现这样的分层VisionSystem/ ├── VisionSystem.sln ├── VisionSystem/ │ ├── Forms/ # 界面窗体 │ │ ├── MainForm.cs # 主界面 │ │ ├── CameraSettingForm.cs │ │ └── RecipeForm.cs # 配方管理 │ ├── Vision/ # 算法封装 │ │ ├── VisionTool.cs # 算子封装基类 │ │ ├── MatchTool.cs # 模板匹配 │ │ ├── MeasureTool.cs # 测量 │ │ └── BlobTool.cs # 缺陷/连通域 │ ├── Hardware/ # 硬件抽象 │ │ ├── ICamera.cs │ │ └── CameraBasler.cs │ ├── Common/ # 工具类 │ │ ├── Logger.cs │ │ └── ConfigHelper.cs │ └── Program.cs ├── halcon/ # hdev 算法文件 └── config/ # 配方与参数这个结构里最值得关注的是Vision/和Hardware/两个目录。前者决定算法能不能复用后者决定换相机时要不要动主流程。如果源码里算法全堆在 MainForm.cs 里那基本只能当学习材料如果按工具类拆开就有二次开发价值。2.3 相机采集的抽象层换相机是视觉项目里最常见的需求变更。今天用某品牌明天客户指定另一个品牌如果采集代码写死在主流程里改起来就是灾难。合格的做法是抽一个接口public interface ICamera { bool Open(); // 打开设备 bool Close(); // 关闭设备 bool GrabImage(out HObject image); // 软触发取一帧 bool SetExposure(double microseconds); // 设置曝光 bool StartGrabbing(); // 开始连续采集 event ActionHObject ImageGrabbed; // 回调推图 }GrabImage用out HObject直接吐 HALCON 图像对象避免中间转 Bitmap 再转回来少一次内存拷贝也少一次格式转换的坑。ImageGrabbed事件用于连续采集模式界面订阅后刷新显示。具体实现类CameraBasler里调用厂商 SDK把 SDK 的图像缓冲转成 HObject转换时注意像素格式对齐——黑白相机一般是 8 位单通道彩色是 RGB 三通道转错了图像会花屏或者颜色错位。参数说明SetExposure单位是微秒工业相机常见范围几百到几万微秒曝光太长运动物体拖影太短图像偏暗噪声大这个值要在现场用实际工件调。Open失败时先查设备是否被其他进程占用再看网口相机是不是 IP 段不对这两个是采集打不开的高频原因。3. 核心算法模块拆解从模板匹配到结果判定3.1 模板匹配定位是后续一切测量的前提视觉检测里工件在视野里的位置每次都不一样直接测量会全错。所以流程第一步几乎都是模板匹配先找到工件在哪、转了多少度再把坐标系对齐。HALCON 里常用create_shape_model或create_ncc_model前者对光照变化更鲁棒后者速度快但怕光变。// 创建形状模板在 HDevelop 调好后导出整理成方法 public HTuple CreateModel(HObject image, HTuple row, HTuple col, HTuple angle) { HObject modelRegion; // 从图像中抠出模板区域 GenRectangle1(out modelRegion, row - 100, col - 100, row 100, col 100); ReduceDomain(image, modelRegion, out HObject reduced); // 金字塔层数 auto角度范围 -180~180步长 auto CreateShapeModel(reduced, auto, -0.39, 0.78, auto, auto, use_polarity, auto, auto, out HTuple modelId); return modelId; } // 在待检图中查找模板 public HTuple FindModel(HObject image, HTuple modelId, out HTuple score) { FindShapeModel(image, modelId, -0.39, 0.78, 0.5, least_squares, 0, 0.9, out HTuple row, out HTuple col, out HTuple angle, out score); return new HTuple(row, col, angle); }逻辑说明CreateShapeModel的参数里角度范围-0.39~0.78弧度约等于 -22°~45°这是按工件可能出现的旋转范围设的范围越大匹配越慢别一上来就设 -180~180。FindShapeModel的0.5是最小匹配分数0.9是贪心度分数设太低会误匹配到相似纹理设太高现场稍有脏污就找不到。score输出匹配得分一般要求 0.7 以上才认为定位可信低于这个值后续测量结果直接判无效别硬算。血泪经验模板区域别抠太大包含太多背景会把背景当特征工件换批次背景一变就匹配不上也别太小特征不够同样不稳。一般框住工件上 2~3 个稳定特征即可。3.2 测量与缺陷检测坐标系对齐后再动手定位拿到row/col/angle后用affine_trans_region或affine_trans_contour_xld把预设的测量 ROI 变换到实际位置这样 ROI 永远跟着工件走。测量类用measure_pos沿一条测量矩形找边缘缺陷类用 Blob 分析或dynamic_threshold分割。// 把标准位置的 ROI 变换到实际工件位置 public HObject AlignRoi(HObject roi, HTuple row, HTuple col, HTuple angle, HTuple refRow, HTuple refCol) { // 构造仿射矩阵先平移到参考点再旋转再平移到实际点 HomMat2dIdentity(out HTuple homMat); HomMat2dTranslate(homMat, -refRow, -refCol, out homMat); HomMat2dRotate(homMat, angle, 0, 0, out homMat); HomMat2dTranslate(homMat, row, col, out homMat); AffineTransRegion(roi, out HObject alignedRoi, homMat, nearest_neighbor); return alignedRoi; }参数说明refRow/refCol是建模板时工件的参考坐标必须和建模板时用的坐标一致否则对齐会整体偏移。nearest_neighbor是插值方式ROI 变换用最近邻就够图像变换才需要考虑双线性。对齐后的 ROI 拿去ReduceDomain再MeasurePos测出的边缘距离就是实际尺寸和标准值一比就能判 OK/NG。缺陷检测这块常见做法是先Threshold或DynamicThreshold分割出暗斑/亮斑再Connection连通域用SelectShape按面积、圆度筛掉噪声剩下的就是缺陷。面积阈值是核心参数设太小灰尘都算缺陷设太大小划伤漏检这个值必须拿实际良品和不良品样本跑一遍统计出来拍脑袋定必然翻车。3.3 结果判定与数据落盘算法跑完得到一堆测量值判定逻辑要单独抽出来别和算法混在一起。判定规则通常来自配方每个检测项有标准值、上下公差、是否启用。public class InspectResult { public string ItemName { get; set; } public double Measured { get; set; } public double Standard { get; set; } public double Tolerance { get; set; } public bool IsOk Math.Abs(Measured - Standard) Tolerance; } public bool JudgeAll(ListInspectResult results) { // 任一项 NG 则整体 NG同时记录哪项挂了 foreach (var r in results) { if (!r.IsOk) { Logger.Warn(${r.ItemName} 超差: 实测 {r.Measured:F3}, 标准 {r.Standard:F3}±{r.Tolerance:F3}); return false; } } return true; }判定结果除了给 PLC还要落盘。检测记录建议存成 CSV 或轻量数据库字段至少包含时间戳、配方名、各测量值、总判定。出问题时这是唯一的黑匣子客户投诉说漏检翻记录能看出当时测量值到底是多少、是算法飘了还是阈值设错了。日志用Logger统一封装别到处Console.WriteLine现场没有控制台给你看。4. 跑起来环境搭建与第一次出图4.1 环境依赖清单这套系统跑起来需要三样东西.NET 运行环境、HALCON 运行时、相机驱动。版本匹配是第一个坎。.NET FrameworkWinForm 项目常见是 4.6.1 或 4.7.2装对应版本的运行时或开发包。用 Visual Studio 打开 .sln 时如果提示框架版本不匹配去项目属性里改目标框架别硬装一堆版本。HALCON需要安装 HALCON 并配置好HALCONROOT环境变量引用里的halcondotnet.dll路径要对。HALCON 版本和 dll 版本必须一致拿 20.11 的 dll 配 22.11 的运行时加载就报错。相机 SDK按实际相机品牌装装完确认能跑厂商自带的采集 Demo这一步过了再回来跑本项目。提示HALCON 是商业授权开发阶段用试用授权或加密狗部署到现场要确认授权方式别代码跑通了卡在授权上。4.2 编译与首次运行用 Visual Studio 打开解决方案先做一次“清理 → 重新生成”看错误列表。常见编译错误集中在两类一是 HALCON 引用路径失效右键引用里的halcondotnet把路径指到本机 HALCON 安装目录的dotnet35或dotnet文件夹二是相机 SDK 的 dll 没引用同样补上。编译通过后运行界面应该能起来。第一次运行大概率没图像因为相机没连或者配方没加载。排查顺序先看相机是否在设备管理器/厂商工具里可见再看程序里的相机选择下拉框有没有枚举到设备最后看日志有没有“打开相机失败”。如果只是想先看算法效果很多这类系统支持“加载本地图片”模式用一张样图跑通匹配和测量流程确认算法链路没问题再接相机。4.3 配方与参数配置视觉系统的参数不是写死在代码里的而是按产品存成配方。换产品时切配方不用改代码重新编译。配方文件一般是 XML 或 JSON包含模板文件路径、ROI 坐标、测量标准值、公差、曝光值等。Recipe NameProductA Camera Exposure8000 Gain0 / Match ModelFilemodel_a.shm MinScore0.7 / Measure ItemName孔径 Standard12.500 Tolerance0.050 / Measure ItemName孔距 Standard30.000 Tolerance0.100 / /Recipe参数说明Exposure单位微秒MinScore是匹配最低分Standard/Tolerance是判定基准。改配方后要重新采图验证别改完直接上产线——参数改了但模板没更新是现场最常见的“昨天还好好的今天全 NG”的原因。5. 避坑与排查现场最容易翻车的几件事5.1 匹配分数够但测量值整体偏移现象模板匹配得分 0.9 以上定位看着很准但所有测量值系统性偏大或偏小一个固定量。原因多半是标定没做或者标定系数用错像素当量mm/pixel不对或者相机工作距离变了没重新标定。解决用标准件已知尺寸的校准块重新标定确认像素当量 实际尺寸 / 像素尺寸把结果写进配方同时检查镜头焦距、工作距离是否和标定时一致动了相机位置必须重标。5.2 换批次工件后大面积误检现象同一配方上一批良品率 99%换一批料突然大量 NG。原因通常是光照或工件表面状态变了模板匹配还能找到但阈值分割的灰度分布漂了Blob 面积全变。解决不要急着调阈值先把当批良品图存下来看灰度直方图对比建配方时的分布如果整体亮度偏移优先调光源或曝光而不是改算法阈值。算法阈值是最后手段改了就破坏了配方的通用性。5.3 程序跑几小时内存持续上涨现象连续运行后内存曲线一路向上最后卡死或崩溃。原因多是 HALCON 对象没释放HObject和HTuple是托管包装的非托管资源循环里GenImage1、FindShapeModel产生的对象不Dispose就会累积。解决所有HObject用using包起来或者流程结束统一DisposeHTuple虽然多数情况由 GC 管但大数组反复创建也建议手动清。用任务管理器看私有工作集跑一小时涨几百 MB 基本就是泄漏。5.4 相机能出图但界面卡顿现象采集正常但界面刷新一顿一顿拖动窗口更明显。原因一般是采集回调里直接操作 UI 控件跨线程访问导致阻塞或异常或者每帧都在做全图匹配算法耗时超过帧间隔。解决回调里只把图像存到缓冲用Invoke或定时器在主线程刷新显示算法和显示解耦显示可以降帧算法该多快多快。匹配前先ReduceDomain缩小搜索区域别整幅图全搜。5.5 部署到现场机器提示找不到 dll现象开发机跑得好好的拷到现场电脑一运行就报halcondotnet.dll加载失败或版本不匹配。原因现场装的是不同版本 HALCON或者只装了运行时没装开发库或者目标平台x64/x86和 dll 位数不一致。解决确认现场 HALCON 版本与开发一致项目平台目标设为 x64现在工业相机基本 64 位把依赖 dll 随程序一起拷到输出目录别依赖系统 PATH。6. 二次开发把算法工具化把流程配置化拿到这套源码做二次开发最值钱的改造方向不是加功能而是把“写死的流程”变成“可配置的工具链”。我一般会做两件事。第一件把每个视觉功能抽成统一的工具接口让流程按配置顺序执行public interface IVisionTool { string Name { get; } bool Execute(HObject input, ref HObject output, Dictionarystring, object param); } // 流程引擎按配方里的工具列表依次执行 public class ToolPipeline { private readonly ListIVisionTool _tools new ListIVisionTool(); public void AddTool(IVisionTool tool) _tools.Add(tool); public bool Run(HObject image, out HObject lastOutput) { lastOutput image; foreach (var tool in _tools) { if (!tool.Execute(lastOutput, ref lastOutput, toolParams[tool.Name])) return false; // 任一工具失败即中断记录失败工具名 } return true; } }这样新增一个检测项只需实现IVisionTool并注册不用动主流程。配方里存工具名和参数换产品就是换一份工具链配置。改造时注意每个工具内部自己管好 HALCON 对象的生命周期Execute返回前把中间对象释放掉否则工具越多泄漏越快。第二件给算法加“离线验证”入口。现场调参最怕改完不知道效果做一个批量跑图功能选一个文件夹的样图跑完整流程输出每张图的测量值和判定和人工标注对比。验证方法很朴素——拿 50 张良品 50 张不良品跑一遍看误检率和漏检率两个都压到可接受范围再上线。这个习惯救过我很多次参数在单张图上调得再漂亮批量一跑原形毕露。改造时还有个细节HALCON 导出的代码里变量名是HObject1、HTuple2这种直接粘进工程能跑但没法维护。我的做法是每接一个算法先在 HDevelop 里把流程和参数注释写清楚导出后立刻重命名成业务语义holeRegion、matchScore再包进工具类。当时嫌麻烦跳过这步的项目半年后自己都看不懂。从那以后我每次接视觉项目都强制先把“采集 → 定位 → 测量 → 判定 → 落盘”这条链路用一张样图跑通再谈界面和产线对接链路不通后面全是空中楼阁。希望帮到你。本文还有配套的精品资源点击获取