代碼優(yōu)化與知識(shí)點(diǎn)詳解
一、代碼優(yōu)化說明
原代碼實(shí)現(xiàn)了基于Halcon的模板匹配功能,包含圖片加載、模板創(chuàng)建、批量匹配等核心邏輯。以下從資源管理、線程安全、異步效率、異常處理四個(gè)維度進(jìn)行優(yōu)化,并保持功能完整性。
二、優(yōu)化后代碼(核心部分)
1. Form1.cs 優(yōu)化版
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace _123模板匹配
{
public partial class Form1 : Form
{
// 新增:取消令牌(支持中途終止匹配)
private CancellationTokenSource _cts;
private bool _isMatching = false;
private string _filterPath;
private readonly Stopwatch _totalStopwatch = new Stopwatch();
private DateTime _singleImageStartTime;
private string _singleImageCost = "0.0 ms";
private string _totalCost = "0.00 s";
private List<string> _imagePaths = new List<string>();
private readonly string[] _supportFormats = { ".jpg", ".png", ".bmp", ".tif", ".tiff", ".jpeg" };
// Halcon資源(實(shí)現(xiàn)IDisposable接口釋放資源)
private HImage _hoImg;
private HObject _hoRectangle;
private HTuple _modelID;
private HTuple _row, _column, _angle, _score;
public Form1()
{
InitializeComponent();
timerTimeDisplay.Tick += timerTimeDisplay_Tick;
// 初始化取消令牌
_cts = new CancellationTokenSource();
}
// 加載單張圖片(優(yōu)化:使用using釋放HImage資源)
private void button1_Click(object sender, EventArgs e)
{
HOperatorSet.ClearWindow(zdy_halconWindow.halconWd);
using (OpenFileDialog ofd = new OpenFileDialog())
{
ofd.Filter = "圖片文件|*.jpg;*.png;*.bmp;*.tif;*.tiff;*.jpeg";
ofd.RestoreDirectory = true;
if (ofd.ShowDialog() == DialogResult.OK)
{
// 使用using自動(dòng)釋放HImage資源
using (_hoImg = new HImage(ofd.FileName))
{
DisplayHObject();
}
}
}
}
// 繪制模板區(qū)域(優(yōu)化:明確釋放舊矩形資源)
private void button2_Click(object sender, EventArgs e)
{
// 釋放舊矩形資源
_hoRectangle?.Dispose();
HOperatorSet.SetDraw(zdy_halconWindow.halconWd, "margin");
HOperatorSet.SetColor(zdy_halconWindow.halconWd, "red");
zdy_halconWindow.Focus();
HOperatorSet.DrawRectangle1(zdy_halconWindow.halconWd,
out HTuple row1, out HTuple column1, out HTuple row2, out HTuple column2);
HOperatorSet.GenRectangle1(out _hoRectangle, row1, column1, row2, column2);
HOperatorSet.DispObj(_hoRectangle, zdy_halconWindow.halconWd);
}
// 創(chuàng)建模板(優(yōu)化:添加異常處理)
private void button3_Click(object sender, EventArgs e)
{
if (_hoImg == null || _hoRectangle == null)
{
MessageBox.Show("請(qǐng)先加載圖片并繪制模板區(qū)域!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
try
{
using (HObject hoModel = new HObject())
{
HOperatorSet.ReduceDomain(_hoImg, _hoRectangle, out hoModel);
HOperatorSet.CreateShapeModel(
hoModel, "auto", new HTuple(0).TupleRad(), new HTuple(360).TupleRad(),
"auto", "auto", "use_polarity", "auto", "auto", out _modelID);
HOperatorSet.WriteShapeModel(_modelID, "model.shm");
HOperatorSet.DispObj(hoModel, zdy_halconWindow.halconWd);
MessageBox.Show("模板創(chuàng)建成功!");
}
}
catch (HOperatorException ex)
{
MessageBox.Show($"模板創(chuàng)建失?。簕ex.Message}", "錯(cuò)誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
// 加載文件夾圖片(優(yōu)化:異步加載+路徑校驗(yàn))
private async void button4_Click(object sender, EventArgs e)
{
using (OpenFileDialog ofd = new OpenFileDialog())
{
ofd.ValidateNames = false;
ofd.CheckFileExists = false;
ofd.RestoreDirectory = true;
ofd.FileName = "選擇文件夾后點(diǎn)擊確定";
ofd.Filter = "文件夾|*.*";
ofd.Title = "請(qǐng)選擇待處理圖片的文件夾";
if (ofd.ShowDialog() == DialogResult.OK)
{
_filterPath = Path.GetDirectoryName(ofd.FileName);
if (!Directory.Exists(_filterPath))
{
MessageBox.Show("文件夾不存在!", "錯(cuò)誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// 異步加載圖片路徑(避免UI阻塞)
_imagePaths = await Task.Run(() =>
Directory.GetFiles(_filterPath)
.Where(path => _supportFormats.Contains(Path.GetExtension(path).ToLower()))
.OrderBy(Path.GetFileName)
.ToList()
);
MessageBox.Show($"已加載{_imagePaths.Count}張圖片!");
if (_imagePaths.Count == 0)
{
MessageBox.Show("文件夾中未找到支持的圖片文件!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
HOperatorSet.ReadShapeModel("model.shm", out _modelID);
}
}
}
}
// 批量匹配(核心優(yōu)化:異步+取消機(jī)制+資源釋放)
private async void button5_Click(object sender, EventArgs e)
{
if (_isMatching || _imagePaths.Count == 0 || _modelID == null)
{
MessageBox.Show("請(qǐng)先加載圖片和模板!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
// 重置取消令牌
_cts?.Dispose();
_cts = new CancellationTokenSource();
var token = _cts.Token;
_isMatching = true;
button5.Enabled = false;
_singleImageCost = "0.0 ms";
_totalStopwatch.Reset();
_totalStopwatch.Start();
timerTimeDisplay.Enabled = true;
try
{
// 異步執(zhí)行匹配(支持取消)
await Task.Run(async () =>
{
foreach (var imagePath in _imagePaths)
{
// 檢查是否取消
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
_singleImageStartTime = DateTime.Now;
// 自動(dòng)釋放HImage資源
using (var currentImg = new HImage(imagePath))
{
// 模板匹配
HOperatorSet.FindShapeModel(
currentImg, _modelID, new HTuple(0).TupleRad(), new HTuple(360).TupleRad(),
0.5, 1, 0.5, "least_squares", 0, 0.5,
out _row, out _column, out _angle, out _score);
// 獲取匹配輪廓并變換
using (HObject modelContours = new HObject(), modelContoursTrans = new HObject())
{
HOperatorSet.GetShapeModelContours(out modelContours, _modelID, 1);
HOperatorSet.VectorAngleToRigid(0, 0, 0, _row, _column, _angle, out HTuple homMat2D);
HOperatorSet.AffineTransContourXld(modelContours, out modelContoursTrans, homMat2D);
// 計(jì)算耗時(shí)并更新UI
var singleSpan = DateTime.Now - _singleImageStartTime;
_singleImageCost = $"{singleSpan.TotalMilliseconds:F1} ms";
// UI操作必須通過Invoke切換到UI線程
Invoke((Action)(() =>
{
HOperatorSet.DispObj(currentImg, zdy_halconWindow.halconWd);
HOperatorSet.DispObj(modelContoursTrans, zdy_halconWindow.halconWd);
}));
}
}
// 異步延遲(不阻塞線程)
await Task.Delay(500, token);
}
}, token);
}
catch (OperationCanceledException)
{
Invoke((Action)(() => MessageBox.Show("匹配已取消")));
}
catch (Exception ex)
{
Invoke((Action)(() => MessageBox.Show($"匹配失?。簕ex.Message}", "錯(cuò)誤", MessageBoxButtons.OK, MessageBoxIcon.Error)));
}
finally
{
// 無論成功/失敗,恢復(fù)狀態(tài)
_totalStopwatch.Stop();
timerTimeDisplay.Enabled = false;
_isMatching = false;
Invoke((Action)(() => button5.Enabled = true));
}
}
// 定時(shí)更新時(shí)間顯示(Timer控件)
private void timerTimeDisplay_Tick(object sender, EventArgs e)
{
_totalCost = $"{_totalStopwatch.Elapsed.TotalSeconds:F2} s";
lblTotalTime.Text = $"總匹配時(shí)間:{_totalCost}";
lblSingleTime.Text = $"單張耗時(shí):{_singleImageCost}";
}
// 釋放資源(避免內(nèi)存泄漏)
protected override void Dispose(bool disposing)
{
if (disposing)
{
components?.Dispose();
_hoImg?.Dispose();
_hoRectangle?.Dispose();
if (_modelID != null && _modelID.IsValid())
HOperatorSet.ClearShapeModel(_modelID);
_cts?.Dispose();
}
base.Dispose(disposing);
}
private void DisplayHObject()
{
HOperatorSet.GetImageSize(_hoImg, out HTuple width, out HTuple height);
HOperatorSet.SetPart(zdy_halconWindow.halconWd, 0, 0, height - 1, width - 1);
HOperatorSet.DispObj(_hoImg, zdy_halconWindow.halconWd);
}
// 其他自動(dòng)生成的事件(保持不變)
private void groupBox1_Enter(object sender, EventArgs e) { }
private void groupBox2_Enter(object sender, EventArgs e) { }
private void lblSingleTime_Click(object sender, EventArgs e) { }
private void Form1_Load(object sender, EventArgs e)
{
lblTotalTime.Text = $"總匹配時(shí)間:{_totalCost}";
lblSingleTime.Text = $"單張耗時(shí):{_singleImageCost}";
}
}
}
三、核心知識(shí)點(diǎn)詳解
1. 異步編程(async/await + Task.Run)
- 作用:將耗時(shí)操作(如模板匹配、文件加載)放入后臺(tái)線程執(zhí)行,避免UI線程阻塞(界面卡頓)。
-
關(guān)鍵代碼:
// 異步方法標(biāo)記 private async void button5_Click(object sender, EventArgs e) { // 耗時(shí)操作放入線程池 await Task.Run(async () => { // 循環(huán)匹配圖片 foreach (var imagePath in _imagePaths) { // 異步延遲(不阻塞線程) await Task.Delay(500, token); } }, token); } -
原理:
-
async:標(biāo)記方法為異步,允許內(nèi)部使用await。 -
await:暫停當(dāng)前方法執(zhí)行,等待異步操作完成后再繼續(xù),期間不阻塞UI線程。 -
Task.Run:將委托代碼放入線程池線程執(zhí)行(后臺(tái)線程),避免占用UI線程。
-
2. 多線程與UI交互(Control.Invoke)
- 問題:WinForm控件只能在創(chuàng)建它們的線程(通常是UI線程)中操作,后臺(tái)線程直接修改UI會(huì)拋出異常。
-
解決方案:使用
Control.Invoke將UI操作委托切換到UI線程執(zhí)行。// 后臺(tái)線程中更新UI Invoke((Action)(() => { HOperatorSet.DispObj(currentImg, zdy_halconWindow.halconWd); lblSingleTime.Text = $"單張耗時(shí):{_singleImageCost}"; })); -
原理:
Invoke會(huì)將委托傳遞給UI線程的消息隊(duì)列,由UI線程處理,保證線程安全。
3. 定時(shí)任務(wù)(System.Windows.Forms.Timer)
- 作用:定期執(zhí)行UI更新操作(如實(shí)時(shí)顯示匹配時(shí)間)。
-
關(guān)鍵代碼:
// 初始化Timer timerTimeDisplay.Interval = 10; // 10毫秒觸發(fā)一次 timerTimeDisplay.Tick += timerTimeDisplay_Tick; // Tick事件(在UI線程執(zhí)行) private void timerTimeDisplay_Tick(object sender, EventArgs e) { lblTotalTime.Text = $"總匹配時(shí)間:{_totalCost}"; // 安全更新UI } -
特點(diǎn):
-
Interval:觸發(fā)間隔(毫秒)。 -
Tick事件:在UI線程執(zhí)行,可直接操作控件(無需Invoke)。 - 適合輕量UI更新,不適合耗時(shí)操作。
-
4. 取消機(jī)制(CancellationTokenSource)
- 作用:允許用戶中途終止長時(shí)間運(yùn)行的異步任務(wù)(如批量匹配)。
-
關(guān)鍵代碼:
// 初始化取消令牌 private CancellationTokenSource _cts = new CancellationTokenSource(); // 在異步任務(wù)中檢查取消 await Task.Run(async () => { foreach (var imagePath in _imagePaths) { if (token.IsCancellationRequested) token.ThrowIfCancellationRequested(); // 拋出取消異常 // 匹配邏輯... } }, _cts.Token); -
使用場景:可添加“取消”按鈕,點(diǎn)擊時(shí)調(diào)用
_cts.Cancel()終止任務(wù)。
5. 資源管理(IDisposable與Halcon對(duì)象釋放)
-
問題:Halcon的
HImage、HObject等非托管資源若不釋放,會(huì)導(dǎo)致內(nèi)存泄漏。 -
解決方案:
- 使用
using語句自動(dòng)釋放實(shí)現(xiàn)IDisposable的對(duì)象。 - 手動(dòng)釋放Halcon特殊資源(如
ClearShapeModel)。
// 自動(dòng)釋放HImage using (var currentImg = new HImage(imagePath)) { // 使用currentImg... } // 釋放模板資源 if (_modelID != null && _modelID.IsValid()) HOperatorSet.ClearShapeModel(_modelID); - 使用
四、優(yōu)化總結(jié)
-
資源安全:通過
using和手動(dòng)釋放,避免Halcon對(duì)象內(nèi)存泄漏。 -
線程安全:所有UI操作通過
Invoke切換到UI線程,防止跨線程異常。 -
異步效率:用
await Task.Delay替代Task.Delay.Wait(),避免阻塞后臺(tái)線程。 - 可擴(kuò)展性:添加取消機(jī)制,支持中途終止匹配,提升用戶體驗(yàn)。
- 穩(wěn)定性:增加異常處理,捕獲文件操作和Halcon函數(shù)可能拋出的異常。