在C#中使用Halcon開發視覺檢測程式

二次元攻城獅發表於2022-11-27

簡介

本文的初衷是希望幫助那些有其它平臺視覺演算法開發經驗的人能快速轉入Halcon平臺下,透過文中的示例開發者能快速瞭解一個Halcon專案開發的基本步驟,讓開發者能把精力完全集中到演算法的開發上面。

首先,你需要安裝HalconHALCON 18.11.0.1的安裝包會放在文章末尾。安裝包分開發和執行時兩個版本,執行時版本一般用於生產環境。
注:開發版本自帶執行時可替代執行時版本,但安裝的東西會比較多。

然後,你需要學會檢視Halcon的幫助手冊,這是很重要的一件事

本文涉及到幫助文件的主要章節如下:

原文 HALCON 18.11.0.1 / Programmer's Guide / Programming With HALCON/.NET
翻譯 HALCON 18.11.0.1/程式設計師指南/使用 HALCON/.NET 程式設計

原文 HALCON 18.11.0.1 / HALCON Operator Reference
翻譯 HALCON 18.11.0.1/ HALCON 運算子參考

文中的示例是我第一次接觸Halcon時的學習測試用例,在電腦裡面躺了一年,最近才有時間整理一下發出來,希望能對你有所幫助。

注:執行本文示例程式前至少安裝Halcon的執行時,否則Halcon的dll無法正常使用

將 HALCON/.NET 新增到應用程式

新增控制元件

右鍵單擊工具箱,然後選擇“選擇項”,彈出的對話方塊選擇“.NET Framework元件”,單擊下面的“瀏覽”,導航到HALCON安裝目錄下的\bin\dotnet35(VS2008以下版本的選擇dotnet20) ,然後選擇halcondotnet.dll

完成上述操作後,HSmartWindowControl和HWindowControl控制元件就會出現在工具箱中,其中HWindowControl控制元件已經過時官方不再推薦使用。

與HWindowControl相比,HSmartWindowControl控制元件具有以下幾個優點:

  • 可以像任何其他控制元件一樣使用
  • 提供預定義的滑鼠互動(移動視窗內容並使用滑鼠滾輪進行縮放), 可以透過雙擊視窗來重置檢視
  • 控制元件會自動重新縮放,而不會閃爍

注:與HSmartWindowControlWPF 相反,HSmartWindowControl需要一個回撥才能使用滑鼠滾輪進行縮放

引用dll

在HALCON安裝目錄下的\bin\dotnet35中,引用以下dll:

  • hdevenginedotnet.dll
  • halcondotnet.dll

注:使用 HALCON XL 開發應用程式時,必須選擇以xl結尾的dll,hhdevelop xl適用於大解析度的影像(大於 32k x 32k )。

引用以下名稱空間:

  • HalconDotNet:控制元件所在的名稱空間
  • HalconTypeLineRectangle2等資料型別所在的名稱空間

呼叫Halcon運算元

ReadImage操作為例,函式原型如下:

static void HOperatorSet.ReadImage(out HObject image, HTuple fileName)

public HImage(HTuple fileName)

public HImage(string fileName)

void HImage.ReadImage(HTuple fileName)

void HImage.ReadImage(string fileName)

注:這些內容幫助手冊上都有,在文章開頭列出來的章節。

在C#呼叫HALCON 運算元有兩種選擇:函式式物件式,前值透過HOperatorSet呼叫運算元並透過out關鍵字傳入關鍵物件,後者直接在關鍵物件上呼叫對應的方法。
兩種方法完全等價,C#是一門物件導向的語言,建議使用物件式的方式呼叫運算元會好一點。

程式示例

本示例只實現下面幾種關鍵功能:

  • 載入、儲存圖片
  • 畫線、框並儲存
  • 抓邊演算法、測寬演算法

先新建一個Winform專案,介面設計如下:

注:專案的解決方案平臺不能使用AnyCPU,只能根據安裝的Halcon位數選擇x64x86,我使用的是x64平臺。

HSmartWindowControl控制元件使用

將HSmartWindowControl控制元件拖入主介面即可,在窗體類裡面定義一個HWindow型別的成員引用控制元件內部的窗體,同時設定控制元件的回撥函式(WPF則不需要)。程式碼如下:

