赞
踩
目录
记录了摄像机模型建立和张正有标定法,并采用python对自己手机的摄像头进行了标定。
小孔成像的原理:当用一个带有小孔的板遮挡在屏幕与物之间,光线通过小孔后会在胶片上形成物的倒像,这是因为光线直线传播的性质。前后移动中间的板,像的大小也会随之发生变化。
根据小孔成像原理有:
将空间点(x,y,z)投影到照片上的坐标为(x',y') ,z代表深度
图片坐标系:图片左下角度为原点
摄像机坐标系:针孔为原点
空间坐标系:自己定原点
在摄像机坐标系下根据相似三角形原理,可以列出x,y的两个等式,以y为例
其中 f 代表焦距,[x’,y’]为照片上的坐标单位为(m)
有下面成立,x同理
考虑像平面成像的原点在中心,而像素坐标原点在左下角,因此[x’,y’]应该加上(Cx,Cy)
考虑到照片坐标为像素单位,要进行单位变化:米->像素 [u,v]为照片上的像素坐标(单位为像素),k,l为图片坐标系下x和y方向单位转化系数
照片上的坐标为
为了将投影关系用矩阵表示,需要引入齐次坐标,根据下面原理,照片坐标可以用 表示,含义与[u,v,1]一样,空间坐标用[x,y,z,1]表示。
变成其次坐标写成矩阵形式:P'为摄像机的齐次坐标,[x,y,z,1] 为空间点。
由于相机工艺问题,对矩阵引入像素平面夹角
给出K为摄像机内参数矩阵,有了K即可算摄像机坐标下的空间点投影到照片上的像素位置(内参矩阵由设备决定,一般厂家会给或自己标定)
将(,),写在一起,均与设备有关。记内参矩阵K为(5个未知量)
,
由于拍照位置不固定,往往需要移动,为了建立起多张图片的联系和更好的描述物体,需要引入世界坐标系,其中Pw为世界坐标系的点,P为摄像机坐标系下的点。R为旋转矩阵(3*3),T为平移矩阵(3*1)
有前面的p = KP 可以写成 p = K[I 0]P的形式,其中 I 为3*3的单位矩阵,K为3*3的内参矩阵,0是3*1的向量,带入上面图片的P进去
这样我们得到了完整的摄像机模型,记住M=K[R T]为(3*4)的投影矩阵为,K为3*3的内参矩阵,R和T构成外参数矩阵,因为R,T是由世界坐标系、拍照位置和摄像机姿态决定,与设备无关。上面的P和p均为齐次坐标。
由于是把已知尺寸的棋盘格并贴在一个平面上,在平面上建立世界坐标系,减小计算[x,y,z=0]由于棋盘尺寸知道,自己打印,则我们可以计算得到每一个角点在世界坐标系下坐标[Xi,Yi,0]。同时通过一些特征点提取算法,也知道其对应照片坐标[Ui,Vi]。
我们将利用这些信息:每一个角点的像素坐标 (u,v) 、每一个角点在世界坐标系下的坐标(X,Y,Z=0),来进行相机的标定,获得相机的内外参矩阵K,R,T、畸变参数。
根据前面的摄像机模型,带入坐标有:
其中s为比例因子,齐次坐标具有比例不变形。表示世界坐标系到图像坐标系的尺度因子
整理z=0有:
上面等式可以写成 p = HPxy ,其中H=K[r1,r2,T]/s,把K和s乘进去有H = [Kr1/s,Kr2/s,KT/s] ,记H为:H = [h1,h2,h3] 。 H 为3*3的矩阵,有9个参数,有一个元素作为齐次坐标,即H有8个自由度需要求解,需要四个对应点。也就是需要四个点来求出图像平面到世界平面的单应性矩阵。这里假设对应照片的H已知。
由于R为旋转矩阵(满足单位正交阵),r1和r2相互垂直,模等于1
结合H = [Kr1/s,Kr2/s,KT/s] = H = [h1,h2,h3]
记:
带入K如下,其中u,v为主点坐标 ,两坐标轴的垂直度为
显然B是一个对称矩阵,其中只有6个未知量(从K看只有5个),将6个未知量表示出来:
将H矩阵表示如下:
我们记vij用h表示
带入上面的h1,h2,h3和B进入下面的公式:
就可以将上面两个等式写成如下形式,一个点代表一个方程,b有6个未知数,显然只需要3个点就可以得到唯一解
或写成下面等式,V为(2*n,6)矩阵n为照片的个数,一个照片对应一个H,n大于等于3就可以得到唯一解b,大于三可以用近似解,令b的模为1。求Vb的模
求出了b,即求出了B,带入即有:
为比例因子为1/s
前面假设的H已经知道,H不知道,这里求解。知道一张照片确定一个H,最小需要4个点。H为内参矩阵和外参矩阵的积。记Z为齐次坐标的尺度因子,为定值。
利用上u和v算出来,由齐次坐标的的性质消去Z
此时,尺度因子Z 已经被消去,因此上式对于同一张图片上所有的角点均成立,像素坐标(u,v)和世界坐标(U,V)已知。(棋盘大小自己决定,在有特征值提取可算,如SIFT算法)。H有9个参数,齐次性去掉一个,剩下8个自由度。当一张图片上的标定板角点数量等于4时,即可求得该图片对应的矩阵。
对上面展开有:
假如我们得到了两幅图片中对应的N个点对(特征点匹配对),那么可以得到如下线性方程组:
写成矩阵形式:
由于单应矩阵H包含了齐次性那个自由度,可设||H||=1约束,因此根据上图的线性方程组,8自由度的H我们至少需要4对对应的点才能计算出单应矩阵。这也回答了前面图像校正中提到的为何至少需要4个点对的根本原因。在真实的应用场景中,我们计算的点对中都会包含噪声。比如点的位置偏差几个像素,甚至出现特征点对误匹配的现象,如果只使用4个点对来计算单应矩阵,那会出现很大的误差。因此,为了使得计算更精确,一般都会使用远大于4个点对来计算单应矩阵。
H已知,则可参考2.3求出K矩阵,同时每张图片的R和T也可以求出。K[r1,r2,T] = H。H和K已知,则r1,r2, T知道 ,r3 = r1×r2。即R和T
从此相机的内参矩阵和外参矩阵均已得到。
基于以上方法的所得结果,需要基于最大似然估计方法进行优化。假定存在于相同模板中n幅图像的m个点均被同样的噪声所破坏,则此时极大似然估计将能求解:表示空间点在对应图片的像素点的实际投影和理论投影之差,应满足最小。
摄像机模型主要将图像的二维点和场景的三维点进行紧密衔接,但在此之中,针孔成像模型应用范围最为广泛,绝大多数应用技术均基于此模型而建立。前面模型的建立和求解秉持小孔成像原理,完成了摄像头的标定,基于镜头观察到的摄像机图像,视为理想化的针孔图像。然而,由于相机光学系统并未选用这种成像,故而往往存在一定的光学畸变误差。这种误差产生的畸变,通常情况下细分为以下三大类别,径向畸变,偏心畸变(切向),薄棱镜畸变。畸变一般有5个参数
在上述内容中,并未对镜头畸变问题进行综合考量,但其通常存在,特别是径向畸变。
径向畸变示意
偏心畸变(切向)示意
径向畸变的修正参数有k1、k2、k3,具体解释如下:
在图像处理中,径向畸变的效应有三种,一种是桶形畸变(barrel distortion),另一种是枕形畸变(pincushion distortion),还有一种是两种的结合叫做胡子畸变(mustache distortion)。为了修正这些畸变,可以使用径向畸变修正参数。
修正参数k1、k2、k3可以由一系列已知点在图像中的实际位置和它们在图像坐标系中的坐标之间的差异得到。这些参数可以用来对图像进行几何校正,使得图像中的点能够更准确地对应到它们在现实世界中的位置。
如下:
等式左边为畸变矫正后的归一化图像坐标,右边的(x,y)是理想的无畸变归一化的图像坐标,r为图像像素点到图像中心点的距离。
切向畸变是一种由于摄像机制造缺陷导致的透镜本身与图像平面不平行而产生的畸变,可以定量描述为:
切向畸变的修正参数包括p1和p2两个参数,这些参数用于描述摄像机制造缺陷导致的透镜本身与图像平面不平行的情况。
如下:
左边的xy是未经修正的图像坐标,即原始坐标系下的坐标,它表示的是摄像机制造缺陷导致的透镜本身与图像平面不平行的情况下的像素位置。
右边的xy是经过切向畸变修正后的图像坐标,它表示的是修正后的像素位置。通过修正,可以使得图像更加接近理想的投影关系,从而减小切向畸变的影响。
前面讲了张正友标定,下面对自己手机的摄像头进行标定。
棋盘可以去tb上购买,但只是自己学习,可以自己打印。这里采用wps表格功能,绘制一个7*7的棋盘,通过表格属性设置棋盘长宽相等(边长具体不影响后面标定,这里设置的为1.5cm*1.5cm)。
颜色填充得到棋盘,转成pdf去打印
拿手机对棋盘拍照,注意:不要放大或广视角,保证拍照比例一致。得9张照片:
现在求(X,Y,Z),将世界坐标系原点设置棋盘内圈的右上角,则坐标为(0,0,0),(1.5,0,0),(3,0,0)....,经验证,棋盘格子尺寸不影响标定。则采用单位坐标,每个内部角点坐标为(0,0,0),(1,0,0),(2,0,0)....,这样一行一行从左到右排下去。
ret,corners = cv2.findChessboardCorners(image,patternSize,flags = None)
此函数能确定图片是否有棋盘图案,并定位棋盘板上的内角点。如果所有的角点被找到且以一定的顺序排列(一行接一行,从左到右),并返回一个非零值。另外,如果没有找到所有的角点,则返回0。
image:输入原始的棋盘板图像。该图像必须是一张8位的灰度图或色彩图。
patternSize:(w,h),棋盘上每一排和每一列的内角数。w=棋盘板一行上黑白块的数量-1,h=棋盘板一列上黑白块的数量-1,例如:7x7的棋盘板,则(w,h)=(6,6)
flags:int,不同的操作标记,能够为0或者下述值的组合:
如果找到返回True和角点在图片上的像素坐标(u,v)
画出角点看看,这个函数主要用于在图像上绘制棋盘格的角点:
cv2.drawChessboardCorners(img,checkerboard_size,corners,ret)
用matplotlib做散点图
为了提高精度对图像中的角点进行亚像素级别的细化,这里可以不采用。
cv2.cornerSubPix(src, window, zeroZone, criteria)
通过上面步骤,我们得到棋盘的空间坐标和对应图像的像素坐标对。我们使用cv2.calibrateCamera()进行标定,这个函数会返回相机的内参数矩阵、畸变系数、旋转矩阵和平移向量。
- ret, mtx, dist, rvecs, tvecs =
- cv.calibrateCamera(obj_points,img_points,gray_shape[::-1],None,None)
obj_points:每张图片对应角点的空间坐标,放在一个列表中。
img_points:每张图片对应角点的像素坐标,放在一个列表中。
gray_shape:灰度照片尺寸,所有照片尺寸应该一样。
返回参数:
得到了相机内参和畸变系数,可以对拍出的照片进行矫正,得到更清晰的图片,但也会剪去一些边界。
使用cv.getOptimalNewCameraMatrix()求得优化后的内参数和畸变系数
再使用cv2.undistort()函数就可以得到去畸变的图像。
下面是摄像机标定全部代码
- import matplotlib.pyplot as plt
- import glob
- import cv2 as cv
- import numpy as np
- def calibration(img_path,size,criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER,30, 0.01)):
- """
- 对通过照片对摄像机进行标定
- :param img_path: 照片坐标
- :param size: 棋盘尺寸(m横,n竖),数方格大小,尽量用正方形
- :param criteria: 终止条件,迭代次数达到30次或残差的绝对值小于0.001 停止
- :return:
- """
- # 棋盘格内的点阵,外面一圈不算
-
- checkerboard_size = (size[0]-1,size[1]-1)
- # 生成棋盘格角点真实空间3D坐标,平面3d坐标,只算内点
- object_points = np.zeros((np.prod(checkerboard_size), 3), dtype=np.float32)
- object_points[:, :2] = np.mgrid[0:checkerboard_size[0], 0:checkerboard_size[1]].T.reshape(-1, 2)
- # print(object_points)
-
- # 用于存储所有图像中的角点坐标
- obj_points = [] # 在真实世界中的3D点
- img_points = [] # 在图像中的2D点
- # 加载所有图像
- gray_shape = []
- images_path = glob.glob(f"{img_path}/*")
-
- for path in images_path:
- img = cv.imread(path) # 读取图像并灰度化
- gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
- # 确定图片是否有棋盘,并定位角点 ret :YES ,corners 为角点在图片上的坐标
- ret, corners = cv.findChessboardCorners(gray, checkerboard_size, None)
-
- gray_shape = gray.shape # 保留灰度图像大小,所有照片应该一样,别放大拍
-
- # 画出来角点看看
- # cv.drawChessboardCorners(img,checkerboard_size,corners,ret)
- # resize_img = cv.resize(img,(600,600))
- # cv.imshow(f"img{img_path}",resize_img)
- # for i in corners:
- # xy = i[0]
- # plt.scatter(xy[0], xy[1])
- # plt.show()
- # continue
-
- if ret == True:
- # 3维点世界坐标放入列表
- obj_points.append(object_points)
- # 寻找亚像素点,没用,不采取该算法,采用则删除下面的 None if True else
- corners2 = None if True else cv.cornerSubPix(gray, corners, (1, 1), (-1, -1), criteria)
- if corners2 is not None: # 找到
- img_points.append(corners2)
- else: # 没找到
- img_points.append(corners)
-
- # 进行摄像机标定 ret 是标定误差
- ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(obj_points,img_points,gray_shape[::-1],None,None)
-
- # 相机矩阵,内部矩阵,内参
- print(f"Camera matrix : {ret}\n")
- print(mtx)
- # np.save("mtx",mtx)
- # 畸变系数
- print("dist : \n")
- # np.save("dist",dist)
- print(dist)
- # 旋转向量
- print("rvecs : \n")
- print(rvecs)
- # 转移向量
- print("tvecs : \n")
- print(tvecs)
-
- # 去畸变
- path = np.random.choice(images_path)
- img = cv.imread(path)
- h, w = img.shape[:2]
- # 得到对应的校正后的 K和ROI, 原来的内参,畸变系数,照片大小,保留比例(和原来相似比例),校正后图片大小
- newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w, h),0.8, (w, h)) # 自由比例参数
- # 矫正图像,使用cv.undistort进行 原图,内参,矫正参数,新的内参
- dst = cv.undistort(img, mtx, dist, None, newcameramtx)
- img = cv.resize(img, (600, 600))
- dst = cv.resize(dst, (600, 600))
- cv.imshow("img",img)
- cv.imshow("dst",dst)
- cv.waitKey(0)
- cv.destroyAllWindows()
-
- if __name__ == '__main__':
- # 输入放照片的地址和棋盘格子数量
- calibration(r"C:\Users\zhouq\Desktop\pic",(7,7))
-
结果:
成功求出了自己手机的摄像头内参,畸变系数和对应照片的外参。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。