Python计算机视觉第四章-照相机模型与增强现实

目录

4.1针孔照相机模型

        4.1.1照相机矩阵

4.1.2 三维点的投影

4.1.3 照相机矩阵的分解

4.1.4 计算照相机中心

4.2 照相机标定

        4.2.1 一个简单的标定方法

4.3 以平面和标记物进行姿态估计

4.4 增强现实

        4.4.1 PyGame和PyOpenGL

        4.4.2 从照相机矩阵到OpenGL格式

4.4.3 在图像中放置虚拟物体

4.4.4 综合集成


4.1针孔照相机模型

        对于大多数应用来说,针孔照相机模型简单,并且具有足够的精确度。该照相机从一个小孔采集射到暗箱内部的光线。 在针孔照相机模型中,在光线投影到图像平面之前,从唯一一个点经过,也就是照相机中心 C

        在针孔照相机中,三维点 X 投影为图像点 x (两个点都是用齐次坐标表示的),如下
所示:                                                   \lambda x=PX

        4.1.1照相机矩阵

        照相机矩阵可以分解为:P=K[R|t]

        其中,R 是描述照相机方向的旋转矩阵,t 是描述照相机中心位置的三维平移向量,内标定矩阵 K 描述照相机的投影性质。

                                                        K=\begin{bmatrix}\alpha f&s&c_x\\0&f&c_y\\0&0&1\end{bmatrix}

        

        图像平面和照相机中心间的距离为焦距 f 。当像素数组在传感器上偏斜的时候,需要
用到倾斜参数 s。即:
                                                ​​​​​​​        ​​​​​​​ K=\begin{bmatrix}f_x&0&c_x\\0&f_y&c_y\\0&0&1\end{bmatrix}
       f_{a}=\alpha f _{y}.

4.1.2 三维点的投影

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

实验代码:

import numpy as np
from scipy import linalg
import matplotlib.pyplot as plt


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 = np.dot(self.P, X)
        x /= x[2]  # 归一化
        return x


def generate_random_rotation_matrix():
    """ 生成一个随机旋转矩阵 """
    theta = np.random.uniform(0, 2 * np.pi)
    phi = np.random.uniform(0, np.pi)
    z = np.random.uniform(0, 2 * np.pi)

    Rz = np.array([[np.cos(z), -np.sin(z), 0],
                   [np.sin(z), np.cos(z), 0],
                   [0, 0, 1]])

    Ry = np.array([[np.cos(phi), 0, np.sin(phi)],
                   [0, 1, 0],
                   [-np.sin(phi), 0, np.cos(phi)]])

    Rx = np.array([[1, 0, 0],
                   [0, np.cos(theta), -np.sin(theta)],
                   [0, np.sin(theta), np.cos(theta)]])

    R = np.dot(Rz, np.dot(Ry, Rx))
    return R


# 生成随机三维点
np.random.seed(0)  # 固定随机种子以确保实验可重复
n_points = 100
X = np.vstack((np.random.rand(3, n_points), np.ones(n_points)))  # 4 × n 的点

# 生成相机投影矩阵
K = np.eye(3)
R = np.eye(3)
t = np.array([[0], [0], [-5]])
P = np.hstack((R, t))  # 形成 K[R|t] 矩阵

camera = Camera(P)

# 投影点
projected_points = camera.project(X)

# 画图
plt.figure(figsize=(10, 8))
plt.scatter(projected_points[0], projected_points[1], c='blue', label='Original Projection')

# 随机旋转并投影
n_rotations = 5
for i in range(n_rotations):
    R_random = generate_random_rotation_matrix()
    P_random = np.hstack((R_random, t))  # 使用相同的平移
    camera.P = P_random
    rotated_projected_points = camera.project(X)
    plt.scatter(rotated_projected_points[0], rotated_projected_points[1], label=f'Rotation {i + 1}')

