07 区域测量与结果可视化#
一句话总结
测量用 area_center(面积 + 质心)和 orientation_region(主轴方向);把结果画到窗口上用 dev_disp_text / disp_message(文字)和 disp_arrow(箭头)。核心坑:坐标是 Row/Column,"image" 与 "window" 两套坐标系别用混。
源文件:
note/05曲别针练习.hdev、note/04套环检测.hdev
一、基本测量算子#
1.1 area_center:面积 + 质心(最常用)#
签名: area_center(Regions : : : Area, Row, Column)
| 输出 | 类型 | 含义 |
|---|---|---|
Area |
number-array | 每个区域的面积(像素数) |
Row |
number-array | 每个区域质心的行坐标 |
Column |
number-array | 每个区域质心的列坐标 |
输出顺序是 Area → Row → Column
源码里写成 area_center (SelectedRegions, Area, X, Y),变量名 X/Y 是随手起的,实际 X 装的是 Row,Y 装的是 Column。
后面 disp_arrow (..., X, Y, ...) 之所以能画对,正是因为 disp_arrow 的前两个坐标参数也是 Row, Column,顺序刚好对上。一旦某处按 (x, y) 理解去用,就会画反。
Area、Row、Column都是元组,长度 = 区域个数,下标一一对应:Area[i]对应Row[i]、Column[i]。- 区域个数:
num := |Area|。
1.2 orientation_region:区域方向#
签名: orientation_region(Regions : : : Phi)
| 项 | 说明 |
|---|---|
输出 Phi |
区域的主轴方向,单位是弧度(rad) |
| 范围 | −π ≤ Phi < π(≈ −180° ~ 180°) |
| 算法 | 基于 elliptic_axis(等效椭圆)的二阶矩主轴;再用"离质心最远的轮廓点"的列坐标确定方向的正负 |
| 前置算子 | threshold、connection |
| 后继算子 | disp_arrow |
必须记住的两件事
Phi是弧度不是角度!直接显示会得到 −3.14 ~ 3.14 的数字,看起来完全不对。要用deg (Phi)转成角度。- 正负号与"等效椭圆"有关,等价方向可能一正一负。例如两条物理上平行、只差一个"反向"的区域,可能一个返回
18°、另一个返回−174.8°(相差 180°)。同一个方向有两种弧度表示,写判断逻辑时要注意归一化。
1.3 角度与弧度的互相转换#
配合格式化输出:
$'3.2f':保留 2 位小数+ 'deg':拼上单位后缀,显示成45.32deg
二、把主轴画出来(画箭头的正确姿势)#
dev_set_line_width (3)
dev_set_color ('yellow')
disp_arrow (WindowHandle, X, Y, X-100*sin(Phi), Y+100*cos(Phi), 3)
签名: disp_arrow(WindowHandle, Row1, Column1, Row2, Column2, Size)
| 参数 | 含义 |
|---|---|
Row1, Column1 |
起点(这里是质心) |
Row2, Column2 |
终点(沿主轴方向延长 100 像素) |
Size |
箭头头部的大小(像素) |
终点公式怎么来的#
要把"从质心出发、方向为 Phi、长度 100 像素"的向量换算成 (Row, Column):
* X 就是 Row,Y 就是 Column
X - 100*sin(Phi) * Row2:Row 轴向下,所以取负号
Y + 100*cos(Phi) * Column2:Column 轴向右,取正号
为什么 Row 那里要有负号
图像坐标里 Row 向下增大,而角度 Phi 是按"数学惯例(y 向上为正、逆时针为正)"定义的。两者方向相反,所以 Row 方向的增量要取负。
一句话:Column 跟 cos 同向,Row 跟 sin 反向。
与官方示例完全一致
HALCON 自带示例 clip.hdev 的写法是:
Length := 80
disp_arrow (WindowID, Row, Column, Row - Length * sin(Phi), Column + Length * cos(Phi), 4)
X-100*sin(Phi), Y+100*cos(Phi) 完全同构,只是长度取 100。把变量名换成 Row/Column 会更清晰。
三、把结果显示到窗口#
3.1 dev_disp_text:开发环境专用文字显示#
| 参数 | 说明 |
|---|---|
Text |
要显示的内容(可以是元组,逐个显示) |
CoordSystem |
'image' 用图像坐标;'window' 用窗口像素坐标 |
Row, Column |
显示位置(配合 CoordSystem 解释) |
Color |
文字颜色 |
GenParamName/Value |
附加样式,如 ['border_radius'], [1] 加圆角背景框 |
* 在窗口固定位置(左上角 100,100)显示统计个数
dev_disp_text ('个数'+num, 'window', 100, 100, 'blue', ['border_radius'], [1])
* 在每个区域的质心处显示其面积
dev_disp_text (Area[Index1], 'image', Row[Index1], Column[Index1], 'black', [], [])
3.2 disp_message:传统绘制文字#
| 参数 | 说明 |
|---|---|
WindowHandle |
窗口句柄(dev_disp_text 不需要,这是两者最大区别) |
String |
字符串或字符串元组 |
CoordSystem |
'image' 或 'window' |
Box |
'true' 带背景框(看得清)、'false' 不带 |
dev_disp_text vs disp_message
dev_disp_text |
disp_message |
|
|---|---|---|
| 需要 WindowHandle | 不需要(HDevelop 里更省事) | 需要 |
| 位置含义 | 文字左上角在 (Row, Column) | 文字左上角在 (Row, Column) |
| 适用场景 | HDevelop 调试 | 导出到 C# / C++ 等语言时用得更多 |
两者不能混用在同一个程序里做精细对齐,因为它们对文本边界的处理略有差异。学阶段统一用 dev_disp_text 即可。
3.3 image 与 window 两套坐标系#
CoordSystem |
含义 | 什么时候用 |
|---|---|---|
'image' |
坐标按图像坐标系解释,会随图像缩放/平移一起动 | 标注目标本身的位置,如"在这个区域上写面积" |
'window' |
坐标按窗口像素解释,固定不动 | 显示状态信息,如左上角的"个数:37" |
源码中的典型搭配
统计类用 window,标注类用 image —— 记住这条就够了。四、其他常用绘制算子#
| 算子 | 作用 |
|---|---|
disp_circle (WindowHandle, Row, Column, Radius) |
画圆 |
disp_cross (WindowHandle, Row, Column, Size, Angle) |
画十字标记(标中心点很好用) |
disp_line (WindowHandle, Row1, Column1, Row2, Column2) |
画线段 |
disp_rectangle1 (WindowHandle, Row1, Column1, Row2, Column2) |
画正矩形 |
disp_rectangle2 (WindowHandle, Row, Column, Phi, Len1, Len2) |
画带角度矩形 |
disp_region (Region, WindowHandle) |
把区域画到指定窗口 |
set_display_font (WindowHandle, Size, Font, Bold, Slant) |
设置字体,如 (WindowID, 14, 'mono', 'true', 'false') |
dev_set_line_width (3) |
设置线宽(箭头、轮廓加粗) |
五、完整源码解读(05曲别针练习.hdev)#
read_image (Image, 'clip')
* 获取图片大小
get_image_size (Image, Width, Height)
dev_close_window ()
dev_open_window (0, 0, Width, Height, 'black', WindowHandle)
dev_display (Image)
* 二值化处理(曲别针比背景暗,所以取暗段)
threshold (Image, Region, 0, 100)
* 分割:每个曲别针变成独立区域
connection (Region, ConnectedRegions)
* 筛选:按面积留下真正的曲别针
select_shape (ConnectedRegions, SelectedRegions, 'area', 'and', 3500, 8107.97)
* 看效果
dev_set_color ('blue')
dev_clear_window ()
dev_display (Image)
dev_display (SelectedRegions)
* 获取坐标和面积(注意:X 实际是 Row, Y 实际是 Column)
area_center (SelectedRegions, Area, X, Y)
* 获取角度(弧度)
orientation_region (SelectedRegions, Phi)
* 显示角度,deg 转成角度、$'3.2f' 保留两位小数
disp_message (WindowHandle, deg(Phi)$'3.2f'+'deg', 'window', X, Y, 'black', 'false')
* 每个曲别针的中心画一条表示方向的箭头
dev_set_line_width (3)
dev_set_color ('yellow')
disp_arrow (WindowHandle, X, Y, X-100*sin(Phi), Y+100*cos(Phi), 3)
数据结构对照表#
此时代码里有 4 个长度相同的元组,下标一一对应:
| 元组 | 长度 | 含义 | 第 i 个元素 |
|---|---|---|---|
SelectedRegions |
N | 区域对象数组 | 第 i 个曲别针 |
Area |
N | 面积 | Area[i] |
X(实为 Row) |
N | 质心行坐标 | X[i] |
Y(实为 Column) |
N | 质心列坐标 | Y[i] |
Phi |
N | 主轴弧度 | Phi[i] |
\"元组下标对齐\"是 HALCON 最核心的思维
HALCON 用平行元组代替"数组 of 结构体"。只要你清楚"第 i 个元素属于第 i 个区域",就能用 for 循环自由组合任意特征。这是写 HALCON 代码的底层直觉。
六、坑位清单#
Danger
- 把
Phi当角度用 —— 必须deg()转换。 - 把
Area, Row, Column当成Area, X, Y—— 名字不重要,位置才重要。 disp_arrow的 Row 增量忘了负号 —— 箭头会朝反方向(相当于镜像)。- 统计文字用
'image'坐标 —— 图像一缩放文字就跑到画面外;统计信息请用'window'。 - 对筛选前的
ConnectedRegions做测量 —— 测到的数量、坐标与显示的目标不匹配。测量对象必须是SelectedRegions。 - 把
|Area|当成面积 ——|Area|是元组长度,即区域个数,不是面积总和。
续篇:区域测量只能到\"像素级质心\"
area_center 给的是整块区域的像素级质心,量不出"一条边在哪""一个圆的半径是多少"。
要亚像素级地量直线 / 圆 / 矩形,用的是 2D 计量(Metrology):画一个近似形状 → 沿边界自动摆一排卡尺 → RANSAC 拟合。
见 [[15 测量模型 2D Metrology]]。