2、Halcon基础回顾:图像数据结构、坐标系定义、常用图像采集与显示方法

在深入探讨ROI(Region of Interest)动态设置与坐标系统一之前,我们必须先夯实Halcon开发中最核心的地基。ROI操作的本质是对图像数据在特定坐标系下的像素集合进行逻辑运算与几何变换。如果对图像数据的存储结构理解不透彻,对坐标系转换的规则掌握不牢,后续的“动态设置”与“统一”将无从谈起。本章将系统梳理Halcon中的图像数据结构(HObject与HImage)、四大核心坐标系(像素、图像、世界、相机)的定义与转换逻辑,以及图像采集与显示的标准流程,为后续章节的实战操作提供坚实的理论支撑。

Halcon 图像数据与坐标系核心链路 图像采集 Image Acquisition 相机/文件/仿真 grab_image HImage / HObject 通道数: 1/3 (灰度/RGB) 类型: byte/int2/uint2/real 宽W × 高H × 通道C 像素坐标系 原点: 左上角 (0,0) 单位: 像素 (Row, Col) 用于ROI/Region定义 转换 图像坐标系 原点: 主点 (Cx, Cy) 单位: 毫米 (mm) 物理尺寸: 像元大小×像素 相机坐标系 原点: 光心 (Xc, Yc, Zc) Z轴: 光轴方向 单位: 毫米 (mm) 世界坐标系 原点: 用户自定义基准 单位: 毫米/米 (mm/m) 机器人/运动控制基准 相机内参 外参 像元尺寸 显示与ROI操作 disp_obj (窗口句柄) | gen_rectangle1 / gen_circle (ROI生成) | reduce_domain (ROI裁剪) 图例 采集/数据 HObject 坐标系 显示/ROI

上图清晰地展示了从图像采集到显示的全链路中,数据对象与坐标系之间的映射关系。在Halcon中,图像数据以HImage(继承自HObject)的形式存在,而ROI本质上也是HObject的一种(Region)。理解它们之间的转换关系,是掌握ROI动态设置的前提。

2.1 图像数据结构:HObject与HImage的底层逻辑

核心原理:句柄式管理与数据封装

Halcon的核心数据对象是HObject,它是一个抽象基类,涵盖了图像(Image)、区域(Region)、轮廓(XLD)等所有图形数据。而HImage是HObject在图像领域的具体实现。在C++或C#接口中,我们通常直接操作HImage对象,但在底层,Halcon通过句柄(Handle)机制管理内存。这意味着,当你执行HImage image;时,只是创建了一个空句柄,真正的数据在调用ReadImage()或GrabImage()后才被分配。

图像数据在内存中并非简单的二维数组,而是由通道(Channel)、类型(Type)和宽高(Width/Height)共同定义的。例如,一张彩色图像通常包含3个通道(R、G、B),每个通道是一个独立的矩阵。Halcon支持多种像素类型,包括byte(0-255)、int2(-32768-32767)、uint2(0-65535)、real(浮点)等。对于ROI操作而言,byte类型是最常见的,因为灰度图像和大多数彩色图像都基于此。

关键点:ROI(Region)与图像(Image)是两种不同的HObject。Region是二值化的像素集合(0或1),而Image是灰度值矩阵。当你对图像应用ROI时,实际上是通过reduce_domain操作,将图像的定义域(Domain)裁剪为ROI区域,从而生成一个新的HImage,该图像仅包含ROI内的像素数据。

操作流程:创建与访问图像数据

  1. 创建图像对象:使用GenImageConst创建指定类型和尺寸的空图像,或使用ReadImage从磁盘加载。
  2. 获取图像属性:通过GetImageSize获取宽高,GetImageType获取类型,CountChannels获取通道数。
  3. 访问像素值:使用GetGrayval获取单点像素值,或使用GetRegionPoints获取区域内的所有点坐标。
// C++ 示例:读取图像并获取基本信息
HImage image;
image.ReadImage("example.png");
int width, height;
image.GetImageSize(&width, &height);
HString type;
image.GetImageType(&type);
int channels = image.CountChannels();
// 输出: Width=640, Height=480, Type=byte, Channels=3

案例拆解:灰度图与彩色图的ROI差异

假设我们有一张工业相机采集的彩色图像(3通道),需要提取其中的一个矩形区域进行缺陷检测。如果直接使用reduce_domain,得到的仍然是3通道图像。但在某些算法(如阈值分割)中,我们可能需要先将其转换为灰度图。此时,Rgb1ToGray将3通道图像转换为单通道灰度图,然后再定义ROI。这种转换会影响后续的像素值分布,因此在实际项目中,必须明确ROI操作是在哪个通道上进行的。

常见误区:初学者常将Region与Image混淆。例如,threshold操作返回的是Region,而不是Image。如果直接对Region调用GetGrayval,会报错。必须通过reduce_domain将Region应用到Image上,才能获取像素值。