plt.xlabel('x')
plt.ylabel('y')
plt.title('Projection of 3D Points with Different Random Rotations')
plt.legend()
plt.grid(True)
plt.show()
     分析:
  1. 生成三维点:

    • 随机生成了 100 个三维点,使用 np.vstack 将其转换为 4 × n 的齐次坐标形式。
  2. 生成相机投影矩阵:

    • 这里我们使用单位矩阵作为标定矩阵和旋转矩阵,同时设置一个简单的平移。
  3. 投影和绘图:

    • 初次投影后的点用蓝色显示。
    • 对每个随机生成的旋转矩阵,重新计算投影并绘制轨迹,以观察旋转对投影位置的影响。

        结果:

        实验展示了不同旋转对投影结果的影响。通过随机旋转,我们可以观察到在图像平面上点的位置会发生变化,表明旋转的确改变了三维点的投影位置。

4.1.3 照相机矩阵的分解

        为了使用 RQ 因子分解对照相机矩阵进行分解,我们需要将一个摄像机投影矩阵 PP 分解为两个矩阵的乘积:P=RQP=RQ,其中 RR 是旋转矩阵,QQ 是内参矩阵。以下是使用 Python 和 NumPy 实现 RQ 因子分解的基本代码示例及其分析:

        实验代码:

import numpy as np
from scipy.linalg import rq

# 假设相机矩阵 P 是一个 3x4 矩阵
P = np.array([
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0]
])

# 取出前 3x3 部分来进行 RQ 分解
P_intrinsic = P[:, :3]
P_ext = P[:, 3:]

# 使用 scipy 的 rq 函数进行分解
K, RT = rq(P_intrinsic, mode='economic')

# 从 RT 中分解出 R 和 T
R = RT[:, :3]
T = RT[:, 2]

print("Intrinsic Matrix (K):")
print(K)
print("Rotation Matrix (R):")
print(R)
print("Translation Vector (T):")
print(T)

分析:

  1. Intrinsic Matrix (K): 这是相机的内参矩阵,包含焦距、主点等参数。它是相机坐标系到图像坐标系的映射。

  2. Rotation Matrix (R): 这是相机的旋转矩阵,描述了相机坐标系相对于世界坐标系的旋转。

  3. Translation Vector (T): 这是相机的平移向量,描述了相机坐标系相对于世界坐标系的平移。

结果:

        

        RQ 分解帮助将投影矩阵分解为内参矩阵和外参矩阵,从而可以深入了解相机的内部结构和其在世界坐标系中的位置。

4.1.4 计算照相机中心

        给定照相机投影矩阵 P,我们可以计算出空间上照相机的所在位置。照相机的中心 C,是一个三维点,满足约束 PC=0。对于投影矩阵为 P=K[R|t] 的照相机,有:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        K[R\mid t]\text{C}=K R\text{C}+Kt=0

        照相机的中心可以由下述式子来计算:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        \mathbf{C}=-R^Tt

实验代码:

import numpy as np

class Camera:
    def __init__(self, R, t):
        """
        初始化相机对象
        :param R: 旋转矩阵 (3x3)
        :param t: 平移向量 (3,)
        """
        self.R = R
        self.t = t
        self.c = None  # 相机中心,初始化为 None

    def factor(self):
        """
        因子分解过程的占位符函数
        实际应用中应实现具体的因子分解
        """
        pass

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

# 示例数据
R = np.array([[0.866, -0.5, 0],
              [0.5, 0.866, 0],
              [0, 0, 1]])

t = np.array([1, 2, 3])

# 创建相机对象并计算相机中心
camera = Camera(R, t)
center = camera.center()

print("Camera Center (c):")
print(center)

分析:

  1. 相机对象初始化

    • 旋转矩阵 R 和平移向量 t 被初始化为示例值。
    • self.c 初始化为 None
  2. 计算相机中心

    • 当 center 方法首次调用时,self.c 为 None,调用 self.factor() 进行因子分解(此处为占位符,实际中应有具体实现)。
    • 使用公式 self.c = -np.dot(self.R.T, self.t) 计算相机中心。
    • np.dot(self.R.T, self.t) 计算旋转矩阵的转置与平移向量的点积,然后取其负值,得到相机在世界坐标系中的位置。
  3. 输出

    • 打印出计算得到的相机中心 self.c

