Python计算机视觉——照相机模型与增强现实

文章目录

  • 照相机模型与增强现实
    • 4.1 针孔照相机模型
      • 4.1.1 照相机矩阵
      • 4.1.2 三维点的投影
      • 4.1.3 照相机矩阵的分解
      • 4.1.4 计算照相机中心
    • 4.2 照相机标定
    • 4.3 以平面和标记物进行姿态估计
    • 4.4 增强现实(AR)
      • 前期准备工作
      • 中期进展
      • 综合代码及运行结果
      • 回顾
  • 20220522相机标定补充实验
    • 实验分析

照相机模型与增强现实

处理三维图像和平面图像之间的映射,需要在映射中加入部分照相机产生图像过程的投影特性。

4.1 针孔照相机模型

针孔照相机模型是计算机视觉中广泛使用的照相机模型,具有足够的精确度,模型如下图所示:
Python计算机视觉——照相机模型与增强现实_第1张图片
在针孔照相机中,三维点 X X X投影为图像点 x x x(两个点都是用齐次坐标表示的),如下所示:
λ x = P X \lambda x=PX λx=PX
这里,3×4 的矩阵 P P P为照相机矩阵(或投影矩阵)。在齐次坐标系中,三维点 X X X的坐标由4个元素组成, X = [ X , Y , Z , W ] X=[X, Y, Z, W] X=[X,Y,Z,W]。这里的标量 λ λ λ是三维点的逆深度。

4.1.1 照相机矩阵

照相机矩阵可以分解为:
P = K [ R ∣ t ] P=K[R|t] P=K[Rt]
其中, R R R是描述照相机方向的旋转矩阵, t t t是描述照相机中心位置的三维平移向量,内标定矩阵 K K K描述照相机的投影性质。
K = [ α f s c x 0 f c y 0 0 1 ] \boldsymbol{K}=\left[\begin{array}{ccc} \alpha f & s & c_{x} \\ 0 & f & c_{y} \\ 0 & 0 & 1 \end{array}\right] K=αf00sf0cxcy1
焦距 f f f为图像平面和照相机中心间的距离。当像素数组在传感器上偏斜的时候,需要用到倾斜参数 s s s。在大多数情况下, s s s可以设置成 0。纵横比例参数 α 是在像素元素非正方形的情况下使用的, f x = α f y f_x=\alpha f_y fx=αfy。通常情况下,我们可以默认设置 α=1。

综上分析,标定矩阵变为:
K = [ f 0 c x 0 f c y 0 0 1 ] \boldsymbol{K}=\left[\begin{array}{ccc} f & 0 & c_{x} \\ 0 & f & c_{y} \\ 0 & 0 & 1 \end{array}\right] K=f000f0cxcy1
标定矩阵中剩余的唯一参数为光心(有时称主点)的坐标 c = [ c x , c y ] c=[cx,cy] c=[cx,cy],也就是光线坐标轴和图像平面的交点,光心的坐标常接近于图像宽度和高度的一半,因此在标定矩阵 K K K中,未知参数只有 f f f

4.1.2 三维点的投影

创建照相机类,处理对照相机和投影建模所需要的全部操作,为了方便使用,在此把需要的函数全部写出:

from numpy import *
from scipy import linalg


class Camera(object):
    """ Class for representing pin-hole cameras. """
    
    def __init__(self,P):
        """ Initialize P = K[R|t] camera model. """
        self.P = P
        self.K = None # calibration matrix
        self.R = None # rotation
        self.t = None # translation
        self.c = None # camera center
        
    
    def project(self,X):
        """    Project points in X (4*n array) and normalize coordinates. """
        
        x = dot(self.P,X)
        for i in range(3):
            x[i] /= x[2]    
        return x
        
        
    def factor(self):
        """    Factorize the camera matrix into K,R,t as P = K[R|t]. """
        
        # factor first 3*3 part
        K,R = linalg.rq(self.P[:,:3])
        
        # make diagonal of K positive
        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 is its own inverse
        self.t = dot(linalg.inv(self.K),self.P[:,3])
        
        return self.K, self.R, self.t
        
    
    def center(self):
        """    Compute and return the camera center. """
    
        if self.c is not None:
            return self.c
        else:
            # compute c by factoring
            self.factor()
            self.c = -dot(self.R.T,self.t)
            return self.c



# helper functions    

