Python计算机视觉编程--第四章

照相机模型与增强现实

    • 一 、针孔照相机模型
      • 1.1 照相机矩阵
      • 1.2 三维点的投影
      • 1.3 照相机矩阵的分解
      • 1.4 照相机中心
    • 二、照相机标定

一 、针孔照相机模型

针孔照相机模型(有时称为射影照相机模型)是计算机视觉中广泛使用的照相机模型。对于大多数应用来说,针孔照相机模型简单,并且具有足够的精准度。这个名字来源于一种类似暗箱机的照相机。该照相机从一个小孔采集射到暗箱内部的光线。在光线投影到图像平面之前,从唯一一个点经过,也就是照相机中心C。
Python计算机视觉编程--第四章_第1张图片
由图像坐标轴和三维坐标系中的x轴和y轴对齐平行的假设,我们可以得出针孔照相机的投影性质。照相机的光学坐标轴和z轴一致,该投影几何一颗简化成相似三角形。在投影之前通过旋转和平移变换,对该坐标系加入三维点,会出现完整的投影变换。
在针孔照相机中,三维点X投影为图像点x(两个点都是用齐次坐标表示的),如下所示:
在这里插入图片描述
这里,3×4的矩阵P为照相机矩阵(或投影矩阵)。注意,在齐次坐标系中,三维点X的坐标由4个元素组成,X=[X,Y,Z,W]。这里的标量λ是三维点的逆深度。如果我们打算在齐次坐标中将最后一个数值归一化为1,那么就会使用到它。

1.1 照相机矩阵

照相机矩阵可以分解为:
在这里插入图片描述
其中,R是描述照相机方向的旋转矩阵,t是描述照相机中心位置的三维平移向量,内标定矩阵K描述照相机的投影性质。

标定矩阵仅和照相机自身的情况相关,通常情况下可以写成:

Python计算机视觉编程--第四章_第2张图片
图像平面和照相机中心间的距离为焦距f。当像素数组在传感器上偏斜的时候,需要用到倾斜参数s。在大多数情况下,s可以设置成0.也就是说:
Python计算机视觉编程--第四章_第3张图片
这里,我们使用了另外的记号fx和fy,两者关系为fx=αfy。

纵横比例参数α是在像素元素非正方形的情况下使用的。通常情况下,我们还可以默认设置α=1.经过这些假设,标定矩阵变为:

Python计算机视觉编程--第四章_第4张图片
除焦距之外,标定矩阵中剩余的唯一参数为光心(有时称为主点)的坐标c=[cx,cy],也就是光线坐标轴和图像平面的交点。因为光心通常在图像的中心,并且图像的坐标是从左上角开始计算的,所以光心的坐标常接近于图像宽度和高度的一半。特别强调一点,这这个例子中,唯一未知的变量是焦距f。

1.2 三维点的投影

下面来创建照相机类,用来处理我们对照相机和投影建模所需要的全部操作:

from scipy import linalg
from pylab import  *

class Camera(object):
    """表示针孔照相机的类"""

    def __init__(self, P):
        """初始化 P = K[R|t] 照相机模型"""
        self.P = P
        self.K = None   # 标定矩阵
        self.R = None   # 旋转
        self.t = None   # 平移
        self.c = None   # 照相机中心

    def project(self, X):
        """X(4×n的数组)的投影点,并进行坐标归一化"""
        x = dot(self.P, X)
        for i in range(3):
            x[i] /= x[2]
        return x

    def rotation_matrix(a):
        """创建一个用于围绕向量a轴旋转的三维旋转矩阵"""
        R = eye(4)
        R[:3,:3] = linalg.expm([0,-a[2],a[1]],[a[2],0,-a[0]],[-a[1],a[0],0])
        return R

1.3 照相机矩阵的分解

如果给定方程(1.1节中)所示的照相机矩阵P,我们需要恢复内参数K以及照相机的位置t和姿势R。矩阵分块操作称为因子分解。这里,我们将使用一种矩阵因子分解的方法,称为RQ因子分解。

将下面的方法添加到Carmera类中:

    def factor(self):
        """将照相机矩阵分解为 K,R,t,其中, R=K[R|t]"""

        # 分解前3×3的部分
        K,R = linalg.rq(self.P[:,:3])

        # 将K的对角线元素设为正值
        T = diag(sign(diag(K)))
        if linalg.det(T) < 0:
            T[1,1] *= -1
        self.K = dot(K,T)
        self.R = dot(T,R)   # T的逆矩阵为其自身
        self.t = dot(linalg.inv(self.K), self.P[:,3])
        
        return self.K, self.R, self.t

RQ因子分解的结果并不是唯一的。在该因子分解中,分解的结果存在符号二义性。由于我们需要限制旋转矩阵R为正定的(否则,旋转坐标轴即可),所以如果需要,我们可以在求解到的结果中加入变换T来改变符号。

在示例照相机上运行下面的代码,观察照相机矩阵分解的效果:

if __name__=='__main__':

    K = array([[1000,0,500],[0,1000,300],[0,0,1]])
    tmp = Camera.rotation_matrix([0,0,1])[:3,:3]
    Rt = hstack((tmp,array([[50],[40],[30]])))
    cam = Camera(dot(K,Rt))

    print(K,Rt)
    print(cam.factor())

1.4 照相机中心

给定照相机投影矩阵P,我们可以计算出空间上照相机的所在位置。照相机的中心C,是一个三维点,满足约束PC=0。对于投影矩阵为P=K[R|t]的照相机,有:
在这里插入图片描述
照相机的中心可以由下述式子来计算:
在这里插入图片描述
注意,如预期一样,照相机的中心和内标定矩阵K无关。

下面的代码可以按照上面公式计算照相机的中心。将其添加到Camera类中,该方法会返回照相机的中心:

    def center(self):
        """计算并返回照相机的中心"""
        if self.c is not None:
            return self.c
        else:
            # 通过因子分解计算c
            self.factor()
            self.c = -dot(self.R.T, self.t)
            return self.c

二、照相机标定

标定照相机是指计算出该照相机的内参数。在我们的例子里,是指计算矩阵K。如果你的应用要求高精度,那么可以扩展该照相机模型,使其包含径向畸变和其他条件。对于大多数应用来说,我们在1.1节中s=0的K公式中的简单照相机模型已经足够。标定照相机的标准方法,拍摄多幅平面期盼模型的图像,然后进行处理计算。
1.代码

import cv2
import numpy as np
import glob

# 找棋盘格角点
# 阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
#棋盘格模板规格
w = 14  #内角点个数,内角点是和其他格子连着的点
h = 9

# 世界坐标系中的棋盘格点,例如(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('picture/*.jpg')
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 死区 求角点的迭代终止条件
        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.waitKey(1000)
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('picture/3_d.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('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))


2.结果
Python计算机视觉编程--第四章_第5张图片
Python计算机视觉编程--第四章_第6张图片
Python计算机视觉编程--第四章_第7张图片

你可能感兴趣的:(python,python)