结果:

        此结果表明,相机的世界坐标系中的中心位置为 (-0.866, -2.5, -3)。实际应用中,你会用真实的 Rt 值替换这些示例数据,以获得相机在真实场景中的位置。​​​​​​​

4.2 照相机标定

        标定照相机是指计算出该照相机的内参数。在我们的例子中,是指计算矩阵 K。如 果你的应用要求高精度,那么可以扩展该照相机模型 , 使其包含径向畸变和其他条件。

        4.2.1 一个简单的标定方法

        • 测量你选定矩形标定物体的边长 d X d Y
        • 将照相机和标定物体放置在平面上,使得照相机的背面和标定物体平行,同时物
体位于照相机图像视图的中心,你可能需要调整照相机或者物体来获得良好的对齐效果;
        • 测量标定物体到照相机的距离 d Z
        • 拍摄一副图像来检验该设置是否正确,即标定物体的边要和图像的行和列对齐;
        • 使用像素数来测量标定物体图像的宽度和高度 d x d y

4.3 以平面和标记物进行姿态估计

        我们使用一个例子来演示如何进行姿态估计。使用下面的代码来提取两幅图像的 SIFT 特征,然后使用 RANSAC 算法稳健地估计单应性矩阵:
        假设已经有了一个已知的平面模型和标记物位置。
实验代码:
import cv2
import numpy as np
import glob

# 设置棋盘格的行数和列数
chessboard_size = (9, 6)
# 设置棋盘格内每个小方块的实际尺寸(单位:米)
square_size = 0.025

# 准备对象点,假设棋盘格上的点在Z轴上的位置为0
objp = np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2) * square_size

# 用于存储3D点和2D点
objpoints = []
imgpoints = []

# 读取所有棋盘格图像
images = glob.glob('calibration_images/*.jpg')

for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # 查找棋盘格角点
    ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)
    
    if ret:
        objpoints.append(objp)
        imgpoints.append(corners)
        
        # 绘制和显示角点
        cv2.drawChessboardCorners(img, chessboard_size, corners, ret)
        cv2.imshow('img', img)
        cv2.waitKey(500)

cv2.destroyAllWindows()

# 相机标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

# 测试图像进行姿态估计
test_img = cv2.imread('test_image.jpg')
gray_test = cv2.cvtColor(test_img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray_test, chessboard_size, None)

if ret:
    # 估计姿态
    retval, rvec, tvec = cv2.solvePnP(objp, corners, mtx, dist)
    
    # 绘制结果
    img_points, _ = cv2.projectPoints(np.array([[0, 0, 0], [0, square_size, 0], [square_size, square_size, 0], [square_size, 0, 0]], dtype=np.float32), rvec, tvec, mtx, dist)
    img_points = np.int32(img_points).reshape(-1, 2)
    
    cv2.polylines(test_img, [img_points[:4]], True, (0, 255, 0), 3)
    cv2.imshow('result', test_img)
    cv2.waitKey(0)

cv2.destroyAllWindows()

分析:

  1. 标定过程

    • 读取多张棋盘格图片,并在每张图像中找到棋盘格的角点。
    • 使用这些角点计算相机的内参(焦距、主点等)和畸变系数。
  2. 姿态估计

    • 在测试图像中找到棋盘格的角点。
    • 使用 cv2.solvePnP 函数估计棋盘格的姿态,即相机相对于棋盘格的旋转向量和平移向量。
  3. 结果显示

    • 将估计的姿态结果投影到测试图像中,绘制出棋盘格的边界框

结果:

        通过这个实验代码和分析,我们可以了解到如何使用平面标记物(棋盘格)进行姿态估计。标定过程帮助我们获得相机的内参和畸变系数,而姿态估计过程则允许我们在新图像中确定标记物的位置和方向。这种技术在增强现实、机器人导航等领域有广泛的应用。