//視窗例項
private HWindow hwindow;
       
public Form1()
{
    InitializeComponent();
    hwindow = hSmartWindowControl1.HalconWindow;//初始化視窗變數
    hSmartWindowControl1.MouseWheel += HSmartWindow_MouseWheel;
}

//滑鼠滾輪迴調
private void HSmartWindow_MouseWheel(object sender, MouseEventArgs e)
{
    Point pt = this.Location;
    MouseEventArgs newe = new MouseEventArgs(e.Button, e.Clicks, e.X - pt.X, e.Y - pt.Y, e.Delta);
    hSmartWindowControl1.HSmartWindowControl_MouseWheel(sender, newe);
}

載入、儲存影像

載入、儲存影像也比較簡單,我們需要先定義一個HImage例項,然後按鈕單擊事件在該例項上呼叫對應的運算元,程式碼如下:

//圖片變數
private HImage image = new HImage();
//載入圖片
private void button_ReadImage_Click(object sender, EventArgs e)
{           
    string imagePath = "TestRead.bmp";
    image.ReadImage(imagePath);
    hwindow.DispImage(image);
    //自動適應圖片(相當於控制元件上面的雙擊操作)
    hwindow.SetPart(0, 0, -2, -2);
}
//儲存圖片
private void button_WriteImage_Click(object sender, EventArgs e)
{
    string imagePath = "TestWrite.bmp";
    image.WriteImage("bmp", 0, imagePath);
    hwindow.DispImage(image);
}

上面程式碼是從程式啟動目下載入TestRead.bmp圖片,儲存圖片到程式啟動目下的TestWrite.bmp,實際路徑可以根據專案情況自己定義。
上面的圖片是自己生成的,不是生產環境下的產品圖片,僅用於程式演示。

擴充套件:載入相機影像

大部分專案都是從相機載入圖片,但這涉及到相機驅動的一些知識,全部介紹一邊會偏移文章主題。
簡單來說,載入相機影像分兩步:

  • 將相機影像儲存到記憶體
  • 將記憶體中的影像傳入Halcon

將相機影像儲存到記憶體是相機驅動的工作,下面只討論怎麼將記憶體中的影像傳入Halcon,程式碼如下:

private void GenImageByPtr()
{
    //這三個引數都可以透過相機驅動得到
    byte[] imageBuf = null;   //影像快取陣列
    int width = 0;            //影像寬度
    int heigth = 0;           //影像高度
    //獲取記憶體影像中間的指標
    IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(imageBuf, 0);
    //載入記憶體中的影像
    image.GenImage1("byte", width, heigth, ptr);
    hwindow.DispImage(image);
}

這裡只列一個簡單的示例,類似的運算元還有copy_imagegen_image3等。

畫線、畫框並儲存

在影像上畫線、框是機器視覺裡面常見的需求,根據線、框確定演算法搜尋的區域和特徵。
在窗體類中定義一個HDrawingObject物件並附加到現有視窗用於互動,同時定義好Line物件、Rectangle2物件用於儲存繪圖的結果。
先在影像視窗上面畫出線和框,然後再用滑鼠手動調整大小、位置,程式碼如下:

//繪圖物件
private HDrawingObject drawingObject = new HDrawingObject();
//線ROI
private Line line = new Line();
//框ROI
private Rectangle2 rectangle2 = new Rectangle2();

private void button_DrawLine_Click(object sender, EventArgs e)
{
    drawingObject.CreateDrawingObjectLine(100, 100, 200, 200);
    //將繪圖物件關聯到Halcon視窗
    hwindow.AttachDrawingObjectToWindow(drawingObject);
}
private void button_SaveLine_Click(object sender, EventArgs e)
{
    HTuple paramName, param;            
    paramName = new HTuple(new string[] { "row1", "column1", "row2", "column2" });
    param = drawingObject.GetDrawingObjectParams(paramName);
    //儲存引數
    line.SetValue(param.ToDArr());
    paramName.Dispose();
    param.Dispose();
    //清除繪圖內容
    drawingObject.ClearDrawingObject();           
}

