
簡介面向C#開發者和機器視覺入門者的一套C#與HALCON視覺檢測項目案例圍繞位置設定界面與顯示操作演示如何借助HALCON的.NET接口將圖像抓取、特征提取、模板匹配、幾何測量等能力集成到WPF桌面應用中。項目從相機連接與實時取像到控件布局、參數輸入、事件驅動流程、數據綁定、多線程界面刷新和異常調試基本覆蓋了視覺檢測操作界面的完整開發鏈路。壓縮包共73個文件包含cs源碼、bmp圖像資源、resx與resources資源配置、xml與dat數據文件、exe和dll可執行及運行庫、sln工程與配置文件整體約3.92MB目錄結構清晰便于按模塊對照研讀。已有546人學習下載尤其適合正在構建視覺定位/顯示界面、希望上手C#與HALCON聯動開發的讀者。從界面設計到HALCON算子調用均有源碼級參考可幫助理解高級編程語言與專業視覺庫在實際項目中的結合方式。1. C# 與 Halcon 的視覺檢測項目位置設定界面和顯示操作要先于算法落地車間里換產品型號時操作工每天按得最多的不是算法參數頁而是位置設定界面。視覺檢測上位機項目里算法能不能檢出來要跑完整個流程才知道但操作工換產品時拉一拉檢測框、對準一下基準點、看一眼圖像上疊加的 ROI 和結果這個動作每調一次就要發生一次。標題里的兩個詞落到工程上就是用 C# 把 Halcon 窗口嵌進上位機給人提供“在圖像上示教位置、存配方、實時看結果”的能力。位置設定界面解決的是“換產品怎么快速改”矩形 ROI 拉一拉、基準十字叉移一移、世界坐標自動換算、配方一鍵保存。顯示操作解決的是“改了怎么看得見”圖像縮放平移、檢測結果疊加、OK/NG 反饋讓窗口里的畫面和產線現場對得上。兩者合在一起是 C# 上位機視覺項目的骨架后面的標定、手眼映射、halcon 測量、缺陷檢測全都掛在這套骨架上跑。這篇按做視覺集成最常見的落地路徑來寫適合正在用 C# winform/wpf 搭上位機、準備把 Halcon 集成進來的開發也適合要設計示教流程的調試工程師。2. 位置設定的數據模型Halcon 像素坐標、世界坐標與配方序列化2.1 Halcon 的 (Row, Col) 坐標系和界面上習慣的 (X, Y) 差在哪接觸 Halcon 的 C# 開發幾乎都會在同一個地方栽跟頭Halcon 圖像坐標系里第一個坐標是 Row行從上往下第二個是 Column列從左往右原點在左上角。而界面和機器人領域習慣的是 X 向右、Y 向上的笛卡爾坐標。把 Halcon 返回的 row 當成 Y、col 當成 X方向是反的想恢復成日常習慣還要再把 Y 取負。位置設定界面如果在這個換算上出錯后續標定和手眼映射會全部跟著偏。另外要區分“控件坐標”和“圖像坐標”。HSmartWindowControl 的鼠標事件給的是控件像素位置一旦圖像被縮放或平移過兩個坐標系就不是等比例關系。顯示操作里任何“鼠標點哪里圖像對應哪里”的需求都要先走一次坐標系換算// 把控件上的鼠標位置換算成當前圖像上的 (row, col) HOperatorSet.ConvertCoordinates( hSmartWindowControl.HalconWindow, new HTuple(e.Y), new HTuple(e.X), window, out HTuple imgRow, out HTuple imgCol, image); // 狀態欄給操作員看的坐標轉成笛卡爾習慣 // X 取 colY 取 row注意 Halcon 里 Y 軸向下 string tip $X {imgCol.D:F1}, Y {imgRow.D:F1};這段代碼的邏輯ConvertCoordinates 接受窗口坐標from 寫 window、to 寫 image算子內部會結合當前 ImagePart 完成縮放和平移的反算輸入順序永遠先 row 后 col。參數里 e.Y、e.X 是 HMouseEventArgs 的控件坐標window 和 image 是最常用的一組坐標系枚舉也可以換成 display 看顯示緩沖區的坐標。換算結果和鼠標位置差一截時優先檢查 ImagePart 是否還是舊尺寸。2.2 一個“位置”記錄應該存哪些字段位置設定界面里的一條記錄絕不是一對坐標這么簡單。以平面定位加區域檢測的常見場景為例一條位置要同時回答三個問題檢測目標在哪塊區域、基準點在哪、機械手要去哪。因此數據結構按三組職責設計直接對照采集、標定、顯示三個階段的數據流public class VisionPosition { public string Name { get; set; } // 位置名如 Pos_A、吸嘴1 public string RecipeName { get; set; } // 所屬配方/產品型號 // 圖像坐標Halcon 習慣 row 在前 public double Row { get; set; } public double Column { get; set; } // 世界坐標平面標定后的物理坐標單位 mm public double WorldX { get; set; } public double WorldY { get; set; } public double Angle { get; set; } // 弧度系統內統一用弧度 // ROI 幾何矩形用兩角點圓用中心半徑 public double RoiRow1 { get; set; } public double RoiCol1 { get; set; } public double RoiRow2 { get; set; } public double RoiCol2 { get; set; } public double RoiRadius { get; set; } public bool Enabled { get; set; } true; }字段拆開的理由是圖像坐標負責“算法在哪找”世界坐標負責“機器往哪動”ROI 負責“界面上畫什么形狀”。三者經常聯動變化例如產品整體轉了 30 度ROI 跟著轉但基準點相對治具的世界坐標不變把三類字段放在同一個類里后續做表格綁定、配方切換、顯示高亮都會順手很多。2.2.1 為什么顯示層還要一份 Rectangle2顯示操作里常用 DispRectangle2 畫帶角度的框它接收的是中心行、中心列、角度、半高、半寬和 RoiRow1 這種兩角點表達不一樣。工程上的常見做法是存儲一律用兩角點示教最直觀渲染時用中心點和半高半寬重新生成 Rectangle2 參數。兩套表達之間換算很簡單但必須在數據邊界上統一成一種存儲格式避免界面上同時存在“存的是一套、畫的是另一套”的情況。2.3 配方文件的序列化方案JSON 為主Halcon 對象單獨落盤位置參數就是產品配方的核心。保存格式的選擇直接影響產線換型效率下面是三種常見方案的對比方案可讀性版本兼容中文支持適用場景JSON好可直接手改好缺字段給默認值好UTF-8推薦主方案winform/wpf 直接解析XML中標簽多好好客戶或 MES 指定接口Halcon 的 .tup / .mat差全是數值一般差只放標定矩陣、模板句柄推薦組合是配方主體用 JSONHalcon 特有的數據HomMat2D 標定矩陣、測量模板單獨導成 .mat 或 .tup 文件配方里只存路徑。HRegion、HImage、HMeasure 這類 Halcon 對象不能直接 JSON 序列化邊界上要轉成幾何數值或 HTuple這正是上一節把 VisionPosition 設計成純數值類的原因。// 配方保存把位置列表整份寫成 UTF-8 的 JSON public static void SavePositions(string path, ListVisionPosition list) { var opt new JsonSerializerOptions { WriteIndented true }; string json JsonSerializer.Serialize(list, opt); File.WriteAllText(path, json, Encoding.UTF8); } // 配方加載文件不存在或解析失敗都返回空列表 public static ListVisionPosition LoadPositions(string path) { if (!File.Exists(path)) return new ListVisionPosition(); try { string json File.ReadAllText(path, Encoding.UTF8); return JsonSerializer.DeserializeListVisionPosition(json) ?? new ListVisionPosition(); } catch (JsonException) { return new ListVisionPosition(); } }這兩段代碼的邏輯WriteIndented 讓配方文件可以直接交給非技術同事肉眼比對顯式傳 Encoding.UTF8 避免 Windows 默認代碼頁把中文位置名讀成亂碼反序列化失敗返回空列表而不是往上拋異常是“界面不能因為一個壞配方就起不來”的防御習慣。參數說明JsonSerializerOptions 來自 System.Text.JsonWriteIndented 控制是否格式化縮進catch JsonException 只接解析錯誤File.Exists 擋掉文件缺失分支。3. 在 C# 工程里集成 Halcon 窗口從 HSmartWindowControl 到顯示操作3.1 控件選型HWindowControl 和 HSmartWindowControl 怎么選Halcon 的 .NET 接口里有兩個常用窗口控件。HWindowControl 是老牌的 WinForms 控件縮放、平移、重繪全部要自己接管HSmartWindowControl 是面向交互場景設計的控件內置滾輪縮放、左鍵平移和自適應顯示還提供 HMouseDown、HMouseMove、HMouseWheel 這一整套事件。WPF 工程對應使用 HSmartWindowControlWPF事件模型和 WinForms 版保持一致。能力HWindowControlHSmartWindowControl滾輪縮放手寫內置圖像平移手寫內置鼠標事件有需自換算有坐標換算配套繪圖調用Draw* 阻塞式Draw* 阻塞式可配合事件自繪顯示自適應手寫 SetPartSetFullImagePart 一步到位新項目直接選 HSmartWindowControl只有老項目遷移才需要繼續維護 HWindowControl。理由很直接視覺檢測調試時放大看缺陷邊緣、平移看 ROI 與工件相對位置是高頻操作滾輪以鼠標為中心縮放自己寫有一定的 ImagePart 換算量不值得重復造。3.2 最小可運行加載圖片并用 HSmartWindowControl 顯示把 halcondotnet.dll 加到工程引用、工具箱里拖入 HSmartWindowControl 之后加載一張本地圖片顯示出來的代碼只需四行private void btnLoadImage_Click(object sender, EventArgs e) { if (openFileDialog1.ShowDialog() ! DialogResult.OK) return; _currentImage?.Dispose(); _currentImage new HImage(openFileDialog1.FileName); // 讀入圖像 // 把 ImagePart 設為整幅圖窗口按控件尺寸自適應縮放 hSmartWindowControl.SetFullImagePart(_currentImage); hSmartWindowControl.HalconWindow.DispObj(_currentImage); RedrawOverlays(); // 把已有的 ROI、十字線重新疊加上去 }SetFullImagePart 的作用是讓窗口一次性看到整幅圖像隨后滾輪縮放、拖拽平移都以這幅圖像為基準DispObj 把 HImage 內部持有的 HObject 畫到窗口上。如果是相機取流把這四行里的 new HImage 換成相機 Grab 返回值即可。老版本 Halcon 沒有 SetFullImagePart 時等價寫法是手動 SetPart(0, 0, height-1, width-1)。3.2.1 ImagePart 是什么ImagePart 是 Halcon 窗口當前可見的圖像區域本質是四個邊界值 (row1, col1, row2, col2)。縮放和平移改變的就是這個區域顯示操作里的坐標換算也以它為基準。HSmartWindowControl 把這塊封裝掉了但理解 ImagePart 仍然重要凡是“圖像上畫框、鼠標點擊命中、窗口里截圖”這三類需求最終都要回到 ImagePart 的數值上。3.3 顯示操作里高頻出現的設置項顏色、線寬、十字線、文字位置設定界面上除了底圖還要畫 ROI、中心點、匹配框、OK/NG 文字。這些東西全部走同一套模式先用設置算子定義樣式再調用 Disp 系列算子畫上去HWindow hWin hSmartWindowControl.HalconWindow; // 畫一個矩形 ROI綠色邊框、線寬 2、只描邊不填充 hWin.SetColor(green); hWin.SetLineWidth(2); hWin.SetDraw(margin); // margin 描邊fill 填充 hWin.DispRectangle2(pos.Row, pos.Column, pos.Angle, 60, 40); // 在基準位置畫十字線 hWin.SetColor(red); hWin.SetLineWidth(1); hWin.DispCross(pos.Row, pos.Column, 25, pos.Angle);DispRectangle2 的參數順序是中心行、中心列、角度、半高、半寬角度為 0 時退化為普通矩形DispCross 的第 4 個參數是十字線偏角與位置角度保持一致畫出來的十字才跟著產品方向轉。這里的 pos 就是第 2 章 VisionPosition 的一條記錄。執行順序上必須先 DispObj 底圖、再畫疊加內容后畫的圖形始終蓋在圖像上方位置設定界面才能保證 ROI 永遠清晰可見。中文文字是顯示操作里最典型的坑。Halcon 自帶的默認字體不包含中文字形直接用 DispText 輸出“檢測結果: OK”會變成方框。常見做法是用 SetTfont 把字體指到系統已安裝的中文字體XLFD 是固定 14 段的字體描述串// 讓 DispText 支持中文指定系統中文字體字段用 * 通配 hWin.SetTfont(-*-Microsoft YaHei-Medium-R-Normal--14-*-*-*-*-*-*-*); hWin.DispText(檢測結果: OK, window, 15, 15, black, lime, box);提示DispText 的坐標系統參數有兩檔window 是相對窗口定位縮放時文字不動image 是相對圖像定位文字跟隨圖像移動。位置設定界面的結果文字一般用 window。如果 SetTfont 的 XLFD 串在你們版本上一直報 invalid font最省事的替代方案是Halcon 窗口內只顯示 OK、NG、數值這類 ASCII 內容中文標題放到窗體自身的 Label 或狀態欄里。很多量產項目的界面就是這么分的。3.4 窗體 Resize 后的重繪順序HSmartWindowControl 在窗體尺寸變化時會自動調整顯示區域但窗口緩沖區的重建會把上次畫上去的疊加圖形沖掉表現為“縮放窗口后 ROI 不見了”。處理方法是在 SizeChanged 里按固定順序重畫private void hSmartWindowControl_SizeChanged(object sender, EventArgs e) { if (_currentImage null) return; hSmartWindowControl.HalconWindow.DispObj(_currentImage); // 先底圖 RedrawOverlays(); // 后疊加 }順序和 3.3 里說的完全一致。RedrawOverlays 內部遍歷位置列表逐個畫 ROI這個函數在位置列表增刪、選中項切換、檢測完成三個時機都要調用是整個顯示操作里復用率最高的入口值得單獨抽出來維護。4. 位置設定界面的交互實現畫 ROI、位置列表聯動與測量喂參4.1 畫 ROI 的兩種實現DrawRectangle1 阻塞式與鼠標事件自繪示教 ROI 是位置設定界面的核心動作。Halcon 提供了 Draw 系列算子DrawRectangle1、DrawCircle、DrawPoint 都能在窗口上交互取數也有基于鼠標事件的自繪方案。兩者對比如下方式代碼量實時預覽與縮放兼容適用場景DrawRectangle1 阻塞式少無窗口自動處理點按鈕后拉一個框的簡單示教鼠標事件自繪多可做需自己換坐標拖動過程要看測量值/尺寸阻塞式是入門最快的寫法鼠標十字光標由 Halcon 窗口自己處理用戶拖完矩形松手算子才返回private void btnTeachRoi_Click(object sender, EventArgs e) { HWindow hWin hSmartWindowControl.HalconWindow; HTuple r1, c1, r2, c2; // 在當前顯示的圖像上拖一個矩形松手后返回兩角點 hWin.DrawRectangle1(out r1, out c1, out r2, out c2); var pos _positions[_currentIndex]; pos.RoiRow1 r1.D; pos.RoiCol1 c1.D; pos.RoiRow2 r2.D; pos.RoiCol2 c2.D; RedrawOverlays(); }注意DrawRectangle1 這類 Draw 算子是阻塞調用期間窗體其他控件不可用。不要把它放到采集線程里執行否則窗口停在等待鼠標狀態取流直接卡死。缺點是繪制期間窗體其他按鈕全部不可用也沒法在拖動過程中實時看到測量結果。需要實時反饋的產品用鼠標事件自繪private bool _drawing; private double _sRow, _sCol; // 拖動起點圖像坐標 private double _eRow, _eCol; // 拖動終點圖像坐標 private void hSmart_HMouseDown(object sender, HMouseEventArgs e) { if (_editMode ! EditMode.DrawRoi) return; _drawing true; // 窗口坐標必須換成圖像坐標否則縮放后畫歪 ConvertWindowToImage(e.X, e.Y, out _sRow, out _sCol); _eRow _sRow; _eCol _sCol; } private void hSmart_HMouseMove(object sender, HMouseEventArgs e) { if (!_drawing) return; HWindow hWin hSmartWindowControl.HalconWindow; // 第一步用白色粗線把上一次的框“擦掉” hWin.SetColor(white); hWin.SetLineWidth(5); hWin.DispRectangle1(_sRow, _sCol, _eRow, _eCol); // 第二步按最新鼠標位置畫紅色新框 ConvertWindowToImage(e.X, e.Y, out _eRow, out _eCol); hWin.SetColor(red); hWin.SetLineWidth(2); hWin.DispRectangle1(_sRow, _sCol, _eRow, _eCol); } private void ConvertWindowToImage(int winX, int winY, out double imgRow, out double imgCol) { // Halcon 窗口坐標行 Y列 X HOperatorSet.ConvertCoordinates(hSmartWindowControl.HalconWindow, new HTuple(winY), new HTuple(winX), window, out HTuple ir, out HTuple ic, image); imgRow ir.D; imgCol ic.D; }自繪方案的邏輯是“先擦后畫”Halcon 窗口沒有 XOR 模式用白色粗線重畫一次舊矩形把它蓋掉再畫新矩形鼠標移動時視覺上就是跟手拖拽。參數說明_editMode 是自定義枚舉用來區分當前是窗口平移模式還是畫框模式避免和 HSmartWindowControl 自帶的左鍵平移手勢沖突進入畫框模式時把平移禁止掉退出時恢復這是工程上的常見約定。4.2 位置列表和圖像聯動選中一條記錄就高亮對應框光能畫框還不夠位置設定界面要有“位置清單”。用 DataGridView 綁定位置列表選中行變化時觸發重繪把選中位置的框變成高亮紅色其余位置保持綠色private void dgvPositions_SelectionChanged(object sender, EventArgs e) { if (dgvPositions.CurrentRow?.DataBoundItem is not VisionPosition sel) return; RedrawOverlays(sel); } private void RedrawOverlays(VisionPosition highlight null) { HWindow hWin hSmartWindowControl.HalconWindow; if (_currentImage ! null) hWin.DispObj(_currentImage); // 先重畫底圖 foreach (var p in _positions) { bool isSel (p highlight); hWin.SetColor(isSel ? red : green); // 選中紅色高亮 hWin.SetLineWidth(isSel ? 3 : 1); hWin.DispRectangle2(p.Row, p.Column, p.Angle, 60, 40); hWin.DispCross(p.Row, p.Column, 25, p.Angle); } }這段代碼的邏輯SelectionChanged 里先從 CurrentRow 的 DataBoundItem 解出位置對象解不出就返回重繪函數每次都先刷底圖再遍歷全部位置選中項用紅色粗線、其他用綠色細線。注意 RedrawOverlays 是 3.4 里說的那個重繪入口這里的 highlight 參數讓同一函數同時服務列表聯動和檢測完成刷新。4.2.1 數據源用 BindingList 而不是 ListDataGridView 的 DataSource 直接綁 List 時新增、刪除、修改字段后表格不會自動刷新還要手動 ResetBindings。常見做法是綁 BindingListVisionPosition集合變化會自動通知表格刷新而 VisionPosition 里的屬性通知則用 INotifyPropertyChanged 實現。位置設定界面里的“新增一條、改個角度、刪一條”這些操作都建立在可感知的集合上。4.3 位置設定之后的下游把坐標喂給測量算子和缺陷檢測位置設定不是終點設定好的坐標要喂給 Halcon 的檢測算子。最典型的是把位置的角度和范圍交給一維測量工具測邊緣對的距離這也是 c# 上位機視覺項目里“位置”最有價值的用法// 用位置記錄里的中心點和角度創建測量對象 HMeasure mm new HMeasure(pos.Row, pos.Column, pos.Angle, 1.0, 30, all, all); HTuple rowEdge, colEdge, amp, dist; mm.MeasurePos(_currentImage, 1.0, true, all, all, out rowEdge, out colEdge, out amp, out dist); // 在界面上把測量到的邊緣點用十字畫出來操作員肉眼核對 HWindow hWin hSmartWindowControl.HalconWindow; for (int i 0; i rowEdge.Length; i) { hWin.SetColor(orange); hWin.DispCross(rowEdge.DArr[i], colEdge.DArr[i], 15, pos.Angle); }HMeasure 構造參數依次是中心行、中心列、角度、平滑系數、邊緣閾值、邊緣選取方式和過渡方向測量矩形沿 pos.Angle 方向張開。MeasurePos 返回的 rowEdge、colEdge 是找到的邊緣點amp 是邊緣幅度dist 是邊緣對距離把邊緣點以十字線形式畫回圖像是顯示操作里最直觀的驗證手段。同理缺陷檢測里把 ROI 區域傳給 Blob 分析或深度學習分類器之前區域的位置和角度同樣來自這里。這套“示教位置 - 存配方 - 啟動時載入 - 喂給算子 - 結果顯示”的閉環就是位置設定界面的完整職責。5. 循環數據采集下 UI 刷新卡頓排查與 Halcon 窗口內容自檢5.1 循環數據采集下 UI 刷新卡頓限流 BeginInvoke 組合相機連續取流時在采集線程里直接調 DispObj 是界面卡頓的最常見原因。Halcon 窗口只能在 UI 線程繪制跨線程調用輕則掉幀、重則報錯而每次都 BeginInvoke 不做限流高頻取流會把 UI 消息隊列塞滿表現為鼠標拖不動、數據表格假死。標準做法是采集線程只管取幀UI 刷新單獨限流private readonly Stopwatch _uiClock new Stopwatch(); private bool _running; private HImage _latest; private void AcquisitionLoop() { _uiClock.Restart(); while (_running) { _latest _camera.GrabImage(); // 采集線程取幀 // 界面刷新限流40ms 一幀約 25fps if (_uiClock.ElapsedMilliseconds 40) { _uiClock.Restart(); BeginInvoke((Action)(() DisplayFrame(_latest))); } // 檢測算法也放這個線程和 UI 隔離 } } private void DisplayFrame(HImage frame) { hSmartWindowControl.HalconWindow.DispObj(frame); RedrawOverlays(); }BeginInvoke 是異步投遞采集線程不會因為界面繪制慢而停頓限流讓界面刷新頻率固定不受采集幀率波動影響。40ms 這個值適合大多數產線節拍需要看高速運動的物體時改為 16ms再快就沒有意義了人眼在 25fps 以上基本感知不到提升。還卡就查 RedrawOverlays 是不是每次都在 DispObj 整幅底圖這一步是顯示開銷的大頭。5.2 把 Halcon 窗口內容導出 PNG遠程定位顯示問題現場反饋“界面顯示不對”時截圖往往比口頭描述可靠。Halcon 的 DumpWindowImage 能把窗口當前內容連疊加圖形一起導出成圖片這個能力在遠程調試工位尤其有用// 把 Halcon 窗口當前顯示內容含所有疊加導出為 PNG HOperatorSet.DumpWindowImage(out HImage dump, hSmartWindowControl.HalconWindow); dump.WriteImage(png, 0, D:\debug\window_snapshot.png); dump.Dispose();DumpWindowImage 導出的分辨率等于 ImagePart 對應的圖像尺寸而不是控件像素尺寸如果導出的圖片和現場肉眼看的不一樣先確認是不是 ImagePart 與控件尺寸不一致導致。把導出功能綁到快捷鍵或右鍵菜單上讓操作員就地生成現場畫面遠程調試能少一半來回確認。最后給一個低成本的自檢流程新位置設定界面做完可以照這個順序驗收加載一張帶明顯角點的測試圖新增三個不同角度的位置并逐個畫 ROI按第 2 章的格式保存配方重啟程序再加載確認所有框的位置、角度、十字線和高亮色都和保存前一致。這個流程同時覆蓋了坐標換算、序列化、重繪三條鏈路視覺項目里最容易出問題的三個環節一次驗完。本文還有配套的精品資源點擊獲取