做机器视觉的人基本都绕不开一个选择:项目周期紧、要稳定落地、又不想从头造轮子的时候,到底用 OpenCV 还是 Halcon。就我个人这几年的一线经验来说,Halcon 在自动化视觉定位、测量、识别和检测场景里确实省心得多,尤其是它的算子体系完整度,HDevelop 里能直接通过图形化变量调试算法流程,从找圆、抓边、测量,到手眼标定、深度学习目标检测,几乎都有现成路径。不过也正因为 Halcon 功能太多,新手最容易栽的坑反而不是算法本身,而是环境、授权、坐标系、工程封装这些“看起来不起眼”的环节。
所以这篇系列教程我不打算空谈功能清单,而是按我自己带项目时一步一步踩过的路,从环境搭建讲到算子组合,再讲到 3D 和工程集成。内容会偏实操,涉及安装配置、边缘提取、直线圆拟合、OCR 条码、深度图转点云、手眼标定、导出 DLL 以及深度学习这几个高频场景。适合刚接触 Halcon、正在做视觉评估,或者已经在项目里摸索的朋友,按章节顺序看也好,挑自己缺的那块跳着看也行。
1. 环境搭建与授权流程:下载安装、许可证和环境变量的一次性梳理
很多人拿到 Halcon 安装包后的第一反应是“装上能打开 HDevelop 就行”,结果往往卡在运行时找不到授权、32 位 64 位混用、或者换了电脑后环境变量没配好。这几个问题看着小,真到现场调试时会非常折磨人,建议一开始就把环境整理干净。
1.1 安装版本与运行库选择
Halcon 针对同一台电脑会提供多个安装项,我一般只关注两个:Development 环境和 Runtime 环境。开发机上装 Development,里面包含 HDevelop、例程、图像算子库和文档;部署到产线工控机上时只装 Runtime 就够了,体积小很多,也不容易被人误改开发环境。
版本位数也要提前定。Halcon 支持 32 位和 64 位两种库,但 HDevelop 和外部调用工程的位数必须对齐。一个非常典型的坑:你用 64 位 Halcon 在 HDevelop 里调通算法,然后用 Qt 工程去调用,结果工程编的是 32 位,链接时一堆 “无法解析的外部符号”。这就是 Arch 不一致导致的。建议新项目统一走 64 位,除非产线相机 SDK 或运动控制卡还停留在 32 位时代。
1.2 许可证的形态与常见误区
Halcon 正常安装完成后必须配置许可证,否则一开 HDevelop 就提示没有有效 license。常见形态有三种。
| 授权类型 | 使用范围 | 关键限制 |
|---|---|---|
| 试用版授权 | 本地评估、算法验证 | 有时限,部分模块可能限制分辨率或者提示水印 |
| 加密狗授权 | 开发机/工控机本地运行 | 需要安装对应驱动,拔掉狗就失效 |
| 联网浮动授权 | 多个开发机共享 | 依赖 license 服务器,现场断网会有风险 |
我见过不少人在网上找各种“替代方案”来解决授权,这里不推荐也不会展开,因为 Halcon 的授权检测会跟系统时间、网卡信息绑定,不稳定方案很容易导致项目现场突然失效。更稳妥的做法是:评估阶段直接联系官方申请试用授权,时间足够验证方案;项目落地阶段把授权成本提前算进报价里,避免交付时被掐住。
1.3 环境变量配置与首次启动的坑
安装完 Halcon 并配置好 license 后,还需要确认系统环境变量。比较关键的是HALCONROOT和HALCONARCH,安装程序一般会自动写入Path,但如果你手动清理过系统变量,或者装了多个版本,就很容易出现 HDevelop 能开但外部程序找不到halcon.dll的情况。
我给自己定的检查方法是三步:
- 打开命令行,输入
echo %HALCONROOT%,确认安装根目录路径正确; - 查看
HALCONARCH是x64-win64还是x86-win32,和开发工程位数对齐; - 在
Path中确认存在%HALCONROOT%\bin\%HALCONARCH%至少一条路径。
如果还要配合其他图像库或相机 SDK 使用,建议把 Halcon 的路径放在相对靠前的位置,避免某些依赖库被同名文件覆盖。首次启动 HDevelop 后,先在“算子窗口”里跑一个read_image和dev_open_window,能正常显示图片,就说明环境链路已经通了。
2. 图像预处理与边缘提取:自适应分割、抓边拟合和测量曲线长度
环境跑通后,第一优先级不是学某个炫酷算子,而是把“图像 -> 灰度 -> 区域/轮廓 -> 几何拟合”这条主链路练熟。一张图进算法,80% 的场景都要在这条链路上做文章。
2.1 图像读取与灰度转换的基础操作
Halcon 中读取图像最常用的是read_image,它会根据文件扩展名自动选择解码方式。彩色工业相机拍回的图像,很多算子默认处理的是灰度图,所以通常要用rgb1_to_gray或convert_image_type转一下。
这里有个细节:Halcon 的图像类型不只有 byte 灰度图,还有uint2、real等深度类型。如果你拿深度相机输出的 uint2 图直接跑threshold,会觉得阈值怎么也调不准,因为灰度范围和 byte 类型差了 256 倍。所以拿到新图先做两件事:查图像类型、看灰度直方图,别上来直接套固定阈值。
2.2 自适应边缘提取:光照不均时的分割逻辑
很多新手理解的边缘提取就是edges_sub_pix,但实际项目中“光照不均+背景复杂”的情况,光靠 Canny 类算子并不够。真正高效的思路是先用区域分割把 ROI 框出来,再做边缘精提取。
对于背景亮暗不均匀的场景,我会用dyn_threshold做自适应分割。它本质是把原图和经过平滑滤波的背景图做差,再按局部像素差判断区域归属。用gauss_filter或mean_image生成背景图,然后用dyn_threshold配合offset参数提取变化剧烈的区域。这个操作常被叫做自适应边缘提取,但它提取到的其实是“差异区域”,后续还要接轮廓算子。
如果目标边缘本身是明显的强边缘,就直接用edges_sub_pix提取亚像素轮廓。它比普通edges_image多一个亚像素细化过程,输出的是 XLD 轮廓对象,后续测量精度会比像素级边缘高一截。
2.3 抓边拟合直线:从亚像素轮廓到直线参数
在实际的视觉定位项目里,我们很少直接用边缘点坐标,而是要拟合出直线,再交给 PLC 或运动控制卡去执行纠偏。Halcon 里最常用的是hough_lines找到候选直线,再用fit_line_contour_xld做最小二乘拟合。
一个从实践中来经验:直接对所有边缘点做全局直线拟合,经常会被缺陷、划痕带偏。正确做法是先用segment_contour_xld把长轮廓拆成多个短轮廓,或者用select_shape_xld按长度筛选,只保留夹持边对应的那一段,再单独拟合。这样拟合出来的直线Row1, Column1, Row2, Column2参数才稳定,计算角度时用angle_lx或atan2都顺手。
2.4 测量曲线长度:轮廓整理与弧长计算
测量曲线长度也是高频需求,比如测软胶条的边缘长度、测不规则轮廓路径。Halcon 提供的contour_length可以直接计算一段 XLD 轮廓的总长度,但真实图片上轮廓往往是断断续续的,直接拿单条轮廓测长度一定会偏小。
我的处理顺序是:先用亚像素边缘提取出若干段轮廓,再用concat_xld把同一条目标的轮廓拼接起来,或者用select_contours_xld过滤掉太短的噪声段,最后才用contour_length统计。如果你需要的是“从某个起点到某个终点之间的长度”,可以先用clip_contours_xld裁剪感兴趣区间,再分别累加,避免把不相关的背景轮廓也算进去。
3. 定位任务里的经典组合:找圆、卡尺测量、OCR 与条码识别
这一章其实是把几个搜索热词串起来:halcon 找圆、halcon 测量、halcon ocr、halcon 条码识别、在图片写入文字。它们经常出现在同一个项目里,比如手机壳定位、封装外观检测、标签信息核对。
3.1 用卡尺找圆:不是“找到圆”而是“拟合圆”
Halcon 里找圆最稳的方案不是直接搜圆模型,而是“测量 + 拟合”。先用measure_pos或measure_pairs在设定 ROI 上做射线式灰度值采样,得到若干个边缘点,最后用fit_circle_contour_xld拟合圆心和半径。
参数调节优先级是这样:
measure_pos里的Sigma控制平滑程度,图像噪声大的时候先提高 Sigma,而不是盲目改边缘阈值;Threshold控制边缘幅度,注意它和检测到的边缘点数量不是线性关系,值太大会漏点;- 输出边缘点后,观察点在圆周上的分布是否均匀,如果都集中在某一段,拟合圆的稳定性会很差。
老实说,圆直径测量这种题目在 Halcon 里已经被封装得很成熟了,但现场最容易出问题的不是拟合算法,而是 ROI 没有跟随产品位置变化。高端的做法是用前一个工位传来的位置结果,动态gen_circle生成测量 ROI,而不是每次固定像素位置。
3.2 卡尺测量:从边缘对到宽度、间距
卡尺测量的核心思想是在矩形 ROI 内做一维灰度采样,找到多个边缘位置。Halcon 的measure_pairs会按“明-暗-明”或“暗-明-暗”成对输出边缘点,非常适合直接测宽度、间隙。
我一直强调要把“像素距离”和“实际物理距离”分开看。卡尺测出来的宽度是像素值,要换算成毫米,必须先标定像素当量,简单场景下用比例尺标定即可:放一个已知尺寸的工件,测同段距离,算出每毫米对应多少像素。精度要求高的情况再考虑畸变校正和标定板标定。
3.3 OCR 识别与条码识别:读取和标注
Halcon 的 OCR 现在最方便的思路是find_text,它会自动在图像中定位文本区域并识别内容,不再像老版本那样需要自己分割字符再逐个分类。对于印刷体数字和英文,这个算子开箱即用效果就不错。
但 OCR 对二值图质量非常敏感,尽量避免直接在灰度图上硬跑。可以先用threshold或局部自适应分割把文字区域转成清晰黑白图,必要时用dilate_char修复断笔,再送入识别。
条码识别相对简单,固定步骤是:
create_bar_code_model创建条码模型;set_bar_code_param指定条码类型,比如 EAN-13、Code128;find_bar_code在图像里寻找并解码;get_bar_code_result取出字符串结果。
条码识别的现场教训:反光会造成条码区域灰度饱和,解码失败概率很高。这时可以先对 ROI 做低通滤波,或者用invert_image处理暗底白条的情况,把条和空的对比关系恢复出来。
说到“在图片写入文字”,很多教程会直接让你用disp_message,但那只是往窗口上画,不会真正写进图像数据。如果你确实想“烧”到图像里保存,最实用的组合是:根据文字内容用get_string_extents算出外接框,生成一个文字区域的 ROI,再把它和原图一起做paint_region覆盖,最后用write_image保存。另外一条路线是直接调 Windows GDI 或 Qt 绘制后再转回 Halcon 图像,自由度更高。
4. 从深度图转点云到手眼标定:2D 视觉走向 3D 的衔接
很多项目做到后面都会发现,光靠平面定位处理不了叠料、高度倾斜和抓取深度问题。Halcon 在 3D 这块的优势是:不要求你从零实现点云算法,深度图转点云、点云匹配、手眼标定都有现成算子,关键是理解数据流转。
4.1 深度图转点云:核心是 XYZ 坐标图
深度图本身只是一个二维数组,灰度值代表距离或高度,要变成点云必须结合相机内参,把每个像素映射到相机坐标系下的 (X, Y, Z)。
Halcon 里常用的路径是:先用标定结果把深度图分别换算成 X 方向、Y 方向、Z 方向的三张坐标图,然后调用xyz_to_object_model_3d生成 ObjectModel3D 点云。这里最容易搞混的是坐标方向和尺度,比如 Z 轴的零点定义、单位是毫米还是米,必须先固定好,否则点云出来后面全是斜的或者反向的。
得到点云后,可以用visualize_object_model_3d直观查看。如果点云噪点大,先用smooth_object_model_3d或者下采样,再做平面分割或寻找曲面。这部分的重点是不要一上来就做复杂的点云匹配,先确认深度图和相机参数是不是处于同一个坐标系描述。
4.2 手眼标定的两种模式与实操顺序
手眼标定解决的核心问题是:让机器人的末端执行器知道“相机看到的物体,在机器人坐标系里到底在哪”。它分为 eye-in-hand(相机装在机器人末端)和 eye-to-hand(相机固定安装在工位上方)两种,Halcon 都支持。
实操时我建议这样推进:
- 先做相机自身标定,拿到内参和畸变系数;
- 把标定板固定在机器人工作空间内,确保相机能清晰看到标定板;
- 机器人带动或固定照多组不同位姿的标定板图像,记录每组机器人位姿;
- 使用
calibrate_hand_eye算子输入标定板图像和位姿记录,输出相机坐标系与机器人坐标系的变换关系; - 验证时用标定结果把某个已知点的相机坐标转换到机器人坐标,看是否与实测一致。
手眼标定现场最常见的翻车点是:标定过程中机器人位姿变化范围太小,导致计算出的变换矩阵在某个方向上的分量极不稳定。一般建议让标定板/相机姿态覆盖 20 度以上的角度变化,并保证平移分量有足够差异,宁可多拍几组,也不要只拍一个角度看世界。
4.3 手眼标定结果不收敛怎么办
很多人标定完发现结果抖动、误差偏大,第一反应是怀疑算法。其实大部分情况是输入数据的锅,优先级排查顺序应该是:
- 标定板是否发生滑动或变形;
- 标定图像是否过曝,找到的角点坐标是否准确;
- 机器人位姿记录是否和图像一一对应;
- 相机内参是否准确,内参错了后续手眼标定很难收敛。
我个人习惯是每次标定都做独立验证,而不是只看重投影误差。拿一个已知位置的标准工件实测一遍,如果转换后的坐标和机器人示教位置能对上毫米级,就说明这套手眼关系是可信的。
5. 算法工程化:导出 DLL 并用 Qt 封装的落地细节
把 HDevelop 里调通的流程搬到产线,最常见的方式就是封装成 DLL 供 C++/Qt 或 C# 调用。这也是搜索里“halcon 导出 dll”“qt 怎么调用 halcon”频繁出现的原因。很多人在这一步被卡住,但拆开来看其实不复杂。
5.1 从 HDevelop 流程到可导出接口
我不建议在 HDevelop 里把一大段脚本直接导出成 DLL,那样接口太乱,维护性差。更稳妥的方式是先把算法流程封装成单个或少量几个 HDevelop 过程(Procedure),每个过程只负责一件事,比如“图像进来,输出圆心坐标和直径”。
随后在 HDevelop 里选择导出为 C++ 代码,再用 Visual Studio 或 Qt 工程封装一个动态库项目,把导出的 C++ 文件编译成 DLL。导出函数最好只接收基本类型,比如字符串路径、double 数组、图像 buffer 地址,避免把 Halcon 的HObject直接暴露给外部程序。
有一点必须注意:如果外部程序负责采集图像,而你希望降低耦合,可以在 DLL 里只暴露两个函数,一个初始化模型,一个传入图像数据并返回结果。这样现场替换相机驱动时,只要保持这个接口不变,算法部分就不用动。
5.2 Qt 调用 DLL 时的内存和数据格式问题
Qt 调用 Halcon DLL 有两种常见方式:一种是直接用 QLibrary 在运行时加载,另一种是工程里直接链接 Halcon 的 lib 并包含头文件。运行时加载更灵活,适合版本升级;直接链接则编译期就能发现问题。
图像数据交接是这里最容易踩坑的点。Halcon 图像在内存里通常是连续存储的,如果你从 QImage 中取到 buffer 再传给 DLL,需要确认三个参数:宽度、高度、通道数。Halcon 默认的通道顺序和 QImage 不一定一致,比如 RGB 图可能分三个独立通道处理。我的建议是传递时统一转换为 byte 灰度图或单通道图像,否则颜色通道错乱会导致检查结果莫名不通过。
提示:在 DLL 内部用完 Halcon 对象后,一定要调用
Clear或者用 RAII 对象管理资源。Halcon 的图像对象如果忘记释放,长时间运行会持续占用内存,现场表现为“跑几个小时就变卡”。
5.3 部署时需要带上的运行文件和许可
DLL 封装好后,部署到产线时还要带上对应位数的 Halcon Runtime 安装包或运行库,不能只在开发机里能跑就觉得完事了。通常会复制halcon.dll、hdevengine.dll等核心文件到可执行程序目录,并配置好 license。
一个经常被忽略的问题:目标工控机上如果缺少 VC++ 运行库,Halcon 的 DLL 会加载失败,但错误提示可能很模糊。建议在部署文档里把安装步骤写成 checklist,逐项核对 Visual C++ Redistributable、Halcon Runtime、环境变量、授权状态。产线环境难调试,越早固化部署清单,后面越省心。
6. 进阶视觉能力:深度学习目标检测、光度立体与高斯差分的使用时机
聊完基础测量和工程化,再补几个搜索热度很高的进阶方向:Halcon 深度学习目标检测、光度立体融合,以及高斯差分。这些功能不是每个项目都用得上,但一旦用上,往往能解决传统算法搞不定的难题。
6.1 基于 Halcon 的深度学习目标检测训练流程
Halcon 从较早版本就开始内置深度学习模块,训练和推理都可以在 HDevelop 里完成。目标检测任务的大致流程是:准备标注数据集、创建检测模型、训练、评估、部署。
数据集准备阶段最花时间,也最决定最终效果。Halcon 的标注工具支持画矩形框,但如果你已经用其他工具标注成 COCO 或 Pascal VOC 格式,可以直接转换。训练时不要盲目加大迭代次数,先观察验证集上的 loss 变化。如果精度上不去,优先检查样本数量是否足够、目标形态是否过于单一,而不是疯狂调学习率。
部署时,apply_dl_model会输出检测框、类别和置信度。你可以在 HDevelop 里用set_dl_model_param调整置信度阈值,实际产线中宁可漏检率高一点,也不要让误检框满天飞,因为后端剔除逻辑往往比算法更脆弱。
6.2 光度立体融合:把多角度打光变成表面信息
光度立体技术的思路是:固定相机,用不同方向的光源分别照亮物体,拍下多张图像,然后通过光度学关系计算每个像素的法向量、反照率和相对高度。Halcon 的photometric_stereo算子可以接受多张图像和对应光源倾斜/方位角参数,输出梯度图、法向图或反照率图。
这个方案尤其适合外观缺陷检测,比如金属表面的划痕、压痕、异物,在普通白光下对比度很低,但切换到不同方向光源后,表面法向变化会非常明显。需要注意的点是:光源方向参数要标定准确,最好用标准球或已知反射体校验;物体表面不能太复杂,否则多角度反射会产生虚假阴影。
6.3 高斯差分:缺陷增强和纹理过滤的实用技巧
高斯差分(DoG)在 Halcon 里通常不直接叫“高斯差分算子”,而是用两个不同尺度的高斯滤波后相减来实现。比如先用gauss_filter做小 Sigma 平滑,再用大 Sigma 平滑,最后sub_image得到差分图。这个结果能滤除低频背景,保留高频边缘和细节,非常适合缺陷增强前的预处理。
实际业务里我经常把 DoG 和动态阈值结合起来:先差分突出缺陷,再做dyn_threshold自适应分割,最后用select_shape过滤小噪声区域。这套组合对付印刷缺陷、表面污点、细微划痕都很稳定,而且不用一开始就上深度学习。毕竟能靠传统图像处理稳定解决的问题,就不要把产线搞得过于复杂。
最后再分享一个项目中的体会:Halcon 学得快不快,很大程度上取决于你能不能把每个算子的输入输出都想清楚。图像和区域是对象,几何拟合输出的是参数;把数据流理清,很多高阶功能其实就是一层窗户纸。真想在某个方向走深,先把上面这些基础场景动手跑一遍,再针对自己的行业案例反复迭代参数,比什么资料都有用。