- 首先这是 径向畸变+切向畸变,都需要矫正(图片保密)
径向畸变就是沿着透镜半径方向分布的畸变,产生原因是光线在原理透镜中心的地方比靠近中心的地方更加弯曲,这种畸变在短焦镜头中表现更加明显,径向畸变主要包括桶形畸变和枕形畸变两种。
成像仪光轴中心的畸变为0,沿着镜头半径方向向边缘移动,畸变越来越严重。畸变的数学模型可以用主点(principle point)周围的泰勒级数展开式的前几项进行描述,通常使用前两项,即k1和k2,对于畸变很大的镜头,如鱼眼镜头,可以增加使用第三项k3来进行描述,成像仪上某点根据其在径向方向上的分布位置,调节公式为:
实现过程是,对输出图的点做遍历——以1080p的图像为例,从点(0,0)到点(1919,1079),一行一行的遍历——依次找到输出点(x, y)对应的原图点(x0, y0)的像素值,再将(x0, y0)的值赋给(x, y)。如果计算出来的对应的原图的点(x0, y0)不是整数,则用二次线性插值计算此点,然后赋值给(x, y)。
举个一维差值示例:
计算得到,要用x0=0.3位置的点赋值到纠正后的图像的对应点上。
x0=0的点RGB(120, 120, 240),x0=1的点RGB(100, 110, 250)。
那么可以计算x0=0.3位置的
R=120*(1-0.3)+100*0.3=114;
G=120*(1-0.3)+110*0.3=117;
B=240*(1-0.3)+250*0.3=243
完成所有遍历后,就得到了输出图像。
下图是距离光心不同距离上的点经过透镜径向畸变后点位的偏移示意图,可以看到,距离光心越远,径向位移越大,表示畸变也越大,在光心附近,几乎没有偏移。
注:OpenCV官网上对于这部分的解释是错了,原图和输出图像说反了。但其软件模型没有问题
3. 畸变模型
同时对径向、切向畸变消除就是将两组式子合并。
最终可以得到5个畸变参数
畸变参数的一般顺序是k1,k2,p1,p2,k3。之所以把k3放在最后其实也很容易理解,因为前面说了一般k1,k2用来处理径向畸变足矣,k3相对而言用的比较少。
在Opencv中他们被排列成一个5*1的矩阵,经常被定义为Mat矩阵的形式,如
Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0))
这5个参数就是相机标定中需要确定的相机的5个畸变系数。求得这5个参数后,就可以校正由于镜头畸变引起的图像的变形失真,下图显示根据镜头畸变系数校正后的效果:
二、物象坐标映射转换
opencv中,函数findhomography可以找到这一单应性矩阵。
Mat findHomography( const Mat& srcPoints, const Mat& dstPoints, Mat& status, int method=0, double ransacReprojThreshold=3 ); Mat findHomography( const Mat& srcPoints, const Mat& dstPoints, vector<uchar>& status, int method=0, double ransacReprojThreshold=3 ); Mat findHomography( const Mat& srcPoints, const Mat& dstPoints, int method=0, double ransacReprojThreshold=3 );
1、srcPoints,dstPoints为CV_32FC2或者vector类型
2、method:0表示使用所有点的常规方法;CV_RANSAC 基于RANSAC鲁棒性的方法;CV_LMEDS 最小中值鲁棒性方法
3、ransacReprojThreshod 仅在RANSAC方法中使用,一个点对被认为是内层围值(非异常值)所允许的最大投影误差。如果srcPoints和dstPoints单位是像素,通常意味着在某些情况下这个参数的范围在1到10之间。
4、status,可选的输出掩码,用在CV_RANSAC或者CV_LMEDS方法中。注意输入掩码将被忽略。
如果参数method设置为默认值0,该函数使用一个简单的最小二乘方案来计算初始的单应性估计。然而,如果不是所有的点对(srcPoints,dstPoints)都适应这个严格的透视变换。(也就是说,有一些异常值),这个初始估计值将很差。在这种情况下,我们可以使用两个鲁棒性算法中的一个。RANSCA和LMEDS这两个方法都尝试不同的随机的相对应点对的子集,每四对点集一组,使用这个子集和一个简单的最小二乘算法来估计单应性矩阵,然后计算得到单应性矩阵的质量quality/goodness。(对于RANSAC方法是内层围点的数量,对于LMeDs是中间的重投影误差)。然后最好的子集用来产生单应性矩阵的初始化估计和inliers/outliers的掩码。
在第二部分,求得的单应性矩阵,是由两部分构成的:内参矩阵和外参矩阵,在OpenCV的3D重建中,对摄像机的内参外参有讲解:
外参:摄像机的旋转平移属于外参,用于描述相机在静态场景下相机的运动,或者在相机固定时,运动物体的刚性运动。因此,在图像拼接或者三维重建中,就需要使用外参来求几幅图像之间的相对运动,从而将其注册到同一个坐标系下面来。
内参:下面给出了内参矩阵,需要注意的是,真实的镜头还会有径向和切向畸变,而这些畸变是属于相机的内参的。
摄像机内参矩阵:
fx s x0 K = 0 fy y0 0 0 1
其中,fx,fy为焦距,一般情况下,二者相等,x0、y0为主点坐标(相对于成像平面),s为坐标轴倾斜参数,理想情况下为0。
摄像机外参矩阵:包括旋转矩阵和平移矩阵,旋转矩阵和平移矩阵共同描述了如何把点从世界坐标系转换到摄像机坐标系。
旋转矩阵:描述了世界坐标系的坐标轴相对于摄像机坐标轴的方向。
平移矩阵:描述了在摄像机坐标系下,空间原点的位置。
1. 准备标定图片
标定图片需要使用标定板在不同位置、不同角度、不同姿态下拍摄,最少需要3张,以10~20张为宜。标定板需要是黑白相间的矩形构成的棋盘图,制作精度要求较高,如下图所示:
(等下等下)
相机畸变矫正
在双目立体视觉中,常常会使用张正友的相机标定方法,得到相机的参数(相机内参和畸变),进而矫正由于相机透镜制造工艺等因素造成的图像畸变。在Opencv 和 Matlab 中都有封装好的函数或者标定工具箱供我们使用,可以利用这些标定得到的数据进行畸变矫正。
- 内参矩阵
A = [fx, 0, cx; 0, fy, cy; 0, 0, 1]
- 畸变系数
D = [k1, k2, p1, p2, k3](通常没有k3)
- 参数说明
fx = f/dx; fy = f/dy;
1、dx和dy是相机单个感光单元芯片的长度和宽度,是一个物理尺寸,有时候会有dx=dy,这时候感光单元是一个正方形;
2、cx和cy分别代表相机感光芯片的中心点在x和y方向上可能存在的偏移;
3、f代表相机的焦距;focal 焦距,fx,fy为焦距,一般情况下,二者相等;
4、k1, k2, k3为径向畸变系数;
5、p1, p2为切向畸变系数。
import os
import cv2
import numpy as np
from tqdm import tqdmdef undistort(frame):fx = 怕有保密把重要参数姑且删掉了cx = fy = cy = k1, k2, p1, p2, k3 = # 相机坐标系到像素坐标系的转换矩阵(内参矩阵)k = np.array([[fx, 0, cx],[0, fy, cy],[0, 0, 1]])# 畸变系数 # "distort":[0.0489211,-0.00395775,-0.00317359,0.000753933],d = np.array([k1, k2, p1, p2, k3])h, w = frame.shape[:2]mapx, mapy = cv2.initUndistortRectifyMap(k, d, None, k, (w, h), 5)return cv2.remap(frame, mapx, mapy, cv2.INTER_LINEAR)# 对目录下的所有图片做畸变矫正,并把畸变矫正后的图片保存下来
def distortion_correction_imgs(input_dir, output_dir):in_imgs = os.listdir(input_dir)for img_name in tqdm(in_imgs):image = cv2.imread(os.path.join(input_dir, img_name))distroted_img = undistort(image)cv2.imwrite(os.path.join(output_dir, img_name), distroted_img)if __name__ == '__main__':input_dir = "D:/a_pic/i"output_dir = "D:/a_pic/o"distortion_correction_imgs(input_dir, output_dir)cv2.waitKey()
cv2.destroyAllWindows()
官方文档:OpenCV: Fisheye camera model