news 2026/10/17 8:53:18

Halcon图形绘制详解:从Region到XLD的几何对象生成与转换

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Halcon图形绘制详解:从Region到XLD的几何对象生成与转换

最近带教一位刚入行的同学跑Halcon基础练习,他指着我屏幕上那个用线段和色块拼出来的楼梯图形问:“这个打印是怎么实现的?”一听“打印”这两个字我就笑了——Halcon里其实没有打印机驱动,日常我们说的“打印”,就是把Region、XLD这类几何对象渲染到图形窗口里。本文就用这个斜式楼梯和正方形的小案例,把Halcon最常打交道的两类对象、四个常用生成算子、坐标与角度的换算讲透。内容适合刚接触Halcon的开发者,也适合准备给团队做入门分享的朋友直接拿去当讲课素材。

1. 动手前先弄清:Halcon里的“打印”到底在做什么

1.1 Region、XLD与图形窗口的关系

Halcon里有两类最常碰到的可显示对象:Region(区域)和XLD(亚像素轮廓)。Region本质是一个像素集合,分辨率对齐到图像栅格,适合做掩膜、ROI、形态学处理;XLD则是由点序列构成的连续轮廓,通常由边缘拟合、亚像素测量得到,坐标精度远高于像素级。

我们说的“打印”,实际是调用dev_display或disp_obj,把几何对象画在dev_open_window打开的图形窗口里。窗口就是画布,不是打印机。画出来的结果可以截图保存,但大多数调试场景下,我们只关心窗口里的即时反馈。

这里有一个初学者最常忽略的点:Region只能“像素级”存在,XLD是“亚像素级”存在。二者不能直接混用,但可以互相转换。后面你会看到,同一个正方形,既可以用矩形算子直接生成Region,也可以先画XLD轮廓再转Region,两条路结果一样,过程完全不同。

1.2 Row/Column坐标系的坑先埋在这

Halcon的图像坐标系原点在左上角。Row代表行号,向下增大;Column代表列号,向右增大。也就是说,Row对应视觉上的Y坐标,Column对应视觉上的X坐标,但Y轴方向是翻转的。

这个点特别容易出问题。我见过不少同学把Row当横坐标、Column当纵坐标,画出来的图形横竖颠倒。你只需要记住一句话:凡是涉及到位置和尺寸的算子,参数顺序基本都是Row在前、Column在后。

后面所有代码都遵循这个顺序,包括顶点序列里的Rows和Cols。

1.3 跑案例前的最小环境与代码模板

案例需要一个能正常运行的HDevelop环境,任何近几年发布的版本都行。不依赖额外库,也不需要真实图像,窗口本身就可以当画布。

先写一个通用初始化片段:

* 关闭可能残留的窗口,打开一个400x400的黑色画布 dev_close_window() dev_open_window(0, 0, 400, 400, 'black', WindowHandle) dev_set_color('green') dev_set_draw('margin') dev_set_line_width(2)

这里重点说下dev_set_draw('margin'),它表示只描边不填充。这在调试组合图形时非常关键。如果默认是'fill'填充模式,多个矩形叠加后重叠区域会被后画的覆盖,你根本看不清边界。等图形逻辑全部确认后,再把'fill'打开看最终效果也不迟。

2. 斜式楼梯第一种做法:循环堆矩形,简单直观

2.1 核心算子与建模思路

gen_rectangle2是Halcon里最常用来画旋转矩形的算子,签名是gen_rectangle2(Rectangle, Row, Column, Phi, HalfWidth, HalfHeight)。它围绕中心点生成一个矩形,HalfWidth控制水平半宽,HalfHeight控制垂直半高,Phi控制整体旋转弧度。

想画出斜式楼梯,最直接的思路是拆解:把楼梯拆成若干个矩形台阶,每个台阶相对上一个在Row和Column方向同时偏移固定步长,循环生成,再合并成一个对象集合。

我特意选gen_rectangle2而不是gen_rectangle1,是因为gen_rectangle2支持旋转参数Phi。等会发现,楼梯的“斜”其实有两种理解:一种是台阶排列方向斜,另一种是台阶本身带倾角。用gen_rectangle2能同时控制这两种倾斜,而gen_rectangle1只能画轴对齐矩形,能力上就少了一截。

