python+OpenCV 相机标定

相机标定

目录

  • 原理
  • 相机标定结果
  • 流程简介
  • 实验过程
  • 总结
  • 代码及调试问题

相机标定在机器人视觉和畸变校正上都是很关键的一部分,接下来用张正友相机标定法标定我的手机(Vivo xpaly5A)后置摄像头。

原理

首先先简单的了解一下相机标定的原理。
摄像机标定(Camera calibration)简单来说是从世界坐标系换到图像坐标系的过程,也就是求最终的投影矩阵 P 的过程。

一般来说,标定的过程分为两个部分:

  • 第一步是从世界坐标系转换为相机坐标系,这一步是三维点到三维点的转换,包括 R,t (相机外参)等参数;
  • 第二部是从相机坐标系转为图像坐标系,这一步是三维点到二维点的转换,包括 K(相机内参)等参数;
世界坐标系转换为相机坐标系

python+OpenCV 相机标定_第1张图片
公式表达如下:
在这里插入图片描述
其中:
python+OpenCV 相机标定_第2张图片

相机坐标系转换为图像坐标系

python+OpenCV 相机标定_第3张图片
其中:

  • C C C 点表示camera centre,即相机的中心点,也是相机坐标系的中心点;

  • Z Z Z 轴表示principal axis,即相机的主轴;

  • p p p 点所在的平面表示image plane,即相机的像平面,也就是图片坐标系所在的二维平面;

  • p p p 点表示principal point,即主点,主轴与像平面相交的点;

  • C C C 点到 p p p 点的距离,也就是右边图中的 f f f 表示focal length,即相机的焦距;

  • 像平面上的 x x x y y y 坐标轴是与相机坐标系上的 X X X Y Y Y 坐标轴互相平行的;

  • 相机坐标系是以 X X X Y Y Y Z Z Z三个轴组成的且原点在 C C C 点,度量值为米(m);

  • 像平面坐标系是以 x x x y y y(小写)两个轴组成的且原点在 p p p 点,度量值为米(m);

  • 图像坐标系一般指图片相对坐标系,在这里可以认为和像平面坐标系在一个平面上,不过原点是在图片的角上,而且度量值为像素的个数(pixel);

通过对上面坐标系的介绍,可以得到以下的转换公式:
在这里插入图片描述
加上偏移量:
python+OpenCV 相机标定_第4张图片

如上图所示,其中主点 p p p 是像平面坐标系的原点,但在图像坐标系中的位置为 ( p x , p y ) (p_{x},p_{y}) (px,py),在这里,图形坐标系的原点是图片的左下角,所以可以得到:
在这里插入图片描述
把这个换成矩阵计算:
在这里插入图片描述
整理一下:
在这里插入图片描述
最后,可以得到矩阵K,就是相机内参:
在这里插入图片描述
投影矩阵P(在这里可以认为旋转矩阵 R 为单位矩阵 I,平移矩阵 t 都为0):
在这里插入图片描述
最终的转换式:
从上面两个转换的过程,我们可以得到从世界坐标轴转换到图像的过程可以把投影矩阵P表示为:
在这里插入图片描述
在这里插入图片描述
在这里, K K K 一般称为相机内参(intrinsic parameters),描述了相机的内部参数,包括焦距 f f f、主点 p p p 的位置、以及像素与真实环境的大小比例等,这个是固有属性,是提供好的; R R R t t t 称为相机外参(extrinsic parameters), R R R 在这里是旋转矩阵,可以转换为三维的旋转向量,分别表示绕 x x x y y y z z z 三个轴的旋转角度, t t t 目前就是一个平移向量,分别表示在 x x x y y y z z z 三个方向上的平移量。

畸变参数

在几何光学和阴极射线管(CRT)显示中,畸变(distortion) 是对直线投影(rectilinear projection)的一种偏移。简单来说直线投影是场景内的一条直线投影到图片上也保持为一条直线。那畸变简单来说就是一条直线投影到图片上不能保持为一条直线了,这是一种光学畸变(optical aberration)。
畸变一般可以分为两大类,包括径向畸变和切向畸变。主要的一般径向畸变有时也会有轻微的切向畸变。

该部分参考:https://blog.csdn.net/honyniu/article/details/51004397

相机标定的结果

了解完一定原理之后,开始实践一下。
首先,要明确通过相机标记我们需要得到以下的参数:

  • 摄像机的内参和外参矩阵,内参和外参系数可以对之后相机拍摄的图像就进行矫正,得到畸变相对很小的图像。
  • 每一幅标定图像的选择和平移矩阵

流程

相机标定的流程可以简单的表述为:

  1. 准备图片。这里要注意,拍摄不同角度的图片时只需要对焦一次就好了,如果反复对焦,得到的每张图片的焦距就会一直变化。
  2. 对每张图片提取角点信息
  3. 对每张图片,进一步提取亚像素角点信息
  4. 相机标定
  5. 畸变矫正

