针孔照相机模型(有时称为射影照相机模型)是计算机视觉中广泛使用的照相机模 型。对于大多数应用来说,针孔照相机模型简单,并且具有足够的精确度。在针孔照相机模型中,在光线投影到图像平面之前,从唯一一个点经过,也就是 照相机中心 C。如下图所示,图像点 x 是由图像平面与连接三维点 X 和照相机中心 C 的直线相 交而成的。虚线表示该照相机的光学坐标轴。
在针孔照相机中,三维点 X 投影为图像点 x(两个点都是用齐次坐标表示的),如下 所示:
λ x = P X λx=PX λx=PX
3×4 的矩阵 P 为照相机矩阵(或投影矩阵)。注意,在齐次坐标系中,三维 点 X 的坐标由 4 个元素组成,X=[X, Y, Z, W]。这里的标量 λ 是三维点的逆深度(深度的倒数)。如果我们打算在齐次坐标中将最后一个数值归一化为 1,那么就会使用到它。
照相机矩阵可以分解为:
P = K [ R ∣ t ] P=K[R|t] P=K[R∣t]
其中,R 是描述照相机方向的旋转矩阵,t 是描述照相机中心位置的三维平移向量, 内标定矩阵 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⎦⎤
既然K的size为3×3,则R的size为3×n,增广矩阵[R|t]必然是3×(n+1)
图像平面和照相机中心间的距离为焦距 f。当像素数组在传感器上偏斜的时候,需要 用到倾斜参数 s。在大多数情况下,s 可以设置成 0。也就是说:
K = [ f x 0 c x 0 f y c y 0 0 1 ] , 其 中 f x = α f y \boldsymbol{K}=\left[\begin{array}{ccc} f_{x} & 0 & c_{x} \\ 0 & f_{y} & c_{y} \\ 0 & 0 & 1 \end{array}\right],其中f_{x}=αf_{y} K=⎣⎡fx000fy0cxcy1⎦⎤,其中fx=αfy
纵横比例参数 α 是在像素元素非正方形的情况下使用的。通常情况下,我们可以默 认设置 α=1。经过这些假设,标定矩阵变为:
K = [ f 0 c x 0 f c y 0 0 1 ] \boldsymbol{K}=\left[\begin{array}{lll} f & 0 & c_{x} \\ 0 & f & c_{y} \\ 0 & 0 & 1 \end{array}\right] K=⎣⎡f000f0cxcy1⎦⎤
除焦距之外,标定矩阵中剩余的唯一参数为光心(有时称主点)的坐标 c = [ c x , c y ] c=[c_{x} ,c_{y}] c=[cx,cy],也就是光线坐标轴和图像平面的交点。因为光心通常在图像的中心,并且图像的坐 标是从左上角开始计算的,所以光心的坐标常接近于图像宽度和高度的一半。特别强调一点,在这个例子中,唯一未知的变量是焦距 f。
下面的例子展示如何将三维中的点投影到图像视图中.使用的数据集在这下载:Visual Geometry Group - University of Oxford。使用其中的 house.p3d三维模型做实验,首先可以看看这模型长啥样,其实就是几百个三维点组成的矩阵,所以shape为[672,3],具体代码和shape如下:
import camera
import numpy as np
import matplotlib.pyplot as plt
# shape = [3, 672]
points = np.loadtxt('./3d/house.p3d').T
# shape = [4, 672]
points = np.vstack((points,np.ones(points.shape[1])))
# shape = [3, 4]
P = np.hstack((np.eye(3),np.array([[0],[0],[-10]])))
cam = camera.Camera(P)
# shape = [3, 672]
x = cam.project(points)
plt.figure()
plt.plot(x[0],x[1],'k.')
plt.show()
同时,可围 绕一个随机的三维向量,进行增量旋转的投影:
一开始我们知道
P = K [ R ∣ t ] P=K[R|t] P=K[R∣t]
我们可以使用RQ因子分解,在已知P矩阵的同时,恢复内参数 K以及照相机的 位置 t 和姿势 R。代码如下:
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
RQ 因子分解的结果并不是唯一的。在该因子分解中,分解的结果存在符号二义性。 由于我们需要限制旋转矩阵 R 为正定的(否则,旋转坐标轴即可),所以如果需要,我们可以在求解到的结果中加入变换 T来改变符号。
给定照相机投影矩阵 P,我们可以计算出空间上照相机的所在位置。照相机的中心 C,是一个三维点,满足约束 PC=0。对于投影矩阵为 P=K[R|t] 的照相机,有:
K [ R ∣ t ] C = K R C + K t = 0 K[R|t]C=KRC+Kt=0 K[R∣t]C=KRC+Kt=0
所以
C = − R T t C=-R^Tt C=−RTt
C与K无关,只和R、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
标定照相机是指计算出该照相机的内参数。也就是算出矩阵K。标定相机的标准方法是,拍摄多幅平面棋盘模式的图像,然后进行处理计算。
其中焦距的公式为:
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
焦距是在特定图像分辨率下计算出来的,焦距和光心是使用像素来度量的,其尺度和图像分辨率相关,代码如下:
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
如果图像中包含平面状的 标记物体,并且已经对照相机进行了标定,那么我们可以计算出照相机的姿态(旋转和平移)。这里的标记物体可以为对任何平坦的物体。举例如下:
1)提取两幅图像的 SIFT 特征,然后使用 RANSAC 算法稳健地 估计单应性矩阵:
import homography
import camera
import sift
# compute features
sift.process_image('C:/Users/jmuaia007/Pictures/Saved Pictures/python1.jpg', 'python1.sift')
l0, d0 = sift.read_features_from_file('python1.sift')
sift.process_image('C:/Users/jmuaia007/Pictures/Saved Pictures/python2.jpg', 'python2.sift')
l1, d1 = sift.read_features_from_file('python2.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),原点在标记物的某位置上:为了检验单应性矩阵结果的正确性,我们需要将一些简单的三维物体放置在标记物 上,这里我们使用一个立方体。你可以使用下面的函数来产生立方体上的点:
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)有了单应性矩阵和照相机的标定矩阵,我们现在可以得出两个视图间的相对变换:
# 计算照相机标定矩阵
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))
因为场景坐标的尺度是任意的,所以我们使用下面的矩阵来创建第一个照相机:
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=K⎣⎡10001000100−1⎦⎤
有了估计出的单应性矩阵,我们可以将其变换到第二幅图像上,绘制出变换后的图像 P 2 P_{2} P2:
P 2 = H P 1 P_{2}=H P_{1} P2=HP1
作为合理性验证,可以使用新矩阵投影标记平面的一个点,然后检查投影后的点是否与使用第一个照相机和单应性矩阵变换后的点相同。
4)实现结果(完整代码):
import homography
import camera
import sift
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
# compute features
sift.process_image('C:/Users/jmuaia007/Pictures/Saved Pictures/python1.jpg', 'python1.sift')
l0, d0 = sift.read_features_from_file('python1.sift')
sift.process_image('C:/Users/jmuaia007/Pictures/Saved Pictures/python2.jpg', 'python2.sift')
l1, d1 = sift.read_features_from_file('python2.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)
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 np.array(p).T
def my_calibration(sz):
row, col = sz
fx = 2555 * col / 2592
fy = 2586 * row / 1936
K = np.diag([fx, fy, 1])
K[0, 2] = 0.5 * col
K[1, 2] = 0.5 * row
return K
# 计算照相机标定矩阵
K = my_calibration((300, 400))
# 位于边长为0.2 z=0平面的三维点
box = cube_points([0, 0, 0.1], 0.1)
# 投影第一幅图像上底部的正方形
cam1 = camera.Camera(np.hstack((K, np.dot(K, np.array([[0], [0], [-1]])))))
# 底部正方形上的点
box_cam1 = cam1.project(homography.make_homog(box[:, :5]))
# 使用H将点变换到第二幅图像中
box_trans = homography.normalize(np.dot(H,box_cam1))
# 从cam1和H中计算第二个照相机矩阵
cam2 = camera.Camera(np.dot(H, cam1.P))
A = np.dot(np.linalg.inv(K), cam2.P[:, :3])
A = np.array([A[:, 0], A[:, 1], np.cross(A[:, 0], A[:, 1])]).T
cam2.P[:, :3] = np.dot(K, A)
# 使用第二个照相机矩阵投影
box_cam2 = cam2.project(homography.make_homog(box))
# 测试:将点投影在 z=0 上,应该能够得到相同的点
point = np.array([1,1,0,1]).T
print(homography.normalize(np.dot(np.dot(H,cam1.P),point)))
print(cam2.project(point))
# plotting
im0 = np.array(Image.open('C:/Users/jmuaia007/Pictures/Saved Pictures/python1.jpg'))
im1 = np.array(Image.open('C:/Users/jmuaia007/Pictures/Saved Pictures/python2.jpg'))
plt.figure()
plt.imshow(im0)
plt.plot(box_cam1[0, :], box_cam1[1, :], linewidth=3)
plt.title('2D projection of bottom square')
plt.axis('off')
plt.figure()
plt.imshow(im1)
plt.plot(box_trans[0, :], box_trans[1, :], linewidth=3)
plt.title('2D projection transfered with H')
plt.axis('off')
plt.figure()
plt.imshow(im1)
plt.plot(box_cam2[0, :], box_cam2[1, :], linewidth=3)
plt.title('3D points projected in second image')
plt.axis('off')
plt.show()
以上就是计算给定平面场景物体的照相机矩阵。我们结合特征匹配和单 应性矩阵,以及照相机标定技术,实现了在一幅图像上放置一个立方体。 有了照相机的姿态估计技术,我们现在就具备了创建简单增强现实应用的基本技能了。
增强现实(Augmented Reality,AR)是将物体和相应信息放置在图像数据上的一 系列操作的总称。最经典的例子是放置一个三维计算机图形学模型,使其看起来属于该场景;如果在视频中,该模型会随着照相机的运动很自然地移动。
PyGame 是非常流行的游戏开发工具包,它可以非常简单地处理显示窗口、输入设 备、事件,以及其他内容。PyGame 是开源的。
PyOpenGL 是 OpenGL 图形编程的 Python 绑定接口。OpenGL 可以安装在几乎所 有的系统上,并且具有很好的图形性能。OpenGL 具有跨平台性,能够在不同的操作系统之间工作。
安装上述的教程很多,这里便不一一赘述,当你可运行下列代码不报错即安装成功:
from OpenGL.GL import *
from OpenGL.GLU import *
import pygame, pygame.image
from pygame.locals import *
还是不放心安装成功没,那就运行下列代码,能转起来,则安装成功:
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()
我们需要做的第一件事是将图像(打算放置虚拟物体的图像)作为背景添加进来。下面的函数可以载入一幅图像,然后将其转换成一个 OpenGL 纹理,并将该纹理放 置在四边形上:该函数首先使用 PyGame 中的一些函数来载入一幅图像,将其序列化为能够在 PyOpenGL 中使用的原始字符串表示。然后,重置模拟视图,清除颜色和深度缓存。 接下来,绑定这个纹理,使其能够在四边形和指定插值中使用它。四边形是在每一 维分别为 -1 和 1 的点上定义的。注意,纹理图像的坐标是从 0 到 1。最后,清除该纹理,避免其干扰之后准备绘制的图像。
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) # 清除纹理
整体代码如下:
# -*- 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 = 4529 * col / 2592
fy = 2387 * 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 = 1280,960
def setup(): # 设置窗口和pygame环境
pygame.init()
pygame.display.set_mode((width, height), OPENGL | DOUBLEBUF)
pygame.display.set_caption("OpenGL AR demo")
# 计算特征
sift.process_image('C:/Users/jmuaia007/Pictures/Saved Pictures/python1.jpg', 'python1.sift')
l0, d0 = sift.read_features_from_file('python1.sift')
sift.process_image('C:/Users/jmuaia007/Pictures/Saved Pictures/python2.jpg', 'python2.sift')
l1, d1 = sift.read_features_from_file('python2.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((1280,960))
# 位于边长为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('C:/Users/jmuaia007/Pictures/Saved Pictures/python1.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()
其中几个参数需要变动:
最终效果如下所示: