简介:这份资源是面向机器视觉开发者与工业检测方向学习者的完整C#与HALCON视觉检测软件系统源代码,可用于工件表面质量检测场景,帮助读者理解从图像采集、预处理、特征提取到决策反馈与界面展示的全流程实现,适合具备一定C#基础、希望深入掌握视觉系统架构的中高级开发者参考。压缩包共245个文件,以80个cs源码文件为核心,辅以bmp测试图像、dll与pdb运行库、resx与resources界面资源、csproj项目文件及sln解决方案等,整体约45.48MB,工程结构完整,可直接在Visual Studio中打开研读。目前已有7066人学习下载,说明其在同类资源中具有较高参考价值。读者可从中获得模块化的系统设计思路、HALCON算法调用方式、相机采集与缺陷判定逻辑,以及可二次开发定制的工程骨架,便于对照自身项目查漏补缺、快速搭建检测原型。
1. 拿到一个 C# + HALCON 视觉检测系统源码,先别急着点运行
做过产线视觉项目的人大概都有这个体会:算法调通只是开始,真正耗时间的是把相机取图、模板匹配、尺寸测量、结果判定、数据落盘、界面刷新这一整条链路串起来,还要保证连续跑八小时不崩。这份 C# + HALCON 的完整视觉检测软件系统源码,解决的正是这个“串起来”的问题——它不是单个算法 Demo,而是一套带界面、带流程、带结果管理的桌面级检测软件骨架。适合两类人:一是刚接触机器视觉、想看清一套完整检测软件长什么样的新手;二是手里有 HALCON 授权、需要快速搭出二次开发底座的熟手。下面按“能干什么、怎么跑、怎么改”的顺序拆。
2. 项目定位与技术栈:这套系统到底由哪几块拼起来
2.1 从检测流程反推系统边界
判断一套视觉软件是不是“完整系统”,我一般不看界面多花哨,而是看它有没有把检测流程切成可管理的阶段。这套源码的骨架基本遵循工业检测的通用节奏:图像采集 → 预处理 → 定位/匹配 → 测量或缺陷判定 → 结果输出 → 数据留存。每个阶段在代码里对应独立的处理单元,而不是全塞在一个按钮事件里。这种切分带来的直接好处是,换相机、换检测项、换判定阈值时,改动面被限制在局部,不会牵一发动全身。
从工程角度看,它更接近“可运行的检测框架”而非“某个产品的成品”。界面层负责参数配置和结果展示,业务层负责调度检测流程,算法层封装 HALCON 算子调用,三层之间通过明确的数据结构传递图像和结果。新手容易忽略的一点是:视觉软件里图像对象(HALCON 的 HObject)生命周期管理很讲究,随手 new 随手丢,跑久了内存就上去了。这套源码在关键路径上做了释放处理,读的时候值得留意。
2.2 技术栈拆解:C# 桌面端 + HALCON 算法库
技术选型上没有意外,是工业视觉里最常见的一组搭配:
| 层次 | 技术 | 作用 |
|---|---|---|
| 界面 | WinForms(部分项目为 WPF) | 参数面板、图像显示、结果列表 |
| 业务逻辑 | C# / .NET | 流程调度、状态管理、文件与数据库操作 |
| 视觉算法 | HALCON(halcondotnet) | 图像处理、模板匹配、测量、Blob 分析 |
| 图像显示 | HALCON HWindowControl | 在原图上叠加区域、轮廓、测量结果 |
| 数据存储 | 本地文件 / 轻量数据库 | 检测记录、NG 图、参数配置 |
C# 调 HALCON 的核心是halcondotnet.dll这层封装,它把 HALCON 的算子映射成 .NET 可调用的类和方法。常见做法是在项目里引用该程序集,然后通过HOperatorSet静态类调用算子,或者用HObject、HTuple这些封装类型。这里有个血泪经验:HALCON 版本和 halcondotnet 程序集版本必须严格对应,混用不同版本的 dll,运行时经常报“找不到入口点”这类玄学错误,排查起来很费劲。
2.3 目录结构:先认清每个文件夹的职责
拿到压缩包解压后,别一头扎进代码。先花十分钟把目录过一遍,后面定位问题会快很多。这类项目的典型结构大致如下(具体名称以实际源码为准):
Solution/ ├── VisionSystem.sln 解决方案入口 ├── MainApp/ 主程序:界面、流程调度 │ ├── Forms/ 各功能窗体 │ ├── Controls/ 自定义控件(图像显示等) │ └── Program.cs 程序入口 ├── VisionCore/ 算法与业务核心 │ ├── Algorithm/ HALCON 算子封装 │ ├── Models/ 图像、结果、参数数据结构 │ └── Services/ 采集、检测、存储服务 ├── Config/ 参数配置文件 ├── Resources/ 图标、标定文件、模板文件 └── Logs/ 运行日志与 NG 图认清VisionCore和MainApp的分界很关键:算法调试去 VisionCore,界面交互去 MainApp。很多新手把算法逻辑直接写进窗体事件,结果想复用到另一个项目时发现根本抽不出来。这套源码把两者分开,正是它值得作为底座的原因。
3. 跑起来:环境准备与第一个检测流程
3.1 环境依赖与 HALCON 授权确认
在动手编译之前,先把环境对齐,否则后面全是无效排查。需要准备的东西不多,但每一样都不能缺:
- Visual Studio(建议 2019 及以上),安装 .NET 桌面开发工作负载;
- HALCON 运行库与开发库,版本要和源码引用的 halcondotnet 一致;
- 有效的 HALCON 授权(开发版或运行版),授权文件放到 HALCON 安装目录下;
- 若项目用到相机,还需对应相机的 SDK 与驱动。
先确认 HALCON 本身能跑:打开 HALCON 自带的 HDevelop,随便读一张图跑个算子,能出结果说明授权和运行库没问题。这一步别省,我见过太多人卡在“代码没错但一运行就报授权异常”,最后发现是 license 没放对位置。
3.2 编译与首次运行:从解决方案到主界面
环境就绪后,用 VS 打开.sln,先做一次“重新生成解决方案”。重点看输出窗口里的引用警告:如果 halcondotnet 显示黄色感叹号,说明程序集路径不对,需要手动重新引用本机 HALCON 安装目录下的 dll。编译通过后,把启动项目设为主程序,F5 运行。
首次启动一般会加载默认配置。如果界面能正常显示、图像显示控件是黑的但没报错,说明基础环境通了。此时不要急着接相机,先用“加载本地图片”这类功能验证算法链路——这是最稳妥的验证顺序。下面是一段典型的 HALCON 读图与显示代码,用来确认算法层是否工作正常:
// 引用 HALCON 封装 using HalconDotNet; // 读取本地图像并显示到控件 HObject image; HTuple width, height; HOperatorSet.ReadImage(out image, "test.png"); // 读图,路径按实际改 HOperatorSet.GetImageSize(image, out width, out height); hWindowControl1.HalconWindow.SetPart(0, 0, height - 1, width - 1); // 适配显示区域 hWindowControl1.HalconWindow.DispObj(image); // 显示原图 image.Dispose(); // 及时释放,避免内存累积这段代码的逻辑很直白:ReadImage把文件读成 HObject,GetImageSize拿到尺寸用于设置显示窗口的坐标范围,SetPart保证图像按 1:1 铺满控件而不是被拉伸,最后DispObj绘制。参数上要注意SetPart的四个值顺序是“行起点、列起点、行终点、列终点”,写反了图像会倒过来或者只显示一角,这是新手高频翻车点。Dispose那行别嫌多余,HALCON 的图像对象不释放,连续检测时内存曲线会一路往上爬。
3.3 接相机与跑通一次完整检测
本地图片验证通过后,再切到相机采集。相机接入通常走厂商 SDK,把采集到的帧转成 HALCON 能处理的格式。常见做法是相机回调里拿到原始数据,用GenImage1或GenImage3生成 HObject,再送进检测流程。这里要注意线程问题:相机回调往往在独立线程,直接操作界面控件会抛跨线程异常,正确姿势是用Invoke或BeginInvoke切回 UI 线程再刷新显示。
跑通一次完整检测的验证标准是:取到图 → 定位到目标 → 输出测量值或 OK/NG → 界面显示叠加结果 → 记录落盘。五个环节缺一个都说明链路没通。建议第一次跑用单张触发模式,别用连续采集,方便逐帧看结果。等单帧稳定了再切连续模式,观察内存和 CPU 占用是否平稳。
4. 核心模块拆解:定位、测量与结果判定怎么落地
4.1 模板匹配定位:让后续测量有稳定基准
工业检测里,被测物在视野中的位置每次都有偏差,直接测量会引入误差。所以流程上通常先做模板匹配,找到目标的位置和角度,再把测量区域跟着变换过去。这套源码的定位模块就是干这个的。核心是创建模板和查找模板两步:
// 创建模板(一般在参数配置阶段做一次) HObject modelRegion; HOperatorSet.GenRectangle1(out modelRegion, 100, 100, 300, 400); // 模板区域 HTuple modelID; HOperatorSet.CreateShapeModel(image, "auto", -0.39, 0.78, "auto", "auto", "use_polarity", "auto", "auto", out modelID); // 查找模板(每次检测执行) HTuple row, column, angle, score; HOperatorSet.FindShapeModel(image, modelID, -0.39, 0.78, 0.7, 1, 0.5, "least_squares", 0, 0.9, out row, out column, out angle, out score);CreateShapeModel的参数里,角度范围(-0.39 到 0.78 弧度)决定了允许的旋转容差,给太窄,来料稍微歪一点就匹配不上;给太宽,又容易误匹配到相似区域。FindShapeModel里的 0.7 是最小匹配分数,低于它就不认。这个阈值是调出来的,不是拍脑袋定的——我一般会拿一批 OK 样本和 NG 样本各跑一遍,看分数分布,把阈值卡在两者之间的安全区。score输出值建议记录到日志里,后期分析误判时它就是关键证据。
4.2 测量与缺陷判定:从区域到数值
定位拿到基准后,测量模块在变换后的 ROI 上做尺寸计算或缺陷提取。尺寸测量常用卡尺(Measure)或轮廓拟合,缺陷检测常用 Blob 分析加特征筛选。以 Blob 为例,典型链路是阈值分割 → 连通域 → 按面积/圆度筛选 → 输出缺陷数量和位置:
HObject region, connectedRegions, selectedRegions; HTuple area, row, column; HOperatorSet.Threshold(image, out region, 80, 255); // 阈值分割 HOperatorSet.Connection(region, out connectedRegions); // 拆连通域 HOperatorSet.SelectShape(connectedRegions, out selectedRegions, "area", "and", 500, 99999); // 按面积筛选 HOperatorSet.AreaCenter(selectedRegions, out area, out row, out column); HOperatorSet.CountObj(selectedRegions, out count); // 缺陷个数阈值上下限(80 到 255)直接决定分割效果,光照变化大的场景建议先做图像增强或改用动态阈值。SelectShape的面积区间是过滤噪点的关键,太小会把灰尘当缺陷,太大又会漏掉真实小缺陷。AreaCenter一次拿到面积和中心坐标,方便后续在界面上标注。判定逻辑通常写在业务层:缺陷个数超过设定值、或某个尺寸超出公差带,就判 NG。把判定阈值做成可配置项,而不是写死在代码里,是这套源码比较务实的地方。
4.3 结果输出与数据留存:NG 图与检测记录
检测结果不能只停在界面上,产线追溯需要留痕。这套系统的输出模块一般做两件事:把 NG 图按时间戳存到指定目录,把检测数值写入记录文件或数据库。存图时建议连同叠加了缺陷标记的渲染图一起存,后期复盘时一眼能看出当时判了什么。记录字段至少包含时间、工位、检测项、测量值、判定结果。这里有个容易忽略的点:磁盘写入要放在后台线程或队列里,别在检测主线程里同步写文件,否则单帧耗时会被 IO 拖长,连续检测时节奏就乱了。
5. 避坑与排查:这类视觉系统最容易翻车的几个地方
5.1 现象:运行一段时间内存持续上涨
原因多半是 HObject 或 HTuple 没有释放。HALCON 的图像对象是托管资源包装的非托管内存,GC 不一定及时回收。解决方式是在每个处理函数的出口显式Dispose,或者用using包裹生命周期明确的局部对象。排查时可以在循环里打印进程内存,看曲线是否单调上升。
5.2 现象:换一台电脑就报找不到 halcondotnet
原因是引用的是绝对路径,或者目标机器没装对应版本的 HALCON 运行库。解决方式是统一 HALCON 版本,引用改为相对路径或通过 NuGet/安装目录引用,部署时把运行库一并带上。别指望目标机“碰巧”有正确版本。
5.3 现象:模板匹配时好时坏,分数忽高忽低
原因通常是光照不稳定或模板区域选得不好。模板区域里如果混入了背景或反光面,匹配稳定性会明显下降。解决方式是重新框选纹理丰富、光照均匀的区域做模板,并在采集端加光源或做曝光固定。必要时降低最小匹配分数并配合角度搜索,但要同步验证误匹配率。
5.4 现象:相机回调里刷新界面直接崩溃
原因是跨线程操作 UI 控件。解决方式是把显示刷新用Invoke切回主线程,或者用生产者-消费者队列,采集线程只负责入队,UI 线程定时出队刷新。直接关掉跨线程检查只能掩盖问题,不能解决。
5.5 现象:连续检测时单帧耗时越来越长
原因可能是日志或图片同步写盘、也可能是图像对象累积。解决方式是把 IO 操作异步化,限制日志级别,定期清理临时文件;同时用性能计时器定位耗时集中在采集、算法还是存储环节,别凭感觉优化。
6. 二次开发改造:把检测项做成可配置的插件式结构
这套源码拿来直接用往往不够,产线需求总会变。我一般会做的第一件事,是把“检测项”从硬编码里抽出来,做成配置驱动。具体做法是定义一个检测项接口,每个检测项实现自己的参数加载、执行和结果输出,主流程只负责按配置顺序调度。这样新增一个检测项时,不用改主流程,只加一个实现类加一段配置。
public interface IDetectionItem { string Name { get; } void LoadParam(string configPath); // 从配置读参数 DetectResult Execute(HObject image); // 执行检测 } // 主流程按配置顺序调度 foreach (var item in detectionItems) { var result = item.Execute(image); resultList.Add(result); if (!result.IsOk) break; // 任一 NG 可提前终止 }接口里LoadParam和Execute分开,是为了让参数调整和检测执行解耦,改阈值不用重新编译。DetectResult里带上测量值、判定和叠加显示用的区域,界面层直接消费,不用再回头查算法细节。验证改造是否成功,我习惯用同一批样本图跑改造前后对比,判定结果必须完全一致,否则说明抽取过程中逻辑被改动了。
还有一个实用技巧:把 HALCON 的模板文件、标定文件路径统一走配置中心管理,别散落在各个窗体里。产线换型时只改配置不改代码,这是能不能快速交付的分水岭。从那以后我每次接手这类视觉系统源码,都强制先把配置和算法分离这一层走一遍,再谈加功能——不然改到后面,自己都不知道哪个阈值藏在哪个按钮事件里。希望帮到你。
本文还有配套的精品资源,点击获取