本节介绍如何根据示例应用程序使用 HDevEngine,这些示例应用程序位于 %HALCONEXAMPLES%\hdevengine\cpp 子目录中。与 "使用 HALCON/C++ 创建应用程序" 一章 中描述的 HALCON/C++ 示例一样,这些示例也提供了适用于 Linux 和 Windows 系统的 CMake 文件。
示例应用程序展示了如何
"创建多线程应用程序" 一节 包含使用 HDevEngine 创建多线程应用程序的其他信息。
本节将介绍如何使用 HDevEngine 加载和执行 HDevelop 程序。代码片段来自示例程序 exec_program(源文件 exec_program.cpp),该程序用于检查塑料零件的边界是否有鳍片。图 22.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());
现在,我们创建一个 HDevProgram 类的实例,并使用 LoadProgram 方法加载 HDevelop 程序。请注意,如果程序加载成功,LoadProgram 会更改工作目录。
调用被封装在一个 try... catch 块中,以处理 HDevEngine 方法中出现的异常,例如,由于未正确指定文件名而出现的异常。有关错误处理的详细说明,请参见 "错误处理" 一节 。
HDevProgram my_program;
try
{
my_program.LoadProgram(program_path.c_str());
}
catch (HDevEngineException& hdev_exception)
...
如果程序加载成功,我们就用 mHDEExecuteName 方法执行程序,并将返回的 HDevProgramCall 类实例存储在变量中,以备后用:
HDevProgramCall prog_call = my_program.Execute();
这就是执行 HDevelop 程序所需要做的全部工作。您还可以使用 GetCtrlVarTuple 方法访问程序的 "结果",即变量。在示例程序中,我们将查询并显示提取鳍片的面积:
HTuple result = prog_call.GetCtrlVarTuple("FinArea");
printf("\nFin Area: %f\n\n", result[0].D());
请注意,只有在程序终止后才能访问程序变量。
如何在程序运行时显示结果,请参阅 "显示" 一节。
本节介绍执行 HDevelop 函数的示例应用程序:
本节将介绍如何使用 HDevEngine 加载和执行外部 HDevelop 函数。下面的代码片段来自示例程序 exec_extproc(源文件 exec_extproc.cpp),它与上一节描述的示例一样,检查塑料零件的边界是否有鳍片。图 22.2 显示了应用程序的截图。
与前面的示例不同,结果显示是在 HALCON/C++ 中明确编程的,而不是依靠内部显示算子。如何自行实现内部显示算子,请参阅 "显示" 一节 。
在执行 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();
}
在 "操作" 例程中,我们使用 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);
现在,每个图像都应由函数处理,该函数具有以下特征,即它将图像作为(图标)输入参数,并将检测到的鳍片区域及其面积分别作为图标和控制输出参数返回:
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 用户向导 )。您可以使用 SetGlobalIconicVarObject 或 SetGlobalCtrlVarTuple 方法设置全局变量的值,也可以使用 GetGlobalIconicVarObject 和 GetGlobalCtrlVarTuple 方法查询全局变量的值。
不过,要注意不要用一个程序的变量值覆盖另一个程序的变量值: 在所有运行的 HDevEngine 实例中,每个全局变量一次只能有一个值。
现在,我们使用 mHDEExecuteName 方法执行函数。
proc_call.Execute();
如果函数执行成功,我们可以使用 HDevProcedureCall 类中的 GetOutputIconicParamObject 和 GetOutputCtrlParamTuple 方法访问其结果,即鳍区及其面积;同样,您可以通过索引或名称指定参数(参见 "HDevProcedureCall" 一节 )。
HRegion fin_region = proc_call.GetOutputIconicParamObject(1);
HTuple fin_area;
proc_call.GetOutputCtrlParamTuple(1, &fin_area);
现在,我们在图形窗口中显示结果。请注意,我们是如何通过选择返回元组的第一个元素来访问该区域的:
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);
示例程序 exec_procedures(源文件 exec_procedures.cpp)通过 HDevEngine 执行本地和外部 HDevelop 函数。它模仿了 "执行 HDevelop 程序" 一节 中描述的 HDevelop 程序的行为。结果显示部分由显式编程完成,部分委托给 HDevelop 函数,使用 "显示" 一节 中描述的内部显示算子实现。
本地函数和外部函数的创建和执行方式完全相同。唯一不同的是,要使用本地函数,必须加载它所包含的程序,而要加载外部函数,必须设置函数的路径。HDevProcedure 提供了不同的构造函数来完成这项任务(参见 "HDevProcedure" 一节 )。
在本节中,我们将介绍如何自行实现 HDevelop 的内部显示算子。文件 my_hdevoperatorimpl.h 和 my_hdevoperatorimpl.cpp 中包含了一个实现示例,在应用程序 exec_program(源文件 exec_program.cpp)和 exec_procedures(源文件 exec_procedures.cpp)中使用,前者已在 "执行 HDevelop 程序 " 一节 中讨论过。
事实上,HDevEngine 并未提供内部显示算子的实现,而是提供了 HDevOperatorImplCpp 类,该类包含所有算子的虚拟方法,您可以自行实现这些方法。这些方法的调用方式与面向对象版本的算子类似,例如 dev_display 的 DevDisplay,并具有相同的参数(该类的定义请参见 "HDevOperatorImplCpp" 一节 )。
实现的第一步是派生该类的子类,并指定要实现的所有方法。 示例文件实现了算子 dev_open_window、 dev_set_window_extents、 dev_set_part、 dev_set_window、 dev_get_window、 dev_clear_window、 dev_clear_window、 dev_close_window、 dev_display、 dev_set_draw、 dev_set_shape、 dev_set_color、 dev_set_colored、 dev_set_lut、 dev_set_paint 和 dev_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_draw 在 DevSetDraw 中被重定向为 set_draw:
int MyHDevOperatorImpl::DevSetDraw(const HTuple& draw)
{
HCkDev(SetDraw(GetCurrentWindow(), draw));
}
正如您所看到的,这些算子都很容易实现。这里不介绍图形窗口算子的实现。我们建议使用示例实现,因为它为单线程和多线程应用程序提供了所有必要的功能。
在本节中,我们将仔细研究 HDevEngine 中的异常。下面的代码片段来自示例应用程序 error_handling(源文件 error_handling.cpp),它引发不同类型的异常并 "捕获" 它们。
HDevEngine 以 HDevEngineException 类的形式 "抛出" 异常,该类包含异常的类型(类别)、描述异常的信息,以及根据异常类型,执行函数的名称或 HALCON 错误代码等信息(有关该类的声明,请参阅 "HDevEngineException" 一节 )。
在图形窗口中显示异常信息的示例代码包含在 my_error_output.cpp 和 my_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.4 )。
图 22.5 中显示的异常是因为一个输入图标参数未初始化(类别 ExceptionInpNotInit)。它包含关于错误发生位置和原因的详细信息。
图 22.6 中显示的异常是通过调用参数无效的算子引发的(类别 ExceptionCall)。
通过UserData方法(参见 "HDevEngineException" 一节),你还可以访问用户异常数据,这些数据是在HDevelop程序或函数中通过算子 throw 抛出的,类似于算子dev_get_exception_data。
如果出现异常(未在函数中捕获),函数调用将被清理。这意味着所有子线程都会被销毁,所有输入和输出参数的值都会被清除。因此,我们建议您在执行调用前设置所有输入参数,即使其中一些参数没有发生变化。请注意,您可以使用 SetEngineAttribute 方法配置 HDevEngine 在加载包含无效行或未解决的函数调用的程序时的行为(参见 "HDevEngine" 一节 )。
在 mfc\exec_procedures_mt_mfc 示例中,三个线程并行执行 HDevelop 函数,用于图像采集、数据码读取和可视化(见 图 22.7 )。请查看示例源文件(目录 mfc\exec_procedures_mt_mfc\source\ 中),了解线程如何同步输入和输出数据。
exec_programs_mt 示例(源文件 exec_programs_mt.cpp)展示了如何在不同的线程中并行执行一个或多个不同的 HDevelop 程序。请注意,该示例非常笼统,没有实现特定的应用程序。
HDevelop 程序必须作为命令行参数传递。您还可以为每个程序指定线程数和/或在每个线程中连续执行程序的频率。在调用不带参数的可执行文件时,会对命令行参数进行说明。
示例应用程序 use_vector_variables 展示了如何在 HDevengine/C++ 中加载和执行包含向量变量的 HDevelop 程序。示例中使用了两个向量进行处理:一个包含输入图像,另一个包含缩放因子。执行程序时,输入图像的灰度值将根据缩放因子进行缩放。请查看示例源文件 use_vector_variables.cpp,了解如何在 HDevengine/C++ 中使用向量变量的详细信息。