ARTICLE DETAIL

资讯详情

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

Halcon与工业相机C++实时采集实战:从配置到丢帧排查

Halcon与工业相机C++实时采集实战:从配置到丢帧排查 上个月给一条装配线做视觉检测被Halcon和工业相机的连接折腾了整整两天——不是相机本身有问题而是从SDK配置到实时图像采集这条链路里藏着太多文档里不会明说的细节。后来跑通之后我复盘了一下其实整个过程并不复杂关键在于把“Halcon怎么跟相机说话”这件事想清楚。这篇文章我就把这套完整流程写出来从环境准备、工程配置到采集代码再到丢帧排错和进阶玩法全程用C的视角来讲给正在搞Halcon工业相机项目的朋友做个参考。这篇内容适合两类人一类是刚接触Halcon想用C接Basler、海康、大华这类工业相机做实时采集的工程师另一类是已经在用厂商SDK但想把Halcon的算法能力跟相机采集合到一套代码里的朋友。我会把每一步为什么要这么做、参数有什么含义、容易踩什么坑都讲明白争取你看完就能照着搭出一套可用的采集Demo。1. 先想明白一件事Halcon在采图链路里到底扮演什么角色1.1 三条采图路线的取舍很多人第一次接触Halcon时会有一个误解Halcon是完整的视觉软件装上之后就能直接操作相机。实际上Halcon是一个算法库它的图像采集能力来自底层的采集接口Framegrabber Interface本质上是把厂商SDK再封装了一层统一的接口。也就是说Halcon并不替代相机驱动而是站在驱动之上跟你对话。在工业相机接入Halcon这件事上常见的路线有三条路线AHalcon原生接口直接采图。通过OpenFramegrabber算子指定接口名比如GigEVision2、Basler、DahengHalcon内部会调用对应的SDK完成打开相机、设置参数、抓帧。这是最省事的方式所有逻辑都在Halcon的语法体系里完成后续做图像处理时数据无需拷贝直接就是HImage对象。路线B相机厂商SDK先取图再封装成HObject传给Halcon。这种方式在相机功能非常特殊比如超高速、特殊触发逻辑时会用到但你要自己管理图像内存和格式转换代码量大效率还低一截。路线C走通用视觉协议。如果相机支持GigE Vision或USB3 VisionWindows系统下也可以不装厂商SDK直接用Halcon的GigEVision2接口连接。好处是省去厂商SDK坏处是个别厂商私有的相机参数会看不到而且大华、海康部分型号对通用协议支持得并不完美。就绝大多数项目来说我建议先把路线A跑通——Halcon对主流品牌都有现成的接口开发和调试效率是最高的。如果你的相机比较小众或者Halcon的接口列表里没有对应品牌再考虑路线B。1.2 为什么这个项目用C而不是C#或Python做视觉项目的人常在这三门语言之间犹豫。C#在开发上位机界面上确实快Python在算法验证上很方便但我这次选C有两个硬性理由第一性能和内存控制。实时采集意味着每一帧图像都要在极短的时间内处理完。C下你可以精确控制图像对象的生命周期避免GC垃圾回收在关键时刻卡一下。尤其是后面要接Halcon深度学习推理时GPU显存里的数据管理C比C#直接得多。第二部署环境往往很旧。产线工控机一般装的是Windows 10 LTSC甚至Windows 7系统运行库、显卡驱动、USB控制器驱动都是能不动就不动。C编译出来的是一个独立exe加几个DLL往工控机上一丢就能跑基本不依赖运行时环境。不过我要提醒一句Halcon在不同语言下的API差异不大底层算子完全一样所以本文讲的思路即使你最后用了C#或Python也是完全通用的。2. 环境准备Halcon安装、许可证与相机驱动的暗坑2.1 Halcon版本和安装目录结构安装Halcon之前先确认版本号。我现在用的是Halcon 22.11这个版本对C的集成做得比较舒服头文件和库文件划分也清晰。装完后默认安装路径类似C:\Program Files\MVTec\HALCON-22.11-Steady里面有几个关键目录你必须清楚bin\x64-win64运行期DLL包括halcon.dll和halconcpp.dll后续发布程序时这两个文件要跟着exe走。include\halconcppC API的头文件核心是HalconCpp.h。include\halconC API的头文件用纯C风格调用时才会用到。lib\x64-win64导入库里面能看到的halcon.lib和halconcpp.lib分别对应C API和C API。doc帮助文档和PDF手册遇到算子的参数不懂时先来这里查。license许可证文件目录。试用版会在这里生成halcon_版本号_eval.dat之类的文件。这里有个特别容易搞混的点C工程里只需要引入halconcpp.lib和HalconCpp.h不需要手动去包含底层C接口。如果两个库都引了某些版本下会因为符号重复导致链接错误。2.2 许可证的暗坑Halcon的license分正式授权和试用评估版。官网下载的时候可以申请一个试用许可证通常能用一个到三个月。申请后会收到一个license.dat或者类似文件必须放进license目录然后重启Halcon相关程序才会生效。我在项目中碰到过一个很坑的现象license到期之后Halcon本身还能打开采集图像时却报访问错误错误信息看起来像是相机驱动问题实际去查才发现是license过期。所以如果你排查了半天相机问题没结果先看一眼license文件的时间戳。另外提醒一点如果你的开发机需要脱机运行比如客户现场没有外网务必提前确认license支持离线环境并且把系统时间同步问题处理干净否则license过期检测会把采集流程全部卡死。2.3 相机驱动Halcon能认到设备的前提Halcon的采图接口本质上是在调用相机的SDK。以Basler为例你需要先安装pylon Camera Software SuiteHalcon的Basler接口才能正常枚举到相机。海康对应的是MVS客户端大华对应的是自己的相机驱动SDK。驱动安装时我建议留意以下三点驱动版本不能太新也不能太旧。太新可能跟Halcon内置的接口版本不匹配太旧又可能不支持新相机。最稳妥的做法是装和相机出厂配套的稳定版驱动而不是追最新版。USB3/GigE驱动的权限。Windows下USB3相机首次插入会装驱动这时最好用设备管理器确认相机出现在“图像设备”里而且没有被禁用。如果出现感叹号先重装厂商驱动而不是直接换线。多品牌相机混插时。装A品牌驱动会卸载B品牌的通用USB驱动导致B品牌相机枚举不到这是多品牌项目最常见的坑建议一台工控机尽量只保留一套品牌驱动生态。3. VS工程配置让编译器找到Halcon的头文件和库3.1 包含目录、库目录和附加依赖项新建一个空的C控制台工程把平台切到x64因为Halcon的x64-win64目录下只有64位库Debug和Release都建议用64位。然后在项目属性里做四件事开启“配置属性”里的“VC目录”把include\halconcpp填进“包含目录”把lib\x64-win64填进“库目录”。在“链接器”的“输入-附加依赖项”里加halconcpp.lib。把平台工具集切到Visual Studio 2019或2022对应的版本根据你装的VS来编译时如果报“Microsoft Visual C 14.0 or greater is required”之类的提示说明VS的C工具链没装全用Visual Studio Installer补上“使用C的桌面开发”组件。确认预处理器里没有强制使用Unicode字符集导致头文件解析异常。一个容易忽略的问题如果你的工程里同时用了OpenCV并且OpenCV是动态库版本那么包含目录的顺序会影响errhandlingapi.h之类的头文件冲突。遇到这种冲突时把Halcon的头文件放到包含目录的最前面通常能解决。3.2 用CMake管理也可以但要注意你的Halcon路径如果你习惯用CMake不必在VS界面里手工点属性。在CMakeLists.txt里可以这样写cmake_minimum_required(VERSION 3.16) project(halcon_acquire LANGUAGES CXX) set(HALCONROOT C:/Program Files/MVTec/HALCON-22.11-Steady) include_directories(${HALCONROOT}/include/halconcpp) link_directories(${HALCONROOT}/lib/x64-win64) add_executable(halcon_acquire main.cpp) target_link_libraries(halcon_acquire halconcpp)这里有个比较隐蔽的点link_directories在CMake新版本里对某些生成器不生效如果最后链接阶段提示找不到halconcpp.lib那就改用绝对路径直接指定库文件target_link_libraries(halcon_acquire C:/Program Files/MVTec/HALCON-22.11-Steady/lib/x64-win64/halconcpp.lib )3.3 运行期DLL怎么处理工程能编译通过只是第一步。运行时如果提示找不到halconcpp.dll是因为程序启动时没去bin\x64-win64目录找DLL。最简单的处理方式是在工程属性的“调试-环境”里加上PATHC:\Program Files\MVTec\HALCON-22.11-Steady\bin\x64-win64;%PATH%。如果要把程序部署到客户机器需要把bin\x64-win64下的halcon.dll、halconcpp.dll有时候还需要halconxl.dll和hdevengine.dll等复制到exe同级目录。这里面有几个DLL是随许可证绑定的拷多了反而麻烦最可靠的做法是装一个Halcon的Runtime运行时MVTec官网上有单独下载或者直接在目标机器上装完整版Halcon并激活。4. 用Halcon打开工业相机从设备枚举到参数配置4.1 先枚举设备InfoFramegrabber拿到相机并装好驱动后在写代码之前我强烈建议先打开Halcon自带的HDevelop在算子窗口里跑一行InfoFramegrabber看看能不能枚举到设备。这一步能帮你把“Halcon接口问题”和“相机硬件问题”尽早分开。在C里调用InfoFramegrabber的代码是这样#include HalconCpp.h using namespace HalconCpp; HTuple information, value; InfoFramegrabber(GigEVision2, device, information, value); for (int i 0; i information.Length(); i) { std::cout Device: information[i].S() std::endl; }这里第一个参数GigEVision2是采集接口名。如果你的相机是Basler也可以尝试Basler平台海康用Hikvision或者走GigEVision2大华在Halcon 22.11里常见的是Daheng接口。具体哪个接口可用可以在HDevelop里点“助手-打开新的Image Acquisition”看接口下拉列表。4.2 OpenFramegrabber参数多但别被吓住OpenFramegrabber的签名很长很多人都记不住其实核心是接口名和设备标识。以GigEVision2接口为例典型的调用是HTuple acqHandle; OpenFramegrabber( GigEVision2, // Name: 采集接口 0, 0, // HorizontalResolution, VerticalResolution0表示用相机的默认分辨率 0, 0, 0, 0, // 图像宽高、位深等通常填0 default, // ImageType用默认 -1, // BitDepth default, // ColorSpace -1, // Generic default, // ExternalTrigger default, // CameraType deviceName, // Device前面InfoFramegrabber枚举到的设备名 -1, // Port -1, // LineIn acqHandle);这里最容易出错的是Device参数。GigE相机枚举出来通常是一个类似CAMERA1的字符串或者一串IP地址不能传-1让Halcon猜否则多相机环境下它会随机连一台很容易连到不想要的设备。如果只有一个相机传-1也能工作但不推荐。4.3 设置曝光、增益和触发模式打开相机之后用SetFramegrabberParam来设置参数。这里有一个基本的API模式SetFramegrabberParam(acqHandle, ExposureTime, 5000.0); // 曝光时间单位微妙 SetFramegrabberParam(acqHandle, Gain, 1.5); SetFramegrabberParam(acqHandle, TriggerMode, Off); // 连续采集模式 SetFramegrabberParam(acqHandle, PixelFormat, Mono8); // 灰度图不同相机品牌的参数名会有微小差异。Basler下曝光时间通常是ExposureTime海康有些型号可能叫ExposureTime但单位不同也有用ExposureAuto的。最靠谱的方式是打开HDevelop的“变量窗口”里勾选“显示帧抓取器参数”它会列出当前相机支持的全部参数名和取值范围。设置参数的正确顺序建议是先设置PixelFormat再设置分辨率最后设置曝光和增益。因为相机内部在切换格式时某些参数会做归一化映射顺序反了可能导致你设的曝光值被自动重置。5. 实时图像采集的完整代码从单帧到连续抓图5.1 同步采集还是异步采集Halcon里有两个最常用的抓图算子GrabImage和GrabImageAsync。GrabImage是同步阻塞式的调用后会等相机输出一帧拿到这一帧才返回。这种模式适合单帧检测或者硬触发模式一帧一触发一帧一处理。连续实时采集时我几乎只用GrabImageAsync它配合GrabImageStart使用。GrabImageStart告诉相机开始连续采集之后的GrabImageAsync请求的是“最新的一帧”而且可以设置一个超时时间避免相机断线时程序死等。有一个细节一定要说清楚GrabImageAsync的最后一个参数Delay如果设成0它会尽可能返回最新的图像如果设成-1它会等相机下一帧到来如果设成正数则会丢弃当前帧再等。实时显示、实时处理这种场景推荐设成0它会自动丢掉缓冲区里的旧帧保证你处理的是最新画面。5.2 一个可运行的C采集Demo下面这段是我在实际项目里简化的一个控制台程序可以直接照着跑通从打开相机到连续采集的完整流程。重点看异步采集循环和显示刷新是怎么配合的#include HalconCpp.h #include iostream #include windows.h using namespace HalconCpp; int main() { try { HTuple acqHandle; // 枚举设备这里假设就一台GigE相机 HTuple info, val; InfoFramegrabber(GigEVision2, device, info, val); if (info.Length() 0) { std::cerr No camera found. std::endl; return -1; } std::cout Using device: info[0].S() std::endl; // 打开相机 OpenFramegrabber(GigEVision2, 0, 0, 0, 0, 0, 0, default, -1, default, -1, false, default, info[0].S(), 0, -1, acqHandle); // 设置参数 SetFramegrabberParam(acqHandle, ExposureTime, 3000.0); SetFramegrabberParam(acqHandle, Gain, 1.0); SetFramegrabberParam(acqHandle, TriggerMode, Off); SetFramegrabberParam(acqHandle, PixelFormat, Mono8); // 创建显示窗口 HWindow window(0, 0, 1024, 768); HImage image; // 开始连续采集 GrabImageStart(acqHandle, -1); char key 0; while (key ! q) // 按q退出 { // 取最新一帧超时5000ms image.GrabImageAsync(acqHandle, 5000); // 显示 window.DispImage(image); // 处理按键控制台程序需要手动刷新消息队列 if (GetAsyncKeyState(Q) 0x8000) break; } // 清理 CloseFramegrabber(acqHandle); std::cout Acquisition stopped. std::endl; } catch (HException e) { std::cerr Halcon error: e.ErrorCode() e.ErrorMessage() std::endl; return -2; } return 0; }注意代码里的HWindow window(0, 0, 1024, 768)会在屏幕上弹出一个图像窗口程序运行时需要能看到窗口GUI所以不要在纯远程桌面无交互会话里跑这个Demo否则窗口创建会失败。5.3 图像显示的一个被忽略的坑控制台程序里弹Halcon窗口很多人会遇到“程序卡住不动”的问题。原因很简单窗口需要消息循环来处理鼠标拖动、缩放、刷新但控制台程序默认没有。解决办法有两个用GetAsyncKeyState这类方式轮询按键让主线程在采集循环里自然保持活跃这也是上面代码用GetAsyncKeyState的原因。把采集放到工作线程窗口和消息循环放到UI线程比如用Qt或C# WinForm。后面这个方案其实就是很多正式项目的最终架构。我在做相机封装时通常会起一个独立的采集线程用一个环形缓冲或者HImage队列把采集到的帧抛给处理线程显示只在UI线程刷新。这样互不阻塞采集帧率也稳定。5.4 把采集流程封装成类实际项目里千万别在主函数里堆一堆Halcon调用否则后面加触发、加ROI、加缺陷检测时代码会乱成一锅粥。我一般会维护一个CameraHalcon类对外暴露三个核心接口bool Open(const std::string deviceName)负责OpenFramegrabber和参数设置。bool Grab(HImage outImage)内部调GrabImageAsync捕获异常并返回采集结果。void Close()释放采集句柄。上层业务代码完全不知道Halcon的存在后面把采集从GigE换成USB3接口或者从Basler换成海康只改这个类的内部实现就行。这是项目能长期维护的关键。6. 丢帧、花屏、采不到图的排查链路6.1 先用厂商自带的软件做硬件裁决排查采集问题时我的第一个动作永远是打开相机厂商自带的客户端软件——Basler用pylon Viewer海康用MVS Client大华也有自己的客户端。在客户端里看相机能不能正常出图。如果客户端也丢帧或者采不到图问题大概率在硬件链路线缆、网卡、供电、相机本身。如果客户端完全正常那问题就出在Halcon这一侧的配置上。这个步骤看似简单能帮你省掉一半的排查时间。上次同事报“Halcon抓不到大华相机图像”我让他先在MVS客户端里点了一下“连接”结果相机直接断连——原因是相机被之前的未正常退出的进程占用了。Halcon再连接自然失败。6.2 Halcon常见报错一览在采集阶段Halcon的报错代码有几个经常出现先把它们记下来错误码含义常见诱因5300Cannot open device相机被占用、设备名错误、驱动异常5310Cannot open device using the specified parameters接口名不对或参数采集模式设置错误5311Grab timeout触发模式没关闭、硬件触发信号没来、曝光时间过长5312No more frames availableGrabImageAsync超时时间内没有新帧5333Invalid parameter相机不支持你设置的这个参数名排查时先看错误码能少走一大段弯路。比如报5311你就该立刻检查触发模式是不是开着、触发源是不是Line0、曝光时间是不是被设成了几十毫秒。6.3 丢帧排查的完整链路实时采集最怕的就是丢帧丢帧意味着产线上某个缺陷可能被漏掉。我按下面这个顺序排查确认在连续采集模式。如果TriggerMode被设成了On而没有实际触发信号采集线程拿到的帧数会骤降表现上就是每隔几秒才有一帧看起来像丢帧。检查曝光时间。曝光时间过大相机的最大帧率会被拖慢。工业相机的最大帧率通常是曝光时间和传感器读出时间的倒数曝光5毫秒就意味着理论帧率最多200帧但如果实际只要求30帧这个参数不是问题。检查带宽和线路质量。GigE相机用网线USB3相机用USB线。网线质量差、USB3接口是扩展卡转接的、或者线长超过标准距离都会导致传输层丢包。在GigE场景试着把GevSCPSPacketSize改小比如从1500改到1200可以缓解一些网卡TCP/IP栈性能不足造成的丢包。看电脑负载。Halcon图像处理如果占用了过多CPU或者GPU内存采集线程可能因为得不到调度而超时。有同事项目丢帧后发现是杀毒软件在后台扫描硬盘把CPU吃满了。关掉Windows Defender的实时保护后丢帧消失。采集线程里不要做耗时处理。这个是最常见的人为丢帧采集线程里直接调了缺陷检测算子一张图处理几百毫秒相机缓冲就溢出了。正确做法是采集进队列处理线程从队列取图一帧算完再接一帧。6.4 三个我踩过的坑第一个坑是软触发开关。某项目用海康相机参数里TriggerMode设成OnTriggerSource设成Software然后用SetFramegrabberParam(acqHandle, TriggerSoftware, 1)去触发。结果发现前几帧正常后面就采不到了。后来才发现海康有些型号的软触发命令要写TriggerSoftwareExec而不是TriggerSoftware。这个参数名的差异只能在HDevelop里逐个看设备的参数列表没有捷径。第二个坑是显示窗口刷新太慢。用DispImage每帧显示一张大分辨率图窗口重绘本身会占用相当多的CPU时间导致采集循环变慢。解决方法是隔几帧显示一次或者把显示分辨率降下来——显示用的是独立窗口降分辨率不影响实际图像数据。第三个坑是程序退出时没关采集句柄。如果CloseFramegrabber没被调用采集线程还挂在那里反复重启程序后相机会被残留进程锁住。Halcon的release模式有自动释放但Debug模式崩溃时经常还是会把相机锁死只能重启电脑或者用厂商工具强杀进程。7. 进阶扩展多相机、硬触发与Halcon深度学习7.1 多相机同时采集很多产线项目不是一台相机而是多台从多个角度拍同一个产品。用Halcon做多相机采集时逻辑上并不复杂对每台相机各自调用一次OpenFramegrabber拿到独立的句柄然后起多个采集线程每个线程只负责自己的句柄。这里有个重要的原则一个采集线程对应一个相机句柄不要在一个线程里串行抓多个相机。串行会让后面的相机等待时间变长如果两个相机同时拍同一个产品前后时间差会导致图像对不上。多线程采集时每个线程里的GrabImageAsync超时时间设置得保守一点否则一台相机异常会导致整条线卡住。7.2 外触发模式让相机跟着产线节奏走连续采集适合做实验室Demo产线上真正常用的是外触发——相机接到光电传感器或者编码器的信号物体到位才拍一帧。Halcon下设置外触发的基本思路是SetFramegrabberParam(acqHandle, TriggerMode, On); SetFramegrabberParam(acqHandle, TriggerSource, Line0); SetFramegrabberParam(acqHandle, TriggerLineDebouncerTime, 20.0); // 防抖再配合GrabImageAsync等帧。外触发有几个常见问题信号电平极性不对要在参数里设上升沿还是下降沿触发、触发信号频率高于相机最大帧率、触发信号抖动导致同一帧被重复触发。还有一个比较隐蔽的坑触发模式打开后采集程序刚启动时会有一段时间等不到触发信号这时GrabImageAsync会超时返回代码里要能容忍这个超时不要把它当成致命错误。7.3 采集之后的图像处理与Halcon深度学习实时采集只是第一步多数项目的真实目标是缺陷检测、定位、测量这些后续处理。因为图像数据在Halcon里本来就是HImage所以可以在采集循环里直接接算法。常见的衔接方式就几行HRegion region; threshold(image, region, 128, 255); // 阈值分割 HTuple area, row, column; area_center(region, area, row, column); // 找区域中心如果做芯片方向检测这类场景最常用的是形状模板匹配先在HDevelop里用create_scaled_shape_model创建模板然后在C采集循环里用find_scaled_shape_model定位根据模板角度判断芯片是否放反。这个流程对新手非常友好模板做好后C代码量很小。到了缺陷检测阶段Halcon的深度学习工具Deep Learning Tool可以直接用来做目标分类、目标检测和语义分割。Hotdog里有个细节在C里调用深度学习模型模型文件是.hdl格式用read_dl_model读入再用apply_dl_model推理。部署机器建议用NVIDIA显卡CUDA和cuDNN版本要跟Halcon深度学习库匹配这个在安装文档里有明确说明。CPU推理也能跑但速度可能不够产线实时要求。另外有一个跟颜色类缺陷检测相关的存量技巧Halcon里做彩色图像时经常要用HSV转换即trans_from_rgb把RGB转到HSV空间再对H通道做阈值用来提取特定颜色的区域。这个方法在电子元件颜色判定、印刷品色差检测上非常好用而且性能开销很小实时采集场景完全扛得住。7.4 联动上位机把采集结果交出去最终项目通常还要把图像和检测结果送给上位机或者MES系统。我的习惯是图像相关的界面用Halcon自带窗口或者Qt的Halcon控件Qt里集成Halcon可以参考MVTec提供的QHalconWidget或者直接用HWindow嵌入到Qt窗口检测结果通过TCP/Modbus等协议上报。脱开界面后采集线程跟算法线程、通讯线程之间用队列串起来整个架构才扛得住产线长时间运行。最后一个我个人的实操体会做Halcon项目一定要把当前基于的Halcon版本和相机SDK版本写进项目文档里。不同版本的接口参数会有细微差别尤其是相机参数名经常变化。我吃过一次亏项目在Halcon 20.11上跑得好好的换到23.05之后SetFramegrabberParam里一个参数名失效了排查半天才找到是版本差异。所以每次版本升级都要先在HDevelop里把采集参数列表重新过一遍再决定能不能直接迁移。
返回列表