第三章 标定与测量(单相机)
测量是把像素距离转换成实际距离,拟合则是放在缺陷检测上,把目标形状进行几何化处理,从而直接从几何参数中进行数学测量
测量往往需要相机参数的标定,就是相机的内参和外参,根据这个参数来把像素距离转换成实际距离,⚠️测量往往会先进行图片畸变矫正
第一节 标定
1.1标定板
在相机实际项目中,我们会用到各种各样的标定板,如棋盘格标定板或圆形阵列标定板,如下图所示:
标定板的材质一般是玻璃、卡纸、塑料等
标定的尺寸也有不同,往往是标定板圆点的圆心之间的距离是4mm,一般是7*7的阵列
标定板具有如下特点:
- 一般标定板需要充满相机视野的1/3或1/4
- 标定板成像灰度值要大于128
- 拍摄标定板时,最好旋转标定板,拍9-16张不同旋转角度的标定板
- 标定板的拍摄数量和后续测量精度没有关系
1.2单相机标定
单相机标定的目的: 畸变矫正和一位二维图像测量(主要是1个像素代表的实际尺寸).
标定之所以很重要,是因为相机成像后往往会产生畸变和缩放,因此需要建立一个模型根据图像信息构造出物体的三维空间数据,那么就需要摄像机的几何参数和光学参数,以及摄像机相对于世界坐标系的方位.
在计算机进行测量时,首先要完成摄像机标定,即相机标定,所谓的相机标定就是确定相机的内参和外参.内参是指相机的固有属性,外参是指物体产生的畸变等
上述的世界坐标系就是几种常用坐标系的一个
名称/表示法 | 功能 |
图像像素坐标系/(u,v) | 以像素为单位,图像的左上点为原点的图像坐标系 |
图像物理坐标系/(x,y) | 以mm为单位,物理单位表示图像像素位置,坐标原点在相机和图像平面的交点 |
相机坐标系/(Xc,Yc,Zc) | 以mm为单位,以相机的光心作为原点 ,Zc轴和光轴重合,垂直于成像平面,摄像方向为正方向,Xc和Yc轴和图像物理坐标系平行 |
世界坐标系/(Xw,Yw,Zw) | 该坐标系描述环境中任何物体的位置,满足右手法则 |
图像像素坐标系和物理坐标系之间的关系,其实物理坐标系是实际物理尺寸坐标系,假设我们的图像像素坐标系坐标系原点在左上角,而物理坐标系在图像中心,假设:每个像素在u轴和v轴方向上的物理尺寸是dx和dy,那么:
u = x/dx + u0
v = y/dy + v0
其中,物理坐标系单位mm,像素坐标系单位是像素pixel,那么dx单位是mm/pixel毫米/像素,显然x/dx单位就是pixel,点(u0,v0)是光轴和图像平面的交点坐标,dx和dy分别是单位像素在x和y方向上的物理尺寸
上述一段话,转换成下图:
世界坐标系和像素坐标系同样可以通过上图来表示
上图中的M是3*4的投影矩阵,M1矩阵的元素数值完全由相机内参ax、ay、u0和v0决定,其中(u0,v0)是像素坐标系的原点坐标,ax和ay表示像素坐标系纵横轴上的尺度因子,即ax = f/dx, ay=f/dy, M2矩阵则是相机外参决定.
世界坐标系到像素坐标系,中间经过相机坐标系的过渡,Xw表示世界坐标系的x轴,单位是mm,uv表示像素坐标系的横纵轴,单位是pixel,从世界坐标系到像素坐标系的转换,就是从mm到pixel的转换.
在Halcon中,可以使用标定文件的算子来进行单相机标定.
//生成标定文件算子,输入标定板参数即可
gen_caltab( : : XNum每行黑色标志圆点数量,
YNum,
MarkDist两个就近圆点中心之间的距离单位是meter,
DiameterRatio黑色圆点直径和两个圆点中心距离比值,
CalPlateDescr存放标定板描述文件路径.descr文件,
CalPlatePSFile存放标定板图像文件路径.ps文件)
1.3单相机标定示例
首先,利用上述的算子gen_caltab()生成.descr 和 .ps文件,规定标定的参数
第二步, 在标定板选项中选择刚才生成的descr文件,如下图
第三步,选择标定,另外需要注意相机参数,选择相机时,需要考虑像元的实际宽高和焦距
第四步,选择采集助手,选择Gray灰度图片
第五步,点击采集获得图片,但需要查看图片的状态,如果状态显示:检测出品质问题不必在意,如果显示:失败则需要重新采集该图片.采集15张图片左右即可,要确保角度不同.
第六步,选择结果,其中摄像机参数就是内参,摄像机位姿就是外参.需要注意内参保存的文件是.cal 外参保存的文件是.dat
到这里就算完成了单相机的标定,接下来简单演示测量,具体的测量方法见第二节
第七步,测量助手,把上述的内参文件.cal和外参文件.dat加载进来
第八步,使用测量助手进行测量
此时,完成了相机的标定,完成了相机内参外参的设置,但还没有进行畸变矫正.
第二节 畸变矫正
单相机的标定就是为了获得相机的内参和外参,但是在测量前必须要完成的就是图像畸变的矫正,而在畸变矫正的过程中,往往不需要用到外参.
所谓的畸变是因为镜头内部结构导致的,存在畸变的对象往往是一维和二维的,因此外参对畸变的矫正并没有太大作用,但外参在三维对象的操作中却有很大作用.
畸变的矫正有如下六个环节:
显然,对于畸变的矫正,关键在于如何获得畸变矫正矩阵.接下来先看一张存在畸变的图片
显然上图的对象是一个存在明显畸变的标定板,如果使用它则会产生难以想象的误差.因此要通过畸变矫正来对它进行修复.
简单的说就是把相机的内参进行调整,从而获得具有无畸变效应的内参,如下面的程序所述:
/*通过矫正畸变的算子,把单相机标定好的内参外参,进行参数修改,获得正确的参数
*/
change_radial_distortion_cam_par(Mode,算法模式
CamParamIn, 输入相机内参
DistortionCoeffs,一般是0
CamParamOut输出相机内参)
/*产生映射,相当于放射矩阵*/
gen_radial_distortion_map(Map,输出映射数据
CamParamIn,原始相机内参即标定的内参
CamParamOut, 矫正过的相机内参
MapType算法模式)
/*应用变换到图片*/
map_image(Image,需要矫正的图片
Map,映射数据
ImageMapped,输出矫正后的图片)
通过畸变矫正矩阵的变换后,可以得到矫正后的图片
第三节 测量矩形
测量步骤:
- 把测量矩形放到待测量区域,Start和End以及夹角Phi表示待测量的方向,起始点角度等
- 在测量矩形内部寻找待测量图像的边缘
- 把边缘进行灰度投影,在垂直投影方向上找到边缘,然后计算哪个边和哪个边的距离
根据测量矩形的方向,把负边缘定义成是由亮到暗,正边缘是由暗到亮
实例一(基于边缘对)
结果图:
解释:

