一、相机投影模型
基本原理
在图像测量过程以及机器视觉应用中,为确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,必须建立相机成像的几何模型,这些几何模型参数就是相机参数。在大多数条件下这些参数必须通过实验与计算才能得到,这个求解参数(内参、外参、畸变参数)的过程就称之为相机标定(或摄像机标定)。无论是在图像测量或者机器视觉应用中,相机参数的标定都是非常关键的环节,其标定结果的精度及算法的稳定性直接影响相机工作产生结果的准确性。因此,做好相机标定是做好后续工作的前提,提高标定精度是科研工作的重点所在。
畸变现象
图像径向畸变原因:
- 透镜质量原因
- 光线在远离透镜中心的地方比靠近中心的地方更加弯曲
图像平移和旋转
相机标定
标定参数线性回归
通过空间中已知坐标的(特征)点 (Xi,Y i,Zi),以及它 们在图像中的对应坐标 (ui,vi),直接估算 11 个待
求解的内部和外部参数。
利用最小二乘法求。
优点:
所有的相机参数集中在一个矩阵中,便于求解
通过矩阵可以直接描述世界坐标中的三维点,到二维 图像平面中点的映射关系。
缺点:
无法直接得知具体的内参数和外参数
求解出的11个未知量,比待标定参数(9个)更多。带 来了参数不独立/相关的问题
对噪声/误差敏感
高精度的标定板难以制作
标定参数非线性优化
当镜头畸变明显时必须引入畸变模型,将线性标定模型转化为非线性标定模型,
通过非线性优化的方法求解相机参数:
用概率的视角去看最小二乘问题
张正友标定法(只考虑径向畸变,不考虑切向畸变)
张正友标定法利用棋盘格标定板可以利用相应的角点提取算法(如Harris角点)得到每一个角点的像素坐标 ( u , v ) (u,v) (u,v)。
张正友标定法将世界坐标系固定于棋盘格上,则棋盘格上任一点的物理坐标 W = 0 W=0 W=0,由于标定板的世界坐标系是人为事先定义好的,标定板上每一个格子的大小是已知的,我们可以计算得到每一个角点在世界坐标系下的物理坐标 ( U , V , W ) = 0 (U,V,W)=0 (U,V,W)=0。
我们将利用这些信息:每一个角点的像素坐标 ( u , v ) (u,v) (u,v) 、每一个角点在世界坐标系下的物理坐标 ( U , V , W ) = 0 (U,V,W)=0 (U,V,W)=0,来进行相机的标定,获得相机的内外参矩阵、畸变参数。
其中,s: 世界坐标系到图像坐标系的尺度因子
K: 相机内参矩阵
(u0,v0): 像主点坐标
α, β: 焦距与像素横纵比的融合
γ: 径向畸变参数
其次,求解Homographic矩阵
对内在参数的约束条件
基本流程
1、打印一张棋盘格A4纸张(黑白间距已知),并贴在一个平板上
2、针对棋盘格拍摄若干张图片(一般10-20张)
3、在图片中检测特征点(Harris角点)
4、根据角点位置信息及图像中的坐标,求解 Homographic矩阵
5、利用解析解估算方法计算出5个内部参数,以及6个外部参数
6、根据极大似然估计策略,设计优化目标并实现参数的refinement
优点
- 只需一个平面
- 标定过程中无需知道棋盘格的方位
- 大量开源代码
相机标定实验
代码
# -*- coding=utf-8 -*-
# name: nan chen
# date: 2021/5/18 10:50
import cv2
import numpy as np
import glob
np.set_printoptions(suppress=True)
# 找棋盘格角点
# 阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 棋盘格模板规格
w = 6 # 内角点个数,内角点是和其他格子连着的点
h = 4
# 世界坐标系中的棋盘格点,例如(0,0,0), (1,0,0), (2,0,0) ....,(8,5,0),去掉Z坐标,记为二维矩阵
objp = np.zeros((w * h, 3), np.float32)
objp[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)
# 储存棋盘格角点的世界坐标和图像坐标对
objpoints = [] # 在世界坐标系中的三维点
imgpoints = [] # 在图像平面的二维点
images = glob.glob('p2/*.jpg')
i = 0
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 找到棋盘格角点
# 棋盘图像(8位灰度或彩色图像) 棋盘尺寸 存放角点的位置
ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
# 如果找到足够点对,将其存储起来
if ret == True:
# 角点精确检测
# 输入图像 角点初始坐标 搜索窗口为2*winsize+1 死区 求角点的迭代终止条件
i += 1
cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
objpoints.append(objp)
imgpoints.append(corners)
# 将角点在图像上显示
cv2.drawChessboardCorners(img, (w, h), corners, ret)
cv2.imshow('findCorners', img)
cv2.imwrite('p2/h' + str(i) + '.jpg', img)
cv2.waitKey(10)
cv2.destroyAllWindows()
# 标定、去畸变
# 输入:世界坐标系里的位置 像素坐标 图像的像素尺寸大小 3*3矩阵,相机内参数矩阵 畸变矩阵
# 输出:标定结果 相机的内参数矩阵 畸变系数 旋转矩阵 平移向量
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# mtx:内参数矩阵
# dist:畸变系数
# rvecs:旋转向量 (外参数)
# tvecs :平移向量 (外参数)
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) # 平移向量 # 外参数
# 去畸变
img2 = cv2.imread('p2/1.jpg')
h, w = img2.shape[:2]
# 我们已经得到了相机内参和畸变系数,在将图像去畸变之前,
# 我们还可以使用cv.getOptimalNewCameraMatrix()优化内参数和畸变系数,
# 通过设定自由自由比例因子alpha。当alpha设为0的时候,
# 将会返回一个剪裁过的将去畸变后不想要的像素去掉的内参数和畸变系数;
# 当alpha设为1的时候,将会返回一个包含额外黑色像素点的内参数和畸变系数,并返回一个ROI用于将其剪裁掉
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 0, (w, h)) # 自由比例参数
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)
# 根据前面ROI区域裁剪图片
# x, y, w, h = roi
# dst = dst[y:y + h, x:x + w]
cv2.imwrite('p2/calibresult.jpg', dst)
# 反投影误差
# 通过反投影误差,我们可以来评估结果的好坏。越接近0,说明结果越理想。
# 通过之前计算的内参数矩阵、畸变系数、旋转矩阵和平移向量,使用cv2.projectPoints()计算三维点到二维图像的投影,
# 然后计算反投影得到的点与图像上检测到的点的误差,最后计算一个对于所有标定图像的平均误差,这个值就是反投影误差。
total_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
total_error += error
print(("total error: "), total_error / len(objpoints))