2025-12-03 自制控件異步模版匹配

代碼優(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é)

  1. 資源安全:通過using和手動(dòng)釋放,避免Halcon對(duì)象內(nèi)存泄漏。
  2. 線程安全:所有UI操作通過Invoke切換到UI線程,防止跨線程異常。
  3. 異步效率:用await Task.Delay替代Task.Delay.Wait(),避免阻塞后臺(tái)線程。
  4. 可擴(kuò)展性:添加取消機(jī)制,支持中途終止匹配,提升用戶體驗(yàn)。
  5. 穩(wěn)定性:增加異常處理,捕獲文件操作和Halcon函數(shù)可能拋出的異常。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容