简介:一个完整的HALCON与C#联合编程示例,面向机器视觉初学者和C#桌面应用开发者,演示如何读取CAD文件并在界面中显示。工程采用相对路径引用测试图纸,将下载后的文件夹放到桌面即可运行;若halcondotnet.dll版本不匹配,也可参照提示手动添加本机相应引用,有助于理解DLL配置与联合编译流程。压缩包为rar格式,共34个文件、452KB,核心内容包括9个C#源码文件、Visual Studio解决方案与工程配置(sln/csproj/config)、可直接运行的exe、DXF测试图纸以及halcondotnet.dll引用等,结构完整,适合直接打开调试。该示例已有2132人学习下载。读者可以从中掌握HALCON与C#的基本交互方式,熟悉CAD文件加载、图像显示、引用管理与路径设置等常用环节,并可将这套工程作为模板,扩展到实际视觉项目中。 做机器视觉上位机这几年,HALCON 和 C# 联合编程是我日常用得最多的组合。很多同行一提到“读取一个 CAD 并显示”,第一反应是“不就是调一个算子吗”,但真到自己动手,从 DXF 文件怎么读、读出来为什么不显示、为什么跟 CAD 里看的方向不一样,到坐标系怎么转、窗口怎么自适应,坑其实不少。这篇文章就把我实际项目里读 CAD 图纸并在 HALCON 窗口显示这套方案完整拆开讲:适合正在做视觉引导、离线生成检测 ROI、或者需要把工程图纸落到上位机界面上的工程师参考,能帮你少走至少一晚上的弯路。
1. 为什么要把 CAD 图纸读进视觉程序
1.1 最有价值的三个应用场景
读 CAD 图纸这件事,表面看只是“显示一个图形”,但放到视觉项目里,它解决的是三类非常实际的需求。
第一类是视觉引导定位。比如机器人抓取工位,工件外形有几十种规格,每种规格的抓取点、轮廓边界都存在 CAD 图里。程序运行前先加载对应型号的 DXF,把轮廓叠加到相机实时画面上,操作员能直接看到“图纸轮廓”和“实际工件”之间的偏差,比纯看坐标数字直观太多。
第二类是离线生成检测区域。电路板、结构件检测时,检测 ROI、屏蔽区域往往能从图纸里精确拿到。直接在图像上任鼠标框选,效率低、容易偏,而且换型号就得重画。把 CAD 里画好的区域轮廓读进来,转成 Region 后配合reduce_domain缩小处理范围,一套代码管所有型号。
第三类是路径和测量辅助。AGV、点胶机这类设备需要离线规划运动轨迹,图纸里的直线、圆弧就是现成的路径点数据,读进 HALCON 转成 XLD 轮廓后,既能显示,也能通过get_contour_xld把点坐标取出来下发到运动控制卡。
1.2 方案选型:自己解析 DXF 不如交给 HALCON
我最早想过自己写 DXF 解析器,后来在项目里试了一版才发现,这条路看着简单,实际是给自己挖坑。
DXF 的实体类型实在太多:LINE、ARC、CIRCLE、POLYLINE、LWPOLYLINE、SPLINE、ELLIPSE、INSERT 块引用,还有各种扩展数据。自己解析时,光是把块(Block)里嵌套的实体炸开、处理好图层开关、再换算圆和弧的凸度参数,就是一个完整的小型 CAD 引擎。而且解析出来的顶点数据要自己维护结构体,显示还得自己用 GDI+ 或 OpenGL 画,交互缩放平移也要重新造轮子。
HALCON 本身擅长处理轮廓数据。读 CAD 可以用read_contour_dxf直接拿到 XLD 类型的轮廓对象,XLD 是 HALCON 的“亲儿子”类型,后续无论显示、转 Region、变换坐标还是做距离测量,全都无缝衔接。显示方面还有现成的HWindowControl控件,缩放、平移、反色显示都自带,省掉一大半 UI 工作量。
1.3 联合编程的分工边界
C# 和 HALCON 联合编程,我的习惯是:C# 管逻辑,HALCON 管数据和渲染。C# 负责文件对话框、工单解析、数据库查型号、日志记录、用户权限这些上位机该干的事;HALCON 负责把 DXF 读成 XLD、把 XLD 显示到窗口、以及后续的标定变换和图像处理。
这样分工的好处是调试时边界非常清晰——界面卡了查 C#,显示不对查 HALCON 算子,两边不用互相甩锅。
2. 动手前的环境准备和核心算子
2.1 C# 工程里接好 HALCON 运行环境
先解决引用问题。安装 HALCON 后,在 Visual Studio 里新建 WinForms 项目,需要添加两个东西:
HalconDotNet.dll引用,一般位于 HALCON 安装目录的bin\dotnet35或bin\dotnet20下。- 工具箱中添加
HWindowControl控件,拖到窗体上,它就是后面的显示画布。
这里有个很关键却经常被忽略的设置:VS 的“目标平台”必须和 HALCON 版本位数一致。装的是 64 位 HALCON,就把项目的Platform target改成x64;装的是 32 位,就改成x86。很多项目一编译就报 “Mixed mode assembly is built against version...”,或者运行时报HALCON error #HALOCN之类,十有八九是 AnyCPU 和位数不匹配导致的。
如果运行时报许可证错误,检查环境变量HALCONROOT和HALCON_LICENSE_FILE是否指向正确的安装路径和许可文件,开发环境配好后基本不用再动。
2.2 read_contour_dxf 一行代码背后的逻辑
读取 DXF 的核心算子就一个:
HOperatorSet.ReadContourDxf(out HObject contours, dxfPath, out HTuple dxfStatus);两个输出参数里,contours是一个 XLD 轮廓对象,存放图纸里所有能转换的几何图元;dxfStatus是状态信息列表,HALCON 会把无法转换的实体类型、被跳过的图层提示信息放到这里。实际开发时,dxfStatus一定要打印出来看一眼,它能帮你判断是图纸本身的问题,还是文件路径的问题。
这个算子背后的处理逻辑是:读取 DXF 的实体段,把 LINE、ARC、CIRCLE、POLYLINE 等基础图元转成 XLD 轮廓,遇到不支持的实体就跳过,并写入dxfStatus。文本标注、填充剖面线这类对象通常要么被忽略,要么转换效果很差,所以源图纸最好清理掉不必要的文字和填充,只留几何轮廓。
2.3 DXF 实体、坐标单位和显示坐标的关系
新手最容易懵的是坐标体系。CAD 图纸里的坐标系,单位一般是毫米,原点通常在图框左下角,Y 轴向上;而 HALCON 的窗口显示坐标是图像坐标,原点在左上角,Y 轴向下。所以读进来的轮廓坐标在数值上可能是几百甚至几千。
这带来两个后果:
- 不做任何处理直接
DispObj,图形可能出现在窗口左上角一个不起眼的位置,或者因为坐标太大跑到画面外,看起来就像“什么都没读出来”。 - 上下方向跟 CAD 里看可能相反,因为两个坐标系的 Y 轴方向默认相反。
理解了这两点,再看到后面显示异常就不会慌。
3. 读取 CAD 并显示到窗口的完整实现
3.1 最简单版本:先保证能读出来
先写一个最小可用版本,把 DXF 读出来,直接显示到HWindowControl上:
private void LoadDxfAndShow(string dxfPath) { HObject contours; HTuple dxfStatus; // 读取 DXF,得到 XLD 轮廓 HOperatorSet.ReadContourDxf(out contours, dxfPath, out dxfStatus); // 获取显示的窗口对象 HWindow window = hWindowControl1.HalconWindow; // 清空窗口,设置显示颜色和线宽 window.ClearWindow(); window.SetColor("green"); window.SetLineWidth(2); // 显示轮廓 window.DispObj(contours); }这段代码能跑通,但一般只适用于坐标范围恰好和窗口显示范围接近的情况。对大多数真实图纸来说,这里只是验证“能不能读”,离“好用”还差一步。另外注意上面代码里contours没释放,实际项目里建议把它保存成一个窗体级字段,等下次重绘时再Dispose掉重新读取,避免内存持续增长。
3.2 进阶版:让图形自动适配窗口范围
解决“读出来但看不到”的核心方法是根据轮廓的包围盒动态设置窗口显示范围。HALCON 里用SmallestRectangle1Xld获取轮廓外接矩形,再把这个矩形映射到窗口客户区大小,换算出一组行列范围,传给SetPart。
private void ShowDxfWithFit(string dxfPath) { HObject contours; HTuple dxfStatus; HOperatorSet.ReadContourDxf(out contours, dxfPath, out dxfStatus); // 获取轮廓包围盒 HTuple r1, c1, r2, c2; HOperatorSet.SmallestRectangle1Xld(contours, out r1, out c1, out r2, out c2); double centerRow = (r1.D + r2.D) / 2.0; double centerCol = (c1.D + c2.D) / 2.0; double objHeight = r2.D - r1.D; double objWidth = c2.D - c1.D; if (objWidth < 1) objWidth = 1; if (objHeight < 1) objHeight = 1; double winWidth = hWindowControl1.Size.Width; double winHeight = hWindowControl1.Size.Height; // 计算适合的缩放比例,留 10% 边距 double scale = Math.Min(winWidth / objWidth, winHeight / objHeight) * 0.9; double halfWidth = winWidth / (2 * scale); double halfHeight = winHeight / (2 * scale); // 设置窗口显示范围(行列范围) hWindowControl1.HalconWindow.SetPart( new HTuple(centerRow - halfHeight), new HTuple(centerCol - halfWidth), new HTuple(centerRow + halfHeight), new HTuple(centerCol + halfWidth)); HWindow window = hWindowControl1.HalconWindow; window.ClearWindow(); window.SetColor("green"); window.SetLineWidth(2); window.DispObj(contours); }这段代码的关键逻辑在SetPart的四个参数:前两个是显示区域左上角在“轮廓坐标”里的行列位置,后两个是右下角位置。它做的事情,相当于把轮廓坐标中的一小块矩形区域放大到整个窗口,从而实现“自动套准”。
这里要特别注意参数顺序:SetPart(row1, col1, row2, col2),第一个是行(row,对应 Y),第二个是列(col,对应 X)。搞反了的话图形会发生奇怪的旋转和拉伸。
3.3 显示效果优化:图层配色和交互细节
SetColor("green")和SetLineWidth(2)是最基础的显示设置。如果 DXF 里有多组轮廓,需要区分不同图层实体,比如外层边框用一种颜色,内部特征用另一种颜色,可以通过dev_set_colored或者手动遍历轮廓对象设置颜色。
HWindowControl 控件自带鼠标缩放的交互逻辑的底子,但如果你发现鼠标滚轮缩放不好用或者拖动不顺畅,可以考虑关闭控件默认事件,自己用HMouseWheel、HMouseDown事件配合SetPart实现定制的缩放平移。实际项目里我更推荐自己做一套,因为可以同时维护一个“当前放大倍数”的状态,方便叠加显示坐标和测量辅助线。
另外显示 CAD 的窗口底色,我习惯在初始化时设成白色或黑色看场景。检测项目里为了跟相机图像叠加时看得清楚,通常用深色底 + 亮色轮廓;如果是给操作员看工程图,浅色底更符合看图纸的习惯。用window.SetWindowParam("background_color", "white")这类参数设置即可。
4. 高频问题与排查思路
4.1 DXF 读不出来或者实体丢失
最常见的情况是 HALCON 读一个 DXF 文件时,dxfStatus里刷出一堆警告,图形只显示了一半。排除路径和权限问题后,大概率是图纸本身的原因。
我去现场排查遇到过几类典型情况:
- 图纸版本太新:比如用最新版 AutoCAD 保存的高版本 DXF,HALCON 不一定完整支持。
- 图元太复杂:里面有大量文字、尺寸标注、填充剖面线,这些实体转换成 XLD 后很难看,而且拖慢读取速度。
- 块没有炸开:图中大量对象是 Block 引用,HALCON 对块引用的支持有限,导致部分图元“消失”。
处理办法很朴素:拿到用户提供的图纸后,先用 AutoCAD 或 DraftSight 打开,EXPLODE炸开所有块,删掉尺寸标注和文字图层,再SAVEAS另存为 Auto CAD 2000/2004 版本的 DXF。这条流程我在多个项目里验证过,基本能解决 90% 的读取不完整问题。
4.2 图形方向、比例和显示范围不对
图纸读进来了,但图形是倒的,或者跟现场实物对不上,问题基本出在坐标系和单位理解上。
HALCON 的坐标行向下为正,所以直接读出来的图形大概率会跟 CAD 里上下颠倒。如果项目里需要和原始图纸方向一致,可以用仿射变换把行坐标翻转一下,再通过 SetPart 调整显示范围:
HObject flipped; HTuple homMat2D; HOperatorSet.HomMat2dIdentity(out homMat2D); HOperatorSet.HomMat2dScale(homMat2D, 1.0, -1.0, 0, 0, out homMat2D); HOperatorSet.AffineTransContourXld(contours, out flipped, homMat2D);这个变换的语义是“列坐标不变,行坐标取反”,相当于把图形以水平线为轴翻转到屏幕坐标系。翻转完行坐标可能出现负值,不需要担心,因为 SetPart 和显示缩放会重新确定可视范围。
比例问题更隐蔽。有些图纸绘制时用的是英制单位,或者 CAD 里用了不同比例缩放,读出来的轮廓数值和实际工件毫米尺寸对不上。我的习惯是在读图后先用SmallestRectangle1Xld输出外接矩形长宽,和图纸标注的尺寸对比一下,确认比例系数,后面做视觉引导时心里有数。
4.3 License、平台位数和控件异常
运行时报 HALCON 错误,先区分是开发环境还是部署环境。
开发环境常见的是许可证路径配置错误,检查HALCON_LICENSE_FILE环境变量,确认指向的许可证文件存在且没有过期。部署到客户现场时,要把 HALCON Runtime 一并安装,并且把许可文件路径配置到目标机器的环境变量里。
平台位数的问题前面提过,再强调一次:HALCON 是原生 C/C++ 写的底层库,C# 是托管代码,两边位数不一致就会在调用算子时炸出莫名其妙的异常。先确认装的 HALCON 是 x64 还是 x86,再改 VS 项目平台目标,这是万能第一步。
HWindowControl 控件异常,常见于在后台线程里调用显示。WinForms 的 UI 控件只能在主线程访问,如果你用一个Task.Run去读 DXF 然后直接去操作 HWindowControl,大概率会碰到跨线程问题。解决方法是把显示逻辑放到 UI 线程,或者用Invoke回到主线程再显示。
5. 从“显示 CAD”到“用 CAD”:几个扩展方向
5.1 坐标标定后的轮廓叠加
读 CAD 显示只是第一步,真正体现价值的是把 CAD 轮廓坐标和相机图像坐标统一起来。
简单场景下,如果相机垂直安装且固定高度,像素当量是固定的,做一个缩放和平移仿射变换,就能把图纸轮廓叠加到实时图像上:
HTuple homMat2D; HOperatorSet.HomMat2dIdentity(out homMat2D); // 先把图纸坐标缩放到像素坐标 HOperatorSet.HomMat2dScale(homMat2D, sY, sX, 0, 0, out homMat2D); // 再平移到图像上的参考点 HOperatorSet.HomMat2dTranslate(homMat2D, tRow, tCol, out homMat2D); HObject contourImage; HOperatorSet.AffineTransContourXld(contours, out contourImage, homMat2D); hWindowControl1.HalconWindow.DispObj(contourImage);如果是做手眼标定,更标准的做法是把手眼矩阵求出来的HomMat2D直接用在这个变换里。这样操作员在界面上看到的就是“图纸上的抓取位置”和“实际工件位置”的偏差,调机效率会高非常多。
5.2 轮廓转 Region 与掩膜
CAD 里的封闭轮廓不仅能显示,还能转成 Region 当 ROI 用。常用算子有两个:
gen_region_contour_xld:把 XLD 轮廓填充成 Region,适合形状不规则的封闭区域。read_region_dxf:直接读 DXF 生成 Region,适合图纸里本来就画好了填充区域的情况。
我做电路板检测项目时,会把禁布区、螺丝孔位这些区域从 CAD 里读出来转成 Region,然后和相机图像求交集、排除干扰点,效果比手动框选稳定得多,而且换型号只需要换图纸,不用重新标定 ROI。
5.3 同类型方案:如果用 C++/Qt 或 VisionPro
后台时不时有人问“Qt 怎么调用 HALCON”,原理和 C# 是一样的。HALCON 提供 C++ 接口,读取轮廓后可以用HWindow的DispObj显示到 HALCON 自带窗口,也可以把 XLD 的坐标点取出来,用 QPainter 在自定义控件里画。区别在于,自己画的话缩放、平移、抗锯齿都要自己实现,而 HALCON 窗口组件已经把这些做完了。所以我的建议是:能用 HALCON 自带的显示控件就别自己造轮子,把精力留给业务逻辑。
如果用 VisionPro 做联合编程,思路也类似,它也有读取 CAD 轮廓的 QuickBuild 工具,但 CAD 数据落地到算法层的灵活性和 HALCON 的 XLD 体系没法比,尤其做自定义标定和轮廓变换的时候,HALCON 的优势非常明显。
最后再分享一个我实际踩过的坑:第一次在项目里读 CAD 时,为了省事直接用了 3D 建模软件导出的 DXF,结果 HALCON 读出来的轮廓断成一截一截,怎么调都补不齐。后来老老实实让机械工程师用二维 CAD 重新整理、删掉多余标注、统一图层,再导出 DXF 2000 格式,问题立刻消失。所以遇到读取异常,先不要怀疑 HALCON,回头看一下图纸本身干不干净,这比调试代码效率高得多。
这套读 CAD 再显示的操作,算是 HALCON 和 C# 联合编程里最基础也最实用的能力之一。你把这套流程跑通之后,后面无论是做视觉引导、区域生成还是路径规划,都会顺手很多。
本文还有配套的精品资源,点击获取