22.2 如何使用 HDevEngine/C++

本节介绍如何根据示例应用程序使用 HDevEngine,这些示例应用程序位于 %HALCONEXAMPLES%\hdevengine\cpp 子目录中。与 "使用 HALCON/C++ 创建应用程序" 一章 中描述的 HALCON/C++ 示例一样,这些示例也提供了适用于 Linux 和 Windows 系统的 CMake 文件。

示例应用程序展示了如何

"创建多线程应用程序" 一节 包含使用 HDevEngine 创建多线程应用程序的其他信息。

22.2.1 执行 HDevelop 程序

本节将介绍如何使用 HDevEngine 加载和执行 HDevelop 程序。代码片段来自示例程序 exec_program(源文件 exec_program.cpp),该程序用于检查塑料零件的边界是否有鳍片。图 22.1 显示了应用程序的截图。

图 22.1: 执行 HDevelop 程序,检测边界上的鳍片。

22.2.1.1 步骤 1:初始化

首先,我们包含 HALCON/C++ 和 HDevEngine 的主要头文件以及相应的命名空间。请注意,在本示例应用程序中,HDevEngine 头文件已通过 my_hdevoperatorimpl.h 头文件包含在内,因此我们无需明确包含 HDevEngineCpp.h 头文件:

#include "HalconCpp.h"
#  include "my_hdevoperatorimpl.h"

using namespace HalconCpp;
using namespace HDevEngineCpp;

The main procedure just calls a procedure that does all the work of the example. First, we create an instance of the main HDevEngine class HDevEngine. 主程序只是调用一个程序来完成示例中的所有工作。首先,我们创建一个主 HDevEngine 类 HDevEngine 的实例。

    HDevEngine         my_engine;

HDevelop 程序的路径和外部函数的路径存储在字符串变量中,语法与所使用的平台相符。请注意,在 Windows 应用程序中,可以在路径字符串中同时使用 / 和 \:

    std::string halcon_examples =
        (std::string)HSystem::GetSystem("example_dir")[0].S();
    std::string program_path(halcon_examples), ext_proc_path(halcon_examples);

    program_path += "/hdevengine/hdevelop/fin_detection.hdev";
    ext_proc_path += "/hdevengine/procedures";

如果 HDevelop 程序调用外部函数,则必须使用 SetProcedurePath 方法设置外部函数路径

    my_engine.SetProcedurePath(ext_proc_path.c_str());

22.2.1.2 步骤 2:加载程序

现在,我们创建一个 HDevProgram 类的实例,并使用 LoadProgram 方法加载 HDevelop 程序。请注意,如果程序加载成功,LoadProgram 会更改工作目录。

调用被封装在一个 try... catch 块中,以处理 HDevEngine 方法中出现的异常,例如,由于未正确指定文件名而出现的异常。有关错误处理的详细说明,请参见 "错误处理" 一节

    HDevProgram my_program;
  try
  {
    my_program.LoadProgram(program_path.c_str());
  }
  catch (HDevEngineException& hdev_exception)
    ...

22.2.1.3 步骤 3:执行程序

如果程序加载成功,我们就用 mHDEExecuteName 方法执行程序,并将返回的 HDevProgramCall 类实例存储在变量中,以备后用:

  HDevProgramCall prog_call = my_program.Execute();

22.2.1.4 步骤 4:获取结果

这就是执行 HDevelop 程序所需要做的全部工作。您还可以使用 GetCtrlVarTuple 方法访问程序的 "结果",即变量。在示例程序中,我们将查询并显示提取鳍片的面积:

  HTuple result = prog_call.GetCtrlVarTuple("FinArea");
  printf("\nFin Area: %f\n\n", result[0].D());

请注意,只有在程序终止后才能访问程序变量。

22.2.1.5 常规: 显示结果

如何在程序运行时显示结果,请参阅 "显示" 一节

22.2.2 执行 HDevelop 程序

本节介绍执行 HDevelop 函数的示例应用程序:

22.2.2.1 执行外部 HDevelop 程序

本节将介绍如何使用 HDevEngine 加载和执行外部 HDevelop 函数。下面的代码片段来自示例程序 exec_extproc(源文件 exec_extproc.cpp),它与上一节描述的示例一样,检查塑料零件的边界是否有鳍片。图 22.2 显示了应用程序的截图。

与前面的示例不同,结果显示是在 HALCON/C++ 中明确编程的,而不是依靠内部显示算子。如何自行实现内部显示算子,请参阅 "显示" 一节

图 22.2: 执行外部 HDevelop 程序,检测边界上的鳍片。

22.2.2.2 步骤 1:初始化

在执行 HDevelop 程序时,我们包含 HALCON/C++ 和 HDevEngine 的主要头文件以及命名空间。在本示例程序中,HDevEngine 头文件通过 my_error_output.h 头文件包含,因此我们不需要明确地包含它。主函数只是调用 run 函数来完成示例中的所有工作。我们创建一个主 HDevEngine 类 HDevEngine 的实例,并使用 SetProcedurePath 方法直接设置外部函数的路径。如果外部函数来自函数库,则外部函数路径可能包括函数库文件的名称。

#include "HalconCpp.h"
#  include "my_error_output.h"

using namespace HalconCpp;
using namespace HDevEngineCpp;

void run(void)
{
  std::string halcon_examples =
      (std::string)HSystem::GetSystem("example_dir")[0].S();
  std::string ext_proc_path(halcon_examples);
  ...
  HDevEngine().SetProcedurePath(ext_proc_path.c_str());

  DetectFin();
}

22.2.2.3 步骤 2:加载程序

在 "操作" 例程中,我们使用 HDevProcedure 类的构造函数加载外部函数,指定函数的名称,并将返回的函数调用存储在 HDevProcedureCall 类的实例中。调用被封装在一个 try... catch 块中,以处理构造函数中出现的异常,例如,由于文件名或函数路径未正确指定而出现的异常。关于错误处理的详细说明,请参见 "错误处理" 一节