2.2 坐标系定义:从像素到世界的四层映射

核心原理:四大坐标系的物理意义

在机器视觉中,坐标系是连接图像与物理世界的桥梁。Halcon中主要涉及四个坐标系:

坐标系原点位置单位典型应用
像素坐标系图像左上角 (0,0)像素 (pixel)ROI定义、Region操作
图像坐标系相机主点 (Cx, Cy)毫米 (mm)畸变校正、物理尺寸测量
相机坐标系光心 (Xc, Yc, Zc)毫米 (mm)3D重建、位姿估计
世界坐标系用户自定义基准毫米/米 (mm/m)机器人引导、多相机拼接

像素坐标系的Row(行)对应Y轴,Column(列)对应X轴。这与数学中常见的笛卡尔坐标系不同,容易混淆。图像坐标系的原点位于相机光轴与成像平面的交点(主点),其X轴向右,Y轴向下,单位是毫米。从像素坐标系到图像坐标系的转换需要知道像元尺寸(Pixel Size)和主点位置。

判断方法:何时使用哪个坐标系?

  • ROI动态设置:通常在像素坐标系下进行,因为ROI是基于图像像素的几何形状(矩形、圆、多边形)。
  • 物理测量:必须转换到图像坐标系或世界坐标系,否则结果会受分辨率影响。
  • 机器人抓取:必须使用世界坐标系,通过标定(Calibration)将像素坐标映射到机器人基座标系。

案例拆解:像素坐标到世界坐标的转换链路

假设我们使用一个500万像素相机(2448×2048),像元尺寸为3.45μm,主点位于(1224, 1024)。我们检测到一个缺陷的像素坐标为(1000, 1500)。要计算其在图像坐标系下的物理位置:

// 像素坐标 (Row, Col) = (1000, 1500)
// 图像坐标 X = (Col - Cx) * PixelSize = (1500 - 1224) * 0.00345 mm = 0.9522 mm
// 图像坐标 Y = (Row - Cy) * PixelSize = (1000 - 1024) * 0.00345 mm = -0.0828 mm

注意,Y轴向下为正,因此负值表示缺陷在主点上方。若要转换到世界坐标系,还需要知道相机的外参(旋转矩阵R和平移向量T),这通常通过CalibrateCamera和HandEyeCalibration获得。

经验提醒:在Halcon中,image_to_world_plane操作可以直接将图像坐标映射到世界坐标平面,但前提是已完成相机标定。对于ROI动态设置,如果ROI是固定的物理区域(如传送带上的检测窗口),建议在标定后直接使用世界坐标定义ROI,避免重复转换。

2.3 常用图像采集与显示方法

核心原理:采集接口与显示窗口管理

Halcon通过Image Acquisition Interface统一管理不同品牌的相机(如Basler、FLIR、海康威视)。采集流程分为三步:打开设备(OpenFramegrabber)、参数设置(SetFramegrabberParam)、抓取图像(GrabImage)。显示方面,Halcon使用HWindow窗口句柄,通过DispObj显示图像或Region。

操作流程:标准采集与显示代码模板

// 采集示例(以GigE相机为例)
HTuple hv_AcqHandle;
OpenFramegrabber("GigEVision2", 0, 0, 0, 0, 0, 0, "default", -1, "default", -1, "false", "default", "cam1", 0, -1, &hv_AcqHandle);
SetFramegrabberParam(hv_AcqHandle, "ExposureTime", 5000.0); // 设置曝光时间
HImage image;
GrabImage(&image, hv_AcqHandle);

// 显示示例
HWindow window(0, 0, 640, 480, 0, "visible", "");
window.DispObj(image);
// 显示ROI区域
HRegion roi;
roi.GenRectangle1(100, 100, 300, 300);
window.DispObj(roi);

案例拆解:实时采集中的ROI动态更新

在高速检测场景中,ROI可能需要根据产品位置实时移动。例如,一个贴片机需要检测PCB板上的元件位置。我们通过模板匹配(FindShapeModel)获取元件中心坐标,然后动态生成一个圆形ROI进行缺陷检测。此时,采集线程与ROI更新线程需要同步,避免使用过期的坐标。

实战场景:假设相机帧率为30fps,ROI更新频率必须高于30Hz。在C++中,可以使用HImage的浅拷贝特性(Clone)来避免数据竞争。同时,显示窗口应使用SetPart设置显示区域,避免图像缩放导致的视觉误差。

常见误区与总结复盘

  • 误区1:忘记设置SetPart,导致显示图像被拉伸,ROI位置看起来偏移。解决:显示前调用window.SetPart(0, 0, height-1, width-1)。
  • 误区2:在循环中频繁OpenFramegrabber和CloseFramegrabber,导致采集延迟。解决:初始化一次,循环抓取。
  • 误区3:混淆Row和Col的顺序。在GenRectangle1中,参数顺序是(Row1, Col1, Row2, Col2),而不是(X1, Y1, X2, Y2)。