2.2 完整代码与逐行拆解

* 初始化 dev_close_window() dev_open_window(0, 0, 500, 500, 'black', WindowHandle) dev_set_color('green') dev_set_draw('margin') dev_set_line_width(2) * 方法一:循环堆矩形 Stairs := [] for i := 1 to 8 by 1 Row := 20 + (i - 1) * 15 Col := 20 + (i - 1) * 15 Phi := -0.15 gen_rectangle2(Rect, Row, Col, Phi, 18, 8) if (i > 1) concat_obj(Stairs, Rect, Stairs) else copy_obj(Rect, Stairs) endif endfor * 显示合并后的对象集合 disp_obj(Stairs, WindowHandle)

运行后你会看到:8个半宽18、半高8的小矩形,每个矩形中心在行、列方向都增加15,并整体旋转-0.15弧度。矩形之间有重叠,但margin模式下每一级台阶的边界都看得清清楚楚,整体形成一条斜着往右下延伸的阶梯带。

几个关键参数解释一下:

  • Row和Col同步增加15,让台阶沿45度方向排列。如果你想改变坡度,把Row增量调大或把Col增量调小即可。
  • Phi统一设成-0.15,让每个台阶沿自身轴线轻微旋转,楼梯轮廓看起来更“斜”。如果设成0,效果就是正交矩形沿斜线排列,变成俯视状态的楼梯。实际项目中两种效果都有用途。
  • concat_obj把新对象追加到对象元组。首次循环用copy_obj把第一个矩形存入Stairs,避免空元组拼接报错。HDevelop里也有等价写法Stairs := [Stairs, Rect],更简洁,但concat_obj在跨语言调用时语义更清晰,我习惯用它。

2.3 调参公式与预览效果

台阶总数n、起始点(R0, C0)、Row步长StepR、Col步长StepC之间的关系可以写成一个通用公式:

第i级台阶的中心坐标:

Row_i = R0 + (i - 1) * StepR Col_i = C0 + (i - 1) * StepC

我整理了一个调参表格,方便你按需微调:

参数本例取值调整效果
台阶数n8楼梯总长度成正比
Row步长15越大楼梯越陡
Col步长15越大楼梯越宽
半宽HalfWidth18每级踏面长度
半高HalfHeight8每级立面高度
Phi-0.15台阶自身倾角,弧度制

特别提醒:当HalfWidth大于ColStep时,相邻台阶会重叠。重叠后如果用union1做区域合并,会形成连通实体,这在做掩膜时是优点;但如果你的目标是逐级统计台阶数量,重叠反而会干扰后续的连通域分析。取舍取决于你到底要“一个整体楼梯”还是“一组独立台阶”。

这是我实际调试中反复纠结过的地方,写在这里算是给后来人指个方向。

2.4 这种做法的真实适用面

循环堆矩形特别适合“每个结构需要独立操作”的场景。

比如做芯片引脚检测时,同一行引脚完全可以用循环生成多个小矩形ROI,每个ROI独立调参数,而不是画成一个整体多边形。这种写法还有一个隐性好处:对象元组里每个对象自带序号,后续用select_obj就能单独取出来做差异性处理,比如给不同的引脚设置不同的灰度阈值。

缺点是参数分散。台阶数量、间距、角度全部散落在循环代码里,后期想调整数值,得翻代码逐行找。如果要生成几十个台阶,这种写法也会显得冗长。这个缺点恰恰引出第二种方法。

3. 斜式楼梯第二种做法:多边形顶点一把梭

3.1 为什么换个思路:外轮廓建模

如果你想要的是一个“整体的楼梯区域”,而不是一组独立矩形,循环堆矩形就不太合适了。虽然可以最后用union1把所有矩形粘成一个连通区域,但绕了一步。

更直接的做法是提取楼梯外轮廓的顶点序列,用gen_region_polygon一次生成。签名很简单:gen_region_polygon(Region, Rows, Columns),按顶点顺序围出一个填充多边形。

顶点顺序只要沿外轮廓走一圈即可,不需要自己计算每一块矩形的位置。这个方法生成的区域天然是一个整体对象,后续做最小外接矩形、凸包、形态学处理时,逻辑非常干净。

