“劳动力阶级,永不为奴!”
清朝三百年,将国人的奴性打磨的根深蒂固,伟人毛主席将反抗的火种播撒至每个人的心中,随着经济的高速发展,一些资产违背国家意愿,逐渐形成联盟,劳动力阶级一定要摆脱轮回的宿命。
1. 边缘检测原理
测量直线或者曲线的前提,是进行边缘检测和轮廓提取。
边缘一般是指图像灰度值变化剧烈的位置。沿着如下左图水平线的位置的灰度值分布函数f(x),如下右图所示。
可知,在边缘的地方,灰度值变化非常明显。为了获取准确且唯一的边缘,那么变化最剧烈的地方就是边缘所在位置,对灰度值分布函数求一阶
导f '(x)。
可知,变化最剧烈的位置就是其一阶导的局部最大值,一阶导最大时,其二阶导f''(x)等于零,如下图所示。
可知,边缘点的位置位于原图f(x)拐点处。以上提取到的是沿着水平线方向检测到的边缘点,属于一维边缘检测。我们根据此原理,可以推导出检测各种曲线的二维边缘是由检测到的一维边缘点组合而成。
因为图像在计算机中存储是离散数字图像,求导过程等效于差分运算,即
f '(x) = f(x 1) - f(x)
提出系数 [-1, 1] 作为滤波模板,跟原图 f(x) 做卷积运算就可以检测边缘了。
2. 几种常见的边缘滤波检测滤波器
为了排除噪声,更准确的检测边缘,一些学者在差分过程进行一些改进,例如Sboel滤波器,使用f '(x) = f(x 1) - f(x - 1) 近似计算一阶差分,还有其他的二阶滤波器Laplace,以及非常稳定可靠,优势很大的Canny滤波器。
重点讨论Canny滤波器,在边缘检测应用中,一般优先考虑Canny滤波器。
Canny计算过程
1) 使用高斯滤波器,以平滑图像,滤除噪声。
2) 一阶差分偏导计算梯度值和方向。
3) 应用非极大值抑制,以消除边缘检测带来的杂散响应。
4) 应用双阈值检测来确定真实的和潜在的边缘。
5) 通过抑制孤立的弱边缘最终完成边缘检测。
其中第3)步骤中,所谓非极大值抑制,就是将第2)中一阶差分算出来的梯度值,把不是极值的点,全部置0,去掉了大部分弱的边缘,会使得图像边缘变得很细。
对于第4)步,在非极大值抑制后,基本可以准确表示实际边缘了,不过可能会仍然存在噪声或者灰度变化引起的一些边缘杂散像素。为了解决此问题,使用高低双梯度阈值过滤像素。例如双阈值t1,t2,t1为低阈值,t2位高阈值,大于 t2 的点肯定是边缘,为强边缘,在 t1, t2 之间的点,为弱边缘,小于 t1 的点肯定不是边缘。
对于第5)步,弱边缘像素为潜在的边缘,因为这些像素可以从真实边缘提取也可以是因噪声或灰度变化引起的。通过查看弱边缘像素及其8个邻域像素,只要其中一个为强边缘像素,则该弱边缘点就可以保留为真实的边缘。
Halcon边缘提取用到的滤波器
halcon中经常使用算子edges_sub_pix进行亚像素的边缘提取。其用到的滤波器有Deriche, Lanser, Shen, or Canny filters。
关于这几个滤波器的对比,帮助文档有如下介绍。
区别如图中红线所示。
1) Deriche, Lanser, Shen为递归滤波器,Canny 为掩膜滤波器;
2)递归滤波器的执行时间不依赖滤波器的大小,Canny的执行时间与滤波器大小成正相关。
3)参数alpha数值越大,Deriche, Lanser, Shen滤波器宽度越小,平滑越差,细节越突出,而Canny效果相反。
对于Deriche, Lanser, Shen滤波器的原理并不了解,感兴趣的话可以参考递归高斯滤波器的原理,所谓递归就是前者的输出参入后者的输入。
递归高斯滤波器的有关论文 “Recursive Gaussian filters”
论文地址:
http://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=0305FFA9D15D51F30408377A3FB162EC?doi=10.1.1.217.5408&rep=rep1&type=pdf
2. Halcon一维测量相关算子
gen_measure_rectangle2( : : Row, Column, Phi, Length1, Length2, Width, Height, Interpolation : MeasureHandle)
名字:生成仿射矩形区域测量句柄
描述:用于提取垂直于仿射矩形的直边
参数:
Row:仿射矩形中心行坐标
Column:仿射矩形中心列坐标
Phi:仿射矩形的纵轴水平角,单位弧度
Length1:仿射矩形宽度的一半
Length2:仿射矩形高度的一半
Width:图像的宽度
Height:图像的高度
Interpolation :插值类型
MeasureHandle:测量对象句柄
gen_measure_arc( : : CenterRow, CenterCol, Radius, AngleStart, AngleExtent, AnnulusRadius, Width, Height, Interpolation : MeasureHandle)
名字:生成环形区域测量句柄
描述:用于提取垂直环形圆弧的直边缘。
参数:
CenterRow:圆弧中心行坐标
CenterCol:圆弧中心列坐标
Radius:圆弧半径
AngleStart:圆弧起始角度
AngleExtent:圆弧角度范围
AnnulusRadius:环形带的半径(宽度的一半)
Width:图像的宽度
Height:图像的高度
Interpolation :插值类型
MeasureHandle:测量对象句柄
测量矩形和测量圆弧的参数可视化如下图:
measure_pairs(Image : : MeasureHandle, Sigma, Threshold, Transition, Select : RowEdgeFirst, ColumnEdgeFirst, AmplitudeFirst, RowEdgeSecond, ColumnEdgeSecond, AmplitudeSecond, IntraDistance, InterDistance)
名字:测量边缘对
描述:提取垂直于仿射矩形或环形圆弧的直边缘对。
参数:
Image:输入图像
MeasureHandle:测量对象句柄
Sigma:高斯平滑参数
Threshold:最小边缘幅度
Transition:边缘对极性,第一个与第二个相反
Select :选择边缘对
RowEdgeFirst:边缘点对的第一个边缘的中心行坐标
ColumnEdgeFirst:边缘点对的第一个边缘的中心列坐标
AmplitudeFirst:第一个边缘的幅度
RowEdgeSecond:第二个边缘中心行坐标
ColumnEdgeSecond:第二个边缘中心列坐标
AmplitudeSecond:第二个边缘幅度
IntraDistance:两个边缘对之间的距离
InterDistance:相邻边缘对之间的距离
measure_pos(Image : : MeasureHandle, Sigma, Threshold, Transition, Select : RowEdge, ColumnEdge, Amplitude, Distance)
名字:测量边缘对
描述:提取垂直于仿射矩形或圆弧的直边缘。
参数:
Image:输入图像
MeasureHandle:测量对象句柄
Sigma:高斯平滑系数
Threshold:最小边缘幅度
Transition:极性
Select:边缘选择
RowEdge:找到的边缘中心的行坐标
ColumnEdge:找到的边缘中心列坐标
Amplitude:边缘幅度
Distance:相邻边缘之间的距离
measure_pairs与measure_pos的区别:一般,measure_pairs可以用于许多组边缘对的情况,measure_pos用于一组边缘对的情况。
幅度是指边缘处明暗灰度各自的均值差,如下图所示。
3. Halcon一维测量原理
像点到点的距离,边缘对的距离等沿着一维方向的测量都属于1D测量范畴。Halocn的一维测量首先构建矩形或者扇形的ROI测量对象,然后在ROI内画出等距离的、长度与ROI宽度一致的、垂直于ROI的轮廓线(profile line)的等距线。如下图所示。
测量ROI的轮廓线尽量与被测边缘垂直,宽度适当宽些,等距线的密集度考虑速度与精度综合选择,这样可以减少噪声。
然后,沿着垂直轮廓线的方向,计算出每一条等距线的平均灰度值,可以得出轮廓线的灰度直方图,同时可以选择使用高斯滤波器平滑灰度直方图。如下图所示。Halcon算子measure_pairs的参数Sigma指定了高斯滤波器的标准差。
最后,求出平滑灰度直方图的一阶导数,一阶导数的极值点作为边缘的亚像素精度候选点,只有一阶导数极值点的绝对值大于预先设定的阈值(测量算子的参数Threshold)边缘候选点才被选作为边缘中心点。Halcon测量算子最后得到每一条边缘与轮廓线的交点。
4. 一维测量算法流程
5. 实例分析1-- 测量保险丝的宽度
完整源码如下:
代码语言:javascript复制*公众号:机器视觉那些事儿*
*1. 算法功能:测量保险丝宽度
*2. 算法思路:
* (1)获取图像
* (2)使用gen_measure_rectangle2 创建测量区域
* (3)使用measure_pairs 检测边缘对
* (4)结果显示
* (5) 清除测量句柄
dev_update_window ('off')
dev_close_window ()
* ****
* (1)获取图像
* ****
read_image (Fuse, 'fuse')
get_image_size (Fuse, Width, Height)
dev_open_window_fit_image (Fuse, 0, 0, Width, Height, WindowID)
set_display_font (WindowID, 12, 'mono', 'true', 'false')
dev_set_draw ('margin')
dev_set_line_width (3)
dev_display (Fuse)
set_display_font (WindowID, 12, 'mono', 'true', 'false')
disp_continue_message (WindowID, 'black', 'true')
stop ()
* ****
* (2) 使用gen_measure_rectangle2 创建测量区域
* ****
* -> 指定测量ROI
Row := 297
Column := 545
Length1 := 80
Length2 := 10
* 旋转90度
Angle := rad(90)
*Length1 为矩形宽 Length2为矩形高
gen_rectangle2 (ROI, Row, Column, Angle, Length1, Length2)
* -> 创建测量区域对象
gen_measure_rectangle2 (Row, Column, Angle, Length1, Length2, Width, Height, 'bilinear', MeasureHandle)
dev_display (ROI)
disp_continue_message (WindowID, 'black', 'true')
stop ()
* ****
* (3) 使用measure_pairs 检测边缘对
* ****
measure_pairs (Fuse, MeasureHandle, 1, 1, 'negative', 'all', RowEdgeFirst, ColumnEdgeFirst, AmplitudeFirst, RowEdgeSecond, ColumnEdgeSecond, AmplitudeSecond, IntraDistance, InterDistance)
disp_continue_message (WindowID, 'black', 'true')
stop ()
* ****
* (4) 显示结果
* ****
for i := 0 to |RowEdgeFirst| - 1 by 1
gen_contour_polygon_xld (EdgeFirst, [-sin(Angle rad(90)) * Length2 RowEdgeFirst[i],-sin(Angle - rad(90)) * Length2 RowEdgeFirst[i]], [cos(Angle rad(90)) * Length2 ColumnEdgeFirst[i],cos(Angle - rad(90)) * Length2 ColumnEdgeFirst[i]])
gen_contour_polygon_xld (EdgeSecond, [-sin(Angle rad(90)) * Length2 RowEdgeSecond[i],-sin(Angle - rad(90)) * Length2 RowEdgeSecond[i]], [cos(Angle rad(90)) * Length2 ColumnEdgeSecond[i],cos(Angle - rad(90)) * Length2 ColumnEdgeSecond[i]])
dev_set_color ('cyan')
dev_display (EdgeFirst)
dev_set_color ('magenta')
dev_display (EdgeSecond)
dev_set_color ('blue')
if (i == 0)
set_tposition (WindowID, RowEdgeFirst[i] 5, ColumnEdgeFirst[i] 20)
else
set_tposition (WindowID, RowEdgeFirst[i] - 40, ColumnEdgeFirst[i] 20)
endif
write_string (WindowID, 'width: ' IntraDistance[i] ' pix')
endfor
disp_continue_message (WindowID, 'black', 'true')
stop ()
* ****
* (5) 清除测量句柄
* ****
close_measure (MeasureHandle)
dev_update_window ('on')
dev_clear_window ()
实例分析2-- 测量铸造零件的孔间距
完整源码如下:
代码语言:javascript复制*公众号:机器视觉那些事儿
*1. 算法功能:测量铸造零件的孔间距
*2. 算法思路:
* (1)使用gen_measure_arc 创建圆弧测量区域
* (2)使用measure_pos测量垂直弧线的边缘点,再利用distance_pp求得孔间距
*读图,初始化
read_image (Zeiss1, 'zeiss1')
get_image_size (Zeiss1, Width, Height)
dev_close_window ()
dev_open_window (0, 0, Width / 2, Height / 2, 'black', WindowHandle)
set_display_font (WindowHandle, 14, 'mono', 'true', 'false')
dev_display (Zeiss1)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
*指定圆弧ROI
* draw_circle (WindowHandle, Row, Column, Radius)
Row := 275
Column := 335
Radius := 107
AngleStart := -rad(55)
AngleExtent := rad(170)
dev_set_draw ('fill')
dev_set_color ('green')
dev_set_line_width (1)
*获取椭圆指定角度的点坐标
get_points_ellipse (AngleStart AngleExtent, Row, Column, 0, Radius, Radius, RowPoint, ColPoint)
*显示圆弧
disp_arc (WindowHandle, Row, Column, AngleExtent, RowPoint, ColPoint)
dev_set_line_width (3)
* (1)使用gen_measure_arc 创建圆弧测量区域
gen_measure_arc (Row, Column, Radius, AngleStart, AngleExtent, 10, Width, Height, 'nearest_neighbor', MeasureHandle)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
* (2)使用measure_pos测量弧线的点得出孔间距离
*测量10次求平均时间
count_seconds (Seconds1)
n := 10
for i := 1 to n by 1
measure_pos (Zeiss1, MeasureHandle, 1, 10, 'all', 'all', RowEdge, ColumnEdge, Amplitude, Distance)
endfor
count_seconds (Seconds2)
Time := (Seconds2 - Seconds1) / n
disp_continue_message (WindowHandle, 'black', 'true')
* stop ()
*计算孔间距离
distance_pp (RowEdge[1], ColumnEdge[1], RowEdge[2], ColumnEdge[2], IntermedDist)
* dev_display (Zeiss1)
dev_set_color ('red')
* disp_circle (WindowHandle, RowEdge, ColumnEdge, RowEdge - RowEdge 1)
disp_line (WindowHandle, RowEdge[1], ColumnEdge[1], RowEdge[2], ColumnEdge[2])
dev_set_color ('yellow')
disp_message (WindowHandle, 'Distance: ' IntermedDist, 'image', 250, 80, 'yellow', 'false')
* dump_window (WindowHandle, 'tiff_rgb', 'C:\Temp\zeiss_result')
*清除测量句柄
close_measure (MeasureHandle)
dev_set_line_width (1)
* disp_continue_message (WindowHandle, 'black', 'true')
stop ()
dev_clear_window ()
注:关于halcon的1D测量方面更多内容,可以参考其再带的PDF文档。打开帮助文档,搜索“measure”,如下图,1D、2D甚至3D的都有详细文档。