本节介绍如何使用 .NET Core 创建一个简单的 HDevEngine/.NET 应用程序。如需更全面的说明,请阅读 "使用 HDevEngine/.NET" 一节 。
dotnet new console -n hdevengine-example cd hdevengine-example dotnet add package MVTec.HalconDotNet -v 23050
using System; using System.Diagnostics; using System.IO; using HalconDotNet; namespace hdevengine_example { class Program { static void Main(string[] args) { string ExampleDir = HSystem.GetSystem("example_dir"); string ProcedurePath = "/hdevengine/procedures"; HDevEngine Engine = new HDevEngine(); Engine.SetProcedurePath(Path.GetFullPath(ExampleDir + ProcedurePath)); HImage Image = new HImage("fin2"); HDevProcedure Procedure = new HDevProcedure("detect_fin"); HDevProcedureCall ProcCall = new HDevProcedureCall(Procedure); ProcCall.SetInputIconicParamObject("Image", Image); ProcCall.Execute(); HTuple FinArea = ProcCall.GetOutputCtrlParamTuple("FinArea"); Console.WriteLine(String.Format("Fin Area: {0}", FinArea.I)); } } }
dotnet run
结果,您将看到以下输出 "Fin Area: 1634'。
本节借助 C# 编写的示例应用程序说明如何使用 HDevEngine/.NET,这些示例应用程序位于 %HALCONEXAMPLES%\hdevengine\c# 子目录下。大多数示例使用 Windows Forms,它仅适用于 Windows 上的 .NET Core。HDevEngine/.NET 的特定信息也适用于一般的 .NET Core。
对于某些示例,还提供 Visual Basic .NET 版本,它们位于 %HALCONEXAMPLES%\hdevengine\vb.net 子目录下。除了两种语言之间的标准差异外,它们完全相同。
示例应用程序展示了如何
本节将介绍如何使用 HDevEngine 加载和执行 HDevelop 程序。代码片段来自示例程序 ExecProgram,该程序用于检查塑料零件的边界是否有鳍片。图 23.1 显示了应用程序的截图;其中包含两个按钮,分别用于加载和执行 HDevelop 程序。
首先,我们创建主 HDevEngine 类 HDevEngine 的全局实例。
private HDevEngine MyEngine = new HDevEngine();
加载表单时,我们会存储 HDevelop 程序的路径,并使用 SetProcedurePath 方法设置外部函数路径:
String ProgramPathString;
private void ExecProgramForm_Load(object sender, System.EventArgs e)
{
string ExampleDir = HSystem.GetSystem("example_dir");
string ProcedurePath = "/hdevengine/procedures";
MyEngine.SetProcedurePath(Path.GetFullPath(ExampleDir + ProcedurePath));
ProgramPathString = Path.GetFullPath(
ExampleDir + "/hdevengine/hdevelop/fin_detection.hdev"
);
}
请注意,后者只有在 HDevelop 程序调用外部函数时才有必要。
单击按钮加载 HDevelop 程序时,将创建一个 HDevProgram 类实例,并将程序路径作为参数。此外,还会创建一个 HDevProgramCall 实例供以后使用。请注意,如果加载了程序,工作目录将被更改。
在构造函数中出现的异常,如文件名未正确指定等,将使用标准的 C# 错误处理机制进行处理:
private void LoadBtn_Click(object sender, System.EventArgs e)
{
try
{
HDevProgram Program = new HDevProgram(ProgramPathString);
ProgramCall = new HDevProgramCall(Program);
}
catch (HDevEngineException Ex)
{
MessageBox.Show(Ex.Message, "HDevEngine Exception");
return;
}
...
}
有关错误处理的更多信息,请参阅 "错误处理 " 一节 。
单击按钮执行程序时,会调用方法 mHDEExecuteName:
private void RunProgram()
{
try
{
try
{
ProgramCall.Execute();
}
...
}
}
这就是执行 HDevelop 程序所需要做的全部工作。您还可以使用 GetCtrlVarTuple 方法访问程序的 "结果",即变量。在示例程序中,我们将查询并显示提取鳍片的面积:
double FinArea;
FinArea = ProgramCall.GetCtrlVarTuple("FinArea");
Window.SetTposition(150, 20);
Window.WriteString("Fin Area: ");
请注意,只有在程序终止后才能访问程序变量。
如何在程序运行时显示结果,请参阅 "显示" 一节。
本节介绍执行 HDevelop 函数的示例应用程序:
本节将介绍如何使用 HDevEngine 加载和执行外部 HDevelop 程序。下面的代码片段来自示例程序 ExecExtProc,它与上一节描述的示例一样,检查塑料零件的边界是否有鳍片。图 23.2 显示了应用程序的截图;其中包含两个按钮,分别用于加载和执行 HDevelop 函数。
与前一个示例不同的是,结果显示是明确编程的,而不是依靠内部显示算子。
在执行 HDevelop 程序时,我们会创建主 HDevEngine 类 HDevEngine 的全局实例,并在加载窗体时使用 SetProcedurePath 方法设置外部函数路径(省略构建路径的代码)。如果外部函数来自函数库,外部函数路径可能包括库文件名。
private HDevEngine MyEngine = new HDevEngine();
private void ExecExtProcForm_Load(object sender, System.EventArgs e)
{
string ProcedurePath = "/hdevengine/procedures";
...
MyEngine.SetProcedurePath(Path.GetFullPath(ExampleDir + ProcedurePath));
}
与本示例应用程序的 C++ 版本不同,我们不希望在自由浮动的图形窗口中显示结果,而是希望在窗体中,即在 HSmartWindowControl 的实例中显示结果(另见 "为可视化添加和自定义 HSmartWindowControl" 一节和 "可视化" 一节)。为了调用 HALCON 算子,我们为底层 HALCON 窗口声明了一个 HWindow 类全局变量;加载窗体时,我们将该变量设置为 HSmartWindowControl 中的 HALCON 窗口,并初始化该窗口:
private HWindow Window;
private void WindowControl_Load(object sender, EventArgs e)
{
Window = WindowControl.HalconWindow;
Window.SetDraw("margin");
Window.SetLineWidth(4);
}
单击 加载 按钮后,HDevelop 函数将通过 HDevProcedure 类的构造函数加载,同时指定函数名称,并以 HDevProcedureCall 类实例的形式创建相应的函数调用。在构造函数中出现的异常,如文件名或函数路径指定不正确,将通过标准的 C# 错误处理机制进行处理。有关错误处理的更多信息,请参阅 "错误处理" 一节 。
private void LoadBtn_Click(object sender, System.EventArgs e)
{
try
{
HDevProcedure Procedure = new HDevProcedure("detect_fin");
ProcCall = new HDevProcedureCall(Procedure);
}
catch (HDevEngineException Ex)
{
MessageBox.Show(Ex.Message, "HDevEngine Exception");
return;
}
}
执行函数由多个步骤组成。首先,我们加载一个示例图像序列:
private void ExecuteBtn_Click(object sender, System.EventArgs e)
{
HFramegrabber Framegrabber = new HFramegrabber();
Framegrabber.OpenFramegrabber("File", 1, 1, 0, 0, 0, 0, "default",
-1, "default", -1, "default", "fin.seq", "default", -1, -1);
现在,每个图像都应由函数处理,该函数具有以下特征,即它将图像作为(图标)输入参数,并将检测到的鳍片区域及其面积分别作为图标和控制输出参数返回:
procedure detect_fin (Image: FinRegion: : FinArea)
我们通过 SetInputIconicParamObject 方法将图像存储在 HDevProcedureCall 实例中,从而将图像作为输入对象传递。要设置的参数可通过其名称指定(也可通过索引指定):
HImage Image = new HImage();
HRegion FinRegion;
HTuple FinArea;
for (int i = 0; i <= 2; i++)
{
Image.GrabImage(Framegrabber);
Image.DispObj(Window);
ProcCall.SetInputIconicParamObject("Image", Image);
除了传递参数,您还可以在 HDevEngine 中使用全局变量(请参阅 HDevelop 用户向导 )。您可以使用 SetGlobalIconicVarObject 或 SetGlobalCtrlVarTuple 方法设置全局变量的值,也可以使用 GetGlobalIconicVarObject 和 GetGlobalCtrlVarTuple 方法查询全局变量。
不过,请注意不要用一个程序的变量值覆盖另一个程序的变量值: 在所有运行的 HDevEngine 实例中,每个全局变量一次只能有一个值。
现在,我们使用 mHDEExecuteName 方法执行函数。
ProcCall.Execute();
如果函数执行成功,我们可以使用类 HDevProcedureCall 的 nmHDEGetOutputIconicParamRegion 和 GetOutputCtrlParamTuple 方法访问其结果,即鳍片区域及其面积;同样,您可以通过参数名称或索引指定参数。请注意,您可以使用 GetOutputIconicParamObject 将图标输出对象作为相应类(此处为 HRegion)的实例或作为 HObject 的实例获取。
FinRegion = ProcCall.GetOutputIconicParamRegion("FinRegion");
FinArea = ProcCall.GetOutputCtrlParamTuple("FinArea");
最后,我们在图形窗口中显示结果:
this.Invoke((MethodInvoker)delegate
{
Image.DispObj(Window);
Window.SetColor("red");
Window.DispObj(FinRegion);
Window.SetColor("white");
Window.SetTposition(150, 20);
Window.WriteString("FinArea: " + FinArea.D);
});
示例应用程序 ExecProcedures 使用 HDevEngine 执行本地和外部 HDevelop 函数。它模仿了 "执行 HDevelop 程序" 一节 中描述的 HDevelop 程序的行为。结果显示部分由显式编程完成,部分委托给 HDevelop 函数,使用 "显示" 一节 中描述的内部显示算子实现。图 23.3 显示了应用程序的截图。
下面,我们将简要介绍部分代码。
本地函数和外部函数的创建和执行方式完全相同。唯一不同的是,要使用本地函数,必须加载其所包含的程序,而要加载外部函数,则必须设置函数路径。在本例中,图像处理函数是本地函数,而另一个是外部函数。请注意,此处省略了构建程序和函数路径的代码。
private HDevProcedureCall InitAcqProcCall;
private HDevProcedureCall ProcessImageProcCall;
private HDevProcedureCall VisualizeDetailsProcCall;
private void ExecProceduresForm_Load(object sender, System.EventArgs e)
{
string ProcedurePath = "/hdevengine/procedures";
...
MyEngine.SetProcedurePath(Path.GetFullPath(ExampleDir + ProcedurePath));
}
private void LoadBtn_Click(object sender, System.EventArgs e)
{
try
{
HDevProgram Program = new HDevProgram(ProgramPathString);
HDevProcedure InitAcqProc = new HDevProcedure(Program, "init_acquisition");
HDevProcedure ProcessImageProc = new HDevProcedure(Program, "detect_fin");
HDevProcedure VisualizeDetailsProc =
new HDevProcedure(Program, "display_zoomed_region");
InitAcqProcCall = new HDevProcedureCall(InitAcqProc);
ProcessImageProcCall = new HDevProcedureCall(ProcessImageProc);
VisualizeDetailsProcCall = new HDevProcedureCall(VisualizeDetailsProc);
...
}
其中一个函数会打开图像采集设备。它会返回相应的句柄,我们将其存储在 HFramegrabber 类的一个实例中。
private HFramegrabber Framegrabber;
private void InitAcqBtn_Click(object sender, System.EventArgs e)
{
InitAcqProcCall.Execute();
Framegrabber =
new HFramegrabber(InitAcqProcCall.GetOutputCtrlParamTuple("AcqHandle").H);
...
}
在示例应用程序中,当应用程序终止并调用类 HFramegrabber 的终结器时,设备将被关闭,而终结器又会调用算子 CloseFramegrabber。如果使用 HDevelop 函数关闭与设备的连接,就会使句柄失效,从而导致终结器引发异常。
与前面的示例一样,图像处理的结果(按钮 Process Image)是通过调用 HALCON/.NET 算子 "手动" 显示的。相反,当您单击 "Visualize Details" 按钮时,将执行一个 HDevelop 函数,放大提取的鳍片。为此,我们将传递 HDevelop 内部显示算子的实现(有关实现类的更多信息,请参阅 "显示" 一节 ),并在执行函数后将其移除。
private void VisualizeDetailsBtn_Click(object sender, System.EventArgs e)
{
MyEngine.SetHDevOperators(MyHDevOperatorImpl);
VisualizeDetailsProcCall.SetInputIconicParamObject("Image", Image);
VisualizeDetailsProcCall.SetInputIconicParamObject("Region", FinRegion);
VisualizeDetailsProcCall.SetInputCtrlParamTuple("ZoomScale", 2);
VisualizeDetailsProcCall.SetInputCtrlParamTuple("Margin", 5);
VisualizeDetailsProcCall.Execute();
MyEngine.SetHDevOperators(null);
}
执行类的实例通过表单的 HALCON 窗口初始化。
private HDevOpMultiWindowImpl MyHDevOperatorImpl;
private void WindowControl_Load(object sender, EventArgs e)
{
Window = WindowControl.HalconWindow;
...
MyHDevOperatorImpl = new HDevOpMultiWindowImpl(Window);
}
如果类 HDevOpMultiWindowImpl 在初始化时没有指定窗口,则会自动打开一个新的 HALCON 窗口,以模拟 HDevelop 的行为。因此,在 HDevelop 程序或函数中使用算子 dev_open_window 将打开另一个窗口。新打开的窗口会自动设置为活动状态。
与 C++ 版本的 HDevEngine 不同,HDevEngine/.NET 已经以两个类的形式提供了 HDevelop 内部显示算子的便捷实现:
在示例代码中,部分程序和函数的实际执行被委托给了后台线程。这是一种很好的做法,因为长时间执行会导致图形用户界面反应迟钝。此外,如果图形用户界面线程被阻塞,使用交互式绘图对象的 HDevelop 代码将无法使用 HSmartWindowControl。
虽然 HALCON 显示算子是线程安全的,但对 Windows 窗体元素的访问却不是(例如,启用按钮或设置标签文本)。因此,在这些示例中,使用 Invoke() 调用将结果可视化委托回 GUI 线程。
最后,通过 HDevOpFixedWindowImpl 或 HDevOpMultiWindowImpl 使用 dev_* 片子进行可视化对于多线程应用程序(并行执行多个程序)是有限制的。这是因为在任何时候都只有一个全局 "活动" 窗口(由 dev_set_window 控制),因此线程无法独立控制其输出窗口。这种行为与 HDevelop(也只有一个活动窗口)中通过 par_start 使用多线程时的行为一致。
果需要并行可视化,我们建议使用直接输出到所需窗口的 HALCON 算子编写显式可视化代码。更多信息,参见 "创建多线程应用程序" 一节 。
示例程序 ExecProgram 使用了 HDevOpMultiWindowImpl。要使用该类(或 HDevOpFixedWindowImpl),可通过 nmHDESetHDevOperatorsName 方法将其实例传递给 HDevEngine:
private void WindowControl_Load(object sender, EventArgs e)
{
Window = WindowControl.HalconWindow;
MyEngine.SetHDevOperators(new HDevOpMultiWindowImpl(Window));
}
如果您的应用程序有这两个类无法满足的特殊显示要求,您可以创建一个实现接口 IHDevOperators 的类,并重载其 DevOpenWindow、DevDisplay 等方法,从而提供自己的显示算子实现,类似于 C++ 版本的 HDevelop(参见 "显示" 一节 )。
在本节中,我们将仔细研究 HDevEngine 中的异常。下面的代码片段来自示例应用程序 ErrorHandling,该程序会在按下按钮时引发并捕获不同类型的异常。图 23.4 显示了应用程序的截图。
HDevEngine 将异常作为 HDevEngineException 类的实例抛出,该类包含异常的类型(类别)、描述异常的消息,以及根据异常类型提供的信息,如执行函数的名称或 HALCON 错误代码(另见 "HDevEngineException" 一节 )。
In the example application, the following procedure displays all the information contained in HDevEngineException in a message box: 在示例应用程序中,以下函数将在消息框中显示 HDevEngineException 中包含的所有信息:
private void DisplayException(HDevEngineException Ex)
{
string FullMessage = "Message: <" + Ex.Message + ">" +
", Error in program / procedure: <" + Ex.ProcedureName + ">" +
", program line: <" + Ex.LineText + ">" +
", line number: <" + Ex.LineNumber + ">" +
", HALCON Error Number: <" + Ex.HalconError + ">";
string Title = "HDevEngine Exception (Category: " +
Ex.Category.ToString() + ")";
MessageBox.Show(FullMessage, Title);
}
当出现异常时,将调用此函数;请注意,前几节中描述的示例应用程序仅显示异常消息。
try
{
HDevProgram Program = new HDevProgram(ProgramPathString);
new HDevProgramCall(Program);
}
catch (HDevEngineException Ex)
{
DisplayException(Ex);
return;
}
图 23.5 显示了由于应用程序试图加载一个不存在的 HDevelop 程序(类别 ExceptionFile)而发生的异常。正如您所看到的,在这种情况下,只有消息包含有用的信息。
下一个异常发生在执行一个输入参数未初始化的函数时(类别 ExceptionInpNotInit):
procedure detect_fin_with_error_inpnotinit (Image: FinRegion: : FinArea) bin_threshold (NotExistingImage, Dark) ...
图 23.6 显示了异常的内容,其中包含关于错误发生位置和原因的详细信息。
最后一个异常是在执行函数时产生的,在该程序中,由于第三个参数无效,对算子 closing_circle 的调用失败(类别 ExceptionCall)。
procedure detect_fin_with_error_call (Image: FinRegion: : FinArea) bin_threshold (Image, Dark) difference (Image, Dark, Background) dev_set_color ('blue') dev_display (Background) closing_circle (Background, ClosedBackground, -1) ...
图 23.7 显示了异常情况的内容。
通过UserData方法(参见 "HDevEngineException" 一节),你还可以访问用户异常数据,这些数据是在HDevelop程序或函数中通过算子 throw 抛出的,类似于算子 dev_get_exception_data。
如果出现异常(未在函数中捕获),函数调用将被清理。这意味着所有子线程都会被销毁,所有输入和输出参数的值都会被清除。因此,我们建议您在执行调用前设置所有输入参数,即使其中一些参数没有发生变化。
请注意,在加载包含无效行或未解决的过程调用的程序时,可以通过 SetEngineAttribute 方法配置 HDevEngine 的行为(参见 "HDevEngine" 一节 )。
HALCON 提供了两个使用 HDevEngine/.NET 多线程的 C# 示例应用程序:
在下文中,我们将简要列出创建多线程 HDevEngine 应用程序时需要遵守的最重要规则。此外,请参阅 "使用 HALCON 进行并行编程" 一节 中有关使用 HALCON 进行并行编程的一般信息,尤其是 "并行化的程序设计问题" 一节 中的样式指南。
本节介绍的示例应用程序 MultiThreading 利用多核或多处理器系统,通过两个线程并行执行同一个 HDevelop 函数(任务)。该函数使用基于形状的匹配来查找瓶盖。
图 23.8 显示了应用程序的结构概览。它由四个线程组成: 主线程(即窗体)负责图形用户界面(GUI),如图 23.9 所示。它由一个用于显示结果的 HALCON 窗口和用于初始化、启动和停止应用程序的按钮组成。
主线程还通过 HDevelop 函数训练形状模型,并通过创建和初始化其他三个线程来初始化应用程序:两个处理线程和所谓的控制线程,控制线程控制这两个处理线程。
控制线程获取图像并将其传递给处理线程,然后处理线程处理图像并将结果传回。控制线程收集结果,但本身并不显示结果,因为 HALCON 窗口中的所有活动都必须由创建该窗口的线程(即主线程)执行。
现在,我们来仔细看看相应的代码。请注意,我们没有显示所有细节,特别是错误处理和终止包括内存管理。
应用程序在 Init 按钮(文件:MultiThreadingForm.cs)的事件处理程序中初始化。
private void InitButton_Click(object sender, System.EventArgs e)
关闭算子自动并行化
HOperatorSet.SetSystem("parallelize_operators", "false");
首先,关闭算子自动并行化功能,否则这两种机制(多线程和算子并行化)将使用超过可用内核/处理器数量的资源,从而降低应用程序的运行速度(参见 "并行化程序设计问题" 一节 中的样式指南)。如果系统有两个以上的内核或处理器,可以考虑将其中一部分分配给自动算子并行化,详见 "定制并行化机制" 一节 。
设置外部函数路径
然后,我们创建一个 HDevEngine 实例,并设置搜索 HDevelop 函数的路径(省略构建路径的代码)。如果外部函数来自函数库,外部函数路径可能包括库文件名。
HDevEngine MyEngine = new HDevEngine(); string ProcedurePath = "/hdevengine/procedures"; ... MyEngine.SetProcedurePath(Path.GetFullPath(ExampleDir + ProcedurePath));
训练形状模型
为了初始化图像处理部分,我们执行了一个 HDevelop 函数来训练盖子的形状模型。
HDevProcedureCall ProcTrain;
HDevProcedure Procedure = new HDevProcedure("train_shape_model");
ProcTrain = new HDevProcedureCall(Procedure);
ProcTrain.Execute();
存储模型数据
该函数返回形状模型的句柄和模型轮廓。我们将这两部分内容存储在形式变量中,以便处理线程可以访问它们。
public HTuple ModelID;
public HXLD ModelContours;
ModelID = ProcTrain.GetOutputCtrlParamTuple("ModelID");
ModelContours = ProcTrain.GetOutputIconicParamXld("ModelContours");
创建并初始化处理引擎
实际的图像处理封装在 EngineThread 类中(文件:EngineThread.cs)。该类的主要成员是一个线程以及 HDevEngine 和 HDevProcedureCall 的实例。此外,EngineThread 还包含用于访问主线程中训练的形状模型数据的变量,以及 "引擎" 已准备好处理下一幅图像的信号事件。
public class EngineThread
{
Thread WorkerObject = null;
HDevProcedureCall ProcCall;
HTuple ModelID;
HXLD ModelContours;
public AutoResetEvent EngineIsReady;
public EngineThread(MultiThreadingForm mainForm)
{
ModelID = mainForm.ModelID;
ModelContours = mainForm.ModelContours;
EngineIsReady = new AutoResetEvent(true);
}
主线程创建并初始化该类的两个实例,并存储它们的事件(文件:MultiThreadingForm.cs)。
EngineThread WorkerEngine1; // Processing thread. EngineThread WorkerEngine2; // Processing thread. AutoResetEvent Engine1Ready; AutoResetEvent Engine2Ready; WorkerEngine1 = new EngineThread(this); WorkerEngine1.Init(); Engine1Ready = WorkerEngine1.EngineIsReady; WorkerEngine2 = new EngineThread(this); WorkerEngine2.Init(); Engine2Ready = WorkerEngine2.EngineIsReady;
EngineThread 通过创建用于检测图像中盖子的函数调用来初始化自身。由于每次调用涉及形状模型的函数的输入参数都是相同的,因此可以提前设置一次(文件:EngineThread.cs)。
public void Init()
{
HDevProcedure Procedure = new HDevProcedure("detect_shape");
ProcCall = new HDevProcedureCall(Procedure);
ProcCall.SetInputCtrlParamTuple("ModelID", ModelID);
ProcCall.SetInputIconicParamObject("ModelContours", ModelContours);
}
初始化图像采集
最后,我们对图像采集进行初始化。句柄存储在表单的一个变量中,以便控制线程可以访问(文件:MultiThreadingForm.cs)。
private HFramegrabber AcqHandle;
string ImagePath = Path.GetFullPath(ExampleDir + "/images/cap_illumination");
AcqHandle = new HFramegrabber("File", 1, 1, 0, 0, 0, 0, "default", -1,
"default", -1, "default", ImagePath, "default", -1, -1);
单击 运行 按钮后,应用程序开始循环处理图像。
启动处理线程和控制线程
首先,主线程启动处理引擎(文件:MultiThreadingForm.cs)。
private void RunButton_Click(object sender, System.EventArgs e)
{
WorkerEngine1.Run();
WorkerEngine2.Run();
相应的方法会创建和启动线程,并设置 "就绪 "信号(文件:EngineThread.cs)。
public void Run()
{
EngineIsReady.Set();
WorkerObject = new Thread(new ThreadStart(Process));
WorkerObject.Start();
}
然后,主线程启动控制线程(文件:MultiThreadingForm.cs):
ControlThread = new Thread(new ThreadStart(Run)); ControlThread.Start();
从控制线程触发处理线程
控制线程的操作包含在 Run(文件:MultiThreadingForm.cs)方法中。只要没有按下 Stop(更多信息请查看项目代码),控制线程就会等待,直到其中一个处理引擎准备就绪。
EngineThread WorkerEngine; // Variable to switch between processing threads.
public void Run()
{
HImage Image;
while (!StopEventHandle.WaitOne(0, true))
{
if (Engine1Ready.WaitOne(0, true))
WorkerEngine = WorkerEngine1;
else if (Engine2Ready.WaitOne(0, true))
WorkerEngine = WorkerEngine2;
else
continue;
Image = AcqHandle.GrabImageAsync(-1);
WorkerEngine.SetImage(Image);
然后,它会获取下一幅图像并将其传递给引擎,引擎会将其存储在一个成员变量中(文件:EngineThread.cs)。
private HImage InputImage = null;
public void SetImage(HImage Img)
{
InputImage = Img;
}
处理图像
在其操作方法(Process)中,处理线程等待图像设置(文件:EngineThread.cs)。实际图像处理由 HDevelop 函数执行,并将图像作为输入参数传递。
public void Process()
{
while (!DelegatedStopEvent.WaitOne(0, true))
{
if (InputImage == null)
continue;
ProcCall.SetInputIconicParamObject("Image", InputImage);
ProcCall.Execute();
将结果传递给控制线程
为了传递结果,我们定义了一个类来存储相关数据:处理过的图像以及找到的盖子的位置、方向和轮廓。
public class ResultContainer
{
public HImage InputImage;
public HXLD FoundContours;
public double Row;
public double Column;
public double Angle;
}
执行函数后,处理线程会访问其结果,并将结果与处理过的图像一起存储在结果类的新实例("结果容器")中。
ResultContainer Result;
HTuple ResultTuple;
Result = new ResultContainer();
Result.InputImage = InputImage;
Result.FoundContours = ProcCall.GetOutputIconicParamXld("ResultObject");
ResultTuple = ProcCall.GetOutputCtrlParamTuple("ResultData");
Result.Row = ResultTuple[0];
Result.Column = ResultTuple[1];
Result.Angle = ResultTuple[2];
然后,处理线程通过将结果容器追加到列表中,将其传递给控制线程。
ResultMutex.WaitOne(); ResultList.Add(Result); ResultMutex.ReleaseMutex();
该列表是主线程(文件:MultiThreadingForm.cs)的成员变量。它受互斥保护,因此线程可以安全地访问它。
public ArrayList ResultList;
public Mutex ResultDataMutex;
public MultiThreadingForm()
{
ResultDataMutex = new Mutex();
ResultList = new ArrayList();
}
处理线程在自己的成员变量中存储对列表和互斥的引用(文件:EngineThread.cs)。
ArrayList ResultList;
Mutex ResultMutex;
public EngineThread(MultiThreadingForm mainForm)
{
ResultList = mainForm.ResultList;
ResultMutex = mainForm.ResultDataMutex;
}
"再次准备就绪"
最后,处理线程通过设置相应的事件和将输入图像设置为 null ,来表示它已准备好处理下一幅图像。
InputImage = null; this.EngineIsReady.Set();
检查是否有新结果
让我们回到控制线程的操作方法(Run)(文件:MultiThreadingForm.cs)。通过传递要处理的图像触发处理线程后,它将检查结果列表是否包含新项目。
int Count = -1; ResultDataMutex.WaitOne(); Count = ResultList.Count; ResultDataMutex.ReleaseMutex();
委托显示
控制线程本身并不执行结果显示,而是通过方法 Invoke 将其委托给主线程(运行表单)。
for (; Count > 0; Count--) Invoke(DelegatedDisplay);
必要的成员由表格定义。
delegate void FuncDelegate();
FuncDelegate DelegatedDisplay;
public MultiThreadingForm()
{
DelegatedDisplay = new FuncDelegate(DisplayResults);
}
请注意,如 "图形的线程问题& quot; 一章 所述,所有 HALCON 可视化操作都会自动委托给正确的线程。
显示结果
实际显示由方法 DisplayResults 执行。每次调用该方法时,它都会从结果列表中移除一个项目,并显示经过处理的图像和找到的盖子的轮廓。然后释放相应的 HALCON 内部存储器。
public void DisplayResults()
{
ResultDataMutex.WaitOne();
Result = (ResultContainer)ResultList[0];
ResultList.Remove(Result);
ResultDataMutex.ReleaseMutex();
Window.ClearWindow();
Window.DispImage(Result.InputImage);
Window.DispObj(Result.FoundContours);
Result.InputImage.Dispose();
Result.FoundContours.Dispose();
}
与上一节不同的是,这里介绍的示例应用程序 MultiThreadingTwoWindows 通过两个线程并行执行不同的 HDevelop 函数(任务)。一个任务是使用形状匹配查找瓶盖,另一个任务是读取 ECC 200 数据码。
图 23.10 显示了应用程序的结构概览。与上一节描述的应用程序一样,它由四个线程组成: 主线程(即窗体)负责图形用户界面(GUI),如 图 23.9 所示。它由一个用于显示结果的 HALCON 窗口和用于初始化、启动和停止应用程序的按钮组成。
主线程还通过创建和初始化其他三个线程(两个处理线程和控制两个处理线程的所谓控制线程)来初始化应用程序。与之前的应用程序不同,这里的处理线程通过 HDevelop 函数分别训练形状模型和数据码模型,从而初始化图像处理任务。
控制线程获取图像并将其传递给处理线程,然后处理线程处理图像并将结果传回。控制线程收集结果,但本身并不显示结果,因为 HALCON 窗口中的所有活动都必须由创建该窗口的线程(即主线程)执行。与前一个应用程序不同的是,两个任务的结果分别显示在两个窗口中。
下面,我们将仔细研究相应的代码,但仅限于与前一个应用程序不同的部分。
与上一个示例一样,应用程序在 Init 按钮(文件:MultiThreadingTwoWindowsForm.cs)的事件处理程序中初始化。
创建并初始化处理引擎
处理引擎的创建和初始化与上一个示例类似,但有一些例外: 首先,形状和数据码模型现在由处理线程而不是控制线程进行训练(参见下面的步骤)。其次,处理引擎现在也有一个变量来表示 "它们的" HALCON 窗口(文件:EngineThread.cs)。
public class EngineThread
{
...
public int WindowIndex = -1;
...
控制线程在创建引擎(文件: MultiThreadingTwoWindowsForm.cs)。
private void InitButton_Click(object sender, System.EventArgs e)
{
...
WorkerEngine1.WindowIndex = 1;
...
WorkerEngine2.WindowIndex = 2;
训练形状和数据码模型
形状和数据码模型的训练现在由处理线程的初始化方法执行,该方法现在有一个指定处理线程任务的参数(文件:MultiThreadingTwoWindowsForm.cs)。
WorkerEngine1.Init("shape");
...
WorkerEngine2.Init("datacode");
用于训练模型和执行图像处理的 HDevelop 函数为两个任务设置了相似的名称,以便自动生成它们的名称(文件:EngineThread.cs)。任务名称本身存储在 EngineThread 类的一个变量中。
public class EngineThread
{
HDevProcedureCall ProcCall;
string Task;
HTuple ModelID;
HXLD ModelContours;
...
public void Init(string Task)
{
string TrainMethod = "train_" + Task + "_model";
string ProcessingMethod = "detect_" + Task;
HDevProcedureCall ProcTrain;
this.Task = Task;
然后,通过执行相应的 HDevelop 函数分别训练形状或数据码的模型,并将返回的模型数据存储在类的变量中。
HDevProcedure Procedure = new HDevProcedure(TrainMethod);
ProcTrain = new HDevProcedureCall(Procedure);
ProcTrain.Execute();
ModelID = ProcTrain.GetOutputCtrlParamTuple("ModelID");
if (Task.Equals("shape"))
{
ModelContours = ProcTrain.GetOutputIconicParamXld("ModelContours");
}
存储模型数据
最后,设置图像处理程序中每次调用都相同的输入参数(文件:EngineThread.cs)。
HDevProcedure Procedure = new HDevProcedure(ProcessingMethod);
ProcCall = new HDevProcedureCall(Procedure);
ProcCall.SetInputCtrlParamTuple("ModelID", ModelID);
if (Task.Equals("shape"))
{
ProcCall.SetInputIconicParamObject("ModelContours", ModelContours);
}
初始化图像采集
两个图像处理任务在不同的图像中执行,因此主线程打开了两个图像采集设备(文件:MultiThreadingTwoWindowsForm.cs,代码未显示)。
触发处理线程
控制线程的操作包含在 Run(文件:MultiThreadingTwoWindowsForm.cs)方法中。只要不按下Stop ,它就会检查处理引擎是否准备就绪,如果准备就绪,就会获取并传递图像。
public void Run()
{
HImage Image;
while (!StopEventHandle.WaitOne(0, true))
{
if (Engine1Ready.WaitOne(0, true))
{
Image = AcqHandle1.GrabImageAsync(-1);
WorkerEngine1.SetImage(Image);
}
if (Engine2Ready.WaitOne(0, true))
{
Image = AcqHandle2.GrabImageAsync(-1);
WorkerEngine2.SetImage(Image);
}
将结果传递给控制线程
存储结果数据的类与上一个示例中的类有很大不同: 它现在还包含一个变量,用于指示显示结果的窗口,以及一个标志,用于显示处理是否成功。由于两个任务的处理结果不同,它们被封装在一个元组中(文件:EngineThread.cs)。
public class ResultContainer
{
public int WindowIndex; // 1 -> shape, 2 -> datacode.
public HImage InputImage;
public HXLD FoundContours;
public HTuple ResultData;
public bool DetectionSuccessful;
}
执行程序后,处理线程会访问其结果,并将结果与处理过的图像和窗口索引一起存储在结果容器的新实例中。
public void Process()
{
ResultContainer Result;
Result = new ResultContainer();
...
Result.InputImage = InputImage;
DetectionSuccessful = ProcCall.GetOutputCtrlParamTuple("DetectionSuccessful").S;
if (DetectionSuccessful.Equals("true"))
{
Result.DetectionSuccessful = true;
Result.FoundContours = ProcCall.GetOutputIconicParamXld("ResultObject");
Result.ResultData = ProcCall.GetOutputCtrlParamTuple("ResultData");
}
else
{
Result.DetectionSuccessful = false;
}
Result.WindowIndex = WindowIndex;
与上一个示例一样,结果显示由主线程在方法 ResultDisplay(文件:MultiThreadingTwoWindowsForm.cs)中执行。主要区别在于,现在是根据结果容器中的变量在两个 HALCON 窗口之间切换显示。
public void DisplayResults()
{
HWindow Window;
if (Result.WindowIndex == 1)
{
Window = Window1;
}
else
{
Window = Window2;
}
此外,现在的显示方法会检查图像处理是否成功,以避免访问不存在的结果元素。对于这两项任务,都会显示结果轮廓,即分别找到的形状或数据码区域。对于数据码任务,还会显示读取的码。
Window.ClearWindow();
Window.DispImage(Result.InputImage);
if (Result.DetectionSuccessful)
{
Window.DispObj(Result.FoundContours);
// Additional display for data code result: code.
if (Result.WindowIndex == 2)
{
Row = (int)Result.ResultData[0].D;
Col = (int)Result.ResultData[1].D;
Window.SetTposition(Row, Col);
Window.WriteString((string)Result.ResultData[2].S);
}
}
else
{
Window.SetColor("red");
Window.SetTposition(20, 20);
Window.WriteString("Detection failed!");
Window.SetColor("green");
}
示例应用程序 UseVectorVariables 展示了如何在 HDevengine/C# 中加载和执行包含向量变量的 HDevelop 示例。示例中使用了两个向量进行处理:一个包含输入图像,另一个包含缩放因子。执行程序时,输入图像的灰度值将根据缩放因子进行缩放。请查看源文件 UseVectorVariablesForm.cs,了解如何在 HDevengine/.NET 中使用向量变量的详细信息。