def rotation_matrix(a):
    """    Creates a 3D rotation matrix for rotation
        around the axis of the vector 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
    

def rq(A):
    from scipy.linalg import qr
    
    Q,R = qr(flipud(A).T)
    R = flipud(R.T)
    Q = Q.T
    
    return R[:,::-1],Q[::-1,:]

下 面 的 例 子 展 示 如 何 将 三 维 中 的 点 投 影 到 图 像 视 图 中 \color{red}{下面的例子展示如何将三维中的点投影到图像视图中}

此例需要牛津多视图数据集中的“Model Housing”数据集,下载地址为:
https://www.robots.ox.ac.uk/~vgg/data/mview/
Python计算机视觉——照相机模型与增强现实_第2张图片
需要图片的话也可以把图片下载下来。
将其中的house.p3d文件复制到你的工作目录里,之后运行代码:

# -*- coding: utf-8 -*-
import random
from PIL import Image
from numpy import *
from pylab import *
import camera

im = np.array(Image.open('house.003.pgm').convert('L'))
figure()
imshow(im)    
show()

points = loadtxt('house.p3d').T
points = vstack((points,ones(points.shape[1])))
P = np.hstack((np.eye(3),np.array(([0],[0],[-10]))))
cam = camera.Camera(P)
x = cam.project(points)
figure()
plot(x[0],x[1],'*')
show()

#创建变换
r = 0.05*random(3)
rot = camera.rotation_matrix(r)
figure()
for t in range(20):
    cam.P = np.dot(cam.P,rot)
    x = cam.project(points)
    plot(x[0],x[1],'b+')
    # 注意对于旋转,t向量是保持不变的
show()

运行结果为:
Python计算机视觉——照相机模型与增强现实_第3张图片
该代码围绕一个随机的三维向量,进行增量旋转的投影。
Python计算机视觉——照相机模型与增强现实_第4张图片

4.1.3 照相机矩阵的分解

对照相机矩阵 P P P,需要恢复内参数 K K K以及照相机的位置 t t t和姿势 R R R。矩阵分块操作称为因子分解,这里使用的矩阵因子分解方法为 R Q RQ RQ因子分解。因为 R Q RQ RQ因子分解的结果不唯一,分解的结果存在符号二义性,所以需要限制矩阵 R R R为正定的,可以在求解到的结果中加入变换 T T T来改变符号

代码如下:

import camera

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.Camera(dot(K,Rt))

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

运行结果如下:
Python计算机视觉——照相机模型与增强现实_第5张图片

4.1.4 计算照相机中心

给定照相机投影矩阵P,可以计算出空间上照相机的所在位置。照相机的中心C满足PC=0,
K [ R ∣ t ] C = K R C + K t = 0 \boldsymbol{K}[\boldsymbol{R} \mid \boldsymbol{t}] \mathbf{C}=\boldsymbol{K} \boldsymbol{R C}+\boldsymbol{K} \boldsymbol{t}=0 K[Rt]C=KRC+Kt=0
于是,
C = − R T t C=-R^Tt C=RTt
代码已经写入camera类,函数为: def center(self)

4.2 照相机标定

标定照相机指的是计算该照相机的内参数,也就是计算本例中的 K K K。标定照
相机的标准方法是,拍摄多幅平面棋盘模式的图像,然后进行处理计算。

一个简单的标定方法:需要准备一个平面矩形的标定物体(一个书本即可)、用于测量的卷尺和直尺,以及一个平面。

下面是具体操作步骤:

  1. 测量你选定矩形标定物体的边长 d X dX dX d Y dY dY
  2. 将照相机和标定物体放置在平面上,使得照相机的背面和标定物体平行,同时物体位于照相机图像视图的中心,你可能需要调整照相机或者物体来获得良好的对齐效果;
  3. 测量标定物体到照相机的距离 d Z dZ dZ
  4. 拍摄一副图像来检验该设置是否正确,即标定物体的边要和图像的行和列对齐;
  5. 使用像素数来测量标定物体图像的宽度和高度 d x dx dx d y dy dy
    Python计算机视觉——照相机模型与增强现实_第6张图片
    使用相似三角形可获得焦距:
    f x = d x   d X   d Z , f y = d y   d Y   d Z f_{x}=\frac{\mathrm{d} x}{\mathrm{~d} X} \mathrm{~d} Z, \quad f_{y}=\frac{\mathrm{d} y}{\mathrm{~d} Y} \mathrm{~d} Z fx= dXdx dZ,fy= dYdy dZ
    p.s.焦距是在特定图像分辨率下计算出来的,焦距和光心是使用像素来度量的,其尺度和图像分辨率相关

代码如下:

def my_calibration(sz):
    row, col = sz
    fx = 2555 * col / 2592
    fy = 2586 * row / 1936
    K = diag([fx, fy, 1])
    K[0, 2] = 0.5 * col
    K[1, 2] = 0.5 * row
    return K

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

如果图像中包含平面状的标记物体,并且以对照相机进行了标定,那么我们可以计算出照相机的姿态(旋转和平移)。这里的标记物可以为对任何平坦的物体。

姿态估计的方法:

1 、 先 提 取 两 幅 图 像 的 S I F T 特 征 , 然 后 使 用 R A N S A C 算 法 稳 健 地 估 计 单 应 性 矩 阵 \color{red}{1、 先提取两幅图像的SIFT特征,然后使用RANSAC算法稳健地估计单应性矩阵} 1SIFT使RANSAC

# compute features
sift.process_image('a3.jpg', 'a3.sift')
l0, d0 = sift.read_features_from_file('a3.sift')

sift.process_image('a4.jpg', 'a4.sift')
l1, d1 = sift.read_features_from_file('a4.sift')

# match features and estimate homography
matches = sift.match_twosided(d0, d1)
ndx = matches.nonzero()[0]
fp = homography.make_homog(l0[ndx, :2].T)
ndx2 = [int(matches[i]) for i in ndx]
tp = homography.make_homog(l1[ndx2, :2].T)

model = homography.RansacModel()
H, inliers = homography.H_from_ransac(fp, tp, model)

2 、 定 义 相 应 的 三 维 坐 标 系 , 使 标 记 物 在 X − Y 平 面 上 ( Z = 0 ) , 原 点 在 标 记 物 的 某 位 置 上 \color{red}{2、 定义相应的三维坐标系,使标记物在X-Y平面上(Z=0),原点在标记物的某位置上} 2使XYZ=0

为了检验单应性矩阵结果的正确性,我们需要将一些简单的三维物体放置在标记物上,这里我们使用一个立方体。可以使用下面的函数来产生立方体上的点:

def cube_points(c, wid):
    """ Creates a list of points for plotting
        a cube with plot. (the first 5 points are
        the bottom square, some sides repeated). """
    p = []
    # bottom
    p.append([c[0] - wid, c[1] - wid, c[2] - wid])
    p.append([c[0] - wid, c[1] + wid, c[2] - wid])
    p.append([c[0] + wid, c[1] + wid, c[2] - wid])
    p.append([c[0] + wid, c[1] - wid, c[2] - wid])
    p.append([c[0] - wid, c[1] - wid, c[2] - wid])  # same as first to close plot

    # top
    p.append([c[0] - wid, c[1] - wid, c[2] + wid])
    p.append([c[0] - wid, c[1] + wid, c[2] + wid])
    p.append([c[0] + wid, c[1] + wid, c[2] + wid])
    p.append([c[0] + wid, c[1] - wid, c[2] + wid])
    p.append([c[0] - wid, c[1] - wid, c[2] + wid])  # same as first to close plot

    # vertical sides
    p.append([c[0] - wid, c[1] - wid, c[2] + wid])
    p.append([c[0] - wid, c[1] + wid, c[2] + wid])
    p.append([c[0] - wid, c[1] + wid, c[2] - wid])
    p.append([c[0] + wid, c[1] + wid, c[2] - wid])
    p.append([c[0] + wid, c[1] + wid, c[2] + wid])
    p.append([c[0] + wid, c[1] - wid, c[2] + wid])
    p.append([c[0] + wid, c[1] - wid, c[2] - wid])

    return array(p).T

3 、 有 了 单 应 性 矩 阵 和 照 相 机 的 标 定 矩 阵 , 现 在 可 以 得 出 两 个 视 图 间 的 相 对 变 换 \color{red}{3、 有了单应性矩阵和照相机的标定矩阵,现在可以得出两个视图间的相对变换} 3

代码为:

# 计算照相机标定矩阵
K = my_calibration((300, 400))

# 位于边长为0.2 z=0平面的三维点
box = cube_points([0, 0, 0.1], 0.1)

# 投影第一幅图像上底部的正方形
cam1 = camera.Camera(hstack((K, dot(K, array([[0], [0], [-1]])))))
# 底部正方形上的点
box_cam1 = cam1.project(homography.make_homog(box[:, :5]))


# 使用H将点变换到第二幅图像中
box_trans = homography.normalize(dot(H,box_cam1))

# 从cam1和H中计算第二个照相机矩阵
cam2 = camera.Camera(dot(H, cam1.P))
A = dot(linalg.inv(K), cam2.P[:, :3])
A = array([A[:, 0], A[:, 1], cross(A[:, 0], A[:, 1])]).T
cam2.P[:, :3] = dot(K, A)

# 使用第二个照相机矩阵投影
box_cam2 = cam2.project(homography.make_homog(box))

# 测试:将点投影在 z=0 上,应该能够得到相同的点
point = array([1,1,0,1]).T
print(homography.normalize(dot(dot(H,cam1.P),point)))
print(cam2.project(point))

我们使用的图像的分辨率为300×400,第一个产生的标定矩阵就是在该图像分辨率下的标定矩阵。
因为场景坐标的尺度是任意的,所以我们使用下面的矩阵来创建第一个照相机:
P 1 = K [ 1 0 0 0 0 1 0 0 0 0 1 − 1 ] \boldsymbol{P}_{1}=\boldsymbol{K}\left[\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & -1 \end{array}\right] P1=K100010001001
有了估计出的单应性矩阵,我们可以将其变换到第二幅图像上,绘制出变换后的图像 P 2 P_2 P2
P 2 = H P 1 P_2=HP_1 P2=HP1
作为合理性验证,可以使用新矩阵投影标记平面的一个点,然后检查投影后的点是否与使用第一个照相机和单应性矩阵变换后的点相同。

4 、 可 视 化 投 影 后 的 点 \color{red}{4、可视化投影后的点 } 4
代码为:

# plotting
im0 = array(Image.open('a3.jpg'))
im1 = array(Image.open('a4.jpg'))

figure()
imshow(im0)
plot(box_cam1[0, :], box_cam1[1, :], linewidth=3)
title('2D projection of bottom square')
axis('off')

figure()
imshow(im1)
plot(box_trans[0, :], box_trans[1, :], linewidth=3)
title('2D projection transfered with H')
axis('off')

figure()
imshow(im1)
plot(box_cam2[0, :], box_cam2[1, :], linewidth=3)
title('3D points projected in second image')
axis('off')

show()

运行结果为:
Python计算机视觉——照相机模型与增强现实_第7张图片

Python计算机视觉——照相机模型与增强现实_第8张图片

Python计算机视觉——照相机模型与增强现实_第9张图片

4.4 增强现实(AR)

增强现实(Augmented Reality,AR)是将物体和相应信息放置在图像数据上的一系列操作的总称。

在本章的最后一节,我们介绍如何建立一个简单的增强现实例子。其中,会用到两个工具包:PyGame和PyOpenGL

OpenGL 中这里主要使用两个部分:GL 部分包含所有以“gl”开头的函数,其中包含我们需要的大部分函数;GLU 部分是 OpenGL的实用函数库,里面包含了一些高层的函数。我们主要使用它来设置照相机投影。
pygame 部分用来设置窗口和事件控制;其中pygame.image 用来载入图像和创建 OpenGL 的纹理,pygame.locals 用来设置 OpenGL 的显示区域。

前期准备工作

P y G a m e 安 装 过 程 : \color{red}{PyGame安装过程:} PyGame

直接在anaconda(或者cmd)下输入:!pip install pygame --upgrade
在这里插入图片描述
之后输入:import pygame,并执行
在这里插入图片描述
得到图中结果,即为安装成功。

P y O p e n G L 安 装 过 程 : \color{red}{PyOpenGL安装过程:} PyOpenGL

点击链接:https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyopengl
不要用QQ浏览器打开!!!(QQ浏览器下载不了pyopengl…)
进入链接后,选到PyOpenGL的下载区域

Python计算机视觉——照相机模型与增强现实_第10张图片
不要下载accelerate的.whl(留意一下,不要下载错了)
在这里插入图片描述
因为我的Python是3.6版本的,所以选择下载下图中的-cp36-(具体下载cp__,因Python版本而异)
注意:下载的是-amd64.whl !!!
Python计算机视觉——照相机模型与增强现实_第11张图片
下载成功后,输入下图代码,无报错。
在这里插入图片描述
测试代码为:

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
 
def Draw():
    glClear(GL_COLOR_BUFFER_BIT)
    glRotatef(0.5, 0, 1, 0)
    glutWireTeapot(0.5)
    glFlush()
 
glutInit()
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA)
glutInitWindowSize(400, 400)
glutCreateWindow("test")
glutDisplayFunc(Draw)
glutIdleFunc(Draw)
glutMainLoop() 

这个壶是会转的!!!(优秀啊)
Python计算机视觉——照相机模型与增强现实_第12张图片
注意:在anaconda上执行上面代码的话,出不来图!!!我是在sublime上执行的。

中期进展

1、假设我们已经获得了标定好的照相机,即已知标定矩阵 K,下面的函数可以将照相机参数转换为 OpenGL 中的投影矩阵

def set_projection_from_camera(K):
    """从照相机标定矩阵中获得视图"""
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    fx = K[0, 0]
    fy = K[1, 1]
    fovy = 2 * math.atan(0.5 * height / fy) * 180 / math.pi
    aspect = (width * fy) / (height * fx)
    near = 0.1  #定义近的和远的裁剪平面
    far = 100.0
    gluPerspective(fovy, aspect, near, far)  #设定透视
    glViewport(0, 0, width, height)

2、获得移除标定矩阵后的 3×4 针孔照相机矩阵(将 P 和 K-1 相乘),并创建一个模拟视图

def set_modelview_from_camera(Rt):
    """从照相机姿态中获得模拟视图矩阵"""
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    Rx = np.array([[1, 0, 0], [0, 0, -1], [0, 1, 0]])  #围绕x轴将茶壶旋转90度,使z轴向上
    R = Rt[:, :3]  #获得旋转的最佳逼近
    U, S, V = np.linalg.svd(R)
    R = np.dot(U, V)
    R[0, :] = -R[0, :]  #改变x轴的符号
    t = Rt[:, 3]  #获得平移量
    M = np.eye(4) #获得4*4的模拟视图矩阵
    M[:3, :3] = np.dot(R, Rx)
    M[:3, 3] = t
    M = M.T  #转置并压平以获得列序数值
    m = M.flatten()
    glLoadMatrixf(m)  #将模拟视图矩阵替换为新的矩阵

3、载入一幅图像,然后将其转换成一个 OpenGL 纹理,并将该纹理放置在四边形上

def draw_background(imname):
    """使用四边形绘制背景图像"""
    
    #载入背景图像,转为OpenGL纹理
    bg_image = pygame.image.load(imname).convert()
    bg_data = pygame.image.tostring(bg_image, "RGBX", 1)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    #绑定纹理
    glEnable(GL_TEXTURE_2D)
    glBindTexture(GL_TEXTURE_2D, glGenTextures(1))
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bg_data)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    #创建四方形填充整个窗口
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-1.0, -1.0, -1.0)
    glTexCoord2f(1.0, 0.0);
    glVertex3f(1.0, -1.0, -1.0)
    glTexCoord2f(1.0, 1.0);
    glVertex3f(1.0, 1.0, -1.0)
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-1.0, 1.0, -1.0)
    glEnd()
    #清除纹理
    glDeleteTextures(1)

4、将物体放置入场景中

def draw_teapot(size):  # 红色茶壶
    glEnable(GL_LIGHTING)
    glEnable(GL_LIGHT0)
    glEnable(GL_DEPTH_TEST)
    glClear(GL_DEPTH_BUFFER_BIT)
    # 绘制红色茶壶
    glMaterialfv(GL_FRONT, GL_AMBIENT, [0, 0, 0, 0])
    glMaterialfv(GL_FRONT, GL_DIFFUSE, [0.5, 0.0, 0.0, 0.0])
    glMaterialfv(GL_FRONT, GL_SPECULAR, [0.7, 0.6, 0.6, 0.0])
    glMaterialf(GL_FRONT, GL_SHININESS, 0.25 * 128.0)
    glutSolidTeapot(size)


def drawFunc(size):  # 白色茶壶
    glRotatef(0.5, 5, 5, 0)  # (角度,x,y,z)
    glutWireTeapot(size)
    # 刷新显示
    glFlush()

(二者选其一即可)

综合代码及运行结果

代码:

# -*- coding: utf-8 -*-
import math
import pickle
import sys
from pylab import *
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import pygame, pygame.image
from pygame.locals import *
import homography, camera
import sift


def cube_points(c, wid):  # 绘制立方体的一各点列表
    """ Creates a list of points for plotting
        a cube with plot. (the first 5 points are
        the bottom square, some sides repeated). """
    p = []
    # 底部
    p.append([c[0] - wid, c[1] - wid, c[2] - wid])
    p.append([c[0] - wid, c[1] + wid, c[2] - wid])
    p.append([c[0] + wid, c[1] + wid, c[2] - wid])
    p.append([c[0] + wid, c[1] - wid, c[2] - wid])
    p.append([c[0] - wid, c[1] - wid, c[2] - wid])  # 和第一个相同

    # 顶部
    p.append([c[0] - wid, c[1] - wid, c[2] + wid])
    p.append([c[0] - wid, c[1] + wid, c[2] + wid])
    p.append([c[0] + wid, c[1] + wid, c[2] + wid])
    p.append([c[0] + wid, c[1] - wid, c[2] + wid])
    p.append([c[0] - wid, c[1] - wid, c[2] + wid])  # 和第一个相同

    # 竖直边
    p.append([c[0] - wid, c[1] - wid, c[2] + wid])
    p.append([c[0] - wid, c[1] + wid, c[2] + wid])
    p.append([c[0] - wid, c[1] + wid, c[2] - wid])
    p.append([c[0] + wid, c[1] + wid, c[2] - wid])
    p.append([c[0] + wid, c[1] + wid, c[2] + wid])
    p.append([c[0] + wid, c[1] - wid, c[2] + wid])
    p.append([c[0] + wid, c[1] - wid, c[2] - wid])

    return array(p).T


def my_calibration(sz):
    row, col = sz
    fx = 2555 * col / 2592
    fy = 2586 * row / 1936
    K = diag([fx, fy, 1])
    K[0, 2] = 0.5 * col
    K[1, 2] = 0.5 * row
    return K


def set_projection_from_camera(K):  # 获取视图
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    fx = K[0, 0]
    fy = K[1, 1]
    fovy = 2 * math.atan(0.5 * height / fy) * 180 / math.pi
    aspect = (width * fy) / (height * fx)
    # 定义近和远的剪裁平面
    near = 0.1
    far = 100.0
    # 设定透视
    gluPerspective(fovy, aspect, near, far)
    glViewport(0, 0, width, height)


def set_modelview_from_camera(Rt):  # 获取矩阵
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    # 围绕x轴将茶壶旋转90度,使z轴向上
    Rx = np.array([[1, 0, 0], [0, 0, -1], [0, 1, 0]])
    # 获得旋转的最佳逼近
    R = Rt[:, :3]
    U, S, V = np.linalg.svd(R)
    R = np.dot(U, V)
    R[0, :] = -R[0, :]  # 改变x轴的符号
    # 获得平移量
    t = Rt[:, 3]
    # 获得4*4的的模拟视图矩阵
    M = np.eye(4)
    M[:3, :3] = np.dot(R, Rx)
    M[:3, 3] = t
    # 转置并压平以获取列序数值
    M = M.T
    m = M.flatten()
    # 将模拟视图矩阵替换成新的矩阵
    glLoadMatrixf(m)


def draw_background(imname):
    # 载入背景图像
    bg_image = pygame.image.load(imname).convert()
    bg_data = pygame.image.tostring(bg_image, "RGBX", 1)  # 将图像转为字符串描述
    glMatrixMode(GL_MODELVIEW)  # 将当前矩阵指定为投影矩阵
    glLoadIdentity()  # 把矩阵设为单位矩阵

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)  # 清楚颜色、深度缓冲
    glEnable(GL_TEXTURE_2D)  # 纹理映射
    glBindTexture(GL_TEXTURE_2D, glGenTextures(1))
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bg_data)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    # 绑定纹理
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-1.0, -1.0, -1.0)
    glTexCoord2f(1.0, 0.0);
    glVertex3f(1.0, -1.0, -1.0)
    glTexCoord2f(1.0, 1.0);
    glVertex3f(1.0, 1.0, -1.0)
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-1.0, 1.0, -1.0)
    glEnd()
    glDeleteTextures(1)  # 清除纹理


def draw_teapot(size):  # 红色茶壶
    glEnable(GL_LIGHTING)
    glEnable(GL_LIGHT0)
    glEnable(GL_DEPTH_TEST)
    glClear(GL_DEPTH_BUFFER_BIT)
    # 绘制红色茶壶
    glMaterialfv(GL_FRONT, GL_AMBIENT, [0, 0, 0, 0])
    glMaterialfv(GL_FRONT, GL_DIFFUSE, [0.5, 0.0, 0.0, 0.0])
    glMaterialfv(GL_FRONT, GL_SPECULAR, [0.7, 0.6, 0.6, 0.0])
    glMaterialf(GL_FRONT, GL_SHININESS, 0.25 * 128.0)
    glutSolidTeapot(size)


def drawFunc(size):  # 白色茶壶
    glRotatef(0.5, 5, 5, 0)  # (角度,x,y,z)
    glutWireTeapot(size)
    # 刷新显示
    glFlush()


width, height = 300,400


def setup():  # 设置窗口和pygame环境
    pygame.init()
    pygame.display.set_mode((width, height), OPENGL | DOUBLEBUF)
    pygame.display.set_caption("OpenGL AR demo")


# 计算特征
sift.process_image('a3.jpg', 'a3.sift')
l0, d0 = sift.read_features_from_file('a3.sift')

sift.process_image('a4.jpg', 'a4.sift')
l1, d1 = sift.read_features_from_file('a4.sift')

# 匹配特征,计算单应性矩阵
matches = sift.match_twosided(d0, d1)
ndx = matches.nonzero()[0]
fp = homography.make_homog(l0[ndx, :2].T)
ndx2 = [int(matches[i]) for i in ndx]
tp = homography.make_homog(l1[ndx2, :2].T)

model = homography.RansacModel()
H, inliers = homography.H_from_ransac(fp, tp, model)

# 计算照相机标定矩阵
K = my_calibration((300,400))
# 位于边长为0.2,z=0平面上的三维点
box = cube_points([0, 0, 0.1], 0.1)

# 投影第一幅图下个上底部的正方形
cam1 = camera.Camera(hstack((K, dot(K, array([[0], [0], [-1]])))))
# 底部正方形上的点
box_cam1 = cam1.project(homography.make_homog(box[:, :5]))

# 使用H将点变换到第二幅图像中
box_trans = homography.normalize(dot(H, box_cam1))

# 从cam1和H中计算第二个照相机矩阵
cam2 = camera.Camera(dot(H, cam1.P))
A = dot(linalg.inv(K), cam2.P[:, :3])
A = array([A[:, 0], A[:, 1], cross(A[:, 0], A[:, 1])]).T
cam2.P[:, :3] = dot(K, A)
# 使用第二个照相机矩阵投影
box_cam2 = cam2.project(homography.make_homog(box))

Rt = dot(linalg.inv(K), cam2.P)
setup()
draw_background("a4.jpg")
set_projection_from_camera(K)
set_modelview_from_camera(Rt)

draw_teapot(0.05)  # 显示红色茶壶
# drawFunc(0.05)  # 显示白色空心茶壶
pygame.display.flip()
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

运行结果:
Python计算机视觉——照相机模型与增强现实_第13张图片
运行后的图发现作为背景板的图片很奇怪,变成了四幅一样的图拼接起来,并且清晰度不是很高,于是又检查了代码。

更 改 代 码 后 运 行 结 果 图 为 : \color{red}{更改代码后运行结果图为:}
Python计算机视觉——照相机模型与增强现实_第14张图片
基 于 上 面 的 综 合 代 码 , 更 改 的 地 方 为 : \color{red}{基于上面的综合代码,更改的地方为:}
Python计算机视觉——照相机模型与增强现实_第15张图片
这个 f x f_x fx f y f_y fy是要自己算的,基于本章的4.2节——一个简单的标定方法,设物体的宽度和高度的测量值分别为130mm和185mm,则 d X = 130 , d Y = 185 dX=130,dY=185 dX=130,dY=185,从照相机到物体的距离为460mm,则 d Z = 460 dZ=460 dZ=460,本章节中我使用的图片像素都是300×400的,因此利用公式
f x = d x   d X   d Z , f y = d y   d Y   d Z f_{x}=\frac{\mathrm{d} x}{\mathrm{~d} X} \mathrm{~d} Z, \quad f_{y}=\frac{\mathrm{d} y}{\mathrm{~d} Y} \mathrm{~d} Z fx= dXdx dZ,fy= dYdy dZ
计算得出 f x = 1061 , f y = 995 f_x=1061,f_y=995 fx=1061,fy=995,因此代码做了调整。

此外,为了能更清晰地看清水壶,对水壶的大小做调整
在这里插入图片描述
也可对矩阵更改
在这里插入图片描述
得到不同角度的壶:D
Python计算机视觉——照相机模型与增强现实_第16张图片Python计算机视觉——照相机模型与增强现实_第17张图片

回顾

1、 代码先是在jupyter下运行的,报出错误:Attempt to call an undefined function glutSolidTeapot, check for bool(glutSolidTeapot) before calling
在这里插入图片描述
改成用sublime就可以了。
2、 在sublime上执行时,会闪现界面,但是是黑屏
Python计算机视觉——照相机模型与增强现实_第18张图片
是因为代码最后的位置放错了
Python计算机视觉——照相机模型与增强现实_第19张图片
此外, 需 要 删 除 D L L S 内 部 文 件 , 只 留 一 个 g l u t 64. v c 14. d l l \color{red}{需要删除DLLS内部文件,只留一个glut64.vc14.dll} DLLSglut64.vc14.dll,如下图所示:
在这里插入图片描述

3、我上网查,包括看书本,发现都建立了一个文件:ar_camera.pkl
在这里插入图片描述
建立的代码为:

import pickle
with open('../data/ar_camera.pkl','wb') as f: 
    pickle.dump(K,f,0) 
    pickle.dump(dot(linalg.inv(K),cam2.P),f)

不过后来我又建个文件夹,重新写AR,没有添加ar_camera.pkl文件,也可以执行出来,以下是本次实验所需要的所有文件:
Python计算机视觉——照相机模型与增强现实_第20张图片
有问题或者需要代码和文件的话,欢迎留言 \ ^ — ^ /

20220522相机标定补充实验

Python计算机视觉——照相机模型与增强现实_第21张图片
代码如下:

# coding=utf-8
import numpy as np
import cv2
import glob
import matplotlib.pyplot as plt
# 终止标准
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

objp = np.zeros((8*11,3), np.float32)
objp[:,:2] = np.mgrid[0:8,0:11].T.reshape(-1, 2)

# 用于存储所有图像中的对象点和图像点的数组
objpoints = [] # 在现实世界空间的3d点
imgpoints = [] # 图像平面中的2d点

#glob是个文件名管理工具
images = glob.glob('*.jpg')
print('...loading')

for fname in images:
    #对每张图片,识别出角点,记录世界物体坐标和图像坐标
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转灰度
    size = gray.shape[::-1]
    #寻找角点,存入corners,ret是找到角点的flag
    ret, corners = cv2.findChessboardCorners(gray, (8, 11), None)
    if ret:
        objpoints.append(objp)
        #执行亚像素级角点检测
        corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        imgpoints.append(corners2)
        # 绘制并显示角点
        img = cv2.drawChessboardCorners(img, (8, 11), corners2, ret)
        # plt.figure()
        # plt.imshow(img)
        # plt.show()
        cv2.imshow('img', img)
        cv2.waitKey(10)
'''
传入所有图片各自角点的三维、二维坐标,相机标定。
每张图片都有自己的旋转和平移矩阵,但是相机内参和畸变系数只有一组。
mtx,相机内参;dist,畸变系数;revcs,旋转矩阵;tvecs,平移矩阵。
'''
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, size, None, None)
print("ret:", 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 ) # 平移向量  # 外参数
print("-----------------------------------------------------")
'''
优化相机内参(camera matrix),这一步可选。
参数1表示保留所有像素点,同时可能引入黑色像素,
设为0表示尽可能裁剪不想要的像素,这是个scale,0-1都可以取。
'''
img2 = cv2.imread("11.jpg")
h,  w = img2.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 0, (w, h))
print ("Optimal mtx:\n", newcameramtx)
#纠正畸变
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)
# 裁剪图像,输出纠正畸变以后的图片
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.jpg', dst)
print ("dst的大小为:", dst.shape)

#计算误差
tot_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)
    tot_error += error
print("total error: ", tot_error/len(objpoints))

实验分析

用手机拍摄10张棋盘格照片,如下:
Python计算机视觉——照相机模型与增强现实_第22张图片
角点提取
Python计算机视觉——照相机模型与增强现实_第23张图片
使用十张棋盘格照片得到的相机内参矩阵为:
Python计算机视觉——照相机模型与增强现实_第24张图片
畸变系数为:
在这里插入图片描述
旋转矩阵:
Python计算机视觉——照相机模型与增强现实_第25张图片
平移矩阵:
Python计算机视觉——照相机模型与增强现实_第26张图片
优化后的相机内参:
Python计算机视觉——照相机模型与增强现实_第27张图片
损失达到0.1965954867401676

再次用相同的十张照片运行代码,相机内参变为:
Python计算机视觉——照相机模型与增强现实_第28张图片

发现即使使用的是相同的照片,得到的相机内参也会发生小的变化,猜测原因是角点检测的过程造成了误差,或者是用于计算的照片数量少,造成了数据的不稳定。因此接下来使用16张照片来计算相机内参。
Python计算机视觉——照相机模型与增强现实_第29张图片
第一次运行结果
相机内参为:
Python计算机视觉——照相机模型与增强现实_第30张图片
优化后为:Python计算机视觉——照相机模型与增强现实_第31张图片
误差为0.09844368322725311
第二次运行结果
相机内参为:
Python计算机视觉——照相机模型与增强现实_第32张图片
优化后为:
Python计算机视觉——照相机模型与增强现实_第33张图片
误差为0.0959813793300506
预计当增加拍摄照片的数量后,误差更小,得到的内参矩阵更准确
当图片被压缩后()再次进行实验,发现得到的内参矩阵发生大幅度变化
Python计算机视觉——照相机模型与增强现实_第34张图片
预测是由于分辨率的大幅降低会影响角点检测的准确程度,也有可能是因为原图是手机拍摄的,而后在电脑上压缩了分辨率,以致对图片造成了不知名的更改

你可能感兴趣的:(Python,计算机视觉,python,opencv,计算机视觉)