Halcon与C#联合编程:基于Blob分析的硬币识别系统实战

发布时间:2026/9/15 13:37:36

Halcon与C#联合编程:基于Blob分析的硬币识别系统实战 手里的几枚硬币混在一起想用代码识别出面值并自动统计总额这大概是很多接触机器视觉的入门者都动过的心思。我自己当初也是从“Halcon和C#联合编程”这个组合开始做视觉项目的核心流程很简单Halcon负责图像处理和特征分析C#负责界面、业务逻辑和结果展示两者通过Halcon提供的.NET接口打通。这个项目虽然是个人练习但麻雀虽小五脏俱全它覆盖了图像采集、预处理、Blob分析、特征提取、分类识别、界面开发、异常处理一整条视觉项目开发链路做一遍下来机器视觉上位机开发的基本功基本就磨出来了。这篇文章我就把这个入门练习完整拆一遍从环境搭建、算法设计、C#集成到问题排查把我踩过的坑、试过的方案、优化过的细节全部写出来。不管你是刚接触Halcon的新手还是想系统学习视觉项目开发流程的在职开发者这篇文章都值得你从头到尾走一遍。1. 项目概述与整体设计思路1.1 这个项目到底在解决什么问题硬币识别看起来简单实际上它是一道非常经典的机器视觉入门综合题。核心任务是把一张包含多枚硬币的图片自动识别出里面每一枚硬币的位置、面值并在界面上直观展示出来。这里面包含的视觉技术点其实很典型先把彩色图像转成灰度图再通过阈值分割把硬币和背景分开接着是形态学处理去掉杂质然后用连通域分析找出每一个独立的硬币区域最后通过面积、圆度、直径这些特征来区分不同面值。从工程角度来说这个项目又涉及另一个重要问题算法不能只停留在Halcon的强大脚本环境里必须能够被C#上位机调用并且把处理结果以友好的方式展示给用户。这就引出了Halcon和C#联合编程的核心——两个不同语言环境之间的数据传递、接口调用、控件嵌入、界面刷新。本质上你做完这个项目就完成了一个最小可用版本的机器视觉检测系统以后再接工业相机、加运动控制、扩展深度学习检测模块都是在这个框架上做加法。所以我在设计这个项目的时候刻意把它分成两条线一条是图像算法线在Halcon里完成算法验证并导出为C#代码另一条是软件工程线在Visual Studio里搭建WinForms界面以Halcon提供的窗口控件显示图像和结果。两条线汇合的地方就是整个项目最值得学习的地方——Halcon和C#到底怎么“对话”。1.2 为什么选择Halcon C#而不是纯HDevelop开发很多初学者会出现一个疑问既然Halcon自带的HDevelop环境那么强写算法、看图像、调参都非常方便为什么还要费劲和C#联合编程这个问题的答案正是这个项目存在的意义。HDevelop本质上是一个“算法实验室”它的定位是让你快速验证思路、调整参数、确认处理流程但它不是一个能给最终用户使用的软件框架。你想做一个真正能交付的上位机程序需要有登录界面也好、参数配置界面也好、用户交互也好还需要连接数据库或者PLC这些都不是HDevelop擅长的领域。C#在Windows桌面应用开发领域非常成熟WinForms也好、WPF也好控件丰富、开发效率高与Halcon的.NET接口配合很稳定所以视觉行业里“Halcon做视觉算法C#做上位机应用”是一个非常主流的组合。我在项目前期做了选型对比如果选择纯HDevelop优点是开发调试快缺点是没法做成独立软件如果选择C# OpenCV虽然完全免费但需要自己实现大量图像算法开发周期长对精度要求高时调参很痛苦选择Halcon C#联合编程前期环境配置有些繁琐但算法验证效率极高、图像算子功能完整后续接海康、大恒等工业相机也都有现成的SDK示例。对比之下Halcon C#是短期学习和长期项目落地都最稳的一个组合。1.3 技术选型与整体架构这个项目我建议使用以下技术组合组件推荐选择说明开发环境Visual Studio 2019/2022Community版本即可视觉算法库Halcon 19.11 / 21.05及以上注意安装时选择对应.NET版本界面框架WinForms上手快Halcon控件集成简单目标平台x64必须与Halcon位数一致语言版本C# 7.0及以上使用较新语法但不需要太激进整体架构上我的设计思路是这样的底层是Halcon算法模块负责图像读取、预处理、硬币检测和特征分类中间层是C#封装的识别服务类把Halcon算子封装成一个一个的方法比如LoadImage、DetectCoins、GetStatistics上层是WinForms界面通过按钮触发识别用HWindowControl显示图像和识别结果用DataGridView展示硬币明细和总额统计。这个分层的好处很直接算法和界面解耦你在HDevelop里优化算法参数时不会影响到界面代码反过来改界面布局也不会碰坏算法逻辑。后续如果你想换算法库或者增加新的识别功能只需替换中间层上层几乎不用动。这虽然不是多么高深的架构设计但在个人入门项目里提前建立分层意识对以后做复杂系统帮助很大。2. 环境准备与开发工程搭建2.1 Halcon安装与License配置我最初在环境配置上花掉了大半个晚上所以这部分我必须放在最前面提醒Halcon安装本身不复杂真正的坑全在License和版本匹配上。Halcon安装包从官网下载对应版本的安装程序安装过程基本一路下一步。安装完成后你会在开始菜单里找到HDevelop开发环境。关于LicenseHalcon提供了试用License申请后会得到一个扩展名为.dat的授权文件。新手最容易在这里出问题安装完成后运行HDevelop如果提示License无效或者缺少相应组件一般有两种原因——一是License文件没有放到指定目录二是安装时选择了不匹配的组件。正确的做法是安装完成后打开Halcon的License Manager工具把.dat授权文件导入进去然后重启HDevelop验证。如果你装了Halcon深度学习相关工具但License不包含该模块运行时同样会报错社区版或试用版License通常只包含基础图像处理算子深度学习推理那一块是单独的授权模块入门期用不到就不用管。另外一个容易被忽视的问题Halcon的64位版本和32位版本的运行时环境是独立的如果你在64位系统上默认安装了64位Halcon那么C#工程的目标平台必须显式设为x64不能使用默认的AnyCPU。我一开始就是因为没注意这个程序一跑就报“试图加载格式不正确的程序”排查了半天才发现是位数不匹配。2.2 C#工程创建与Halcon引用设置在Visual Studio里新建一个Windows窗体应用项目工程名就叫CoinRecognition。创建好之后最关键的一步是把Halcon的.NET接口程序集引入到项目中来。在解决方案资源管理器里右键“引用”选择“添加引用”在弹出的管理器里点击“浏览”然后进入Halcon安装目录下的bin文件夹——需要说明的是不同版本这个文件夹位置有差异我用的版本中路径大概是D:\Program Files\MVTec\Halcon-21.05\bin\dotnet35或者D:\Program Files\MVTec\Halcon-21.05\bin\dotnet45选择halcondotnet.dll这个文件添加进去。如果你运行的是Halcon 13以上版本运行时还需要把Halcon的bin目录下的原生DLLhalcon.dll、hcanvas.dll等复制到C#程序输出目录最简单的方式是在工程属性里把平台改为x64然后把这些DLL复制到Debug目录中或者直接把bin目录配置到系统PATH环境变量。我第一次没复制原生DLL调试时一直报找不到halcon.dll的错差点怀疑是安装有问题。引用了halcondotnet.dll之后工具箱里会多出一个Halcon的控件组其中HWindowControl就是我们要用的图像显示窗口。你只需要像拖普通按钮一样把它拖到窗体上就行。2.3 Halcon与C#联合编程的常用模式这里我要展开说一下Halcon与C#结合的三种模式因为很多教程直接就跳进代码了没讲清楚这三者区别导致新手后期非常混乱。第一种是HDevEngine动态调用模式C#程序嵌入Halcon的脚本引擎直接加载并执行你写好的.hdev脚本或过程文件。它的优点是算法调整时只需修改HDevelop脚本不需要重新编译C#工程调试速度快缺点是脚本执行效率略低变量传递比较麻烦而且最终交付时要带上脚本文件。第二种是导出C#代码模式在HDevelop里完成算法开发和调参后通过“文件-导出”功能把整个脚本导出成C#代码文件然后复制到C#工程里直接调用。这是最直观、最适合入门的一种方式因为导出的代码结构清晰算子调用一一对应你能清楚地看到每一步图像处理在C#里长什么样。缺点是后续改算法后要手工重新导出同步。第三种是封装DLL模式把HDevelop脚本封装成一个C#类库项目对外暴露几个方法供主程序调用。这种方式工程化和复用性最好最接近实际商业项目里的做法但前期搭建工作量也最大。考虑到这是个人入门练习我的建议是先用第二种“导出C#代码模式”把整个流程跑通熟悉之后再去学HDevEngine动态调用后面真正做项目时再考虑封装DLL。我用一张简洁的对比表说明这三种模式你按需选型模式开发效率部署复杂度适用场景HDevEngine动态调用高改脚本即生效需携带脚本文件频繁调试算法的原型验证导出C#代码中改算法需重导出全部编译进EXE入门学习算法相对定型封装DLL低初期建设成本高最清洁接口稳定商业项目、团队协作开发3. 图像采集与预处理细节3.1 图像获取本地读图与相机采集入门练习阶段我强烈建议先用本地图片代替工业相机把整套流程跑通再考虑接硬件。原因很简单相机采集涉及SDK初始化、曝光参数设置、帧率控制、缓冲区管理等问题这些对入门项目来说属于额外复杂度而且没有硬件时根本没法调试。在Halcon里读取一张本地图片的算子非常简单read_image (Image, coins.png)但要注意Halcon的图片路径支持相对路径和绝对路径相对路径的基准是当前工作目录。在HDevelop里测试时基准是HDevelop安装目录下的bin文件夹经常出现路径对不上导致读取失败。我建议在HDevelop里开发时直接用绝对路径到了C#工程里再用Path.Combine获取图片所在目录这样两边都不容易出问题。如果你确实想体验相机采集Halcon有通用的接口比如如果你用海康相机先安装厂商SDK然后在Halcon里用信息码方式打开相机open_framegrabber (GigEVision2, 0, 0, 0, 0, 0, 0, default, -1, default, -1, false, Camera1, , 0, -1, AcqHandle) grab_image_start (AcqHandle, -1) grab_image (Image, AcqHandle)不过个人练习阶段接相机会牵扯到GigE网口配置、相机IP设置这些硬件知识先用本地图片把核心算法和界面业务打通是性价比最高的学习路径。3.2 图像预处理从彩色图到干净的二值图硬币识别的第一个关键环节是预处理。标准的工业视觉流程是彩色图转灰度图再做阈值分割然后形态学处理。在Halcon里彩色转灰度用rgb1_to_gray这一步是为了减少数据量、简化后续处理。如果硬币和背景在灰度空间里对比度不够比如背景本身就有很多类似硬币的圆形图案可以在阈值化之前加一步增强比如scale_image或者更高级的emphasize算子。接下来是阈值分割。硬币检测最常用的是全局阈值HDevelop里通过threshold算子实现threshold (GrayImage, Region, 0, 90)这里我没法给你一个万能的阈值区间因为硬币的实际灰度值取决于你的打光方案、硬币新旧、背景颜色。这也是为什么很多机器视觉初学者觉得阈值分割难——它本质上是在找“石头剪刀布”的平衡点。在实际项目中我会在界面上加一个“阈值调节”的滑动条实时显示分割效果这样比在代码里反复改数字试错高效得多。阈值分割之后你会得到一块块二值区域。刚分割出来的区域往往会有很多细小噪声点比如硬币上的污渍被分成了碎片或者背景里的细小物体被误分割进来。这时候就要用形态学处理来清洗。最常见的做法是用开运算去掉小的噪点区域用闭运算填补硬币内部的小空洞opening_circle (Region, RegionOpening, 3.5) closure_circle (RegionOpening, RegionClosing, 3.5)需要提醒的是所谓“清晰干净的二值图”并不是所有情况下都越干净越好。开运算的半径如果设得太大会把硬币边缘磨掉一圈进而影响后续直径测量的精度。所以形态学结构元素的大小一定要根据硬币在图像中的尺寸比例来选先量一下硬币区域大概多大再选1/10左右的圆形半径。4. 硬币识别的核心算法实现4.1 Blob分析把每个硬币单独拎出来预处理完成后图像里剩下的就是一些高亮区域这些区域里可能包含多个硬币也可能包含干扰物体。下一步要做的就是把连在一起或靠得很近的像素块拆分成一个个独立的区域术语叫连通域分析Halcon里的核心算子是connectionconnection (RegionClosing, ConnectedRegions)它的原理很好理解把相邻像素看成连通的连成一片的算一个区域哪怕几个硬币紧挨着只要边缘没有真正粘连就能被拆成不同的连通域。但实际场景中硬币之间往往会有接触甚至部分重叠这样它们在二值图上是连成一大块的connection根本分不开。这个问题的工业级解法比较复杂要用到分水岭(watersheds)或者基于距离变换的分割算法。作为入门练习我们先用一个物理层面的小技巧拍摄时让硬币之间尽量分散减少接触概率。这是最务实的做法很多教科书不讲但实际项目中打光、摆盘本来就是设计方案的一部分。连通域拆出来以后需要用形状特征筛选出真正的硬币区域。硬币的基本特征很明确接近圆形面积在一个合理范围内。对应到Halcon算子select_shape (ConnectedRegions, SelectedRegions, [area, circularity], and, [2000, 0.75], [99999, 1.0])这里circularity是圆度1.0表示完美圆一般硬币在0.75以上就算比较圆了。面积参数需要根据你的图像分辨率调整先对整幅图算一下每个硬币大概占多少像素再设上下限。如果筛选后还有杂质混进来我用的土办法是把结果区域叠加到原图上用肉眼确认剔除的到底是硬币还是噪声这样调参方向就明确了。4.2 面值判断用直径和面积做分类硬币的识别核心其实在分类。不同面值的硬币直径不同所以最直观的特征就是外接圆直径。Halcon里有现成算子smallest_circle可以计算每个区域的质心坐标和最小外接圆半径smallest_circle (SelectedRegions, Row, Column, Radius)Radius就是半径的像素值直径等于2 * Radius。拿到直径后就可以按预先设定的阈值区间来区分硬币面值。这里有个关键细节我要特别讲清楚像素直径并不等于物理尺寸它受相机分辨率和工作距离影响。同一枚硬币放在距离镜头远的地方在图像里像素直径就小放到近处就大。所以如果要从像素直径直接换算成物理直径必须做像素标定通常在固定高度下拍摄一张已知尺寸的标准件算出每毫米对应多少像素。在入门项目里为了简化有两种处理思路一是固定拍摄距离把这套系统当成“同一个高度条件下”的专用检测装置用一张已知面值的硬币图片来标定像素比例然后按像素直径直接分类。二是用相对特征不关心绝对直径而是用面积占比、圆度差异等其他特征作为分类依据。我做这个项目时选了第一种因为直接、直观也更贴近真实项目中“建立检测工位”的思维模式。分类阈值可以用Halcon的class_2d_sup或者直接写if - else判断。我推荐在C#里做判断因为分类逻辑属于业务逻辑在界面上修改阈值区间也更方便。比如1元硬币的直径约25毫米5角硬币约20.5毫米1角约19毫米如果你标定了像素比例就能在C#里写类似的判断if (diameterMm 23 diameterMm 27) type 1元; else if (diameterMm 18.5 diameterMm 22) type 5角; else if (diameterMm 17 diameterMm 20) type 1角;注意这些标准直径会因硬币版本不同有细微差异实际项目里要根据你所在地流通的硬币版本建立标准样本库用真实样本测量后填入阈值不能照搬网上数据。4.3 HDevelop脚本全流程与导出C#代码在HDevelop里把上述流程串起来完整脚本大概长这样read_image (Image, coins.png) rgb1_to_gray (Image, GrayImage) threshold (GrayImage, Region, 0, 90) opening_circle (Region, RegionOpening, 3.5) connection (RegionOpening, ConnectedRegions) select_shape (ConnectedRegions, SelectedRegions, [area, circularity], and, [2000, 0.75], [99999, 1.0]) smallest_circle (SelectedRegions, Row, Column, Radius)调试通过后在HDevelop里选择“文件-导出”语言选C#导出后你会得到一个.cs文件。打开它你会发现Halcon把每个算子都转成了HOperatorSet类的静态方法调用返回的图像对象都是HObject类型数值结果都是HTuple类型整体结构非常直白。把导出的代码复制到C#工程里后建议把核心逻辑攒成一个方法方便界面调用。示例代码大致如下private void DetectCoins(string imagePath, out HObject displayImage, out Liststring coinLabels, out int totalCount) { HObject ho_Image, ho_GrayImage, ho_Region, ho_RegionOpening; HObject ho_ConnectedRegions, ho_SelectedRegions; HTuple hv_Row null, hv_Column null, hv_Radius null; HOperatorSet.GenEmptyObj(out ho_Image); HOperatorSet.GenEmptyObj(out ho_GrayImage); HOperatorSet.GenEmptyObj(out ho_Region); // ... 各对象初始化 HOperatorSet.ReadImage(out ho_Image, imagePath); HOperatorSet.Rgb1ToGray(ho_Image, out ho_GrayImage); HOperatorSet.Threshold(ho_GrayImage, out ho_Region, 0, 90); HOperatorSet.OpeningCircle(ho_Region, out ho_RegionOpening, 3.5); HOperatorSet.Connection(ho_RegionOpening, out ho_ConnectedRegions); HOperatorSet.SelectShape(ho_ConnectedRegions, out ho_SelectedRegions, area, and, 2000, 99999); HOperatorSet.SmallestCircle(ho_SelectedRegions, out hv_Row, out hv_Column, out hv_Radius); // 根据 hv_Radius 计算直径并分类 // 依次释放 HObject 对象 ho_Image.Dispose(); ho_GrayImage.Dispose(); // ... }这里我要特意强调一个新手极其容易踩的坑Halcon的.NET接口里HObject对象是非托管资源的封装如果只创建不释放跑不了几十次程序就会内存暴涨最终报出内存不足。正确做法是每个用过的HObject在不再需要后都调用Dispose()。我在代码里养成了一个习惯所有图像算法都写到一个方法里最后统一释放中间异常路径也尽量用try-finally包裹避免早退导致资源泄漏。4.4 用HDevEngine动态加载脚本模式的替代方案做完上面的导出代码方案后你可以进一步了解一下HDevEngine模式因为在以后实际工作中这个模式非常常用。它允许你在C#里直接加载HDevelop脚本然后像调用函数一样执行Halcon代码。做法是在C#工程里引用hdevenginedotnet.dll然后创建HDevEngine对象加载脚本程序。示例逻辑如下HDevEngine engine new HDevEngine(); engine.SetProcedurePath(D:\HalconScripts); HDevProcedure procedure new HDevProcedure(coin_detect); HDevProcedureCall call procedure.CreateCall(); call.SetInputIconicParamObject(Image, ho_Image); call.Execute(); HObject resultRegion call.GetOutputIconicParamObject(ResultRegion);这个模式的优缺点我在前面表格里已经列过了这里补充一个实际体会如果你用脚本模式HDevelop脚本里必须把变量定义清楚尤其是输入输出参数否则C#端根本不知道如何传递数据。建议在HDevelop脚本最上面用*注释写明这个过程的输入是什么、输出是什么方便自己在C#端对照调用。5. 界面开发与结果展示5.1 主界面布局与HWindowControl控件的使用C#界面是这个项目的门面也是初学者最容易忽视的地方。很多人觉得界面就是个壳子算法跑通就行但实际开发中你会发现一个布局合理、反馈清晰的上位机界面对于调试效率和用户体验的影响是决定性的。我的界面设计是这样安排的窗体左侧放一个HWindowControl作为图像显示区占主体面积右侧放一个按钮面板依次排列“打开图片”、“开始检测”、“清空结果”三个按钮下方用一个ListView或DataGridView表格用来显示识别出的每枚硬币的序号、面值、直径、中心坐标底部的状态栏显示识别总数和金额合计。HWindowControl是Halcon自带的控件你可以直接把它当普通控件拖到窗体上。往窗口里显示图像的基本写法是HOperatorSet.ReadImage(out ho_Image, imagePath); HOperatorSet.DispObj(ho_Image, hWindowControl1.HalconWindow);如果在显示前不清理窗口内容新旧图像会叠加在一起看起来非常脏。所以每次显示前要清理hWindowControl1.HalconWindow.Clear(); hWindowControl1.HalconWindow.SetPart(0, 0, height, width); hWindowControl1.HalconWindow.DispObj(ho_Image);SetPart算子是很多人不知道但很有用的一个设置它定义了显示窗口的坐标系。默认情况下窗口显示范围可能被拉伸用了SetPart设置成图像的实际大小后能保证图像按正确宽高比显示。5.2 识别结果的叠加可视化只把原图显示出来还不够识别结果的可视化才是亮点。你要做的是在图像上把每枚硬币的外接圆画出来并在旁边标上面值和序号。在Halcon里可以用disp_circle和disp_message这两个算子HOperatorSet.DispCircle(hWindowControl1.HalconWindow, hv_Row[i].D, hv_Column[i].D, hv_Radius[i].D); string msg $硬币{i 1}: {coinType}; HOperatorSet.DispMessage(hWindowControl1.HalconWindow, msg, image, hv_Row[i].D - 20, hv_Column[i].D, black, true);disp_message在HDevelop里很常用但在C#里有些版本的接口调用要先设置颜色和字体。如果你在C#调用时发现文字显示不出来可以先这样设置HOperatorSet.SetColor(hWindowControl1.HalconWindow, green); HOperatorSet.SetLineWidth(hWindowControl1.HalconWindow, 3);画完之后还要注意一个顺序问题先DispObj显示原图再在同一个窗口上叠加画圆和文字。如果顺序反了原图会覆盖掉你画的标记看你结果时总觉得像是啥都没画上。5.3 表格统计与总额计算识别不是终点把结果显示成表格和汇总金额才能体现出这套程序的实用价值。我用ListView来展示每枚硬币明细因为ListView的列设置很灵活每行可以放多个字段。具体做法是遍历识别出的每一枚硬币往ListView里添加一行同时累加金额。这一步逻辑虽然简单但有一个细节值得分享在界面线程里直接执行Halcon算法会导致界面卡顿如果图片分辨率高、硬币数量多点击按钮后界面会“假死”十几秒。这是因为Halcon运算跑在了UI线程上阻塞了界面消息循环。优化的方式是使用Task.Run把检测放到后台线程等结果返回后再通过Invoke更新界面。这也是我强烈建议入门阶段就把异步思想加进去的原因后期接相机做连续检测时这套异步模型就是基础。private async void btnDetect_Click(object sender, EventArgs e) { btnDetect.Enabled false; listView1.Items.Clear(); await Task.Run(() DetectCoins(imagePath, out resultImage, out labels)); ShowResult(resultImage, labels); btnDetect.Enabled true; }6. 常见问题与排查技巧实录6.1 运行时报错与DLL问题速查表做联合编程最让人头疼的往往不是算法本身而是一堆莫名其妙的运行时报错。我把实操中最常遇到的几类问题整理成速查表遇到类似情况可以照着排查。报错现象根本原因解决方案初始化Halcon失败License错误License未正确配置或模块不匹配用License Manager重新导入确认功能模块包含基础图像处理找不到halcon.dll原生DLL未随程序部署把Halcon bin目录下的dll复制到Debug目录或用PATH环境变量指向该目录试图加载格式不正确的程序C#工程是AnyCPU或x86Halcon是x64工程平台目标改为x64HObject对象内存持续增长创建的HObject没有Dispose用完立即Dispose用try-finally确保异常路径也释放DispObj后窗口黑屏窗口未Clear或HObject已释放显示前Clear窗口确保HObject生命周期覆盖显示过程导出的C#代码中HTuple索引越界部分硬币特征不合格被过滤掉判断HTuple长度空结果单独处理我印象最深的是一次HOperatorSet.SmallestCircle返回的hv_Radius长度和预期不一致。排查后发现是因为select_shape过滤掉了部分硬币造成后续分类代码里按固定索引访问hv_Radius时报越界。从那以后我每做一步筛选都会先判断结果长度再往后执行这个习惯至今受用。6.2 识别误判与漏判的定位方法阈值分割参数不合理是硬币检测误判漏判的最主要来源。灰度阈值设得太高硬币和背景分离不彻底硬币区域出现空洞后续特征提取的面积就偏小阈值设得太低背景里的杂质也被分割进来了圆度筛选都救不回来。定位这类问题我的经验是“分层检查法”先用present_region检查阈值分割结果与原图的叠加效果确认硬币区域是否完整再查看连通域分析后的区域数量是否和实际硬币数量一致最后看到特征筛选后被保留下来的区域列表逐条确认筛掉的到底是什么。这样一层层往下走很快就能定位是哪个环节出了问题。还得提一个很多教程不会提到的问题非均匀光照。如果你在自然光下拍摄硬币图像边缘大概率有阴影全局阈值很难同时兼顾亮区和暗区的硬币。遇到这种情况可以用局部阈值分割算子local_threshold替代全局threshold它根据每个像素邻域的自适应灰度来决定分割结果对光照不均匀的图像鲁棒性更好。不过算子的几个参数需要耐心调整入门时可以先固定光照环境用光源把硬币区域打均匀再考虑动态阈值。6.3 多硬币粘连与边缘残缺的情况处理真正的硬币识别场景里硬币可能会两两紧挨着甚至边缘有残缺。边缘残缺并不难处理形态学闭运算可以填充细小的缺口但如果缺口太大比如硬币少了一大块开运算和闭运算都救不回来圆度特征会明显降低此时可以降低circularity阈值限制或者改用其他特征辅助判断。硬币粘连才是最麻烦的。当两个硬币紧贴在一起二值化后就是一个大区域connection算子对它们无能为力。工业上处理粘连目标一般先把区域用距离变换转成灰度图再用分水岭算法分割。Halcon里最直接的粘连分割流程是对区域做distance_transform得到距离图然后对距离图做分水岭分割watersheds再求分割结果和原区域的交集。具体代码如下distance_transform (RegionClosing, DistanceImage, octagon, 1, 30) watersheds (DistanceImage, Watersheds, WatershedsNumber) intersection (Watersheds, RegionClosing, DividedRegions)但我要坦诚地说分水岭算法效果不是百分百稳定尤其是硬币重叠面积较大时分割线会歪斜切出来的一半区域形状并不完整分类结果也会受影响。入门阶段我更推荐从物理上避免拍之前把硬币拨开让它们相互分离这也是实际工业项目中常见的做法——通过震动送料器把零件一个一个分离开。做视觉项目永远要记得光学设计和机械结构上的处理往往比纯算法解决更可靠。7. 最后的实际体会做这个硬币识别项目我自己最大的收获不在某一单个知识点上而在于完整地体验了从算法验证到软件交付的整个闭环。你会发现HDevelop里几十行脚本跑通只是第一步真正让程序“可用”还要在C#里处理资源释放、界面异步、异常分支、路径管理一大堆琐碎问题。这些东西教程不会替你解决只有自己踩一遍才会长记性。给你两个实操建议第一保存一份正确的HDevelop脚本随时用它来验证图像算法不要直接在C#里搓HOperatorSet的代码那样效率太低了第二专门建一个Result输出文件夹把每次处理的图片、中间结果图、识别统计都存下来这是强大的调试资产比任何日志都直观。如果按我的路子把这个练习做完接下来你可以尝试三个扩展方向一是接入工业相机做实时连续检测体验视频流采集与算法性能调优二是加入参数界面把阈值、圆度、面积上下限做成可配置项让操作员不用改代码就能调参三是把模板匹配加进来比如用Halcon的find_shape_model学一枚标准硬币的形状比单纯用阈值分割和特征分类更贴近许多工业检测场景。这些方向随便做一个你对整个视觉系统的理解都会再上一个台阶。
延伸阅读