private void button_DrawRect_Click(object sender, EventArgs e)
{
    drawingObject.CreateDrawingObjectRectangle2(300, 400, 0, 300, 200);
    //將繪圖物件關聯到Halcon視窗
    hwindow.AttachDrawingObjectToWindow(drawingObject);
}
private void button_SaveRect_Click(object sender, EventArgs e)
{
    HTuple paramName, param;
    paramName = new HTuple(new string[] { "row", "column", "phi", "length1", "length2" });
    param = drawingObject.GetDrawingObjectParams(paramName);
    //儲存引數
    rectangle2.SetValue(param.ToDArr());
    paramName.Dispose();
    param.Dispose();
    //清除繪圖內容
    drawingObject.ClearDrawingObject();
}

上面的paramName可以取以下值,裡面包含了LineRectangle2類屬性名:

"color", "column", "column1", "column2", "end_angle", "font", "length1", "length2", "line_style", 
"line_width", "phi", "radius", "radius1", "radius2", "row", "row1", "row2", "start_angle", "string", "type"

檢測演算法

用Halcon開發檢測演算法一般有兩種方法:

  • 根據直接呼叫Halcon在對應語言平臺下的運算元介面
  • 用Halcon自帶的指令碼語言開發演算法然後轉成C#類

第一種自由度比較高,程式碼看起來也比較簡潔易懂,但上手比較困難。第二種更簡單,但生成的類很難看,而且與程式整合的時候需要做一些改動。
兩種方法並不是絕對對立的,一般會先用Halcon驗證演算法,然後參考匯出的C#類實現自己的檢測演算法。

抓邊演算法

抓變演算法直接呼叫的是Halcon的C#運算元介面,裡面有用到2D 測量模型

2D測量模型

簡述一下2D 測量的使用步驟:

  • 建立測量模型並指定影像大小:首先必須使用create_metrology_model建立測量模型,然後使用set_metrology_model_image_size指定測量結果所在的影像的大小。

  • 提供近似值:將測量物件新增到測量模型中,每個測量物件由影像中相應物件的近似形狀引數控制測量的引數組成,控制測量的引數包括例如指定測量區域的尺寸和分佈的引數,測量物件有以下幾種:

    • :add_metrology_object_circle_measure
    • 橢圓:add_metrology_object_ellipse_measure
    • 矩形:add_metrology_object_rectangle2_measure
    • :add_metrology_object_line_measure
    • 使用一個運算子建立不同形狀:add_metrology_object_generic

要直觀檢查定義的度量物件,可以使用運算子get_metrology_object_model_contour訪問其XLD輪廓。要直觀檢查建立的測量區域,可以使用運算子get_metrology_object_measures訪問其XLD輪廓。

  • 修改模型引數:如果已執行相機校準,則可以使用set_metrology_model_param,沒有就忽略(本示例沒有使用)。

  • 修改物件引數:當將測量物件新增到測量模型時,可以設定許多引數,之後還可以使用運算子set_metrology_object_param修改其中的一些(本示例是在新增時設定的引數,所以沒有此步驟)。

  • 調整測量模型:在執行下一次測量之前平移和旋轉測量模型,可以使用操作員align_metrology_model。通常使用基於形狀的匹配來獲得對準引數,相當於測量前的位置就糾偏(本示例比較簡單沒有此步驟)。

  • 應用測量:使用apply_metrology_model執行測量過程。

  • 訪問結果:測量後,可以使用get_metrology_object_result訪問結果,也可以使用get_metrology_object_measures獲取定位邊的行座標和列座標再進一步處理(本示例使用前者)。

程式碼實現

抓變演算法的C#程式碼如下:

