C#与Halcon混合编程的几种方式
Halcon的学习过程中,关于Halcon的混合编程是无法避免的,Halcon可以和很多种语言进行混编,这里仅赘述与C#语言进行混编的一些简单方式。
C#与Halcon进行混编的方式大体可以分为:使用Halcon导出功能、面向对象的方式、Halcon引擎。
当然,除了以上方法,还有导出库工程这样的方式可以选择,这里就不再阐述。
一、Halcon导出功能
下图为使用Halcon编写的简单程序
然后点击文件-》导出,或者直接点击如下按钮
点击导出之后,选择语言为C#-Halcon/.NET,选择好导出文件的路径,则会得到一个和Halcon程序同名的.cs文件
打开.cs文件,我们看到导出的代码中有一个action的方法,这个方法是即是我们需要重点关注的部分,我们在Halcon中写的代码都在这个方法中有所实现。以下为action的代码:
1 private void action() 2 { 3 4 5 // Local iconic variables 6 7 HObject ho_Image, ho_Region, ho_ConnectedRegions; 8 HObject ho_RegionDilation; 9 10 // Local control variables 11 12 HTuple hv_Width = null, hv_Height = null; 13 // Initialize local and output iconic variables 14 HOperatorSet.GenEmptyObj(out ho_Image); 15 HOperatorSet.GenEmptyObj(out ho_Region); 16 HOperatorSet.GenEmptyObj(out ho_ConnectedRegions); 17 HOperatorSet.GenEmptyObj(out ho_RegionDilation); 18 ho_Image.Dispose(); 19 HOperatorSet.ReadImage(out ho_Image, "printer_chip/printer_chip_01"); 20 HOperatorSet.GetImageSize(ho_Image, out hv_Width, out hv_Height); 21 if (HDevWindowStack.IsOpen()) 22 { 23 HOperatorSet.SetPart(HDevWindowStack.GetActive(), 0, 0, hv_Height, hv_Width); 24 } 25 ho_Region.Dispose(); 26 HOperatorSet.Threshold(ho_Image, out ho_Region, 128, 255); 27 ho_ConnectedRegions.Dispose(); 28 HOperatorSet.Connection(ho_Region, out ho_ConnectedRegions); 29 ho_RegionDilation.Dispose(); 30 HOperatorSet.DilationCircle(ho_ConnectedRegions, out ho_RegionDilation, 3.5); 31 if (HDevWindowStack.IsOpen()) 32 { 33 HOperatorSet.DispObj(ho_Image, HDevWindowStack.GetActive()); 34 } 35 if (HDevWindowStack.IsOpen()) 36 { 37 HOperatorSet.DispObj(ho_ConnectedRegions, HDevWindowStack.GetActive()); 38 } 39 40 41 ho_Image.Dispose(); 42 ho_Region.Dispose(); 43 ho_ConnectedRegions.Dispose(); 44 ho_RegionDilation.Dispose(); 45 46 }
现在,需要我们对这个代码进行简单的更改,就可以在C#的Halcon窗体控件中将其显示出来,首先,新建一个C#窗体程序,添加halcondotnet的引用,并且添加其命名空间,在主窗体上添加一个Halcon的窗体控件和一个Button控件,并在Button的Click事件对应的方法中添加如下代码即可:
代码如下:
1 namespace Halconprogram 2 { 3 public partial class Form1 : Form 4 { 5 public Form1() 6 { 7 InitializeComponent(); 8 } 9 10 private void button1_Click(object sender, EventArgs e) 11 { 12 13 // Local iconic variables 14 15 HObject ho_Image, ho_Region, ho_ConnectedRegions; 16 HObject ho_RegionDilation; 17 18 // Local control variables 19 20 HTuple hv_Width = null, hv_Height = null; 21 // Initialize local and output iconic variables 22 HOperatorSet.GenEmptyObj(out ho_Image); 23 HOperatorSet.GenEmptyObj(out ho_Region); 24 HOperatorSet.GenEmptyObj(out ho_ConnectedRegions); 25 HOperatorSet.GenEmptyObj(out ho_RegionDilation); 26 ho_Image.Dispose(); 27 HOperatorSet.ReadImage(out ho_Image, "printer_chip/printer_chip_01"); 28 HOperatorSet.GetImageSize(ho_Image, out hv_Width, out hv_Height); 29 30 HOperatorSet.SetPart(hWindowControl1.HalconID, 0, 0, hv_Height, hv_Width); 31 32 ho_Region.Dispose(); 33 HOperatorSet.Threshold(ho_Image, out ho_Region, 128, 255); 34 ho_ConnectedRegions.Dispose(); 35 HOperatorSet.Connection(ho_Region, out ho_ConnectedRegions); 36 ho_RegionDilation.Dispose(); 37 HOperatorSet.DilationCircle(ho_ConnectedRegions, out ho_RegionDilation, 3.5); 38 39 HOperatorSet.DispObj(ho_Image, hWindowControl1.HalconID); 40 41 42 HOperatorSet.DispObj(ho_ConnectedRegions, hWindowControl1.HalconID); 43 44 45 ho_Image.Dispose(); 46 ho_Region.Dispose(); 47 ho_ConnectedRegions.Dispose(); 48 ho_RegionDilation.Dispose(); 49 } 50 } 51 }
比较前两段代码可以发现,只是对Halcon中导出的.cs文件进行了简单的窗体句柄的更改,其他地方没有任何改变,即可实现想要的效果。
需要注意的是,如果Halcon代码中存在外部函数的话,在导出时,除了action方法外,还需要将导出的cs文件中对应外部函数的整个方法实现也要拷贝到自己的工程中。
二、面向对象的方式
使用Halcon程序导出为C#程序之后,可以发现它的可读性并不太好,而且更改起来不太方便,所有的对象都是HObject类型。
以下程序不使用Halcon的IDE,直接添加引用和命名空间后,在C#环境下编写:
1 namespace Halconprogram 2 { 3 public partial class Form1 : Form 4 { 5 public Form1() 6 { 7 InitializeComponent(); 8 } 9 10 private void button1_Click(object sender, EventArgs e) 11 { 12 HImage img = new HImage("printer_chip/printer_chip_01"); 13 img.GetImageSize(out HTuple width, out HTuple height); 14 hWindowControl1.HalconWindow.SetPart(0D, 0, height, width); 15 HRegion region = img.Threshold(128D, 255); 16 HRegion conRegion = region.Connection(); 17 HRegion dilationRegion = conRegion.DilationCircle(3.5); 18 hWindowControl1.HalconWindow.SetColored(12); 19 hWindowControl1.HalconWindow.DispObj(img); 20 hWindowControl1.HalconWindow.DispObj(dilationRegion); 21 22 img.Dispose(); 23 region.Dispose(); 24 conRegion.Dispose(); 25 dilationRegion.Dispose(); 26 } 27 } 28 }
可以看出,使用这种方式同样能达到相同的效果,并且对于图像、区域、XLD都有相关的HImage、HRegion、HXLD类与其对应,看起来更加直观。其实在使用导出功能时,调用Halcon的方法基本使用的都是HOperatorSet这个类,这个类里面包含了Halcon的几乎所有的算子的静态方法,那么为什么Halcon还要提供如HImage、HRegion、HXLD等许多的其他的类呢,我觉得是因为在一些情况下,使用面向对象的方式更容易进行代码封装,而且更容易去修改代码。不过,存在即合理,我觉得在一些代码量较小的Halcon算法中,使用这种方法的确挺好的,可以很好的锻炼自己脱离Halcon的IDE去写Halcon算法的能力,但是面对代码量很大的Halcon算法,个人觉得这不是一个很好好的方式,写起来会比较累,不那么容易调试,前提还是在你对这种方式足够熟悉的基础上。
三、Halcon引擎
在了解Halcon引擎之前,我们最好先了解HDevelop函数文件,即.hdvp后缀的Halcon文件。使用Halcon引擎在C#中进行编程的方式我觉得主要是面向三种Halcon程序:
1.不含自己创建的外部函数的Halcon程序,
这样的程序只需要new一个HDevEngine实例,一个HDevProgram实例,以及一个HDevProgramCall实例(其实可以不用这个实例也可以,为了方便可以new一个),在编写时主要需要注意的是HDev文件的路径,我这里将HDev文件放在了Debug目录下的HDEV文件夹内。
最终效果:
代码:
1 namespace MyProgramCall 2 { 3 public partial class Form1 : Form 4 { 5 6 public Form1() 7 { 8 InitializeComponent(); 9 } 10 HDevEngine hDevEngine = new HDevEngine();//引擎 11 HDevProgram HDevProgram;//Halcon程序 12 HDevProgramCall HDevProgramCall;//Halcon程序执行实例 13 HWindow window;//窗体 14 HDevOpMultiWindowImpl HDevOpMultiWindowImpl;//方便Halcon程序操作显示的实例 15 string exePath = ""; 16 private void btn_Init_Click(object sender, EventArgs e) 17 { 18 window = hWindowControl1.HalconWindow; 19 //通过以下两句,表示整个要执行的Halcon的HDev程序的父窗口(即显示窗口)为当前的窗口控件,如果不添加以下两句,则无法将Halcon程序的显示内容显示在窗体控件上,不过写程序时不建议通过将Halcon程序内的内容通过这种方式显示在控件上,可以直接拿到想要的对象然后再显示比较好 20 //HDevOpMultiWindowImpl = new HDevOpMultiWindowImpl(window); 21 //hDevEngine.SetHDevOperators(HDevOpMultiWindowImpl); 22 HDevProgram = new HDevProgram(exePath + "HDEV\\test.hdev");//实例化一个HDevProgram的实例 23 HDevProgramCall = new HDevProgramCall(HDevProgram);//实例化一个执行Halcon程序的实例 24 MessageBox.Show("初始化成功!!!"); 25 } 26 27 private void Form1_Load(object sender, EventArgs e) 28 { 29 exePath = AppDomain.CurrentDomain.BaseDirectory; 30 } 31 32 private void btn_Execute_Click(object sender, EventArgs e) 33 { 34 try 35 { 36 HDevProgramCall.Execute(); 37 } 38 catch (HDevEngineException Ex) 39 { 40 MessageBox.Show(Ex.Message, "HDevEngine Exception"); 41 return; 42 } 43 44 //建议不使用以上显示的两句,手动显示想要显示的部分 45 HImage img = HDevProgramCall.GetIconicVarImage("Image"); 46 HRegion region = HDevProgramCall.GetIconicVarRegion("RegionDilation"); 47 if(img.IsInitialized()) 48 { 49 img.GetImageSize(out HTuple width, out HTuple height); 50 window.SetPart(0D, 0, height, width); 51 //window.SetColor("green"); 52 window.SetColored(12); 53 window.SetDraw("fill"); 54 window.DispObj(img); 55 window.DispObj(region); 56 } 57 img.Dispose(); 58 } 59 } 60 }
2.一个单一的HDevlop函数文件
如果只是封装好的一个.hdvp后缀的外部函数的文件,同样可以利用Halcon引擎来调用。这里我封装了一个十分简单的加法的Halcon函数文件,在调用时,只需要传入适当的参数,即可调用函数
这个函数有两个输入的控制参数分别为num1和num2,有一个输出的控制参数为a,在C#中建立一个如下的窗口界面:
其中三个textbox分别代表两个输入和一个输出,ADD按钮表示执行这个函数,label2表示通过引擎调用这个函数的消耗时间
效果如下:
代码:
1 namespace hdvpFUc 2 { 3 public partial class Form1 : Form 4 { 5 6 public Form1() 7 { 8 InitializeComponent(); 9 } 10 11 HDevEngine hDevEngine = new HDevEngine();//引擎 12 HDevProcedure HDevProcedure;//Halcon函数 13 Stopwatch sw = new Stopwatch(); 14 string exePath = ""; 15 public void LoadAlgorithm() 16 { 17 string procedurePath = exePath + "HDEV"; 18 hDevEngine.SetProcedurePath(procedurePath);//使用引擎配置函数的路径,参数为这个函数的目录 19 HDevProcedure = new HDevProcedure("add_Num");//new一个Halcon外部函数的实例,参数为函数名 20 HDevProcedureCall hDevProcedureCall = new HDevProcedureCall(HDevProcedure);//new一个函数执行的实例 21 HTuple num1 = new HTuple(int.Parse(textBox1.Text)); 22 HTuple num2 = new HTuple(int.Parse(textBox2.Text)); 23 try 24 { 25 //设置函数参数,这里的参数名要和函数内的参数名一一对应 26 hDevProcedureCall.SetInputCtrlParamTuple("num1", num1); 27 hDevProcedureCall.SetInputCtrlParamTuple("num2", num2); 28 sw.Start(); 29 hDevProcedureCall.Execute();//执行函数 30 sw.Stop(); 31 } 32 catch (HDevEngineException EX) 33 { 34 MessageBox.Show(EX.ToString()); 35 return; 36 } 37 string time = sw.ElapsedMilliseconds.ToString(); 38 label2.Text = time; 39 HTuple a = hDevProcedureCall.GetOutputCtrlParamTuple("a");//获得函数的输出的控制参数”a" 40 textBox3.Text = a.ToString(); 41 } 42 private void button1_Click(object sender, EventArgs e) 43 { 44 LoadAlgorithm(); 45 } 46 47 private void Form1_Load(object sender, EventArgs e) 48 { 49 exePath = AppDomain.CurrentDomain.BaseDirectory; 50 } 51 } 52 }
3.包含外部函数的Halcon程序
个人觉得这个是最常用的情况,使用Halcon引擎的情况下,在Halcon程序写好后,我们往往会对其中的核心代码封装成一个外部函数文件,并设置好输入输出的参数。
其实就是将1、2两步结合在一起即可,想执行整个程序就通过HDevProgramCall的实例调用其Execute方法,并通过这个实例的Getxxxxxx开头的方法获取想得到的控制参数或者图标参数,如果想执行Halcon程序中的某个外部函数,那么就通过HDevProcedureCall的实例,在执行其Execute方法前通过这个实例的Setxxxxx开头的方法传入相关参数,然后执行即可,获取参数的方法和前面一样。这里只通过一个简单的示例说明
路径:
.hdev文件
.hdvp文件
效果
代码:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.IO; 7 using System.Linq; 8 using System.Text; 9 using System.Threading; 10 using System.Threading.Tasks; 11 using System.Windows.Forms; 12 using HalconDotNet; 13 namespace MultiDisplayEngeeni 14 { 15 public partial class Form1 : Form 16 { 17 HDevEngine hDevEngine; 18 HDevProgram hDevProgram; 19 HDevProgramCall hDevProgramCall; 20 HDevProcedure hDevProcedure; 21 HDevProcedureCall hDevProcedureCall; 22 String[] _fileNames; 23 HImage img = new HImage(); 24 HXLDCont contour = new HXLDCont(); 25 HXLD affContour = new HXLD(); 26 HShapeModel HShapeModel = new HShapeModel("board.shm"); 27 int _Index = 0; 28 ManualResetEvent ManualResetEvent = new ManualResetEvent(false); 29 string exePath = ""; 30 public Form1() 31 { 32 InitializeComponent(); 33 } 34 35 private void btn_Init_Click(object sender, EventArgs e) 36 { 37 InitHdevlop(); 38 Action(); 39 btn_Init.Enabled = false; 40 btn_Start.Enabled = true; 41 } 42 43 public void InitHdevlop() 44 { 45 hDevEngine = new HDevEngine(); 46 hDevEngine.SetProcedurePath(@"./HDVP"); 47 hDevProgram = new HDevProgram(@"./HDEV\affine_trans_model.hdev"); 48 hDevProgramCall = new HDevProgramCall(hDevProgram); 49 hDevProcedure = new HDevProcedure(hDevProgram, "affine_trans_find_model"); 50 hDevProcedureCall = new HDevProcedureCall(hDevProcedure); 51 } 52 53 public void Action() 54 { 55 Action ac = new Action(() => { 56 hWindowControl1.HalconWindow.DispImage(img); 57 hWindowControl1.HalconWindow.DispObj(affContour); 58 }); 59 Task.Run(() => 60 { 61 while (true) 62 { 63 try 64 { 65 ManualResetEvent.WaitOne(); 66 img.Dispose(); 67 affContour.Dispose(); 68 string imgPath = exePath + _fileNames[_Index]; 69 img = new HImage(imgPath); 70 img.GetImageSize(out HTuple width, out HTuple height); 71 hWindowControl1.HalconWindow.SetPart(0D, 0, height, width); 72 hDevProcedureCall.SetInputCtrlParamTuple("ModelID", HShapeModel); 73 hDevProcedureCall.SetInputIconicParamObject("Image", img); 74 hDevProcedureCall.SetInputIconicParamObject("ModelContours", contour); 75 hDevProcedureCall.Execute(); 76 affContour = hDevProcedureCall.GetOutputIconicParamXld("ContoursAffineTrans"); 77 Invoke(ac); 78 _Index++; 79 if (_Index == _fileNames.Length - 1) 80 { 81 _Index = 0; 82 } 83 Thread.Sleep(10); 84 } 85 catch (Exception) 86 { 87 return; 88 } 89 } 90 }); 91 } 92 93 private void Form1_Load(object sender, EventArgs e) 94 { 95 exePath = AppDomain.CurrentDomain.BaseDirectory; 96 _fileNames = Directory.GetFiles("Images"); 97 hWindowControl1.HalconWindow.SetColor("blue"); 98 contour.ReadObject("contours.hobj"); 99 btn_Init.Enabled = true; 100 btn_Start.Enabled = false; 101 btn_Stop.Enabled = false; 102 } 103 104 private void btn_Start_Click(object sender, EventArgs e) 105 { 106 btn_Start.Enabled = false; 107 btn_Stop.Enabled = true; 108 ManualResetEvent.Set(); 109 } 110 111 private void btn_Stop_Click(object sender, EventArgs e) 112 { 113 btn_Start.Enabled = true; 114 btn_Stop.Enabled = false; 115 ManualResetEvent.Reset(); 116 } 117 } 118 }
在使用Halcon引擎时,个人觉得需要注意的是路径的配置(最好使用相对路径,方便程序移植),以及传参和获取参数时要保持和Halcon程序内部的参数名保持一致。
最后,做个小总结,关于具体使用哪种方法较好,我觉得因人而异,因为每种方法都可以实现相同的效果,并且各有优缺点
1.导出比较方便快捷,但是其不易封装和修改,可读性差。
2.面向对象的方式易封装和修改,并且完全可以脱离Halcon的IDE,但是它要求比较高的熟练度,而且面对较为复杂的程序和较多的代码量,这样的效率比较低。
3.使用Halcon引擎在代码修改方面十分方便,只需要在Halcon的IDE中修改好保存即可,并且也不存在可读性差的问题,无论是较为复杂的程序还是很简单的程序都是适用的,但是它比较依赖Halcon的软件,在移植的时候,Halcon的程序和外部函数文件也要一同进行移植。
以上所有内容仅为个人看法,如有不当,欢迎指正!