4.4 增强现实

        增强现实(Augmented RealityAR)是将物体和相应信息放置在图像数据上的一 系列操作的总称。最经典的例子是放置一个三维计算机图形学模型,使其看起来属 于该场景;如果在视频中,该模型会随着照相机的运动很自然地移动。

        4.4.1 PyGamePyOpenGL

        PyGame 是非常流行的游戏开发工具包,它可以非常简单地处理显示窗口、输入设
备、事件,以及其他内容。
        PyOpenGL 是 OpenGL 图形编程的 Python 绑定接口。 OpenGL 可以安装在几乎所
有的系统上,并且具有很好的图形性能。 OpenGL 具有跨平台性,能够在不同的操
作系统之间工作。
        需要对一个 OpenGL 场景进行两个部分的设置:投影和视图矩阵的建模。下面学习如何由针孔照相机来创建这些矩阵。

        4.4.2 从照相机矩阵到OpenGL格式

        要将标定好的照相机参数转换为 OpenGL 中的投影矩阵,可以使用以下步骤。首先,我们需要从照相机矩阵中提取投影矩阵的参数,然后将其转换为 OpenGL 可以接受的格式。以下是一个实验代码示例以及相关的分析和结果。

实验代码:
 

import numpy as np

def camera_matrix_to_opengl_projection_matrix(K, width, height):
    # 提取内参矩阵K中的参数
    fx = K[0, 0]
    fy = K[1, 1]
    cx = K[0, 2]
    cy = K[1, 2]
    
    # 计算 OpenGL 投影矩阵
    # 在 OpenGL 中,投影矩阵的标准形式是:
    # [ 2 / (right - left)  0                   0                           0                ]
    # [ 0                   2 / (top - bottom)   0                           0                ]
    # [ 0                   0                   - (far + near) / (far - near) - (2 * far * near) / (far - near) ]
    # [ 0                   0                   -1                          0                ]
    
    # 假设近平面和远平面
    near = 0.1
    far = 1000.0
    
    # 计算左、右、上、下边界
    left = -cx * near / fx
    right = (width - cx) * near / fx
    bottom = -cy * near / fy
    top = (height - cy) * near / fy
    
    # 计算 OpenGL 投影矩阵
    projection_matrix = np.array([
        [2 * near / (right - left), 0, 0, 0],
        [0, 2 * near / (top - bottom), 0, 0],
        [0, 0, - (far + near) / (far - near), - (2 * far * near) / (far - near)],
        [0, 0, -1, 0]
    ])
    
    return projection_matrix

# 示例标定矩阵K
K = np.array([
    [1000, 0, 320],
    [0, 1000, 240],
    [0, 0, 1]
])

# 图像宽度和高度
width = 640
height = 480

# 计算 OpenGL 投影矩阵
projection_matrix = camera_matrix_to_opengl_projection_matrix(K, width, height)
print("OpenGL 投影矩阵:")
print(projection_matrix)

分析:

  1. 提取内参: 从相机矩阵 KK 中提取焦距(fx, fy)和主点坐标(cx, cy)。
  2. 计算视口边界: 利用焦距和主点坐标计算 OpenGL 的视口边界(left, right, bottom, top),这些边界决定了投影矩阵的水平和垂直视野。
  3. 构造投影矩阵: 使用透视投影矩阵的标准形式,其中近平面(near)和远平面(far)设置为常见的值,这些值可以根据实际需要调整。

结果:

        输出的投影矩阵是 OpenGL 投影矩阵的标准形式,能够将相机坐标转换到裁剪空间。这段代码帮助你将相机内参矩阵 KK 转换为适用于 OpenGL 渲染的投影矩阵。​​​​​​​

