相机的功能就是将真实的三维世界拍摄形成二维的图片。所以可以将相机成像的过程看做一个函数,输入是一个三维的场景,输出是二维的图片。但是,当我们想将二维的图片反映射成三维场景时,很明显,我们无法仅通过一张二维图来得到真实的三维场景。也就是说,上述的函数是不可逆的。
相机标定的目标是用一个具体的数学模型来模拟复杂的成像过程,并且求解出该数学模型中的一些参数,包括相机的内参,畸变系数和外参。这样我们便能够近似这个三维到二维的过程,进而找到这个函数的反函数,便可以从获取二维重建出三维。
一旦标定得到这些参数,我们就得到了一个完整的相机成像的数学模型,其在在机器视觉、图像测量、摄影测量、三维重建等应用普遍应用。如
从世界坐标系变换到相机坐标系属于刚体变换,即物体不会发生形变,只需要进行旋转和平移。
[ X C Y C Z C ] = R [ X W Y W Z W ] + T \begin{bmatrix} X_C \\ Y_C \\ Z_C \end{bmatrix} = R \begin{bmatrix} X_W \\ Y_W \\ Z_W \end{bmatrix} + T ⎣⎡XCYCZC⎦⎤=R⎣⎡XWYWZW⎦⎤+T
R ∈ R 3 × 3 R \in R^{3 \times3} R∈R3×3: 旋转矩阵;
T ∈ R 3 × 1 T \in R^{3 \times 1} T∈R3×1: 平移向量;
齐次表达:
[ X C Y C Z C 1 ] = [ R T 0 1 ] [ X W Y W Z W 1 ] \begin{bmatrix} X_C \\ Y_C \\ Z_C \\ 1 \end{bmatrix} = \begin{bmatrix} R & T \\ 0 & 1 \\ \end{bmatrix} \begin{bmatrix} X_W \\ Y_W \\ Z_W \\ 1 \end{bmatrix} ⎣⎢⎢⎡XCYCZC1⎦⎥⎥⎤=[R0T1]⎣⎢⎢⎡XWYWZW1⎦⎥⎥⎤
这里就是相机的外参矩阵,一旦世界坐标系发生变化,外参矩阵也要随之变化。
从相机坐标系到图像坐标系,属于透视投影关系,从3D转换到2D。根据图中的相似三角形可以得出以下对应关系:
Δ A B O C ∼ Δ o C O C Δ P B O C ∼ Δ p C O C \Delta ABO_C \sim \Delta oCO_C \\ \Delta PBO_C \sim \Delta pCO_C \\ ΔABOC∼ΔoCOCΔPBOC∼ΔpCOC
A B o C = A O C o O C = P B p C = X C x = Z C f = Y C y \frac{AB}{oC} = \frac{AO_C}{oO_C} = \frac{PB}{pC}=\frac{X_C}{x} = \frac{Z_C}{f} = \frac{Y_C}{y} oCAB=oOCAOC=pCPB=xXC=fZC=yYC
此处的 f f f对应相机焦距,最终,图像物理坐标系中的一点和相机坐标系中的对应点的转换关系便可以表示如下:
{ x = f X C Z C y = f Y C Z C \left\{ \begin{gathered} x = f \frac{X_C}{Z_C} \\ y = f \frac{Y_C}{Z_C} \end{gathered} \right. ⎩⎪⎪⎨⎪⎪⎧x=fZCXCy=fZCYC
其矩阵表示如下:
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⎦⎥⎥⎤
图像物理坐标系与像素坐标系只是坐标原点位置不一致,且单位长度(mm和pixel)不一致,因此,只需要进行伸缩变换及平移变换。
{ u = x d x + u 0 v = y d y + v 0 \left\{ \begin{gathered} u=\frac{x}{dx}+u_0 \\ v=\frac{y}{dy} + v_0 \end{gathered} \right. ⎩⎪⎨⎪⎧u=dxx+u0v=dyy+v0
表达为矩阵形式如下:
[ u v 1 ] = [ 1 d x 0 u 0 0 1 d y v 0 0 0 1 ] [ x y 1 ] \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} \frac{1}{dx} & 0 & u_0 \\ 0 & \frac{1}{dy} & v_0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} ⎣⎡uv1⎦⎤=⎣⎡dx1000dy10u0v01⎦⎤⎣⎡xy1⎦⎤
根据以上四个坐标系之间的转换关系,在不考虑相机畸变的情况下,物体从世界坐标系投影到像素坐标系的过程如下:
畸变(distortion)是对直线投影(rectilinear projection)的一种偏移。简单来说直线投影是场景内的一条直线投影到图片上也保持为一条直线。畸变简单来说就是一条直线投影到图片上不能保持为一条直线了,这是一种光学畸变(optical aberration),由于镜头不规整和镜头与感光片不平行导致的。
畸变一般可以分为:径向畸变、切向畸变
d r dr dr: 径向畸变
d t dt dt: 切向畸变
径向畸变是由于镜头不规整造成的。图像径向畸变是图像像素点以畸变中心为中心点,沿着径向产生的位置偏差,从而导致图像中所成的像发生形变。径向畸变分为桶形畸变和枕形畸变。
切向畸变是由于透镜本身与相机传感器平面(感光面)不平行而产生的,这种情况多是由于透镜被粘贴到镜头模组上的安装偏差导致。
相机主光轴中心的畸变为0,沿着镜头半径方向向边缘移动,畸变越来越严重。畸变的数学模型可以用主点(principle point)周围的泰勒级数展开式的前几项进行描述,通常使用前两项,即k1和k2,对于畸变很大的镜头,如鱼眼镜头,可以增加使用第三项k3来进行描述,相机上某点根据其在径向方向上的分布位置,校正公式为:
公式里 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)是畸变点在相机上的原始位置, ( x , y ) (x, y) (x,y)是畸变较正后新的位置。
相机内参: f , u 0 , v 0 , 1 / d x , 1 / d y f, u_0, v_0, 1/dx, 1/dy f,u0,v0,1/dx,1/dy
f f f: 焦距
u 0 , v 0 u_0, v_0 u0,v0: 主点在像素坐标系下的偏移
d x , d y dx, dy dx,dy: 表示x方向和y方向的一个像素分别占多少个单位
在OpenCV及Matlab标定工具箱中,标定直接得到的是内参矩阵。因此在标定过程是得不到物理焦距 f f f的 。
指相机相对于某个世界坐标系的方向(旋转和平移)。
相机的外参是6个;
R: 三个轴的旋转参数分别是 ( w 、 δ 、 θ ) (w、δ、θ) (w、δ、θ),把每个轴的3x3旋转矩阵进行组合(矩阵之间相乘),得到集合三个轴旋转信息的R,其大小还是3x3;
T: 三个轴的平移参数 ( T x 、 T y 、 T z ) (Tx、Ty、Tz) (Tx、Ty、Tz)。
opencv中标定得到直接是 ( w 、 δ 、 θ ) (w、δ、θ) (w、δ、θ)和 ( T x 、 T y 、 T z ) (Tx、Ty、Tz) (Tx、Ty、Tz),进而通过下面的转换得到外参矩阵。
mR, _ = cv2.Rodrigues([w、δ、θ]) # 通过罗德里格斯公式将旋转向量转换为旋转矩阵
mT = [Tx、Ty、Tz] # 平移矩阵
exmat = np.concatenate([mR, mT], axis=1)
exmat = np.vstack([exmat, np.array([0, 0, 0, 1])]) # 外参矩阵
k 1 , k 2 , k 3 k_1,k_2,k_3 k1,k2,k3径向畸变系数, p 1 , p 2 p_1,p_2 p1,p2是切向畸变系数。
在Opencv中他们被排列成一个5*1的矩阵,依次包含 [ k 1 、 k 2 、 p 1 、 p 2 、 k 3 ] [k_1、k_2、p_1、p_2、k_3] [k1、k2、p1、p2、k3]。
相机标定就是为了标定出上述的内参,外参和畸变系数。所以一旦相机结构固定,包括镜头结构固定,对焦距离固定,相机的内参就是固定的。但是一旦相机的位置发生移动,外参就会变化。因此,我们现在的任务就是找出一些点的像素坐标和对应的世界坐标,来求解出内参,外参和畸变系数。
目前最常用的获取这些点对的方式是采用标定板,其标定流程如下:
cv2.findChessboardCorners
从照片中提取棋盘格角点的像素坐标。cv2.cornerSubPix
进行角点坐标亚像素优化。cv2.calibrateCamera
求解相机内参,外参和畸变系数;https://github.com/dyfcalid/CameraCalibration
上述代码包含了普通相机和鱼眼相机的内参和外参标定代码。
import argparse
import cv2
import numpy as np
import os
parser = argparse.ArgumentParser(description="Camera Intrinsic Calibration")
parser.add_argument('-fw','--FRAME_WIDTH', default=1280, type=int, help='Camera Frame Width')
parser.add_argument('-fh','--FRAME_HEIGHT', default=1024, type=int, help='Camera Frame Height')
parser.add_argument('-bw','--BORAD_WIDTH', default=6, type=int, help='Chess Board Width (corners number)')
parser.add_argument('-bh','--BORAD_HEIGHT', default=7, type=int, help='Chess Board Height (corners number)')
parser.add_argument('-size','--SQUARE_SIZE', default=100, type=int, help='Chess Board Square Size (mm)')
parser.add_argument('-num','--CALIB_NUMBER', default=5, type=int, help='Least Required Calibration Frame Number')
parser.add_argument('-subpix','--SUBPIX_REGION', default=5, type=int, help='Corners Subpix Optimization Region')
parser.add_argument('-fs', '--FOCAL_SCALE', default=1, type=float, help='Camera Undistort Focal Scale')
parser.add_argument('-ss', '--SIZE_SCALE', default=1, type=float, help='Camera Undistort Size Scale')
args = parser.parse_args([]) # Jupyter Notebook中直接运行时要加[], py文件则去掉
class CalibData: # 标定数据类
def __init__(self):
self.camera_mat = None # 相机内参
self.dist_coeff = None # 畸变参数
self.rvecs = None # 旋转向量
self.tvecs = None # 平移向量
self.map1 = None # 映射矩阵1
self.map2 = None # 映射矩阵2
self.reproj_err = None # 重投影误差
self.ok = False # 数据采集完成标志
self.camera_mat_dst = None # 无畸变图相机内参
class Normal: # 平面相机
def __init__(self):
self.data = CalibData()
self.inited = False
self.BOARD = np.array([ [(j * args.SQUARE_SIZE, i * args.SQUARE_SIZE, 0.)]
for i in range(args.BORAD_HEIGHT)
for j in range(args.BORAD_WIDTH) ],dtype=np.float32)
# 多图的2D3D点对标定相机参数
def update(self, corners, frame_size):
board = [self.BOARD] * len(corners)
if not self.inited:
self._update_init(board, corners, frame_size)
self.inited = True
else:
self._update_refine(board, corners, frame_size)
self._calc_reproj_err(corners)
self._get_undistort_maps()
# 首图的2D3D点对进行标定初始化
def _update_init(self, board, corners, frame_size):
data = self.data
data.camera_mat = np.eye(3, 3)
data.dist_coeff = np.zeros((5, 1)) # 畸变向量的尺寸根据使用模型修改
data.ok, data.camera_mat, data.dist_coeff, data.rvecs, data.tvecs = cv2.calibrateCamera(
board, corners, frame_size, data.camera_mat, data.dist_coeff,
criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 30, 1e-6))
data.ok = data.ok and cv2.checkRange(data.camera_mat) and cv2.checkRange(data.dist_coeff)
# 其余图的2D3D点对进行初始化标定结果的优化
def _update_refine(self, board, corners, frame_size):
data = self.data
data.ok, data.camera_mat, data.dist_coeff, data.rvecs, data.tvecs = cv2.calibrateCamera(
board, corners, frame_size, data.camera_mat, data.dist_coeff,
flags = cv2.CALIB_USE_INTRINSIC_GUESS,
criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 10, 1e-6))
data.ok = data.ok and cv2.checkRange(data.camera_mat) and cv2.checkRange(data.dist_coeff)
# 计算重投影误差
def _calc_reproj_err(self, corners):
if not self.inited: return
data = self.data
data.reproj_err = []
for i in range(len(corners)):
corners_reproj, _ = cv2.projectPoints(self.BOARD, data.rvecs[i], data.tvecs[i], data.camera_mat, data.dist_coeff)
err = cv2.norm(corners_reproj, corners[i], cv2.NORM_L2) / len(corners_reproj)
data.reproj_err.append(err)
# 获取新的相机内参矩阵
def _get_camera_mat_dst(self, camera_mat):
camera_mat_dst = camera_mat.copy()
camera_mat_dst[0][0] *= args.FOCAL_SCALE # FOCAL_SCALE < 1, 则畸变校正后的图会放大,否则图会缩小
camera_mat_dst[1][1] *= args.FOCAL_SCALE
camera_mat_dst[0][2] = args.FRAME_WIDTH / 2 * args.SIZE_SCALE
camera_mat_dst[1][2] = args.FRAME_HEIGHT / 2 * args.SIZE_SCALE
return camera_mat_dst
# 获取畸变校正映射
def _get_undistort_maps(self):
data = self.data
data.camera_mat_dst = self._get_camera_mat_dst(data.camera_mat)
data.map1, data.map2 = cv2.initUndistortRectifyMap(
data.camera_mat, data.dist_coeff, np.eye(3, 3), data.camera_mat_dst,
(int(args.FRAME_WIDTH * args.SIZE_SCALE), int(args.FRAME_HEIGHT * args.SIZE_SCALE)), cv2.CV_16SC2)
class InCalibrator: # 内参标定器
def __init__(self):
self.camera = Normal() # 普通相机类
self.corners = []
# 获取args参数,供外部调用修改参数
@staticmethod
def get_args():
return args
# 获取棋盘格角点坐标
def get_corners(self, img):
ok, corners = cv2.findChessboardCorners(img, (args.BORAD_WIDTH, args.BORAD_HEIGHT),
flags = cv2.CALIB_CB_ADAPTIVE_THRESH|cv2.CALIB_CB_NORMALIZE_IMAGE|cv2.CALIB_CB_FAST_CHECK)
if ok:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 角点坐标亚像素优化
corners = cv2.cornerSubPix(gray, corners, (args.SUBPIX_REGION, args.SUBPIX_REGION), (-1, -1),
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.01))
return ok, corners
# 在图上绘制棋盘格角点
def draw_corners(self, img):
ok, corners = self.get_corners(img)
cv2.drawChessboardCorners(img, (args.BORAD_WIDTH, args.BORAD_HEIGHT), corners, ok)
return img
# 图像去畸变
def undistort(self, img):
data = self.camera.data
return cv2.remap(img, data.map1, data.map2, cv2.INTER_LINEAR)
# 使用现有角点坐标标定
def calibrate(self, img):
if len(self.corners) >= args.CALIB_NUMBER:
self.camera.update(self.corners, img.shape[1::-1]) # 更新标定数据
return self.camera.data
def __call__(self, raw_frame):
ok, corners = self.get_corners(raw_frame)
result = self.camera.data
if ok:
self.corners.append(corners) # 加入新的角点坐标
result = self.calibrate(raw_frame) # 得到标定结果
return result
from glob import glob
from tqdm import tqdm
import matplotlib.pyplot as plt
calibrator = InCalibrator()
image_list = glob(os.path.join('/Users/nickccnie/Desktop/能力沉淀/10.代码库/CameraCalibration-master/IntrinsicCalibration/data', '*.jpg'))
for img_path in tqdm(image_list):
image = cv2.imread(img_path)
result = calibrator(image)
# img = calibrator.draw_corners(image)
# plt.figure(figsize=(10, 10))
# plt.imshow(img)
# plt.show()
print('相机内参:')
print(calibrator.camera.data.camera_mat)
print('畸变系数:')
print(calibrator.camera.data.dist_coeff)
print('重投影误差:')
print(calibrator.camera.data.reproj_err)
相机内参:
[[429.01201174 0. 567.41969891]
[ 0. 419.76447848 467.46234827]
[ 0. 0. 1. ]]
畸变系数:
[[-0.28372365]
[ 0.06597315]
[ 0.01174763]
[ 0.00297211]
[-0.0063206 ]]
重投影误差:
[0.27869147314646936, 0.4886522637692187, 0.17598982232576374, 0.48923469676289055, 0.514708116953903, 0.5678517808250879, 0.4997095543126862, 0.48520367988999785, 0.5357485071819145, 0.429865668423967]
img = cv2.imread(image_list[0])
undistort_img = calibrator.undistort(img)
plt.figure(figsize=(20, 20))
plt.subplot(121)
plt.imshow(img)
plt.subplot(122)
plt.imshow(undistort_img)
plt.show()
inmat = calibrator.camera.data.camera_mat
distCoeffs = calibrator.camera.data.dist_coeff
inmat_dst = calibrator.camera.data.camera_mat_dst
rvec = calibrator.camera.data.rvecs[0]
tvec = calibrator.camera.data.tvecs[0]
mR, _ = cv2.Rodrigues(rvec)
mT = tvec
exmat = np.concatenate([mR, mT], axis=1)
exmat = np.vstack([exmat, np.array([0, 0, 0, 1])])
print('第一张图对应的相机外参--旋转:')
print(calibrator.camera.data.rvecs[-1])
print('第一张图对应的相机外参--平移:')
print(calibrator.camera.data.tvecs[-1])
print('第一张图对应的相机外参:')
print(exmat)
第一张图对应的相机外参--旋转:
[[-1.23794909]
[ 0.0842647 ]
[ 0.01222559]]
第一张图对应的相机外参--平移:
[[-317.82200853]
[ 355.12147718]
[1161.07445248]]
第一张图对应的相机外参:
[[ 9.97197679e-01 -1.29569903e-02 7.36811127e-02 -1.54700207e+02]
[-2.47796673e-02 8.72085682e-01 4.88725415e-01 -1.87299319e+02]
[-7.05886538e-02 -4.89181643e-01 8.69320747e-01 1.05852036e+03]
[ 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00]]
box_tl = [510, 398]
box_tr = [704, 394]
box_dl = [489, 548]
box_dr = [735, 541]
pts = np.array([box_tl, box_tr, box_dr, box_dl], np.int32) # 每个点都是(x, y)
pts = pts.reshape((-1,1,2))
draw_img = cv2.polylines(img.copy(),[pts],True,(0,255,0), 2)
plt.figure(figsize=(20, 20))
plt.imshow(draw_img)
plt.show()
undistort_pts = cv2.undistortPoints(pts.astype(np.float32), inmat, distCoeffs, P=inmat_dst)
print(undistort_pts)
draw_undistort_img = cv2.polylines(undistort_img.copy(),[undistort_pts.astype(np.int32)],True,(0,255,0), 2)
plt.figure(figsize=(20, 20))
plt.imshow(draw_undistort_img)
plt.show()
[[[581.46466 441.02515]]
[[782.36176 434.583 ]]
[[815.69165 588.1761 ]]
[[560.1518 593.54535]]]
def camera2world(point2D, rVec, tVec, cameraMat, height):
"""
Function used to convert given 2D points back to real-world 3D points
point2D : An array of 2D points
rVec : Rotation vector
tVec : Translation vector
cameraMat: Camera Matrix used in solvePnP
height : Height in real-world 3D space
Return : output_array: Output array of 3D points
"""
point3D = []
point2D = (np.array(point2D, dtype='float32')).reshape(-1, 2)
numPts = point2D.shape[0]
point2D_op = np.hstack((point2D, np.ones((numPts, 1))))
rMat = cv2.Rodrigues(rVec)[0]
rMat_inv = np.linalg.inv(rMat)
kMat_inv = np.linalg.inv(cameraMat)
for point in range(numPts):
uvPoint = point2D_op[point, :].reshape(3, 1)
tempMat = np.matmul(rMat_inv, kMat_inv)
tempMat1 = np.matmul(tempMat, uvPoint)
tempMat2 = np.matmul(rMat_inv, tVec)
s = (height + tempMat2[2]) / tempMat1[2]
p = tempMat1 * s - tempMat2
point3D.append(p)
point3D = (np.array(point3D, dtype='float32')).reshape([-1, 1, 3])
return point3D
pts_world = camera2world(undistort_pts, rvec, tvec, inmat_dst, height=0).reshape(-1, 3)
print(pts_world)
x = pts_world[:, 0] #构造横坐标数据
y = pts_world[:, 1] #构造纵坐标数据
plt.plot(x, y, 'bo')
plt.fill(x, y, 'r')
plt.show() # 可以看出,世界坐标系中的box基本是个矩形
[[ 1.1295952e+01 1.1067908e+01 -1.1368684e-13]
[ 4.9372278e+02 1.3734733e+01 0.0000000e+00]
[ 4.9998770e+02 4.0107578e+02 0.0000000e+00]
[-5.7722647e-02 4.0629202e+02 0.0000000e+00]]
def world2camera(points):
if points.size == 0:
return np.empty((0, 3), dtype=np.int)
# 组织为齐次坐标
points = np.concatenate([points, np.ones((points.shape[0], 1))], axis=1)
# 坐标变换
points = np.dot(inmat_dst, np.dot(exmat[:-1], points.T)).T
points = points / points[:, -1].reshape(-1, 1)
points = points[:, :2]
return points
point_camera_reprj = world2camera(pts_world)
print(point_camera_reprj)
draw_undistort_img = cv2.polylines(undistort_img.copy(),[point_camera_reprj[:, None, :].astype(np.int32)],True,(255,0,0), 2)
plt.figure(figsize=(20, 20))
plt.imshow(draw_undistort_img)
plt.show()
[[581.46466074 441.02514653]
[782.36175752 434.58300772]
[815.69165941 588.17609256]
[560.15179407 593.54535244]]
cv2.findChessboardCorners ( image, # 棋盘图像
patternSize, # 棋盘格行和列的【内角点】数量
corners, # 输出数组
flags # 操作标志
)
flags:
CV_CALIB_CB_ADAPTIVE_THRESH # 使用自适应阈值处理将图像转换为黑白图像
CV_CALIB_CB_NORMALIZE_IMAGE # 对图像进行归一化。
CV_CALIB_CB_FILTER_QUADS # 过滤在轮廓检索阶段提取的假四边形。
CALIB_CB_FAST_CHECK # 对查找棋盘角的图像进行快速检查
cv2.cornerSubPix (image, # 棋盘图像
corners, # 棋盘角点
winSize, # 搜索窗口边长的一半
zeroZone, # 搜索区域死区大小的一半, (-1,-1)代表无
criteria # 迭代停止标准
)
cv2.calibrateCamera (objectPoints, # 角点在棋盘中的空间坐标向量
imagePoints, # 角点在图像中的坐标向量
image_size, # 图片大小
K, # 相机内参矩阵
D, # 畸变参数向量
rvecs, # 旋转向量
tvecs, # 平移向量
flags, # 操作标志
criteria # 迭代优化算法的停止标准
)
flags:
cv2.CALIB_USE_INTRINSIC_GUESS # 当相机内参矩阵包含有效的fx,fy,cx,cy初始值时,这些值会进一步进行优化
# 否则,(cx,cy)初始化设置为图像中心(使用imageSize),并且以最小二乘法计算焦距
cv2.CALIB_FIX_PRINCIPAL_POINT # 固定光轴点(当设置CALIB_USE_INTRINSIC_GUESS时可以使用)
cv2.CALIB_FIX_ASPECT_RATIO # 固定fx/fy的值,函数仅将fy视为自由参数
cv2.CALIB_ZERO_TANGENT_DIST # 切向畸变系数(p1,p2) 设置为零并保持为零
cv2.CALIB_FIX_FOCAL_LENGTH # 如果设置了CALIB_USE_INTRINSIC_GUESS,则在全局优化过程中不会更改焦距
cv2.CALIB_FIX_K1 (K1-K6) # 固定相应的径向畸变系数为0或给定的初始值
cv2.CALIB_RATIONAL_MODEL # 理想模型:启用系数k4,k5和k6。此时返回8个或更多的系数
cv2.CALIB_THIN_PRISM_MODEL # 薄棱镜模型:启用系数s1,s2,s3和s4。此时返回12个或更多的系数
cv2.CALIB_FIX_S1_S2_S3_S4 # 固定薄棱镜畸变系数为0或给定的初始值
cv2.CALIB_TILTED_MODEL # 倾斜模型:启用系数tauX和tauY。此时返回14个系数
cv2.CALIB_FIX_TAUX_TAUY # 固定倾斜传感器模型的系数为0或给定的初始值
drawChessboardCorners
:绘制棋盘格检测结果projectPoints
: 计算重投影误差OpenCV 针对不同的使用场景提供了几个不同用法的畸变校正函数。https://docs.opencv.org/3.4.6/da/d54/group__imgproc__transform.html#ga55c716492470bfe86b0ee9bf3a1f0f7e
主要有以下几种:
initUndistortRectifyMap() remap()组合
undistort()
undistortPoints()
(1) initUndistortRectifyMap() undistort()组合
(2)undistort()
(3)undistortPoints()
cv2.initUndistortRectifyMap (K, # 相机内参矩阵
D, # 畸变向量
R, # 旋转矩阵
P, # 新的相机矩阵
size, # 输出图像大小
m1type, # 映射矩阵类型
map1, # 输出映射矩阵1
map2 # 输出映射矩阵2
)
def remap(src, # 源图像数据
map1, # 用于插值的X坐标
map2, # 用于插值的Y坐标
interpolation, # 插值算法
dst=None,
borderMode=None, # 边界模式,有默认值BORDER_CONSTANT,表示目标图像中“离群点(outliers)”的像素值不会被此函数修改。
borderValue=None # 当有常数边界时使用的值,其有默认值Scalar( ),即默认值为0。
)
cv2.undistort(src, # 输入原图
dst, # 输出矫正后的图像
cameraMatrix, # 内参矩阵
distCoeffs, # 畸变系数
newCameraMatrix # 默认情况下,它与 cameraMatrix 相同
)
cv.undistortPoints(src, # 待校正像素点坐标,1xN 或 Nx1 2channel
cameraMatrix, # 内参矩阵
distCoeffs, # 畸变系数
R, # R参数是用在双目里的,单目里置为空矩阵;
P # P矩阵值为空时,得到的点坐标是归一化坐标,这时候数值就会明显很小;
# 通常使用时是想得到在同一个相机下的真实像素,所以P设置为内参就可以了
)
cvFindExtrinsicCameraParams2
: 已知内参求外参solvePnP
cvProjectPoints2
: 2D点映射到3D点cvProjectPoints
:3D点映射到2D点`