简介:这是一套面向工业自动化开发者与机器视觉工程师的C#与Halcon深度集成框架源码,聚焦于AOI检测、机械手引导定位及多类智能装备(点胶机、插件机、激光切割/焊接机、视觉螺丝/贴合/裁板机)的视觉控制需求,解决从算法调用、手眼标定到运动控制的一体化开发难题。资源共1427个文件,以671个C#核心逻辑文件(.cs)、65个工程配置文件(.csproj)、101个Halcon依赖DLL及185个本地化资源文件(.resx)为主干,辅以脚本支持(.config/.settings)、图标与界面资源(.ico/.png),整体压缩包71.93MB,结构清晰、模块解耦,体现典型插件式架构设计。已有368人学习下载。用户可直接在VS2019中编译运行,开箱即用;完整包含手眼标定实现、相机静/动态检测适配、C#脚本扩展能力,以及VisionCore、RexVision等核心模块源码,便于快速二次开发与场景迁移。
1. C# + Halcon 机器视觉框架:不是“拿来就能跑”的玩具,而是产线级视觉系统落地的最小可行骨架
你拿到一个标着“C# + Halcon 机器视觉框架源码,VS2019 直接编译”的压缩包,双击解压、打开 .sln、点启动——结果弹出HalconDotNet.dll找不到、HALCON_LICENSE未激活、HDevEngine初始化失败、相机连接超时……这不是玄学,是绝大多数工程师第一次接触这类框架的真实翻车现场。这个标题说的不是教学 Demo,而是一套面向工业设备集成的视觉中间件雏形:它把 AOI 缺陷检测、机械手引导定位、点胶/插件/激光切割/螺丝锁附/贴合/焊接/裁板等典型工位的共性逻辑(图像采集→预处理→特征提取→决策输出→设备通信)做了分层封装,用 C# 做业务胶水,Halcon 做底层算子引擎,VS2019 是它当前最稳的编译靶场。适合两类人:一是产线自动化工程师想快速搭起视觉模块原型,避开从零写 HALCON C++ 接口或 Python 脚本调度的坑;二是视觉算法工程师需要把训练好的模型(如分类/分割结果)嵌入到已有 C# 上位机中,不重写整套 UI 和通信层。它不解决“怎么写一个圆检测”,但解决“怎么让圆检测结果变成 PLC 能读的 Modbus 寄存器值”——这才是标题里那些“点胶机”“视觉螺丝机”背后的真实诉求。
2. 框架结构拆解:为什么必须用 C# 做壳、Halcon 做核、VS2019 做编译器
2.1 三层架构:UI 层 / 视觉引擎层 / 设备交互层的职责边界
这个框架不是单个 WinForm 窗体塞满 Halcon 算子调用,而是明确划出三块责任区:
- UI 层(C# WinForm/WPF):负责参数配置(ROI 区域、阈值滑块、相机 IP)、实时图像显示(HWindowControl)、日志面板、启停按钮。它不碰图像数据,只通过事件(如
OnImageAcquired)接收处理结果。 - 视觉引擎层(Halcon + C# 封装):核心是
HalconVisionEngine类,它内部持有一个HDevEngine实例(非HObject直接操作),加载.hdev脚本或编译后的.hdv文件;所有图像处理流程(如 AOI 检测)被抽象为IVisionTask接口,不同任务(螺丝识别、焊缝定位)实现该接口,复用同一套图像输入/输出管道。 - 设备交互层(C# 原生):封装了 Modbus TCP(对接 PLC)、串口(对接老式控制器)、TCP Socket(对接激光器/点胶阀)的通用通信类。视觉引擎层完成检测后,调用此层将
ResultCode、X/Y/Theta、DefectType等结构化数据发给设备。
提示:框架里没有硬编码相机型号(如 Basler、Hikvision),而是通过
ICameraProvider接口注入具体实现。你替换 Basler 的PylonGrabber或海康的NETSDK,只需改一行ServiceProvider.Register<ICameraProvider, BaslerCamera>();,不碰视觉逻辑。
2.2 Halcon 版本与 C# 绑定的关键约束:为什么必须是 Halcon 20.11 或 21.05?
标题强调“VS2019 可直接编译”,这背后是 Halcon .NET API 的 ABI 兼容性陷阱。Halcon 从 13.0 开始提供HalconDotNet.dll,但直到 20.11 才真正稳定支持 .NET Framework 4.7.2(VS2019 默认目标框架)。若你强行用 Halcon 18.12,会遇到:
HObject析构时触发AccessViolationException(内存释放顺序错乱);HDevEngine加载含gen_measure_pose的脚本时报HALCON Error 3001(算子未注册);- 多线程调用
ReadImage时图像句柄泄漏(Halcon 19.05 修复)。
我们实测过 Halcon 20.11、21.05、22.05 三个版本:
| Halcon 版本 | VS2019 兼容性 | .NET Framework 支持 | 是否支持 HDevEngine 并发执行 | 推荐指数 |
|---|---|---|---|---|
| 20.11 | ✅ 完美 | 4.7.2+ | ✅(需手动加HDevEngine.SetThreadCount(4)) | ★★★★★ |
| 21.05 | ✅ | 4.7.2+ | ✅(默认启用多线程) | ★★★★☆ |
| 22.05 | ⚠️ 需升级 VS2022 | .NET 5+ | ❌(C# 项目需迁移到 .NET 6) | ★★☆☆☆ |
所以,当你下载框架源码,第一件事不是编译,而是检查packages.config或csproj中的<PackageReference Include="HalconDotNet" Version="20.11.0.0" />—— 若是22.05.0.0,立刻降级。Halcon 官网下载页有历史版本归档,搜 “Halcon 20.11 download” 即可。
2.3 VS2019 编译链路:为什么不能用 VS2022 或 VS2017?
VS2019 是这个框架的“黄金编译器”,原因有三:
- 平台工具集匹配:Halcon 20.11 的
HalconDotNet.dll是用 Visual Studio 2019 的 v142 工具集(MSVC 14.2)编译的。VS2017 用 v141,VS2022 用 v143,二进制不兼容会导致DllNotFoundException; - Windows SDK 版本:框架中调用
Windows.Devices.Enumeration获取 USB 相机(用于某些国产面阵相机),VS2019 默认 SDK 10.0.19041.0,而 VS2017 最高只支持 10.0.17763.0,缺少DeviceWatcher新 API; - NuGet 包还原机制:VS2019 对
packages.config的解析最稳定。VS2022 默认用PackageReference,若框架仍用旧式packages.config,还原时会漏掉HalconDotNet依赖,编译报The type or namespace name 'Halcon' could not be found。
验证方法:打开.sln后,右键项目 → “属性” → “常规” → 查看 “平台工具集” 是否为Visual Studio 2019 (v142);再看 “目标框架” 是否为.NET Framework 4.7.2。二者缺一不可。
3. 编译前必做三件事:环境、License、相机驱动
3.1 Halcon License 激活:不是“填序列号”,而是绑定硬件指纹
Halcon 的 license 不是传统软件密钥,而是基于机器硬件 ID(CPU ID + 网卡 MAC)生成的.lic文件。框架源码里通常带一个halcon.lic模板,但直接复制过去会失败——因为你的电脑指纹和作者的不一样。正确流程:
- 安装 Halcon 20.11(官网下载
halcon-20.11-win64.exe); - 运行
Halcon\bin\win64\halconlicense.exe; - 点 “Generate Hardware ID”,保存
hardware_id.txt; - 访问 MVTec 官网 License Portal(需注册账号),上传
hardware_id.txt,下载生成的halcon_license.lic; - 将该文件放到框架根目录,并在
App.config中指定路径:
<appSettings> <add key="HalconLicensePath" value=".\halcon_license.lic"/> </appSettings>注意:若跳过第 3 步直接填序列号,Halcon 会报
HALCON Error 1001: License file not found or invalid。这是血泪经验——曾有同事花两天排查代码,最后发现 license 文件名少了个下划线。
3.2 相机 SDK 集成:Basler、海康、大恒,选哪个?
框架源码里Camera文件夹下通常有BaslerCamera.cs、HikCamera.cs等空壳类,但实际运行时需自行集成对应 SDK。我们推荐 Basler Pylon(稳定性最高):
- 下载 Pylon 6.3.0(适配 Halcon 20.11);
- 在项目中引用
pyloncsharp.dll(路径:C:\Program Files\Basler\pylon 6\Components\Bin\x64\); - 修改
BaslerCamera.cs的Open()方法:
public override bool Open() { try { // 关键:必须先初始化 Pylon,再创建 Halcon 图像容器 _camera = new Camera(); _camera.Open(); _camera.Parameters[PLCameraInstance].TrySetValue(PLCameraInstance.GainRaw, 100); // 创建 Halcon 图像对象,尺寸与相机一致 _hImage = new HObject(); HTuple width, height; _camera.Parameters[PLCameraInstance].TryGetValue(PLCameraInstance.Width, out width); _camera.Parameters[PLCameraInstance].TryGetValue(PLCameraInstance.Height, out height); GenImageConst(out _hImage, "byte", width, height); // 初始化空图 return true; } catch (Exception ex) { Log.Error($"Basler open failed: {ex.Message}"); return false; } }逻辑说明:
GenImageConst创建的是 Halcon 内存中的图像容器,后续GrabOne会把 Pylon 获取的原始数据(byte[])拷贝进去。若跳过此步直接ReadImage,Halcon 会报HALCON Error 3002: Image object is not initialized。
3.3 Halcon DLL 引用路径:别信“复制到 bin 目录就行”
Halcon 的HalconDotNet.dll不是普通 .NET 程序集,它依赖一组原生 DLL(halcondotnet.dll,halcon.dll,halconxl.dll等),且必须放在与可执行文件同级目录,而非bin\Debug。框架编译后生成VisionSystem.exe,则所有 Halcon DLL 必须放在VisionSystem.exe所在文件夹下。
常见错误:
- 把
HalconDotNet.dll放bin\Debug,其他 DLL 放Halcon\bin\win64→ 运行时报Unable to load DLL 'halcon.dll'; - 用
Copy Local = True属性 → Visual Studio 只复制HalconDotNet.dll,漏掉halcon.dll。
正确做法:在.csproj中添加以下 Post-Build Event(构建后事件):
xcopy "$(HALCON_ROOT)\bin\win64\*.dll" "$(TargetDir)" /Y /I其中$(HALCON_ROOT)是 Halcon 安装路径(如C:\Program Files\MVTec\HALCON-20.11),需在 VS 的“工具 → 选项 → 项目和解决方案 → 生成并运行”中预先定义。
4. 视觉任务开发:从 AOI 检测到机械手定位的四步落地法
4.1 AOI 缺陷检测:用 Halcon 脚本生成 .hdv,而非硬编码算子
框架不鼓励你在 C# 里写Threshold,Connection,SelectShape—— 这样维护成本高、调试难。正确姿势是:
- 在 Halcon Developer(HDevelop)中写好 AOI 流程(如 PCB 焊点虚焊检测);
- 导出为
.hdev脚本; - 用 Halcon 自带工具
hdev2hdv.exe编译成.hdv(字节码,防反编译); - C# 中加载执行:
var engine = new HDevEngine(); engine.AddProcedurePath(@"C:\VisionTasks\AOI_Solder.hdv"); HDevEngineThread thread = engine.CreateThread(); thread.Execute("AOI_Solder", new HTuple(), new HTuple()); // 输入输出通过 HTuple 传递 HTuple result = thread.GetOutputCtrlParam("DefectCount"); // 获取缺陷数量参数说明:
AOI_Solder是脚本中read_image后第一个dev_display之前的算子名;DefectCount是脚本里set_output_ctrl_param('DefectCount', Count)定义的输出变量。这样 C# 只管传图、取结果,算法逻辑全在 Halcon 脚本里,算法工程师改脚本无需 C# 工程师介入。
4.2 机械手定位:亚像素级坐标转换的三个关键校准
让视觉告诉机械手“螺丝在哪”,不是简单返回(x,y)像素坐标,而是要转换成机械手坐标系下的(X,Y,θ)。框架中CoordinateCalibrator类负责此事,需三步校准:
- 像素-毫米转换(Scale Calibration):在视野中放已知尺寸标定板(如 10mm×10mm 方格),拍图后用
find_caltab+calibrate_cameras获取CameraParam; - 坐标系旋转补偿(Rotation Calibration):机械手法兰盘上贴标记点,移动机械手记录多组
(机械手X,机械手Y)和(图像u,v),用vector_to_hom_mat2d求出齐次变换矩阵; - Z轴高度补偿(Z Compensation):若工件高度变化,需用激光测距仪测 Z,查表修正 XY 偏移(
get_metrology_object_measures输出的Row,Column需按Z插值缩放)。
最终定位公式:
[ X ] [ R11 R12 Tx ] [ u ] [ Y ] = [ R21 R22 Ty ] × [ v ] + [OffsetX, OffsetY] [ 1 ] [ 0 0 1 ] [ 1 ]框架中GetRobotPose()方法封装了全部计算,你只需提供标定数据文件calib_data.xml。
4.3 激光切割/焊接路径生成:Halcon 骨架提取 + C# 轨迹平滑
对切割轮廓(如钣金件外沿),Halcon 用skeleton提取中心线骨架,但原始骨架含毛刺、断点。框架的LaserPathGenerator类做了三步后处理:
skeleton→connection→select_shape筛选最长连通域;get_contour_xld提取 XLD 轮廓 →gen_polygons_xld转多边形 →simplify_polygon_xld简化顶点;- C# 层用 Bézier 曲线拟合(
GraphicsPath.AddBezier),输出 G 代码段:
public string GenerateGCode(List<PointF> points, float feedRate = 1000f) { var gcode = new StringBuilder(); gcode.AppendLine("G21 ; mm mode"); gcode.AppendLine("G90 ; absolute positioning"); for (int i = 0; i < points.Count; i++) { var p = points[i]; gcode.AppendLine($"G1 X{p.X:F3} Y{p.Y:F3} F{feedRate}"); } return gcode.ToString(); }注意:
simplify_polygon_xld的MaxDeviation参数至关重要。设太小(如 0.1)保留毛刺;设太大(如 5.0)丢失拐角。我们产线实测:对 0.5mm 线宽切割,MaxDeviation=0.8最平衡。
5. 避坑指南:编译成功后,90% 的崩溃都发生在这五个环节
5.1 现象:程序启动后黑屏,HWindowControl 不显示图像
原因:Halcon 的HWindowControl必须在 UI 线程(主线程)中初始化,且需设置HWindow.Hold()防止图像被 GC 回收。框架中若在后台线程(如Task.Run)中调用DispObj,窗口无响应。
解决:所有DispObj、ClearWindow必须用Invoke切回 UI 线程:
this.Invoke((MethodInvoker)delegate { hWindowControl1.HobjectToHimage(_hImage); hWindowControl1.DispObj(_hImage); });5.2 现象:AOI 检测偶尔漏检,重启后又正常
原因:Halcon 的HDevEngine默认单线程执行,若多个视觉任务(如 AOI + 定位)并发调用Execute,后一个会阻塞前一个,导致图像缓存超时丢帧。
解决:显式设置线程池:
var engine = new HDevEngine(); engine.SetThreadCount(4); // 至少等于并发任务数 // 并确保每个任务使用独立 HDevEngineThread5.3 现象:Modbus 通信失败,PLC 收不到数据
原因:框架中ModbusClient默认超时 1000ms,但某些国产 PLC 响应慢(如汇川 AM600),实际需 1500ms。
解决:在ModbusClient.cs中修改:
public ModbusClient(string ip, int port = 502) { _client = new TcpClient(ip, port); _client.ReceiveTimeout = 1500; // 原为 1000 }5.4 现象:Halcon 脚本中read_image报错HALCON Error 3003: File not found
原因:脚本里写的路径是绝对路径(如C:/Images/test.bmp),但部署到客户现场路径变了。
解决:脚本中改用相对路径 + C# 传参:
* 在 HDevelop 中写: read_image (Image, 'default') * 占位符 * C# 中传真实路径: thread.SetInputCtrlParam('FileName', @"D:\Work\Images\part1.bmp"); thread.Execute("AOI_Task");5.5 现象:VS2019 编译通过,但运行时报System.DllNotFoundException: halcon.dll
原因:halcon.dll依赖MSVCP140.dll、VCRUNTIME140.dll等 VC++ 运行库,而客户电脑未安装 Visual C++ 2015-2019 Redistributable。
解决:打包时包含vcredist_x64.exe,并在安装脚本中静默安装:
vcredist_x64.exe /quiet /norestart6. 进阶技巧:如何把框架从“能跑”升级为“产线可用”的六个硬核动作
6.1 图像缓存池:避免频繁new HObject()导致 GC 崩溃
Halcon 的HObject是非托管资源,每new一次就分配一块内存,Dispose()不及时会 OOM。框架默认每次GrabOne都新建HObject,高频采集(如 30fps)下 10 分钟必崩。我们改成对象池:
public class HImagePool { private readonly ConcurrentBag<HObject> _pool = new(); private readonly int _maxSize = 10; public HObject Rent() { if (_pool.TryTake(out var obj)) return obj; return new HObject(); // 池空时新建 } public void Return(HObject obj) { if (_pool.Count < _maxSize) _pool.Add(obj); else obj.Dispose(); // 池满则释放 } } // 使用: var image = _imagePool.Rent(); _hCamera.GrabOne(image); // 直接复用 // ...处理... _imagePool.Return(image); // 归还效果:内存占用从峰值 2.1GB 降到 450MB,GC 次数减少 83%。这是我们在激光焊接机上实测的数据。
6.2 Halcon 浓淡补正:应对 LED 光源不均的工业级方案
产线 LED 光源总有边缘变暗问题,illumination_compensation算子效果有限。我们用框架的IlluminationCompensator类,结合三步法:
- 拍一张全白标定图(无工件);
- 用
mean_image+scale_image生成增益图GainImage; - 实时图像
div_image除以GainImage:
// 预处理阶段执行一次 GenImageConst(out GainImage, "real", Width, Height); MeanImage(WhiteImage, out MeanImage); ScaleImage(MeanImage, out GainImage, 1.0 / MeanValue, 0); // 均值归一化 // 实时处理 DivImage(RealImage, GainImage, out CompensatedImage, 1, 0);参数说明:
MeanValue是WhiteImage的全局均值,GainImage中每个像素 =MeanValue / 当前像素值,这样暗区增益 >1,亮区增益 ≈1。比illumination_compensation更可控。
6.3 视觉与 PLC 的心跳机制:防止通信假死
框架默认用 Modbus 读写寄存器,但若 PLC 断电或网络闪断,C# 端不会自动重连,任务卡死。我们加了心跳:
- C# 每 500ms 向 PLC 的
40001寄存器写0xAAAA; - PLC 程序监控
40001,若 2 秒未更新,置位故障标志; - C# 端读
40002,若值 ≠0xAAAA,触发重连:
private async Task HeartbeatLoop() { while (_isRunning) { try { await _modbus.WriteSingleRegisterAsync(0, 0xAAAA); // 地址 40001 var ack = await _modbus.ReadHoldingRegistersAsync(1, 1); // 读 40002 if (ack[0] != 0xAAAA) ReconnectPlc(); } catch { ReconnectPlc(); } await Task.Delay(500); } }6.4 日志分级与图像快照:调试时不再靠猜
框架自带日志,但产线问题常需“看到那一刻的图”。我们在VisionTaskBase中加了快照钩子:
protected virtual void OnTaskFailed(string reason, HObject image) { var path = $@"Logs\Fail_{DateTime.Now:yyyyMMdd_HHmmss}.png"; WriteImage(image, "png", path); // Halcon 原生保存 Log.Error($"{reason} | Snapshot saved: {path}"); }配合 NLog 配置,ERROR 级日志自动附加 PNG 路径,售后工程师远程拿到日志,直接打开图看缺陷形态。
6.5 Halcon 算子性能对比表:选对算子省 70% 时间
不是所有 Halcon 算子都一样快。我们在 i7-8700K + GTX1060 上实测 1920×1080 图像:
| 任务 | 推荐算子 | 耗时(ms) | 替代方案(耗时) | 适用场景 |
|---|---|---|---|---|
| 二值化 | fast_threshold | 8.2 | threshold(24.5) | 实时 AOI |
| 边缘检测 | edges_sub_pix | 32.1 | canny(41.7) | 精密定位 |
| 形状匹配 | find_shape_model | 15.3 | find_ncc_model(68.9) | 有灰度变化的工件 |
| 字符识别(OCR) | do_ocr_single_class | 4.8 | do_ocr_multi_class(12.3) | 单字体、高对比度 |
| 骨架提取 | skeleton | 11.6 | thin_image(29.4) | 激光路径 |
血泪经验:曾用
canny做焊缝跟踪,帧率卡在 8fps;换成edges_sub_pix后升到 22fps,满足 15fps 产线节拍。
6.6 部署包瘦身:从 1.2GB 到 280MB 的实战压缩
原始 Halcon 安装包 1.2GB,全打包进部署包不现实。我们只保留必需文件:
halcon.dll,halcondotnet.dll,halconxl.dll,halconcl.dll(GPU 加速);Halcon\bin\win64\halconcpp.dll(若用 C++ 混合);Halcon\images\下的标定板图(caltab.desc等);- 删除
Halcon\help\,Halcon\examples\,Halcon\c\全部内容。
最终部署包 280MB,含 VS2019 运行库、Pylon SDK、框架 EXE 及配置。U 盘拷贝时间从 12 分钟缩至 3 分钟。
我干了八年视觉集成,踩过的最大坑不是算法不准,而是没搞懂 Halcon 的内存模型和 VS 的工具链绑定。现在我的习惯是:拿到新框架,先查csproj的<TargetFramework>和<PlatformToolset>,再核packages.config的 Halcon 版本,最后跑一遍HalconDotNet.dll的Dependency Walker看缺失的 DLL——这三步做完,编译成功率从 30% 提到 95%。希望帮到你。
本文还有配套的精品资源,点击获取