***该实例由于锯片很薄,采用漫反射背光照明
* 该程序演示了如何通过测量图像中的角度来检查锯片的齿。
*
* 初始化一些常量
PI := rad(180)
PI_2 := rad(90)
*
* 显示初始化
dev_update_off ()
read_image (Image, 'saw_blade/saw_01')
get_image_size (Image, Width, Height)
dev_close_window ()
dev_open_window_fit_image (Image, 0, 0, 640, 640, WindowHandleMain)
set_display_font (WindowHandleMain, 16, 'mono', 'true', 'false')
get_window_extents (WindowHandleMain, WindowRow, WindowColumn, WindowWidth, WindowHeight)
dev_open_window (400, WindowWidth / 2, 300, 300, 'black', WindowHandleZoom)
dev_set_line_width (2)
*
* 主循环:处理不同的图像
for Index1 := 1 to 7 by 1 //循环所以图像
read_image (Image, 'saw_blade/saw_' + Index1$'02')
*
*通过亚像素精确的阈值获得锯的轮廓。
threshold_sub_pix (Image, Border, 128) //以亚像素精度从图像中提取crossing,而是将灰度值小于Threshold的区域与灰度值大于Threshold的区域分割线返回Border
*
* 分割,选择和分类线性轮廓零件
segment_contours_xld (Border, ContoursSplit, 'lines_circles', 5, 6, 4) //将XLD轮廓分割为线段、圆弧或椭圆弧
select_shape_xld (ContoursSplit, SelectedContours, 'contlength', 'and', 30, 200) //使用形状特征选择轮廓或多边形
count_obj (SelectedContours, Number) //计算区域的数量
gen_empty_obj (ToothSides) //生成空数组对象
for Index2 := 1 to Number by 1
select_obj (SelectedContours, SelectedContour, Index2) //循环所有对象
*获得全局轮廓属性
get_contour_global_attrib_xld (SelectedContour, 'cont_approx', Attrib) //获得全局轮廓属性,
如果Attrib =0,这一部分轮廓最适合被拟合为椭圆弧。
如果Attrib =1,这一部分轮廓最适合被拟合为圆弧。
如果Attrib =-1,这一部分轮廓最适合被拟合为-直线。
* *属性“ cont_approx”已由操作员设置
* segment_contours, 对于直线,该值为-1。
if (Attrib == -1)
concat_obj (ToothSides, SelectedContour, ToothSides) //两个区域组合到一起,生成新区域ToothSides
endif
endfor
sort_contours_xld (ToothSides, ToothSidesSorted, 'upper_left', 'true', 'column') //根据轮廓的相对位置对轮廓进行排序
*
* 计算锯齿每侧的方向
fit_line_contour_xld (ToothSidesSorted, 'tukey', -1, 0, 5, 2, Rows1, Columns1, Rows2, Columns2, Nr, Nc, Dist) //对一些线段的XLD做近似计算直线计算
fit_line_contour_xld( Contours : : Algorithm, MaxNumPoints, ClippingEndPoints, Iterations, ClippingFactor : RowBegin, ColBegin, RowEnd, ColEnd, Nr, Nc, Dist )
Contours(in):输入轮廓
Algorithm(in):形成线的算法
MaxNumPoints(in):用于计算的最大轮廓点个数
ClippingEndPoints(in):在逼近过程中被忽略的开始及末尾点个数
Iterations(in):迭代的最大次数
ClippingFactor(in):消除异常值的裁剪因子
RowBegin(out):线段开始点的行坐标
ColBegin(out):线段开始的列坐标
RowEnd(out):线段结尾的行坐标
ColEnd(out):线段结尾的列坐标
Nr(out):线参数:法向量的行坐标
Nc(out):法向量的列坐标
Dist(out):原点到该线的距离
line_orientation (Rows1, Columns1, Rows2, Columns2, Orientations) //返回直线的角度:-90到90之间
* 测量属于同一锯齿角度
Angles := []
* 检查第一颗锯齿是否完全可见。
*如果没有,请从第二颗锯齿开始测量。
if (Rows1[0] > Rows2[0]) //如果起始坐标Rows1>结束坐标 Rows2,说明是锯齿的右侧,则第一颗锯齿没有左侧,第一颗锯齿可以跳过不计算了
Start := 1
else
Start := 2
endif
for Index2 := Start to |Orientations| - 1 by 2
Angle := abs(Orientations[Index2 - 1] - Orientations[Index2])
* 确保角度在[0,PI / 2]度的范围内。
if (Angle > PI_2)
Angle := PI - Angle
endif
*
* 显示结果
dev_display_tooth (Image, ToothSides, Index2, Rows1, Columns1, Rows2, Columns2, WindowHandleMain, WindowHandleZoom)
dev_set_window (WindowHandleMain)
dev_disp_text ('Tooth ' + (Index2 + 1) / 2, 'window', 10, 10, 'black', [], [])
dev_disp_text ('Included Angle: ' + deg(Angle)$'.2f' + ' deg', 'window', 35, 10, 'black', [], [])
dev_disp_text ('Press Run (F5) to continue', 'window', 'bottom', 'right', 'black', [], [])
Angles := [Angles,Angle]
stop ()
endfor
endfor
*
dev_set_window (WindowHandleMain)
dev_disp_text (' End of program ', 'window', 'bottom', 'right', 'black', [], [])
dev_set_window (WindowHandleZoom)
dev_close_window ()
值得借鉴:
1.将锯齿从背景中分割,使用亚像素精度算法。通常有两种亚像素精度算法:亚像素轮廓精度提取算法(边缘提取)、亚像素阈值分割算法(阈值分割),亚像素阈值分割算法适合此例子(背光照射使得背景为白色、锯齿为黑色),阈值采用均值128。
2.将每个锯齿分割,使用segment_contours_xld将轮廓分割为线段、直线以及椭圆,并出去过长、过短的线段。
3.进一步除去锯齿间的圆弧 get_contour_global_attrib_xld
4.从左至右 对轮廓排序,并使用fit_line_contour_xld 对直线拟合
5.计算拟合直线的方向,line_orientation得到的直线方向范围是[-90,90]
6.通过相邻两侧拟合直线(同一锯齿)相减, 计算每个锯齿的角度
计算锯齿没测的方向,直接的方法是图(b)用过轮廓起点、终点的直线角度,但锯齿顶部不一定是理想的尖端,因此用轮廓线上的所有点(拟合直线)更好。
图(c)可以看出,亚像素精度下,拟合直线的角度与真实直线的角度(a)几乎相等。