检测流程:
- gen_measure_rectangle2()
- mesure_pairs()
- close_measure()
dev_update_window ('off')
dev_close_window ()
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 ()
Row := 297
Column := 545
Length1 := 80
Length2 := 10
Angle := rad(90)
gen_rectangle2 (ROI, Row, Column, Angle, Length1, Length2)
// 生成测量矩形
// gen_measure_rectangle2( : : Row, Column, Phi, 矩形半宽Length1, 半高Length2, 图片Width, Height, Interpolation差值算法 : MeasureHandle输出测量矩形句柄)
gen_measure_rectangle2 (Row, Column, Angle, Length1, Length2, Width, Height, 'bilinear', MeasureHandle)
dev_display (ROI)
disp_continue_message (WindowID, 'black', 'true')
stop ()
/* 两个边缘作为一对,测量该对之间的距离,以及边缘对和边缘对之间的距离
注意这里的第一条边缘是说的每个边缘对中的第一个边缘,所以输出的是个数组
同理,第二条边缘也是每个边缘对的第二个,输出的也是个数组
边缘对内部的距离就是边缘对中第一个和第二个边缘的距离,同样输出的也是数组
边缘对之间的距离是每个边缘对的中心点到另一个边缘对的中心点的距离
*/
/*measure_pairs(Image : : MeasureHandle测量矩形句柄,
Sigma平滑参数,
Threshold最小边缘像素变化幅度,
Transition边缘类型是正还是负,
Select选择第几个边缘对,
RowEdgeFirst输出第一个边缘的中心行坐标,
ColumnEdgeFirst,
AmplitudeFirst输出第一个边缘的像素变化幅度即一阶差分导数值,
RowEdgeSecond输出第二个边的中心行坐标,
ColumnEdgeSecond,
AmplitudeSecond,
IntraDistance输出边缘对内部边缘之间的距离,
InterDistance第1边缘对的第2条边 到 第2边缘对第1个边缘的距离)
*/
measure_pairs (Fuse, MeasureHandle, 1, 1, 'negative', 'all', RowEdgeFirst, ColumnEdgeFirst, AmplitudeFirst, RowEdgeSecond, ColumnEdgeSecond, AmplitudeSecond, IntraDistance, InterDistance)
disp_continue_message (WindowID, 'black', 'true')
stop ()
// 解析来就是结果可视化了
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 ()
close_measure (MeasureHandle)
dev_update_window ('on')
dev_clear_window ()
实例二(非边缘对)
实例一和实例二的输出测量的数组都是控制变量数组,即常规数组,所以索引从0开始
独立边缘测量步骤:
- gen_measrue_rectangle2()
- measure_pos()
- close_measure()
结果图:
dev_close_window ()
read_image (Image, 'ic_pin')
get_image_size (Image, Width, Height)
dev_open_window (0, 0, Width / 2, Height / 2, 'black', WindowHandle)
set_display_font (WindowHandle, 14, 'mono', 'true', 'false')
dev_display (Image)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
// draw_rectangle2 (WindowHandle, Row, Column, Phi, Length1, Length2)
Row := 47
Column := 485
Phi := 0
Length1 := 420
Length2 := 10
dev_set_color ('green')
dev_set_draw ('margin')
dev_set_line_width (3)
//生成一个矩形区域
gen_rectangle2 (Rectangle, Row, Column, Phi, Length1, Length2)
// 生成一个测量矩形在上述区域上
gen_measure_rectangle2 (Row, Column, Phi, Length1, Length2, Width, Height, 'nearest_neighbor', MeasureHandle)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
// 设置运行参数
dev_update_pc ('off')
dev_update_var ('off')
// n时测量次数
n := 100
count_seconds (Seconds1)
for i := 1 to n by 1
// 使用边缘对测量
measure_pairs (Image, MeasureHandle, 1.5, 30, 'negative', 'all', RowEdgeFirst, ColumnEdgeFirst, AmplitudeFirst, RowEdgeSecond, ColumnEdgeSecond, AmplitudeSecond, PinWidth, PinDistance)
endfor
count_seconds (Seconds2)
Time := Seconds2 - Seconds1
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
dev_set_color ('red')
disp_line (WindowHandle, RowEdgeFirst, ColumnEdgeFirst, RowEdgeSecond, ColumnEdgeSecond)
// sum是元素求和函数
avgPinWidth := sum(PinWidth) / |PinWidth|
avgPinDistance := sum(PinDistance) / |PinDistance|
numPins := |PinWidth|
dev_set_color ('yellow')
disp_message (WindowHandle, 'Number of pins: ' + numPins, 'image', 200, 100, 'yellow', 'false')
disp_message (WindowHandle, 'Average Pin Width: ' + avgPinWidth, 'image', 260, 100, 'yellow', 'false')
disp_message (WindowHandle, 'Average Pin Distance: ' + avgPinDistance, 'image', 320, 100, 'yellow', 'false')
* dump_window (WindowHandle, 'tiff_rgb', 'C:\\Temp\\pins_result')
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
* draw_rectangle1 (WindowHandle, Row1, Column1, Row2, Column2)
Row1 := 0
Column1 := 600
Row2 := 100
Column2 := 700
dev_set_color ('blue')
disp_rectangle1 (WindowHandle, Row1, Column1, Row2, Column2)
stop ()
// 这个只是更改显示区域,并不是裁剪了图片
dev_set_part (Row1, Column1, Row2, Column2)
dev_display (Image)
dev_set_color ('green')
dev_display (Rectangle)
dev_set_color ('red')
disp_line (WindowHandle, RowEdgeFirst, ColumnEdgeFirst, RowEdgeSecond, ColumnEdgeSecond)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
close_measure (MeasureHandle)
dev_set_part (0, 0, Height - 1, Width - 1)
dev_display (Image)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
// 这里完成了第一次的测量,开始另一个区域测量
dev_set_color ('green')
* draw_rectangle2 (WindowHandle, Row, Column, Phi, Length1, Length2)
Row := 508
Column := 200
Phi := -1.5708
Length1 := 482
Length2 := 35
gen_rectangle2 (Rectangle, Row, Column, Phi, Length1, Length2)
gen_measure_rectangle2 (Row, Column, Phi, Length1, Length2, Width, Height, 'nearest_neighbor', MeasureHandle)
stop ()
//不是用边缘对的测量方法
// 不把边缘按对来分,每个边缘都是一个独立的边缘
/measure_pos(输入图片,
输出测量句柄,
平滑参数,
边缘差分最小阈值,
边缘正负类型,
选择哪个边缘,
输出边缘中心点的行坐标列坐标,
输出对应边缘差分值,
输出每个相邻边缘的中心点距离)
/
measure_pos (Image, MeasureHandle, 1.5, 30, 'all', 'all', RowEdge, ColumnEdge, Amplitude, Distance)
// 根据得到的信息来计算高度
PinHeight1 := RowEdge[1] - RowEdge[0]
PinHeight2 := RowEdge[3] - RowEdge[2]
dev_set_color ('red')
disp_line (WindowHandle, RowEdge, ColumnEdge - Length2, RowEdge, ColumnEdge + Length2)
disp_message (WindowHandle, 'Pin Height: ' + PinHeight1, 'image', RowEdge[1] + 40, ColumnEdge[1] + 100, 'yellow', 'false')
disp_message (WindowHandle, 'Pin Height: ' + PinHeight2, 'image', RowEdge[3] - 120, ColumnEdge[3] + 100, 'yellow', 'false')
* dump_window (WindowHandle, 'tiff_rgb', 'C:\\Temp\\pins_height_result')
close_measure (MeasureHandle)
dev_set_draw ('fill')
dev_set_line_width (1)
dev_close_window ()
read_image (Image, 'ic_pin')
get_image_size (Image, Width, Height)
dev_open_window (0, 0, Width / 2, Height / 2, 'black', WindowHandle)
set_display_font (WindowHandle, 14, 'mono', 'true', 'false')
dev_display (Image)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
* draw_rectangle2 (WindowHandle, Row, Column, Phi, Length1, Length2)
Row := 47
Column := 485
Phi := 0
Length1 := 420
Length2 := 10
dev_set_color ('green')
dev_set_draw ('margin')
dev_set_line_width (3)
gen_rectangle2 (Rectangle, Row, Column, Phi, Length1, Length2)
gen_measure_rectangle2 (Row, Column, Phi, Length1, Length2, Width, Height, 'nearest_neighbor', MeasureHandle)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
dev_update_pc ('off')
dev_update_var ('off')
n := 100
count_seconds (Seconds1)
for i := 1 to n by 1
measure_pairs (Image, MeasureHandle, 1.5, 30, 'negative', 'all', RowEdgeFirst, ColumnEdgeFirst, AmplitudeFirst, RowEdgeSecond, ColumnEdgeSecond, AmplitudeSecond, PinWidth, PinDistance)
endfor
count_seconds (Seconds2)
Time := Seconds2 - Seconds1
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
dev_set_color ('red')
disp_line (WindowHandle, RowEdgeFirst, ColumnEdgeFirst, RowEdgeSecond, ColumnEdgeSecond)
avgPinWidth := sum(PinWidth) / |PinWidth|
avgPinDistance := sum(PinDistance) / |PinDistance|
numPins := |PinWidth|
dev_set_color ('yellow')
disp_message (WindowHandle, 'Number of pins: ' + numPins, 'image', 200, 100, 'yellow', 'false')
disp_message (WindowHandle, 'Average Pin Width: ' + avgPinWidth, 'image', 260, 100, 'yellow', 'false')
disp_message (WindowHandle, 'Average Pin Distance: ' + avgPinDistance, 'image', 320, 100, 'yellow', 'false')
* dump_window (WindowHandle, 'tiff_rgb', 'C:\\Temp\\pins_result')
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
* draw_rectangle1 (WindowHandle, Row1, Column1, Row2, Column2)
Row1 := 0
Column1 := 600
Row2 := 100
Column2 := 700
dev_set_color ('blue')
disp_rectangle1 (WindowHandle, Row1, Column1, Row2, Column2)
stop ()
dev_set_part (Row1, Column1, Row2, Column2)
dev_display (Image)
dev_set_color ('green')
dev_display (Rectangle)
dev_set_color ('red')
disp_line (WindowHandle, RowEdgeFirst, ColumnEdgeFirst, RowEdgeSecond, ColumnEdgeSecond)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
close_measure (MeasureHandle)
dev_set_part (0, 0, Height - 1, Width - 1)
dev_display (Image)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
dev_set_color ('green')
* draw_rectangle2 (WindowHandle, Row, Column, Phi, Length1, Length2)
Row := 508
Column := 200
Phi := -1.5708
Length1 := 482
Length2 := 35
gen_rectangle2 (Rectangle, Row, Column, Phi, Length1, Length2)
gen_measure_rectangle2 (Row, Column, Phi, Length1, Length2, Width, Height, 'nearest_neighbor', MeasureHandle)
stop ()
measure_pos (Image, MeasureHandle, 1.5, 30, 'all', 'all', RowEdge, ColumnEdge, Amplitude, Distance)
PinHeight1 := RowEdge[1] - RowEdge[0]
PinHeight2 := RowEdge[3] - RowEdge[2]
dev_set_color ('red')
disp_line (WindowHandle, RowEdge, ColumnEdge - Length2, RowEdge, ColumnEdge + Length2)
disp_message (WindowHandle, 'Pin Height: ' + PinHeight1, 'image', RowEdge[1] + 40, ColumnEdge[1] + 100, 'yellow', 'false')
disp_message (WindowHandle, 'Pin Height: ' + PinHeight2, 'image', RowEdge[3] - 120, ColumnEdge[3] + 100, 'yellow', 'false')
* dump_window (WindowHandle, 'tiff_rgb', 'C:\\Temp\\pins_height_result')
close_measure (MeasureHandle)
dev_set_draw ('fill')
dev_set_line_width (1)
实例三(形状匹配+定位测量)
- 匹配好目标形状,定位其位置
- 根据每一帧目标的变化,形成仿射矩阵
- 根据放射矩阵,把测量矩形的区域进行相同变换
- 对测量区域进行边缘测量
dev_update_pc ('off')
dev_update_window ('off')
dev_update_var ('off')
open_framegrabber ('File', 1, 1, 0, 0, 0, 0, 'default', -1, 'default', -1, 'default', 'board/board.seq', 'default', -1, 1, FGHandle)
grab_image (Image, FGHandle)
get_image_size (Image, Width, Height)
dev_close_window ()
dev_open_window (0, 0, Width, Height, 'black', WindowHandle)
dev_open_window (Height + 70, 0, Width, 120, 'black', WindowHandleText)
dev_set_window (WindowHandle)
set_display_font (WindowHandle, 16, 'mono', 'true', 'false')
set_display_font (WindowHandleText, 16, 'mono', 'true', 'false')
dev_set_color ('red')
dev_display (Image)
Row1 := 188
Column1 := 182
Row2 := 298
Column2 := 412
gen_rectangle1 (Rectangle, Row1, Column1, Row2, Column2)
area_center (Rectangle, Area, Row, Column)
Rect1Row := -102
Rect1Col := 5
Rect2Row := 107
Rect2Col := 5
RectPhi := 0
RectLength1 := 170
RectLength2 := 5
// 设置2个测量矩形的矩形区域
gen_rectangle2 (Rectangle1, Row + Rect1Row, Column + Rect1Col, RectPhi, RectLength1, RectLength2)
gen_rectangle2 (Rectangle2, Row + Rect2Row, Column + Rect2Col, RectPhi, RectLength1, RectLength2)
reduce_domain (Image, Rectangle, ImageReduced)
create_shape_model (ImageReduced, 4, 0, rad(360), rad(1), 'none', 'use_polarity', 30, 10, ModelID)
get_shape_model_contours (ShapeModel, ModelID, 1)
hom_mat2d_identity (HomMat2DIdentity)
hom_mat2d_translate (HomMat2DIdentity, Row, Column, HomMat2DTranslate)
affine_trans_contour_xld (ShapeModel, ShapeModelTrans, HomMat2DTranslate)
dev_display (Image)
dev_set_color ('green')
dev_display (ShapeModelTrans)
dev_set_color ('blue')
dev_set_draw ('margin')
dev_set_line_width (3)
dev_display (Rectangle1)
dev_display (Rectangle2)
dev_set_draw ('fill')
dev_set_line_width (1)
dev_set_color ('yellow')
disp_message (WindowHandle, ['Press left button to start','and stop the demo'], 'window', 12, 12, 'black', 'true')
get_mbutton (WindowHandle, Row3, Column3, Button1)
wait_seconds (0.5)
Button := 0
while (Button != 1)
dev_set_window (WindowHandle)
dev_set_part (0, 0, Height - 1, Width - 1)
grab_image (ImageCheck, FGHandle)
dev_display (ImageCheck)
count_seconds (S1)
find_shape_model (ImageCheck, ModelID, 0, rad(360), 0.7, 1, 0.5, 'least_squares', 4, 0.7, RowCheck, ColumnCheck, AngleCheck, Score)
count_seconds (S2)
dev_display (ImageCheck)
if (|Score| > 0)
dev_set_color ('green')
hom_mat2d_identity (HomMat2DIdentity)
hom_mat2d_translate (HomMat2DIdentity, RowCheck, ColumnCheck, HomMat2DTranslate)
hom_mat2d_rotate (HomMat2DTranslate, AngleCheck, RowCheck, ColumnCheck, HomMat2DRotate)
affine_trans_contour_xld (ShapeModel, ShapeModelTrans, HomMat2DRotate)
dev_display (ShapeModelTrans)
// 针对像素点进行仿射变换,参数(放射矩阵,像素行列坐标,输出变换后的行列坐标)
affine_trans_pixel (HomMat2DRotate, Rect1Row, Rect1Col, Rect1RowCheck, Rect1ColCheck)
affine_trans_pixel (HomMat2DRotate, Rect2Row, Rect2Col, Rect2RowCheck, Rect2ColCheck)
gen_rectangle2 (Rectangle1Check, Rect1RowCheck, Rect1ColCheck, AngleCheck, RectLength1, RectLength2)
gen_rectangle2 (Rectangle2Check, Rect2RowCheck, Rect2ColCheck, AngleCheck, RectLength1, RectLength2)
dev_set_color ('blue')
dev_set_draw ('margin')
dev_set_line_width (3)
dev_display (Rectangle1Check)
dev_display (Rectangle2Check)
dev_set_draw ('fill')
count_seconds (S3)
// 根据上述的测量矩形区域经过变换后得到仿射结果区域,然后测量该区域的边缘距离
gen_measure_rectangle2 (Rect1RowCheck, Rect1ColCheck, AngleCheck, RectLength1, RectLength2, Width, Height, 'bilinear', MeasureHandle1)
gen_measure_rectangle2 (Rect2RowCheck, Rect2ColCheck, AngleCheck, RectLength1, RectLength2, Width, Height, 'bilinear', MeasureHandle2)
measure_pairs (ImageCheck, MeasureHandle1, 2, 90, 'positive', 'all', RowEdgeFirst1, ColumnEdgeFirst1, AmplitudeFirst1, RowEdgeSecond1, ColumnEdgeSecond1, AmplitudeSecond1, IntraDistance1, InterDistance1)
measure_pairs (ImageCheck, MeasureHandle2, 2, 90, 'positive', 'all', RowEdgeFirst2, ColumnEdgeFirst2, AmplitudeFirst2, RowEdgeSecond2, ColumnEdgeSecond2, AmplitudeSecond2, IntraDistance2, InterDistance2)
close_measure (MeasureHandle1)
close_measure (MeasureHandle2)
count_seconds (S4)
// 显示结果
dev_set_color ('red')
disp_line (WindowHandle, RowEdgeFirst1 - RectLength2 * cos(AngleCheck), ColumnEdgeFirst1 - RectLength2 * sin(AngleCheck), RowEdgeFirst1 + RectLength2 * cos(AngleCheck), ColumnEdgeFirst1 + RectLength2 * sin(AngleCheck))
disp_line (WindowHandle, RowEdgeSecond1 - RectLength2 * cos(AngleCheck), ColumnEdgeSecond1 - RectLength2 * sin(AngleCheck), RowEdgeSecond1 + RectLength2 * cos(AngleCheck), ColumnEdgeSecond1 + RectLength2 * sin(AngleCheck))
disp_line (WindowHandle, RowEdgeFirst2 - RectLength2 * cos(AngleCheck), ColumnEdgeFirst2 - RectLength2 * sin(AngleCheck), RowEdgeFirst2 + RectLength2 * cos(AngleCheck), ColumnEdgeFirst2 + RectLength2 * sin(AngleCheck))
disp_line (WindowHandle, RowEdgeSecond2 - RectLength2 * cos(AngleCheck), ColumnEdgeSecond2 - RectLength2 * sin(AngleCheck), RowEdgeSecond2 + RectLength2 * cos(AngleCheck), ColumnEdgeSecond2 + RectLength2 * sin(AngleCheck))
dev_set_line_width (1)
NumLeads := |IntraDistance1| + |IntraDistance2|
MinDistance := min([InterDistance1,InterDistance2])
dev_set_window (WindowHandleText)
dev_set_part (0, 0, 119, Width - 1)
dev_clear_window ()
disp_message (WindowHandleText, 'Matching: Time: ' + ((S2 - S1) * 1000)$'5.2f' + 'ms , Score: ' + Score$'7.5f', 'image', 20, 20, 'green', 'false')
disp_message (WindowHandleText, 'Measure: Time: ' + ((S4 - S3) * 1000)$'5.2f' + ' ms, Num. leads: ' + NumLeads$'2d', 'image', 50, 20, 'red', 'false')
disp_message (WindowHandleText, ' Min. lead dist: ' + MinDistance$'6.3f', 'image', 80, 20, 'red', 'false')
endif
dev_error_var (Error, 1)
dev_set_check ('~give_error')
get_mposition (WindowHandle, R, C, Button)
dev_error_var (Error, 0)
dev_set_check ('give_error')
if (Error != H_MSG_TRUE)
Button := 0
endif
endwhile
dev_set_window (WindowHandleText)
dev_close_window ()
clear_shape_model (ModelID)
close_framegrabber (FGHandle)
第四节 测量弧形
一般步骤:
- 采集图像
- 标定图像
- 畸变矫正
- 测量
- 创建测量弧形
- 像素距离转换成实际距离(根据内参外参)
- 显示结果
需要测量的目标是个弧形,或者非直线,如下图

