相机参数标定
1. 前言
在图像测量过程以及机器视觉应用中,为确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,必须建立相机成像的几何模型,这些几何模型参数就是相机参数。在大多数条件下这些参数必须通过实验与计算才能得到,这个求解参数的过程就称之为相机标定(或摄像机标定)。
较早的相机标定的方法需要计算相机投影矩阵M中的11个未知参数,需要严格个出三个两两互相垂直的平面来做标定,该实验条件较为严格,一般情况难以实现。此后张正友于1998年在论文:"A Flexible New Technique fro Camera Calibration"提出了基于单平面棋盘格的相机标定方法。该方法介于传统的标定方法和自标定方法之间,使用简单实用性强,有以下优点:
- 不需要额外的器材,一张打印的棋盘格即可。
- 标定简单,相机和标定板可以任意放置。
- 标定的精度高。
2. 基本原理
2.1 相机标定
同步标定内部参数和外部参数,一般包括两种策略:
- 光学标定: 利用已知的几何信息(如定长棋盘格)实现参数求解。
- 自标定: 在静态场景中利用 structure from motion估算参数。
2.2 相机模型
通过空间中已知坐标的(特征)点 ,以及它们在图像中的对应坐标 ,直接估算 11 个待求解的内部和外部参数。
即为:
则有:
2.2 线性回归标定参数
由相机模型公式变形可得:
即为如下矩阵:
采用最小二乘法解决上述问题。
线性回归标定参数的优缺点
优点:
- 所有的相机参数集中在一个矩阵中,便于求解。
- 通过矩阵可以直接描述世界坐标中的三维点,到二维
图像平面中点的映射关系。
缺点:
- 无法直接得知具体的内参数和外参数。(QR分解)
- 求解出的11个未知量,比待标定参数(9个)更多。带
来了参数不独立/相关的问题 - 对噪声/误差敏感
2.3 非线性优化标定参数
用概率的视角去看最小二乘问题
特征点投影方程如下:
给定{(ui,vi)},标定参数矩阵 M 的概率为:
给定{(ui,vi)},标定参数矩阵 M 的似然函数为:
2.4 张正友相机标定原理
2.4.1 基本概念
- 2D图像点:
- 3D图像点:
- 齐次坐标:
- 描述空间坐标到图像坐标的映射
- : 世界坐标系到图像坐标系的尺度因子
- : 相机内参矩阵
- : 像主点坐标
- : 焦距与像素横纵比的融合
- : 径向畸变参数
2.4.2 单应性矩阵
不妨设棋盘格位于
,定义旋转矩阵R的第i列为
, 则有:
于是空间到图像的映射可改为:
其中H 是描述Homographic矩阵,可通过最小二乘,从角点世界坐标到图像坐标的关系求解。
2.4.3 内部参数
令
为
Homography有8个自由度,通过上述等式的矩阵运算,根据
和
正交,以及归一化的约束可以得到如下等式:
2.4.4 闭合解
定义
其中
B时对称阵,其未知量可表示为6D向量b
设
中的第
列为
根据b的定义,可以推出如下公式:
可以推导出
如果有
组观察图像,则
是
的矩阵
根据最小二乘定义,
的解是
最小特征值对应的特征向量。因此, 可以直接估算出
,后续可以通过
求解内参。
• 当观测平面
时,可以得到
的唯一解
• 当
时, 一般可令畸变参数
= 0
• 当
时, 仅能估算出
与
, 此时一般可假定像主点坐标
与
为0.
内部参数可通过如下公式计算(cholesky分解):
外部参数可通过Homography求解:
由
可推出:
一般而言,求解出的
不会满足正交与归一的标准。
在实际操作中,
可以通过SVD分解实现规范化。
2.4.3 极大似然估计
-
给定 张棋盘格图像,每张图像有 个角点
-
最小化下述公式等同于极大似然估计
-
上述非线性优化问题可以利用Levenberg-Marquardt 算法求解
-
需要初值
2.4.5 最小化重投影误差
重投影误差:是像素坐标(观测到的投影位置)与3D点按照当前估计的位姿进行投影得到的位置相比较得到的误差。
如下图所示,我们通过特征匹配知道,观测值
和
是同一个空间点
的投影,
的投影
与观测值
之间有一定的距离,也就是重投影误差。于是我们调整相机的位姿,使这个距离变小,由于这个调整需要考虑很多个点,所以最后每个点的误差通常不会精确到0。
在使用已知标志点给摄像机定位时,重投影误差并非最好的选择,因为重投影误差模型会认为标志点存在误差,从而重新估计标志点的坐标,引入多余的误差;而此时,事实上,重投影误差已经退化为单边几何变换误差,所以在这种情况下,单边几何变换误差才是最好的代价函数。
3. 张正友相机标定基本流程
- 打印一张棋盘格A4纸张(黑白间距已知),并贴在一个平板上
- 针对棋盘格拍摄若干张图片(一般10-20张)
- 在图片中检测特征点(Harris角点)
- 根据角点位置信息及图像中的坐标,求解Homographic矩阵
- 利用解析解估算方法计算出5个内部参数,以及6个外部参数
- 根据极大似然估计策略,设计优化目标并实现
参数的refinement
4. 实验准备及数据
本次实验中使用的相机为:IPhone7后置摄像头。
制作棋盘格并打印,其pdf文件如下:
链接:https://pan.baidu.com/s/1kZRrMKz8CLdax15fmnKxtw
提取码:7fmr
将打印出的棋盘格固定在平板上,进行拍摄照片,图片数量最好为15~20张,本次实验拍摄17张图像。构造数据集如下:
5. 实验代码
# -*- coding: utf-8 -*-
"""
Homework: Calibrate the Camera with ZhangZhengyou Method.
Picture File Folder: ".\pic\RGB_camera_calib_img", Without Distort.
By YouZhiyuan 2019.11.18
"""
import os
import re
import numpy as np
import cv2
import glob
np.set_printoptions(suppress=True)
def calib(inter_corner_shape, size_per_grid, img_dir, img_type):
# criteria: only for subpix calibration, which is not used here.
# criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
w, h = inter_corner_shape
# cp_int: corner point in int form, save the coordinate of corner points in world sapce in 'int' form
# like (0,0,0), (1,0,0), (2,0,0) ....,(10,7,0).
cp_int = np.zeros((w * h, 3), np.float32)
cp_int[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)
# cp_world: corner point in world space, save the coordinate of corner points in world space.
cp_world = cp_int * size_per_grid
obj_points = [] # the points in world space
img_points = [] # the points in image space (relevant to obj_points)
images = glob.glob(img_dir + os.sep + '**.' + img_type)
for fname in images:
# print(re.sub("\D", "", fname))
img = cv2.imread(fname)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# find the corners, cp_img: corner points in pixel space.
ret, cp_img = cv2.findChessboardCorners(gray_img, (w, h), None)
# if ret is True, save.
if ret == True:
# cv2.cornerSubPix(gray_img,cp_img,(11,11),(-1,-1),criteria)
obj_points.append(cp_world)
img_points.append(cp_img)
# view the corners
cv2.drawChessboardCorners(img, (w, h), cp_img, ret)
cv2.imwrite('FoundC'+re.sub("\D", "", fname)+'.jpg', img)
# cv2.waitKey(1000)
# cv2.destroyAllWindows()
# calibrate the camera
ret, mat_inter, coff_dis, v_rot, v_trans = cv2.calibrateCamera(obj_points, img_points, gray_img.shape[::-1], None,
None)
print(("ret:"), ret) # 重投影误差
print(("internal matrix:\n"), mat_inter) # 内参数矩阵
# in the form of (k_1,k_2,p_1,p_2,k_3)
print(("distortion cofficients:\n"), coff_dis) # 畸变系数
print(("rotation vectors:\n"), v_rot) # 旋转向量 # 外参数
print(("translation vectors:\n"), v_trans) # 平移向量 # 外参数
# calculate the error of reproject
total_error = 0
for i in range(len(obj_points)):
img_points_repro, _ = cv2.projectPoints(obj_points[i], v_rot[i], v_trans[i], mat_inter, coff_dis)
error = cv2.norm(img_points[i], img_points_repro, cv2.NORM_L2) / len(img_points_repro)
total_error += error
print(("Average Error of Reproject: "), total_error / len(obj_points))
return mat_inter, coff_dis
if __name__ == '__main__':
inter_corner_shape = (8, 6)
size_per_grid = 0.028
img_dir = ".\\data"
img_type = "jpg"
calib(inter_corner_shape, size_per_grid, img_dir, img_type)
6. 实验结果及分析
6.1 角点检测结果及分析
在代码运行中保存每张图片的角点检测图片,选取FoundCorner6.jpg放大观察如下:
由上图的放大区域可以看到共检测到了24个角点,可以看到放大区域中的所有黑色方格和白色方格的四个角都被检测为角点。
6.2 内部参数计算结果
控制台输出内部参数计算结果截图如下。其中1号方框内的数据表示:重投影误差;2号方框内的数据表示:内参数矩阵;3号方框内的数据表示:畸变系数;
6.3 外部参数计算结果
外部参数(旋转向量)的输出较多,为方便复制的输出结果如下:
rotation vectors:
[array([[ 0.08544752],[ 0.01262193],[-1.27658653]]),
array([[ 0.00017702],[-0.22666298], [-1.31589393]]),
array([[ 0.05786146],[ 0.32043949],[ 1.35247793]]),
array([[-0.22217286],[ 0.19649448],[-1.23054809]]),
array([[ 0.29268055], [-0.51259816],[-1.22304696]]),
array([[-0.18142884], [-0.26229903],[ 0.25036144]]),
array([[-0.07664752],[ 0.02384459], [-0.00115754]]),
array([[-0.02962394], [-0.04646427], [-1.13415801]]),
array([[ 0.31597367], [ 0.52081219], [ 1.09633532]]),
array([[-0.15250206], [ 0.31823889], [-0.18611884]]),
array([[ 0.0429856 ],[ 0.02530346], [-1.42615273]]),
array([[-0.01434251], [-0.04721317],[-1.42809882]]),
array([[-0.13343727], [-0.06032883],[-1.38794802]]),
array([[-0.05500072], [ 0.01296218],[ 1.56714706]]),
array([[-0.06118106],[ 0.05681526],[ 1.456494 ]]),
array([[-0.04264622],[ 0.02104299],[ 1.45450974]]),
array([[-0.03200693], [-0.05149377],[-1.56555323]])]
外部参数(平移向量)的输出较多,为方便复制的输出结果如下:
translation vectors:
[array([[-0.09091744], [ 0.082059 ], [ 0.39976627]]),
array([[-0.06494083],[ 0.06902907],[ 0.36145401]]),
array([[ 0.02344282],[-0.10982814], [ 0.40461869]]),
array([[-0.09944981], [ 0.06591263],[ 0.44676297]]),
array([[-0.08148695],[ 0.07102436],[ 0.30257503]]),
array([[-0.04677319],[-0.08328905], [ 0.37345418]]),
array([[-0.09775231],[-0.0598347 ], [ 0.35787261]]),
array([[-0.10861916], [ 0.06690967], [ 0.41883281]]),
array([[ 0.01580203],[-0.1134467 ], [ 0.3564163 ]]),
array([[-0.12227016],[-0.04945632], [ 0.41903326]]),
array([[-0.0691557 ], [ 0.11330688], [ 0.35711506]]),
array([[-0.0708636 ], [ 0.08392852],[ 0.312731 ]]),
array([[-0.10549313], [ 0.05052104], [ 0.450821 ]]),
array([[ 0.07601946], [-0.10353966],[ 0.29816626]]),
array([[ 0.08873036],[-0.14247998],[ 0.45183176]]),
array([[ 0.06259065],[-0.10878105], [ 0.30669367]]),
array([[-0.07005296], [ 0.09441181], [ 0.34586461]])]
6.4 外部参数可视化结果
针对相机参数的外部参数的可视化可以通过Matlab实现。
1.以棋盘格为中心
2.以相机为中心
7. 实验遇到的问题
实验运行结束时观察输出的相机参数,可以看到内部参数使用科学计数法输出,观察起来较为不便,如图所示:
所以考虑取消科学计数法,即代码中加入如下语句即可取消科学计数法输出:
import numpy as np
np.set_printoptions(suppress=True)