void DetectFin()
{
  try
  {
    HDevProcedure     proc("detect_fin");
    HDevProcedureCall proc_call(proc);

在执行函数之前,我们先打开并初始化显示结果的图形窗口,并加载一个示例图像序列:

    const char* image_sequ_str = "fin";

    HWindow win(00, 100, 384, 288);
    win.SetPart(0, 0, 575, 767);
    win.SetDraw("margin");
    win.SetLineWidth(4);

    HFramegrabber fg("File", 1, 1, 0, 0, 0, 0, "default", -1, "default", -1,
                     "default", image_sequ_str, "default", -1, -1);

22.2.2.4 步骤 3:设置程序的输入参数

现在,每个图像都应由函数处理,该函数具有以下特征,即它将图像作为(图标)输入参数,并将检测到的鳍片区域及其面积分别作为图标和控制输出参数返回:

  procedure detect_fin (Image: FinRegion: : FinArea)

我们通过 SetInputIconicParamObject 方法将图像存储在 HDevProcedureCall 实例中,从而将图像作为输入对象传递。要设置哪个参数,可通过其索引(从 1 开始)来指定;还有一种方法可通过其名称来指定(参见 "HDevProcedureCall" 一节):

    for (long i = 0; i < 3; i++)
    {
      HImage image = fg.GrabImage();

      proc_call.SetInputIconicParamObject(1, image);

除了传递参数,您还可以在 HDevEngine 中使用全局变量(请参阅 HDevelop 用户向导 )。您可以使用 SetGlobalIconicVarObjectSetGlobalCtrlVarTuple 方法设置全局变量的值,也可以使用 GetGlobalIconicVarObjectGetGlobalCtrlVarTuple 方法查询全局变量的值。
不过,要注意不要用一个程序的变量值覆盖另一个程序的变量值: 在所有运行的 HDevEngine 实例中,每个全局变量一次只能有一个值。

22.2.2.5 步骤 4:执行程序

现在,我们使用 mHDEExecuteName 方法执行函数。

      proc_call.Execute();

22.2.2.6 步骤 5:获取函数的输出参数

如果函数执行成功,我们可以使用 HDevProcedureCall 类中的 GetOutputIconicParamObjectGetOutputCtrlParamTuple 方法访问其结果,即鳍区及其面积;同样,您可以通过索引或名称指定参数(参见 "HDevProcedureCall" 一节 )。

      HRegion fin_region = proc_call.GetOutputIconicParamObject(1);
      HTuple  fin_area;
      proc_call.GetOutputCtrlParamTuple(1, &fin_area);

22.2.2.7 步骤 6:显示函数结果

现在,我们在图形窗口中显示结果。请注意,我们是如何通过选择返回元组的第一个元素来访问该区域的:

        char fin_area_str[200];
        sprintf(fin_area_str, "Fin Area: %ld", (long)(fin_area[0].L()));
        win.DispImage(image);
        win.SetColor("red");
        win.DispRegion(fin_region);
        win.SetColor("white");
        win.SetTposition(150, 20);
        win.WriteString(fin_area_str);

22.2.2.8 执行本地和外部 HDevelop 函数

示例程序 exec_procedures(源文件 exec_procedures.cpp)通过 HDevEngine 执行本地和外部 HDevelop 函数。它模仿了 "执行 HDevelop 程序" 一节 中描述的 HDevelop 程序的行为。结果显示部分由显式编程完成,部分委托给 HDevelop 函数,使用 "显示" 一节 中描述的内部显示算子实现。

本地函数和外部函数的创建和执行方式完全相同。唯一不同的是,要使用本地函数,必须加载它所包含的程序,而要加载外部函数,必须设置函数的路径。HDevProcedure 提供了不同的构造函数来完成这项任务(参见 "HDevProcedure" 一节 )。

22.2.3 显示

在本节中,我们将介绍如何自行实现 HDevelop 的内部显示算子。文件 my_hdevoperatorimpl.hmy_hdevoperatorimpl.cpp 中包含了一个实现示例,在应用程序 exec_program(源文件 exec_program.cpp)和 exec_procedures(源文件 exec_procedures.cpp)中使用,前者已在 "执行 HDevelop 程序 " 一节 中讨论过。

事实上,HDevEngine 并未提供内部显示算子的实现,而是提供了 HDevOperatorImplCpp 类,该类包含所有算子的虚拟方法,您可以自行实现这些方法。这些方法的调用方式与面向对象版本的算子类似,例如 dev_displayDevDisplay,并具有相同的参数(该类的定义请参见 "HDevOperatorImplCpp" 一节 )。

实现的第一步是派生该类的子类,并指定要实现的所有方法。 示例文件实现了算子 dev_open_windowdev_set_window_extentsdev_set_partdev_set_windowdev_get_windowdev_clear_windowdev_clear_windowdev_close_windowdev_displaydev_set_drawdev_set_shapedev_set_colordev_set_coloreddev_set_lutdev_set_paintdev_set_line_width

class MyHDevOperatorImpl : public HDevEngineCpp::HDevOperatorImplCpp
{
public:
  virtual int DevOpenWindow(const HalconCpp::HTuple& row,
                            const HalconCpp::HTuple& col,
                            const HalconCpp::HTuple& width,
                            const HalconCpp::HTuple& height,
                            const HalconCpp::HTuple& background,
                            HalconCpp::HTuple* win_id);
  virtual int DevSetWindowExtents(const HalconCpp::HTuple& row,
                                  const HalconCpp::HTuple& col,
                                  const HalconCpp::HTuple& width,
                                  const HalconCpp::HTuple& height);
  virtual int DevSetPart(const HalconCpp::HTuple& row1,
                         const HalconCpp::HTuple& col1,
                         const HalconCpp::HTuple& row2,
                         const HalconCpp::HTuple& col2);
  virtual int DevSetWindow(const HalconCpp::HTuple& win_id);
  virtual int DevGetWindow(HalconCpp::HTuple* win_id);
  virtual int DevClearWindow();
  virtual int DevCloseWindow();
  virtual int DevDisplay(const HalconCpp::HObject& obj);
  virtual int DevDispText(const HalconCpp::HTuple& string,
                          const HalconCpp::HTuple& coordSystem,
                          const HalconCpp::HTuple& row,
                          const HalconCpp::HTuple& column,
                          const HalconCpp::HTuple& color,
                          const HalconCpp::HTuple& GenParamName,
                          const HalconCpp::HTuple& GenParamValue);
  virtual int DevSetDraw(const HalconCpp::HTuple& draw);
  virtual int DevSetContourStyle(const HalconCpp::HTuple& style);
  virtual int DevSetShape(const HalconCpp::HTuple& shape);
  virtual int DevSetColor(const HalconCpp::HTuple& color);
  virtual int DevSetColored(const HalconCpp::HTuple& colored);
  virtual int DevSetLut(const HalconCpp::HTuple& lut);
  virtual int DevSetPaint(const HalconCpp::HTuple& paint);
  virtual int DevSetLineWidth(const HalconCpp::HTuple& width);
};

除这些方法外,该类还包含处理多个图形窗口的方法。这些方法使用第二个类来管理所有打开的窗口。该类是线程安全和可重入的,但本节不作详细介绍。

  HalconCpp::HTuple GetCurrentWindow()  const;
  size_t            GetCount()          const;
  void              AddWindow(const HalconCpp::HTuple& id);
  HalconCpp::HTuple PopWindow();
  Hlong             SetWindow(const HalconCpp::HTuple& id);

  class WinIdContainer

在执行的 HDevelop 程序中,使用了两个图形窗口,一个用于主显示,另一个用于放大图像(见 图 22.1)。

要使用 HDevOperatorImplCpp 的实现,需要包含头文件:

#include "my_hdevoperatorimpl.h"

通过 SetHDevOperatorImpl 方法,您可以将 HDevOperatorImplCpp 版本的实例传递给 HDevEngine,当 HDevelop 程序或函数中使用相应算子时,HDevEngine 将调用其方法。

    my_engine.SetHDevOperatorImpl(&op_impl);

现在,我们来仔细看看示例中显示算子的实现。它试图模仿 HDevelop 中的行为: 可以打开多个图形窗口,其中一个窗口为 "活动" 或 "当前" 窗口。内部显示算子的方法只是调用相应的非内部显示算子: 例如,在 HDevelop 程序中调用 dev_display 会在 DevDisplay 中 "重定向" 到 disp_obj,并将要显示的图标对象和活动窗口的句柄作为参数:

int MyHDevOperatorImpl::DevDisplay(const HObject& obj)
{
  HCkDev(DispObj(obj, GetCurrentWindow()));
}

同样,dev_set_drawDevSetDraw 中被重定向为 set_draw

int MyHDevOperatorImpl::DevSetDraw(const HTuple& draw)
{
  HCkDev(SetDraw(GetCurrentWindow(), draw));
}

正如您所看到的,这些算子都很容易实现。这里不介绍图形窗口算子的实现。我们建议使用示例实现,因为它为单线程和多线程应用程序提供了所有必要的功能。

22.2.4 错误处理

在本节中,我们将仔细研究 HDevEngine 中的异常。下面的代码片段来自示例应用程序 error_handling(源文件 error_handling.cpp),它引发不同类型的异常并 "捕获" 它们。

HDevEngine 以 HDevEngineException 类的形式 "抛出" 异常,该类包含异常的类型(类别)、描述异常的信息,以及根据异常类型,执行函数的名称或 HALCON 错误代码等信息(有关该类的声明,请参阅 "HDevEngineException" 一节 )。

在图形窗口中显示异常信息的示例代码包含在 my_error_output.cppmy_error_output.h 文件中:

#include "my_error_output.h"

文件提供了两种函数。较简单的函数只显示错误信息,等待点击鼠标继续:

void DispMessage(const char* message)
{
  HWindow win(100, 100, ERR_WIN_WIDTH_SIMPLE, ERR_WIN_HEIGHT_SIMPLE, NULL,
              "visible", "");
  win.SetPart(0, 0, ERR_WIN_HEIGHT_SIMPLE - 1, ERR_WIN_WIDTH_SIMPLE - 1);
  win.SetColor("yellow");
  win.SetTposition(10, 10);
  WriteMessageNL(win, message);

  // wait for mouse click to continue
  win.SetColor("red");
  win.SetTposition(ERR_WIN_HEIGHT_SIMPLE / 2 + 10, ERR_WIN_WIDTH_SIMPLE / 2);
  win.WriteString("...click into window to continue");
  win.Click();
}

更复杂的是打印异常的所有可用信息(只显示相关代码):

void DispErrorMessage(const HDevEngineCpp::HDevEngineException& exception,
                      const char* context_msg /*=NULL*/)
{
  char text[2000];

  HWindow win(100, 100, ERR_WIN_WIDTH_COMPLEX, ERR_WIN_HEIGHT_COMPLEX, NULL,
              "visible", "");

  WriteMessageNL(win, exception.Message());

  sprintf(text, "    Error category: <%d : %s>", exception.Category(),
          exception.CategoryText());
  WriteMessageNL(win, text);

  sprintf(text, "    Error code:   <%d>", exception.HalconErrorCode());
  WriteMessageNL(win, text);

  sprintf(text, "    Procedure:      <%s>", exception.ExecProcedureName());
  WriteMessageNL(win, text);

  sprintf(text, "        Line:         <%d : %s>", exception.ProgLineNum(),
          exception.ProgLineName());
  WriteMessageNL(win, text);
}

出现异常时会调用该函数。该示例会引发不同的错误并显示相应的信息;其中一些将在下文中介绍。图 22.3 显示了由于应用程序试图加载一个不存在的 HDevelop 程序(类别 ExceptionFile)而发生的异常。

  try
  {
    program.LoadProgram(wrong_program_path.c_str());
  }
  catch (HDevEngineException& hdev_exception)
  {
    DispErrorMessage(hdev_exception,
                     "Error #1: Try to load a program that does not exist");
  }

图 22.3: 无法找到 HDevelop 程序时的异常内容。

当加载的程序找不到外部函数时,也会出现同样的异常情况(见 图 22.4 )。

图 22.4: 如果无法加载 HDevelop 程序的外部函数,则会出现异常内容。

图 22.5 中显示的异常是因为一个输入图标参数未初始化(类别 ExceptionInpNotInit)。它包含关于错误发生位置和原因的详细信息。

图 22.5:输入参数未初始化时的异常内容。

图 22.6 中显示的异常是通过调用参数无效的算子引发的(类别 ExceptionCall)。

图 22.6:HALCON 算子调用发生错误时的异常内容。

通过UserData方法(参见 "HDevEngineException" 一节),你还可以访问用户异常数据,这些数据是在HDevelop程序或函数中通过算子 throw 抛出的,类似于算子dev_get_exception_data

如果出现异常(未在函数中捕获),函数调用将被清理。这意味着所有子线程都会被销毁,所有输入和输出参数的值都会被清除。因此,我们建议您在执行调用前设置所有输入参数,即使其中一些参数没有发生变化。请注意,您可以使用 SetEngineAttribute 方法配置 HDevEngine 在加载包含无效行或未解决的函数调用的程序时的行为(参见 "HDevEngine" 一节 )。

22.2.5 创建多线程应用程序

mfc\exec_procedures_mt_mfc 示例中,三个线程并行执行 HDevelop 函数,用于图像采集、数据码读取和可视化(见 图 22.7 )。请查看示例源文件(目录 mfc\exec_procedures_mt_mfc\source\ 中),了解线程如何同步输入和输出数据。

图 22.7:使用三个线程并行执行图像采集、数据码读取和可视化的示例程序。

exec_programs_mt 示例(源文件 exec_programs_mt.cpp)展示了如何在不同的线程中并行执行一个或多个不同的 HDevelop 程序。请注意,该示例非常笼统,没有实现特定的应用程序。

HDevelop 程序必须作为命令行参数传递。您还可以为每个程序指定线程数和/或在每个线程中连续执行程序的频率。在调用不带参数的可执行文件时,会对命令行参数进行说明。

22.2.6 使用向量变量执行 HDevelop 程序

示例应用程序 use_vector_variables 展示了如何在 HDevengine/C++ 中加载和执行包含向量变量的 HDevelop 程序。示例中使用了两个向量进行处理:一个包含输入图像,另一个包含缩放因子。执行程序时,输入图像的灰度值将根据缩放因子进行缩放。请查看示例源文件 use_vector_variables.cpp,了解如何在 HDevengine/C++ 中使用向量变量的详细信息。