# 1 简介 计算倾斜与旋转对象在世界坐标系下的尺寸。该样例主要计算HALCON标定板的边距与标志点的直径。
2 测量流程
2.1 相机标定
- 创建相机标定模型
- 设置标定模型的参数
- 提取每一张图像的标志点与外轮廓
- 执行相机标定,获取标定误差
2.2 边距
- 获取标定板的黑色外框
- 通过轮廓选择平行的轮廓线
- 获取过中心且垂直于轮廓线的标志点坐标
- 计算交点
- 构建二维测量模型
- 通过测量模型计算边缘点坐标
- 映射到世界坐标系
- 在世界坐标系中计算边距
2.3 标志点半径
- 提取标志点外轮廓
- 椭圆拟合
- 映射到世界坐标系
- 世界坐标系下的椭圆拟合
- 计算半径的平均值于方差
3 代码解析
3.1 程序初始化
// 关闭当前的图像窗口
dev_close_window ()
// 以指定的位置、尺寸、背景颜色打开一个新的窗口
dev_open_window (0, 0, 768, 576, 'black', WindowHandle)
// 取消所有自动更新
dev_update_off ()
// 设置绘制模式
dev_set_draw ('margin')
// 设置显示线宽
dev_set_line_width (3)
// 设置显示字体
set_display_font (WindowHandle, 14, 'mono', 'true', 'false')
3.2 相机标定
// 指定标定板描述文件
CalTabDescrFile := 'caltab_big.descr'
/* 设置初始相机内参
* 参数按位置顺序依次为:
* 镜头焦距
* 径向扭曲系数
* 像元水平尺寸(单位:米)
* 像元垂直尺寸(单位:米)
* 图像坐标系下的主点列坐标(单位:像素)
* 图像坐标系下的主点行坐标(单位:像素)
* 图像宽度(单位:像素)
* 图像高度(单位:像素)
*/
gen_cam_par_area_scan_division (0.008, 0, 0.0000086, 0.0000086, 384, 288, 768, 576, StartCamPar)
/* 创建相机标定模型
* 参数按位置顺序依次为:
* 标定的类型
* 同时标定的相机数量
* 标定板对象的数量
* 输出的标定模型句柄
*/
create_calib_data ('calibration_object', 1, 1, CalibDataID)
/* 设置标定模型的初始相机内参
* 参数按位置顺序依次为:
* 标定模型句柄
* 相机索引
* 相机类型
* 相机内参
*/
set_calib_data_cam_param (CalibDataID, 0, [], StartCamPar)
/* 通过标定板描述文件确定标定板对象
* 参数按位置顺序依次为:
* 标定句柄
* 标定对象索引
* 标定对象描述文件
*/
set_calib_data_calib_object (CalibDataID, 0, CalTabDescrFile)
// 指定用于标定的标定板图像的数量
NumImages := 10
// 循环读取图像,并提取标志点
for I := 1 to NumImages by 1
// 读取图像
read_image (Image, 'calib/calib-3d-coord-' + I$'02d')
// 显示图像
dev_display (Image)
// 显示提示信息
Message := 'Find calibration plate in\nall calibration images (' + I + '/' + NumImages + ')'
disp_message (WindowHandle, Message, 'window', 12, 12, 'black', 'true')
/* 在图像中查找标定对象
* 参数按位置顺序依次为:
* 标定对象图像
* 标定句柄
* 相机索引
* 标定对象索引
* 额外参数名称集合
* 额外参数值集合
*/
find_calib_object (Image, CalibDataID, 0, 0, I - 1, [], [])
/* 获取初始标定参数
* 参数按位置顺序依次为:
* 标定句柄
* 标定数据类型,'calib_obj', 'calib_obj_pose', 'camera', 'model', 'tool'
* 索引
* 数据名
* 返回数据值
*/
get_calib_data (CalibDataID, 'camera', 0, 'init_params', StartCamPar)
/* 获取标志点的图像坐标与姿态
* 参数按位置顺序依次为:
* 标定句柄
* 相机索引
* 标定对象索引
* 标定对象姿态索引
* 标志点行坐标
* 标志点列坐标
* 标志点索引号
* 标定对象相对于相机坐标系的姿态
*/
get_calib_data_observ_points (CalibDataID, 0, 0, I - 1, Row, Column, Index, Pose)
/* 获取标定对象边界轮廓
* 参数按位置顺序依次为:
* 输出轮廓
* 标定句柄
* 轮廓名,'caltab', 'last_caltab', 'marks'
* 相机索引
* 标定对象索引
* 标定对象姿态索引
*/
get_calib_data_observ_contours (Contours, CalibDataID, 'caltab', 0, 0, I - 1)
// 创建十字标识
gen_cross_contour_xld (Cross, Row, Column, 6, 0.785398)
// 设置颜色
dev_set_color ('green')
// 显示轮廓
dev_display (Contours)
// 设置颜色
dev_set_color ('yellow')
// 显示十字标识
dev_display (Cross)
endfor
// 显示提示信息
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
// 执行标定
calibrate_cameras (CalibDataID, Error)
// 获取标定后的相机参
get_calib_data (CalibDataID, 'camera', 0, 'params', CamParam)
3.3 标定对象中心点提取
为了计算边距或标志点的半径,都需要对图像进行预处理。因此,先介绍预处理函数。
/* 函数的参数依次为:
* Image : (IN) 标定对象图像
* PlateRegion : (OUT) 标定对象区域
* CalibDataID : (IN) 标定句柄
* PoseIndex : (IN) 标定姿态索引
* Distance : (OUT) 边距的粗略距离
* Phi : (OUT) 标定对象的旋转角度
* RowCenter : (OUT) 标定板中心行坐标
* ColumnCenter: (OUT) 标定板中心列坐标
*/
// 固定阈值二值化
threshold (Image, Region, 0, 120)
// 连通域提取
connection (Region, ConnectedRegions)
// 根据条件选择区域
select_shape (ConnectedRegions, SelectedRegions, ['holes_num','rect2_len1','rect2_len2'], 'and', [1,120,120], [1,200,200])
// 填充区域
fill_up (SelectedRegions, PlateRegion)
/* 根据区域的边界生成轮廓
* 第三个参数为提取边界的模式,有以下值:
* center : 边界像素的中心点连接成的轮廓
* border : 边界像素的外轮廓
* border_holes : 除了边界像素的被提取,孔边界也被提取
*/
gen_contour_region_xld (PlateRegion, Contours, 'center')
/* 将轮廓分割成直线段和圆弧段或椭圆弧段
* 参数按位置顺序依次为:
* 输入轮廓
* 分割后轮廓
* 分割模式,'lines', 'lines_circles', 'lines_ellipses'
* 平滑轮廓使用的点数
* 第一次迭代使用弧替代直线段的最大距离
* 第二次迭代使用弧替代直线段的最大距离
*/
segment_contours_xld (Contours, ContoursSplit, 'lines', 7, 4, 2)
// 计算回归线
regress_contours_xld (ContoursSplit, RegressContours, 'no', 1)
// 轮廓选择
select_contours_xld (RegressContours, VerticalContours, 'direction', rad(45), rad(135), -0.5, 0.5)
// 轮廓选择
select_contours_xld (VerticalContours, LongContours, 'length', 150, 500, -0.5, 0.5)
// 选择第一个垂直边缘
select_obj (LongContours, Contour, 1)
// 获取轮廓点
get_contour_xld (Contour, Rows, Columns)
// 获取起始点
RowBegin1 := Rows[0]
ColBegin1 := Columns[0]
// 获取终止点
RowEnd1 := Rows[|Rows| - 1]
ColEnd1 := Columns[|Columns| - 1]
// 选择第二个垂直边缘
select_obj (LongContours, Contour, 2)
// 获取轮廓点
get_contour_xld (Contour, Rows, Columns)
// 获取起始点
RowBegin2 := Rows[0]
ColBegin2 := Columns[0]
// 获取终止点
RowEnd2 := Rows[|Rows| - 1]
ColEnd2 := Columns[|Columns| - 1]
// 获取标志点的中心坐标
get_calib_data_observ_points (CalibDataID, 0, 0, PoseIndex - 1, Row, Column, PoseIndex, _Pose)
// 选取过中心且垂直于提取垂直边缘的标志点中心
Row1 := Row[find(PoseIndex,21)]
Row2 := Row[find(PoseIndex,27)]
Column1 := Column[find(PoseIndex,21)]
Column2 := Column[find(PoseIndex,27)]
// 显示直线段,并计算焦点
dev_get_window (WindowHandle)
disp_line (WindowHandle, Row1, Column1, Row2, Column2)
disp_line (WindowHandle, RowBegin1, ColBegin1, RowEnd1, ColEnd1)
intersection_lines (Row1, Column1, Row2, Column2, RowBegin1, ColBegin1, RowEnd1, ColEnd1, RowA, ColA, IsOverlapping)
// 显示直线段,并计算焦点
dev_get_window (WindowHandle)
disp_line (WindowHandle, Row1, Column1, Row2, Column2)
disp_line (WindowHandle, RowBegin2, ColBegin2, RowEnd2, ColEnd2)
intersection_lines (Row1, Column1, Row2, Column2, RowBegin2, ColBegin2, RowEnd2, ColEnd2, RowB, ColB, IsOverlapping)
// 计算距离
distance_pp (RowA, ColA, RowB, ColB, Distance)
// 计算角度
line_orientation (RowA, ColA, RowB, ColB, Phi)
// 计算中心坐标
RowCenter := (RowA + RowB) / 2
ColumnCenter := (ColA + ColB) / 2
3.4 边距测量
// 读取图像
read_image (Image, 'calib/calib-3d-coord-01')
// 获取测量参数
get_measure_positions (Image, PlateRegion, CalibDataID, 0, Distance, Phi, RowCenter, ColumnCenter)
// 生成测量矩形,用于显示
gen_rectangle2_contour_xld (Rectangle, RowCenter, ColumnCenter, Phi, Distance * 0.52, 8)
// 矩形区域测量
gen_measure_rectangle2 (RowCenter, ColumnCenter, Phi, Distance * 0.52, 8, 768, 576, 'nearest_neighbor', MeasureHandle)
// 执行测量
measure_pos (Image, MeasureHandle, 1, 40, 'all', 'all', RowEdge, ColumnEdge, Amplitude, Distance1)
// 显示测量点的位置
Rows := [RowEdge[0],RowEdge[|RowEdge| - 1]]
Columns := [ColumnEdge[0],ColumnEdge[|RowEdge| - 1]]
gen_cross_contour_xld (Cross, Rows, Columns, 16, Phi)
// 获取标定板的姿态
get_calib_data (CalibDataID, 'calib_obj_pose', [0,0], 'pose', Pose)
// 图像坐标系到世界坐标系的映射
image_points_to_world_plane (CamParam, Pose, Rows, Columns, 'm', SX, SY)
// 世界坐标系的距离季孙
distance_pp (SY[0], SX[0], SY[1], SX[1], Width)
// 显示测量结果
dev_display (Image)
dev_set_color ('white')
dev_set_line_width (3)
dev_display (Rectangle)
dev_set_color ('green')
dev_set_draw ('fill')
dev_set_line_width (2)
dev_display (Cross)
dev_set_draw ('margin')
disp_message (WindowHandle, 'Width = ' + (Width * 100)$'8.3f' + 'cm', 'window', 12, 12, 'black', 'true')
disp_continue_message (WindowHandle, 'black', 'true')
3.5 半径测量
// 腐蚀区域
erosion_circle (PlateRegion, ROI, 17.5)
// 抠图
reduce_domain (Image, ROI, ImageReduced)
// 亚像素边缘提取
edges_sub_pix (ImageReduced, Edges, 'canny', 1, 20, 60)
// 轮廓选择
select_contours_xld (Edges, SelectedEdges, 'contour_length', 20, 99999999, -0.5, 0.5)
// 椭圆拟合
fit_ellipse_contour_xld (SelectedEdges, 'fitzgibbon', -1, 2, 0, 200, 3, 2, Row, Column, Phi, Radius1, Radius2, StartPhi, EndPhi, PointOrder)
MeanRadius1 := mean(Radius1)
MeanRadius2 := mean(Radius2)
DevRadius1 := deviation(Radius1)
DevRadius2 := deviation(Radius2)
// 映射到世界坐标
contour_to_world_plane_xld (SelectedEdges, WorldCircles, CamParam, Pose, 'mm')
// 世界坐标系下的边缘拟合
fit_ellipse_contour_xld (WorldCircles, 'fitzgibbon', -1, 2, 0, 200, 3, 2, Row, Column, Phi, RadiusW1, RadiusW2, StartPhi, EndPhi, PointOrder)
MeanRadiusW1 := mean(RadiusW1)
MeanRadiusW2 := mean(RadiusW2)
DevRadiusW1 := deviation(RadiusW1)
DevRadiusW2 := deviation(RadiusW2)
// 显示结果
dev_display (Image)
dev_set_color ('yellow')
dev_set_line_width (3)
dev_display (SelectedEdges)
Message := 'Measured dimensions of the ellipses'
Message[0] := ' Mean Radius1; Mean Radius2; (Standard deviations [%])'
Message[1] := 'Image coordinates: ' + MeanRadius1$'5.2f' + 'px; ' + MeanRadius2$'5.2f' + 'px (' + (DevRadius1 / MeanRadius1 * 100)$'4.2f' + ', ' + (DevRadius2 / MeanRadius2 * 100)$'4.2f' + ')'
Message[2] := 'World coordinates: ' + (MeanRadiusW1 / 10)$'5.2f' + 'cm; ' + (MeanRadiusW2 / 10)$'5.2f' + 'cm (' + (DevRadiusW1 / MeanRadiusW1 * 100)$'4.2f' + ', ' + (DevRadiusW2 / MeanRadiusW2 * 100)$'4.2f' + ')'
disp_message (WindowHandle, Message, 'window', 12, 12, 'black', 'true')