C#调用Halcon绘制工具实战:如何优雅取消绘制操作

1次阅读
没有评论

共计 2659 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

背景痛点

在 C# 中集成 Halcon 进行图像处理时,绘图工具(如画 ROI 区域)的交互经常遇到两个典型问题:

C# 调用 Halcon 绘制工具实战:如何优雅取消绘制操作

  1. UI 线程阻塞:当直接调用 Halcon 的绘图方法(如draw_rectangle1)时,整个操作会同步阻塞 UI 线程,导致界面冻结直到绘制完成。这对需要长时间绘制或用户可能需要中途取消的场景很不友好。

  2. 内存泄漏风险 :直接使用hDevWindowStack.Push() 和Pop()管理绘图窗口时,如果绘制过程中抛出异常或提前退出,可能导致 Halcon 句柄未正确释放,最终积累内存泄漏。

技术方案对比

1. 暴力终止(Thread.Abort)

  • 不推荐:强制终止线程可能导致 Halcon 内部状态不一致,甚至引发死锁。

2. ManualResetEvent 信号量

  • 中等推荐:通过信号量通知绘图线程停止,但需要手动处理资源清理,代码较复杂。

3. CancellationTokenSource(推荐)

  • 优点:与.NET 异步任务模型天然契合,可无缝集成async/await。
  • 关键点 :结合 Halcon 的set_check("~give_error") 模式,允许绘图操作被外部取消而不抛出异常。

代码实现

封装绘图上下文

public class HalconDrawingContext : IDisposable
{
    private HTuple mWindowHandle;
    private CancellationTokenSource mCts;

    public HalconDrawingContext(HTuple windowHandle)
    {
        mWindowHandle = windowHandle;
        mCts = new CancellationTokenSource();
        HOperatorSet.SetCheck("~give_error"); // HDevelop: set_check('~give_error')
    }

    public async Task<HObject> DrawRectangle1Async(IProgress<string> progress = null)
    {return await Task.Run(() =>
        {
            HObject roi = null;
            try
            {progress?.Report("开始绘制矩形...");
                HOperatorSet.DrawRectangle1(mWindowHandle, out HTuple row1, out HTuple column1,
                                           out HTuple row2, out HTuple column2); // HDevelop: draw_rectangle1
                if (mCts.IsCancellationRequested)
                    return null;

                HOperatorSet.GenRectangle1(out roi, row1, column1, row2, column2);
                progress?.Report("绘制完成");
                return roi;
            }
            finally
            {if (mCts.IsCancellationRequested)
                    roi?.Dispose();}
        }, mCts.Token);
    }

    public void Cancel() => mCts?.Cancel();

    public void Dispose()
    {mCts?.Dispose();
        HOperatorSet.SetCheck("give_error"); // 恢复默认错误处理
    }
}

使用示例

// 在 WinForms/WPF 按钮事件中
private async void btnDraw_Click(object sender, EventArgs e)
{using (var ctx = new HalconDrawingContext(hWindowControl.HalconWindow))
    {var progress = new Progress<string>(msg => labelStatus.Text = msg);
        var cancelBtn = new Button {Text = "取消"};
        cancelBtn.Click += (s, _) => ctx.Cancel();

        try
        {var roi = await ctx.DrawRectangle1Async(progress);
            if (roi != null)
                DisplayROI(roi); // 显示绘制结果
        }
        catch (OperationCanceledException)
        {MessageBox.Show("用户取消了绘制");
        }
    }
}

避坑指南

1. 句柄泄漏检测

// 检查未释放的 Halcon 对象数量
HOperatorSet.CountObj(HObject.EMPTY, out HTuple count); // HDevelop: count_obj(EMPTY, Count)
Debug.WriteLine($"未释放对象数: {count}");

2. 异常处理

⚠️ Halcon 的错误代码需要转换为.NET 异常:

try
{// Halcon 操作}
catch (HOperatorException ex)
{var errCode = ex.GetErrorCode();
    if (errCode == 1400) // 用户取消
        HandleCancellation();
    else
        throw;
}

3. 高 DPI 坐标映射

在窗体构造函数中添加:

if (Environment.OSVersion.Version.Major >= 6)
    SetProcessDPIAware();

[DllImport("user32.dll")]
private static extern bool SetProcessDPIAware();

验证指标

1. 取消延迟测试

var sw = Stopwatch.StartNew();
ctx.Cancel();
Debug.WriteLine($"取消延迟: {sw.ElapsedMilliseconds}ms");
// 应 <100ms

2. 内存泄漏测试

var before = GC.GetTotalMemory(true);
// 执行绘制并取消
var after = GC.GetTotalMemory(false);
Debug.Assert(Math.Abs(after - before) < 1024); // 内存增长应 <1KB

思考题

如何扩展本方案支持多绘图窗口竞争取消?可以考虑:

  1. 使用 CancellationTokenSource.CreateLinkedTokenSource 合并多个取消令牌
  2. 为每个绘图窗口维护独立的绘图上下文
  3. 引入全局取消注册表,通过窗口句柄映射到对应的取消操作
正文完
 0
评论(没有评论)