5.2 调用 HALCON 算子

关于如何通过 HALCON/C++ 接口调用 HALCON 算子,详见 HALCON 算子参考手册。例如,图 5.1 显示了算子 MeanImage 的部分条目。

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)

图 5.1:mean_image 参考手册条目的头部和参数部分。

请注意,参考手册并未列出算子的所有可能签名。完整列表可在 include\halconcpp\HOperatorSet.h 文件中找到。

下面,我们

5.2.1 深入了解参数

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);

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 HBarCode::FindBarCode  
(const HImage& Image, const wchar_t* 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

HRegion HImage::FindBarCode  
(const HBarCode& BarCodeHandle, const wchar_t* CodeType, HString* DecodedDataStrings) const

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 ),然后返回平滑图像数组。这就是所谓的元组模式;更多信息,参见 "元组模式" 一节

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)

static HString HInfo::InfoFramegrabber  
(const wchar_t* Name, const wchar_t* 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)

图 5.3: info_framegrabber 参考手册条目的头部和参数部分。

字符串参数

无论 HALCON 库的编码方式如何(set_system('filename_encoding', ...)),HALCON/C++ 接口都希望传递给 HALCON 算子和 HTupleHString 实例的原始字符指针字符串采用 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);

5.2.2 通过类调用算子

如上一节所述,HALCON/C++ 参考手册说明了可以通过哪些类调用算子。例如,可以通过 HImageHBarCode 类对象调用 FindBarCode(见 图 5.2 )。在这两种情况下,相应的输入参数(分别为 ImageBarCodeHandle)不会再出现在括号中,因为它已被调用类的实例(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: 通过 HBarCodeHImage 或过程式方法使用 FindBarCode

图 5.4 展示了调用 FindBarCode 的三种方法的代码示例。在比较面向对象方法和过程式方法时,可以看到对算子 ReadImageCreateBarCodeModel 的调用分别被 HImageHBarCode 类的特殊构造函数所取代。下文将详细讨论这一主题。

5.2.3 构造函数和 Halcon 算子

图 5.4 所示,HALCON/C++ 参数类提供了额外的构造函数,这些构造函数基于合适的 HALCON 算子。示例中使用的 HImageHBarCode 构造函数分别基于 ReadImageCreateBarCodeModel

经验法则:如果一个类仅作为输出参数出现在一个算子中,那么就会自动存在一个基于该算子的构造函数。因此,如 图 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 中的相应头文件中找到。

5.2.4 析构函数和 Halcon 算子

所有 HALCON/C++ 类都提供了默认的析构函数,可以自动释放相应的内存。

封装句柄的类,如 HShapeModelHFramegrabber,其默认析构函数的工作原理分别类似于 ClearShapeModelCloseFramegrabber 这样的成员。

无需调用这些算子,因为您可以按照 "构造函数和 Halcon 算子" 一节 中的说明重新初始化实例。

基本上,我们将销毁句柄和销毁底层数据结构区分开来。销毁数据结构有两种方式: 当数据结构的最后一个引用被删除时自动销毁。通过调用操作符(如 CloseWindow)明确销毁。显式销毁会使引用失效,但访问是安全的。

5.2.5 元组模式

正如在 "深入了解参数" 一节 中提到的,许多 HALCON 算子可以在所谓的元组模式下调用。在这种模式下,您可以通过一次调用将算子应用于多个图像或区域。标准情况下,例如使用单个图像调用算子,称为简单模式。算子是否支持元组模式,可在参考手册中查看。例如,请看 图 5.5 ,这是算子 CharThreshold 的参考手册条目摘录: 在参数部分,参数 Image 被描述为 image(-array);这表明您可以将算子同时应用于多个图像。

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)

图 5.5:参考手册中 CharThreshold 条目的头部和参数部分。

如果使用多个图像(即图像元组)调用 CharThreshold,输出参数也会自动变成元组。因此,参数 CharactersThreshold 分别描述为 region(-array)integer(-array)

请注意,HTuple 类也可以包含混合类型的控制参数数组(元组);有关该类的更多信息,请参阅 "元组" 一节 。与控制参数相反,图标参数在两种模式下都是 HObject 类的实例,因为该类既可以包含单个对象,也可以包含对象数组。

在面向对象方法中,控制参数可以是基本类型(仅简单模式),也可以是 HTuple 实例(简单和元组模式)。

在做完这些理论性较强的介绍之后,让我们来看两个例子,这两个例子都是用面向对象和过程式方法实现的。这两个例子突出了一些有趣的要点:

第一个示例展示了如何在简单模式下应用 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, &region, 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, &regions, 2, 95, &thresholds);
  CountObj(images, &num);

  for (int i = 0; i < num; i++)
  {
    SelectObj(images, &image, i + 1);
    DispObj(image, window);
    SelectObj(regions, &region, i + 1);
    DispObj(region, window);
    cout << "Threshold for 'alpha" << i + 1 << "': " << thresholds[i].L();
  }