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

代码优化与知识点详解

一、代码优化说明

原代码实现了基于Halcon的模板匹配功能,包含图片加载、模板创建、批量匹配等核心逻辑。以下从资源管理、线程安全、异步效率、异常处理四个维度进行优化,并保持功能完整性。

二、优化后代码(核心部分)

1. Form1.cs 优化版

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资源(实现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();
        }

        // 加载单张图片(优化:使用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自动释放HImage资源
                    using (_hoImg = new HImage(ofd.FileName))
                    {
                        DisplayHObject();
                    }
                }
            }
        }

        // 绘制模板区域(优化:明确释放旧矩形资源)
        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);
        }

        // 创建模板(优化:添加异常处理)
        private void button3_Click(object sender, EventArgs e)
        {
            if (_hoImg == null || _hoRectangle == null)
            {
                MessageBox.Show("请先加载图片并绘制模板区域!", "提示", 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("模板创建成功!");
                }
            }
            catch (HOperatorException ex)
            {
                MessageBox.Show($"模板创建失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        // 加载文件夹图片(优化:异步加载+路径校验)
        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 = "选择文件夹后点击确定";
                ofd.Filter = "文件夹|*.*";
                ofd.Title = "请选择待处理图片的文件夹";

                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    _filterPath = Path.GetDirectoryName(ofd.FileName);
                    if (!Directory.Exists(_filterPath))
                    {
                        MessageBox.Show("文件夹不存在!", "错误", 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);
                    }
                }
            }
        }

        // 批量匹配(核心优化:异步+取消机制+资源释放)
        private async void button5_Click(object sender, EventArgs e)
        {
            if (_isMatching || _imagePaths.Count == 0 || _modelID == null)
            {
                MessageBox.Show("请先加载图片和模板!", "提示", 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
            {
                // 异步执行匹配(支持取消)
                await Task.Run(async () =>
                {
                    foreach (var imagePath in _imagePaths)
                    {
                        // 检查是否取消
                        if (token.IsCancellationRequested)
                        {
                            token.ThrowIfCancellationRequested();
                        }

                        _singleImageStartTime = DateTime.Now;
                        // 自动释放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);

                                // 计算耗时并更新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}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error)));
            }
            finally
            {
                // 无论成功/失败,恢复状态
                _totalStopwatch.Stop();
                timerTimeDisplay.Enabled = false;
                _isMatching = false;
                Invoke((Action)(() => button5.Enabled = true));
            }
        }

        // 定时更新时间显示(Timer控件)
        private void timerTimeDisplay_Tick(object sender, EventArgs e)
        {
            _totalCost = $"{_totalStopwatch.Elapsed.TotalSeconds:F2} s";
            lblTotalTime.Text = $"总匹配时间:{_totalCost}";
            lblSingleTime.Text = $"单张耗时:{_singleImageCost}";
        }

        // 释放资源(避免内存泄漏)
        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);
        }

        // 其他自动生成的事件(保持不变)
        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 = $"总匹配时间:{_totalCost}";
            lblSingleTime.Text = $"单张耗时:{_singleImageCost}";
        }
    }
}

三、核心知识点详解

1. 异步编程(async/await + Task.Run)

  • 作用:将耗时操作(如模板匹配、文件加载)放入后台线程执行,避免UI线程阻塞(界面卡顿)。
  • 关键代码
    // 异步方法标记
    private async void button5_Click(object sender, EventArgs e)
    {
        // 耗时操作放入线程池
        await Task.Run(async () => 
        {
            // 循环匹配图片
            foreach (var imagePath in _imagePaths)
            {
                // 异步延迟(不阻塞线程)
                await Task.Delay(500, token);
            }
        }, token);
    }
    
  • 原理
    • async:标记方法为异步,允许内部使用await
    • await:暂停当前方法执行,等待异步操作完成后再继续,期间不阻塞UI线程。
    • Task.Run:将委托代码放入线程池线程执行(后台线程),避免占用UI线程。

2. 多线程与UI交互(Control.Invoke)

  • 问题:WinForm控件只能在创建它们的线程(通常是UI线程)中操作,后台线程直接修改UI会抛出异常。
  • 解决方案:使用Control.Invoke将UI操作委托切换到UI线程执行。
    // 后台线程中更新UI
    Invoke((Action)(() =>
    {
        HOperatorSet.DispObj(currentImg, zdy_halconWindow.halconWd);
        lblSingleTime.Text = $"单张耗时:{_singleImageCost}";
    }));
    
  • 原理Invoke会将委托传递给UI线程的消息队列,由UI线程处理,保证线程安全。

3. 定时任务(System.Windows.Forms.Timer)

  • 作用:定期执行UI更新操作(如实时显示匹配时间)。
  • 关键代码
    // 初始化Timer
    timerTimeDisplay.Interval = 10; // 10毫秒触发一次
    timerTimeDisplay.Tick += timerTimeDisplay_Tick;
    
    // Tick事件(在UI线程执行)
    private void timerTimeDisplay_Tick(object sender, EventArgs e)
    {
        lblTotalTime.Text = $"总匹配时间:{_totalCost}"; // 安全更新UI
    }
    
  • 特点
    • Interval:触发间隔(毫秒)。
    • Tick事件:在UI线程执行,可直接操作控件(无需Invoke)。
    • 适合轻量UI更新,不适合耗时操作。

4. 取消机制(CancellationTokenSource)

  • 作用:允许用户中途终止长时间运行的异步任务(如批量匹配)。
  • 关键代码
    // 初始化取消令牌
    private CancellationTokenSource _cts = new CancellationTokenSource();
    
    // 在异步任务中检查取消
    await Task.Run(async () =>
    {
        foreach (var imagePath in _imagePaths)
        {
            if (token.IsCancellationRequested)
                token.ThrowIfCancellationRequested(); // 抛出取消异常
            // 匹配逻辑...
        }
    }, _cts.Token);
    
  • 使用场景:可添加“取消”按钮,点击时调用_cts.Cancel()终止任务。

5. 资源管理(IDisposable与Halcon对象释放)

  • 问题:Halcon的HImageHObject等非托管资源若不释放,会导致内存泄漏。
  • 解决方案
    • 使用using语句自动释放实现IDisposable的对象。
    • 手动释放Halcon特殊资源(如ClearShapeModel)。
    // 自动释放HImage
    using (var currentImg = new HImage(imagePath))
    {
        // 使用currentImg...
    }
    
    // 释放模板资源
    if (_modelID != null && _modelID.IsValid())
        HOperatorSet.ClearShapeModel(_modelID);
    

四、优化总结

  1. 资源安全:通过using和手动释放,避免Halcon对象内存泄漏。
  2. 线程安全:所有UI操作通过Invoke切换到UI线程,防止跨线程异常。
  3. 异步效率:用await Task.Delay替代Task.Delay.Wait(),避免阻塞后台线程。
  4. 可扩展性:添加取消机制,支持中途终止匹配,提升用户体验。
  5. 稳定性:增加异常处理,捕获文件操作和Halcon函数可能抛出的异常。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容