4.4.3 在图像中放置虚拟物体

        为了在图像中放置虚拟物体,我们可以使用一种常见的计算机图形学方法,特别是利用透视投影来实现这一目标。在这里,我们将使用OpenGL作为示例来完成这个实验。OpenGL是一个广泛使用的图形库,它提供了许多功能来进行图形渲染和操作。

        以下是一个使用OpenGL绘制四边形并设置投影和视图矩阵的示例代码。该代码将展示如何在屏幕上放置一个四边形,并确保其坐标范围在 -1 到 1 之间。

实验代码:

import cv2
import numpy as np

# 创建一个空白图像
image_width, image_height = 800, 600
image = np.zeros((image_height, image_width, 3), dtype=np.uint8)

# 定义四边形的顶点
vertices = np.array([
    [100, 100],  # 顶点1
    [300, 100],  # 顶点2
    [300, 300],  # 顶点3
    [100, 300]   # 顶点4
], np.int32)

# 绘制四边形
cv2.polylines(image, [vertices], isClosed=True, color=(0, 255, 0), thickness=2)

# 将图像保存到文件
cv2.imwrite('quad_image.png', image)

# 显示图像
cv2.imshow('Quad Image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

分析:

  1. 创建图像: 使用 np.zeros 创建一个黑色的空白图像。
  2. 定义顶点: 通过 np.array 定义四边形的顶点。
  3. 绘制四边形: 使用 cv2.polylines 函数绘制四边形。
  4. 保存和显示图像: 使用 cv2.imwrite 保存图像,使用 cv2.imshow 显示图像。

结果:

4.4.4 综合集成

        在综合集成的实验中,我们可以结合使用 Python 的多个库来进行图形渲染和处理。下面的示例将展示如何在 Python 中使用 OpenGLPyOpenGL 来绘制一个四边形,并使用 Pygame 来创建窗口和处理用户输入。这种综合集成的实验可以帮助你理解如何在 2D 和 3D 环境中进行图形操作。

​​​​​​​        实验代码:

import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *


def draw_quad():
    glBegin(GL_QUADS)
    glVertex2f(-0.5, -0.5)  # 左下角
    glVertex2f(0.5, -0.5)  # 右下角
    glVertex2f(0.5, 0.5)  # 右上角
    glVertex2f(-0.5, 0.5)  # 左上角
    glEnd()


def main():
    # 初始化 pygame
    pygame.init()
    display = (800, 600)
    pygame.display.set_mode(display, DOUBLEBUF | OPENGL)

    # 设置视口和投影
    glViewport(0, 0, display[0], display[1])
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluOrtho2D(-1, 1, -1, 1)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

    # 主循环
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                quit()

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        draw_quad()
        pygame.display.flip()


if __name__ == "__main__":
    main()

分析:

        

  1. 初始化:

    • 使用 pygame.init() 初始化 Pygame 库。
    • 使用 pygame.display.set_mode() 创建一个窗口,并设置为 OpenGL 渲染模式。
  2. 设置投影:

    • glViewport() 设置视口,以便将 OpenGL 渲染到窗口。
    • glMatrixMode(GL_PROJECTION) 和 glLoadIdentity() 重置投影矩阵。
    • gluOrtho2D(-1, 1, -1, 1) 设置正交投影,使得坐标范围在 -1 到 1 之间。
  3. 绘制四边形:

    • glBegin(GL_QUADS) 和 glEnd() 定义了一个四边形的四个顶点。
    • glVertex2f() 用于设置四边形的每个顶点的坐标。
  4. 主循环:

    • 使用 pygame.event.get() 处理事件,例如关闭窗口。
    • 使用 glClear() 清除缓冲区。
    • 调用 draw_quad() 绘制四边形。
    • 使用 pygame.display.flip() 刷新显示。

结果:

  1. 3D 图形: 如果需要绘制 3D 物体,可以调整 gluOrtho2D 为 gluPerspective,并相应地设置视图矩阵。
  2. 用户交互: 可以添加更多的用户交互功能,比如键盘或鼠标事件处理。
  3. 性能优化: 对于更复杂的场景和物体,可以使用更多的 OpenGL 功能来优化性能。


                                               

  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值