private void button_FindEdge_Click(object sender, EventArgs e)
{
    //建立測量物件
    HMetrologyModel hMetrologyModely = new HMetrologyModel();
    //設定圖片大小            
    image.GetImageSize(out int width, out int height);
    hMetrologyModely.SetMetrologyModelImageSize(width, height);
    //新增直線測量
    double measureLength1= 30, measureLength2=30, measureSigma=1, measureThreshold=30;
    HTuple genParamName = new HTuple(), genParamValue = new HTuple();
    hMetrologyModely.AddMetrologyObjectLineMeasure(line.Row1, line.Column1,line.Row2, line.Column2, measureLength1, measureLength2, measureSigma, measureThreshold, genParamName, genParamValue);
    //執行並獲取結果
    hMetrologyModely.ApplyMetrologyModel(image);
    //獲取測量區域
    HTuple mRow = new HTuple(), mCol = new HTuple();
    HXLDCont mContours = hMetrologyModely.GetMetrologyObjectMeasures("all", "all", out mRow, out mCol); //檢測區域輪廓
    HXLDCont mmContours = hMetrologyModely.GetMetrologyObjectModelContour("all", 1);    //測量物件輪廓
    //引數順序 ["row_begin", "column_begin", "row_end", "column_end"]
    HTuple  lineRet =hMetrologyModely.GetMetrologyObjectResult("all", "all", "result_type", "all_param");
    double[] retAry = lineRet.DArr;
    //列印結果
    hwindow.SetLineWidth(2);
    hwindow.SetColor("green");
    hwindow.DispLine(retAry[0], retAry[1], retAry[2], retAry[3]);
    hwindow.SetColor("blue");
    hwindow.DispXld(mContours);
    hwindow.SetColor("yellow");
    hwindow.DispXld(mmContours);
    //清空測量物件
    hMetrologyModely.ClearMetrologyModel();
    //清理物件
    hMetrologyModely?.Dispose();
    genParamName?.Dispose();
    genParamValue?.Dispose();
    mRow.Dispose();
    mCol.Dispose();
    mContours.Dispose();
    mmContours.Dispose();
}

Halcon的程式碼如下:

*讀取圖片
read_image (Image, 'D:/test.bmp')
dev_get_window (WindowHandle)

*畫線
Row1:=1218.79
Column1:=1002.95
Row2:=1242.07
Column2:=2786.18
*draw_line (WindowHandle, Row1, Column1, Row2, Column2)
*gen_region_line (RegionLines, Row1, Column1, Row2, Column2)

*建立測量幾何形狀所需的資料結構
create_metrology_model (MetrologyHandle)
get_image_size (Image, Width, Height)
set_metrology_model_image_size (MetrologyHandle, Width, Height)  
add_metrology_object_line_measure (MetrologyHandle, Row1, Column1, Row2, Column2, 100, 50, 1, 30, [], [], Index)

apply_metrology_model (Image, MetrologyHandle)

get_metrology_object_result (MetrologyHandle, 'all', 'all', 'result_type','all_param', Parameter)

get_metrology_object_measures(Contours, MetrologyHandle, 'all', 'all', Row, Column)

get_metrology_object_model_contour (Contour, MetrologyHandle, 0, 1.5)

*清空測量物件,否則會導致記憶體洩露
clear_metrology_model (MetrologyHandle)

*視覺化
dev_clear_window ()
dev_display(Image)
dev_set_color('green')
dev_set_line_width(1)
disp_line (WindowHandle, Parameter[0], Parameter[1], Parameter[2], Parameter[3])
dev_display (Contours)
dev_display (Contour)

使用方法

直接在介面上點選“開啟圖片”->“畫線ROI”(預設位置我都調好了,你也可以自己調整大小、位置)->“抓邊”,過程如下:

測寬演算法

測寬演算法使用一維測量中的measure_pairs運算元提取直邊對,然後計算兩個直邊的距離。程式碼太長這裡就不貼了,完整的專案原始碼會在文章末尾給出。
需要注意,measure_pairs運算元的搜尋框必須和目標邊緣完全垂直,否則寬度資料會不準確,運算元原理如下:

直接在介面上點選“開啟圖片”->“畫框ROI”(預設位置我都調好了,你也可以自己調整大小、位置)->“測寬”,過程如下:

上面的箭頭就是框的方向,測量邊必須與框的方向接近垂直否則會運算失敗,實際專案中還是建議用2D測量單獨抓兩個邊來測寬度。
原始碼裡面顯示邊緣的DispEdgeMarker方法,是直接從measure_pairs運算元示例裡面匯出轉C#的,所以風格會比較奇怪。

附件

相關文章