1D测量之一
学习如何使用Halcon进行1D测量,可以参考:
1、 Halcon自带的文档【solution_guide_iii_a_1d_measuring.pdf】
2、 Halcon例程【measure_switch.hdev】
其中 【solution_guide_iii_a_1d_measuring.pdf】,第一章以Halcon例程【measure_switch.hdev】为例分析了测量的过程。
第二章讲了测量的一些基础知识,可以参考文章:https://www.cnblogs.com/xiaomaLV2/p/3386273.html。
第三章解释了如何用测量对象来检测边缘、边缘对、特定灰度值的点和灰度值分布图,描述了如何把测量结果转换为世界坐标。
第四章描述了怎么解决更加复杂的测量问题。
下面是【solution_guide_iii_a_1d_measuring.pdf】
第二章测量的一些基础知识的一部分内容:
HALCON 得出一维边缘的步骤:
第一步画出一个矩形或扇形的ROI(测量对象),然后在ROI内画出等距离的、长度与ROI宽度一致的、垂直于ROI的轮廓线(profile line)的等距线。
第二步算出每一条等距线的平均灰度值,可以得出轮廓线的灰度直方图。
如果等距线不是垂直或水平的,那么等距线经过的就不是一个完整的像素,需要进行插值处理。构建测量对象的算子(如gen_measure_rectangle2)的参数Interpolation可以决定插值的方法, 设置Interpolation = ’nearest_neighbor’则取最近像素的灰度值,这种方法速度最快,但精度低,设置Interpolation = ’bilinear’采用双线性插值法,这种方法精度最高但是速度最慢。
等距线的长度,也就是ROI的宽度影响了轮廓线垂直方向上的平均灰度值。因此,如果我们想要检测的边缘垂直于轮廓线的话,ROI的宽度尽量打,否则,尽量小。如果边缘不垂直于轮廓线,获取到的灰度直方图会含有较多噪声,检测到的边缘会不那么准确。
第三步用高斯滤波器平滑灰度直方图,测量算子(如measure_pairs)的参数Sigma指定了高斯滤波器的标准差。
第四步求出平滑灰度直方图的一阶导数,一阶导数的极值点作为边缘的候选点,候选点均为亚像素精度,只有一阶导数极值点的绝对值大于预先设定的阈值(测量算子的参数Threshold)边缘候选点才被选作为边缘中心点。测量算子最后得到每一条边缘与轮廓线的交点。
下面是【solution_guide_iii_a_1d_measuring.pdf】 第三章使用测量对象的一部分内容:
例程measure_ic_leads.hdev中使用了 算子measure_pos来测量IC管脚的长度。测量对象的轮廓线如图中红线所示。算子measure_pos可以用于检测单个边缘。算子的参数Transition = ’negative’时,只返回从亮到暗的边,
Transition =’positive’时,只返回从暗到亮的边。Transition = ’all’时,返回所有的边。
例程中用了算子measure_pairs来测量IC管脚的宽度。边缘分组成对:如果参数Transition =“positive”,则只有那些第一条边为从暗到亮和第二条边为从亮到黑的边缘对会返回(也就是说包围亮区域的两条边会返回),且返回的点(RowEdgeFirst,ColumnEdgeFirst)为矩形长轴方向上由黑到亮的边缘点,点(RowEdgeSecond,ColumnEdgeSecond)为由亮至暗的边缘点。如果Transition =“negative”,则相反。
如果找到具有相同转换的多于一个的连续边缘,则将第一个边缘用作对元素。这种行为可能会导致在阈值不能被选择得足够高以抑制相同转换的连续边缘的应用中出现问题(如图a)。对于这些应用,存在第二种配对模式,即仅选择一个具有连续上升沿和下降沿的序列上的相应最强边缘。通过将“_strongest”附加到任何以上转换模式,例如“negative_strongest”可以选择此模式。
在measure_caliper.hdev测量除了游标卡尺的齿距线之间的距离。
首先创建一个只包含最长齿距线的测量对象,再通过算子measure_projection算出灰度直方图。
1D测量之二
平滑得到的灰度直方图
Sigma := 0.3
create_funct_1d_array (GrayValues, Function) //将离散的灰度值连成函数
smooth_funct_1d_gauss (Function, Sigma, SmoothedFunction)
对平滑后的灰度直方图进行求导
derivate_funct_1d (SmoothedFunction, 'first', FirstDerivative)
zero_crossings_funct_1d (FirstDerivative, ZeroCrossings)
导数为0的点就是边缘点。但是如果直接找0点的话,会找到很多,我们不太好筛选,所以我们选择求二阶导数找最大值来筛选。先设定一个阈值,大于这个阈值的点才被定为边缘点。
MinimumMagnitudeOfSecondDerivative := 8
PositionOfSalientLine := []
for i := 0 to |ZeroCrossings|-1 by 1
get_y_value_funct_1d (SecondDerivative, ZeroCrossings[i], 'constant', Y)
if (Y > MinimumMagnitudeOfSecondDerivative)
PositionOfSalientLine := [PositionOfSalientLine,ZeroCrossings[i]]
endif
endfor
通过得到的边缘点对应的值PositionOfSalientLine算出坐标点
RowStart := floor(Row+0.5)+floor(Length1)*sin(Phi)
ColStart := floor(Column+0.5)-floor(Length1)*cos(Phi)
RowLine := RowStart-PositionOfSalientLine*sin(Phi)
ColLine := ColStart+PositionOfSalientLine*cos(Phi)
下面是【solution_guide_iii_a_1d_measuring.pdf】
第四章高级测量:模糊测量对象的一部分内容:
模糊测量对象是标准测量对象的扩展,可以通过特定的权重函数选择边缘和边缘对。
模糊隶属度函数可以通过位置,大小或灰度值等特征选择合适的边缘对。如例程fuzzy_measure_switch.hdev
通过算子create_funct_1d_pairs创建一个隶属度函数
create_funct_1d_pairs ([7,9,11], [0.0,1.0,0.0], SizeFunction)
通过算子set_fuzzy_measure将一个标准测量对象转为模糊对象
SetType := 'size'\\通过边缘对包围区域的大小选择边缘对
set_fuzzy_measure (MeasureHandle, SetType, SizeFunction)
应用模糊对象进行测量
Sigma := 0.9
AmpThresh := 12
FuzzyThresh := 0.5
Transition := 'negative'
Select := 'all'
fuzzy_measure_pairs (Image, MeasureHandle, Sigma, AmpThresh, FuzzyThresh, \
Transition, RowEdgeFirst, ColumnEdgeFirst, \
AmplitudeFirst, RowEdgeSecond, ColumnEdgeSecond, \
AmplitudeSecond, RowEdgeCenter, ColumnEdgeCenter, \
FuzzyScore, IntraDistance, InterDistance)
FuzzyThresh 超过给定的值 0.5的边缘对将被返回。
set_fuzzy_measure_norm_pair (MeasureHandle, PairSize, 'size', FuzzyMembershipFunctionPairSizeNormalized)
将PairSize与模糊函数FuzzyMembershipFunctionPairSizeNormalized的X轴相乘构造新的模糊函数,用新的构造函数将常规的测量对象转换为模糊测量对象。
可以通过以下的特征对边缘对进行选择,具体的区别可以参考【solution_guide_iii_a_1d_measuring.pdf】的附录。
“contrast”:边缘的振幅。提取到的边缘对的两条边缘的振幅的几何平均值。
“position”:每个边缘到测量对象的参考点的距离,参考点为测量对象的轮廓线的起点。'position_center'或'position_end'将参考点设置为测量对象的轮廓线的中间或末尾。'position_first_edge'/'position_last_edge'将参考点设置为第一/最后提取的边缘在测量对象在轮廓线上的位置。如果提取的是边缘对时,取两个边缘到对应参考点的距离的几何平均值。
类似于'position','position_pair'评估每个边缘对的中心点与测量对象的参考点的距离,参考点为测量对象的轮廓线的起点。测量对象的参考点的位置可以分别由'position_pair_center','position_pair_end'和'position_first_pair','position_last_pair'设置。与“position”相反,该模糊集仅由fuzzy_measure_pairs /fuzzy_measure_pairing使用。
'size':以像素为单位来计算的一对边缘的两条边之间的距离。
‘size_diff’:计算一对边缘的两条边之间的距离与参考距离的差异。计算公式如下:
'size_abs_diff’:计算一对边缘的两条边之间的距离与参考距离的差异的绝对值。
''gray' :一对边缘边的两条边之间的平均灰度值。
多模糊集构造模糊测量对象时,计算方法:如果在位置20的点的灰度值为80,计算出来的值分别如下
membership value_position = 20 = 0.67
membership value_contrast = 80 = 0.60
则总的值的计算公式为
几何平均值的定义:
1D测量之三
测量的目的就是得到测量点的图像坐标
1. 首先在测量之前我们明白,测量点和背景之间一定有灰度值的差异,这是前提。
2. 和前面一样,首先必须找到一个ROI。然后通过gen_measure_rectangle2得到一个测量的句柄。
3. 由于我们不是测量边缘对,所以要换方法了,但是和测量边缘对的原理相同,我们需要先得到灰度值分布(gray value Profile),算子measure_projection可以找到,但是注意,他返回的是原始的,没有经过平滑滤波的“线条”上的灰度值,注意这里的Profile只是一个元组,也就是从0开始间隔1采集到的像素值(把Profile均分为主轴长度个像素Length1*2,当然像素值要插值),我们所要找的测量点必须要在这个“线条”上,然后才能得到测量点的坐标。
4. 为了减小噪声的影响,使结果更准确,必须先对Profile进行平滑滤波smooth_funct_1d_gauss,但是前面说过Profile是离散的点,必须要先把这些点使用create_funct_1d_array连接成一个函数才可以对其进行函数操作。下图是对某图Profile像滤波过后的灰度分布(注意根据横坐标的长度可以看出ROI Length1 = 220)
5. 平滑过后预处理就结束了。要开始找测量点了,找边缘最常用的就是找梯度求导了derivate_funct_1d (fuction: Mode: Derivative),其中Mode = first表示一阶导数,second表示求二阶导数。在求导过后找点就是边缘点了,很明显导数为0的点就是边缘点。但是如果直接找0点的话,会找到很多,我们不太好筛选,所以我们选择求二阶导数找最大值来筛选。先设定一个阈值,大于这个阈值的点才被定为测量点。下图是对上图Profile Line进行一阶导和二阶导
6. get_y_value_funct_1d函数你给他提供X值它给你返回Y值,Y值就是我们要筛选的点,过滤后剩下的X值,就是我们需要的测量点的位置信息,但是这个位置是相对于Profile的,也就是从Profile开始点(Start)到测量点的距离(Position of line)。我们需要将它转换成他对应的坐标。我们已知的就是ROI的中心点坐标和ROI的长和宽。一般情况下,中心点的坐标是四舍五入的,Length1的长度是向上取整的。
RowStart =└ (Row + 0.5)┘ +└ Length1┘ · sin(Phi)
ColStart = └ (Column + 0.5)┘ −└ Length1┘ · cos(Phi)
RowLine = RowStart − PositionOfSalientLine · sin(Phi)
ColLine = ColStart + PositionOfSalientLine · cos(Phi)
Hdevelop中这样写:
RowStart := floor(Row+0.5)+floor(Length1)*sin(Phi)
ColStart := floor(Column+0.5)-floor(Length1)*cos(Phi)
RowLine = RowStart − PositionOfSalientLine · sin(Phi)
ColLine = ColStart + PositionOfSalientLine · cos(Phi)
这样每一个测量点的坐标就可以计算出来了,计算图解见下图:
dev_close_window() read_image (Image, 'D:/picture/20131005110428.jpeg') decompose3 (Image, Red, Green, Blue) rgb3_to_gray (Red, Green, Blue, ImageGray) write_image (ImageGray, 'tiff', 0, 'C:/Users/YangK/Desktop/cizi.tiff') get_image_size (ImageGray, Width, Height) dev_open_window (0, 0, Width, Height, 'black', WindowHandle) dev_set_draw ('margin') dev_set_color ('red') dev_display (ImageGray) Row := 335.5 Column := 348.5 Angle := -2.22819 Length1 := 72.0069 Length2 := 5 * Length2 := 7.63302 *draw_rectangle2_mod (WindowHandle, 100, 100, 0, 100, 50, Column1, Row2, Column2, Length1, Length2) gen_measure_rectangle2 (Row, Column, Angle, Length1, Length2, Width, Height, 'nearest_neighbor', MeasureHandle) gen_rectangle2 (Rectangle, Row, Column, Angle, Length1, Length2) measure_projection (Image, MeasureHandle, GrayValues) create_funct_1d_array (GrayValues, Function) smooth_funct_1d_gauss (Function, 0.3, SmoothedFunction) derivate_funct_1d (SmoothedFunction, 'first', FirstDerivative) derivate_funct_1d (SmoothedFunction, 'second', SecondDerivative) zero_crossings_funct_1d (FirstDerivative, ZeroCrossings) MinimumMagnitudeOfSecondDerivative := 8 PositionOfSalientLine := [] for i:=0 to |ZeroCrossings|-1 by 1 get_y_value_funct_1d (SecondDerivative, ZeroCrossings[i], 'constant', Y) if(Y > MinimumMagnitudeOfSecondDerivative) * 写成PositionOfSalientLinep[i] := ZeroCrossings[i]不可以 PositionOfSalientLine := [PositionOfSalientLine,ZeroCrossings[i]] endif endfor stop() RowStart := floor(Row+0.5)+floor(Length1)*sin(Angle) ColStart := floor(Column+0.5)-floor(Length1)*cos(Angle) RowLine := RowStart-PositionOfSalientLine*sin(Angle) ColLine := ColStart+PositionOfSalientLine*cos(Angle) NumRows := |RowLine| NumCols := |ColLine| Num := min([NumRows, NumCols]) for i := 0 to Num-1 by 1 Row := RowLine[i] Col := ColLine[i] * RowStart := Row - Length2*sin(rad(90) - Angle) * RowEnd := Row + Length2*sin(rad(90) - Angle) * ColStart := Col - Length2*cos(rad(90) - Angle) * ColEnd := Col + Length2*cos(rad(90) - Angle) RowStart := Row+Length2*cos(Angle) RowEnd := Row-Length2*cos(Angle) ColStart := Col+Length2*sin(Angle) ColEnd := Col-Length2*sin(Angle) gen_contour_polygon_xld (Marker, [RowStart,RowEnd], [ColStart,ColEnd]) dev_set_color ('white') dev_set_line_width(1) dev_display (Marker) endfor
1D测量之四
测量之1D边缘提取过程介绍
原理解释
2、计算平均灰度值
3、如何提取边缘
1D边缘提取步骤
1、通过算子gen_measure_rectangle2 生成MeasureHandle句柄,沿着切片方向计算平均灰度值(可选择插值方式,仅当轮廓线和要测量图像的边界不垂直时起作用)
2、对平均灰度值(轮廓)进行高斯滤波,目的是让曲线更平滑,消除噪点。
3、对平滑后的轮廓求一阶导数(根据Sigma x sqrt(2π)因子进行缩放)。
4、根据设置的参数提取边缘点。
1D测量之五
measure_pos
原型
measure_pos(Image : : MeasureHandle, Sigma, Threshold, Transition, Select : RowEdge, ColumnEdge, Amplitude, Distance)
功能
提取垂直于矩形或者环形弧的直边。
参数列表
Image (input_object) :输入图像
MeasureHandle (input_control) :测量对象句柄
Sigma (input_control) :高斯滤波参数
Threshold (input_control) :边缘强度控制阈值
Transition (input_control) :边界从白到黑还是从黑到白(分别对应’negative’, ‘positive’,也可以选择‘all’)
Select (input_control) :选择寻点的模式( 'all’输出所有点, 'first’输出第一个点, 'last’输出最后一个点)
RowEdge (output_control) :寻到直边的中心点行坐标
ColumnEdge (output_control) :寻到直边的中心点列坐标
Amplitude (output_control) :寻到直边的边缘强度值
Distance (output_control) :连续边之间的距离
详解
有关1D测量的概念解释参考:https://blog.csdn.net/qq_18620653/article/details/106214957
measure_pos通过计算垂直于矩形或者环形弧长轴(轮廓线)的每个切片上的平均灰度值,这些平均灰度值称为一维边界轮廓。
切片采样虽然是亚像素级别,但行列移动步长始终为1Pixel(可以用助手拖动轮廓或一阶导数折线图上的红色线条观察)。由于在生成测量对象句柄时,绝大部分计算是雷同的,为了提高Measure_Pos的测量效率,算子gen_measure_rectangle2 或者 gen_measure_arc 只需要执行一次即可(下面会举例说明)。在亚像素级别切片平均灰度值计算过程中,测量的精度和效率需要权衡。通过算子gen_measure_rectangle2 可以选择不同的插值方式(仅当轮廓线和要测量的图像边界不垂直的时候才起作用),当然不同的插值方式精度和效率不同,可以根据自己的需求舍弃某一方(Halcon 测量之1D边缘提取过程介绍(二)有说明)。通过gen_measure_rectangle2 生成测量对象句柄MeasureHandle。
在计算一维边缘轮廓之后,通过将轮廓与标准差Sigma的高斯平滑核的导数卷积来计算亚像素边缘位置(其实就是平滑轮廓,去除噪点)。紧接着,对平滑后的轮廓求一阶导数,并取其绝对值作为输出。最后,通过参数Threshold来选择强边界。需要注意的是振幅值按系数Sigma x sqrt(2π)对一阶导数进行缩放。
此外,如果Transition模式选择positive,也即是沿着矩形或者环形弧主轴方向边界是从黑到白的;相反,如果选择negative,寻边是从白到黑;如果选择‘all’,获取的边界是以上两种情况的并集。如果参数Select = ‘all’,寻到所有的点都会返回;如果Select = ‘first’,仅返回第一个点;如果Select = ‘last’,仅返回最后一个点。
提取到的边将会以坐标的方式返回,这个坐标是边与轮廓线的交点,同时边缘梯度对应输出(Amplitude);另外,连续边缘点间的距离输出到参数Distance中。这里,Distance[i]对应的是Edge[i]和Edge[i+1]之间的距离,即最终Distance 的数组长度比RowEdge以及ColumnEdge少1。
说了那么多,只怕遗漏重要内容,影响对算子的正确理解。如果仅想大致了解算子,可以参考下面简要总结:
1、通过算子gen_measure_rectangle2 生成MeasureHandle句柄,沿着切片方向计算平均灰度值(可选择插值方式,仅当轮廓线和要测量图像的边界不垂直时起作用)
2、对平均灰度值(轮廓)进行高斯滤波,目的是让曲线更平滑,消除噪点。
3、对平滑后的轮廓求一阶导数(根据Sigma x sqrt(2π)因子进行缩放)。
4、根据设置的参数提取边缘点。
如何设置参数Threshold的值
Halcon测量核心应该就是这个参数的调节,因为合适的值不仅可以提取到高精度的边界,而且程序稳定性好。
方式一:
打开测量助手,调节最小边缘幅度,保证提取到边界前提下,确定最小边缘幅度的范围,然后在范围内设置一个合适值即可。优点方便快捷,不需要熟悉测量工具运行机制,傻瓜式操作;缺点依赖Halcon软件。
式二:
结合文中以及下图解释,可以直接计算,大致确定最大边缘幅度。
首先查看要提取边界黑白界限的灰度值GrayLightValue、GrayDarkValue,求差值SubGrayValue = GrayLightValue - GrayDarkValue,其实SubGrayValue 这个值就是灰度图像某个位置的一阶导数,最后计算幅度值AmplitudeValue = SubGrayValue x Sigma x sqrt(2π) (大致值,因为过程中没有插值运算,平均灰度值也是按照一个像素宽计算的,但相差不大)优点是可以快速调试参数,且如果在现场调试可以脱离Halcon软件;缺点会有微小误差。
举例
* **测量变量初始化** * 第一个测量对象轮廓线中心点行坐标 MeasureStartRow := 70 * 第一个测量对象轮廓线中心点列坐标 MeasureStartCol := 130 * 测量对象角度(90度) MeasurePhi := 1.57079 * 测量对象长轴 MeasureLength1 := 100 * 测量对象短轴 MeasureLength2 := 10 * 寻边个数 FindEdgeNum := 37 * 加载一张图像 read_image (Image, 'ic_pin') * 显示窗口初始化 dev_close_window () dev_update_off () get_image_size (Image, ImageWidth, ImageHeight) dev_open_window_fit_image (Image, 0, 0, -1, -1, WindowHandle) dev_set_line_width (2) dev_set_draw ('margin') set_display_font (WindowHandle, 16, 'mono', 'true', 'false') * 灰度值闭运算消除小引脚 gray_closing_rect (Image, ImageClosing, 60, 60) dev_display (ImageClosing) * 生成测量对象句柄 * 易出错示例 gen_measure_rectangle2 (MeasureStartRow, MeasureStartCol, rad(0), MeasureLength2, MeasureLength1, ImageWidth, ImageHeight, 'nearest_neighbor', MeasureHandle) gen_rectangle2 (Rectangle, MeasureStartRow, MeasureStartCol, rad(0), MeasureLength2, MeasureLength1) * 正确示例 gen_measure_rectangle2 (MeasureStartRow, MeasureStartCol, MeasurePhi, MeasureLength1, MeasureLength2, ImageWidth, ImageHeight, 'nearest_neighbor', MeasureHandle) for Index := 0 to FindEdgeNum by 1 * 移动测量对象句柄 translate_measure (MeasureHandle, MeasureStartRow, MeasureStartCol + 2 * MeasureLength2 * Index) * 根据测量变量生成Rectangle2(用于可视化,可注释掉) gen_rectangle2 (Rectangle, MeasureStartRow, MeasureStartCol + 2 * MeasureLength2 * Index, MeasurePhi, MeasureLength1, MeasureLength2) dev_display (Rectangle) * 测量 measure_pos (ImageClosing, MeasureHandle, 1, 30, 'positive', 'first', RowEdge, ColumnEdge, Amplitude, Distance) * 显示寻到的边界位置点 dev_set_color ('green') disp_cross (WindowHandle, RowEdge, ColumnEdge, 20, 0) dev_set_color ('red') endfor * 关闭测量对象 close_measure (MeasureHandle)
---------------------
作者:hackpig
来源:www.skcircle.com
版权声明:本文为博主原创文章,转载请附上博文链接!

