一、理论描述
1. 单相机标定的目的
1.1 广义目的:畸变校正&一维/二维测量
- 畸变矫正: 相机拍照时通过镜头上的透镜把实物投影到像平面上,但是由于透镜制造精度以及组装工艺的偏差会引入畸变,导致原始图像的产生失真,会对拍摄的物体的形状产生变化,影响测量。
步骤如下:
① 通过标定求出相机内参。
② 通过有畸变的内参求出无畸变的内参。用chage_radial_distortion_cam_par()
函数完成。
③ 利用gen_radial_distortion_map()
算子,求出有畸变的内参和无畸变的内参之间的映射关系。
④ 将上边的映射关系作用到产生畸变的物体当中,完成畸变校正。 - 一维/二维的测量:求解像素当量的大小,从而完成图像像素距离(
Pix
)和物理实际距离(mm
)之间的转换。
个人浅显理解: 当相机进行标定(九点标定为例)时,由于已经事前输入标定板的准确参数,如圆的排列方式、直径、中心距等(圆形标定板)。然后通过加载多张标定板图片,halcon
可以通过函数求出发生畸变的标定板图像像素尺寸与标定板标准尺寸之间的映射关系,从而从完成了畸变校正。
1.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
。 - 畸变参数: 采用理想针孔模型,由于通过针孔的光线少,相机曝光太慢。所以在实际使用中均采用透镜,可以使图像生成迅速,但代价是引入了畸变(径向畸变和切向畸变)。
小结: 相机的畸变和内参是相机本身的固有特性,标定一次即可一直使用。但由于相机本身并非理想的小孔成像模型以及计算误差,采用不同的图片进行标定时得到的结果都有差异。一般重投影误差很小的话,标定结果均可用。
2. 求解相机的内参和外参目的
当我们在图片中确定了某个物体的位置,如何让机器手去到空间中的实际位置进行抓取呢?这就需要对坐标进行转换。而从像素点到空间点的转换与空间点到像素点的转换是相反的,我们先将后者的推导过程。
2.1 图像坐标系(x,y)
->像素坐标系(u,v)
像素坐标系UOV
不利于坐标变换,因此需要建立图像坐标系XOY
,其坐标轴的单位通常为毫米(mm)
,原点是相机光轴与相面的交点(称为主点),即图像的中心点。X
轴、Y
轴分别与u
轴、v
轴平行。故两个坐标系实际是平移关系,即可以通过平移就可得到。
两坐标轴互相垂直:
将上述关系转换为矩阵形式:
[ u v 1 ] = [ 1 / d x 0 u 0 0 1 / d y v 0 0 0 1 ] [ x y 1 ] \left [ \right ] = \left [ \right ] \left [ \right ]uv1=1/dx0001/dy0u0v01xy1
即像素坐标系下的坐标uv
与图像坐标系下的坐标xy
之间关系:
{ u = x / d x + u 0 v = y / d y + v 0{u=x/dx+u0v=y/dy+v0
其中,dx
,dy
代表像元尺寸,x/dx
在图像坐标系中x点的像素长度,(u0,v0)为图像中心。
这里引用了齐次矩阵(就是将一个原本是2
维的x,y
向量用一个3
维向量(加个1)来表示),引入齐次矩阵的目的主要是合并矩阵运算中的乘法和加法,表示为y = ax + b
的形式。即它提供了用矩阵运算把二维、三维甚至高维空间中的一个点集从一个坐标系变换到另一个坐标系的有效方法
2.2 相机坐标系(Xc,Yc,Zc)
->图像坐标系(x,y)
根据小孔成像原理,如图:
其中,Zc 为比例因子(Zc不为0),f
为有效焦距(相机光心到成像平面的距离),(Xc,Yc,Zc,1)是空间点在相机坐标系中的齐次坐标,(x,y,1
)是像点在图像坐标系中的齐次坐标。
根据相似三角形原理可得到:
x = f Z c X c , y = f Z c Y c x = \frac{f}{Z_c}X_c ,y = \frac{f}{Z_c}Y_cx=ZcfXc,y=ZcfYc
将上述关系转换为矩阵形式:
Z c [ x y 1 ] = [ f 0 0 0 0 f 0 0 0 0 1 0 ] [ X c Y c Z c 1 ] Z_c \left [ \right ] = \left [ \right ] \left [ \right ]Zcxy1=f000f0001000XcYcZc1
从相机坐标系(Xc,Yc,Zc
)到图像坐标系(x,y
)是一个三维坐标到二维坐标(3D->2D
)的过程,称之为透视投影变换(仿射变换的延伸,新增形状可能也发生了变化,即畸变)。
Z c [ x y 1 ] = [ f 0 0 0 0 f 0 0 0 0 1 0 ] [ X c Y c Z c 1 ] Z_c \left [ \right ] = \left [ \right ] \left [ \right ]Zcxy1=f000f0001000XcYcZc1
2.3 世界坐标系(Xw,Yw,Zw)
->相机坐标系(Xc,Yc,Zc)
世界坐标(Xw,Yw,Zw
)与相机坐标(Xc,Yc,Zc
)同为三维坐标(右手系,三轴互相垂直),两个坐标系的关系为刚体变换(刚体变换:当物体不发生形变时,对一个几何物体作旋转,平移的运动,对应的就是相机外参)。
可以先凭空想象下,有两个坐标系A与B,如何将A坐标系下的坐标转换到B坐标系表示,首先将A坐标系以原点为基准任意旋转,使其x轴,y轴,z轴与B坐标轴平行且同方向,接着平移AB坐标系原点的直线距离,就可以将A坐标系下的坐标转换到B坐标系,这个平移旋转矩阵就代表着两个三维坐标之间的关系。
用以下等式表示两个坐标系之间的关系:
[ X c Y c Z c ] = R [ X w Y w Z w ] + T \left [ \right ] = R \left [ \right ] + TXcYcZc=RXwYwZw+T
其中旋转矩阵R可以看成空间坐标分别沿着X,Y,Z轴的三个旋转矩阵点乘得到的结果。
当绕Z轴旋转θ角度,新旧坐标的关系为:
{ x = x ’ c o s θ − y ’ s i n θ y = x ’ s i n θ + y ’ c o s θ z = z ’⎩⎨⎧x=x’cosθ−y’sinθy=x’sinθ+y’cosθz=z’
用矩阵表示为:
[ x y z ] = [ c o s θ − s i n θ 0 s i n θ c o s θ 0 0 0 1 ] [ x ’ y ’ z ’ ] = R 1 [ x ’ y ’ z ’ ] \left [ \right ] = \left [ \right ] \left [ \right ] = R_1 \left [ \right ]xyz=cosθsinθ0−sinθcosθ0001x’y’z’=R1x’y’z’
同理,绕X轴,Y轴旋转φ和ω角度,可以得到:
[ x y z ] = [ 1 0 0 0 c o s φ s i n φ 0 − s i n φ c o s φ ] [ x ’ y ’ z ’ ] = R 2 [ x ’ y ’ z ’ ] \left [ \right ] = \left [ \right ] \left [ \right ] = R_2 \left [ \right ]xyz=1000cosφ−sinφ0sinφcosφx’y’z’=R2x’y’z’
[ x y z ] = [ c o s ω 0 − s i n ω 0 1 0 s i n ω 0 c o s ω ] [ x ’ y ’ z ’ ] = R 3 [ x ’ y ’ z ’ ] \left [ \right ] = \left [ \right ] \left [ \right ] = R_3 \left [ \right ]xyz=cosω0sinω010−sinω0cosωx’y’z’=R3x’y’z’
于是,得到旋转矩阵R = R1*R2*R3
,维度为3X3
,T
为平移矩阵,维度为3X1
。
拓展为其次坐标:
[ X c Y c Z c 1 ] = [ R T 0 1 ] [ X w Y w Z w 1 ] \left [ \right ] = \left [ \right ] \left [ \right ]XcYcZc1=[R0T1]XwYwZw1
2.4 世界坐标系(Xw,Yw,Zw)
转化为像素坐标系(u,v)
综合上面推导的过程,世界坐标(Xw,Yw,Zw)—>相机坐标(Xc,Yc,Zc)—>图像坐标(x,y)—>像素坐标(u,v),以上顺序用矩阵表示为不断左乘下一步,即:
Z c [ u v 1 ] = [ 1 / d x 0 u 0 0 1 / d y v 0 0 0 1 ] [ f 0 0 0 0 f 0 0 0 0 1 0 ] [ R T 0 1 ] [ X w Y w Z w 1 ] = [ f / d x 0 u 0 0 0 f / d y v 0 0 0 0 1 0 ] [ R T 0 1 ] [ X w Y w Z w 1 ] = M 1 M 2 X W = M W Z_c \left [ \right ] = \left [ \right ] \left [ \right ] \left [ \right ] \left [ \right ] \\ = \left [ \right ] \left [ \right ] \left [ \right ] = M_1M_2X_W = MWZcuv1=1/dx0001/dy0u0v01f000f0001000[R0T1]XwYwZw1=f/dx000f/dy0u0v01000[R0T1]XwYwZw1=M1M2XW=MW
其中,M1称为相机的内参矩阵,完全由相机的内部参数f/dx,f/dy,uo,vo
决定,(uo,vo)
为主点坐标,f/dx,f/dy
表示x、y
轴方向焦距的像素长度;M2完全由相机相对于世界坐标系的方位决定,称为相机的外参;Xw为空间点在世界坐标系下的齐次坐标;M为一个3*4
的矩阵,称为投影矩阵。
总结:其实说白了,我们需要找到世界坐标系和像素坐标系之间的关系,而两者的关系可以通过将世界坐标系转化为相机坐标系,相机坐标系通过投影变换转化为CCD的图像物理坐标系,在通过变换可以把图像物理坐标系的物理单位转化为图像像素坐标系的像素单位[即(x,y)
→(u,v)
],从而得到世界坐标系和像素坐标系之间的关系,即通过A->B
,B->C
,C->D
之间的关系,找到A->D
的关系。
要想找到世界坐标系到像素坐标系之间的转换关系,必须要求出相机内参,外参,而标定就是求相机内外参的过程,通过求得的内外参,达到对成像物体的一、二维测量和畸变矫正的目的。
小结:
- 像素坐标系
(u,v)
:像素坐标系便是我们从halcon
窗口里看到的图像像素坐标,在仿射变化等图像处理中都是以像素坐标系下开展的。(单位:Pix
),原点在图像左上角(二维) - 图像坐标系
(x,y)
:为了描述成像过程中物体从相机坐标系到图像坐标系的投影透射关系而引入,方便进一步得到像素坐标系下的坐标。(单位:mm
),以成像图像中心为原点(二维) - 相机坐标系
(Xc,Yc,Zc)
:在相机上建立的坐标系,为了从相机的角度描述物体位置而定义。
(单位:mm
),以镜头光心为原点,光轴为Z轴(三维)。 - 世界坐标系
(Xw,Yw,Zw)
:用户定义的三维世界的坐标系,为了描述目标物在真实世界里的位置而被引入(标定板所在的坐标系)。
(单位:mm
),真实世界中物体的实际位置(三维)。
二、标定流程
实现的原理是根据相机的像元尺寸、焦距和标定板的描述文件(.descr
)来找到显示的标定板图像上面的标志点,从而确定标定板实际输入的参数(Distance、Diameter
等)和这些参数对应的图像像素大小的关系,完成标定。
这里我用的标定板是7*7
圆点标定板,型号:HC070—3.75
标定板数据可参考:
标定步骤:
1. 使用gen_caltab算子生成一个标定文件
标定前需要生成一个.descr的描述文件,也就是世界坐标系与像素坐标系的关系,当我们下一次做别的项目时,只需要调用描述文件,即可完成标定过程。生成标记文件的算子:
这里我使用的是7*7的圆形标定板,具体设置参数如下:
函数:gen_caltab(7, 7, 0.0075, 0.5, 'C:/Users/Administrator/Desktop/caltab.descr ', 'caltab.ps')
参数:
7(in): x方向黑色标志圆点的数量
7(in):y方向黑色标志圆点的数量
0.0075(in):两个相邻的黑色圆点之间的圆心距离,单位:米
0.5(in):标记点直径与标记点圆心之间距离的比值
'C:/Users/Administrator/Desktop/caltab.descr '(in) :标定板的描述文件的保存路径
‘caltab.ps’(in): 标定板图形文件,描述标定板的一些信息,打印标定板时会用到,可以用photoshop打开
2. 打开标定助手,加载标定文件,设置相机参数
注:选择摄像机模型,我的是面阵相机。镜头是远心镜头就选择远心;像元的宽高填写相机的像元尺寸,如果不知道去问相机厂家或查相机型号。焦距填写镜头的焦距
3. 标定、获取标定结果图
实时获取各个角度、位置的标定板图片(9-16张左右),并选择其中一种图片设置参考位姿,然后标定。
可以看到相机的参数:
4. 保存相机内、外参,下次使用测量助手直接调用内外参文件
注:内参后缀名是.dat
,halcon
标定助手默认的内参后缀为.cal,保存的时候需要更改
三、标定后的畸变校正与实际测量
截止到上面,相机标定已经完成了,得到相机内外参之后就可以根据自己的实际需求对图像进行畸变校正或像素尺寸到实际尺寸的转换来进行实际测量了,方法如下:
1. 畸变校正
可以接上面的第三步之后:生成标定数据(相机内、外参)代码,从而进行畸变校正
代码:
*相机内参 CameraParameters := [0.0375147,-270.806,8.30152e-006,8.3e-006,647.48,520.914,1280,960] *相机位姿,即外参(旋转矩阵+平移矢量) CameraPose := [-0.0091626,-0.00625214,0.700967,2.46926,358.933,179.443,0] *1、校正径向畸变,得到新的相机内参 change_radial_distortion_cam_par ('adaptive', CameraParameters, 0, CamParamOut) stop () - Image Acquisition 02: Code generated by Image Acquisition 02 open_framegrabber ('GigEVision', 0, 0, 0, 0, 0, 0, 'default', -1, 'default', -1, 'false', 'default', 'CAMERA_QBY_DM', 0, -1, AcqHandle) grab_image_start (AcqHandle, -1) while (true) grab_image_async (Image, AcqHandle, -1) *2、对发生径向畸变的图像生成投影映射,图像的映射数据存在第一个参数中 gen_radial_distortion_map (Map, CameraParameters, CamParamOut, 'bilinear') *3、对图像进行畸变校正 map_image (Image, Map, ImageMapped) endwhile close_framegrabber (AcqHandle)
ImageMapped
就是进行了畸变矫正的图像
2. 像素坐标系转化成世界坐标系(求实际尺寸)
*相机内参 CameraParameters := [0.0375147,-270.806,8.30152e-006,8.3e-006,647.48,520.914,1280,960] *相机位姿,即外参(旋转矩阵+平移矢量) CameraPose := [-0.0091626,-0.00625214,0.700967,2.46926,358.933,179.443,0] read_image (Image, ImageFiles[Index]) *** *这里省略的是检测得到像素坐标尺寸 *** *将像素坐标尺寸转换成实际坐标尺寸(参考后面实战测量硬币) image_points_to_world_plane (CameraParameters, CameraPose, TmpCtrl_ImageRows, TmpCtrl_ImageColumns, 'mm', TmpCtrl_WorldX, TmpCtrl_WorldY)
3. 图像坐标系转化成世界坐标系(可选项,图像校正时会用到,也可以仿射变换对图像进行校正)
标定完之后可以插入代码看一下:
代码:
*相机内参 CameraParameters := [0.0375147,-270.806,8.30152e-006,8.3e-006,647.48,520.914,1280,960] *相机位姿,即外参(旋转矩阵+平移矢量) CameraPose := [-0.0091626,-0.00625214,0.700967,2.46926,358.933,179.443,0] read_image (Image, ImageFiles[Index]) get_image_size (Image, Width, Height) 相机视野宽度 TmpCtrl_RectificationWidth := 79 转换成米单位 TmpCtrl_RectificationWidth := TmpCtrl_RectificationWidth / 1000.0 生成转换矩阵 gen_image_to_world_plane_map (TmpObj_RectificationMap, CameraParameters, CameraPose, Width, Height, Width, Height, TmpCtrl_RectificationWidth / Width, 'bilinear') 转换 map_image (Image, TmpObj_RectificationMap, TmpObj_RectifiedImage)
注意:相机标定之后,相机焦距、上下位置不能再动,否则需要重新标定。
四、实战演练:测量一元硬币直径
1. 打开测量助手,加载上面得到的相机内、外参
2. 绘制测量直线,获取硬币边缘坐标
直接拉一条直线,得到硬币边缘的像素坐标信息,再通过上面标定得到的像素坐标和实际坐标之间的转换关系,可以计算出硬币的最终直径大小为24.1232mm
。
3. 直接生成代码
生成代码后,加入上面的畸变校正,即可完成完整的标定过程
open_framegrabber ('GigEVision', 0, 0, 0, 0, 0, 0, 'default', -1, 'default', -1, 'false', 'default', 'CAMERA_QBY_DM', 0, -1, AcqHandle) *加载相机内外参 CameraParameters := [0.0220788,-613.469,6.00309e-006,6e-006,613.491,509.255,1280,960] CameraPose := [-0.00464111,-0.00298404,0.341764,2.05327,359.502,89.9295,0] *1、校正径向畸变,得到新的相机内参 change_radial_distortion_cam_par ('adaptive', CameraParameters, 0, CamParamOut) AmplitudeThreshold := 40 RoiWidthLen2 := 25 set_system ('int_zooming', 'true') *测量矩形尺寸 LineRowStart_Measure_01_0 := 495.5 LineColumnStart_Measure_01_0 := 385.5 LineRowEnd_Measure_01_0 := 498.167 LineColumnEnd_Measure_01_0 := 745.5 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 *生成一个测量矩形 gen_measure_rectangle2 (TmpCtrl_Row, TmpCtrl_Column, TmpCtrl_Phi, TmpCtrl_Len1, TmpCtrl_Len2, 1280, 960, 'nearest_neighbor', MsrHandle_Measure_01_0) while (true) grab_image (Image, AcqHandle) *2、对发生径向畸变的图像生成投影映射,图像的映射数据存在第一个参数中 gen_radial_distortion_map (Map, CameraParameters, CamParamOut, 'bilinear') *3、对图像进行畸变校正 map_image (Image, Map, ImageMapped) measure_pos (ImageMapped, MsrHandle_Measure_01_0, 19.5, 40, 'all', 'all', Row_Measure_01_0, Column_Measure_01_0, Amplitude_Measure_01_0, Distance_Measure_01_0) * 通过image_points_to_world_plane将像素尺寸转换成实际尺寸 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) endif endwhile close_measure (MsrHandle_Measure_01_0)
五、疑惑解答
这里主要回答一下有人问我的问题:
1.halcon是否只能使用halcon专用的标定板?
- halcon自带例程中出现的,用halcon定义的标定板 ,如下图
halcon提供了简便、精准的标定算子并且提供了标定助手,这无疑大大方便了广大开发者。但是也不是一定要使用该标定板才能完成标定。 - 用户自定义标定板,用户可以制作任何形状、形式的标定板
所以,halcon并非只能使用专用标定板,也可以使用自定义标定板就行标定。
使用halcon定义标定板的优势是可以使用halcon的标定板提取算子,提取标记点,而当使用自己定义的标定板格式则需要自己完成此部分工作。
2.halcon标定板如何摆放,拍照数量有无限制?
halcon建议拍摄数量在9-16张,并非拍的越多越好,并且对摆放位置做了建议,如下图所示(注:上面我标定的时候并没有做Z轴的倾斜摆放,因为我测量时不涉及Z平面的,所以只是在XY平面做旋转摆放)
标定板充满标定视野的1/3-1/4,对于标定板成像灰度值亮度应大于128,以便halcon算子能较顺利的提取到标定板
标定时候注意标定图像的倾角需要有变化。若采集的图像里标定板与相机所成角度相近,标定板只在某一个平面平移和旋转,则焦距计算会出现错误。
3.halcon的标定板其中一个角上为什么有缺口?
halcon的标定板其中一个角上有缺口,这是为了在标定板上建立参考坐标系用的,标定相机的同时可以完成手眼标定。
戳戳小手帮忙点个免费的赞和关注吧,嘿嘿。 |