文章目录
- 四、HDevelop编程
- 4.1. 新建一个新程序
- 4.2. 输入一个算子
- 4.3. 指定参数
- 4.4. 获取帮助
- 4.5. 添加其他程序
- 4.6. 理解图像显示
- 4.7. 检查变量
- 4.8. 利用灰度直方图改进阈值
- 4.9. 编辑代码行
- 4.10. 重新执行程序
- 4.11. 保存程序
- 4.12. 选择特征区域
- 4.13. 打开图形窗口
- 4.14. 循环遍历结果
- 4.15. 总结
四、HDevelop编程
本章节介绍如何用HDevelop来开发你自己的机器视觉应用程序。这意味着,这些内容在实际开发中要积极遵循。接下来,假设HDevelop的属性都为默认值,即整个IDE处于一个默认状态(一般在新安装后,HALCON是处于默认状态的)。如果你不确定当前配置是否是默认状态,你可以调用以下命令行来启动HDevelop,以使用默认设置。
hdevelop -reset_preferences
本章节会处理一个简单例子。给定下列图片,目标是统计回形针的数量并确定它们的方向。
4.1. 新建一个新程序
打开HDevelop,单击 文件>新程序(New Program,翻译成新建程序好些;中文版HDevelop的翻译比较死) 来新建一个程序。若当前程序有未保存的更改,HDevelop会通知你。如果你重新排列过窗口,单击 窗口>排列窗口 可以恢复至默认布局(而且是适应窗口大小的) 。
第一件要做的事是读取图像并将其存储在一个图像变量(iconic variable)中。从上一章节可知,我们可以轻松拖动一个图像到HDevelop窗口中,这将插入 read_image 算子至程序中。
4.2. 输入一个算子
单击算子窗口的文本框,输入 read_image 并按下回车键(Enter,macOS中是Return键)。你也可以输入算子名称的子串(比如read_image,输入read_i)并按下回车键(其实就是字符串匹配)。HDevelop将打开匹配输入字串的算子列表。用这种方式,你可以轻松地选择算子,而无需输入算子的确切名称(全名)。接着用鼠标或方向键标记所需的算子,然后按下回车键确认选择。若你不小心选错了算子,可以单击算子名称边上的下拉箭头重新打开列表。
4.3. 指定参数
在选中一个算子后,它的参数会显示在算子窗口中。参数会按图像和控制参数分组。参数名称边上的图标表示参数类型:输入 与 输出。语义类型显示在参数的右侧。参数在文本域中指定。首个参数会获取到输入焦点。
在 Image 文本域中输入 Clip ,图像会存储到该变量中。接着在 FileName 文本域输入 ‘clip’ (按Tab可以跳转到下一个输入域,按下Shift+Tab可以返回上一个输入域,用这种方式不用鼠标就能输入所有参数)。
点击 确定 将算子添加到当前程序中 并执行 (强调一下,确定按钮是有执行效果的)。接着,将执行以下操作:
- 算子调用会被添加至当前程序的第一行。
- IC会前进一行,因为在该行插入后,后面可能将插入新的行。
- 字符 * 添加到了窗口标题中
,以指示当前程序中存在未保存的更改。在程序窗口中的当前程序(main)也会被 * 标记。 - 程序行被执行,且PC前进。更准确地说:从PC到IC的所有行都被执行。
- 图像显示在图形窗口中。
- 状态栏更新,即 read_image 的执行时间会被显示,加载图像的格式会被报告。
- 输出变量 Clip 会被创建并显示在变量窗口中。
- 算子窗口被清除,并为插入下一个算子做准备。
小结
这小节可能说了一堆也不知道是什么,其实就是图形化配置算子的调用。
先选择目标算子,然后配置算子的输入输出参数,点击确定就会生成代码插入到程序中执行。执行完,图像输出到输出变量中,供后续使用。
4.4. 获取帮助
你也许会好奇 clip 图像是从哪里加载的,因为我们没有指定任何路径,甚至是文件扩展名(这里指上小节的FileName文本域中,我们仅输入一个’clip’就成功加载图片了)。这与HALCON算子 read_image 工作方式的一个细节有关。HDevelop对此一无所知,它只是执行带有你提供的参数的算子罢了。
在HDevelop内,访问算子说明文档非常容易。
双击程序窗口的第一行代码,该算子的信息会显示在算子窗口中,可供编辑。现在点击 帮助(算子窗口中的) 打开HDevelop在线帮助窗口。它将自动跳转到所显示算子的文档。参考手册是完全交叉链接的(cross-linked,交叉链接大概是说可以互相跳转)。窗口左侧的导航提供了对文档的快速访问。目录 选项卡显示了文档的层次结构。
算子 选项卡列出了所有可直接访问的算子。在 查找 文本框输入字串,以快速查找匹配的算子。
在本章节的剩余部分,尽可能地使用帮助文档来获取算子的信息。
4.5. 添加其他程序
通过阈值化来选出回形针
现在,我们要从图像中分离出回形针。首先,用肉眼可以看出来,回形针很明显与背景不同,因此基于灰度值(gray value)做选取比较合适,该操作被称为 阈值化(thresholding) (常说的二值化,其实就是阈值化的一个特例;很多场景下两者也可以等同起来)。
在算子窗口中输入 threshold ,这既是一个算子的全名,也是某些算子名称的一部分。因此,当你按下回车键,将会得到一个匹配了字符 threshold 的算子列表。再次按下回车键确认所选算子并显示其参数。
可以看到,上图中输入参数 Image 被自动设为 Clip 。因为没有默认值的输入变量,HDevelop会收集之前相同类型的输入变量,自动推断出合理的建议值。因此,最近的相匹配的输出参数的名称会出现在这(最近指的是当前程序行前面的)。在本例中,只有 Clip 可用。
将 MinGray 和 MaxGray 分别设为 0 和 30 。这将选中图像中的深色像素。
点击 应用 ,该按钮会执行算子但不会将其添加到程序中。此外,它会保持当前参数窗口打开可编辑的状态。这样,你就能很容易地尝试不同的设置值,并立即查看结果。所选像素(即所谓的 region )会存储在输出变量 Region 中,并显示在变量窗口中。该 region 是一个图像掩码(mask):白色像素会被选中,而黑色像素不会。
该区域还会在图形窗口中显示为覆盖层。所选像素显示为红色(可以更改默认设置,使其变为其他颜色)。
所选的阈值并不完美,稍后我们会对此进行更正。现在,单击 输入 将算子添加到程序窗口。与单击 确定 的效果不同,这并不会执行算子。注意,变量 Region 会保持其值,但不再显示在图形窗口中。而且,PC也没有前进,说明程序的第二行并未执行。
如果某些输入参数使用的变量名会在以后添加到程序中,那么使用 输入 添加程序行会很有用。
后继函数(Successor)
本小节中,一会儿是后继函数,一会儿是后继算子。我觉得两者应该是等价的,只是HDevelop中文版翻译的问题,不必纠结。
点击刚插入的程序行以选中它,可以让HDevelop根据所选行推荐算子。打开菜单 建议>后继函数 。该菜单是动态填充的,会显示当前算子的常用后继算子。现在,我们把选中的像素分割成连续的区域。鼠标指针移到菜单项上,状态栏会显示所标注算子的简短说明。查看菜单项,单击 connection 算子项。通过该菜单选择的任何算子都会转移到算子窗口中。
同样,HDevelop建议的变量名看起来也很合理,因此点击 确定 。这次,执行了两行程序:阈值操作(threshold)和连接操作(connection)。该结果也如前文所述,单击 确定 会从PC执行到IC。
在图形窗口中,由 connection 算子计算的连续区域现在以交替的颜色显示(交替就是说相邻不同色)。
4.6. 理解图像显示
本小节中的区域指的是region类型的图像变量。和XLD、image一样都是图像数据类型(iconic data)。
在执行了程序的三行代码之后,图形窗口实际上显示了三层图像变量:
- image Clip,回形针图像
- region Region
- region元组(tuple of regions), ConnectedRegions (从上到下)。
(这里的image和region是图像变量的类型)
将鼠标悬停在变量窗口的图像变量上,可查看变量的基本信息。
在图形窗口的上下文菜单(鼠标右键出现的菜单)中可以调整图像和最上层 region 的显示属性。对于图像(images),可以设置查找表(look-up table,缩写为LUT)和显示模式(称作Paint,显示)。LUT指定灰度值映射。尝试不同设置:在图形窗口中右击,从Lut和Paint菜单中选择一些值。确保菜单中的项 立刻应用变化(Apply Changes Immediately) 是选中的。注意观察图的显示如何变化,而region保持不变。
菜单项 彩色(Colored)、颜色(Color)、画/绘图方式(Draw)、线宽(Line Width)和形状(Shape) 会更改最顶部region的显示属性。将 画/绘图方式 设为 margin(边缘) ,颜色 设为 cyan(青色) , 形状 设为 eclipse(椭圆) 。ConnectedRegion (最顶层) 的显示也随之改变。而区域 Region 仍然以红色填充显示。
一种更便捷的方法是通过上下文菜单中的 设置参数 来一次设置多个显示属性。单击该项,会打开如下的设置窗口。
尝试一些设置后,单击 重置 按钮可以恢复默认可视化设置。
无法更改最顶层以外的区域(或XLD)的显示属性。你可以在图形窗口中通过双击变量窗口中的图像变量手动重建图像堆栈,并在每次添加一层时更改属性。每当添加使用全域(full domain)的图像时,堆栈会被清除。要手动清除堆栈(以及图形窗口),单击 清空图形窗口 图标。
4.7. 检查变量
将鼠标指针移到变量 ConnectedRegions 上时,你会看到它包含98个区域(regions)。
右键单击图像变量 ConnectedRegions 并选中 清除/显示 ,以在图形窗口中只显示连接的区域。再次右键单击,选中 显示目录>选择… 。该菜单项会打开一个变量检查窗口,其中列出了变量 ConnectedRegions 的内容。该检查窗口的选定区域将使用当前可视化设置显示在图形窗口中。将 画/绘图方式 设为 边缘 , 形状 设为 椭圆 ,并从列表中选中一些区域。效果如下:
现在,暂时关闭变量检查窗口。产生了如此大量的区域是由于 threshold 算子的边界设置过于粗糙。下面将使用HDevelop的可视化工具来交互式地找到更合适的设置。
4.8. 利用灰度直方图改进阈值
点击 可视化/工具 => 灰度直方图 ,打开灰度直方图检查工具。它的用途之一是直观地确定阈值边界。由于图形窗口当前仅显示区域(未显示背景),因此灰度直方图为空。双击变量窗口中的 Clip 图像变量,以重新显示原始图形,并观察其灰度直方图。
展开直方图窗口下方 范围选筛选及代码生成 项。在其操作列中选中 阈值 ,单击 阈值 左侧的图标以使操作可视化。现在,你可以通过更改 最小化(Min) 和 最大化(Max) 中的值或拖动直方图区域中的竖线来尝试不同的阈值边界。对这些值的任何更改都会立即显示在活动的图形窗口中。0和56分别适用于下界和上界。
4.9. 编辑代码行
要在算子窗口中编辑代码行,需要在程序窗口中双击该行。在算子窗口中对参数进行更改并单击 确定 或 替换 ,程序中的代码行会被更新。当然,你也可以直接在程序窗口中编辑程序。
双击上述程序的第二行调整阈值操作。将 MaxGray 的30替换为56,单击 替换 。程序窗口中的程序行被更新。
4.10. 重新执行程序
最后一步编辑程序,只是进行了微小的修改。通常,在编辑了程序中的许多行并对变量进行了多次更改后,你会希望将程序重置到初始状态并再次运行以查看更改后的整体效果。
点击 执行>重置程序执行 来重置程序。现在,选中 执行>运行 来运行完整的程序,或 重复点击 执行>单步跳过函数 来一行行执行程序。
4.11. 保存程序
现在是保存程序的好时机。选中 文件>保存 并为你的程序指定目标目录和文件名。
4.12. 选择特征区域
在修改阈值操作之后,检查变量 ConnectedRegions ,产生了更好的结果。但是,图像左侧边缘的一个连续区域仍然被返回了,如下图。
为了只获取与回形针重合的区域,需要一个标准进一步减少目前的区域。与灰度直方图类似,HDevelop提供了特征直方图工具,它能帮你根据区域的共同属性或特征选出特征区域。
点击 可视化/工具 > 特征直方图 来打开该工具。展开窗口下方的 特征选择与代码生成 项。 特征 列允许区域选择将基于的特征。默认特征是 area(面积) ,在本例中这就足够了:实际的回形针大小几乎相同,因此区域的面积是一个共同特征。在特征直方图中,横轴对应所选特征的值。纵轴对应某特征出现的频次。
与灰度直方图窗口类似,你可以可视化所选区域,即面积大小在 最小值 和 最大值之间的区域,分别由绿色和红色竖线表示。单击所选特征左侧的图标,启用可视化。
在特征直方图窗口的 输入和输出 段指定参数。拖动绿色和红色线,观察这会如何影响选中的区域。在直方图中,我们可以看到,为了覆盖所有的回形针,我们可以选择面积介于4000和直方图最大值之间的区域。当你对所选内容满意时,单击 插入代码 按钮。下面这行代码(具有类似数值)将添加至程序中IC的位置:
select_shape (ConnectedRegions, SelectedRegions, 'area', 'and', 4000, 6703.77)
运行程序,并检查输出变量 SelectedRegions 。与回形针对应的区域现在能被正确识别了。要获取回形针的方向和重心(center of gravity),将以下算子调用添加到程序中:
orientation_region (SelectedRegions, Phi)
area_center (SelectedRegions, Area, Row, Column)
算子 orientation_region 会返回一个元组值:SelectedRegions 中的每个区域,在 Phi 中都有对应的方位值返回。算子 area_center 以同样的方式将每个输入区域的 Area(面积)、Row、Column 作为元组返回。再次运行程序并检查计算出的控制变量。你能在一个检查窗口中检查多个控制变量。若控制变量都是相互关联的,如本例,这会很有用。在变量窗口中选择所有控制变量(按住Ctrl),然后右键单击 监视/查看(Inspect) 。
4.13. 打开图形窗口
到目前为止,图像数据的可视化依赖于这样一个事实:HDevelop启动时,图形窗口始终默认打开。而在程序中显式地打开不同数量的图形窗口是一种比较好的做法。
在程序的第一行前面添加以下代码:
dev_close_window ()
dev_open_window (0, 0, 512, 512, 'black', WindowHandle)
第一行关闭所有打开的图形窗口,以确保程序开始时的状态一致。第二行打开一个图形窗口,该窗口可以通过输出变量 WindowHandle 引用。窗口句柄是一个神奇的值,它也显示在相应图形窗口的标题栏中。
4.14. 循环遍历结果
作为一个集成开发环境,HDevelop还提供了其他编程语言的特性:变量赋值、表达式和控制流。变量赋值和控制流是以特定的HDevelop算子实现的,这些算子可以从菜单 算子>控制 中选择。表达式是根据特定的HDevelop语言实现的,可用于算子调用的输入控制参数。
要遍历 Phi 中的元素,我们使用一个 for 循环,从0(元组第一个元素的下标)开始计数到元素个数减1。 for 循环的输入就像一个普通的HALCON算子:在算子窗口中输入 for 并指定其参数,如下图。
注意,若选中右侧的复选框(checkbox),则会自动输入 endfor 。还要注意的是,IC会被放置在添加的行之间,以便可以直接往循环体添加程序。
符号 |Phi| - 1 是HDevelop语言的一部分。该运算计算的是Phi元素的个数减1。当插入到程序窗口中时, for 算子会以不同的格式显示,以增强可读性。
将以下指令添加到程序中。它在程序窗口中会自动缩进,以突出显示 for 循环中的嵌套。第一行末尾处的反斜杠允许程序行在下一行继续,以提高可读性(C语言中也有,相当于避免单行程序过长,而在显示上分成多行)。
dev_disp_text (deg(Phi[Index]) + ' degrees', 'image', Row[Index], \
Column[Index], 'black', [], [])
dev_disp_text 算子提供了一种便捷的方法,以在活动的图形窗口中的特定位置输出文本。按 F1 可获取相关参数含义的更多信息(选中程序行的算子,按下F1会跳转到说明文档)。符号 Phi[Index] 是HDevelop语言的另一种操作。它提供了对元组中单个值的访问(有点像数组了)。函数 deg 将参数从弧度转化为角度。本例中,+ 用于字符串连接,参数 'degrees’是一个字符串值。在连接 + 的两个操作数之前,会自动进行数值参数的类型转换(double类型转字符串类型)。其中细节在 HDevelop语言 章节中有解释。
注意,选择围绕 dev_disp_text 的循环是为了说明如何访问元组的单个元素。在该例中,实际上并不需要循环,因为 dev_disp_text 足够智能,可以直接操作元组。可以用以下调用来代替循环:
dev_disp_text (deg(Phi) + ' degrees', 'image', Row, Column, 'black', [], [])
4.15. 总结
这是一个在HDevelop中创建程序的基本方式。选择一个算子,指定它的参数,使用 应用 按钮尝试不同的设置值,使用 输入 或 确定 添加新的程序行,然后在程序窗口中双击进行编辑。开发过程中,使用HDevelop提供的交互式工具来辅助开发,如,为算子找到合适的阈值。