增强现实是将物体和相应信息放置在图像数据上的一系列操作的总称。
PyGame是非常流行的游戏开发工具包,可以简单地处理显示窗口、输入设备、事件,以及其他内容。
PyOpenGL是OpenGL图形编程的Python绑定接口,可以安装在几乎所有的系统上并且具有很好的图形性能。
我们使用OpenGL将一个三维模型放置在一个场景中。为了使用PyGame和PyOpenGL工具包来完成,需要在脚本的开始部分载入下边的命令:
from OpenGL.GL import *
from OpenGL.GLU import *
import pygame, pygame.image
from pygame.locals import *
在这里主要使用OpenGL中的两个部分:GL部分包含所有以"gl"开头的函数;GLU部分是OpenGL的实用函数库,里边包含一些高层的函数,我们主要使用它完成设置照相机投影。
pygame部分用来设置窗口和事件控制;其中pygame.image用来载入图像和创建OpenGL的纹理,pygame.locals用来设置OpenGL的显示区域。
OpenGL使用44的矩阵表示变换,与34的照相机矩阵略有差别。但是,照相机与场景的变换分成了两个矩阵,GL_PROJECTION矩阵和GL_MODLEVIEW矩阵。前者处理图像成像的性质,等价于内标定矩阵K;后者处理物体和照相机之间的三维变换关系,对应于照相机矩阵中的R和t部分。
下面的函数将照相机参数转换为OpenGL中的投影矩阵:
def set_projection_from_camera(K):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
fx = K[0, 0]
fy = K[1, 1]
fovy = 2 * arctan(0.5 * height / fy) * 180 / pi
aspect = (width * fy) / (height * fx)
near = 0.1
far = 100.0
gluPerspective(fovy, aspect, near, far)
glViewport(0, 0, width, height)
在上述代码中,函数glMatrixMode()将工作矩阵设置为GL_PROJECTION,接下来的命令会修改这个矩阵。然后glLoadIdentity()函数将该矩阵设置为单位矩阵,根据图像高度、照相机的焦距以及纵横比,计算出视图中的垂直场。OpenGL的投影同样具有近距离和远距离的裁剪平面来限制场景拍摄的深度范围。
使用GLU的使用函数gluPerspective()设置投影矩阵,将整个图像定义为视图部分。
使用下面的函数实现如何获得移除标定矩阵后的3*4针孔照相机矩阵,并创建一个模拟视图:
def set_modelview_from_camera(Rt):
glMatrixModel(GL_MODELVIEW)
glLoadIdentity()
Rx = array([[1, 0, 0], [0, 0, -1], [0, 1, 0]])
R = Rt[:, :3]
U, S, V = linalg.svd(R)
R = dot(U, V)
R[0, :] = -R[0, :]
t = Rt[:, 3]
M = eye(4)
M[:3, :3] = dot(R, Rx)
M[:3, 3] = t
M = M.T
m = M.flatten()
glLoadMatrixf(m)
上面的函数中,首先切换到GL_MODELVIEW矩阵,然后将该矩阵重置。创建一个90度的旋转矩阵,由于估计照相机矩阵时,可能会有错误或者噪声干扰,所以要确保照相机矩阵的旋转部分确实是一个旋转矩阵。将模拟视图矩阵M设置成为旋转矩阵的乘积,glLoadMatrixf()函数通过输入参数为按列排列的16个数值数组,来设置模拟视图。将M矩阵转置,然后压平并输入glLoadMatrixf()函数。
我们首先需要将图像作为背景添加进来,可以通过创建一个四边形的方式来完成,该四边形为整个视图。下边的函数可以载入一幅图像,然后将其转换成一个OpenGL纹理,并将该纹理放置在四边形上:
def draw_background(imname):
bg_image = pygame.image.load(imname).convert()
bg_data = pygame.image.tostring(bg_image, "RGBX", 1)
glMatrixMode(GL_MODEVIEW)
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)
该函数首先使用PyGame中的一些函数来载入一幅图像,将其序列化为能够在PyOpenGL中使用的原始字符串表示。然后重置模拟视图,清除颜色和深度缓存。接下来绑定这个纹理,使其能够在四边形和指定插值中使用。
使用下边的命令产生一个相对大小为size的茶壶模型:
from OpenGL.GLUT import *
glutSolidTeapot(size)
使用下边的函数设置其颜色和其他特性:
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.0, 0.5, 0.5, 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)
使用下边的脚本命令完成最后的操作:
width, height = 1000, 747
def setup():
"""设置窗口和pygame环境"""
pygame.init()
pygame.display.set_mode((width, height), OPENGL | DOUBLEBUF)
pygame.display.set_caption("OpenGL AR demo")
sift.process_image('book_frontal.jpg', 'im0.sift')
l0, d0 = sift.read_features_from_file('im0.sift')
sift.process_image('book_perspective.jpg', 'im1.sift')
l1, d1 = sift.read_features_from_file('im1.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((747,1000))
cam1 = camera.Camera(hstack((K, dot(K, array([[0], [0], [-1]])))))
box = cube_points([0, 0, 0.1], 0.1)
box_cam1 = cam1.project(homography.make_homog(box[:, :5]))
box_trans = homography.normalize(dot(H, box_cam1))
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(my_homography.make_homog(box))
Rt = dot(linalg.inv(K), cam2.P)
with open('ar_camera.pkl', 'r') as f:
K = pickle.load(f)
Rt = pickle.load(f)
setup()
draw_background('book_perspective.bmp')
set_projection_from_camera(K)
set_modelview_from_camera(Rt)
draw_teapot(0.02)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
pygame.display.flip()
setup()函数会初始化PyGame,将窗口设置为图像大小,绘制图像区域为两倍的OpenGL窗口缓存大小,载入背景使其与窗口相符,然后设定照相机和模拟视图矩阵,最后在正确的位置绘制出茶壶。显示的结果如下图所示: