1. 设计任务
摄像机标定要求自制标定板,使用网络摄像机或手机摄像头进行标定。将标定的摄像机内参和外参进行保存。设计测量方案,使用标定过的摄像机对包含垂直边缘的物品(直尺刻度线,矩形物体边缘等)进行距离或边长的测量。标定过程和测量过程,均需要保持摄像机与测量平面之间的距离固定,物品高度不能过高,否则影响测量结果。给出设计的中间过程和必要的截图以及最终测量结果,并对测量结果进行误差计算和分析。测量对象不限,可以是学生卡、刻度线或自行绘制打印的带矩形边缘的图像等。
2.课题分析设计原理
- 摄像机标定原理阐述
(1)坐标系的转换
- 世界坐标系(world coordinate)(xw,yw,zw)也称为测量坐标系,是一个三维直角坐标系,以其为基准可以描述相机和待测物体的空间位置。世界坐标系的位置可以根据实际情况自由确定。
- 相机坐标系(camera coordinate)(xc,yc,zc),也是一个三维直角坐标系,原点位于镜头光心处,x、y轴分别与相面的两边平行,z轴为镜头光轴,与像平面垂直。
- 世界坐标系转换为相机坐标系。
其中R其中为3*3的旋转矩阵,t为3*1的平移矢量,[xc,yc,zc,1]T为相机坐标系的齐次坐标,(xw,yw,zw,1)T为世界坐标系的齐次坐标。
- 相机坐标系(Xc,Yc,Zc)->图像坐标系(x,y)。
其中,Zc 为比例因子(Zc不为0),f为有效焦距(相机光心到成像平面的距离),(Xc,Yc,Zc,1)是空间点在相机坐标系中的齐次坐标,(x,y,1)是像点在图像坐标系中的齐次坐标。
从相机标系(Xc,Yc,Zc)到图像坐标系(x,y)是一个三维坐标到二维坐标(3D->2D)的过程,称之为透视投影变换(仿射变换的延伸,新增形状可能也发生了变化,即畸变)。
(2)相机内参与畸变参数
相机内参(投影变换): 相机的固有属性,相机的内参数是六个分别为:1/dx、1/dy、r、u0、v0、f,在进行畸变校正时需要用到相机的内参。
f:焦距,单位毫米
dx:单位像素x方向宽度,单位毫米,1/dx:x方向1毫米内有多少个像素(dx、dy代表像元尺寸)
f/dx:使用像素来描述x轴方向焦距的长度
f/dy:使用像素来描述y轴方向焦距的长度
u0,v0:表示图像的中心像素坐标和图像原点像素坐标之间相差的横向和纵向像素数,理论值应该是图像宽度、高度的一半,但实际是有偏差的,一般越好的摄像头则其越接近于分辨率的一半。
相机外参(仿射变换): 相机在世界坐标系中的参数,比如相机的位置、旋转方向等。
它表示相机与测量平面之间的位置关系,因为物体在经过透镜成像之后,实际上是经过了平移和旋转,而外参就是告诉我们物体成像后经过了哪种平移和旋转,相机的外参包括平移矢量和旋转矩阵。也就是说只有知道了相机的外参,才能够完成测量平面坐标系和相机坐标系之间的坐标转换。在halcon中,测量平面定义为世界坐标系中平面z=0。
畸变参数: 采用理想针孔模型,由于通过针孔的光线少,相机曝光太慢。所以在实际使用中均采用透镜,可以使图像生成迅速,但代价是引入了畸变(径向畸变和切向畸变)。
- 单目测量原理阐述
单目测量的原理主要基于几何光学和图像处理技术。通过相机获取物体的图像。相机的成像过程遵循一定的几何投影关系。后利用图像处理算法对图像进行分析,比如识别出目标物体的特征点、边缘等。根据已知的相机参数(如焦距、像素尺寸等)以及目标在图像中的位置和特征,运用几何关系来推算目标的一些物理属性,如距离、尺寸、角度等。
3. 方案及流程设计
1)标定板制作,展示标定板图像;
首先创建一个模板,在halcon中点击助手>>打开新的Calibration。
在命令行输入gen_caltab (7, 7, 0.00375, 0.5, 'caliFile.descr', 'cali_ps.ps')
生成一个descr文件和ps文件,7行7列的,黑点圆心为0.00375的,0.5直径除以距离的标定板。
将ps模板文件打印下来拍若干张图片,如下图所示。
2)摄像机标定,给出标定的内参和外参;
在描述文件一栏选入标定的.descr描述文件作为模板。如下图所示。
然后点击标定,加载进去刚刚拍摄的照片,并标定。选择一个拍摄良好的作为参考位姿。如果有失败的将其移除,但是要保证图片有10-20张左右。最后点击标定可以得到摄像机的内参和外参。保存于所在文件夹供下一步使用。
在摄像机参数一栏点击保存,设为内参。摄像机位姿一栏设为外参也同理保存下来以供后续使用。
本次标定的实验参数如以下代码所示。
CameraParameters := ['area_scan_division',0.00356811,4552.91,8.21369e-006,8.3e-006,249.403,217.277,512,348]
CameraPose := [-0.00413296,-0.0127381,0.059779,350.588,356.876,98.7804,0]
stop ()
其中焦距为0.00356811,中心点坐标,249.403,217.277,图像宽512,348
外参摄像机位姿参数x=-0.00413296,y=-0.0127381,z=0.059779
- 对设计方案中垂直于测量矩形框的直边进行提取,并测量直边之间的距离,从而得到平面测量对象的尺寸;
打开测量文件如下图所示;
在输入中选择传入图像文件。选择要标定的文件。本文采用33.jpg文件。然后传入刚刚取得的内参与外参参数在标定来源一栏分别采用.cal用内参.dat用外参文件.
利用绘制线段来测量真实的长度如下图所示。
如同可知测量长度为80.4565.再打开验证程序和实际测绘来验证标定是否成功。
- 给出直尺或程序测量工具进行实际尺寸测量的示意图;
(1)编写程序验证。直接运行测试程序来测量标定的是否成功,如下图所示。
该验证程序的物体长度测量为78.0236mm。有2mm的误差所在标定成功。
(2)实际测量来验证标定程序的结果。
经实际测量可得长度为79.236mm。
- 结果分析及总结
本次相机标定工作取得了较为满意的结果。内参矩阵和畸变系数的估计较为准确,重投影误差处于合理范围内,且在不同条件下表现出较好的一致性。这为后续基于该相机的视觉测量、三维重建等应用奠定了坚实的基础。然而,在某些特定场景下,可能还需要进一步优化标定流程或采用更精确的标定方法来提高精度。同时,也应持续关注相机性能的变化,适时进行重新标定以保证准确性。通过标定获得的内参矩阵各项参数反映了相机自身的光学特性,如焦距、主点位置等。对这些参数的准确性进行评估,观察其是否符合相机的实际规格和预期表现,分析畸变系数可以了解相机镜头的畸变程度。较小的畸变系数通常意味着更好的成像质量和更准确的测量结果。重投影误差是衡量标定精度的重要指标。较低的重投影误差表明相机模型与实际成像过程拟合较好,标定结果较为可靠。
5.附录
- 标定代码
gen_caltab (7, 7, 0.00375, 0.5, 'caliFile.descr', 'cali_ps.ps')
- 标定测量代码
read_image (Image33, 'C:/Users/admin/Desktop/视觉大报告/33.jpg')
* Measure 01: Code generated by Measure 01
* Measure 01: Initialize calibration
CameraParameters := ['area_scan_division',0.00356811,4552.91,8.21369e-06,8.3e-06,249.403,217.277,512,348]
CameraPose := [-0.00444506,-0.00758943,0.0612608,352.777,357.139,40.1379,0]
* Measure 01: Prepare measurement
AmplitudeThreshold := 40
RoiWidthLen2 := 5
set_system ('int_zooming', 'true')
* Measure 01: Coordinates for line Measure 01 [0]
LineRowStart_Measure_01_0 := 1088.34
LineColumnStart_Measure_01_0 := 184.293
LineRowEnd_Measure_01_0 := 1089.61
LineColumnEnd_Measure_01_0 := 1108.32
* Measure 01: Convert coordinates to rectangle2 type
TmpCtrl_Row := 0.5*(LineRowStart_Measure_01_0+LineRowEnd_Measure_01_0)
TmpCtrl_Column := 0.5*(LineColumnStart_Measure_01_0+LineColumnEnd_Measure_01_0)
TmpCtrl_Dr := LineRowStart_Measure_01_0-LineRowEnd_Measure_01_0
TmpCtrl_Dc := LineColumnEnd_Measure_01_0-LineColumnStart_Measure_01_0
TmpCtrl_Phi := atan2(TmpCtrl_Dr, TmpCtrl_Dc)
TmpCtrl_Len1 := 0.5*sqrt(TmpCtrl_Dr*TmpCtrl_Dr + TmpCtrl_Dc*TmpCtrl_Dc)
TmpCtrl_Len2 := RoiWidthLen2
* Measure 01: Create measure for line Measure 01 [0]
* Measure 01: Attention: This assumes all images have the same size!
gen_measure_rectangle2 (TmpCtrl_Row, TmpCtrl_Column, TmpCtrl_Phi, TmpCtrl_Len1, TmpCtrl_Len2, 1276, 1702, 'nearest_neighbor', MsrHandle_Measure_01_0)
* Measure 01: Coordinates for line Measure 01 [1]
LineRowStart_Measure_01_1 := 1193.08
LineColumnStart_Measure_01_1 := 179.931
LineRowEnd_Measure_01_1 := 1184.35
LineColumnEnd_Measure_01_1 := 1117.88
* Measure 01: Convert coordinates to rectangle2 type
TmpCtrl_Row := 0.5*(LineRowStart_Measure_01_1+LineRowEnd_Measure_01_1)
TmpCtrl_Column := 0.5*(LineColumnStart_Measure_01_1+LineColumnEnd_Measure_01_1)
TmpCtrl_Dr := LineRowStart_Measure_01_1-LineRowEnd_Measure_01_1
TmpCtrl_Dc := LineColumnEnd_Measure_01_1-LineColumnStart_Measure_01_1
TmpCtrl_Phi := atan2(TmpCtrl_Dr, TmpCtrl_Dc)
TmpCtrl_Len1 := 0.5*sqrt(TmpCtrl_Dr*TmpCtrl_Dr + TmpCtrl_Dc*TmpCtrl_Dc)
TmpCtrl_Len2 := RoiWidthLen2
* Measure 01: Create measure for line Measure 01 [1]
* Measure 01: Attention: This assumes all images have the same size!
gen_measure_rectangle2 (TmpCtrl_Row, TmpCtrl_Column, TmpCtrl_Phi, TmpCtrl_Len1, TmpCtrl_Len2, 1276, 1702, 'nearest_neighbor', MsrHandle_Measure_01_1)
* Measure 01: Coordinates for line Measure 01 [2]
LineRowStart_Measure_01_2 := 1022.88
LineColumnStart_Measure_01_2 := 201.744
LineRowEnd_Measure_01_2 := 1027.25
LineColumnEnd_Measure_01_2 := 1122.24
* Measure 01: Convert coordinates to rectangle2 type
TmpCtrl_Row := 0.5*(LineRowStart_Measure_01_2+LineRowEnd_Measure_01_2)
TmpCtrl_Column := 0.5*(LineColumnStart_Measure_01_2+LineColumnEnd_Measure_01_2)
TmpCtrl_Dr := LineRowStart_Measure_01_2-LineRowEnd_Measure_01_2
TmpCtrl_Dc := LineColumnEnd_Measure_01_2-LineColumnStart_Measure_01_2
TmpCtrl_Phi := atan2(TmpCtrl_Dr, TmpCtrl_Dc)
TmpCtrl_Len1 := 0.5*sqrt(TmpCtrl_Dr*TmpCtrl_Dr + TmpCtrl_Dc*TmpCtrl_Dc)
TmpCtrl_Len2 := RoiWidthLen2
* Measure 01: Create measure for line Measure 01 [2]
* Measure 01: Attention: This assumes all images have the same size!
gen_measure_rectangle2 (TmpCtrl_Row, TmpCtrl_Column, TmpCtrl_Phi, TmpCtrl_Len1, TmpCtrl_Len2, 1276, 1702, 'nearest_neighbor', MsrHandle_Measure_01_2)
* Measure 01: ***************************************************************
* Measure 01: * The code which follows is to be executed once / measurement *
* Measure 01: ***************************************************************
* Measure 01: The image is assumed to be made available in the
* Measure 01: variable last displayed in the graphics window
copy_obj (Image33, Image, 1, 1)
* Measure 01: Execute measurements
measure_pos (Image, MsrHandle_Measure_01_0, 1, AmplitudeThreshold, 'all', 'all', Row_Measure_01_0, Column_Measure_01_0, Amplitude_Measure_01_0, Distance_Measure_01_0)
measure_pos (Image, MsrHandle_Measure_01_1, 1, AmplitudeThreshold, 'all', 'all', Row_Measure_01_1, Column_Measure_01_1, Amplitude_Measure_01_1, Distance_Measure_01_1)
measure_pos (Image, MsrHandle_Measure_01_2, 1, AmplitudeThreshold, 'all', 'all', Row_Measure_01_2, Column_Measure_01_2, Amplitude_Measure_01_2, Distance_Measure_01_2)
* Measure 01: Transform to world coordinates
* Measure 01: Calibrate positions for Measure 01 [0]
image_points_to_world_plane (CameraParameters, CameraPose, Row_Measure_01_0, Column_Measure_01_0, 0.001, Column_World_Measure_01_0, Row_World_Measure_01_0)
* Measure 01: Calibrate distances
TmpCtrl_Length := |Row_World_Measure_01_0|
if (TmpCtrl_Length > 0)
tuple_select_range (Row_World_Measure_01_0, 0, TmpCtrl_Length - 2, TmpCtrl_RowFrom)
tuple_select_range (Column_World_Measure_01_0, 0, TmpCtrl_Length - 2, TmpCtrl_ColumnFrom)
tuple_select_range (Row_World_Measure_01_0, 1, TmpCtrl_Length - 1, TmpCtrl_RowTo)
tuple_select_range (Column_World_Measure_01_0, 1, TmpCtrl_Length - 1, TmpCtrl_ColumnTo)
distance_pp (TmpCtrl_RowFrom, TmpCtrl_ColumnFrom, TmpCtrl_RowTo, TmpCtrl_ColumnTo, Distance_World_Measure_01_0)
endif
* Measure 01: Calibrate positions for Measure 01 [1]
image_points_to_world_plane (CameraParameters, CameraPose, Row_Measure_01_1, Column_Measure_01_1, 0.001, Column_World_Measure_01_1, Row_World_Measure_01_1)
* Measure 01: Calibrate distances
TmpCtrl_Length := |Row_World_Measure_01_1|
if (TmpCtrl_Length > 0)
tuple_select_range (Row_World_Measure_01_1, 0, TmpCtrl_Length - 2, TmpCtrl_RowFrom)
tuple_select_range (Column_World_Measure_01_1, 0, TmpCtrl_Length - 2, TmpCtrl_ColumnFrom)
tuple_select_range (Row_World_Measure_01_1, 1, TmpCtrl_Length - 1, TmpCtrl_RowTo)
tuple_select_range (Column_World_Measure_01_1, 1, TmpCtrl_Length - 1, TmpCtrl_ColumnTo)
distance_pp (TmpCtrl_RowFrom, TmpCtrl_ColumnFrom, TmpCtrl_RowTo, TmpCtrl_ColumnTo, Distance_World_Measure_01_1)
endif
* Measure 01: Calibrate positions for Measure 01 [2]
image_points_to_world_plane (CameraParameters, CameraPose, Row_Measure_01_2, Column_Measure_01_2, 0.001, Column_World_Measure_01_2, Row_World_Measure_01_2)
* Measure 01: Calibrate distances
TmpCtrl_Length := |Row_World_Measure_01_2|
if (TmpCtrl_Length > 0)
tuple_select_range (Row_World_Measure_01_2, 0, TmpCtrl_Length - 2, TmpCtrl_RowFrom)
tuple_select_range (Column_World_Measure_01_2, 0, TmpCtrl_Length - 2, TmpCtrl_ColumnFrom)
tuple_select_range (Row_World_Measure_01_2, 1, TmpCtrl_Length - 1, TmpCtrl_RowTo)
tuple_select_range (Column_World_Measure_01_2, 1, TmpCtrl_Length - 1, TmpCtrl_ColumnTo)
distance_pp (TmpCtrl_RowFrom, TmpCtrl_ColumnFrom, TmpCtrl_RowTo, TmpCtrl_ColumnTo, Distance_World_Measure_01_2)
endif
* Measure 01: Do something with the results
* Measure 01: Clear measure when done
close_measure (MsrHandle_Measure_01_0)
close_measure (MsrHandle_Measure_01_1)
close_measure (MsrHandle_Measure_01_2)
Tuple1:=[Distance_World_Measure_01_0,Distance_World_Measure_01_1,Distance_World_Measure_01_2]
tuple_mean(Tuple1,Mean)
tuple_deviation(Tuple1,Deviation)
- 测试代码
dev_clear_window ()
dev_set_color ('green')
dev_set_draw ('fill')
*读取图片
read_image (Image1, '33')
rgb1_to_gray (Image1, GrayImage)
scale_image (GrayImage, ImageScaled, 9.44444, -1133)
threshold (GrayImage, Regions, 35, 123)
connection (Regions, ConnectedRegions)
select_shape_std (ConnectedRegions, SelectedRegions, 'max_area', 70)
gen_contour_region_xld (SelectedRegions, Contours, 'border')
segment_contours_xld (Contours, ContoursSplit, 'lines_circles', 5, 4, 2)
*提取两条边线区域
select_obj (ContoursSplit, ObjectSelected1, 6)
select_obj (ContoursSplit, ObjectSelected2, 21)
gen_region_contour_xld (ObjectSelected1, Region1, 'filled')
gen_region_contour_xld (ObjectSelected2, Region2, 'filled')
distance_rr_min (Region1, Region2, MinDistance, Row11, Column11, Row21, Column21)
*读取标定数据
read_cam_par ('cam_para.cal', CameraParam)
read_pose ('cam_pose.dat', Pose)
*像素坐标转实际坐标
image_points_to_world_plane (CameraParam, Pose, Row11, Column11, 'mm', X1, Y1)
image_points_to_world_plane (CameraParam, Pose, Row21, Column21, 'mm', X2, Y2)
tuple_abs (X1, x11)
tuple_abs (X2, x22)
tuple_abs (Y1, y11)
tuple_abs (Y2, y22)
*实际距离计算
distance_pp (x11, y11, x22, y22, Distance)
*结果显示
dev_display (Image1)
dev_display (Region1)
dev_display (Region2)
dev_disp_text ('物体长度是:'+Distance+'mm', 'window', 20, 20, 'black', [], [])