实验数据准备

主要准备图片即可,10张左右的标定图片,黑白棋盘格即可。
可以给图片进行一定的缩小,加快运算速度。
拍摄过程中,如果方格纸是横着摆的就一直横着摆着拍,因为横纵角点个数是要提前设置好的。

实验过程

  1. 提取角点:
    使用函数:findChessboardCorners(image,(w,h),None);
    第一个参数为图片,第二个为图片横纵角点的个数。

  2. 找寻亚像素角点:cornerSubPix(gray, corners, (winsize, winsize), (-1, -1), criteria);
    winsize为搜索窗口边长的一半。
    zeroZone:搜索区域中间的dead region边长的一半,有时用于避免自相关矩阵的奇异性。如果值设为(-1,-1)则表示没有这个区域。
    criteria:角点精准化迭代过程的终止条件。也就是当迭代次数超过criteria.maxCount,或者角点位置变化小于criteria.epsilon时,停止迭代过程。

  3. 可以使用该函数把角点画出来:drawChessboardCorners(image, (w, h), corners, ret);
    其中corners和ret为第一个函数的输出值。
    效果如下:

  4. 标定

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, size, None, None);

其中:
obj_points = [] # 存储3D点
img_points = [] # 存储2D点

实验结果

mtx 内参数矩阵
dist 畸变系数
rvecs 旋转向量
tvecs 平移向量

python+OpenCV 相机标定_第5张图片
python+OpenCV 相机标定_第6张图片
准备了9张图片,可以有两张无论如何也不能完成标定,这两张和其他图片并没有很大的差别,角度光线也都很正常。
畸变矫正
在这里插入图片描述
左图为矫正过的图片,右边为原图

总结

通过相机标定得到了,vivo xplay5A的后置摄像头内置参数矩阵如下:
在这里插入图片描述
也得到了每张图片的外参数,并且实现了畸变矫正,虽然效果极其不明显。

代码及调试问题

import cv2
import numpy as np
import glob

# 设置寻找亚像素角点的参数,采用的停止准则是最大循环次数30和最大误差容限0.001
criteria = (cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 30, 0.001)

# 获取标定板角点的位置
objp = np.zeros((6 * 8, 3), np.float32)
objp[:, :2] = np.mgrid[0:8, 0:6].T.reshape(-1, 2)  # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y

obj_points = []  # 存储3D点
img_points = []  # 存储2D点

images = glob.glob("D:/pycharmfile/biaoding/image/*.jpg")
i=0;
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    size = gray.shape[::-1]
    ret, corners = cv2.findChessboardCorners(gray, (8, 6), None)
    #print(corners)

    if ret:

        obj_points.append(objp)

        corners2 = cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria)  # 在原角点的基础上寻找亚像素角点
        #print(corners2)
        if [corners2]:
            img_points.append(corners2)
        else:
            img_points.append(corners)

        cv2.drawChessboardCorners(img, (8, 6), corners, ret)  # 记住,OpenCV的绘制函数一般无返回值
        i+=1;
        cv2.imwrite('conimg'+str(i)+'.jpg', img)
        cv2.waitKey(1500)

print(len(img_points))
cv2.destroyAllWindows()

# 标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, size, None, None)

print("ret:", ret)
print("mtx:\n", mtx) # 内参数矩阵
print("dist:\n", dist)  # 畸变系数   distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print("rvecs:\n", rvecs)  # 旋转向量  # 外参数
print("tvecs:\n", tvecs ) # 平移向量  # 外参数

print("-----------------------------------------------------")

img = cv2.imread(images[2])
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))#显示更大范围的图片(正常重映射之后会删掉一部分图像)
print (newcameramtx)
print("------------------使用undistort函数-------------------")
dst = cv2.undistort(img,mtx,dist,None,newcameramtx)
x,y,w,h = roi
dst1 = dst[y:y+h,x:x+w]
cv2.imwrite('calibresult3.jpg', dst1)
print ("方法一:dst的大小为:", dst1.shape)


参考:https://blog.csdn.net/firemicrocosm/article/details/48594897#

调试问题:

  1. 可以从上面的图看到我的图片角点是 8 ∗ 6 8*6 86的,所以源代码中所有跟 8 ∗ 6 8*6 86的相关数据都要改成相符合当前棋盘角点数量的值。调试过程可以在下方的图示的地方输出ret,如果它的输出值为true,说明你已经找到了正确的角点数。
    在这里插入图片描述
  2. 如果调试过程出现这样的错误:
The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

需要在光标处,把corners2加上大括号变成[corners2]
python+OpenCV 相机标定_第7张图片
python+OpenCV 相机标定_第8张图片

你可能感兴趣的:(计算机视觉)