HALCON/C++ 接口的基础知识
前言
HALCON/C++ 接口提供了两种不同的方法来在 C++ 程序中使用 HALCON 的功能:过程方法和面向对象方法。 过程方法对应于在 C 或 HDevelop 中直接调用 HALCON 运算符,例如:
HObject original_image, smoothed_image;
ReadImage(&original_image, "monkey");
MeanImage(original_image, &smoothed_image, 11, 11);
除了过程方法之外,HALCON/C++ 允许以面向对象的方式调用 HALCON 运算符,即通过一组类。 例如,上面的代码可以“翻译”为:
HImage original_image("monkey");
HImage smoothed_image = original_image.MeanImage(11, 11);
这个简单的例子已经表明,这两种方法会导致明显不同的代码:运算符调用的参数数量和类型不同。 此外,功能可能以不同的方式提供; 例如,可以通过类 HImage 的构造函数从文件中读取图像。 一般来说,我们建议使用面向对象的方法。 但是请注意,HDevelop 只能将程序导出为过程 C++ 代码。
1、 The Namespace HalconCpp
从 HALCON 11 开始,HALCON/C++ 的所有函数和类都使用命名空间 HalconCpp 来防止与其他 C++ 库的潜在名称冲突。
您可以通过三种方式指定(“使用”)命名空间:
- 具体来说,通过在每个类名或操作符调用前加上命名空间
HalconCpp::HObject original_image, smoothed_image;
HalconCpp::ReadImage(&original_image, "monkey");
- 在本地,通过使用命名空间 HalconCpp 放置指令; 在块的开头,例如,在函数的开头:
int main(int argc, char *argv[])
{
using namespace HalconCpp;
HObject original_image, smoothed_image;
ReadImage(&original_image, "monkey");
}
然后,您可以在此块中使用 HALCON 的类和函数而无需前缀。
- 全局,通过在包含 HalconCpp.h 之后直接放置指令 using。 然后,您不需要在整个应用程序中使用前缀。
#include<HalconCpp.h>
using namespace std;
using namespace HalconCpp;
哪种方法最合适取决于您的应用程序,更确切地说取决于它包含的其他库以及是否存在名称冲突。
2、 调用 HALCON 运算符
HALCON 操作员参考手册中详细描述了如何通过 HALCON/C++ 接口调用 HALCON 操作员。 例如,图 5.1 显示了运算符 MeanImage 的部分条目。
请注意,参考手册并未列出操作员的所有可能签名。 完整列表可以在文件 include\halconcpp\HOperatorSet.h 中找到。
mean_image 参考手册条目的参数部分的头部和部分
void MeanImage (const HObject& Image, HObject* ImageMean, const HTuple& MaskWidth,
const HTuple& MaskHeight)
HImage HImage::MeanImage (Hlong MaskWidth, Hlong MaskHeight) const
//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)
2.1 仔细观察参数
HALCON 区分了两种类型的参数:图标参数和控制参数。图标参数与原始图像(图像、区域、XLD 对象)相关,而控制参数是各种字母数字值,例如整数、浮点数或字符串。
一种特殊形式的控制参数是所谓的句柄。这种类型的一个众所周知的代表是窗口句柄,它提供对打开的 HALCON 窗口的访问,例如,在其中显示图像。此外,当操作符共享复杂数据时使用句柄,例如,用于创建并使用模型数据的基于形状匹配的操作符,或用于访问输入/输出设备,例如图像采集设备。封装句柄的类在第 51 页的 6.2.2 节中有详细描述。
图标参数和控制参数都可以作为 HALCON 运算符的输入和输出参数出现。例如,算子 MeanImage 需要一个图标输入参数、一个图标输出参数和两个输入控制参数(见图 5.1);图 5.2 显示了一个具有所有四种参数类型的运算符。请注意,如果您通过类调用运算符,某些参数如何从括号内“消失”;该机制在第 39 页的 5.2.2 节中有更详细的描述。
HALCON 关于参数的理念的一个重要概念是输入参数不会被操作员修改。因此,它们通过值(例如,图 5.1 中的 Hlong MaskWidth)或通过常量引用(例如,const HObject& Image)传递。如果通过类调用运算符,调用实例充当输入参数,则此原理也适用。因此,在以下示例代码中,调用 MeanImage 不会修改原始图像;运算符的结果,即平滑的图像,通过返回值提供:
HImage original_image("monkey");
HImage smoothed_image = original_image.MeanImage(11, 11);
与输入参数相反,输出参数总是被修改,因此它们必须通过引用传递。 请注意,运算符需要一个指向已经存在的变量或类实例的指针! 例如,当如下代码行调用运算符 FindBarCode 时,HTuple 类的变量在使用运算符 & 传递相应的指针之前被声明。
find_bar_code 参考手册条目的参数部分的头部和部分
void FindBarCode (const HObject& Image, HObject* SymbolRegions,
const HTuple& BarCodeHandle, const HTuple& CodeType, HTuple* DecodedDataStrings)
HRegion HBarCode::FindBarCode (const HImage& Image, const HTuple& CodeType,
HTuple* DecodedDataStrings) const
HRegion HBarCode::FindBarCode (const HImage& Image, const HString& CodeType,
HString* DecodedDataStrings) const
HRegion HBarCode::FindBarCode (const HImage& Image, const char* CodeType,
HString* DecodedDataStrings) const
HRegion HImage::FindBarCode (const HBarCode& BarCodeHandle, const HTuple& CodeType,
HTuple* DecodedDataStrings) const
HRegion HImage::FindBarCode (const HBarCode& BarCodeHandle, const HString& CodeType,
HString* DecodedDataStrings) const
HRegion HImage::FindBarCode (const HBarCode& BarCodeHandle, const char* CodeType,
HString* DecodedDataStrings) const
Image (input_object) . . . . . . singlechannelimage ❀ HImage (byte / uint2)
SymbolRegions (output_object) . . . . . . .region(-array) ❀ HRegion
BarCodeHandle (input_control) . . . . . .barcode ❀ HTuple (Hlong)
CodeType (input_control) . . . . . . . . string(-array) ❀ HTuple (HString)
DecodedDataStrings (output_control) . . .string(-array) ❀ HTuple (HString)
HImage image("barcode/ean13/ean1301");
HBarCode barcode(HTuple(), HTuple()); HString result;
HRegion code_region = barcode.FindBarCode(image, "EAN-13",&result);
面的例子展示了输出参数的另一个有趣的方面:当通过类调用操作符时,一个输出参数可能会成为返回值(更多细节请参见第 2.2 节); 在示例中,FindBarCode 返回条码区域。
许多 HALCON 运算符对某些参数接受多个值。 例如,您可以使用图像数组调用运算符 MeanImage; 然后,返回一组平滑的图像。 这称为元组模式;
String Parameters
输出字符串总是具有自动内存管理的 HString 类型。 在以下示例代码中,使用两个输出字符串参数调用操作符 InfoFramegrabber(参见图 5.3)以查询当前安装的图像采集板:
info_framegrabber 参考手册条目的参数部分的头部和部分
void InfoFramegrabber (const HTuple& Name, const HTuple& Query, HTuple* Information,
HTuple* ValueList)
static HString HInfo::InfoFramegrabber (const HString& Name, const HString& Query,
HTuple* ValueList)
static HString HInfo::InfoFramegrabber (const char* Name, const char* Query,
HTuple* ValueList)
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)
HString sInfo, sValue;
InfoFramegrabber(FGName, "info_boards", &sInfo, &sValue);
请注意,也没有必要为作为 HTuple 返回的多个输出字符串参数分配内存:
HTuple tInfo, tValues;
InfoFramegrabber(FGName, "info_boards", &tInfo, &tValues);
2.2 通过类调用运算符
如上一节所述,HALCON/C++ 参考手册显示了可以通过哪些类调用运算符。例如,FindBarCode 可以通过类 HImage 或 HBarCode 的对象调用(参见第 38 页的图 5.2)。在这两种情况下,相应的输入参数(分别为 Image 或 BarCodeHandle)不再出现在括号内,因为它被类的调用实例 (this) 替换。
过程操作符签名还有一个进一步的区别:第一个输出参数(在示例中是条码区域 SymbolRegions)也从括号内消失,成为返回值而不是错误代码(有关错误处理的更多信息,请参见第 45 页上的第 5.3 节)。
图 5.4 描述了调用 FindBarCode 的三种方式的代码示例。在比较面向对象和过程方法时,您可以看到对操作符 ReadImage 和 CreateBarCodeModel 的调用分别被类 HImage 和 HBarCode 的特殊构造函数所替代。下面将更详细地讨论该主题。
通过 HOmage 或在程序方法中使用 Find BarCode 到 BarCode。
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);
2.3 构造函数和 Halcon 运算符
从图 5.4 中可以看出,HALCON/C++ 参数类提供了额外的构造函数,这些构造函数基于合适的 HALCON 运算符。示例中使用的 HImage 和 HBarCode 的构造函数分别基于 ReadImage 和 CreateBarCodeModel。
作为一个经验法则:如果一个类只作为一个操作符中的输出参数出现,那么自动存在一个基于这个操作符的构造函数。这样,HBarCode 的实例可以基于 CreateBarCodeModel 构建,如图 5.4 所示,HShapeModel 的实例基于 CreateShapeModel,HFramegrabber 的实例基于 OpenFramegrabber 等。请注意,对于存在许多此类运算符的类(例如,HImage),实际上仅将具有明确参数列表的常用运算符的子集用作构造函数。
此外,所有类都有空构造函数来创建未初始化的对象。例如,您可以使用默认构造函数创建 HBarCode 的实例,然后使用 CreateBarCodeModel 对其进行初始化,如下所示:
HBarCode barcode;
barcode.CreateBarCodeModel(HTuple(), HTuple());
如果实例已经初始化,则相应的数据结构会在构造和重新初始化它们之前自动销毁
HImage image; // still uninitialized
image.ReadImage("clip");
下面我们简要介绍一下最重要的类。 可以在 HALCON 运算符参考和 %HALCONROOT%\include\cpp 中的相应头文件中找到可用构造函数的完整和最新列表。
-
Images:
类 HImage 提供基于运算符 ReadImage、GenImage1 和 GenImageConst 的构造函数。 -
Regions:
类 HRegion 提供了基于操作符(如 GenRectangle2 或 GenCircle)的构造函数。 -
Windows:
HWindow 类提供了一个基于操作符 OpenWindow 的构造函数。
当然,您可以使用 CloseWindow 关闭窗口,然后使用 OpenWindow 再次打开它。 相对于标志性的参数类,你可以通过HWindow的一个实例以直观的方式调用“类构造函数”操作符OpenWindow,即修改调用实例; 此外返回相应的句柄。
2.4 析构函数和 Halcon 运算符
所有 HALCON/C++ 类都提供了默认的析构函数,可以自动释放相应的内存。 对于某些类,析构函数基于合适的运算符:
Windows:
HWindow 类的默认析构函数基于 CloseWindow 关闭窗口。 请注意,运算符本身不是析构函数,即您可以使用 CloseWindow 关闭窗口,然后使用 OpenWindow 再次打开它。
Other Handle Classes:
封装句柄的其他类的默认析构函数,例如 HShapeModel 或 HFramegrabber,分别应用像 ClearShapeModel 或 CloseFramegrabber 这样的运算符。 与 CloseWindow 不同的是,不能通过类的实例调用这些操作符,可以在相应的参考手册条目中看到; 对于像 ClearAllShapeModels 这样的运算符也是如此。 实际上,不需要调用这些运算符,因为您可以按照 2.3 节中的描述重新初始化实例。
请注意,您不得将 ClearShapeModel、ClearAllShapeModels 或 CloseFramegrabber 等运算符与相应句柄类的实例一起使用!
2.5 元组模式
正如在第2.1 节中已经提到的,可以在所谓的元组模式下调用许多 HALCON 运算符。 例如,在此模式下,您可以通过一次调用将运算符应用于多个图像或区域。 标准情况,例如,使用单个图像调用操作符,称为简单模式。 操作符是否支持元组模式可以在参考手册中查看。
CharThreshold 参考手册条目的参数部分的头部和部分
void CharThreshold (const HObject& Image, const HObject& HistoRegion, HObject* Characters,
const HTuple& Sigma, const HTuple& Percent, HTuple* Threshold)
HRegion HImage::CharThreshold (const HRegion& HistoRegion, double Sigma,
const HTuple& Percent, HTuple* Threshold) const
HRegion HImage::CharThreshold (const HRegion& HistoRegion, double Sigma, double Percent,
Hlong* Threshold) const
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)
CharThreshold:在参数部分,参数Image被描述为一个image(-array);这表明您可以一次将运算符应用于多个图像。
如果您使用多个图像调用 CharThreshold,即使用图像元组,则输出参数也会自动变为元组。因此,参数 Characters 和 Threshold 分别被描述为 region(-array) 和 integer(-array)。
请注意,HTuple 类还可以包含混合类型的控制参数的数组(元组);与控制参数相反,图标参数在两种模式下都是类 HObject 的实例,因为此类可以包含单个对象和对象数组。
在面向对象的方法中,控制参数可以是基本类型(仅限简单模式)或 HTuple 的实例(简单和元组模式)。
在这个相当理论的介绍之后,让我们看一下示例代码。在图 5.6 中,CharThreshold 以简单模式应用,即应用于单个图像,在图 5.7 中同时应用于两个图像。这两个例子都是在面向对象和过程方法中实现的。这些示例突出了一些有趣的观点:
• 访问图标对象:
正如预期的那样,在面向对象的方法中,通过数组运算符 [] 访问单个图像和区域; 可以通过 CountObj() 方法查询数组中对象的数量。 在程序方法中,必须使用运算符 SelectObj 显式选择对象; 可以通过 CountObj 查询对象的数量。
请注意,对象索引从 1 开始(如 SelectObj.
• HObject 的多态性:
类 HObject 用于所有类型的图标对象。 更重要的是,可以使用图像对象
在简单模式下、通过 HImage 或在过程方法中使用 CharThreshold(省略声明和打开窗口)
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;
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;
对于需要区域的参数,如示例中对 CharThreshold 的调用; 在这种情况下,自动提取图像的域,即像素“有效”的区域。 面向对象的方法支持从 HImage 到 HRegion 的隐式转换。
在元组模式或过程方法中使用 CharThreshold(省略声明和打开窗口)
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();
}
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();
}
3、 Error Handling
错误处理完全基于使用 try … catch 块的异常。
try
{
image.ReadImage(filename);
}
catch (HException &except)
{
if (except.ErrorCode() == H_ERR_FNF)
{
// Handle file not found error
}
else
{
// Pass on unexpected error to caller throw except;
}
}
4 、内存管理
HALCON 的所有类,即不仅是 HImage、HRegion、HTuple、HFramegrabber 等,还有在过程方法中调用操作符时使用的类 HObject,在它们的析构函数中自动释放它们分配的资源(另见第2.4 节)。此外,当重新构造实例时,例如,通过第 2.3 节中提到的通过已经初始化的实例调用 CreateBarCodeModel,已分配的内存会在重用实例之前自动释放。因此,无需在 HALCON/C++ 中调用操作符 ClearObj;更重要的是,如果你使用它,HALCON 会抱怨已经释放的内存。要在实例超出范围之前显式释放资源,您可以调用实例的 Clear() 方法。
但是,您有一种情况需要进行显式内存管理:这适用于在过程方法中使用句柄时:创建句柄时分配的内存(例如,使用 OpenFramegrabber)仅在调用“互补”运算符时才被释放,在例如 CloseFramegrabber — 或在程序结束时。
5 、如何结合面向过程和面向对象的代码
如前所述,我们建议尽可能使用面向对象的方法。 但是,使用过程式方法是有一些原因的,例如,如果您想快速集成 HDevelop 导出的代码,则只能创建过程式代码。
最小的麻烦是由基本控制参数引起的,因为这两种方法都使用基本类型 long 等和类 HTuple。 标志性参数和句柄可以转换如下:
• 将 HObject 转换为标志性参数类
HObject p_image;
ReadImage(&p_image, "barcode/ean13/ean1301");
HImage o_image(p_image);
标志性参数可以从 HObject 转换为,例如,HImage,只需调用带有过程变量作为参数的构造函数即可。
• 将句柄转换为句柄类
HTuple p_barcode;
CreateBarCodeModel(HTuple(), HTuple(), &p_barcode);
HBarCode o_barcode(p_barcode[0]);
o_code_region = o_barcode.FindBarCode(o_image, "EAN-13", &result);
句柄不能通过构造函数直接转换; 相反,您使用程序句柄作为参数调用 SetHandle() 方法。
• 通过句柄访问句柄类
p_barcode = o_barcode.GetHandle();
请注意,o_barcode 仍然是句柄的所有者。 要在不破坏对象的情况下重置 o_barcode,请使用 InvalidateHandle()。
类似地,可以通过方法 GetHandle() 从相应的类中提取句柄。 您甚至可以省略该方法,因为句柄类提供了强制转换运算符,可以将它们自动转换为句柄。
p_barcode = o_barcode;
请注意,可以在需要 HObject 的程序代码中使用 HImage 的实例。
正如第 41 页的 5.2.4 节中所述,您不得将 ClearShapeModel、ClearAllShapeModels 或 CloseFramegrabber 等运算符与相应句柄类的实例一起使用!
6、 I/O Streams
HALCON/C++ 默认提供 iostream 操作符。 请注意,可能需要启用命名空间 std:
#include <iostream>
#include<HalconCpp.h>
using namespace std;
using namespace HalconCpp;
如果要使用旧的 iostream 接口(即 <iostream.h> 而不是 ),必须添加以下行(否则,可能会与 HALCON 包含文件发生冲突):
#define HCPP_NO_USE_IOSTREAM
7、 Windows API 冲突
FindText、CreateMutex、CreateEvent 和 DeleteFile 也是 Windows API 的函数。 如果定义了 UNICODE,则在 FindTextW、CreateMutexW、CreateEventW 和 DeleteFileW 上有定义,否则在 FindTextA、CreateMutexA、CreateMutexA 和 DeleteFileA 上有定义。 这些定义在 HalconCpp.h 中未定义。 如果要使用相应的 Windows API 调用,则必须直接使用 FindTextA、FindTextW、CreateMutexA、CreateMutexW、CreateEventA、CreateEventW、DeleteFileA 或 DeleteFileW。