採用HALCON機器視覺軟體及C#語言檢測工件位置的方法 (之四)
四.工件位置檢測演算法
我們希望得到模組右下角第一個插座中的最右側的插針位置P(m,n)及該插座相對於夾具的角度a。如圖31所示。
圖31 插針座標與工件的夾角
因為插針有一定的長度,且鏡頭有變形,直接檢測插針前端面的位置偏差較大;且插針前端面的面積小、形狀一致性不好,不容易檢測。
經分析,插座底部塑膠基座的形狀特徵明顯,採用形狀匹配函式檢測插座底部影象,間接檢測插針位置檢測,試驗證明成功率高。
為提高檢測精度和可靠性,採用形狀匹配函式檢測三個特徵影象的位置,並用其中2個位置座標計算插針座標P和工件夾角a。這樣比僅檢測一個特徵影象的位置和轉角的方法可靠。
1。 偏移量(offset)的設定與計算
圖32 插針位置的偏移量
如圖32所示,點A、C為最右邊的2個檢測結果的中心座標。P點是最右側的插針位置。
1)設定偏移量
已知:a, b, c, d, m, n
求:q, L
由:k1 = (n - b) / (m - a)
k2 = (b - d) / (a - c)
得:q = arctan( (k1 – k2) / (1 + k1 X k2) ) (1)
及:
q、L是相對於插座的尺寸,與工件夾角a無關。
因此,選取好模板、進行一次匹配後,只需手動設定一次插針位置P(m,n);之後,進行插針位置檢測時,用A點、C點的座標及q、L,即可求出P和a。
2)計算偏移量
已知:a, b, c, d, q, L
求:a, m, n
a = arctan((b - d) / (a - c)) (3)
b = q + a
m = a + L X cos(b) (4)
n = b + L X sin(b)
1。 C#程式設計
1)將HDevelop的程式My_shape_match匯出C#程式碼如下:
//
// File generated by HDevelop for HALCON/DOTNET (C#) Version 10。0
//
// This file is intended to be used with the HDevelopTemplate or
// HDevelopTemplateWPF projects located under %HALCONEXAMPLES%\c#
using System;
using HalconDotNet;
public partial class HDevelopExport
{
public HTuple hv_ExpDefaultWinHandle;
// Procedures
// External procedures
// Chapter: Matching / Shape-Based
// Short Description: Display the results of Shape-Based Matching。
public void dev_display_shape_matching_results (HTuple hv_ModelID, HTuple hv_Color, HTuple hv_Row, HTuple hv_Column, HTuple hv_Angle, HTuple hv_ScaleR, HTuple hv_ScaleC, HTuple hv_Model)
{
// 該方法的功能是:顯示形狀匹配的結果。
// 對應於HDevelop中的函式dev_display_shape_matching_results()
// 不使用該方法,程式略。
}
// Chapter: Graphics / Text
// Short Description: This procedure writes a
text message。
public void disp_message (HTuple hv_WindowHandle, HTuple hv_String, HTuple hv_CoordSystem, HTuple hv_Row, HTuple hv_Column, HTuple
hv_Color, HTuple hv_Box)
{
// 該方法的功能是:在窗體中顯示文字資訊。
// 對應於HDevelop中的函式disp_message()
// 不使用該方法,程式略。
}
// Main procedure
private void action()
{
HSystem sys = new HSystem();
// Local iconic variables
HObject ho_Image=null, ho_Rectangle=null,
ho_ImageReduced=null;
HObject ho_Mask, ho_Cross;
// Local control variables
HTuple hv_Error = null;
HTuple hv_AcqHandle, hv_WindowID=new HTuple();
HTuple hv_Button, hv_R=new HTuple(), hv_C=new HTuple();
HTuple hv_Row1=new HTuple(), hv_Column1=new HTuple(), hv_Row2=new HTuple();
HTuple hv_Column2=new HTuple(), hv_ModelID,
hv_S1, hv_Row;
HTuple hv_Column, hv_Angle, hv_Score, hv_S2, hv_Runtime;
HTuple hv_y0, hv_y1, hv_x0, hv_x1;
// Initialize local and output iconic variables
HOperatorSet。GenEmptyObj(out ho_Image);
HOperatorSet。GenEmptyObj(out ho_Rectangle);
HOperatorSet。GenEmptyObj(out ho_ImageReduced);
HOperatorSet。GenEmptyObj(out ho_Mask);
HOperatorSet。GenEmptyObj(out ho_Cross);
try
{
HOperatorSet。CloseAllFramegrabbers();
//open camera with default settings:
HOperatorSet。OpenFramegrabber(“DahengCAM”, 1, 1, 0, 0, 0, 0,
“default”, -1, “default”, -1, “default”, “default”, “default”, -1, -1, out
hv_AcqHandle);
//open a window
//dev_close_window(。。。);
//dev_open_window(。。。);
//Define the region fill mode as margin
HOperatorSet。SetDraw(hv_ExpDefaultWinHandle, “margin”);
hv_Button = 0;
while ((int)(new HTuple(hv_Button。TupleEqual(0))) != 0)
{
// Grabbing images from a Daheng USB 2。0 camera
ho_Image。Dispose();
HOperatorSet。GrabImage(out ho_Image, hv_AcqHandle);
HOperatorSet。DispObj(ho_Image, hv_ExpDefaultWinHandle);
disp_message(hv_ExpDefaultWinHandle, “Load a old template press LEFT key, Set a new template press middle key”, “window”, 12, 12, “black”, “true”);
//draw a rectangle to select region of testing。
HOperatorSet。DispRectangle1(hv_ExpDefaultWinHandle, 320, 250, 630, 750);
// 掃描滑鼠按鍵
{
// 不使用該方法,程式略。
}
}
// 滑鼠中鍵按下,設定新的形狀模板
if ((int)(new HTuple(hv_Button。TupleEqual(2))) != 0)
{
disp_message(hv_ExpDefaultWinHandle, “draw rectangle for shape model。 ”,
“window”, 12, 12, “black”, “true”);
HOperatorSet。DrawRectangle1(hv_ExpDefaultWinHandle, out hv_Row1, out
hv_Column1, out hv_Row2, out hv_Column2);
ho_Rectangle。Dispose();
HOperatorSet。GenRectangle1(out ho_Rectangle, hv_Row1, hv_Column1, hv_Row2,
hv_Column2);
ho_ImageReduced。Dispose();
HOperatorSet。ReduceDomain(ho_Image, ho_Rectangle, out ho_ImageReduced);
HOperatorSet。WriteImage(ho_ImageReduced, “png”, 0, “D:/Vision/MySample/MyShapeMatch/Image006。png”);
}
// 滑鼠左鍵按下,讀取硬碟中已有的形狀模板
if ((int)(new HTuple(hv_Button。TupleEqual(1))) != 0)
{
disp_message(hv_ExpDefaultWinHandle, “Load a old template ”,
“window”, 12, 12, “black”, “true”);
ho_ImageReduced。Dispose();
HOperatorSet。ReadImage(out ho_ImageReduced, “Image006。png”);
}
// 生成一個待檢測的小區域,被檢測區域的影象名為Mask
ho_Rectangle。Dispose();
HOperatorSet。GenRectangle1(out ho_Rectangle, 320, 250, 630, 750);
ho_Mask。Dispose();
HOperatorSet。ReduceDomain(ho_Image, ho_Rectangle, out ho_Mask);
// 生成形狀模板、形狀匹配
HOperatorSet。CreateShapeModel(ho_ImageReduced, “auto”, (new
HTuple(-45))。TupleRad() , (new HTuple(90))。TupleRad(), “auto”, “auto”, “use_polarity”, “auto”, “auto”, out hv_ModelID);
HOperatorSet。CountSeconds(out hv_S1);
HOperatorSet。FindShapeModel(ho_Mask, hv_ModelID, (new HTuple(-45))。TupleRad()
, (new HTuple(90))。TupleRad(), 0。5, 3, 0。0, “least_squares”, 0, 0。5, out hv_Row,
out hv_Column, out hv_Angle, out hv_Score);
HOperatorSet。CountSeconds(out hv_S2);
hv_Runtime = (hv_S2-hv_S1)*1000;
// 顯示匹配結果
HOperatorSet。DispRectangle1(hv_ExpDefaultWinHandle, 320, 250, 630, 750);
dev_display_shape_matching_results(hv_ModelID, “green”, hv_Row, hv_Column,
hv_Angle, 1, 1, 0);
ho_Cross。Dispose();
HOperatorSet。GenCrossContourXld(out ho_Cross, hv_Row, hv_Column, 26, (new HTuple(45))。TupleRad() );
HOperatorSet。SetColor(hv_ExpDefaultWinHandle, “red”);
HOperatorSet。DispObj(ho_Cross, hv_ExpDefaultWinHandle);
hv_y0 = hv_Row[0];
hv_y1 = hv_Row[1];
hv_x0 = hv_Column[0];
hv_x1 = hv_Column[1];
HOperatorSet。DispLine(hv_ExpDefaultWinHandle, hv_y0, hv_x0, hv_y1,
hv_x1);
disp_message(hv_ExpDefaultWinHandle, ((new HTuple(hv_Score。TupleLength())+“ shapes located in ”)+(hv_Runtime。TupleString( “。1f”)))+“ ms ”,
“window”, 12, 12, “black”, “true”);
HOperatorSet。ClearShapeModel(hv_ModelID);
HOperatorSet。CloseFramegrabber(hv_AcqHandle);
}
catch (HalconException
HDevExpDefaultException)
{
ho_Image。Dispose();
ho_Rectangle。Dispose();
ho_ImageReduced。Dispose();
ho_Mask。Dispose();
ho_Cross。Dispose();
throw HDevExpDefaultException;
}
ho_Image。Dispose();
ho_Rectangle。Dispose();
ho_ImageReduced。Dispose();
ho_Mask。Dispose();
ho_Cross。Dispose();
}
public void InitHalcon()
{
// Default settings used in HDevelop
HOperatorSet。SetSystem(“do_low_error”, “false”);
}
public void RunHalcon(HTuple Window)
{
hv_ExpDefaultWinHandle = Window;
action();
}
}
1)將匯出的C#程式碼整理編寫幾個功能獨立的方法
如本文第二節“HALCON與C#混合程式設計的方法”所述,在Program。cs程式中編寫類HDevelopExport;然後在其中根據匯出的C#程式碼編寫以下幾個方法:halcon初始化InitHalcon();攝像頭初始化InitCamera(HTuple Window);採集影象、顯示影象GrabAndDisplay();設定模板SetTemplate();從檔案中讀取模板ReadTemplate();模板匹配MatchTemplate();顯示偏移點位置DisplayOffset();顯示計算角度,劃線DisplayLine();關閉相機CloseCamera()。
形狀匹配函式的引數設定與本文三。5。節的相同。
Program。cs程式如下所示:
using System;
using System。Collections。Generic;
using System。Linq;
using System。Windows。Forms;
using HalconDotNet;
namespace MatchMyTemplate
{
public partial class HDevelopExport
{
// 定義變數
public HTuple hv_ExpDefaultWinHandle;
HObject ho_Image, ho_Rectangle, ho_ImageReduced;
HObject ho_ModelContours;
HObject ho_Mask;
HTuple hv_AcqHandle;
HTuple hv_S1, hv_S2;
HTuple hv_Width, hv_Height;
HTuple hv_ModelID, hv_Row, hv_Column;
HTuple hv_Row1, hv_Column1, hv_Row2, hv_Column2;
HTuple hv_Angle, hv_Score, hv_Runtime;
HTuple hv_R;
public double[] Px;
public double[] Py;
public int ResultN;
public double usedTime;
public double Cx,Cy;
public double Fx,Fy;
public double Cr = 9;
public void InitHalcon() // 初始化halcon
{
HOperatorSet。SetSystem(“do_low_error”, “false”);
}
public void InitCamera(HTuple Window) // 攝像頭初始化
{
hv_ExpDefaultWinHandle = Window;
HOperatorSet。GenEmptyObj(out ho_Image); // 生ho_Image資料區
// Grabbing images from a Daheng USB 2。0 camera
HOperatorSet。CloseAllFramegrabbers();
HOperatorSet。OpenFramegrabber(“DahengCAM”, 1, 1, 0, 0, 0, 0, “default”, -1, “default”,-1, “default”, “default”, “default”, -1, -1, out hv_AcqHandle);
//open camera with default settings
}
public void GrabAndDisplay() // 採集影象、顯示影象
{
ho_Image。Dispose(); // 清除ho_Image中的資料
HOperatorSet。GrabImage(out ho_Image, hv_AcqHandle); // 採集影象
HOperatorSet。GetImageSize(ho_Image, out hv_Width, out hv_Height); // 獲取圖片的尺寸
HOperatorSet。SetPart(hv_ExpDefaultWinHandle, 0, 0, hv_Height - 1, hv_Width - 1);
HOperatorSet。DispObj(ho_Image, hv_ExpDefaultWinHandle);
// 顯示ho_Image中的圖片
HOperatorSet。SetDraw(hv_ExpDefaultWinHandle,“margin”); // 填充模式為只畫框
HOperatorSet。SetColor(hv_ExpDefaultWinHandle,“red”); // 畫線顏色紅
HOperatorSet。DispRectangle1(hv_ExpDefaultWinHandle,320, 250, 630, 750);
}
public void SetTemplate() // 設定模板
{
MessageBox。Show(“在紅框中按下滑鼠左鍵畫方框選模板,按右鍵結束”);
HOperatorSet。DrawRectangle1(hv_ExpDefaultWinHandle,
out hv_Row1, out hv_Column1, out hv_Row2, out hv_Column2);
HOperatorSet。GenRectangle1(out ho_Rectangle, hv_Row1, hv_Column1, hv_Row2,
hv_Column2);
HOperatorSet。ReduceDomain(ho_Image, ho_Rectangle, out ho_ImageReduced);
HOperatorSet。WriteImage(ho_ImageReduced, “png”, 0, “D:/Vision/MySample/ShapeMatch/Image005。png”);
MessageBox。Show(“模板已儲存”);
}
public void CloseCamera() // 關閉相機
{
HOperatorSet。CloseFramegrabber(hv_AcqHandle);
}
public void ReadTemplate() // 從檔案中讀取模板
{
HOperatorSet。GenEmptyObj(out ho_ImageReduced);
ho_ImageReduced。Dispose();
HOperatorSet。ReadImage(out ho_ImageReduced, “D:/Vision/MySample/ShapeMatch/Image005。png”);
}
public void MatchTemplate() // 模板匹配
{
int i,j;
double temp;
HOperatorSet。GenRectangle1(out ho_Rectangle, 320, 250, 630, 750);
HOperatorSet。ReduceDomain(ho_Image, ho_Rectangle, out ho_Mask);
//Reduce image range
HOperatorSet。CreateShapeModel(ho_ImageReduced, “auto”, (new HTuple(-45))。TupleRad() ,(new HTuple(90))。TupleRad(),
“auto”, “auto”,“use_polarity”, “auto”, “auto”,out hv_ModelID);
HOperatorSet。CountSeconds(out hv_S1); // Match start
HOperatorSet。FindShapeModel(ho_Mask,hv_ModelID, (new HTuple(-45))。TupleRad()
,(new HTuple(90))。TupleRad(),0。5, 3, 0。0, “ least_squares ”, 0,0。5, out hv_Row,
out hv_Column, outhv_Angle, out hv_Score);
HOperatorSet。CountSeconds(out hv_S2); // Match stop
hv_Runtime = (hv_S2 - hv_S1) * 1000;
usedTime = hv_Runtime;
ResultN = new HTuple(hv_Row。TupleLength());
// 獲取匹配結果個數
if (ResultN >1)
{
Px = new double[ResultN];
Py = new double[ResultN];
hv_R = new HTuple(); //HTuple變數初始化
for (i = 0; i < ResultN; i++)
{
Py[i] = hv_Row[i]; // 將搜尋結果的中心座標讀出
Px[i] = hv_Column[i];
Aa[i] = hv_Angle[i];
hv_R[i] = 8; //設定圓半徑
}
for (i = 0; i < ResultN; i++) // 從大到小排序
{
for (j = i + 1; j < ResultN; j++)
{
if (Px[i] < Px[j])
{
temp = Px[i];
Px[i] = Px[j];
Px[j] = temp;
temp = Py[i];
Py[i] = Py[j];
Py[j] = temp;
temp = Aa[i];
Aa[i] = Aa[j];
Aa[j] = temp;
}
}
}
HOperatorSet。SetColor(hv_ExpDefaultWinHandle,“red”); // 顯示匹配結果位置
HOperatorSet。DispCircle(hv_ExpDefaultWinHandle,hv_Row, hv_Column, hv_R);
HOperatorSet。ClearShapeModel(hv_ModelID);
Cy = Py[0]+ 10; // offset的初始值
Cx = Px[0]+ 60;
}
else
{
MessageBox。Show(“匹配失敗!?”);
}
}
public void DisplayOffset() // 顯示偏移點位置
{
HOperatorSet。SetColor(hv_ExpDefaultWinHandle, “red”);
HOperatorSet。DispObj(ho_Image,hv_ExpDefaultWinHandle);
HOperatorSet。DispCircle(hv_ExpDefaultWinHandle,hv_Row, hv_Column, hv_R);
HOperatorSet。DispLine(hv_ExpDefaultWinHandle,hv_Row[0], hv_Column[0], hv_Row[1], hv_Column[1]);
HOperatorSet。SetColor(hv_ExpDefaultWinHandle,“yellow”);
HOperatorSet。DispCircle(hv_ExpDefaultWinHandle,Cy, Cx, Cr);
}
public void DisplayLine() //顯示計算角度,劃線
{
HOperatorSet。SetColor(hv_ExpDefaultWinHandle, “yellow”);
HOperatorSet。DispLine(hv_ExpDefaultWinHandle, Cy, Cx, Fy, Fx);
}
}
static class Program
{
static void Main()
{
Application。EnableVisualStyles();
Application。SetCompatibleTextRenderingDefault(false);
Application。Run(new Form1());
}
}
}
2)設計窗體及程式碼
設計窗體如圖33所示。Form1。cs程式程式碼如下:
using System;
using System。Collections。Generic;
using System。ComponentModel;
using System。Data;
using System。Drawing;
using System。Linq;
using System。Text;
using System。Windows。Forms;
namespace MatchMyTemplate
{
public partial class Form1 : Form
{
HDevelopExport hd = new HDevelopExport();
public double Lam,
theta;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e) // 開啟相機
{
hd。InitCamera(hWindowControl1。HalconWindow); // 攝像頭初始化
timer1。Enabled = true; // 開始定時拍照顯示
textBox1。Text = “”;
textBox1。Refresh();
}
private void timer1_Tick(object sender, EventArgs e) // 定時拍照、顯示
{
hd。GrabAndDisplay();
}
private void button3_Click(object sender, EventArgs e) // 關閉相機
{
imer1。Enabled = false;
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e) // 關閉窗體
{
timer1。Enabled = false; // 停止拍照
hd。CloseCamera();
}
private void button2_Click(object sender, EventArgs e) // 影象匹配、計算2點距離
{
int j, m;
double d;
double[] x;
double[] y;
timer1。Enabled = false; // 停止拍照
hd。MatchTemplate(); // 影象匹配
button2。Visible = false;
m = hd。ResultN;
x = new double[m];
y = new double[m];
for (j = 0; j < m; j++) // 獲取匹配座標
{
x[j] = hd。Px[j];
y[j] = hd。Py[j];
}
textBox1。Text = “匹配時間:” + hd。usedTime。ToString(“#####。##”)
+ “ ms” + “\r\n” + “\r\n”;
for (j = 0; j < m; j++)
{
if (j < m - 1) // 計算2點距離
{
d = (y[j + 1] - y[j]) *(y[j + 1] - y[j]) + (x[j + 1] - x[j]) * (x[j + 1] - x[j]);
d = Math。Sqrt(d);
d = 0。0485 * d;
textBox1。Text =textBox1。Text + “d(” + Convert。ToString(j + 1) + “)
= ” + d。ToString(“#####。##”)+ “mm” + “\r\n”;
}
textBox1。Text = textBox1。Text +“x(”+ Convert。ToString(j + 1) + “) = ” + Convert。ToString(x[j])+“ y(” + Convert。ToString(j+ 1) + “) = ” + Convert。ToString(y[j]) + “\r\n”+ “\r\n”;
}
}
private void button5_Click(object sender, EventArgs e) // 讀模板
{
hd。ReadTemplate();
button2。Visible = true;
}
private void button4_Click(object sender, EventArgs e) // 設定模板
{
timer1。Enabled = false; // 停止拍照
hd。SetTemplate();
}
private void Form1_Load(object sender, EventArgs e) // 載入窗體
{
button2。Visible = false;
}
private void button7_Click(object sender, EventArgs e) // 計算偏移點位置
{
double a, b, c, d, m, n, k1, k2;
a = hd。Px[0];
b = hd。Py[0];
c = hd。Px[1];
d = hd。Py[1];
m = hd。Cx;
n = hd。Cy;
k1 = (b - n) / (a - m);
k2 = (d - b) / (c - a);
Lam = Math。Sqrt((b - n) * (b - n) +(a - m) * (a - m));
theta = Math。Atan((k1 - k2) / (1 + k1* k2));
}
private void button8_Click(object sender, EventArgs e) // 偏移點上移
{
hd。Cy = hd。Cy - 1;
hd。DisplayOffset();
}
private void button9_Click(object sender, EventArgs e) // 偏移點下移
{
hd。Cy = hd。Cy + 1;
hd。DisplayOffset();
}
private void button10_Click(object sender, EventArgs e) // 偏移點左移
{
hd。Cx = hd。Cx - 1;
hd。DisplayOffset();
}
private void button11_Click(object sender, EventArgs e) // 偏移點右移
{
hd。Cx = hd。Cx + 1;
hd。DisplayOffset();
}
private void button6_Click(object sender, EventArgs e) // 計算、顯示插針位置和夾角
{
double a, b, c, d, m, n;
double alpha, beta;
a = hd。Px[0];
b = hd。Py[0];
c = hd。Px[1];
d = hd。Py[1];
alpha = Math。Atan((d - b) / (c - a));
beta = theta + alpha ;
m = a + Lam * Math。Cos(beta);
n = b + Lam * Math。Sin(beta);
textBox1。Text = textBox1。Text + “ m = ” + Convert。ToString(m) + “\r\n” + “
n = ” + Convert。ToString(n) + “\r\n” + “alpha = ” + Convert。ToString(alpha)
+ “\r\n” + “ theta = ” + Convert。ToString(theta)+ “\r\n” + “ beta = ” + Convert。ToString(beta);
hd。Cx = m;
hd。Cy = n;
hd。Fx = m-600*Math。Cos(alpha); // 計算夾角斜線的終點座標
hd。Fy = n-600*Math。Sin(alpha);
hd。DisplayOffset(); // 顯示插針位置
hd。DisplayLine(); // 顯示工件夾角
}
}
}
1。 實驗結果
如圖33所示,首先點選“Camera On”鍵,開啟相機,再點選“Set template”鍵,設定模板,模板影象如圖34所示。
圖33 Form外觀及模板設定 圖34 模板影象
如果已經設定好模板,則點選“Load template”鍵,讀取模板影象。然後,點選“Match”鍵開始匹配。
點選“adjust offset”區域中的四個按鍵,可以調整偏移點,如圖35中的黃色圓點所示。將黃色圓點移動至最右邊的插針位置上。點選“Set offset”計算偏移點的引數q、L。參見公式(1)、(2)。
圖35 插針位置檢測結果
之後,開始檢查插針位置及夾角。
關閉相機,然後再開啟相機。將模組任意擺放,但右下角最右側的4個插針必須在紅色框內,如圖33所示。
點選“Load template”鍵,再點選“Match”鍵,再點選“Display target”鍵,檢測結果顯示如圖35所示。
圖35中3個紅色點是3個匹配結果的中心點。匹配時間為13毫秒左右。
經標定,1個畫素為0。0485毫米。2個插針之間的公稱距離為5毫米。檢測資料表明:位置誤差小於1個畫素。最右側插針位置由黃點標註,其座標是由公式(4)計算而得。實驗表明檢測精度令人滿意。
由於顯示影象的窗體座標y軸方向朝下,匹配結果的轉角a方向定義與一般直角座標相反。所以轉角a為正時,計算得出的夾角alpha為負。夾角alpha是由公式(3)計算得出,和匹配函式給出的旋轉角a(1)~a(3)有一定的偏差。但圖35中的黃色線是根據alpha計算畫出,顯然,其精度更高。
五.小結
HALCON軟體功能十分強大,使用比較簡單;但沒有中文說明書,讀英語文件較費時間。由HALCON程式匯出的C#程式碼,不能直接使用,只能參考。希望本文能對想學習、使用機器視覺位置檢測的工程師有些幫助。
六.參考文獻
[1] MVTec Software GmbH, HALCON Solution Guide II-B。 München,Germany, 2010。
[2] 李衛平,左力。 運動控制系統原理與應用。 武漢:華中科技大學出版社,2013。
[3] 孫國棟,趙大興。機器視覺檢測理論與演算法。 北京:科學出版社,2015。
[4] 孫正。 數字影象處理與識別。 北京:機械工業出版社,2016。