【相机标定】相机标定中的坐标变换,内外参求解,畸变校正,标定代码

本文循序渐进,从坐标系变换、求解单应 H {\color{#86C166}{H}} H、求解内参 K {\color{#E16B8C}{K}} K、求解外参 [ R 1 R 2 T ] \begin{bmatrix}{\color{#2EA9DF}{R_1}}&{\color{#2EA9DF}{R_2}}&{\color{#2EA9DF}{T}} \end{bmatrix} [R1R2T]、径向畸变校正 [ k 1 k 2 k 3 ] \begin{bmatrix}{\color{#F75C2F}{k_1}}&{\color{#F75C2F}{k_2}}&{\color{#F75C2F}{k_3}} \end{bmatrix} [k1k2k3]、标定代码的6个方面概括性的总结了传统相机校正的理论推导和工程应用,文章比较精炼,适合备为复习和应用参考。

1 坐标系变换

设世界坐标系 X W X_W XW,相机坐标系 X C X_C XC,图像物理坐标系 x x x,图像像素坐标系 u u u.

则世界坐标系与相机坐标系有以下变换关系:

[ X C Y C Z C 1 ] = [ R 3 × 3 T 3 × 1 0 1 ] [ X W Y W Z W 1 ] \begin{bmatrix}X_C\\Y_C\\Z_C\\1\end{bmatrix}=\begin{bmatrix}{\color{#2EA9DF}{R_{3\times3}}}&{\color{#2EA9DF}{T_{3\times1}}}\\0&1 \end{bmatrix}\begin{bmatrix}X_W\\Y_W\\Z_W\\1\end{bmatrix} XCYCZC1 =[R3×30T3×11] XWYWZW1

相机坐标系与图像物理坐标系有以下变换关系, f f f为物理焦距:

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\begin{bmatrix}x\\y\\1\end{bmatrix}=\begin{bmatrix}f&0&0&0\\0&f&0&0\\0&0&1&0\end{bmatrix}\begin{bmatrix}X_C\\Y_C\\Z_C\\1\end{bmatrix} ZC xy1 = f000f0001000 XCYCZC1

图像物理坐标系与图像像素坐标系有以下变换关系, a a a为像元尺寸, ( u 0 , v 0 ) (u_0,v_0) (u0,v0)为像平面中心像素坐标:

[ u v 1 ] = [ 1 a x 0 u 0 0 1 a y v 0 0 0 1 ] [ x y 1 ] \begin{bmatrix}u\\v\\1\end{bmatrix}=\begin{bmatrix}\frac{1}{a_x}&0&u_0\\0&\frac{1}{a_y}&v_0\\0&0&1\end{bmatrix}\begin{bmatrix}x\\y\\1\end{bmatrix} uv1 = ax1000ay10u0v01 xy1

综合起来,世界坐标系与图像像素坐标系有以下变换关系(像素焦距 F x = f a x , F y = f a y F_x=\frac{f}{a_x},F_y=\frac{f}{a_y} Fx=axf,Fy=ayf):

Z C [ u v 1 ] = [ F x 0 u 0 0 0 F y v 0 0 0 0 1 0 ] [ R 3 × 3 T 3 × 1 0 1 ] [ X W Y W Z W 1 ] Z_C\begin{bmatrix}u\\v\\1\end{bmatrix}=\begin{bmatrix}F_x&0&u_0&0\\0&F_y&v_0&0\\0&0&1&0\end{bmatrix}\begin{bmatrix}{\color{#2EA9DF}{R_{3\times3}}}&{\color{#2EA9DF}{T_{3\times1}}}\\0&1 \end{bmatrix}\begin{bmatrix}X_W\\Y_W\\Z_W\\1\end{bmatrix} ZC uv1 = Fx000Fy0u0v01000 [R3×30T3×11] XWYWZW1

简化后有:

Z C [ u v 1 ] = [ F x 0 u 0 0 F y v 0 0 0 1 ] [ R 3 × 3 T 3 × 1 ] [ X W Y W Z W 1 ] = K [ R 3 × 3 T 3 × 1 ] [ X W Y W Z W 1 ] Z_C\begin{bmatrix}u\\v\\1\end{bmatrix}=\begin{bmatrix}F_x&0&u_0\\0&F_y&v_0\\0&0&1\end{bmatrix}\begin{bmatrix}{\color{#2EA9DF}{R_{3\times3}}}&{\color{#2EA9DF}{T_{3\times1}}} \end{bmatrix}\begin{bmatrix}X_W\\Y_W\\Z_W\\1\end{bmatrix}={\color{#E16B8C}{K}}\begin{bmatrix}{\color{#2EA9DF}{R_{3\times3}}}&{\color{#2EA9DF}{T_{3\times1}}} \end{bmatrix}\begin{bmatrix}X_W\\Y_W\\Z_W\\1\end{bmatrix} ZC uv1 = Fx000Fy0u0v01 [R3×3T3×1] XWYWZW1 =K[R3×3T3×1] XWYWZW1

Z W ≡ 0 Z_W\equiv0 ZW0时(e.g. 对象是标定板),有以下单应变换:

[ u v 1 ] = 1 Z C K [ R 1 R 2 T ] [ X W Y W 1 ] = H [ X W Y W 1 ] = [ H 1 H 2 H 3 ] [ X W Y W 1 ] \begin{bmatrix}u\\v\\1\end{bmatrix}=\frac{1}{Z_C}{\color{#E16B8C}{K}}\begin{bmatrix}{\color{#2EA9DF}{R_1}}&{\color{#2EA9DF}{R_2}}&{\color{#2EA9DF}{T}} \end{bmatrix}\begin{bmatrix}X_W\\Y_W\\1\end{bmatrix}={\color{#86C166}{H}}\begin{bmatrix}X_W\\Y_W\\1\end{bmatrix}=\begin{bmatrix}{\color{#86C166}{H_1}}&{\color{#86C166}{H_2}}&{\color{#86C166}{H_3}}\end{bmatrix}\begin{bmatrix}X_W\\Y_W\\1\end{bmatrix} uv1 =ZC1K[R1R2T] XWYW1 =H XWYW1 =[H1H2H3] XWYW1

2 求解单应 H {\color{#86C166}{H}} H

其中 H {\color{#86C166}{H}} H有9个元素,但其中一个元素作为齐次参数(令 h 33 {\color{#86C166}{h_{33}}} h33=1),固只需要求解其中的8个未知参数,

也就说,单张图片至少要检测4个角点即获取4对 ( u , v , 1 ) − ( X W , Y W , 1 ) (u,v,1)-(X_W,Y_W,1) (u,v,1)(XW,YW,1)的关系式得到8个方程来求解 H H H的数值.

3 求解内参 K {\color{#E16B8C}{K}} K

1 Z C K [ R 1 R 2 T ] = [ H 1 H 2 H 3 ] \frac{1}{Z_C}{\color{#E16B8C}{K}}\begin{bmatrix}{\color{#2EA9DF}{R_1}}&{\color{#2EA9DF}{R_2}}&{\color{#2EA9DF}{T}} \end{bmatrix}=\begin{bmatrix}{\color{#86C166}{H_1}}&{\color{#86C166}{H_2}}&{\color{#86C166}{H_3}}\end{bmatrix} ZC1K[R1R2T]=[H1H2H3]有:

{ R 1 = Z C K − 1 , R 2 = Z C K − 1 H 2 \begin{cases}{\color{#2EA9DF}{R_1}}=Z_C{\color{#E16B8C}{{\color{#E16B8C}{K^{-1}}}}},\\{\color{#2EA9DF}{R_2}}=Z_C{\color{#E16B8C}{K^{-1}}} {\color{#86C166}{H_2}}\end{cases} {R1=ZCK1,R2=ZCK1H2

R 1 T R 2 = 0 {\color{#2EA9DF}{R_1^T}}{\color{#2EA9DF}{R_2}}=0 R1TR2=0 R 1 T R 1 = R 2 T R 2 {\color{#2EA9DF}{R_1^T}}{\color{#2EA9DF}{R_1}}={\color{#2EA9DF}{R_2^T}}{\color{#2EA9DF}{R_2}} R1TR1=R2TR2有:

{ H 1 T K − T K − 1 H 2 = 0 , H 1 T K − T K − 1 H 1 = H 2 T K − T K − 1 H 2 \begin{cases}{\color{#86C166}{H_1^T}}{\color{#E16B8C}{K^{-T}}}{\color{#E16B8C}{K^{-1}}}{\color{#86C166}{H_2}}=0,\\{\color{#86C166}{H_1^T}}{\color{#E16B8C}{K^{-T}}}{\color{#E16B8C}{K^{-1}}}{\color{#86C166}{H_1}}={\color{#86C166}{H_2^T}}{\color{#E16B8C}{K^{-T}}}{\color{#E16B8C}{K^{-1}}}{\color{#86C166}{H_2}}\end{cases} {H1TKTK1H2=0,H1TKTK1H1=H2TKTK1H2

因为 K {\color{#E16B8C}{K}} K中有4个未知参数,则至少需要2张图片,每张图片提供一组上述的两个方程,构成4个方程,此时将构造出方程 A x = b Ax=b Ax=b x x x为4个未知参数构成的向量( x x x等效为内参 K {\color{#E16B8C}{K}} K,同时 A , b A,b A,b表示一些常量).

使用最小二乘法最小化方程平方误差,由此可以求得 K {\color{#E16B8C}{K}} K

m i n    ∣ ∣ A x − b ∣ ∣ 2 ⇒ \mathbf{min}\;||Ax-b||^2\quad\Rightarrow min∣∣Axb2

A T ( A x − b ) = 0 ⇒ A^T(Ax-b)=0\quad\Rightarrow AT(Axb)=0

x = ( A T A ) − 1 A T b x=(A^TA)^{-1}A^Tb x=(ATA)1ATb

4 求解外参 [ R 1 R 2 T ] \begin{bmatrix}{\color{#2EA9DF}{R_1}}&{\color{#2EA9DF}{R_2}}&{\color{#2EA9DF}{T}} \end{bmatrix} [R1R2T]

对于每一张图片,其外参都不一样.

R 1 T R 1 = 1 {\color{#2EA9DF}{R_1^T}}{\color{#2EA9DF}{R_1}}=1 R1TR1=1有:

Z C 2 H 1 T K − T K − 1 H 1 = 1 Z_C^2{\color{#86C166}{H_1^T}}{\color{#E16B8C}{K^{-T}}}{\color{#E16B8C}{K^{-1}}}{\color{#86C166}{H_1}}=1 ZC2H1TKTK1H1=1

由此可以解有:

Z C = 1 ∣ ∣ K − 1 H 1 ∣ ∣ Z_C=\frac{1}{||{\color{#E16B8C}{K^{-1}}}{\color{#86C166}{H_1}}||} ZC=∣∣K1H1∣∣1

由此有:

[ R 1 R 2 T ] = Z C K − 1 [ H 1 H 2 H 3 ] \begin{bmatrix}{\color{#2EA9DF}{R_1}}&{\color{#2EA9DF}{R_2}}&{\color{#2EA9DF}{T}} \end{bmatrix}=Z_C{\color{#E16B8C}{K^{-1}}}\begin{bmatrix}{\color{#86C166}{H_1}}&{\color{#86C166}{H_2}}&{\color{#86C166}{H_3}}\end{bmatrix} [R1R2T]=ZCK1[H1H2H3]

5 径向畸变的校正

径向畸变是由透镜自身引起的,不可忽略,而切向畸变一般是由安装引起的,可以忽略。

对于径向畸变,有以下校正公式, r r r为像素到像素中心的距离:

{ u c o r r = u ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) , v c o r r = v ( 1 + k 1 r 2 + k 2 r 4 + k 3 r 6 ) \begin{cases}u_{corr}=u(1+{\color{#F75C2F}{k_1}}r^2+{\color{#F75C2F}{k_2}}r^4+{\color{#F75C2F}{k_3}}r^6),\\v_{corr}=v(1+{\color{#F75C2F}{k_1}}r^2+{\color{#F75C2F}{k_2}}r^4+{\color{#F75C2F}{k_3}}r^6)\end{cases} {ucorr=u(1+k1r2+k2r4+k3r6),vcorr=v(1+k1r2+k2r4+k3r6)

对于切向畸变,有以下校正公式, r r r为像素到像素中心的距离:

{ u c o r r = u + [ 2 p 1 u v + p 2 ( r 2 + 2 u 2 ) ] , v c o r r = v + [ 2 p 2 u v + p 1 ( r 2 + 2 v 2 ) ] \begin{cases}u_{corr}=u+[2p_1uv+p_2(r^2+2u^2)],\\v_{corr}=v+[2p_2uv+p_1(r^2+2v^2)]\end{cases} {ucorr=u+[2p1uv+p2(r2+2u2)],vcorr=v+[2p2uv+p1(r2+2v2)]

设以下符号表示:

  • 图像像素坐标系点坐标: m m m ( u , v ) (u,v) (u,v), 世界坐标系点坐标: M M M ( X W , Y W ) (X_W,Y_W) (XW,YW)
  • 图像的个数为 n n n,每张图像的角点数为 m m m
  • m ^ i j = f ( K , R i , T i , M i j ) \hat m_{ij}=f({\color{#E16B8C}{K}},{\color{#2EA9DF}{R_i}},{\color{#2EA9DF}{T_i}},M_{ij}) m^ij=f(K,Ri,Ti,Mij)为在不考虑径向畸变时,用第 i i i张图像的第 j j j个角点的世界坐标经重投影函数 f f f后得到的像素坐标估计 m ^ i j \hat m_{ij} m^ij
  • m ^ i j = g ( K , R i , T i , k 1 , k 2 , k 3 , M i j ) \hat m_{ij}=g({\color{#E16B8C}{K}},{\color{#2EA9DF}{R_i}},{\color{#2EA9DF}{T_i}},{\color{#F75C2F}{k_1}},{\color{#F75C2F}{k_2}},{\color{#F75C2F}{k_3}},M_{ij}) m^ij=g(K,Ri,Ti,k1,k2,k3,Mij)为在考虑径向畸变时,用第 i i i张图像的第 j j j个角点的世界坐标经重投影函数 g g g后得到的像素坐标估计 m ^ i j \hat m_{ij} m^ij​.

则可以通过最小二乘的方法求得畸变参数 k 1 , k 2 , k 3 {\color{#F75C2F}{k_1}},{\color{#F75C2F}{k_2}},{\color{#F75C2F}{k_3}} k1,k2,k3

m i n ∑ i = 1 n ∑ j = 1 m ∣ ∣ m i j − m ^ i j ∣ ∣ 2 \mathbf{min} \sum_{i=1}^{n}\sum_{j=1}^{m}||m_{ij}-\hat m_{ij}||^2 mini=1nj=1m∣∣mijm^ij2

6 标定代码

以下是封装好重要函数的相机标定Python程序和注释解释。

#重要的函数
#1 ok, corners = detect_chessboard(img, rows, cols)  角点检测
#2 cv2.drawChessboardCorners(img, (cols, rows), corners, ok) 画角点
#3 ok, camera_mat, dist_coeff, rvecs, tvecs = cv2.calibrateCamera( 标定
#4      object_points, img_points, img_wh, camera_mat, dist_coeff,
#       criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 100, 1e-6))
#5 proj_points = cv2.projectPoints(object_points[i], rvecs[i], tvecs[i], camera_mat, dist_coeff)[0] 重投影
import os
import cv2
import numpy as np
np.set_printoptions(precision=4, suppress=True) #关闭科学计数法,便于显示


def image_from_camera():
    # 从摄像头中读取图像并保存(按键盘s键, 按ESC键退出程序)
    cap = cv2.VideoCapture(0)
    assert cap.isOpened(), 'Fail to open camera!'

    # 指定一个目录用于存放摄像头中截取的图像
    save_dir = '../data'
    # 图像文件命名范式: 0001.jpg, 0002.jpg, ...
    name_format = '%04d.jpg'
    name_idx = 1

    while True:
        ok, img = cap.read()
        assert ok, 'Fail to read camera!'
        cv2.imshow('1', img)
        key = cv2.waitKey(1) #以1ms为限制等待
        if key == -1:  # 什么键也没有按下
            continue
        if (key == 27):  # ESC键
            break
        if chr(key) == 's':  # s 键
            name = os.path.join(save_dir, name_format % name_idx)
            name_idx += 1
            cv2.imwrite(name, img)
            print('Saved image to \'%s\'', name)
    return

def detect_chessboard(img, rows=12, cols=6, subpix_sz=5):
    """
    从棋盘格图像中检测棋盘格角点
    :param img: 棋盘格图像,BGR或gray
    :param rows: 角点行数(不是方块的个数, 不算边缘角点) height
    :param cols: 角点列数  width
    :param subpix_sz: 对棋盘格角点坐标优化时的局部搜索范围,应不大于图像中最小方块的边长,单位是像素
    :return: 检测的角点,shape=(rows*cols,1,2)。如果实际检测的角点数目小于rows*cols则报错。
    corners (num,1,2)
    """

    ok, corners = cv2.findChessboardCorners(
        img, (cols, rows),
        cv2.CALIB_CB_ADAPTIVE_THRESH | cv2.CALIB_CB_NORMALIZE_IMAGE | cv2.CALIB_CB_FAST_CHECK)

    if ok:
        #亚像素级优化(GRAY),增加其准确性
        #zeroZone设置排除的区域,设置为(-1,-1)表示全部区域都检测
        #criteria迭代终止条件(次数或精度)
        corners = cv2.cornerSubPix(
            img if len(img.shape) == 2 else cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),
            corners, (subpix_sz, subpix_sz), (-1, -1),
            (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.01))

    return ok, corners


if __name__ == '__main__':
    # 第一步,用摄像头采集一些棋盘格图片
    # image_from_camera()

    # 第二步,读取每张图片,进行角点检测
    imgs = []  # 所选取的图像
    img_paths = []  # 对应的路径
    img_points = []  #9,72,1,2# 从图像中检测的角点
    cols, rows = 12, 6
    img_num = len(os.listdir(r'../data1'))
    draw = False
    for i in range(1, img_num+1):
        img_path = '../data1/%04d.jpg' % i
        img = cv2.imread(img_path) #BGR或GRAY格式
        assert img is not None, 'Fail to read image: ' + img_path
        ok, corners = detect_chessboard(img, rows, cols)
        if not ok:
            cv2.putText(img, 'Fail to detect corners!', org=(50, 200),
                        fontFace=cv2.FONT_HERSHEY_PLAIN , fontScale=4, color=(255, 255, 0), thickness=2)
            cv2.imshow('chessboard-corners-%04d'%i, img)
            cv2.waitKey()
            cv2.destroyWindow('chessboard-corners-%04d'%i)
        else:
            if draw:
                cv2.drawChessboardCorners(img, (cols, rows), corners, ok)
                cv2.imshow('chessboard-corners-%04d'%i, img)
                cv2.waitKey()
                cv2.destroyWindow('chessboard-corners-%04d'%i)
            imgs.append(img)
            img_paths.append(img_path)
            img_points.append(corners)

    nimgs = len(imgs)
    img_wh = img.shape[1::-1]  # (W,H)图像的宽以及高,所有图片必须具有相同尺寸,标定时要用到
    print('Got {} images for camera calibration.'.format(nimgs))

    # 第三步,进行相机标定
    # 先构造世界坐标系下的角点坐标,角点应该进行正确排序:先是第一行第一列,然后第一行第二列,...
    object_points = np.zeros((rows * cols, 1, 3), dtype='float32')
    square_sz = 138  #mm    any constant (edge length of eac chess board square, in millimeters or meters)
    for r in range(rows):
        for c in range(cols):
            object_points[r * cols + c, 0, 0] = c * square_sz  # world coordinate - X 行方向
            object_points[r * cols + c, 0, 1] = r * square_sz  # world coordinate - Y 列方向
    object_points = [object_points] * nimgs #9,72,1,3
    # 标定
    camera_mat = np.eye(3, 3)  # 如果有初始相机参数,可以在这里进行设置,将提高标定准确率
    dist_coeff = np.zeros((5, 1))  # 畸变参数k1 k2 k3 p1 p2
    ok, camera_mat, dist_coeff, rvecs, tvecs = cv2.calibrateCamera(
        object_points, img_points, img_wh, camera_mat, dist_coeff,
        criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 100, 1e-6))
    #R (9,3,1) T (9,3,1)
    # 每个旋转向量 rvec 的尺寸为 (3, 1),表示一个三维向量。这个向量通常是以列向量的形式表示的,包含三个元素,分别对应于绕 x、y、z 轴的旋转角度(以弧度为单位)
    assert ok and cv2.checkRange(camera_mat) and cv2.checkRange(dist_coeff), 'Calibration failed!' #checkRange预测是否在有效范围之内

    # 保存标定结果
    print('camera_mat:\n', camera_mat)
    print('dist_coeff:\n', dist_coeff)
    np.save(r'../output/camera_mat', camera_mat)
    np.save(r'../output/dist_coeff', dist_coeff)
    print('Saved calibration results to: camera_mat.npy and dist_coeff.npy')

    # 第四步,做一些简单的验证

    # 计算标定误差:将物理点投影到图像空间中,并与对应的图像点进行比较,计算重投影误差
    for i in range(nimgs):
        proj_points = cv2.projectPoints(object_points[i], rvecs[i], tvecs[i], camera_mat, dist_coeff)[0]  #第二个返回时雅可比矩阵,一般用不上
        average_err = cv2.norm(proj_points, img_points[i], cv2.NORM_L2) / len(img_points[i])  #单位mm
        print('Reprojection error for image \'%s\': %g' % (img_paths[i], average_err))

    # 对相机畸变进行补偿,得到去畸变的图像
    for i in range(nimgs):
        img_undistort = cv2.undistort(imgs[i], camera_mat, dist_coeff)  #校正
        cv2.imshow('original', imgs[i])
        cv2.imshow('undistort', img_undistort)
        if cv2.waitKey() == 27:
            break

7 关于标定过程注意事项

  • 标定板选用长宽不一样的,以免角点检测失误;
  • 拍摄角度尽量不要使横纵方向完全颠倒;
  • 标定过程中保证相机的光圈和焦距参数不变;
  • 图片应为10-25张。
  • 28
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值