C#+HALCON视觉检测源码:WinForm上位机与采图定位测量判定通讯

发布时间:2026/10/26 8:44:13
C#+HALCON视觉检测源码:WinForm上位机与采图定位测量判定通讯 简介面向机器视觉开发者与工业检测方向学习者的C#结合HALCON视觉检测软件系统完整源代码聚焦工件表面质量检测场景可作为缺陷检测、图像采集处理与决策反馈全流程的工程参考也适合有C#基础、希望深入HALCON算法调用的中高级读者进行二次开发与定制。压缩包共245个文件约45.48MB以80个cs源码文件、35张bmp测试图像、29个dll与23个pdb程序集及调试文件为主另有10个resx和resources界面资源、6个csproj工程文件及配置、数据库脚本等完整保留项目工程结构与运行依赖。已有7066人学习下载说明该案例在机器视觉入门与工程实践社群中具有一定参考价值。读者可从中了解图像采集、图像预处理、特征提取、决策反馈和图形化界面等模块的组织方式参考C#调用HALCON完成形状匹配、模板匹配、测量与缺陷判定的实现思路并借助现有工程快速验证算法、查漏补缺提升视觉系统搭建与二次开发能力。1. 从一条换型就要重编译的检测线说起这套 C# HALCON 系统能顶什么活某公司的视觉组接手过一条小件外观检测线。最初的做法是在 HDevelop 里把流程调通导出成 C# 代码整段塞进一个按钮的点击事件里。第一版跑得挺好第二个月客户加了三个新型号每换一次型号就得改算子参数、重新编译、重新部署现场调试的人连日志都看不到。后来他们把这份 C# HALCON 的视觉检测系统源代码重新梳理了一遍才把“流程”和“算子”真正拆开。这份资源适合两类人手里有工业相机、要用 C# 做上位机界面但不知道 HALCON 算子和 WinForm 怎么对接的开发者以及已经有能跑的检测程序想看看别人怎么组织采图、定位、测量、判定、通讯这五块的熟手。它更像一套把机器视觉检测从取像到结果输出串成完整链路的工程骨架并非只会“读图—阈值—显示”的 Demo。2. 技术栈与工程结构解决方案里到底分了哪几层2.1 选型清单WinForm HalconDotNet .NET Framework 4.x 的现实理由现场工控机大多还是五六年前的配置集显、4G 内存、Win7 或 Win10 LTSC这种环境下 WPF 的渲染开销和部署复杂度都不划算所以整套系统用 WinForm 做界面是合理选择。算法侧用 HALCON 的托管封装 HalconDotNet通过引用 HALCON 安装目录下 dotnet 目录里的 halcondotnet.dll 调用算子本质是把 HDevelop 导出的过程调用包成 C# 方法。版本对齐是这套组合里最关键的一件事。halcondotnet.dll、halcon.dll、运行时授权文件三者版本必须一致工程平台目标必须与引用的 HALCON 库位数一致现在基本都是 x64。很多新手第一次跑就报“无法加载 DLL halcon”八成是 AnyCPU 加了个 32 位库或者 NuGet 上随手装了个来路不明的包。组成常见取向说明UI 框架WinForm.NET Framework 4.6.2 / 4.8现场部署简单控件够用拍照显示用 PictureBox 或自绘 Panel视觉库HALCON HalconDotNet算子用 HDevelop 调通后导出 C# 代码再封装进工具类图像采集相机厂商 SDK 回调 HObject 转换比 HALCON 自带采集接口更贴近现场曝光增益控制更细通讯Modbus TCP / 厂商私有协议与 PLC 做触发、结果、心跳三类信号握手配置JSON 或 XML把型号、参数、ROI 从代码里挪出去日志文本滚动日志 图片留档NG 图必须存这是后面追问题的唯一证据值得注意的是这套源码的价值不在于用了哪个版本的 HALCON而在于它把“型号参数”外置了。同一个检测工位换型号只改配置不改代码这才是产线愿意接受的东西。2.2 目录结构UI、流程、算子、设备四层切开一个能长期维护的视觉项目目录结构基本能看出作者的思路。这套代码的组织方式大致如下VisionSystem/ ├─ VisionSystem.sln ├─ 01.UI/ 主界面、参数配置页、结果与统计页、图像回放 ├─ 02.Core/ 流程引擎、任务调度、结果判定、状态机 ├─ 03.HalconTools/ 算子封装定位、测量、Blob、模板、标定 ├─ 04.Devices/ 相机、光源控制器、PLC、IO 卡驱动封装 ├─ 05.Infrastructure/ 日志、配置读写、异常处理、授权检查 └─ 06.Tests/ 离线图片回放与算法回归分层的意义在于03 层只认 HObject不认任何界面控件01 层只认结果对象不认算子句柄。中间靠 02 层的流程引擎粘合。这样改算法的时候不用动界面改界面的时候不会误伤算法。血泪经验是绝大多数“视觉软件越改越乱”的项目问题都出在把 HOperatorSet 调用直接写在 Form 的按钮事件里。算子句柄没有释放、图像对象被 UI 线程持有、异常吞掉不记录最后全变成玄学问题。2.3 一份 JSON 描述一个检测方案配置和代码分离把每个检测项抽象成统一的接口是这套代码里最值得抄的部分。它定义了一个很薄的工具契约// 所有视觉工具的统一入口新增算子只要实现这个接口即可 public interface IVisionTool { string Name { get; } // 工具名如 LocateChip、MeasurePin void Load(JObject cfg); // 从方案 JSON 中读取本工具的参数 ToolResult Run(HObject image, HObject roi); // image 为原图roi 为上一级传下来的搜索区域 } public class ToolResult { public bool Ok { get; set; } // 单项判定结果 public double[] Values { get; set; } // 测量值如宽度、间距、角度 public HTuple Row { get; set; } // 定位输出供下一级工具使用 public HTuple Col { get; set; } public HTuple Angle { get; set; } public HObject DebugRegion { get; set; } // 调试用叠加区域正式跑图时置空省内存 }配合的方案配置文件大致长这样一个型号一个文件换型就是换文件{ model: TYPE-A, tools: [ { name: LocateChip, type: ShapeMatch, angleStart: -30, angleExtent: 60, minScore: 0.7 }, { name: MeasurePin, type: Caliper, edgeThreshold: 25, tolerance: 0.05 }, { name: BlobDefect, type: Blob, minArea: 80, maxArea: 5000, grayMin: 0, grayMax: 60 } ] }Load负责反序列化参数Run负责执行并回填结果DebugRegion是调试期叠加显示用的正式跑的时候一定要置空否则每小时几百张图的句柄堆积起来会把内存吃掉。这套结构的直接好处是加一个新检测项只需要写一个实现类并在配置里加一行不用碰流程引擎也不用碰界面。3. 采图链路从相机回调到第一帧能参与运算的 HObject3.1 相机接入厂商 SDK 回调还是 HALCON 的图像采集接口两条路各有取舍。用 HALCON 的open_framegrabber/grab_image_async接口统一、换相机改一个字符串就行但曝光、增益、触发延时这些细节控制不如厂商 SDK 细用厂商 SDK 注册回调拿原始数据控制力强代价是要自己写格式转换。产线项目里我一般选后者相机厂商的 SDK 拿到的是 byte 数组或已封装的位图转成 HObject 只是一次内存拷贝的事而曝光时间、白平衡、IO 触发沿这些参数在调试时天天要动。转换的关键是别用HImage的构造函数去接Bitmap那条路会多一次托管到非托管的复制2000 万像素下能明显感到卡。// 把相机 SDK 回调里的原始 buffer 直接包成 HObject避免 Bitmap 中转 public HObject BufferToHObject(byte[] buffer, int width, int height) { // 单通道灰度图byte 数组 - HObject指针只在调用期间有效 HOperatorSet.GenImage1(out HObject image, byte, width, height, buffer); return image; } // 彩色相机用三通道注意 BGR 顺序要和 HALCON 的 bgr 对齐 public HObject BufferToHObjectRgb(byte[] b, byte[] g, byte[] r, int w, int h) { HOperatorSet.GenImage3(out HObject image, byte, w, h, b, g, r); return image; }GenImage1的第三个第四个参数是宽高第五个是数据指针。这里传 byte[] 时 HALCON 会自动做一次封送图像对象持有的是自己的一份副本所以回调里的 buffer 之后被相机 SDK 复用不影响结果。如果是用HTuple接指针的写法GenImage1Extern就得保证指针在图像生命周期内有效一旦相机 buffer 被回收就会出现读脏数据的花屏这种问题排查起来极其费劲。采图这一层还要处理一件事触发模式。产线一般是 PLC 给硬触发相机收到信号后曝光出图回调里把图丢进队列。软件触发只适合调试。3.2 HObject 与 Bitmap 互转显示、存图、回放三处都要用界面上要显示就得转成 BitmapNG 图要存档可以直接用 HALCON 的write_image离线回放要读图用read_image拿到 HObject。三条路径都要经过转换所以这段工具代码必须写得干净。8 位灰度图转 Bitmap 有个经典坑直接new Bitmap(w, h, PixelFormat.Format8bppIndexed)出来的图如果不写调色板显示出来是全黑的很多人第一次遇到会以为是图像没取到。public static Bitmap ToBitmap(HObject image) { HOperatorSet.GetImageSize(image, out HTuple width, out HTuple height); HOperatorSet.GetImageType(image, out HTuple type); int w width.I, h height.I; if (type.S byte) { HOperatorSet.GetImagePointer1(image, out IntPtr ptr, out _, out _, out _); var bmp new Bitmap(w, h, PixelFormat.Format8bppIndexed); // 8 位索引图必须显式写灰度调色板否则整幅图显示为全黑 ColorPalette palette bmp.Palette; for (int i 0; i 256; i) palette.Entries[i] Color.FromArgb(i, i, i); bmp.Palette palette; BitmapData data bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); // 逐行拷贝因为 Bitmap 的 stride 可能大于 width for (int y 0; y h; y) CopyMemory(data.Scan0 y * data.Stride, ptr y * w, (IntPtr)w); bmp.UnlockBits(data); return bmp; } // 三通道图走 Format24bppRgb注意 HALCON 的通道顺序是 RGB throw new NotSupportedException($unsupported image type: {type.S}); }LockBits加Scan0 y * Stride的写法比逐像素SetPixel快两个数量级这是处理大图时的基本操作。这里没有直接用Marshal.Copy一次性拷贝整块是因为 Bitmap 每行的 stride 会被对齐到 4 字节边界宽度不是 4 的倍数时直接整块拷会错行。这个细节不注意1920×1200 这种宽度能对1280×1024 也能对但换个 500 万像素的相机就花屏了。存 NG 图时我一般用 HALCON 自己写HOperatorSet.WriteImage(image, bmp, 0, path)好处是不用管调色板读回来还是 HObject离线复现问题的路径最短。3.3 采集线程与队列别让 UI 线程碰算子一套检测系统正常的线程模型是三段相机回调线程只负责把图塞进BlockingCollectionHObject处理线程从队列取图跑流程引擎得到结果UI 线程只负责把结果和叠加图刷到界面上。三段之间用有界队列隔开队列满了就丢旧图并计数报警宁可丢帧也不能让队列无限涨。private readonly BlockingCollectionHObject _queue new BlockingCollectionHObject(8); // 处理线程主循环单线程跑算子避免多线程争抢 HALCON 内部资源 private void WorkerLoop(CancellationToken token) { foreach (HObject image in _queue.GetConsumingEnumerable(token)) { try { ToolResult result _engine.Run(image); // 流程引擎内部串行执行各工具 BeginInvoke(new Action(() ShowResult(result))); // 结果回 UI跨线程必须 Invoke } catch (Exception ex) { _logger.Error(process failed, ex); // 异常一定要落日志别吞 } finally { image.Dispose(); // HObject 是句柄资源必须显式释放 } } }队列容量设成 8 是根据节拍来的单张处理耗时 60ms、来图间隔 80ms 的产线队列基本是空的如果处理耗时超过来图间隔队列会很快涨满这时候要做的是优化算法或者降分辨率而不是把队列调大。把队列从 8 调到 800 只是把问题推迟半小时然后一次性爆内存。image.Dispose()这一行不能省。HObject 背后是非托管内存靠 GC 回收的话跑一晚上能把 8G 内存吃干净这是新手最容易踩的坑之一。4. 检测算子与结果判定定位、测量、缺陷三条腿怎么串成一条流4.1 定位先行CreateShapeModel 到 FindShapeModel 的参数怎么定多相机多工位的系统里几乎所有的测量和缺陷分析都依赖一个前提工件位置是已知的。所以流程的第一步永远是模板匹配定位后续所有 ROI 都跟着定位结果做仿射偏移而不是写死坐标。模板的创建一般放在“示教”模式里做一次把 ModelID 序列化保存正式跑的时候直接读不要每张图都重新建模板。// 示教从一张合格品图像里抠出定位模板参数存盘 public void Teach(HObject teachImage, HObject roiRegion) { HOperatorSet.ReduceDomain(teachImage, roiRegion, out HObject modelImage); // 金字塔层数给 auto角度 ±30°对比度阈值 auto 让 HALCON 自己挑边缘 HOperatorSet.CreateShapeModel(modelImage, auto, -0.52, 0.52, auto, auto, use_polarity, auto, auto, out HTuple modelId); HOperatorSet.WriteShapeModel(modelId, locate_chip.shm); HOperatorSet.ClearShapeModel(modelId); } // 运行在整幅图里找模板取分数最高的一处作为定位基准 public ToolResult Locate(HObject image, HTuple modelId) { HOperatorSet.FindShapeModel(image, modelId, -0.52, 0.52, 0.7, 1, 0.5, least_squares, 0, 0.8, out HTuple row, out HTuple col, out HTuple angle, out HTuple score); if (score.Length 0 || score.D 0.7) return new ToolResult { Ok false }; // 没找到就整张图判 NG不要带着错位置往下跑 return new ToolResult { Ok true, Row row, Col col, Angle angle, Values new[] { score.D } }; }几个参数的经验值AngleStart和AngleExtent用弧度±30° 是-0.52和1.05MinScore现场一般给 0.60.75给到 0.9 会因为光照波动频繁误判漏检NumMatches设 1 还是多值看工件一个视野多个件的场合要用多值再按行列排序Greediness给 0.80.9给 1.0 最快但可能漏掉给 0.5 慢一倍。SubPixel用least_squares比interpolation精度高代价是耗时增加 20% 左右精密测量场合值得。定位分数低于阈值时正确的处理是整张判 NG 并报警绝不能带着一个错误的位置继续做测量否则下游全是垃圾数据。定位之后用AffineTransContourXld或直接对 ROI 做AffineTransRegion把每个工具的名义坐标变换到实际位置。这一步做扎实了后面的测量就简单了。4.2 缺陷与尺寸Blob 分析和卡尺测量的组合缺陷检测用 Blob 分析的组合拳阈值分割、连通、按面积和形状筛选、统计。核心参数是灰度区间和面积上下限这两个值必须拿真实样片反复试不能拍脑袋。public ToolResult BlobDefect(HObject image, HObject roi, double grayMin, double grayMax, int minArea, int maxArea) { HOperatorSet.ReduceDomain(image, roi, out HObject reduced); HOperatorSet.Threshold(reduced, out HObject region, grayMin, grayMax); // 先做一次形态学开运算去掉噪点和毛刺否则面积全是碎片 HOperatorSet.OpeningCircle(region, out HObject cleaned, 1.5); HOperatorSet.Connection(cleaned, out HObject connected); HOperatorSet.SelectShape(connected, out HObject defects, area, and, minArea, maxArea); HOperatorSet.CountObj(defects, out HTuple count); return new ToolResult { Ok count.I 0, // 检出即 NG Values new[] { (double)count.I }, DebugRegion defects }; }OpeningCircle的半径 1.5 像素是个万能起点能干掉单像素噪声又不会吃掉真实的小缺陷SelectShape的area配合and是闭区间参数字符串顺序不能反。面积上限别忘了设否则一大片反光会被当成一个巨型缺陷产线上一天能报几百次假 NG。尺寸测量用卡尺Caliper比 Blob 的边界定位精度高一个量级因为它沿垂直于边缘的方向做一维灰度投影能找到亚像素边缘。// 在定位结果给出的位置生成测量矩形沿 Phi 方向找一对边缘并算间距 HOperatorSet.GenMeasureRectangle2(row, col, phi, len1, len2, imgW, imgH, nearest_neighbor, out HTuple handle); // Sigma 平滑尺度Threshold 边缘幅值阈值Transition 选 all/positive/negative HOperatorSet.MeasurePairs(image, handle, 1.0, 30, all, out HTuple r1, out HTuple c1, out HTuple amp1, out HTuple r2, out HTuple c2, out HTuple amp2, out HTuple rc, out HTuple cc, out HTuple intra, out HTuple inter); HOperatorSet.CloseMeasure(handle); double width inter.D; // 一对边缘的距离单位像素乘标定系数才是毫米GenMeasureRectangle2的len1是沿边缘方向的半长决定了平均多少个像素给大一点抗噪但会掩盖局部变形len2是垂直边缘方向的半长要略大于预期的边缘搜索范围。Sigma给 1.0 是默认值噪声大调到 1.52.0但调太大会把真实边缘也抹平。Threshold给 2040反光强的工件要往上加。inter.D拿到的是像素距离乘上标定系数mmPerPixel才是物理尺寸。标定系数用标准块规或者圆点标定板算别拿图纸尺寸反推那样量出来永远是准的因为你自己校准过。4.3 判定与输出公差带、NG 分类、PLC 握手所有工具跑完结果汇总到一个判定器里。判定逻辑本身很简单麻烦的是 NG 分类和与 PLC 的时序。public JudgeResult Judge(IListToolResult results, Spec spec) { var judge new JudgeResult { Ok true, Code 0 }; // 定位失败优先级最高直接返回后面的数据都不可信 if (!results[0].Ok) { judge.Ok false; judge.Code 100; return judge; } foreach (var r in results.Skip(1)) { if (!r.Ok) { judge.Ok false; judge.Code 200; continue; } // 测量值逐个比对公差带超上限和超下限给不同错误码方便现场分因 for (int i 0; i r.Values.Length; i) { double v r.Values[i]; if (v spec.Upper[i]) { judge.Ok false; judge.Code 301; } else if (v spec.Lower[i]) { judge.Ok false; judge.Code 302; } } } return judge; }错误码分段是有讲究的100 段是定位类、200 段是缺陷类、300 段是尺寸类。现场操作员看到屏幕上跳 301 还是 302能立刻判断是工件偏大还是偏小不用翻日志。这套编码规则比任何文档都管用。PLC 握手一般用三个信号InPosition工件到位、ResultReady结果就绪、ResultOk结果值。时序上必须做到置位ResultReady之后保持到收到 PLC 的Ack信号才复位否则 PLC 扫描周期和软件线程不同步时会丢结果。这个握手逻辑写成状态机比散落在各处的if可靠得多。5. 部署与运行避坑五条现场翻车记录和它们的解法5.1 启动阶段环境与授权现象程序在开发机上跑得好好的拷到现场工控机上双击就报“无法加载 DLL halcon”或“找不到指定的模块”。原因一是平台目标设成了 AnyCPU在 64 位系统上跑起来找 32 位库二是 HALCON 的运行时 DLL 没跟着发布只拷了 exe三是运行环境变量里没有 HALCON 的 bin 目录。解决平台目标固定 x64把 halcon.dll、halconcpp.dll、halcondotnet.dll 等运行时文件随程序一起发布到输出目录安装 HALCON 运行环境Runtime而不是完整的开发环境。这三件事做完99% 的启动失败就没了。现象程序能起来一调算法就抛异常提示授权无效。原因开发授权和运行授权是两套东西。开发机上有加密狗或者开发授权文件现场机器上没有或者授权文件对应的 HALCON 版本和引用库版本差了半代。解决现场机器需要部署对应的运行授权并且版本严格对齐。发布前在干净的机器上做一次冒烟测试别等到产线停机才发现。5.2 运行阶段算法打滑与结果跳动现象模板匹配偶尔完全找不到分数掉到 0.3 以下下一张图又正常。原因八成是光照漂移或工件表面状态变化油污、划痕、反光。也有可能是模板本身选得不好抠的 ROI 里包含了背景边缘背景一动模板就跟着动。解决模板 ROI 只包含工件上形状最独特、最不容易变化的那块结构别贪多。角度范围收到实际可能的范围范围越大匹配越慢也越容易误匹配。光照问题要从光源上解决算法上可以加一层Emphasize增强边缘或者改用CreateScaledShapeModel应对尺寸批次差异。现象尺寸测量值整体偏了 0.02mm但重复性很好。原因标定系数过期了。镜头换过、工作距离调过、相机重新装过标定系数没更新。解决把标定系数写进配置文件的方案里和型号绑定每次换镜头或调整工作距离必须重标。我一般还会在系统里放一个“标定检查”按钮用标准件跑一遍看偏差超过阈值就报警不让开工。5.3 长跑阶段内存与线程现象程序跑三四个小时以后越来越慢最后卡死或崩溃。原因HObject 和 HTuple 里的句柄没有主动释放全压在 GC 的终结器队列里或者 DebugRegion 在正式跑的时候还留着每张图都存一份调试区域。解决所有 HObject 用完立刻Dispose()HWindow、HMeasure、HShapeModel这类句柄资源用using或者显式Clear正式模式把调试区域和叠加显示关掉只在 NG 时生成一张存档图。上线前用 2000 张历史图做一次连跑压测看内存曲线是不是平的这个习惯能省掉很多半夜的电话。6. 二次开发改造把固定流程改成可编排的检测方案拿到这套代码之后我做的第一件事通常是把流程引擎从“写死的顺序调用”改成“按配置串工具”。改造点很小IVisionTool加一个DependsOn属性引擎按依赖关系拓扑排序执行每个工具的Run拿到的是上游工具的ToolResult。这样“定位 → 测量 → 缺陷”和“定位 → 缺陷 → 测量”只是配置顺序不同代码一行不用改。第二步是给工具加一个Clone()支持在方案编辑器里复制粘贴检测项。视觉项目的参数调试极其耗时操作员经常要“把这个测量项复制一份位置偏移 5mm 测下一个引脚”没有克隆功能的话每个引脚都要手工填一遍参数十个引脚就是十倍的调试时间。第三步是加离线回放。工程里保留一个06.Tests目录把生产的 NG 图批量导进去改完参数跑一遍看命中率变化。这一步比任何单元测试都值钱因为视觉算法的“正确”是统计意义上的单张图对不代表批量对。改造点改动量收益流程依赖编排引擎层加 50 行换检测项不用改代码工具克隆每个工具加 5 行多工位调试时间减半离线批量回放独立控制台工程参数调整有数据支撑NG 图自动归档结果层加 20 行客诉时能立刻复现改造完必须验证。我做验证的方式很土但有效拿 30 片标准件连续跑三遍每次记录同一测量项的极差极差不超过公差带的 1/10 才算过关然后拿 30 片已知 NG 的件跑看漏检和误判。这套流程走下来参数才敢往产线上放。从那以后我每次接新的视觉项目都先把采图、定位、测量、判定这四段拆成独立接口再动手写算子哪怕项目再小也这么干。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询