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的程序和外部函數文件也要一同進行移植。
以上所有內容僅為個人看法,如有不當,歡迎指正!