坏处也明显:顶点顺序一旦写错,很容易得到自交多边形或完全变形的图形,而且不像循环堆矩形那样容易看出是哪一步的问题。

3.2 顶点序列怎么算:循环生成可延伸

为了避免硬编码一堆数组,我习惯用循环生成顶点元组。下面设计一个6级台阶的侧视楼梯,平台宽20、升高20,起始点(50, 50)。

思路是每一级台阶贡献两个顶点:水平踏面末端和竖直立面底端。看代码:

Rows := [] Cols := [] for i := 0 to 5 by 1 Rows := [Rows, 50 + i * 20, 50 + i * 20] Cols := [Cols, 50 + i * 20, 50 + (i + 1) * 20] endfor * 底边和左边闭合 Rows := [Rows, 50 + 6 * 20, 50] Cols := [Cols, 50, 50] gen_region_polygon(StairsPoly, Rows, Cols) disp_obj(StairsPoly, WindowHandle)

手动模拟一下顶点序列前几段:

(50, 50) -> (50, 70) -> (70, 70) -> (70, 90) -> (90, 90) -> (90, 110) ...

第一个点到第二个点是水平踏面,第二个点到第三个点是竖直向下走一级,第三个点到第四个点又是一格踏面,依此类推。走到最右下角后,最后一组顶点拉一条水平底边回起始行,再从底边直接竖着回到起点,闭合多边形。生成的就是一个完整的锯齿形楼梯区域,不是一堆矩形拼接,视觉上就是一个斜楼梯的整体。

这里有两个常用变种:

  • 如果想画朝左上方的斜式楼梯,把循环里的增量改成负值即可。
  • 如果StepRow不等于StepCol,台阶会沿更陡或更缓的方向倾斜。

但要注意:如果希望台阶本身带倾角,也就是边界不是横平竖直而是斜的,这种多边形方案做不到,因为锯齿边必须是水平与垂直交替的。这时候只能回到gen_rectangle2旋转法。这是两种方法真正的分水岭。

3.3 两种楼梯方法的对比,别选错

我把两种方法做成了一张对比表,方便你按场景选型:

对比维度循环堆矩形多边形顶点法
生成对象数多个独立矩形单个整体区域
是否支持台阶自身倾角支持,通过Phi调整不支持,锯齿边只能横平竖直
区域是否连通不一定,取决于重叠与否必然连通
后续单独处理某级台阶容易,select_obj即可很麻烦,要先拆分
代码可读性循环较直观顶点生成段稍短
典型场景引脚ROI、逐级测量掩膜、形态学、整体分析

我见过不少人在这个方法选择上纠结很久,其实没有绝对的优劣。如果你是做模板匹配前的ROI截取,或者做形态学预处理,多边形顶点法明显更快;如果你要做多目标独立分析,循环堆矩形更好使。先想清楚后续算子要什么,再选生成方式,别本末倒置。

4. 正方形的两种实现:矩形算子和XLD路线

4.1 方法一:gen_rectangle1/gen_rectangle2一把生成

画正方形最简单的方式就是矩形算子。

gen_rectangle1(Rect, Row1, Col1, Row2, Col2)接收两个对角顶点,生成轴对齐矩形;gen_rectangle2(Rect, Row, Col, Phi, HalfWidth, HalfHeight)围绕中心生成,支持旋转。

要得到一个200×200的正方形,两种写法:

* 轴对齐正方形,左上角(20,20),右下角(220,220) gen_rectangle1(Square1, 20, 20, 220, 220) * 中心点(120,120),半宽半高均为100,角度0为轴对齐 gen_rectangle2(Square2, 120, 120, rad(0), 100, 100)

这里有个细节必须强调:gen_rectangle2的Phi单位是弧度,不是角度。rad(0)就是0度,rad(45)是45度。

当Phi等于rad(45)时,正方形看起来会变成菱形,这在画旋转ROI时非常常用。比如标记倾斜的晶圆边缘或倾斜的码盘标记,都需要这种“带角度的正方形”。如果某些资料里写的是角度值,跑出来却完全不对,大概率就是忘记转弧度。

4.2 方法二:多边形XLD转区域,自由度更高