更多相关文章

2026/9/15 13:37:36

ecstore 电商项目从零搭建:PHP 环境、伪静态与上线避坑

“从零搭建 ecstore 电商项目”这个事儿,我在过去几年里前前后后干了不下十次,踩过的坑比很多新手看过的教程都多。这系统是老牌开源商城,功能底子厚实,但正因为老,它对环境、对操作顺序、对某些“约定俗成”的细节特别…

2026/9/15 13:37:36

OpenGL PBO异步回读:解决glReadPixels卡顿的实战指南

先说结论:如果你在用 OpenGL 做渲染,同时又需要把 GPU 生成的像素数据拿回 CPU 侧处理,比如截图、视频编码、离屏渲染回读、OpenCV 取帧,那我强烈建议你把 PBO 异步回读当成标配。这个技术不是炫技,是实打实把帧率救回…

2026/9/15 13:57:38

文件上传漏洞从攻击到防御:绕过手法、代码审计与加固实践

做安全的这些年,如果说哪个漏洞让我觉得“看似不起眼、实际特别致命”,文件上传漏洞绝对排得上前三名。很多开发同学觉得上传功能不过就是“接收文件、存到服务器”,能有什么风险?可真出了问题,往往就是服务器直接被拿…

2026/9/15 13:57:38

