ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

WPF+Halcon工业二维码识别实战:从相机取流到结果输出

WPF+Halcon工业二维码识别实战:从相机取流到结果输出 简介一份面向WPF开发者的Halcon二维码识别实战资源适合需要在桌面应用中集成视觉识别功能的技术人员。内容涵盖WPF界面设计、Halcon库引入与API调用以及从图像预处理、二维码定位到解码输出的完整处理思路。压缩包共233个文件大小约35.34MB以C#源码、XAML界面、PNG示例图片及DLL依赖库为主并附带配置与工程文件便于直接查看和调试。已有161人学习下载。通过该工程读者可快速掌握WPF与Halcon的混合编程方式理解二维码识别应用的模块划分与异常处理并可作为二次开发的基础模板。相较于零散代码片段这份资源提供了可运行的项目结构和较完整的演示素材能有效减少环境配置与功能联调的时间成本。 做工业视觉这行久了肯定会碰到一个需求产线上要读二维码而且要稳定读、反复读、跟PLC交互。我前阵子正好用WPF Halcon完成了一套二维码识别程序从相机取流到界面显示、结果输出全程自己搭。整个过程踩了不少坑但也积累了一套可以直接复用的方案今天把核心实现和关键细节完整拆出来。这篇文章适合谁如果你已经在用C#做上位机想在WPF里集成Halcon做二维码识别或者你被ZXing、OpenCV的识别率折磨过想试试商业算法再或者你已经装了Halcon但不懂怎么高效调它的识别接口那么这篇非常适合你。1. 为什么在WPF里用Halcon做二维码识别1.1 先说我踩过的选型弯路最开始我并没有直接上Halcon而是拿ZXing.Net做了一版。原因很简单开源、免费、集成方便NuGet一个包搞定。当时的场景是读饮料瓶盖上的DataMatrix码白底黑码印刷清晰光照还可以。ZXing表现其实还行出码率大约在85%左右。但一上产线就完蛋了。瓶盖有反光码有时被压花压掉一小块有时在流水线上有运动模糊。ZXing的识别率直线掉到60%以下而且没有好的办法告诉它“图像哪里是码”——只能整帧搜索CPU占用还高。后来我换成Halcon同样的图像默认参数下识别率就到了95%以上。差距不是一星半点尤其在二维码破损、畸变、低对比度这些工业场景里Halcon的find_data_code_2d算子确实有非常强的鲁棒性。1.2 Halcon到底强在哪你可以把Halcon的二维码识别理解为一套专门为“工业图像”优化的完整引擎。它不仅做解码内部还自己处理了定位、透视校正、二值化、模块提取这一步。这就是它和ZXing的本质区别ZXing更多是“纯解码器”而Halcon是“定位增强解码”一体。而且Halcon提供了DataCode2D模型可以精细控制最小对比度、条码形状、极性、是否允许镜像等参数。这些在干扰严重的产线环境下非常关键。1.3 WPF做界面比WinForms顺手得多我选WPF而不是WinForms纯粹是界面开发效率的问题。WPF的Canvas、Border、绑定机制做工业软件的上位机界面非常灵活。比如我要在图像上叠加显示识别框、坐标十字线、检测结果列表这些在WPF里用DrawingVisual或者简单的Path就能漂亮实现。再加上MVVM结构业务逻辑和界面分离后面接PLC、数据库、MES都很方便。还有一个原因Halcon自带的HSmartWindowControl控件是WinForms和WPF版本都有的。WPF版本可以嵌入到WindowsFormsHost中也可以直接用新版Halcon自带的WPF控件19.11之后版本对WPF集成更友好。所以在WPF里做图像显示、缩放、ROI绘制都不成问题。2. 环境准备MVTec Halcon与WPF工程的那些坑2.1 版本与License的选择Halcon目前主流版本是Halcon 17.12、19.11、20.11、21.11、22.11等。版本大版本其实无所谓二维码识别算子create_data_code_2d_model和find_data_code_2d很多年前就有了非常稳定。我自己用的是Halcon 20.11。需要特别注意Halcon的License是分机器码的不是装完就能永久用。如果没有正版狗或授权文件评估版只能试用30天重启一次每次重新获取临时license如果公司已经买了正版会有一个license.dat之类的授权文件放在Halcon安装目录的license文件夹下。这里提醒一句不要想着去网上找什么和谐文件工业项目用盗版license风险极大MVTec的授权校验不定期更新且产线设备一旦出问题连技术支持都得不到得不偿失。在WPF里调用Halcon不需要单独安装完整开发环境吗开发机上需要装Halcon但部署到产线机器时可以只放运行时runtime文件和对应dll不过为了省事很多项目会直接在工控机上装完整Halcon Development版本。我个人建议开发机装完整版部署机只拷贝运行库。2.2 工程配置x64、引用和运行时文件WPF工程默认AnyCPU但Halcon的C#接口分x86和x64我们必须在工程里显式指定x64绝大多数现代工控机都是64位。这个不设置的话运行时会报BadImageFormatException很容易排查到但第一次碰到还是会懵。你需要引用的核心DLL有三个halcondotnet.dllC#接口主程序集hdevengine.dllHDevEngine运行库如果要用HDevelop脚本hcanvas.dll图像显示画布显示图像时需要引用方式可以直接浏览到C:\Program Files\MVTec\HALCON-20.11\bin\dotnet35不同版本目录不一样有的在bin\dotnet35有的在bin\x64-win64\dotnet35。推荐用NuGet不Halcon没有官方NuGet包自己手动引用最稳妥。还有一点任何引用Halcon的WPF程序在运行时需要把halcondotnet.dll复制到输出目录。如果你忘了启动时会报“无法加载 DLL 或程序集”。解决办法是在“添加引用”后把dll的“本地复制”属性设为True或者手动拷贝到exe目录。2.3 让WPF显示Halcon图像显示图像有两种常见方案第一种用HSmartWindowControlWPF这是官方WPF控件操作最方便。在XAML中直接声明hSmart:HSmartWindowControlWPF x:NameHalconDisplay Width800 Height600 /第二种用HWindowControlWinForms控件把它放到WindowsFormsHost里WindowsFormsHost x:NameFormsHost hWindow:HWindowControl x:NameHWind / /WindowsFormsHost两种方式本质都一样是对HALCON窗口HWindow的封装。个人推荐HSmartWindowControlWPF因为支持鼠标缩放、拖动还自带SetPart管理图像显示范围不需要自己写交互逻辑。注意这两个控件在使用前需要在代码中调用HalconDotNet.HalconAPI.Initialize()一般会自动初始化但有些版本在非UI线程首次创建控件时可能出问题。3. 从图像到结果二维码识别接口的完整调用逻辑3.1 二维码模型创建与参数设定Halcon二维码识别思路非常简洁三步走创建模型、传入图像、查找解码。创建模型用算子create_data_code_2d_model第一个参数是码制。常见码制有QR CodeData Matrix ECC 200Code 128一维条码也走这个模型PDF417我这里主要用QR Code和DataMatrix两种。创建后返回一个DataCodeHandle之后所有识别操作都依赖这个句柄。句柄务必复用不要每帧都创建销毁性能差距很大。create_data_code_2d_model (QR Code, [], [], DataCodeHandle)调用后可以设置一些参数比如是否启用“模块尺寸指导”“极性检测”“镜像码”。常用设置set_data_code_2d_param (DataCodeHandle, default_parameters, enhanced) set_data_code_2d_param (DataCodeHandle, contrast_min, 30) set_data_code_2d_param (DataCodeHandle, persistence, 1)default_parameters有三个档位standard、enhanced、maximum。识别率从低到高耗时也从小到大。我一开始直接用maximum结果单帧解码要200毫秒产线上根本来不及。后来改为enhanced耗时60毫秒左右识别率几乎一样。所以要按实际场景调不是越强越好。3.2 图像预处理不要乱加网上很多教程上来就中值滤波、灰度拉伸、二值化但Halcon的读码算子里内置了比较完整的图像预处理策略。如果图像不是特别恶劣直接传原图给find_data_code_2d即可。反而预处理不当会把二维码边缘弄糊导致识别失败。什么时候需要预处理严重反光导致码区域过曝可以先用scale_image或equ_histo_image做灰度拉伸。图像太暗、整体低对比可以用emphasize增强对比度。运动模糊明显要考虑是不是相机曝光时间太大而不是在图像上做去模糊。我习惯的做法是先用一张典型的坏图在HDevelop里反复试找到最适合的预处理序列然后再把它转成C#代码。不要试图用一套预处理通吃所有工况。3.3 解码与结果输出核心算子是find_data_code_2dfind_data_code_2d (Image, SymbolXLDs, DataCodeHandle, [], [], ResultHandles, DecodedDataStrings)输出两个关键信息ResultHandles解码结果句柄。DecodedDataStrings解码出的字符串。同时输出的SymbolXLDs是二维码边缘轮廓和位置信息可以拿去做定位框叠加。如果二维码没有读出来DecodedDataStrings是空数组ResultHandles也不会包含有效结果。这时程序要做的是自动跳过该帧不抛异常。C#里调用find_data_code_2d的方式如下后面完整示例中会展示。4. 实际代码一个最小可用的二维码识别模块4.1 核心C#实现下面我直接给一个我在项目里精简过的实现包含模型初始化、灰度图转换、识别、取结果。using HalconDotNet; using System; using System.Windows; namespace QrDemo { public class QrCodeRecognizer : IDisposable { private HDataCode2D _dataCode2D; private HTuple _resultHandles; private HTuple _decodedStrings; private HObject _hoImage; private HObject _hoSymbolXlds; public QrCodeRecognizer(string symbology QR Code) { // 创建读码模型 _dataCode2D new HDataCode2D(symbology, new HTuple(), new HTuple()); // 增强模式兼顾识别率和速度 _dataCode2D.SetDataCode2dParam(default_parameters, enhanced); // 允许读取镜像码 _dataCode2D.SetDataCode2dParam(mirror, any); } public string Recognize(HObject inputImage) { _hoImage inputImage; _hoSymbolXlds new HObject(); _hoSymbolXlds.GenEmptyObj(); // 执行查找 _dataCode2D.FindDataCode2d( _hoImage, _hoSymbolXlds, new HTuple(), new HTuple(), out _resultHandles, out _decodedStrings); if (_decodedStrings null || _decodedStrings.Length 0) return string.Empty; // 一般读一个码直接取第一个结果 return _decodedStrings.SArr[0]; } public void Dispose() { _dataCode2D?.Dispose(); _hoSymbolXlds?.Dispose(); } } }这里面有两个细节要注意第一FindDataCode2d方法的重载签名要和实际引用版本一致。不同Halcon版本生成的方法可能参数类型略有区别以你IDE里智能提示为准。通常第4、5个参数是空HTuple不要传null否则容易报HALCON error #13012之类的错误。第二DecodedDataStrings有可能是HTuple里的字符串也有可能是Tuple类型.SArr是字符串数组访问器。如果只有一个条码直接取索引0。4.2 HDevelop原型转C#的两个坑很多朋友习惯先在HDevelop里写出脚本然后点“文件→导出→C#”再复制生成的代码。这个路子没问题但导出的代码很啰嗦你还需要做两步清理把HOperatorSet静态类调用里的out参数整理成C#风格。Halcon导出的代码会保留HObject和HTuple临时变量直接用就行。注意HObject和HTuple的释放。C#接口虽然会做托管层面处理但HObject底层还是非托管资源频繁创建不释放会内存上涨。建议用完的临时图像对象调用.Dispose()。另一个更隐蔽的问题是图像数据类型。find_data_code_2d接受byte灰度图。如果你从相机SDK拿到的是byte[]或Bitmap一定要转成HObject再传入。从byte[]创建HObject的常规写法public static HObject ByteArray2HObject(byte[] imageData, int width, int height) { HObject image null; HOperatorSet.GenImage1(out image, byte, width, height, imageData); return image; }这里要求imageData是连续的行数据且每行像素数为width如果BMP带行补零必须先把Stride去掉转成紧密排列的灰度数据否则图像会斜切。这也是很多新手遇到的“识别图像是斜的”原因。5. 实测中踩过的坑解码率低、崩溃和卡界面5.1 反光、污损码这样处理在一开始跑通Demo之后我拿现场采集的图去测发现解码率不是文档里写的99%而是70%左右。主要问题集中在反光和污损。反光区域会把二维码的模块变成一片亮白Halcon的默认二值化就失效了。我的处理流程是先把灰度图统计直方图如果亮度超过阈值就用scale_image把高光压一压或者用emphasize增强边缘。更有用的一个手段是设置polarity参数。二维码的极性有两种深色底浅色码和浅色底深色码。默认是dark_on_light深码浅底如果底是深色但码是浅色不设置极性会直接失败。Halcon里可以设置polarity为any让它自动检测代价是稍微多一点耗时。污损方面Halcon提供了module_size_min参数用于指定码的模块最小像素尺寸。设置合理后它能更好地区分噪声和真实码点。如果污损很严重可以用中值滤波median_image去掉小颗粒噪声但要控制滤波窗口别超过3×3否则码点边缘会被抹平。5.2 跨线程调用Halcon导致的崩溃这是工业软件里最容易崩的坑。我之前图省事在BackgroundWorker或Task里直接调用Halcon识别然后把结果显示在WPF控件上。结果程序运行时经常闪退错误信息是HALCON error #8511: Can not open display或直接是AccessViolation。根因是Halcon的HWindow图像窗口和HObject对象都绑定线程上下文跨线程释放句柄会使底层计数混乱。正确做法有两种把识别逻辑全部放到独立线程图像显示时通过Dispatcher.Invoke切回UI线程再关联窗口控件。如果使用HSmartWindowControlWPF让这个控件的窗口句柄始终在UI线程创建识别线程只处理HObject最后把结果字符串和轮廓数据传回UI线程绘制。我最后采用的做法是完全在后台线程做识别不访问任何窗口。识别得到的结果只是坐标和字符串UI线程拿到后自己画框。这样既稳定又顺滑。5.3 图像格式转换的性能陷阱从相机取到的图像是byte[]每次取图都转HObject是不可避免的但要注意GenImage1的性能。我之前用了一个很蠢的写法每帧都重新new HObject()然后将数组拷贝进去结果发现GC压力很大帧率上不去。后来改成了图像缓冲池预先分配好HObject在回调里用GenImage1重新关联同一块byte[]并在上一帧处理完成后再复用。这样内存波动小连续采集5小时不掉帧。另外如果相机SDK能直接给出IntPtr和行字节数可以使用GenImage1Extern它不会拷贝数据直接指向外部缓冲性能最好。但注意数据生命周期要控制好Halcon异步处理期间缓冲不能释放。6. 性能优化从单张识别到产线连续读取6.1 复用句柄避免重复建模前面已经提过create_data_code_2d_model一定要在程序启动时创建一次不要每帧创建。创建模型本身要解析内部参数耗时几十毫秒产线上每帧几十毫秒是不可接受的。我可以给你一组我的实际测试数据使用Halcon 20.11一张1280×960灰度图QR码约占图像面积十分之一enhanced参数下首次执行约70毫秒之后稳定在45毫秒左右。如果不复用模型每帧执行时间会增加到110毫秒左右。所以句柄复用是最基本也是收益最大的优化。6.2 设置超时与多ROI识别产线场景中不是每帧都能读到码。Halcon的find_data_code_2d在比较复杂的图像上可能会因为搜索耗时变长默认没有超时一旦图像里出现大量伪码点耗时可能飙升到几百毫秒。可以通过set_data_code_2d_param设置timeout参数单位是毫秒。我设了50毫秒超时超时后算子返回空结果不阻塞流水线。另外如果图像中有多个二维码可以设置element_size_max或直接使用stop_after_result_num参数只取前N个结果避免全部解码。对于多个ROIHalcon的模型本身是整图搜索的但我们可以先用reduce_domain把图像域裁剪到感兴趣区域减小搜索范围从而大幅提速。比如流水线只拍固定位置就提前把码区域裁剪出来识别速度能从50毫秒降到20毫秒。6.3 异步流水线设计我最终的方案是实现了一个简单的三线程流水线线程A相机采集回调触发。线程B图像预处理 二维码识别。线程CUI绘制和结果上传。线程之间用Channel或BlockingCollection传递图像缓冲。采集线程只负责把图像放入队列如果队列已满就丢弃最旧帧保证实时性识别线程每处理完一帧将结果对象放入UI队列UI线程每50毫秒刷新一次绘制。这样识别耗时再长也不会卡住采图UI永远流畅而且可以通过队列长度动态控制是否抽帧。7. 关于工程落地最后说几句实在话不要迷信Halcon开箱即用。它只是一个强大的算法库真正决定项目成败的是你的图像打光、相机选型和现场调试。我在项目里踩的坑至少有一半不是软件问题而是产线上光源晃、遮挡、镜头畸变这些物理问题。软件层面能做的是模型参数越早确定越好界面显示越简单越好线程模型越清晰越好。还有一个小经验任何读码程序都要保留原始图像功能。现场识别失败时如果能把当时的图存下来后面排查会省很多事。我在程序里加了一个“异常帧自动保存”功能按F9热键或失败超过阈值时自动把当前帧存成bmp这个功能在调试阶段帮了大忙。识别失败的原因千奇百怪没有原始图像一切分析都是空谈。本文还有配套的精品资源点击获取
返回列表