实例一(创建弧形)
// measure_arc.hdev
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 ()
// 画一个圆圈或者写入参数
* 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(get_points_ellipse( : : Angle=上图中的AgnleStart+AngleExtent, Row, Column椭圆中心点行列坐标, Phi椭圆长轴和水平的夹角, Radius1椭圆长半轴2 : RowPoint, ColPoint输出椭圆在Angle角度上的一个点)
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)
//生成一个测量弧形
// gen_measure_arc( : : CenterRow, CenterCol, Radius, AngleStart, AngleExtent, AnnulusRadius见上图, Width, Height图片宽高, Interpolation 差值算法: MeasureHandle输出测量结果句柄)
gen_measure_arc (Row, Column, Radius, AngleStart, AngleExtent, 10, Width, Height, 'nearest_neighbor', MeasureHandle)
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
count_seconds (Seconds1)
n := 10
for i := 1 to n by 1
// 开始测量弧形
// measure_pos(Image : : MeasureHandle测量弧形句柄, Sigma平滑参数, Threshold最小边缘幅度即梯度值, Transition是正还是负边缘, Select选择哪些边缘 : RowEdge, ColumnEdge输出边缘结果的行列坐标, Amplitude边缘幅度, Distance在一个测量弧形里面,各边缘之间的连续距离)
// ⚠️Distance注意两点,1⃣️在一个测量矩形内,2⃣️连续边缘之间的连续距离,即不是两个连续边缘的中心点直线距离,注意区别
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 ()
// pp表示两个点,即求两个点之间的距离
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 ()