有时候我们希望先拿到一个精确的轮廓对象,再从轮廓转成区域。因为轮廓在亚像素测量中更有价值,比如后续要做亚像素边缘拟合、轮廓匹配。

画正方形的第二条路线就是用gen_contour_polygon_xld生成闭合多边形轮廓,再用gen_region_contour_xld填充成区域:

* 第一个点与最后一个点相同,形成闭合轮廓 gen_contour_polygon_xld(SquareContour, [50,50,250,250,50], [50,250,250,50,50]) gen_region_contour_xld(SquareContour, SquareRegion, 'filled') disp_obj(SquareContour, WindowHandle) * 换个颜色显示区域 dev_set_color('red') disp_obj(SquareRegion, WindowHandle)

执行后你会看到绿色轮廓和红色填充同时存在,非常直观。

这里有两个关键点:

第一,gen_region_contour_xld的第二个参数是填充方式,'filled'生成实心区域,'margin'只保留轮廓边。只要你需要的是区域掩膜,就用'filled'。

第二,闭合轮廓必须首尾重合。上面代码里第一个点和最后一个点都是(50,50)。如果忘了闭合,填充区域会出现缺角,甚至完全无法生成有效区域。

为什么明明有更简单的gen_rectangle1,还要走XLD这条路?因为实际项目里很多轮廓不是我们凭空画的,而是从图像边缘拟合出来的。我们要把已有的XLD轮廓转成ROI区域,这套流程就绕不开。先把空手画正方形的流程跑通,后面处理拟合轮廓时才不会慌。

4.3 区域、轮廓、多边形三者如何来回倒

实际工程里,Region、XLD、点坐标三种形态经常需要互相转换。我把常用转换路径整理成一张表:

起点终点常用算子
矩形/多边形区域gen_rectangle1 / gen_rectangle2 或 gen_region_polygon
区域XLD轮廓region_to_xld
XLD轮廓区域gen_region_contour_xld
XLD轮廓点坐标get_contour_xld
点坐标XLD轮廓gen_contour_polygon_xld / gen_contour_xld
点坐标区域gen_region_polygon

这张表是我刚开始用Halcon时最常翻的参考。你记住了这套转换路径,后面做模板匹配兴趣区、亚像素边缘测量、形态学预裁剪,基本都能快速套用,不用每次翻算子文档查来查去。

5. 四个图形画完后,我踩过的坑和积累的习惯

5.1 窗口显示状态三件套:颜色、填充、线宽

很多人跑完代码发现“我怎么没看到图形”,大概率不是生成失败,而是显示状态问题。

dev_set_color控制颜色,dev_set_draw控制填充还是描边,dev_set_line_width控制线宽。特别是多个对象一起显示时,如果不设置颜色,所有对象全用默认色叠在一起,根本分不清谁是谁。此时可以调用dev_set_colored(12),让Halcon自动为每个对象分配不同颜色。

还有一个我常犯的错:循环里disp_obj之前忘了dev_clear_window,导致上一帧图形残留在窗口里,看起来像重影。HDevelop的dev_display自带清窗逻辑,但纯disp_obj不会。

我的调试固定动作是三行连招:

dev_clear_window() dev_set_draw('margin') disp_obj(Obj, WindowHandle)

每次都是干净的一帧,绝不让迭代结果叠成一锅粥。

5.2 角度写错等于白画:弧度与正方向

有一次我让一个学员画旋转正方形,他把gen_rectangle2的Phi直接写成45。结果图形完全乱掉,显示成一团线。原因很简单:Halcon所有角度参数统一用弧度,他传的是角度,等于让矩形旋转了约2578度,画面自然没法看。

如果你习惯用角度,记得用rad(45)或tuple_rad(45)做转换。

方向问题同样容易踩坑。由于Row轴向下为正,当Phi为正值时,矩形在视觉上是顺时针旋转;想要逆时针效果,需要给负角度。我最早调斜式楼梯时,以为画歪了是因为代码写错,调试了很久才发现是方向符号理解反了。

稳妥做法是写一个只有一行的小脚本,分别测试Phi=0.1和Phi=-0.1的效果,几秒钟就能确认方向对应的符号。别凭直觉写正负号,在这个坐标系里直觉经常不靠谱。

5.3 从“画图形”到“画掩膜”的一点现实经验

