流程:
实现流程:
1》采集图像
2》预处理(RoI) :缩小干扰的区域
3》亚像素的边缘提取(xld)
edges_sub_pix 边缘提取算子
threshold_sub_pix 边缘提取算子(基于像素)
中心线 : line_gauss line_color line_facet
区域和xld的互转算子
zero_crossing_sub_pix 不能处理灰度图,只能处理衍生高斯 差分高斯图
4》筛选、分割、联合
筛选
轮廓的分割算子 :segment_contours_xld
联合算子: 有三个(视情况而定)
union_collinear_contours_xld 共线联合
union_colse_contours_xld 闭合联合
union_colse_contours_xld 邻近联合
union_cocircular_contours_xld共圆联合
5》 拟合
最小二乘法: fit_line_contour_xld() 对亚像素拟合直线的函数
fit_circle_contour_xld()对亚像素拟合圆的函数
fit_ellipse_contour_xld()对亚像素拟合圆的函数
fit_rectangle2_contour_xld()对亚像素拟合j矩形的函数
6》 计算(像素值)
7》 引入内参和外参将像素值转换成射界坐标(mm)
一、边缘提取
亚像素:
面阵相机的成像面的最小单位是像素,例如某芯片的像素间距为5.2微米,在相机机拍摄时,将物理世界中连续的图像进行了离散化处理;到成像面上每一个像素点只代表其附近的颜色,至于“附近”到什么程度?就很困难解释。两个像素之间有5.2微米的距离,在宏观上可以看作是连在一起的,但是在微观上,它们之间还有无限的更小的东西存在,这个更小的东西我们称它为“亚像素”;实际上“亚像素”应该是存在的,只是硬件上没有个细微的传感器把它检测出来。于是软件上把它近似地计算出来。
亚像素的精度:
亚像素精度是指相邻两像素之间细分情况,输入值通常为二分之一,三分之一或四分之一。这意味着每个像素将被分为更小的单元从而对这些更小的单元实施插值算法。例如,如果选择四分之一,就相当于每个像素在横向和纵向上都被当作四个像素来计算。
亚像素的应用:
在机器视觉中,亚像素是一个比较常见的概念,在许多函数中,都可以选择是否使用亚像素,而在测量中,如位置、直线、圆等,都会出现亚像素。如测量某个圆的直径为100.12像素。这个后面的0.12就是亚像素。因为从像素中可以理解到,工业相机最小物理单元其实就是像素,但是在机器视觉测量中还是会得到小数点后的值,这个就是通过软件计算得到的,其实在真实情况下,并不一定是十分准确的。这个值通常在灰度图中会更容易反应出来,而在二值图像中,因为值只有0,1。因此很多函数并不一定会计算亚像素。
边缘检测定义:
边缘检测是图形图像处理、计算机视觉和机器视觉中的一个基本工具,通常用于特征提取和特征检测,旨在检测一张数字图像中有明显变化的边缘或者不连续的区域,在一维空间中,类似的操作被称作步长检测(step detection)。边缘是一幅图像中不同屈原之间的边界线,通常一个边缘图像是一个二值图像。边缘检测的目的是捕捉亮度急剧变化的区域,而这些区域通常是我们关注的。在一幅图像中两度不连续的区域通常是以下几项之一:
(1)图像深度不连续处
(2)图像(梯度)朝向不连续处
(3)图像光照(强度)不连续处
(4)纹理变化处
理想情况下,对所给图像应用边缘检测器可以得到一系列连续的曲线,用于表示对象的边界。因此应用边缘检测算法所得到的结果将会大大减少图像数据量,从而过滤掉很多我们不需要的信息,留下图像的重要结构,所要处理的工作即被大大简化。然而,从普通图片上提取的边缘往往被图像的分割所破坏,也就是说,检测到的曲线通常不是连续的,有一些边缘曲线段开,就会丢失边缘线段,而且会出现一些我们不感兴趣的边缘。这就需要边缘检测算法的准确性
edges_sub_pix (ImageROI, Edges, 'lanser2', 0.3, 10, 30) 利用Deriche、Lanser、Shen和Canny滤波器提取亚像素精度边缘
提取图像精确边缘 (精确边缘就是亚像素边缘) 一阶导数 (10,30,这两个越小,边缘越多,越细腻,越大,边缘越少)
Image: // 输入图像
Edges: //输出边缘轮廓
Filter: //边缘提取算法的名称 (canny,lanser1 )
Alpha: // 高斯平滑系数, 0.3 参数指定值越小,平滑越强大,会减少边缘细节。(canny刚好相反,值越大,边缘细节越少,当有多段边缘的时候可以调大这个值来是的其平滑)
Low: // 低阈值 (边缘幅度值 像素)
High: // 高阈值 (边缘幅度值 像素)
canny的原理: 1.高斯平滑滤波
2.利用sobel其梯度(即有大小,又有方向)
3.费极大值抑制(越大 越小)
4.高低阈值控制(如果线是段的,要连起来,可以将这个值变大)中间看8领域
滞后性:简单的说就是延迟,落后;一个现象与另一密切相关的现象相对而言的落后迟延,尤其指物理上的果没有及时跟着因而出现;
滞后性阈值的描述:
使用边缘滤波,得到的边缘都是大于一个像素的轮廓,因此要对所得到的图像进行骨架化,从而得到比较清晰的边缘轮廓。有时候还需要进行非最大抑制处理
这样,经过先对边缘幅度进行阈值分割,然后对分割出的区域进行骨架化处理,再进行非最大抑制处理,一般就可得到清晰的边缘。但是,有时候我们选择高的阈值以保证只将相关边缘选出时,边缘通常被割裂成诺干段;另一方面,如果选择低的阈值以保证边缘不会断裂成一段一段时,我们最终的分割结果中又会包含很多不相关边缘。针对这种情况,Canny提出来一种特殊的阈值分割算法来分割边缘:滞后阈值分割。
滞后阈值分割使用两个阈值----高阈值和低阈值。边缘幅度比高阈值大的那些点立即作为安全边缘点被接受。边缘幅度比低阈值小的那些点被立即剔除。边缘幅度在高阈值和低阈值之间的那些点按如下原则处理:只有在这些点能按某一路径和安全边缘点相连时,他们才作为边缘点被接受。组成这一路径的所有点的边缘幅度都比低阈值要大。我们也能把这个过程理解为,首先边缘幅度大于高阈值的所有边缘点,然后在边缘幅度大于低阈值的情况下尽可能延长边缘。
边缘检测算法基本都是基于微分的数学基础之上的。一般都是先对图像进行滤波处理,然后再进行阈值分割。由于一阶微分只需要使用一个滤波器就可以满足要求,因此一般使用一阶微分来读取边缘的。由于有的时候剖面上噪声太多,影响取图质量,所以要对剖面图的图像进行滤波处理。这里就涉及到两种卷积运算:一种是用于图像平滑处理的滤波器卷积计算;另一种则是用于对图像进行求导的滤波器卷积计算。
边缘滤波器选取有三个准则:
一是边缘滤波器产生的输出信噪比要最大化,这样可以对一个边缘点的错检和漏检可能性要低;
二是提取出来的位置方差要最小化,这样提取出来的边缘更靠近真正的边缘;
三是提取出来的边缘位置之间的距离要最大化,这样边缘检测器对每个真正的边缘只返回唯一的一个边缘,可以避免多重响应
二、轮廓分割
segment_contours_xld(Contours : ContoursSplit : Mode, SmoothCont, MaxLineDist1, MaxLineDist2 : )
*Contours Edges:待分割的轮廓
*ContoursSplit:分割后的轮廓
* Mode :
* 如果Mode='lines'则将输入轮廓分割成线条,
* 如果Mode='lines_circles'则将其分割成线条和圆弧,
* 如果Mode='lines_ellipses'则将其分割成线条和椭圆弧;
*SmoothCont 平滑轮廓系数 不能等于0 最好大于等于3并且是奇数 ,建议值是5
*MaxLineDist1 轮廓线和近似线之间的最大距离(第一次迭代) 最大线距需要大于等于0.0;
*MaxLineDist2 轮廓线和近似线之间的最大距离(第二次迭代) 最小线距需要大于等于0.0
描述:segment_contours_xld在分割时,
如果Mode='lines'则将输入轮廓分割成线条,
如果Mode='lines_circles'则将其分割成线条和圆弧,
如果Mode='lines_ellipses'则将其分割成线条和椭圆弧;
(1)、segment_contours_xld首先通过折线来逼近输入的轮廓,这样,在弯曲的地方轮廓就会被过度的分割,如果用圆弧可以更好地逼近轮廓,则用圆弧或椭圆弧分别代替相邻的线段。如果SmoothCont设置为> 0,则首先对输入的轮廓进行平滑,这是必要的,因为平滑抑制了轮廓上的异常值,所以一方面可以防止在分割特别短的线的时后带来的异常,另一方面,在使用圆或椭圆分割时,可以实现更稳健的分割;
(2)、最初的折线逼近是使用Ramer算法通过MaxLineDist1的最大距离来完成的,在此之后,圆形或椭圆形的弧被匹配到相邻的线段中,如果所产生的弧线到轮廓线的最大距离小于两条线段的最大距离,两个线段被替换为圆弧,迭代此过程,直到不再发生更改;
(3)、在此之后,仍然由线段逼近的轮廓部分再次用最大距离MaxLineDist2的多边形逼近进行分割,并且新创建的线段在可能的情况下合并为圆形或椭圆弧。显然,这只会在MaxLineDist2 < MaxLineDist1时更改输出,这种两步方法比使用MaxLineDist2的一步方法更有效,由于在第一步中生成的线段较少,因此必须较少地进行圆或椭圆拟合。因此,使用长圆弧逼近部分输入轮廓会更高效;之后,再用短圆弧去逼近输入的轮廓,最后再细化使用过长圆弧逼近的轮廓的末端;
(4)、所述所得轮廓长度至少为3像素,并且包含所述输入轮廓的至少6个连续点;所有输入的长度小于3像素或少于6个轮廓点的轮廓线将被复制到输出轮廓线,不做任何修改。
参数设置注意事项:
SmoothCont (input_control)
(1)、不能等于0;
(2)、最好大于等于3并且是奇数;
(3)、建议值是5;
MaxLineDist1 (input_control) MaxLineDist2 (input_control)
(1)、需要大于等于0.0;
MaxLineDist1这个第一次分割,MaxLineDist2是第二次分割,第一次分割为主,第二次在第一次的基础上逼近理想值)
联合算子: 有三个(视情况而定)
union_collinear_contours_xld 共线联合
union_colse_contours_xld 闭合联合
union_colse_contours_xld 邻近联合
union_cocircular_contours_xld共圆联合
三、拟合
最小二乘法:
fit_line_contour_xld() 对亚像素拟合直线的函数
fit_circle_contour_xld()对亚像素拟合圆的函数
fit_ellipse_contour_xld()对亚像素拟合圆的函数
fit_rectangle2_contour_xld()对亚像素拟合j矩形的函数
*get_contour_global_attrib_xld(Contour : : Name : Attrib)获得轮廓的全局属性
* 属性名(Name)包含如下:
* 'regr_norm_row', 回归线单位法向量的行坐标,法向量从原点指向该线
* 'regr_norm_col', 回归线单位法向量的列坐标,法向量从原点指向该线。
* 'regr_mean_dist', 包含每个等高线点到回归线之间的欧式距离的平均值
* 'regr_dev_dist', 轮廓线点与回归线之间(欧几里得)距离的标准差
* 'cont_approx', 如果'cont_approx'=-1,这一部分轮廓最适合被拟合为直线段。
* 如果'cont_approx'=0,这一部分轮廓最适合被拟合为椭圆弧。
* 如果'cont_approx'=1,这一部分轮廓最适合被拟合为圆弧。
* 'bright_dark', 建立整流网各网格点之间的连接
* 'is_hole' 判断是否是包含孔的边界,'is_hole'设置为1,是包含孔的边界;否则设置为0
* 如果Attrib是 -1 是适合拟合成直线,
* 1 说明适合拟合成圆 ,
* 0说明适合拟合出椭圆
measure_metal_part
* measure_metal_part.hdev: inspects metal part by fitting lines and circles
*
dev_close_window ()
dev_update_window ('off')
* ****
* step: acquire image
* ****
read_image (Image, 'metal-parts/metal-parts-01')
get_image_size (Image, Width, Height)
dev_open_window_fit_image (Image, 0, 0, Width, Width, WindowID)
set_display_font (WindowID, 14, 'mono', 'true', 'false')
dev_set_draw ('margin')
dev_set_line_width (3)
dev_display (Image)
disp_continue_message (WindowID, 'black', 'true')
stop ()
* ****
* step: create contours
* ****
* edges_sub_pix :边缘提取算子:利用Deriche、Lanser、Shen和Canny滤波器提取亚像素精度边缘
*Image: 输入图像
*Edges: 输出边缘轮廓
*Filter: 边缘提取算法的名称 (canny,lanser1 )
*Alpha: 高斯平滑系数, 0.3 参数指定值越小,平滑越强大,会减少边缘细节。(canny刚好相反,值越大,边缘细节越少,当有多段边缘的时候可以调大这个值来是的其平滑)
*Low: 低阈值 (边缘幅度值 像素)
*High: 高阈值 (边缘幅度值 像素)
edges_sub_pix (Image, Edges, 'canny', 1.9, 40, 90)
dev_display (Edges)
disp_continue_message (WindowID, 'black', 'true')
stop ()
* ****
* step: process contours
* ****
segment_contours_xld (Edges, ContoursSplit, 'lines_circles', 6, 4, 4)
*segment_contours_xld(Contours : ContoursSplit : Mode, SmoothCont, MaxLineDist1, MaxLineDist2 : )
*Contours Edges:待分割的轮廓
*ContoursSplit:分割后的轮廓
* Mode :
* 如果Mode='lines'则将输入轮廓分割成线条,
* 如果Mode='lines_circles'则将其分割成线条和圆弧,
* 如果Mode='lines_ellipses'则将其分割成线条和椭圆弧;
*SmoothCont 平滑轮廓系数 不能等于0 最好大于等于3并且是奇数 ,建议值是5
*MaxLineDist1 轮廓线和近似线之间的最大距离(第一次迭代) 最大线距需要大于等于0.0;
*MaxLineDist2 轮廓线和近似线之间的最大距离(第二次迭代) 最小线距需要大于等于0.0;
sort_contours_xld (ContoursSplit, SortedContours, 'upper_left', 'true', 'column')
dev_clear_window ()
dev_set_colored (12)
dev_display (SortedContours)
disp_continue_message (WindowID, 'black', 'true')
stop ()
* ****
* step: perform fitting
* ****
ROI := [115,225,395,535]
dev_open_window (0, round(Width / 2), (ROI[3] - ROI[1]) * 2, (ROI[2] - ROI[0]) * 2, 'black', WindowHandleZoom)
dev_set_part (round(ROI[0]), round(ROI[1]), round(ROI[2]), round(ROI[3]))
set_display_font (WindowHandleZoom, 14, 'mono', 'true', 'false')
count_obj (SortedContours, NumSegments)
dev_display (Image)
NumCircles := 0
NumLines := 0
for i := 1 to NumSegments by 1
select_obj (SortedContours, SingleSegment, i)
*get_contour_global_attrib_xld返回XLD轮廓的全局属性值
get_contour_global_attrib_xld (SingleSegment, 'cont_approx', Attrib)
*get_contour_global_attrib_xld(Contour : : Name : Attrib)获得轮廓的全局属性
* 属性名(Name)包含如下:
* 'regr_norm_row', 回归线单位法向量的行坐标,法向量从原点指向该线
* 'regr_norm_col', 回归线单位法向量的列坐标,法向量从原点指向该线。
* 'regr_mean_dist', 包含每个等高线点到回归线之间的欧式距离的平均值
* 'regr_dev_dist', 轮廓线点与回归线之间(欧几里得)距离的标准差
* 'cont_approx', 如果'cont_approx'=-1,这一部分轮廓最适合被拟合为直线段。
* 如果'cont_approx'=0,这一部分轮廓最适合被拟合为椭圆弧。
* 如果'cont_approx'=1,这一部分轮廓最适合被拟合为圆弧。
* 'bright_dark', 建立整流网各网格点之间的连接
* 'is_hole' 判断是否是包含孔的边界,'is_hole'设置为1,是包含孔的边界;否则设置为0
* 如果Attrib是 -1 是适合拟合成直线,
* 1 说明适合拟合成圆 ,
* 0说明适合拟合出椭圆
if (Attrib == 1)
NumCircles := NumCircles + 1
*拟合圆
fit_circle_contour_xld (SingleSegment, 'atukey', -1, 2, 0, 5, 2, Row, Column, Radius, StartPhi, EndPhi, PointOrder)
* 生产椭圆的
gen_ellipse_contour_xld (ContEllipse, Row, Column, 0, Radius, Radius, 0, rad(360), 'positive', 1.0)
dev_set_color ('white')
dev_display (ContEllipse)
set_tposition (WindowHandleZoom, Row - Radius - 10, Column)
write_string (WindowHandleZoom, 'C' + NumCircles)
ResultText := 'C' + NumCircles + ': radius = ' + Radius
else
NumLines := NumLines + 1
fit_line_contour_xld (SingleSegment, 'tukey', -1, 0, 5, 2, RowBegin, ColBegin, RowEnd, ColEnd, Nr, Nc, Dist)
gen_contour_polygon_xld (Line, [RowBegin,RowEnd], [ColBegin,ColEnd])
dev_set_color ('yellow')
dev_display (Line)
distance_pp (RowBegin, ColBegin, RowEnd, ColEnd, Length)
set_tposition (WindowHandleZoom, (RowBegin + RowEnd) / 2 - Nr * 10, (ColBegin + ColEnd) / 2)
write_string (WindowHandleZoom, 'L' + NumLines)
ResultText := 'L' + NumLines + ': length = ' + Length
endif
set_tposition (WindowHandleZoom, 275 + i * 10, 230)
write_string (WindowHandleZoom, ResultText)
endfor
disp_continue_message (WindowID, 'black', 'true')
stop ()
dev_set_window (WindowHandleZoom)
dev_close_window ()
dev_clear_window ()