【计算机视觉4】相机标定

一.原理:


1.四个坐标系:

世界坐标系、相机坐标系、像素坐标系、成像平面坐标系。
我们可以把现实生活遇到的任何事物用坐标系表示出来,也可以用坐标系表示整个世界,于是便建立起了世界坐标系。想象一下,摄像机拍摄的是一张二维图片,因此整个摄像机可以用一个坐标系去标识它获取到的某个物体的位置,这是相机坐标系。像素坐标系就是相片的坐标系。成像平面坐标系类似于像素坐标系。
原本我们期望的拍摄效果:是每个坐标系中的像素都相互对应,类似于一种一元一次方程,但由于镜头或者其他关系,现在这条“直线”弯了,得到的图像也会出现“弯曲”,因此我们需要把它矫正。

2.相机参数

相机都有不同的内部参数、外部参数;

内部参数:有一个参数矩阵(fx,fy,cx,cy)和一个畸变系数(三个径向k1,k2,k3;两个切向p1,p2);内部参数是唯一的,就是一部相机只有一组内部参数。
相机将场景中的三维点变换为图像中的二维点,也就是各个坐标系变换的组合,可将变换过程整理为矩阵相乘的形式:

【计算机视觉4】相机标定_第1张图片

3.标定结束

标定完成后,你会得到标定的内部参数,标定完之后就可以直接用内参数和畸变参数得到畸变校正图像。接下来就可以使用OpenCV了,即用内参数和畸变参数作为initUndistortRectifyMap()函数的输入,得到原图像与畸变校正图像的x,y坐标映射关系,即两个变换矩阵。再以这两个变换矩阵作为remap()函数的输入,得到畸变校正图像。

二.步骤:

  1. 准备一张打印好的棋盘格,对棋盘格进行不同角度的拍摄,10-20张图片为宜;
  2. 对每张图片提取角点信息;
  3. 在图片中画出提取出的角点;
  4. 相机标定;
  5. 对标定结果评价,计算误差;
  6. 使用标定结果对原图片进行矫正;

三.数据采集:

【计算机视觉4】相机标定_第2张图片

 

四.相机标定

4.1 角点提取

(获取角点两个坐标系下的坐标,作为求解方程中的已知量)

首先,基于cv2.findChessboardCorners寻找棋盘格角点:

# 定位角点
def find_chessboard_cor(img):
    # 转为灰度图
    gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # OpenCV内置函数提取棋盘格角点, (11,8)为棋盘格尺寸-1(12x9)
    is_success, corner = cv2.findChessboardCorners(gray_img, (11,8), None)
    # 计算亚像素时停止迭代的标准
    # 后者表示迭代次数达到了最大次数时停止,前者表示角点位置变化的最小值已经达到最小时停止迭代
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
    # 亚像素角点检测,提高角点定位精度, (7, 7)为考虑角点周围区域的大小
    corner = cv2.cornerSubPix(gray_img, corner, (7, 7), (-1, -1), criteria) 
    return is_success, corner

值得注意的是,这里我们必须使用cv2.cornerSubPix再进行一次迭代定位亚像素角点,使得角点的定位更为精确。

基于cv2.drawChessboardCorners可视化棋盘格角点:

# 可视化角点
def draw_chessboard_cor(img, cor, is_success):
    cv2.drawChessboardCorners(img, (11,8), cor, is_success)
    cv2.imshow('cor', img)
    cv2.waitKey(50)

OpenCV提取棋盘角点可视化:

【计算机视觉4】相机标定_第3张图片

 

别忘了我们提取了棋盘格角点坐标的目的:这一步是为了获取角点在图像坐标系中的坐标。为了求解相机内外参矩阵,紧接着我们还需要获取棋盘格在原始世界坐标系中的坐标。

值得一提的是,无论是固定相机移动棋盘格进行拍摄,还是固定棋盘格移动相机进行拍摄,在张正友标定法的假设中,棋盘格永远处在世界坐标系Z=0的平面上,棋盘格最左上角的角点为世界坐标系原点,往右为x轴正方向,往下为y轴正方向:

【计算机视觉4】相机标定_第4张图片

 由于同一张标定板的规格不会改变,因此,世界坐标系通过人为定义即可:

        # 注:相机参数的计算只要求角点之间的世界坐标比例一致,因此可以单位化
        world_coord = np.zeros((w * h, 3), np.float32)
        world_coord[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)


 

世界坐标(单位化):

[[ 0. 0. 0.]
[ 1. 0. 0.]
[ 2. 0. 0.]
[ 3. 0. 0.]
[ 4. 0. 0.]
[ 5. 0. 0.]
[ 6. 0. 0.]
[ 7. 0. 0.]

… …

]]

shape = (88, 3) # 每个棋盘格上提取11*8=88个角点

4.2 参数求解

 # 求解摄像机的内在参数和外在参数
    # ret 非0表示标定成功 mtx 内参数矩阵,dist 畸变系数,rvecs 旋转向量,tvecs 平移向量
    # 注:求解的结果的单位为像素,若想化为度量单位还需乘上每个像素代表的实际尺寸(如:毫米/像素)
   


# 求解内外参数
def CamCalibrate(w, h, num, root):
    # 图像缩放比例(如果你的图像进行了缩放,与实际拍摄的分辨率不一致,最终求得的参数需要乘上这个比例进行校正)
    ratio = 1 # 3648 / 1920   
    world, cam = [], []

    # 多张图像进行标定,减小误差:
    for i in range(num):
        img = cv2.imread(root + str(i+1)+'.jpg')
        # img,_,_ = utils.auto_reshape(img, 1920)
        # 定位角点
        is_success, cam_coord = find_chessboard_cor(img)
        print('第'+str(i+1)+'张角点提取完毕, 角点数 =',cam_coord.shape[0])
        # 可视化角点
        # draw_chessboard_cor(img, cam_coord, is_success)
        # 角点的世界坐标:
        # 注:相机参数的计算只要求角点之间的世界坐标比例一致,因此可以单位化
        world_coord = np.zeros((w * h, 3), np.float32)
        world_coord[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)
        world_coord[:,1] = -world_coord[:,1]
        # world_coord[:,:2] = np.mgrid[0:w*len:len,0:h*len:len].T.reshape(-1,2)
        # 将世界坐标与像素坐标加入待求解系数矩阵
        world.append(world_coord)
        cam.append(cam_coord)


    # 求解摄像机的内在参数和外在参数
    # ret 非0表示标定成功 mtx 内参数矩阵,dist 畸变系数,rvecs 旋转向量,tvecs 平移向量
    # 注:求解的结果的单位为像素,若想化为度量单位还需乘上每个像素代表的实际尺寸(如:毫米/像素)
    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(world, cam, (img.shape[1], img.shape[0]), None, None)
    rvecs = np.array(rvecs).reshape(-1,3)
    tvecs = np.array(tvecs).reshape(-1,3)
    # 单位:像素(1像素=??mm)
    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)  # 平移向量  # 外参数
    
    np.save('./param/mtx.npy',mtx)
    np.save('./param/dist.npy',dist)
    np.save('./param/rvecs.npy',rvecs)
    np.save('./param/tvecs.npy',tvecs)
    np.save('./param/world.npy',np.array(world))
    np.save('./param/cam.npy',np.array(cam))
    return ret, mtx, dist, rvecs, tvecs

4.3 结果

标定结果 ret: 1.4146770040984205
内参矩阵 mtx:
[[2.88760072e+03 0.00000000e+00 1.82151647e+03]
[0.00000000e+00 2.88741538e+03 1.37233666e+03]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
畸变系数 dist:
[[ 0.01488259 0.03388964 -0.00044845 -0.00178608 0.03024054]]
旋转向量(外参) rvecs:
[[ 2.77705978 -0.46989843 0.1686412 ]
[-2.8145649 -0.91512141 -0.43737601]
… …
[ 2.67447733 0.66658815 -0.11254641]
[ 2.818184 -0.02291704 0.51413859]]
平移向量(外参) tvecs:
[[-5.06957615 -2.43006408 16.33928058]
[ 0.29829902 -4.17287918 13.05665933]
… …
[-3.8811355 -4.39040007 19.95916291]
[-4.53034049 -4.16521408 14.12011733]]
 

你可能感兴趣的:(计算机视觉,人工智能)