TraffMonetizer与PacketStream带宽劫持解析及彻底清理指南

1. 先搞清楚:TraffMonetizer和PacketStream到底是什么1.1 它们的正常用途:把闲置带宽换成钱先说结论:TraffMonetizer和PacketStream本身并不是传统意义的木马或病毒,它们是正规注册的"共享带宽"商业项目。官网自己怎么定…

2026/9/15 13:57:38

GitHub新手入门:从建仓到协作的全流程实战指南

我这两年陆陆续续带过不少新人入门 GitHub,发现一个很有趣的现象:大部分人是被第一次的全英文报错吓退的,而不是被概念难倒。什么 “Permission denied (publickey)”“non-fast-forward”“Failed to connect to github.com port 443”&…

2026/9/15 13:52:38

Matlab电机仿真工程拆解:PMSM控制与FFRLS惯量辨识实战

简介:面向电子信息工程、计算机、数学等专业学生,这份基于Matlab的电机仿真项目资料整理了完整的源码、数据与报告,适合在课程设计、期末大作业或毕业设计中作为仿真实例与代码参考。压缩包共68个文件,以27个m脚本、25个slx仿真模…

2026/9/15 4:54:30

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/15 0:01:16

AI英语单词APP开发:自适应学习算法与移动端优化实践

1. 项目概述 作为一名在移动应用开发领域摸爬滚打多年的老手,我最近完成了一个AI英语单词APP的开发项目。这个项目将传统单词记忆方法与现代AI技术相结合,打造了一款能够智能适应不同用户学习习惯的英语学习工具。 市面上大多数单词APP都存在一个通病&a…

2026/9/15 0:01:16

Flutter与OpenHarmony结合开发手语学习APP实战

1. 项目背景与核心价值作为一名同时接触过Flutter和OpenHarmony的开发者,最近我完成了一个基于Flutter for OpenHarmony的手语学习APP实战项目。这个项目最大的特点在于实现了跨平台框架与国产操作系统深度结合的创新实践——用Flutter开发的应用能完美运行在OpenHa…

2026/9/15 0:01:16

六个月成为机器人工程师:从ROS2到SLAM的实战路径

1. 六个月的紧迫感从哪来:先搞清楚你要成为哪种机器人工程师说实话,六个月的期限并不是一个宽松的时间线。市面上任何一本正经的机器人学教材都超过五百页,ROS2的官方文档可以翻到你怀疑人生,再加上ABB、KUKA这些工业机器人厂家动…

2026/9/14 11:59:31

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/14 13:53:59

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/15 11:42:23

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码