代码优化与知识点详解
一、代码优化说明
原代码实现了基于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的
HImage、HObject等非托管资源若不释放,会导致内存泄漏。 -
解决方案:
- 使用
using语句自动释放实现IDisposable的对象。 - 手动释放Halcon特殊资源(如
ClearShapeModel)。
// 自动释放HImage using (var currentImg = new HImage(imagePath)) { // 使用currentImg... } // 释放模板资源 if (_modelID != null && _modelID.IsValid()) HOperatorSet.ClearShapeModel(_modelID); - 使用
四、优化总结
-
资源安全:通过
using和手动释放,避免Halcon对象内存泄漏。 -
线程安全:所有UI操作通过
Invoke切换到UI线程,防止跨线程异常。 -
异步效率:用
await Task.Delay替代Task.Delay.Wait(),避免阻塞后台线程。 - 可扩展性:添加取消机制,支持中途终止匹配,提升用户体验。
- 稳定性:增加异常处理,捕获文件操作和Halcon函数可能抛出的异常。