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

资讯详情

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

C#联合Halcon实现可交互ROI绘制:从坐标转换到模板匹配实战

C#联合Halcon实现可交互ROI绘制:从坐标转换到模板匹配实战 简介基于C#与Halcon联合开发绘制ROI功能的完整示例工程面向具备一定C#基础、希望在.NET桌面应用中集成机器视觉交互能力的开发者适合快速搭建视觉工具的交互原型。资源以Visual Studio工程形式组织涵盖Halcon窗口控件嵌入、鼠标事件捕获以及矩形、圆形、箭头等感兴趣区域对象的创建与编辑并配有视图模型代码便于理解界面与逻辑的分离组织方式。压缩包共32个文件以cs源码、exe可执行程序、dll运行库、resx界面资源、config配置等类型为主整体仅773KB体量小巧但结构完整pdb调试符号还可辅助断点跟踪学习。已有374人学习该资源既可直接编译运行观察ROI绘制效果也可作为图像测量、缺陷定位、交互式标注等视觉应用场景的起步模板便于二次扩展与界面调整。示例同时展示了绘制结果如何应用到后续图像分析与测量流程中代码注释与模块划分清晰对工程落地和二次开发具有一定参考价值。 做C#上位机联调Halcon的项目有一段时间了最近客户提了一个很典型的需求程序不能写死检测区域要让现场调试人员用鼠标在图像上自由圈定检测范围。这就是所谓的ROIRegion of Interest感兴趣区域绘制功能。说实话这个功能本身不算难但坑不少从坐标转换到DrawingObject的封装再到和模板匹配、测量、缺陷检测这些具体业务的衔接每一步处理不好都会让项目后期很难受。这篇文章我就以“C#联合Halcon实现绘制ROI功能”为主线把整个实现思路、环境搭建、核心代码、扩展应用和避坑经验完整梳理一遍。适合正在做C#上位机视觉项目、或者刚开始接触Halcon二次开发的朋友参考尤其是想把ROI真正落地到模板匹配和测量项目里的情况。1. 项目背景与整体思路1.1 为什么需要手工绘制ROI在工业视觉项目里ROI几乎是绕不开的一环。我做过几个检测项目第一版都是直接对整幅图像处理后来发现两条硬伤一是速度扛不住分辨率稍高一点全图跑模板匹配或缺陷检测节拍直接超了二是稳定性差图像边缘的固定结构、光源反光区域容易误检把检测区域圈定在关键部位之后误判率立刻降下来一大截。但问题在于“圈定区域”这件事在交付时要留给谁来做。如果交给上位机开发人员每次换产品型号都要改代码重新编译维护成本太高。最合理的方式是程序提供交互界面现场工程师用鼠标在图像上拖拽出一个矩形、圆形或任意多边形区域程序把这个区域保存下来后续处理只针对这个区域进行。这就是C#联合Halcon实现绘制ROI功能的核心价值。1.2 绘制ROI的方案选型实现方式主要有三种我在这几个项目里都试过方案实现方式交互体验开发量适用场景Halcon自带交互算子draw_rectangle2、draw_circle等一次性绘制后立即返回小简单工具类Demo不适合商用HWindowControl鼠标事件自绘矩形、圆形一般绘制过程中无实时反馈中需要完全自定义交互逻辑时HDrawingObject对象官方可交互对象支持缩放、旋转、拖拽好绘制后仍可调整中模板匹配、测量等正式项目首选我在实际项目里推荐的是第三种HDrawingObject。它的好处是绘制完成后用户可以继续拖拽边框、调整大小和角度实时感知ROI和图像内容的关系这对现场调试非常友好。而对于不规则多边形区域再配合鼠标事件方案做补充。2. 环境准备与项目搭建2.1 引入Halcon的C#接口环境这块Halcon版本我用的是20.11开发环境是Visual Studio 2019 .NET Framework 4.8。Halcon安装完成后在安装目录下能找到两个关键文件halcondotnet.dll和halconxl.dll前者是.NET调用Halcon的桥接库后者是XL版。工程里添加引用后还要记得把这两个dll复制到exe输出目录避免在其他电脑上跑不起来。代码里引入命名空间using HalconDotNet;这里有个细节Halcon的C#接口分两种窗口控件老版的HWindowControl和相对较新的HSmartWindowControl或HSmartWindowControlPlus。老版控件在一些高分屏和复杂绘制操作下容易闪烁新版控件在内部做了缓冲优化。我的建议是新项目直接使用HSmartWindowControlPlus省掉很多重绘问题。2.2 界面搭建与图像显示在WinForm界面上拖入一个HSmartWindowControl设置好Dock属性再做几个按钮比如“打开图像”“绘制矩形ROI”“绘制圆形ROI”“清除ROI”“保存配置”界面结构就基本成型了。图像显示的核心代码非常简单HOperatorSet.ReadImage(out HObject image, test.png); hSmartWindowControlPlus.HalconWindow.DispObj(image); hSmartWindowControlPlus.HalconWindow.SetPart(0, 0, height - 1, width - 1);SetPart这一步特别重要它定义了窗口显示的图像范围如果不设置图像可能会被缩放变形。许多新手在这里踩坑后面讲到坐标转换的时候你会明白这一步的深层意义。2.3 授权与运行环境Halcon的授权是个绕不开的话题。开发阶段需要安装Halcon开发版有试用License可以用通常是一个dat文件。正式部署到客户现场时需要在目标机器上安装对应的Halcon Runtime运行环境并把License文件放到HALCONLICENSE环境变量指向的目录下。常见报错“wrong license”或者“can not find feature in”基本都是这一环节出了问题可能是License过期、版本不匹配或者License目录指向错误。注意License问题在项目验收时最容易爆发建议在开发前期就把Runtime部署脚本准备好别最后一天去现场装环境。3. 核心功能实现让鼠标在窗口上绘制ROI3.1 基于鼠标事件的自定义绘制HDrawingObject虽好但有些场景下还是需要自己控制鼠标交互。举个典型例子绘制不规则多边形官方没有直接对应的DrawingObject基元就得自己实现。这里我以矩形为例把鼠标事件画ROI的核心思路讲透思路通了多边形也顺手。思路是这样的鼠标按下时记录起点鼠标移动时实时更新当前终点鼠标抬起时确定最终的矩形。代码如下private HTuple startRow, startCol, currentRow, currentCol; private bool isDrawing false; private void hSmartWindowControlPlus_HMouseDown(object sender, HMouseEventArgs e) { if (currentDrawMode ! DrawMode.Rectangle) return; isDrawing true; // 将鼠标坐标转为图像坐标 double row, col; hSmartWindowControlPlus.HalconWindow.ConvertCoordinates( hSmartWindowControlPlus.HalconWindow.GetMpositionRow(), hSmartWindowControlPlus.HalconWindow.GetMpositionColumn(), out row, out col); startRow row; startCol col; currentRow row; currentCol col; } private void hSmartWindowControlPlus_HMouseMove(object sender, HMouseEventArgs e) { if (!isDrawing) return; double row, col; hSmartWindowControlPlus.HalconWindow.GetMposition(out row, out col); currentRow row; currentCol col; // 实时刷新显示 DrawCurrentRectangle(); } private void hSmartWindowControlPlus_HMouseUp(object sender, HMouseEventArgs e) { isDrawing false; // 生成最终Region并保存 HOperatorSet.GenRectangle1(out HObject roiRegion, startRow, startCol, currentRow, currentCol); roiList.Add(roiRegion); }这段代码里最关键的是GetMposition拿到的行、列坐标本身就基于图像坐标系了这要归功于之前调用了SetPart。如果没有正确设置SetPart窗口坐标和图像坐标不一致画出来的矩形就会和鼠标位置“错位”。这个问题我在第5章的常见问题部分还会展开。3.2 用HDrawingObject实现可拖拽的ROI如果只是要矩形、椭圆这些常规ROI我更推荐HDrawingObject。这个对象是Halcon官方提供的交互式绘图工具好处是创建后自动附加到窗口用户可以拖拽移动、拉伸边缘甚至可以旋转针对Rectangle2和Ellipse。整个过程不需要自己处理鼠标事件代码非常清爽。创建矩形ROI并让它显示在窗口上HObject image; HOperatorSet.ReadImage(out image, test.png); // 创建Rectangle2类型的DrawingObject参数分别是中心行、中心列、角度、半宽、半高 HDrawingObject drawObj HDrawingObject.CreateRectangle2(200, 300, 0, 80, 120); // 附加到Halcon窗口 hSmartWindowControlPlus.HalconWindow.AttachDrawingObject(drawObj); // 注册事件鼠标拖动结束后触发 drawObj.Move DrawObj_Move; drawObj.Resize DrawObj_Resize; drawObj.Drag DrawObj_Drag; drawObj.Select DrawObj_Select;事件处理里可以实时读取当前ROI的参数private void DrawObj_Drag(object sender, EventArgs e) { HDrawingObject obj sender as HDrawingObject; HTuple row, col, phi, len1, len2; obj.GetDrawingObjectParams(out row, out col, out phi, out len1, out len2); // 这里拿到的是Rectangle2的中心坐标、角度和半宽半高 }这里需要说明一下GetDrawingObjectParams的输出参数它们的含义随DrawingObject类型不同而变化。如果是矩形Rectangle2顺序是row、col、phi、len1、len2如果是圆形Circle则顺序是row、col、radius。不少人在参数解读上栽跟头建议用一个统一的封装方法把不同类型返回的参数转成一个自定义的ROI结构体后续处理就方便了。一个很实用的细节AttachDrawingObject之后如果用户在界面上把ROI拖到了窗口外对象不会自动销毁只是ROI区域超出图像范围需要在后续计算里做一次Intersection裁剪避免生成无效区域。3.3 多ROI区域的管理与参数持久化项目里常常不止一个ROI。比如一个工件上要检测三个关键尺寸就要画三条测量带的ROI。我的做法是维护一个ListHDrawingObject同时在界面左侧用一个DataGridView列出所有ROI的编号和类型用户点“删除”按钮就移除选中的对象。保存和加载ROI也是必备功能。Halcon的Region可以直接存成文件// 保存所有ROI先合并成一个Region HObject UnionRegion; HOperatorSet.Union1(roiRegionList, out UnionRegion); HOperatorSet.WriteRegion(UnionRegion, roi_config.reg); // 加载 HOperatorSet.ReadRegion(out HObject loadedRegion, roi_config.reg); HOperatorSet.GetRegionPoints(loadedRegion, out HTuple rows, out HTuple cols);不过很多时候保存的不只是Region本身还有ROI的类型和参数。更方便的形式是直接把参数序列化成XML或JSON比如记录是Rectangle2还是Ellipse然后保存它的中心坐标、角度、半宽半高等参数。下次启动时把这些参数重新喂给HDrawingObject.CreateRectangle2就能恢复出完全一样的可交互ROI。4. 扩展应用把ROI用在模板匹配、测量和缺陷检测4.1 模板匹配中先用ROI裁剪搜索区模板匹配是Halcon最常用的功能之一。拿到用户画的ROI后最标准的做法是把它转成Region然后用ReduceDomain把原始图像裁剪到Region范围内再做模板创建。注意这里有个常见误区截取ROI区域做模板时模板图像必须包含足够的特征纹理如果ROI刚好画在纯色区域模板描述子几乎没有区分度创建模板时容易报错“Not enough texture”。创建模板的核心代码// roiRegion: 用户绘制的ROI区域 HOperatorSet.ReduceDomain(image, roiRegion, out HObject templateImage); HOperatorSet.CreateShapeModel(templateImage, 5, -0.39, 0.79, 0.01, none, use_polarity, 20, 10, out HTuple modelID);匹配时同样可以利用ROI来限制搜索范围HOperatorSet.ReduceDomain(searchImage, roiRegion, out HObject reduceSearchImage); HOperatorSet.FindShapeModel(reduceSearchImage, modelID, -0.39, 0.79, 0.5, 1, 0.5, least_squares, 0, 0.9, out HTuple row, out HTuple column, out HTuple angle, out HTuple score);这样做有两点好处一是搜索面积大幅缩小速度提升明显二是ROI外的干扰特征被直接丢掉匹配的鲁棒性更好。我实际测过一个项目全图匹配耗时大约80ms圈定ROI后直接降到15ms以内效果立竿见影。4.2 测量功能中ROI带的用法尺寸测量场景下所谓的“画ROI”往往是指沿着工件边缘画一条测量带。这种方式在Halcon里通常用一个矩形ROI配合MeasurePos来实现。操作方式是用户用鼠标画一个细长的矩形程序根据矩形的参数创建一个测量对象然后再定位边缘。// 从DrawingObject中获取矩形参数 HTuple row, col, phi, len1, len2; drawObj.GetDrawingObjectParams(out row, out col, out phi, out len1, out len2); // 创建测量对象 HOperatorSet.CreateMetrologyModel(out HTuple metrologyHandle); HOperatorSet.AddMetrologyObjectGeneric(metrologyHandle, rectangle2, new HTuple(row, col, phi, len1, len2), 20, 5, 1, 30, out HTuple objIndex); HOperatorSet.ApplyMetrologyModel(image, metrologyHandle); HOperatorSet.GetMetrologyObjectResult(metrologyHandle, objIndex, all, result_type, out HTuple measureResult);这里有个经验测量带即矩形的宽度要盖住边缘位置但不能太宽否则会把相邻边缘也包含进来。一般根据边缘跳动范围来定皮毛经验值是半宽取5~15像素。如果测量对象经常测偏先检查ROI有没有跟着产品实际位置跑偏而不是怀疑算法出问题。4.3 缺陷检测中多区域ROI的工程化管理在缺陷检测项目里ROI的意义不仅在于缩小处理范围更在于划分检测逻辑。比如检测一块PCB板不同区域可能对应不同缺陷标准焊盘区域的缺陷阈值和基板区域的阈值不一样。这时就要给每个区域配置独立的ROI参数形成一张“区域-算法-阈值”的配置表。我的做法是把所有ROI以编号为键存进一个字典每个ROI关联一组算法参数。处理图像时遍历每个ROI裁剪区域后用对应参数跑缺陷检测算法。这样做的好处非常明显现场来了新工件只需要在界面上重新画一遍区域再调整每个区域的阈值参数不需要改一行代码。这个思路同样适用于深度学习场景。最近我在一个项目里把Halcon的预处理和YOLO模型结合起来先用Halcon对原图做ROI裁剪和方向校正再把规整后的图像送到YOLO推理。这样模型只需要关注ROI内部的缺陷特征背景干扰大幅减少准确率提升非常明显。ROI在这里变成了一道关键的前置关卡把工业现场的复杂性挡在了模型外面。5. 常见问题与避坑指南5.1 坐标系转换不对画的ROI“漂移”这是最高频的问题症状是鼠标画在图像左边结果ROI出现在右边或者缩放的图像上ROI和鼠标位置对不上。根因在于控件坐标和图像坐标没有对齐。当你使用SetPart设置了显示范围后GetMposition返回的就是图像坐标如果窗口内容和图像没有一一对应例如没有调用SetPart、或者窗口内部还有其他偏移就必须手工换算。换算逻辑其实很简单// 如果知道图像的显示偏移和缩放比例 double imageCol (controlCol - imagePartColumnOffset) / zoomFactor; double imageRow (controlRow - imagePartRowOffset) / zoomFactor;最省心的规避方式是始终在用HSmartWindowControlPlus显示图像后立刻调用SetPart并且后续所有鼠标事件都通过GetMposition获取坐标不要自己去做二次换算避免引入误差。5.2 HWindowControl闪烁、重绘卡顿旧版HWindowControl在频繁刷新绘制内容时很容易闪烁这也是我推荐用HSmartWindowControl的原因。如果你还在用旧控件最简单的补救办法是设置窗口控件的DoubleBuffered属性为true。绘制流程上也尽量把多个绘制操作放在同一次显示刷新里不要一帧图像里反复调用DispObj。另外不要在UI线程上做耗时图像处理。比如在鼠标拖动DrawingObject的过程中去执行模板匹配会导致界面卡顿拖动过程就会变得一顿一顿的。正确的做法是把匹配任务放进后台线程拖动结束后才开始计算计算结果再通过Invoke回到UI线程显示。5.3 Halcon报错“wrong license”或“can not find feature in”这两种报错都指向授权或版本问题。前者是License文件不对或过期后者往往是要用的功能在当前License版本里未授权或者dll版本和License不匹配。排查路径有以下几步确认License文件路径在环境变量HALCONLICENSE中确认Runtime安装版本和开发版一致确认halcondotnet.dll的版本和已安装的Runtime版本一致。这个坑我见过太多次了往往开发机上好好的部署到现场就报错多数就是Runtime版本混乱导致的。5.4 DrawingObject的内存与句柄释放最后提醒一个隐蔽的问题。DrawingObject是托管封装但底层持有Halcon原生句柄使用后如果不释放会慢慢把句柄池耗尽。程序长时间运行后可能出现“Out of memory”或者“Too many open files”之类的异常。释放代码如下private void ClearAllROI() { foreach (var obj in drawingObjectList) { hSmartWindowControlPlus.HalconWindow.DetachDrawingObject(obj); obj.Dispose(); } drawingObjectList.Clear(); }注意在遍历集合时不要直接Remove先收集再统一释放否则会抛集合已修改的异常。另一个经验是切换产品型号时把旧的ROI和DrawingObject全部释放重新加载新配置而不是在旧对象上反复修改。最后再分享一个我个人的做法画完ROI后先用它跑一遍全图对比测试把耗时的变化打印出来给现场人员看。很多时候“要不要圈ROI”这个争论一张耗时对比表就能让客户心服口服。这个功能做完项目交付时会省掉很多不必要的沟通成本强烈建议你也试试。本文还有配套的精品资源点击获取
返回列表