尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C#与Halcon协同构建可交互ROI标定系统

C#与Halcon协同构建可交互ROI标定系统 简介本资源是一套面向C#图像处理开发者与工业视觉工程师的Halcon ROI交互式绘制工具库聚焦解决在C#项目中便捷集成Halcon并实现矩形、圆形、多边形等常用ROI动态创建、拖拽、缩放与参数化导出的核心需求。压缩包共49个文件含19个核心C#源码如ROIRectangle1.cs、ROICircle.cs、ROIController.cs、GraphicsContext.cs等3个Halcon互操作DLL2个可执行演示程序exe及配套配置文件config、资源文件resx和VS工程文件csproj/sln完整覆盖从环境初始化、HWnd图像窗口控制到ROI几何运算与HObject数据对接的全链路实现包体仅703KB轻量易集成。已有306人学习下载适用于VS2022等新版开发环境代码结构清晰、模块职责分明附带可直接运行的Form1主界面示例开发者可快速复用ROI绘制逻辑无缝衔接Halcon后续的分割、测量与缺陷检测流程。1. C# 联合 Halcon 绘制 ROI不是简单画个框而是构建可交互、可复用、可嵌入工业上位机的视觉标定能力在实际产线视觉项目中你常会遇到这样的场景操作员需要在实时显示的相机图像上手动圈出待检测区域比如 PCB 上某个焊点阵列、金属件边缘轮廓、或药瓶瓶身标签区域这个区域要能被后续 Halcon 算法精准调用——但直接用 WinForms 的Graphics.DrawRectangle画出来的只是静态图层无法与 Halcon 的HObject图像数据联动更无法导出为Region供reduce_domain、measure_pos或深度学习预处理使用。本标题指向的正是这一类C# 上位机与 Halcon 引擎深度协同的 ROI 构建方案它不依赖 Halcon 自带的HDevelop交互控件而是通过 C# 原生 UI如 WPF 或 WinForms捕获鼠标事件将坐标映射到 Halcon 图像空间生成标准HRegion对象并支持保存/加载、多 ROI 管理、实时高亮反馈。适合 C# 上位机开发工程师、机器视觉集成工程师尤其适用于需定制化交互逻辑如 ROI 锁定、拖拽缩放、与 PLC 信号联动的工业现场部署。2. Halcon ROI 的本质与 C# 协同设计原则为什么不能只用 PictureBox.Draw()2.1 ROI 在 Halcon 中不是“图形”而是可计算的 Region 对象Halcon 中的 ROIRegion of Interest本质是像素级掩膜mask底层为HRegion类型存储的是连通域坐标集合或描述符如矩形、圆、多边形参数。它参与所有图像运算reduce_domain限制处理范围、threshold只在 ROI 内二值化、inspect_shape_model仅在 ROI 内匹配模板。这与 C# UI 层绘制的Pen/Brush完全不同——后者只是 GDI 渲染指令不携带任何图像语义信息。若仅在 PictureBox 上画框Halcon 引擎根本“看不见”该区域后续算法仍需全图处理既低效又易受背景干扰。提示Halcon 的gen_rectangle1、gen_circle、gen_polygon等算子生成的是HRegion而disp_region是将其叠加到图像上显示C# 需同步维护两套坐标UI 控件坐标系像素和 Halcon 图像坐标系行/列索引原点在左上角但部分算子如area_center返回坐标系可能不同。2.2 C# 与 Halcon 坐标映射的关键HalconWindow 控件是桥梁而非可选配件Halcon 提供的HalconDotNet.dll封装了完整的 .NET API但图像显示必须通过HWindowControlWPF或HWindowControlWinFormsWinForms控件因其内部封装了 Halcon 的窗口句柄与坐标转换逻辑。直接用PictureBox加HalconImage.ToBitmap()显示会导致坐标失真Halcon 图像缩放时HWindowControl自动维护Row,Column到屏幕像素的映射关系而ToBitmap()丢失了该上下文。2.2.1 初始化 Halcon 窗口并绑定事件的最小可行代码// WinForms 场景窗体设计器中拖入 HWindowControlWinForms 控件命名为 halconWnd private void Form1_Load(object sender, EventArgs e) { // 初始化 Halcon 窗口关键设置自动缩放和坐标映射 halconWnd.HalconWindow.SetPart(0, 0, 511, 767); // 设置显示区域适配图像尺寸 halconWnd.HalconWindow.SetDraw(margin); // 启用描边模式便于 ROI 边界高亮 halconWnd.HalconWindow.SetLineWidth(2); // 设置线条宽度 // 绑定鼠标事件注意必须在 Halcon 窗口上监听非窗体本身 halconWnd.HalconWindow.Click OnHalconWindowClick; halconWnd.HalconWindow.MouseMove OnHalconWindowMouseMove; halconWnd.HalconWindow.MouseDown OnHalconWindowMouseDown; halconWnd.HalconWindow.MouseUp OnHalconWindowMouseUp; }SetPart(0,0,511,767)定义 Halcon 窗口显示的图像区域行/列范围单位为像素。若图像尺寸为 512×768则此设置确保满屏显示且无拉伸。SetDraw(margin)启用描边渲染模式后续disp_region会以粗边框形式显示 ROI比填充更清晰。事件绑定对象是halconWnd.HalconWindow而非halconWnd控件本身——这是 Halcon.NET 的约定确保坐标已转换为图像空间。2.3 ROI 类型选型矩形、圆形、多边形的 Halcon 实现差异ROI 类型Halcon 创建方式C# 交互逻辑要点典型适用场景矩形Rectangle1gen_rectangle1(out region, Row1, Column1, Row2, Column2)拖拽起点→终点需实时计算Row1/Column1左上与Row2/Column2右下快速框选规则工件、字符区域圆形Circlegen_circle(out region, Row, Column, Radius)鼠标按下为圆心移动时半径 距离需sqrt((dx)^2(dy)^2)检测圆形孔洞、LED 灯珠定位多边形Polygongen_polygon(out region, Rows, Columns)记录点击序列双击闭合需HRegion.Union1()合并多个线段不规则缺陷区域、复杂轮廓标定注意gen_rectangle1的Row1/Column1是左上角Row2/Column2是右下角必须保证 Row1 Row2 且 Column1 Column2否则 Halcon 报错HALCON Error 3001: Invalid region parameters。C# 中需在生成前做坐标校验double r1 Math.Min(startRow, endRow); double r2 Math.Max(startRow, endRow); double c1 Math.Min(startCol, endCol); double c2 Math.Max(startCol, endCol); HOperatorSet.GenRectangle1(out region, r1, c1, r2, c2);3. 实现可交互 ROI 绘制从鼠标事件到 Halcon Region 的完整链路3.1 鼠标事件状态机设计四阶段精准控制 ROI 生命周期ROI 绘制不是简单“按下-拖拽-释放”需明确状态流转以支持取消、重绘、多 ROI 切换状态触发条件C# 行为Halcon 同步动作Idle初始状态或完成绘制后清除临时 ROI 显示clear_window()或clear_region()DrawingMouseDown→MouseMove持续记录起点实时更新临时 ROIdisp_region(tempRegion, window)循环刷新CommittedMouseUp将临时 ROI 存入ListHRegion赋予 IDcopy_obj(tempRegion, out finalRegion, 0)生成独立对象Editing双击已有 ROI激活编辑模式显示控制点get_region_points(finalRegion, out rows, out cols)获取顶点3.1.1 核心状态变量与事件处理骨架private enum DrawState { Idle, Drawing, Committed, Editing } private DrawState currentState DrawState.Idle; private HRegion currentTempRegion; // 临时 ROI未提交 private ListHRegion allROIs new ListHRegion(); // 已提交 ROI 集合 private int nextROIId 1; private void OnHalconWindowMouseDown(HWindow window, double row, double col, HTuple button) { if (button.I 1) // 左键 { if (currentState DrawState.Idle) { startRow row; startCol col; currentState DrawState.Drawing; } } } private void OnHalconWindowMouseMove(HWindow window, double row, double col, HTuple button) { if (currentState DrawState.Drawing) { // 实时生成临时矩形 ROI此处以矩形为例 double r1 Math.Min(startRow, row); double r2 Math.Max(startRow, row); double c1 Math.Min(startCol, col); double c2 Math.Max(startCol, col); HOperatorSet.GenRectangle1(out currentTempRegion, r1, c1, r2, c2); halconWnd.HalconWindow.ClearWindow(); halconWnd.HalconWindow.DispObj(hImage); // 显示原始图像 halconWnd.HalconWindow.DispRegion(currentTempRegion); // 叠加临时 ROI } } private void OnHalconWindowMouseUp(HWindow window, double row, double col, HTuple button) { if (currentState DrawState.Drawing button.I 1) { // 提交临时 ROI 为正式 ROI allROIs.Add(currentTempRegion.CopyObj()); // CopyObj() 确保独立内存 currentTempRegion.Dispose(); // 释放临时对象 currentState DrawState.Committed; // 更新 UI显示 ROI 编号如 ROI-1 UpdateROILabels(); } }CopyObj()是关键currentTempRegion是引用对象直接Add()会导致所有 ROI 指向同一内存修改一个则全部变化。CopyObj()创建深拷贝。ClearWindow()DispObj()DispRegion()组合确保每次刷新都是干净的——先清空窗口再画图像最后叠 ROI。避免残留旧 ROI。3.2 ROI 数据持久化XML 序列化与 Halcon Region 的二进制兼容性Halcon 的HRegion无法直接序列化为 JSON/XML因其内部是 Halcon 引擎管理的非托管内存。正确做法是提取 ROI 的几何参数存为结构化数据3.2.1 提取矩形 ROI 参数并保存为 XMLpublic class ROISerializable { public int Id { get; set; } public string Type { get; set; } // rectangle, circle, polygon public double[] Params { get; set; } // [r1,c1,r2,c2] for rectangle } private ListROISerializable SerializeROIs() { var list new ListROISerializable(); for (int i 0; i allROIs.Count; i) { var roi allROIs[i]; var serial new ROISerializable { Id i 1, Type rectangle }; // 获取矩形参数gen_rectangle1 的逆向解析 HTuple row1, col1, row2, col2; HOperatorSet.GetRegionBox(roi, out row1, out col1, out row2, out col2); serial.Params new double[] { row1.D, col1.D, row2.D, col2.D }; list.Add(serial); } return list; } // 保存到文件 var xml XmlSerializer.Serialize(SerializeROIs(), typeof(ListROISerializable)); File.WriteAllText(rois.xml, xml);GetRegionBox()是 Halcon 提供的标准 ROI 参数提取算子对gen_rectangle1生成的 ROI 返回精确的Row1/Column1/Row2/Column2。避免使用SerializeObject直接序列化HRegion——会抛出System.Runtime.Serialization.SerializationException。3.2.2 从 XML 还原 ROI 的 Halcon 创建流程private void LoadROIsFromXml(string filePath) { var list XmlSerializer.DeserializeListROISerializable(File.ReadAllText(filePath)); allROIs.Clear(); foreach (var item in list) { if (item.Type rectangle item.Params.Length 4) { HOperatorSet.GenRectangle1( out HRegion region, item.Params[0], item.Params[1], item.Params[2], item.Params[3]); allROIs.Add(region); } } RedrawAllROIs(); // 重新显示所有 ROI }RedrawAllROIs()内部调用halconWnd.HalconWindow.ClearWindow()→DispObj(hImage)→ 循环DispRegion(roi)确保加载后立即可见。4. 多 ROI 管理与工业级应用增强ROI 分组、锁定、与测量算子联动4.1 ROI 分组与颜色编码解决产线多工位 ROI 混淆问题单张图像常需多个 ROI 对应不同检测项如 A 工位测尺寸、B 工位检缺陷、C 工位读码。纯靠编号易出错需视觉区分ROI 属性Halcon 实现C# UI 同步颜色set_line_width(window, 2)set_color(window, red)halconWnd.HalconWindow.SetColor(red)标签disp_message(window, A-Size, window, 12, 12, black, true)halconWnd.HalconWindow.DispMessage(A-Size, ...)分组开关clear_region(window)配合DispRegion()条件调用CheckBox 控件绑定IsChecked事件4.1.1 动态切换 ROI 显示组的代码实现private Dictionarystring, Listint roiGroups new Dictionarystring, Listint { { SizeCheck, new Listint { 0, 2 } }, // 第1、第3个 ROI 属于尺寸检测组 { DefectCheck, new Listint { 1, 3 } } // 第2、第4个 ROI 属于缺陷检测组 }; private void chkSizeCheck_CheckedChanged(object sender, EventArgs e) { UpdateROIGroupVisibility(SizeCheck, chkSizeCheck.Checked); } private void UpdateROIGroupVisibility(string groupName, bool visible) { halconWnd.HalconWindow.ClearWindow(); halconWnd.HalconWindow.DispObj(hImage); if (visible roiGroups.ContainsKey(groupName)) { halconWnd.HalconWindow.SetColor(green); foreach (int idx in roiGroups[groupName]) { if (idx allROIs.Count) halconWnd.HalconWindow.DispRegion(allROIs[idx]); } } }SetColor(green)影响后续DispRegion()的绘制颜色无需为每个 ROI 单独设色。分组逻辑完全由 C# 控制Halcon 只负责渲染解耦清晰。4.2 ROI 与 Halcon 测量算子直连跳过手动坐标提取实现“画完即测”工业场景中ROI 绘制后常需立即执行测量如measure_pos找边缘、area_center算重心。传统做法是先get_region_points提取坐标再传给测量算子——效率低且易出错。最佳实践是直接将 ROI 作为reduce_domain输入让 Halcon 在 ROI 内部执行全栈运算4.2.1 使用 ROI 限定测量区域的典型链路// 假设 allROIs[0] 是已绘制的矩形 ROI HRegion roi allROIs[0]; // 步骤1限定图像处理域关键 HObject reducedImage; HOperatorSet.ReduceDomain(hImage, roi, out reducedImage); // 步骤2在限定区域内执行阈值分割避免背景干扰 HObject binImage; HOperatorSet.Threshold(reducedImage, out binImage, 128, 255); // 步骤3在二值图内找边缘measure_pos HTuple row, column, amplitude; HOperatorSet.MeasurePos( binImage, 10, 30, // 拟合直线长度与宽度 1, 30, // 最小幅度与最大幅度 all, all, out row, out column, out amplitude); // 步骤4将结果坐标映射回原始图像空间因 reduce_domain 改变了坐标系 // Halcon 自动处理row/column 已是原始图像坐标无需额外转换 Console.WriteLine($Edge at Row{row.D:F2}, Col{column.D:F2});ReduceDomain是 ROI 价值的核心体现它生成的新图像reducedImage仅包含 ROI 内像素后续所有算子threshold,measure_pos,connection均在此子图上运行速度提升 3~10 倍抗干扰能力显著增强。measure_pos返回的坐标是原始图像坐标系非 reducedImage 的局部坐标Halcon 内部已自动映射开发者无需手动加偏移。4.3 ROI 锁定机制防止误操作覆盖关键标定区域产线运行中已标定的 ROI 需禁止修改。Halcon 无原生锁定 API需 C# 层拦截事件private bool isROILocked false; private void btnLockROI_Click(object sender, EventArgs e) { isROILocked !isROILocked; btnLockROI.Text isROILocked ? 解锁 ROI : 锁定 ROI; halconWnd.Enabled !isROILocked; // 禁用整个 Halcon 控件 } // 在鼠标事件开头添加拦截 private void OnHalconWindowMouseDown(HWindow window, double row, double col, HTuple button) { if (isROILocked) return; // 直接退出不响应任何绘制 // ...原有逻辑 }halconWnd.Enabled false是最简方案禁用控件后所有鼠标/键盘事件均被系统拦截无需逐个事件加判断。若需部分功能如仅禁用绘制但允许缩放则需在每个事件处理器首行加if (isROILocked) return;。5. 排查 ROI 绘制常见失效场景坐标偏移、显示消失、Region 为空的三类根因5.1 坐标偏移明明鼠标点在目标位置ROI 却偏移 50 像素根因Halcon 窗口SetPart()设置与图像实际尺寸不匹配导致坐标映射失真。验证方法在OnHalconWindowClick中打印row/col同时用GetImageSize()获取图像真实宽高对比是否一致。修复步骤加载图像后立即获取尺寸HOperatorSet.GetImageSize(hImage, out width, out height);设置窗口显示区域halconWnd.HalconWindow.SetPart(0, 0, height.I - 1, width.I - 1);注意Halcon 行/列顺序height对应行数确保SetPart()在DispObj()之前调用否则无效。5.2 ROI 显示消失DispRegion()调用后无任何图形根因HRegion对象为空Empty Region常见于gen_rectangle1参数非法如Row1 Row2或reduce_domain后未生成有效区域。诊断命令// 在 DispRegion 前插入检查 HTuple area; HOperatorSet.AreaCenter(roi, out area, out _, out _); Console.WriteLine($ROI Area {area.D}); // 若为 0则 Region 为空修复方案对矩形 ROI强制校验坐标r1 Math.Min(r1,r2); r2 Math.Max(r1,r2);同理处理c1/c2。对多边形 ROI确保顶点数 ≥ 3且Rows/Columns数组长度相等。5.3 ROI 无法参与后续运算reduce_domain报错 “Invalid domain”根因HRegion与HObject图像尺寸不匹配。Halcon 要求 ROI 的 bounding box 必须完全落在图像范围内。检查逻辑HTuple minRow, maxRow, minCol, maxCol; HOperatorSet.GetRegionBox(roi, out minRow, out minCol, out maxRow, out maxCol); HOperatorSet.GetImageSize(hImage, out imgWidth, out imgHeight); bool isValid (minRow.D 0 maxRow.D imgHeight.I minCol.D 0 maxCol.D imgWidth.I); if (!isValid) Console.WriteLine(ROI 超出图像边界);预防措施在GenRectangle1前做边界裁剪r1 Math.Max(0, Math.Min(r1, imgHeight.I - 1)); r2 Math.Max(0, Math.Min(r2, imgHeight.I - 1)); c1 Math.Max(0, Math.Min(c1, imgWidth.I - 1)); c2 Math.Max(0, Math.Min(c2, imgWidth.I - 1));提示Halcon 的gen_rectangle1不自动裁剪超出边界的 ROI 会导致reduce_domain失败错误码HALCON Error 3002。务必在生成前做显式校验。5.4 性能卡顿绘制多个 ROI 时 UI 响应迟滞根因频繁ClearWindow()DispObj()DispRegion()导致 GDI 渲染压力过大。优化方案启用 Halcon 双缓冲与批量显示// 初始化时启用双缓冲 halconWnd.HalconWindow.SetUpdateMode(buffered); // 绘制时改为批量操作 halconWnd.HalconWindow.ClearWindow(); halconWnd.HalconWindow.DispObj(hImage); foreach (var roi in allROIs) halconWnd.HalconWindow.DispRegion(roi); halconWnd.HalconWindow.FlushBuffer(); // 一次性刷新SetUpdateMode(buffered)开启双缓冲避免闪烁。FlushBuffer()替代多次DispRegion()减少渲染调用次数提升 30% 帧率。本文还有配套的精品资源点击获取
返回列表