资讯详情

多相机缺陷检测系统实战:VS2015、Qt5.9与Halcon20组合解析

📅 2026/10/10 11:39:17 | 华诺云谱 👁 阅读
多相机缺陷检测系统实战:VS2015、Qt5.9与Halcon20组合解析
前段时间需要维护一套多相机缺陷检测的系统打开工程一看编译环境是VS2015、Qt5.9和Halcon20的组合。放在今天来看这套搭配多少有点“老”但完整跑过项目、处理完产线上的各种幺蛾子之后我反而越来越觉得这三个版本凑在一起不是偶然而是产线机器视觉设备里非常务实的一种解法。这篇文章就围绕这套“VS2015 Qt5.9 Halcon20”的多个相机缺陷检测源码来讲。我会把系统架构、算法流程、工程坑点、打包部署整个链路都复盘一遍适合正在做视觉检测设备、或者拿到一套老代码不知道从哪下手的工程师参考。里面提到的思路和踩坑记录都是实际项目里的真东西不是demo级别的玩具代码。1. 这个“奇妙组合”是怎么被逼出来的1.1 需求端为什么一台设备要装多个相机我接触的这套源码对应的是一台外观缺陷检测设备。产品是一个方形结构件需要检测上下表面和两个侧面四个工位各装一个相机。检测目标包括划痕、脏污、压伤、缺料、异物标准还不完全相同——上下表面用明场看大面积脏污侧面用低角度光抓划痕其中一个面还要求检测局部微小凸点。多相机不是简单的“多买几个摄像头”它带来三个层面的问题多路图像要并发采集帧率还不能掉得太难看每个相机的检测参数、ROI区域、打光条件都不一样必须能独立配置产品在流水线上移动同一个产品不同工位拍出来的图要能关联到一起便于判定最终OK/NG。这些需求如果软件架构设计得不好后面每个环节都会冒问题。所以先明确一点多相机检测系统的核心难点不在某个算法有多高级而在多路图像怎么组织、怎么调度、怎么管理各自的参数。1.2 版本选型不是念旧是“刚好每一步都不得不这么做”选定VS2015、Qt5.9、Halcon20这个组合很多人第一反应是“怎么还用这么老的版本”。我在项目里也反复权衡过最后坚持下来是有实际原因的。先看VS2015。产线工控机很多时候是客户指定的系统环境陈旧是常态。有些型号的工控机出厂自带的老版运行库、PLC通信库、IO卡驱动就是基于v140工具集编译的。我们如果拿了VS2022或者更新工具链编译出的exe一上工控机就容易缺VCRUNTIME140.dll、MSVCP140.dll。VS2015的v140工具集生成的目标文件在兼容性上非常稳尤其配合Halcon和相机SDK这种吃C运行时很敏感的库少掉很多“运行时冲突”的麻烦。再看Qt5.9。这个版本是Qt官方的LTS长期支持版本虽然老但它在老旧显卡驱动的工控机上显示非常稳定不依赖太新的OpenGL特性。VS2015对应Qt5.9的MSVC2015_64套件编出来的程序能直接嵌进VS里调试不需要像MinGW那样处理链接库格式不一致的问题。如果你用MinGW版Qt去链Halcon的MSVC版lib一堆无法解析的外部符号非常折磨人。最后是Halcon20。与其说是选Halcon20不如说是选它的多相机接口稳定性和并发支持。比它更老的Halcon 13、17在GigE Vision多路取流上偶尔会掉帧Halcon20在20.11之后对多线程的算子支持明显改善而且还能直接兼容VS2015。也就是说在这个组合里编译器是旧但能跑界面库是旧但稳视觉库是新但兼容三者正好怼到一块去了。1.3 Halcon20的安装与授权细节这一步栽过很多次关于Halcon20安装我先给一个比较稳的版本Halcon 20.11 Progress。安装的时候有几个坑值得记一下。第一位数必须对齐。小学三年级都知道但实际项目里总有同事在这个上翻车。相机SDK是64位的那Halcon就必须装64位VS工程也必须选x64平台。Halcon安装目录下会分x64-win64和x86-win32子目录装完以后别装错。三处都是64位才不会出现链接阶段signature不匹配的问题。第二环境变量。正常安装时安装程序会自动把HALCONROOT指向安装目录同时把C:\Program Files\MVTec\HALCON-20.11\bin\x64-win64写进PATH。但很多工控机是用的镜像部署系统环境变量被精简掉Halcon的DLL找不到程序一启动就报错。我建议在开发机上专门打开环境变量确认一下HALCONROOTC:\Program Files\MVTec\HALCON-20.11 HALCONARCHx64-win64 PATH...\bin\x64-win64;...\bin\x64-win64\dotnet第三license。开发调试阶段用试用license或正常采购的开发授权正式部署时用runtime license。常见问题是把开发版license直接丢到产线电脑上结果到期后相机全锁很难堪。还有一个小细节如果同一台机器装过多个Halcon版本秋水一样的PATH变量会把版本搞混插件装完还是提示找不到hdevengine.dll。装完之后建议先用Halcon自带的例程验证相机是否都能正常取流。examples\cpp下有现成的open_framegrabber例子直接改成对应相机IP地址跑通了再往Qt工程里集成。这个步骤能帮你排除相机、网卡、IP设置、防火墙等底层因素不然到最后你会发现Qt工程里报错本质上跟Qt一点关系都没有。2. 多相机采集与任务流水线不是“多开几个线程”那么简单2.1 采集层设计用Halcon还是相机SDK多相机采集第一件事是决定用Halcon的接口直接取图还是用相机厂商SDK。我在这套源码里两条路都走过给你说下对比。如果相机支持GigE Vision或者USB3 Vision标准协议直接用Halcon的GrabImageAsync是最省力的。好处很明显抓出来的图像直接是HImage对象后续threshold、connection这些算子直接调用不需要做内存拷贝Halcon内部对多路相机采集有独立buffer管理一台相机一路handle不容易相互干扰代码量小不用维护每家厂商不同的SDK回调。但某些场景下必须用厂商SDK比如相机私有的触发模式、多相机硬同步、GPIO控制光源、某些特定型号的相机没有走标准协议。这种情况就老老实实拿SDK回调里的图像buffer用GenBuffer或者GenImage1包成HObject再进处理管线。我这套系统里主流相机走的是Halcon GigEVision接口只有带硬触发联动的关键工位加了一个厂商SDK辅助控制。整体逻辑就是能用标准协议就用Halcon必须私有控制才引SDK。2.2 一个相机一个采集线程 检测线程池多相机采集最容易踩的坑是“一锅炖”——在UI线程里循环反复抓四个相机的图像然后发现界面卡顿、图像掉帧、CPU占用还爆满。正确的做法是把采集和界面彻底分离。我给这套源码设计的是这样一套结构每个相机一个独立的采集线程std::thread内部循环调GrabImageAsync抓到图像后塞进一个线程安全的任务队列带容量上限避免内存爆掉检测线程从队列里取图跑Halcon算法得出OK/NG结果和缺陷坐标检测结果通过信号槽机制发回主界面主界面只负责显示和操作交互。代码骨架大致长这样// 采集线程内 HTuple hv_AcqHandle; OpenFramegrabber(GigEVision, 0, 0, 0, 0, 0, 0, default, -1, default, -1, false, default, cameraIP, 0, -1, hv_AcqHandle); GrabImageStart(hv_AcqHandle, -1); while (bRunning !bStopRequested) { HImage image; image.GrabImageAsync(hv_AcqHandle, -1); if (queue.size() MAX_QUEUE_LENGTH) { queue.push(std::move(image)); } QThread::msleep(2); } CloseFramegrabber(hv_AcqHandle);// 检测线程内 try { HImage image; if (queue.try_pop(image, 200)) { DetectorResult result detector.run(image); emit resultReady(cameraId, result); } } catch (HException e) { emit errorOccurred(cameraId, QString(e.ErrorMessage().Text())); }这里有个细节队列长度一定要设上限。如果检测速度跟不上采集速度队列无限增长程序会在一分钟之内吃光所有内存。我一般设100到200帧上限满了就丢新帧宁可少检一帧不可让程序崩掉。2.3 多相机参数与工件ID联动管理多个相机的检测参数绝不能写死在代码里。原因很简单产品换型、光源调整、相机角度微调这些在产线现场时刻都在发生总不能为了改一个曝光值重新编译一次程序。我的做法是每个相机一个配置段全部丢到一个JSON文件里统一管理。内容包含相机IP、曝光时间、增益、触发模式ROI区域坐标每一类缺陷算法的使能开关和阈值参数检测结果的保存路径、是否存NG图。换产品的时候界面上下拉选择产品型号程序自动把所有相机参数加载进来。另外多相机检测一定要解决“同一产品多工位结果关联”的问题。我这边用条码或者产品流水号做主键每个相机检测完成后把结果回写到产品记录里等所有工位都检测完汇总规则再触发最终判断。这样才不会有“一个面NG了但另外几个面OK设备却不报警”的低级问题。3. Halcon缺陷检测三板斧打光、分割、特征筛选3.1 打光先行图像质量决定了算法的天花板说句得罪人的话很多团队做缺陷检测遇到算法不稳定第一反应是疯狂调Halcon算子参数但真正的问题往往是打光就不对。我举几个实际例子。检测金属表面划痕如果用普通的环形LED照明划痕方向不固定反光时有时无。后面改成多角度低角度光源让结构光从侧面掠过表面划痕散射光明显增强黑白对比一下就出来了。检测凸点或者颗粒用暗场照明背景压暗颗粒边缘散射光变成亮点阈值分割极其轻松。所以在这套源码里我特别强调一个原则**算法的任务是稳定提取特征不是替光源擦屁股。**拿到图像先不要急着写代码先调光源角度、亮度、相机曝光让缺陷和非缺陷区域的灰度差值保持在比较明显的水平上。图像质量不好后面一切算子都是白搭。3.2 缺陷分割不同缺陷对应不同的算子套路在Halcon里做缺陷检测总的核心思想是“把缺陷区域从背景中剥离出来”。针对不同缺陷类型我总结了四种最实用的套路划痕与浅色脏污适合用局部动态阈值dyn_threshold。先对原图做均值滤波或高斯平滑得到一张背景估计图再用原图和背景图做差超过动态阈值的像素就是疑似缺陷。HObject smoothed, diffRegion; MeanImage(image, smoothed, 21, 21); DynThreshold(image, smoothed, diffRegion, 5, dark);凸点凹点用中值滤波差分。中值滤波对边缘保持比较好原图减掉中值结果后非边缘区域的微小灰度突变会被放大凸点凹点直接暴露。规则纹理表面的异常频域滤波更有效。先做FFT用滤波器把周期性纹理的频段抑制掉再反变换回来纹理变成均匀背景local defect才显露出来。这个比空域卷积更高效。大面积脏污或整体灰阶不均先做背景校正。可以用一个大尺度的高斯滤波作为背景估计然后原图像素灰度除以背景灰度再乘一个标准值把光照不均的影响抹平最后阈值分割。分割完了通常还要做一轮形态学清理。connection把连通域打散opening_circle去掉细小的毛刺噪声closing_circle填补缺陷内部的小孔洞。3.3 特征筛选压误检的关键一步分割出来的区域并不全是缺陷。金属反光、灰尘、正常结构边缘、螺纹轮廓这些都有可能被分割出来。这时候就要用select_shape、select_gray这类特征筛选算子。我个人经验里最有效的几个特征组合是面积area太小的面积基本是噪点可以过滤宽高比width/height真正的划痕长宽比很高灰尘接近圆形圆形度circularity污点通常较圆划痕通常细长灰度均值和标准差区域亮度与原图对应位置的灰阶差异可以区分真实缺陷和反光假点。举个例子一段疑似划痕区域面积只有5个像素宽高比却很大这时候不能直接判NG否则正常的微小氧化点都会报警。我会再加一条约束区域的灰度均值要比周围背景暗多少以上或者划痕的长度必须超过某个像素值。把这些规则组合起来误检率能压下来一个数量级。HObject selected; SelectShape(connected, selected, area, and, 20, 99999); SelectShape(selected, selected, width, and, 3, 99999); SelectShape(selected, selected, height, and, 3, 99999); SelectGray(selected, selected, image, mean, and, 0, 128);调参这件事我不建议对着实时视频盲调而是先收集一批有代表性的样本图比如100张正常品和50张已知NG品离线跑一遍算法把所有候选区域的面积、宽高比、灰度均值统计成一张表再根据分布去定阈值。这样做出来的参数可解释、可复现而不是“差不多调到不误报为止”。4. VS2015与Qt5.9工程里的坑从环境变量到内存释放4.1 环境变量与“HALCONROOT找不到”的排查链路这套源码交付后最容易出现的第一类报错是启动时弹窗找不到hdevengine.dll或找不到halcon.dll或者“无法定位程序输入点”。排查顺序不要乱。先确认开发机能正常跑再拿到工控机或者同事的机器上。常见原因没有安装Halcon runtime或者PATH里没有bin目录halconcpp.dll存在但依赖的hdevengine.dll不在同目录系统里同时装了新老HalconPATH顺序被改坏程序加载到另一个版本的DLL。解决方式把Halcon的bin目录显式加到PATH并且确认HALCONROOT指向正确版本。这类问题在视觉项目里出现频率非常高我甚至见过有同事把整个Halcon的lib文件夹拷到exe目录以此解决DLL依赖虽然丑但当年确实能救急。反倒是VS2015安装包丢失或者损坏这类问题多数情况下是Windows Installer缓存C:\ProgramData\Package Cache被清理过导致的碰到就建议直接修复安装VS2015并保证磁盘空间充足离线ISO用官方镜像。4.2 Qt VS Tools 与 moc、uic 生成链问题VS2015下用Qt开发需要装Qt VS Tools插件。这个插件负责调用moc元对象编译器、uic界面编译器、rcc资源编译器。实际使用中最常见的坑是新增了一个类里面写了Q_OBJECT但moc没有自动重新生成编译报“未定义的虚表”或者“无法解析的外部符号”改了UI文件界面不生效因为uic没有重新运行Debug和Release混用链接报一堆LNK2038之类。我的经验是每次从Git上拉完代码先做一次“全部清理”然后在Qt VS Tools菜单里强制重新运行moc。另外一个被忽视的点是字符集Halcon20的HTuple默认宽字符处理Qt默认UTF-8如果项目设置了多字节字符集混合使用时容易在字符串传递上出乱码和截断建议在工程属性里统一使用Unicode字符集。4.3 图像对象、内存与相机释放的顺序敏感多相机程序的崩溃重灾区十个里面有八个跟释放顺序有关。我整理过一条明确的释放原则先停采集线程等待线程join退出再释放任务队列里所有未处理的HImage最后再调用CloseFramegrabber关闭相机。如果先关了相机句柄采集线程还在下一次GrabImageAsync里程序就直接访问冲突。如果是用了厂商SDK顺序变成先停止采集回调再释放图像buffer再卸载SDK库最后关UI窗口。另外Halcon的HObject是引用计数类型多线程共享同一个HObject时如果一边读取一边析构会触发计数错乱。所以我在源码里有一条硬性规定检测线程拿到的是图像的一个独立拷贝或者检测完成后立刻把对象回收绝不跨线程共享同一个HObject实例。还有一个小坑某些工控机显卡驱动对Qt5.9的渲染支持不完整图像显示控件频繁刷新会闪烁这个不是Halcon的问题解决办法是改用QPixmap离线绘制后一次性贴图不要在paintEvent里实时做格式转换。5. 性能优化与产线稳定性断线重连和误检闭环5.1 相机断线重连产线稳定性最容易翻车的点一套外观检测设备算法再准确相机只要掉线一次整个工位就停摆。GigE相机最怕网线松动、工控机网卡休眠、供电波动。为此我在源码里加了三重保障采集线程内对每次抓图设置超时超过5秒没有新帧就判定断线断线后自动进入重连流程重新OpenFramegrabber最多重试10次每次间隔1秒界面状态栏实时显示相机连接状态断开标红恢复标绿。重连时有个细节有些相机在重连前必须调用一下OpenFramegrabber中的reset参数或者先释放旧句柄再重新获取直接复用句柄多半会一直失败。为了保证UI在重连期间不卡死重连逻辑放在独立线程里跑弹窗提示放主界面。这套机制上线之后现场报障率明显下降。5.2 误检统计与调参闭环用数据代替感觉我在做这套源码时给程序内置了一个“离线回放模式”。现场采集的图像全部按日期、相机ID、产品型号分类保存然后可以在调试界面上回放任意一批图边调整算法参数边看效果。这里面最核心的方法是“NG留样”。每张判NG的图强制保存原图、预处理结果图、缺陷分割图这样积累一段时间后可以回放分析有哪些NG是真正的缺陷有哪些是误检。我经常对同事说误检不是靠智商消灭的是靠样本量消灭的。你手里的NG样本越多参数就能调得越干净。同时OK品也要抽一部分保存专门用来验证“调完参数后是否把正常件误杀了”。这两类样本要分开目录管理每次算法改动都跑一遍全量回放对比误检率和漏检率全部通过才更新到产线版本。5.3 性能分析先量化再谈优化多相机系统经常出现“CPU占用高但不知道卡在哪”的情况。我习惯用QElapsedTimer给每一段处理计时采集耗时GrabImageAsync返回时间算法耗时从HImage传入到缺陷结果输出结果存储耗时写文件、写数据库UI刷新耗时。把四个数据打到同一个日志文件里一跑便知瓶颈在哪。如果算法耗时长优先看是否用多线程并行算子Halcon里可以通过set_system(thread_num, n)开启部分算子的内置并行也可以把多相机检测任务分到线程池每个相机一个检测线程充分利用多核。我有个很深的体会真正拖慢系统的往往是存储环节。如果每张图都同步写高清原图到机械硬盘一个产品多几个面整条线速度就被IO拖死了。正确做法是图像保存全部走异步队列NG图必存、OK图抽存检测线程绝不等磁盘写完成再继续。6. 部署收尾从开发机到工控机打包清单与运行环境6.1 Qt打包与Halcon运行时合并到了交付阶段最怕的就是开发机上能跑拷到工控机上各种缺DLL。Qt这边有现成工具windeployqt --release --compiler-runtime app.exe这条命令会把Qt5.9运行库、VS2015的C运行库都收集到exe同目录。注意一定要加--compiler-runtime不然目标机器上没装VS2015 Redistributable的话照样报缺少VCRUNTIME140.dll。Halcon这边则要手动拷贝runtime最低限度包括halcon.dllhalconcpp.dllhdevengine.dll对应的runtime license文件如果还用了Halcon的扩展或第三方库一并拷入。不要为了省事把整个Halcon安装目录复制过去授权容易出问题文件又多又杂。另外license文件要放到Halcon的license目录下或者在代码里显式指定路径这个不配好工控机上打开相机就会报license错误。之前有同事从WinForm项目转过来问Qt怎么打包安装程序。其实思路是一样的WinForm可以用安装项目或InstallShieldQt照样可以用Inno Setup写脚本把整个exe目录打成Setup包。差别只是Qt多了一步windeployqt其他没有本质区别。6.2 工控机运行环境标准化产线工控机的环境一定要标准化否则同一个exe在这个工厂能跑到另一个工厂就瘫痪。我在交付时固定了一份运行环境清单操作系统Win10企业版LTSC或者Win7 SP1前提是相机SDK还支持关闭Windows自动更新防止半夜自动重启把产线搞停电源设置为高性能模式关闭屏幕睡眠和硬盘休眠防火墙对相机网卡网段放行或直接关闭公网连接相机网卡禁用节能模式这个我踩过网卡一节能GigE相机掉帧掉得怀疑人生。这些步骤看着基础但每一项都是在产线上真金白银换来的经验。机器视觉设备是7x24小时跑的任何“意外”其实都是必然提前在环境层面堵死比事后救火省心得多。6.3 源码与参数管理的一点体会最后说回标题里的“多个相机缺陷检测源码”本身。这套源码我维护了大半年最大的感触是多相机系统的代码本身不复杂复杂的是参数、配置和依赖的版本管理。我的做法是所有相机参数、检测参数、产品型号配置全部放JSON独立文件绝不混进二进制每个版本的Halcon runtime、相机SDK、Qt库版本记录在README里锁死依赖Git分支按产品型号区分改参数直接改配置文件不重新编译exe。这套实践下来现场调试的效率高了不少。很多时候客户反馈问题远程要一份配置文件就能复现不用反复传exe。如果你也要在产线上搭一套多相机缺陷检测系统我的建议是别纠结版本新旧先把环境变量、相机释放顺序、打包依赖这三件事用纸列清楚能少熬很多个夜。VS2015、Qt5.9和Halcon20这个组合在把多个相机缺陷检测源码从开发机迁到工控机这件事上确实是我试过的搭配里最省心的一版。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