斜式楼梯和正方形这两个案例看着简单,但它们延伸出来的技能非常实用。

我在一个图像分割项目里,为了把倾斜的表单区域裁出来,就是用gen_region_polygon按表格外边框顶点生成掩膜,再通过reduce_domain把图像裁剪到区域内。区域叠加在图上显示时,视觉上就像给表格画了一个斜框,裁剪后表格里的所有内容自动对齐到后续处理管线。

正方形ROI则在模板匹配里反复出现。先用gen_rectangle1确定搜索窗口,再通过reduce_domain缩小搜索范围,匹配速度能提升好几倍。如果搜索目标有旋转角度,改用gen_rectangle2带上Phi参数,把旋转范围先限制住,匹配效率和精度都上一个台阶。

这也是我强烈建议初学者认真练一遍这类“画图形”小案例的原因。它训练的不仅是几个算子的用法,更是Region和XLD之间的选择意识——什么时候用区域逻辑,什么时候用轮廓几何,这种判断能力才是最值钱的东西。

最后补一个我自己常年保留的调试习惯:凡是画组合图形的Demo,开场一律把dev_set_draw('margin')写在初始化里,等图形逻辑全部确认之后,再改成'fill'看最终填充效果。这个习惯帮我排掉了不知道多少“图形叠在一起看不清边界”的无效调试时间。

斜式楼梯和正方形只是开始,等你把这条“画图形”的手感练熟,再去做ROI裁剪、掩膜生成、亚像素轮廓重建,会发现一切都顺很多。动手把这几个例子敲一遍,比看十遍文档都管用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 8:52:11

梯度下降与反向传播:从原理到实践,彻底搞懂神经网络参数更新

梯度下降这四个字,很多新手一上来就懵,觉得是数学地狱。但换个角度想,它就是让你在浓雾里下山:你看不见整座山,只能感到脚下是平还是陡,然后朝着最陡的下坡方向迈一步,再感觉、再迈步&#xff0…

作者头像 李华
网站建设 2026/10/17 8:51:44

Claude三种形态怎么选?网页版、桌面端与Claude Code环境配置全指南

1. 先搞清楚你要的到底是哪个“Claude”很多人第一次接触 Claude,上来就问“怎么安装”“环境怎么配”,结果搜出来的教程一半在讲网页版注册,一半在讲命令行工具,还有人在折腾桌面客户端,看完更懵了。我自己最开始也踩…

作者头像 李华
网站建设 2026/10/17 8:51:42

Unity跨平台二维码生成与识别实战:Zxing工程化落地指南

1. 项目概述:为什么在Unity里搞二维码不是“加个插件就完事”?Unity里做二维码生成和识别,表面看就是调个Zxing库、拖两个脚本的事——我最早接手这个需求时也是这么想的。结果在某次AR导览项目中,现场测试阶段直接翻车&#xff1…

作者头像 李华
网站建设 2026/10/17 8:51:34

RIP路由协议从原理到抓包实验:跳数、计时器与防环机制全解析

做网络这一行,早晚都会碰见RIP。这个距离矢量路由协议在OSPF、BGP这些“现代选手”面前显得老派,但它在路由学习路径里的地位很特殊:原理简单、报文结构直观、实验环境搭建门槛低,非常适合用来理解“路由信息到底是怎么在网络里传…

作者头像 李华
网站建设 2026/10/17 8:51:09

企业级周边游平台管理系统:SpringBoot+Vue前后端分离实战解析

1. 项目定位与整体设计思路1.1 这个系统到底解决什么问题我拿到这个项目的时候,第一反应是:周边游平台听着不大,但真要落地上线,牵涉的角色和流程一点都不少。景区、旅行社、地接、游客、平台运营方,哪一环掉了都会影响…

作者头像 李华
网站建设 2026/10/17 8:51:07

有什么工具能把图片一键转成拼豆图纸?5 类方案盘点与推荐

为什么要用工具转拼豆图纸 拼豆图纸是把图片转成 “每格一种颜色” 的网格,并标注色号与用豆量。人工逐格像素化、对色号、数豆量,耗时长、易出错,新手往往因此放弃个性化创作,所以需要工具辅助。5 类方案对比方案代表优点局限适合…

作者头像 李华