关于如何通过 HALCON/C++ 接口调用 HALCON 算子,详见 HALCON 算子参考手册。例如,图 5.1 显示了算子 MeanImage 的部分条目。
|
|
| Image (input_object) | (multichannel-)image(-array) → HImage (byte / int2 / uint2 / int4 / int8 / real / vector_field) |
| ImageMean (output_object) | (multichannel-)image(-array) → HImage (byte / int2 / uint2 / int4 / int8 / real / vector_field) |
| MaskWidth (input_control) | extent.x → HTuple (Hlong) |
| MaskHeight (input_control) | extent.y → HTuple (Hlong) |
图 5.1:mean_image 参考手册条目的头部和参数部分。
请注意,参考手册并未列出算子的所有可能签名。完整列表可在 include\halconcpp\HOperatorSet.h 文件中找到。
下面,我们
HALCON 将参数分为两类:图标参数和控制参数。图标参数与原始图像(图像、区域、XLD 对象)相关,而控制参数是整数、浮点数、字符串或句柄等值。
控制参数的一种特殊形式是所谓的句柄。这种类型的一个著名代表是窗口句柄,它提供了对已打开的 HALCON 窗口的访问,例如在其中显示图像。此外,当算子共享复杂数据时也会用到句柄,例如基于形状的匹配算子,它可以创建并使用模型数据,或者用于访问输入/输出设备,例如图像采集设备。封装句柄的类将在 "封装句柄的类" 一节 中详细介绍。
图标参数和控制参数都可以作为 HALCON 算子的输入和输出参数出现。例如,算子 MeanImage 需要一个图标输入参数、一个图标输出参数和两个输入控制参数(见 图 5.1 );图 5.2 显示了一个具有所有四种参数类型的算子。请注意,如果通过类调用算子,有些参数会从括号中 "消失";"通过类调用算子" 一节 将详细介绍这种机制。
HALCON 关于参数的一个重要理念是输入参数不会被算子修改。因此,它们通过值传递(如 图 5.1 中的 Hlong MaskWidth)或通过常量引用传递(如 const HObject& Image)。如果通过类调用算子,调用实例作为输入参数,那么这一理念也同样适用。因此,在下面的示例代码中,调用 MeanImage 时不会修改原始图像;而是通过返回值提供算子的结果,即平滑图像:
HImage original_image("monkey");
HImage smoothed_image = original_image.MeanImage(11, 11);
|
|
| Image (input_object) | singlechannelimage → HImage (byte) |
| SymbolRegions (output_object) | region(-array) → HRegion |
| BarCodeHandle (input_control) | barcode → HTuple (HHandle) |
| CodeType (input_control) | string(-array) → HTuple (HString) |
| DecodedDataStrings (output_control) | string(-array) → HTuple (HString) |
图 5.2:find_bar_code 参考手册条目的头部和参数部分。
与输入参数不同,输出参数总是被修改的,因此必须通过引用传递。请注意,算子需要指向一个已经存在的变量或类实例的指针!例如,在调用算子 FindBarCode 时,如下行代码所示,在使用算子 & 传递相应指针之前,先声明 HTuple 类变量。
HImage image("barcode/ean13/ean1301");
HBarCode barcode(HTuple(), HTuple());
HString result;
HRegion code_region = barcode.FindBarCode(image, "EAN-13", &result);
上例显示了输出参数的另一个有趣方面: 通过类调用算子时,一个输出参数可能成为返回值(详见 "通过类调用算子" 一节 );在本例中,FindBarCode 返回条形码区域。
许多 HALCON 算子接受多个特定参数值。例如,可以使用图像数组调用算子 MeanImage(见 图 5.1 ),然后返回平滑图像数组。这就是所谓的元组模式;更多信息,参见 "元组模式" 一节 。
|
|
| Name (input_control) | string → HTuple (HString) |
| Query (input_control) | string → HTuple (HString) |
| Information (output_control) | string → HTuple (HString) |
| ValueList (output_control) | string-array → HTuple (HString / Hlong / double) |
图 5.3: info_framegrabber 参考手册条目的头部和参数部分。
无论 HALCON 库的编码方式如何(set_system('filename_encoding', ...)),HALCON/C++ 接口都希望传递给 HALCON 算子和 HTuple 或 HString 实例的原始字符指针字符串采用 UTF-8 编码。
输出字符串始终是 HString 类型,具有自动内存管理功能。这些字符串默认也是 UTF-8 编码。可以通过调用 HalconCpp::SetHcppInterfaceStringEncodingIsUtf8(false) 将 HALCON/C++ 接口的编码(接口编码)更改为本地 8 位编码。可以通过 HalconCpp::IsHcppInterfaceStringEncodingUtf8() 获取当前的接口编码。不建议来回切换接口编码。该设置只应在程序开始时(第一个 HALCON 算子或赋值之前)调整一次,因为 HTuple 实例无法存储所包含字符串的编码,也就是说,对于所有写入和读取访问,必须设置相同的编码。此外,接口编码是全局设置的,因此不适合多线程程序: 在一个线程中更改设置会对其他线程产生影响。
在下面的示例代码中,算子 InfoFramegrabber(另见 图 5.3 )通过两个输出字符串参数被调用,以查询当前安装的图像采集卡:
HString sInfo, sValue; InfoFramegrabber(FGName, "info_boards", &sInfo, &sValue);
请注意,也无需为以 HTuple 返回的多个输出字符串参数分配内存:
HTuple tInfo, tValues; InfoFramegrabber(FGName, "info_boards", &tInfo, &tValues);
如上一节所述,HALCON/C++ 参考手册说明了可以通过哪些类调用算子。例如,可以通过 HImage 或 HBarCode 类对象调用 FindBarCode(见 图 5.2 )。在这两种情况下,相应的输入参数(分别为 Image 或 BarCodeHandle)不会再出现在括号中,因为它已被调用类的实例(this)所取代。
程序算子签名还有一个不同之处: 第一个输出参数(在本例中为条形码区域 SymbolRegions)也从括号中消失,成为返回值而不是错误代码(有关错误处理的更多信息,请参阅 "错误处理" 一节 )。
HImage image("barcode/ean13/ean1301");
HBarCode barcode(HTuple(), HTuple());
HString result;
HRegion code_region = barcode.FindBarCode(image, "EAN-13", &result);
HRegion code_region = image.FindBarCode(barcode, "EAN-13", &result);
HObject image; HTuple barcode; HObject code_region; HTuple result; ReadImage(&image, "barcode/ean13/ean1301"); CreateBarCodeModel(HTuple(), HTuple(), &barcode); FindBarCode(image, &code_region, barcode, "EAN-13", &result);
图 5.4: 通过 HBarCode、HImage 或过程式方法使用 FindBarCode。
图 5.4 展示了调用 FindBarCode 的三种方法的代码示例。在比较面向对象方法和过程式方法时,可以看到对算子 ReadImage 和 CreateBarCodeModel 的调用分别被 HImage 和 HBarCode 类的特殊构造函数所取代。下文将详细讨论这一主题。
如 图 5.4 所示,HALCON/C++ 参数类提供了额外的构造函数,这些构造函数基于合适的 HALCON 算子。示例中使用的 HImage 和 HBarCode 构造函数分别基于 ReadImage 和 CreateBarCodeModel。
经验法则:如果一个类仅作为输出参数出现在一个算子中,那么就会自动存在一个基于该算子的构造函数。因此,如 图 5.4 所示,HBarCode 的实例可以基于 CreateBarCodeModel 来构造,HShapeModel 的实例可以基于 CreateShapeModel 来构造,HFramegrabber 的实例可以基于 OpenFramegrabber 来构造,等等。需要注意的是,对于存在许多此类算子的类(如 HImage),只有具有明确参数列表的常用算子子集被实际用作构造函数。
此外,所有类都有空构造函数来创建未初始化的对象。例如,您可以使用默认构造函数创建一个 HBarCode 实例,然后使用 CreateBarCodeModel 对其进行初始化,如下所示:
HBarCode barcode; barcode.CreateBarCodeModel(HTuple(), HTuple());
如果实例已经初始化,则在重新构造和初始化之前,会自动销毁相应的数据结构(另见 "析构函数和 Halcon 算子" 一节 )。"其他句柄类" 一节 对句柄类有更详细的介绍。
HImage image; // still uninitialized image.ReadImage("clip");
下面我们将简要介绍最重要的类。可用构造函数的最新完整列表可在 HALCON 算子参考和 %HALCONROOT%\include\cpp 中的相应头文件中找到。
当然,您也可以使用 CloseWindow 关闭窗口,然后再使用 OpenWindow 打开它。与图标参数类不同的是,您可以通过 HWindow 实例以直观的方式调用 "类构造函数" 算子 OpenWindow,即修改调用的实例,并返回相应的句柄。HWindow 在 "窗口 " 一节 中有更详细的描述。
所有 HALCON/C++ 类都提供了默认的析构函数,可以自动释放相应的内存。
封装句柄的类,如 HShapeModel 或 HFramegrabber,其默认析构函数的工作原理分别类似于 ClearShapeModel 或 CloseFramegrabber 这样的成员。
无需调用这些算子,因为您可以按照 "构造函数和 Halcon 算子" 一节 中的说明重新初始化实例。
基本上,我们将销毁句柄和销毁底层数据结构区分开来。销毁数据结构有两种方式: 当数据结构的最后一个引用被删除时自动销毁。通过调用操作符(如 CloseWindow)明确销毁。显式销毁会使引用失效,但访问是安全的。
正如在 "深入了解参数" 一节 中提到的,许多 HALCON 算子可以在所谓的元组模式下调用。在这种模式下,您可以通过一次调用将算子应用于多个图像或区域。标准情况下,例如使用单个图像调用算子,称为简单模式。算子是否支持元组模式,可在参考手册中查看。例如,请看 图 5.5 ,这是算子 CharThreshold 的参考手册条目摘录: 在参数部分,参数 Image 被描述为 image(-array);这表明您可以将算子同时应用于多个图像。
|
|
| Image (input_object) | singlechannelimage(-array) → HImage (byte) |
| HistoRegion (input_object) | region → HRegion |
| Characters (output_object) | region(-array) → HRegion |
| Sigma (input_control) | number → HTuple (double) |
| Percent (input_control) | number → HTuple (double / Hlong) |
| Threshold (output_control) | integer(-array) → HTuple (Hlong) |
图 5.5:参考手册中 CharThreshold 条目的头部和参数部分。
如果使用多个图像(即图像元组)调用 CharThreshold,输出参数也会自动变成元组。因此,参数 Characters 和 Threshold 分别描述为 region(-array) 和 integer(-array)。
请注意,HTuple 类也可以包含混合类型的控制参数数组(元组);有关该类的更多信息,请参阅 "元组" 一节 。与控制参数相反,图标参数在两种模式下都是 HObject 类的实例,因为该类既可以包含单个对象,也可以包含对象数组。
在面向对象方法中,控制参数可以是基本类型(仅简单模式),也可以是 HTuple 实例(简单和元组模式)。
在做完这些理论性较强的介绍之后,让我们来看两个例子,这两个例子都是用面向对象和过程式方法实现的。这两个例子突出了一些有趣的要点:
请注意,对象索引以 1 开始(如 SelectObj 所用)。
第一个示例展示了如何在简单模式下应用 CharThreshold,即对单张图像进行应用:
// object-oriented approach HImage image("alpha1"); HRegion region; Hlong threshold; region = image.CharThreshold(image.GetDomain(), 2, 95, &threshold); image.DispImage(window); region.DispRegion(window); cout << "Threshold for 'alpha1': " << threshold;
// procedural approach HObject image; HObject region; HTuple threshold; ReadImage(&image, "alpha1"); CharThreshold(image, image, ®ion, 2, 95, &threshold); DispObj(image, window); DispObj(region, window); cout << "Threshold for 'alpha1': " << threshold.ToString();
第二个示例展示了 CharThreshold 如何在元组模式下应用,即同时应用于两幅图像:
// object-oriented approach HImage images; HRegion regions; HTuple thresholds; images.GenEmptyObj(); for (int i = 1; i <= 2; i++) { images = images.ConcatObj(HImage(HTuple("alpha") + i)); } regions = images.CharThreshold(images.GetDomain()[1], 2, 95, &thresholds); for (int i = 1; i <= images.CountObj(); i++) { images[i].DispImage(window); regions[i].DispRegion(window); cout << "Threshold for 'alpha" << i << "': " << thresholds[i - 1].L(); window.Click();
// procedural approach HObject images, image; HObject regions, region; HTuple num; HTuple thresholds; GenEmptyObj(&images); for (int i = 1; i <= 2; i++) { ReadImage(&image, HTuple("alpha") + i); ConcatObj(images, image, &images); } CharThreshold(images, image, ®ions, 2, 95, &thresholds); CountObj(images, &num); for (int i = 0; i < num; i++) { SelectObj(images, &image, i + 1); DispObj(image, window); SelectObj(regions, ®ion, i + 1); DispObj(region, window); cout << "Threshold for 'alpha" << i + 1 << "': " << thresholds[i].L(); }