当前位置:   article > 正文

python计算机视觉编程_计算机视觉编程-照相机模型与增强现实

牛津多视图数据集中的“model housing”数据集

497c5cfe51151bea8030982f4ad27028.png

这部分,我们将尝试对照相机进行建模,并有效地使用这些模型。在之前的几篇文章中,我们已经讲过图像到图像之间的映射和变换。为了处理三维图像和平面图像之间的映射,我们需要在映射中加入部分照相机产生图像过程的投影特性。这篇文章中我们将会讲述如何确定照相机的参数,以及具体应用中,如何增强现实,如何使用图像间的投影变换。

4.1 针孔照相机模型

针孔照相机模型是计算机视觉中广泛使用的照相机模型。对于大多数应用来说,针孔照相机模型很简单,并且有足够的准确度。这个名字来源于一种类似暗箱的照相机。该照相机从一个小孔采集射到暗箱内部的光线。在针孔照相机模型中,在光线投影到图像平面之前,从唯一一个点经过,也就是照相机中心C。下图从照相机中心前画出图像平面图解。事实上,在真实的照相机中,图像平面位于照相机中心之后,但是照相机的模型和下图模型是一样的。

85524f77ac225f49f79313d828e69050.png
该图为针孔照相机模型。图像点x 是由图像平面与连接三维点x和照相机中心c的直线相交而成的。虚线表示该照相机的光学坐标轴。

由图像坐标轴和三维坐标系中的x轴和y轴对齐平行的假设,我们可以得到针孔照相机的头型性质。照相机的光学坐标轴和z轴一致,该投影几何可以简化成相似三角形。在投影之前通过旋转和平移变换,对该坐标系加入三维点,会出现完整的投影转换。

在针孔照相机中,三维点x投影为图像x λx = PX.这里3*4 的矩阵p为照相机矩阵。注意,三维点X的坐标由4个元素组成,X = [X, Y , Z, W]. 这里的标量λ是三维点的逆深度。如果我们打算齐次坐标中将最后一个数值归化为1,那么就会使用到它。

4.1.1照相机矩阵

照相机矩阵分解为:

P = K [R | t],其中R是描述照相机方向的旋转矩阵,t是描述照相机中心位置的三维平移向量,内标定矩阵K描述照相机的投影性质。标定矩阵仅和照相机自身的情况相关,通常情况下可以写成:

4a21a53cc1298bc8bcbe44c2f430b165.png

图像平面和照相机中心间的距离为焦距f。当像素数组在传感器上偏斜的时候,需要用到倾斜参数s。在大多数情况下,s可以设置成0。也就是说:

d82ae8bda53125a03a4e3e5e981de912.png

这里,我们使用了另外的记号fx和fy,两者的关系为fx=afy。

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

d9eef3424fd280af8f80fb164541bf13.png

除了焦距之外,标定矩阵中剩余的唯一参数为光心的坐标c = [cx , cy],也就是光线坐标轴和图像平面的交点。因为光心通常在图像的中心,并且图像的坐标是从左上角开始计算的,所以光心的坐标常接近与图像宽度和高度的一半。

4.1.2 三维点的投影

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

from 

下面的例子展示如何将三维中的点投影到图像视图中。在这个例子中,我们将使用牛津多视图数据集中的“MODEL Housing"数据集,http://www.robots.ox.ac.uk/~vgg/data/data-mview.html.

  1. import camera
  2. #载入点
  3. points = loadtxt('house.p3d').T
  4. points = vstack((points,ones(points.shape[1])))
  5. #设置照相机参数
  6. P = hstack((eye(3),array([0],[0],[-10])))
  7. cam = camera.Camera(P)
  8. x = cam.project(points)
  9. #绘制投影
  10. figure()
  11. plot(x[0],x[1],'k.')
  12. show()

首先,我们使用齐次坐标来表示这些点。然后我们使用一个投影矩阵来创建Camera对象将这些三维点投影到图像平面并执行绘制操作输出结果如下:

8f4837f9c51aa543696fec05d648a7ef.png

为了研究照相机的移动会如何该表投影的效果,你可以使用下面的代码。改代码围绕一个随机的三维向量,进行增强旋转的投影。

  1. # create transformation
  2. r = 0.05*random.rand(3)
  3. rot = camera.rotation_matrix(r)
  4. # rotate camera and project
  5. figure()
  6. for t in range(20):
  7. cam.P = dot(cam.P,rot)
  8. x = cam.project(points)
  9. plot(x[0],x[1],'k.')
  10. show()

在上面的嗲马中,我们使用了rotation_matrix()函数,该函数能够创建围绕一个方向进行三维旋转的旋转矩阵:

  1. def rotation_matrix(a):
  2. """ Creates a 3D rotation matrix for rotation
  3. around the axis of the vector a. """
  4. R = eye(4)
  5. R[:3,:3] = linalg.expm([[0,-a[2],a[1]],[a[2],0,-a[0]],[-a[1],a[0],0]])
  6. return R

4.1.3 照相机矩阵的分解

如果给定如方程所示的照相机矩阵P,我们需要回复内参数K以及照相机的位置t和姿势R。矩阵分块操作成为因子分解。这里我们使用一种矩阵因子分解的方法,成为RQ因子分解。将下面的方法添加到Camera类中:

def 

RQ因子分解的并不是唯一的。在该因子分解中,分解的结果存在符号二义性,由于我们需要限制旋转矩阵R为正定的,所以如果需要,我们可以求解到结果中加入变换T来改变符号。在示例照相机上运行下面的代码,观察照相机矩阵分解的效果:

  1. import camera
  2. K = array([[1000,0,500],[0,1000,300],[0,0,1]])
  3. tmp = camera.rotation_matrix([0,0,1])[:3,:3]
  4. Rt = hstack((tmp,array([[50],[40],[30]])))
  5. cam = camera.Camera(dot(K,Rt))
  6. print K,Rt
  7. print cam.factor()

4.1.4 计算照相机中心

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

95cf659c866f80a6433bd8fa945fb1f6.png

照相机的中心可以有下述式子来计算:C=−RT t.。注意,如预期一样,照相机的中心和内定矩阵K无关。下面的代码可以按照上面公式计算照相机的中心。将其添加到Camera类,该方法会返回照相机的中心:

  1. def center(self):
  2. """ Compute and return the camera center. """
  3. if self.c is not None:
  4. return self.c
  5. else:
  6. # compute c by factoring
  7. self.factor()
  8. self.c = -dot(self.R.T,self.t)
  9. return self.c

上面的一些方法构成了Camera类的基本操作。现在让我们一起探讨如何使用针孔照相机模型。

4.2 照相机标定

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

d82ae8bda53125a03a4e3e5e981de912.png

4.2.1 一个简单的标定方法

这里我们将要介绍一种简单的照相机标定方法。大多数参数可以使用基本的假设来设定,比较难处理的是获得正确的焦距,对于这种标定方法,你需要准备一个平面矩形的标定物体,用于测量的卷尺和直尺以及一个平面。下面是具体操作步骤:

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

实验设置情况如下图所示。现在使用下面的相似三角形关系可以获得焦距:

5f92b7e384802946876dbfe4d7638688.png

54560c0dbe83dc4f887a4d6567772463.png

对于上图所示的特定设置,物体宽度和高度的测量值分别为130mm和185mm,则,dX=130,dY=185.从照相机到物体的距离为460mm,则dZ=460.你可以使用任意的测量单位,只有测量值的比例才影响最终焦距的计算。你可以使用ginput()函数来获得图像中的4个点,图像中物体的宽度和高度的分别是722和1040像素。将这些值代入上面的关系表达式可以获得焦距的大小:fx= 2555, fy= 2586. 值得注意的是,我们想在获取的焦距实在特定图像分辨率下计算出来的。在这个例子中,图像大小为2592*1936像素。记住,焦距和光心是使用像素来度量的,其尺度和图像分辨率有关。如果你使用其他的图像分辨率来拍摄(例如一个略缩图像)那么这些值都会改变。将照相机的这些测量数值写入一个如下所示的辅助函数中会方便一些:

def 

该函数的输入参数为表示图像大小的元组,返回参数为标定矩阵。这里我们假设光心为图像的中心。

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

我们使用一个例子来演示如何进行姿态估计。我们使用下面的代码来提取两幅图中的SIFT特征,然后使用RANSAC算法稳健的估计单应性矩阵:

  1. import homography
  2. import camera
  3. import sift
  4. # compute features
  5. sift.process_image('book_frontal.JPG','im0.sift')
  6. l0,d0 = sift.read_features_from_file('im0.sift')
  7. sift.process_image('book_perspective.JPG','im1.sift')
  8. l1,d1 = sift.read_features_from_file('im1.sift')
  9. # match features and estimate homography
  10. matches = sift.match_twosided(d0,d1)
  11. ndx = matches.nonzero()[0]
  12. fp = homography.make_homog(l0[ndx,:2].T)
  13. ndx2 = [int(matches[i]) for i in ndx]
  14. tp = homography.make_homog(l1[ndx2,:2].T)
  15. model = homography.RansacModel()
  16. H = homography.H_from_ransac(fp,tp,model)

d5f5a6d80022f7f68718337953a83c0b.png

现在我们得到单应性矩阵。该单应性矩阵将一幅图像中标记物上的点映射到另一幅图像中的对应点。下面我们定义相应的三维坐标系,是标记物在X-Y平面上,原点在标记物的某位置上。

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

  1. def cube_points(c,wid):
  2. """ Creates a list of points for plotting
  3. a cube with plot. (the first 5 points are
  4. the bottom square, some sides repeated). """
  5. p = []
  6. # bottom
  7. p.append([c[0]-wid,c[1]-wid,c[2]-wid])
  8. p.append([c[0]-wid,c[1]+wid,c[2]-wid])
  9. p.append([c[0]+wid,c[1]+wid,c[2]-wid])
  10. p.append([c[0]+wid,c[1]-wid,c[2]-wid])
  11. p.append([c[0]-wid,c[1]-wid,c[2]-wid]) # same as first to close plot
  12. # top
  13. p.append([c[0]-wid,c[1]-wid,c[2]+wid])
  14. p.append([c[0]-wid,c[1]+wid,c[2]+wid])
  15. p.append([c[0]+wid,c[1]+wid,c[2]+wid])
  16. p.append([c[0]+wid,c[1]-wid,c[2]+wid])
  17. p.append([c[0]-wid,c[1]-wid,c[2]+wid]) # same as first to close plot
  18. # vertical sides
  19. p.append([c[0]-wid,c[1]-wid,c[2]+wid])
  20. p.append([c[0]-wid,c[1]+wid,c[2]+wid])
  21. p.append([c[0]-wid,c[1]+wid,c[2]-wid])
  22. p.append([c[0]+wid,c[1]+wid,c[2]-wid])
  23. p.append([c[0]+wid,c[1]+wid,c[2]+wid])
  24. p.append([c[0]+wid,c[1]-wid,c[2]+wid])
  25. p.append([c[0]+wid,c[1]-wid,c[2]-wid])
  26. return array(p).T

在上面函数中,一些数据点会重复出现,plot()函数会绘制出漂亮的立方体。

有了单应性矩阵和照相机的标定矩阵,我们现在可以得出两个视图间的相对变换:

  1. # camera calibration
  2. K = my_calibration((747,1000))
  3. # 3D points at plane z=0 with sides of length 0.2
  4. box = cube_points([0,0,0.1],0.1)
  5. # project bottom square in first image
  6. cam1 = camera.Camera( hstack((K,dot(K,array([[0],[0],[-1]])) )) )
  7. # first points are the bottom square
  8. box_cam1 = cam1.project(homography.make_homog(box[:,:5]))
  9. # use H to transfer points to the second image
  10. box_trans = homography.normalize(dot(H,box_cam1))
  11. # compute second camera matrix from cam1 and H
  12. cam2 = camera.Camera(dot(H,cam1.P))
  13. A = dot(linalg.inv(K),cam2.P[:,:3])
  14. A = array([A[:,0],A[:,1],cross(A[:,0],A[:,1])]).T
  15. cam2.P[:,:3] = dot(K,A)
  16. # project with the second camera
  17. box_cam2 = cam2.project(homography.make_homog(box))
  18. # test: projecting point on z=0 should give the same
  19. point = array([1,1,0,1]).T
  20. print homography.normalize(dot(dot(H,cam1.P),point))
  21. print cam2.project(point)

这里我们使用图像分辨率为747 x 1000,第一个生产力的标定矩阵就是在该图像分辨率大小下的标定矩阵。下面我们在原点附近创建立方体上的点。cube_points()函数产生的前五个点对应于立方体底部的点,在这个例子中对应于位于标记物 上Z=0平面内的点。第一幅图像是书本的主视图,我们将其视作这个例子中的模板图像。因为场景坐标的尺度是任意的,所以我们使用下面的矩阵来创建第一个照相机:

a1f990a71bc22ae0ca7bec2a447d7c0a.png

其中,图像的坐标轴和照相机是对齐的,并且放置在标记物的正上方。将前5个三维点投影到图像上。有了估计出的单应性矩阵,我们可以将其变换到第二幅图象中,绘制出变换后的图像,并且在同样的标志物位置绘制出这些点。现在结合P1和H构建第二幅图像的照相机位置:

P2 = HP1,该矩阵将标记平面Z=0上的点变换到正确的位置。也就是说P2矩阵的前两列和第四列是正确的。由于我们知道前3x3矩阵块应该为KR,并且R是旋转矩阵,所以我们可以将P2乘以标定矩阵的逆,然后将第三列替换为前两列的交叉乘积,以此来恢复第三列。

作为合理性检验,我们可以使用新矩阵投影标记平面的一个点,然后检查投影后的点是否与使用第一个照相机和单应性矩阵变换后的点相同。你会发现,控制台上得到了相同的输出结果。你可以用如下代码来可视化这些投影后的点:

  1. im0 = array(Image.open('book_frontal.JPG'))
  2. im1 = array(Image.open('book_perspective.JPG'))
  3. # 2D projection of bottom square
  4. figure()
  5. imshow(im0)
  6. plot(box_cam1[0,:],box_cam1[1,:],linewidth=3)
  7. # 2D projection transferred with H
  8. figure()
  9. imshow(im1)
  10. plot(box_trans[0,:],box_trans[1,:],linewidth=3)
  11. # 3D cube
  12. figure()
  13. imshow(im1)
  14. plot(box_cam2[0,:],box_cam2[1,:],linewidth=3)
  15. show()

该代码绘制出如上图所示的三幅图。为了能够在后面的例子中再次使用这些计算的结果,我们可以使用pickle将这些照相机矩阵保存起来:

  1. import pickle
  2. with open('ar_camera.pkl','w') as f:
  3. pickle.dump(K,f)
  4. pickle.dump(dot(linalg.inv(K),cam2.P),f)

现在我们已经学会了计算给定平面场景物体的照相机矩阵。我们结合特征匹配和单应性矩阵,以及照相机标定技术,简单演示了如何在一幅图像上面放置一个立方体。有了照相机的姿态估计技术,我们现在就具备了创建简单增强现实应用技术的基本技能了。

4.4 增强现实

增强现实(Augumented Reality,AR)是将物体和相应信息放置在图像数据上的一系列操作的总称。最经典的例子是放置一个三维计算机图像学模型,使其看起来属于这个场景;如果在视频中,该模型会随着照相机的运动很自然地移动。在之前的文章中,给定一幅带有标记平面的图像,我们能够计算出照相机的位置和姿态,使用这些信息来放置计算机图形学模型,能够正确表示它们。接下来,我将介绍如何创建一个简单的增强现实例子。其中,我们会用到两个工具包:Pygame 和 PyOpenGL。

4.4.1 PyGame和PyOpenGL

PyGame 是非常流行的游戏开发工具包,它可以非常简单地处理显示窗口,输入设备,事件,以及其他内容。PyGame是开源的,可以从http://www.pygame.org/下载。事实上他是一个Python绑定的SDL游戏引擎。PyOpenGL是OpenGL图形编程的Python绑定接口。OpenGL可以安装在你胡所有的系统上,并且具有很好的图形性能。OpenGL具有跨平台性,能够在不同的操作系统之间工作,关于OpenGL的更多信息,参见The Industry Standard for High Performance Graphics

在开始界面有针对初学者的很多资源。PyOpenGL是开源,并且已于安装。我们使用OpenGL将一个三维模型放置在一个场景中。为了使用Pygame和PyOpenGL工具包来完成该应用,需要在脚本的开始部分载入下面的命令:

  1. from OpenGL.GL import *
  2. from OpenGL.GLU import *
  3. import pygame, pygame.image
  4. from pygame.locals import *

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

需要对一个OpenGL场景进行两个部分的设置:投影和视图矩阵的建模。让我们一起来学习如何有针孔照相机创建这些矩阵。

4.4.2 从照相机矩阵到OpenGL格式

OpenGL使用4x4 的矩阵来表示变换(包括三维变换和投影)。这和我们的3x4照相机矩阵略有差别。但是照相机与场景的变换分成了两个矩阵GLprojection 和GLMODELVIEW矩阵。GL_PROJECTION 处理图像成像的性质,等价于我们的内标定矩阵K。GLMODELVIEW矩阵处理物体和照相机之间的三维变换关系,对应于我们照相机矩阵中的R和t部分。一个不同之处是,假设照相机坐标系的中心,GL_ModelView 矩阵实际上包含了将物体放置在照相机前面的变换。OpenGL有很多特性,我们会在下面例子中一一讲解。假设我们已经获得了标定好的照相机,即已知标定矩阵K,下面的函数可以将照相机参数转化为OpenGL中的投影矩阵:

  1. def set_projection_from_camera(K):
  2. """ Set view from a camera calibration matrix. """
  3. glMatrixMode(GL_PROJECTION)
  4. glLoadIdentity()
  5. fx = K[0,0]
  6. fy = K[1,1]
  7. fovy = 2*arctan(0.5*height/fy)*180/pi
  8. aspect = (width*fy)/(height*fx)
  9. # define the near and far clipping planes
  10. near = 0.1
  11. far = 100.0
  12. # set perspective
  13. gluPerspective(fovy,aspect,near,far)
  14. glViewport(0,0,width,height)

我们假设标定矩阵文章之前提到的那样简单,光心为图像的中心。第一个函数glMatrixMode()将工作矩阵设置为GL_PROJECTION,接下来的命令会修改这个矩阵。然后,glLoadIdentity()函数将该矩阵设置为单位矩阵,这是充值矩阵的一般操作。然后我们根据图像的高度,照相机的焦距以及纵横比,计算出视图中的垂直场。OpenGL的投影同样具有近距离和远距离的剪裁平面来限制场景拍摄的深度范围。我们设置近深度为一个小的数值,使得照相机能够包含最近的物体,而远深度设置为一个很大的值。我们使用GLU的实用函数gluPerspective()来设置投影矩阵,将整个图像定义为视图部分。和下面的模拟视图函数相似,你可以使用glLoadMatrixf()函数的一个选项来定义一个完全的投影矩阵。当简单版本的标定矩阵不够好时,可以使用完全投影矩阵。

模拟视图矩阵表示相对的旋转和平移,该变换将该物体放置在照相机前。模拟视图矩阵是个典型的4x4矩阵,如下所示

fc4c5922ec2792478f3180f5a78fdc86.png

其中,R是旋转矩阵,列向量表示3个坐标轴的方向,t是平移向量。当创建模拟视图矩阵时,旋转矩阵需要包括所有的旋转,可以将单个旋转分量相乘来获得旋转矩阵。下面的函数实现如何获得移除标定矩阵后的3x4针孔照相机矩阵,并创建一个模拟视图:

  1. def set_modelview_from_camera(Rt):
  2. """ Set the model view matrix from camera pose. """
  3. glMatrixMode(GL_MODELVIEW)
  4. glLoadIdentity()
  5. # rotate teapot 90 deg around x-axis so that z-axis is up
  6. Rx = array([[1,0,0],[0,0,-1],[0,1,0]])
  7. # set rotation to best approximation
  8. R = Rt[:,:3]
  9. U,S,V = linalg.svd(R)
  10. R = dot(U,V)
  11. R[0,:] = -R[0,:] # change sign of x-axis
  12. # set translation
  13. t = Rt[:,3]
  14. # setup 4*4 model view matrix
  15. M = eye(4)
  16. M[:3,:3] = dot(R,Rx)
  17. M[:3,3] = t
  18. # transpose and flatten to get column order
  19. M = M.T
  20. m = M.flatten()
  21. # replace model view with the new matrix
  22. glLoadMatrixf(m)

在上面函数中,我们首先切换到GL_MODELVIEW矩阵,然后重置该矩阵。然后,由于需要旋转该物体,所以我们创建一个90度的旋转矩阵。接下来,由于估计照相机矩阵式,可能会有错误或者噪声和干扰,所以我们确保照相机矩阵的旋转部分确实是个旋转矩阵。该操作使用SVD分解法,旋转矩阵的最佳逼近可以通过R = UVT来获得。由于OpenGL中的坐标系和上面用到的相同,所以我们将x轴翻转。然后,我们将模拟视图矩阵M设置为旋转矩阵的乘积glLoadMatrix()函数通过输入参数为按排列的16个数值数组,来设置模拟视图。将M矩阵转置,然后压平并输入glLoadMatrixf()函数。

4.4.3在图像中放置虚拟物体

我们需要做的第一件事是将图像作为背景添加进来。在OpenGL中,该操作可以通过创建一个四边形的方式来完成,该四边形为整个视图。完成该操作最简单的方式是绘制出四边形,同时将投影和模拟视图矩阵重置,使得每一维的坐标范围在-1到1之间。下面的函数可以载入一个图像,然后将其转换成一个OpenGL纹理,并将纹理放置在四边形上:

  1. def draw_background(imname):
  2. """ Draw background image using a quad. """
  3. # load background image (should be .bmp) to OpenGL texture
  4. bg_image = pygame.image.load(imname).convert()
  5. bg_data = pygame.image.tostring(bg_image,"RGBX",1)
  6. glMatrixMode(GL_MODELVIEW)
  7. glLoadIdentity()
  8. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
  9. # bind the texture
  10. glEnable(GL_TEXTURE_2D)
  11. glBindTexture(GL_TEXTURE_2D,glGenTextures(1))
  12. glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,bg_data)
  13. glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST)
  14. glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST)
  15. # create quad to fill the whole window
  16. glBegin(GL_QUADS)
  17. glTexCoord2f(0.0,0.0); glVertex3f(-1.0,-1.0,-1.0)
  18. glTexCoord2f(1.0,0.0); glVertex3f( 1.0,-1.0,-1.0)
  19. glTexCoord2f(1.0,1.0); glVertex3f( 1.0, 1.0,-1.0)
  20. glTexCoord2f(0.0,1.0); glVertex3f(-1.0, 1.0,-1.0)
  21. glEnd()
  22. # clear the texture
  23. glDeleteTextures(1)

该函数首先使用PyGame中的一些函数来载入一副图像,将其序列化为能够在PyOpenGL中使用的原始字符串表示。然后重置模拟视图,清除颜色和深度缓存。接下来绑定这个纹理,使其能够在四边形和指定插值中使用它。四边形是在每一维分别为-1和1的点上定义的。注意,纹理图像的坐标是0到1。最后,清除该纹理,避免其干扰之后准备绘制的图像。

现在已经准备好将物体放置在场景中。我们将使用”hello world“的计算机图形学的例子,Utah茶壶http://en.wikipedia.org/wiki/Utah_teapot。这个茶壶有丰富的历史,在GLUT用作一个标准形状:

  1. from OpenGL.GLUT import *
  2. glutSolidTeapot(size)

该命令产生一个相对大小为size的茶壶模型。下面的函数将会设置颜色和其他特性,产生一个红色的漂亮茶壶

  1. def draw_teapot(size):
  2. """ Draw a red teapot at the origin. """
  3. glEnable(GL_LIGHTING)
  4. glEnable(GL_LIGHT0)
  5. glEnable(GL_DEPTH_TEST)
  6. glClear(GL_DEPTH_BUFFER_BIT)
  7. 4.4 Augmented Reality | 93
  8. # draw red teapot
  9. glMaterialfv(GL_FRONT,GL_AMBIENT,[0,0,0,0])
  10. glMaterialfv(GL_FRONT,GL_DIFFUSE,[0.5,0.0,0.0,0.0])
  11. glMaterialfv(GL_FRONT,GL_SPECULAR,[0.7,0.6,0.6,0.0])
  12. glMaterialf(GL_FRONT,GL_SHININESS,0.25*128.0)
  13. glutSolidTeapot(size)

上面的函数中,前两行激活了灯光效果和一盏灯。灯被设计为GL——LIGHT0和GL_LIGHT1等。在本例中,我们只是用一盏灯。glEnable()函数用来激活OpenGL的一些特性。这些特性是使用大写常量来定义的。关闭特性可以使用glDisable()函数来表示。接下来深度测试被激活,使物体按照其深度表示出来,然后清理深度缓存。接下来,指定物体的物质特性,例如漫反射和镜面反射颜色。在最后一行代码中将指定的物质加入到Utah茶壶上

4.4.4 综合集成

下面的完整脚本可以生成之后的图像:

  1. from OpenGL.GL import *
  2. from OpenGL.GLU import *
  3. from OpenGL.GLUT import *
  4. import pygame, pygame.image
  5. from pygame.locals import *
  6. import pickle
  7. width,height = 1000,747
  8. def setup():
  9. """ Setup window and pygame environment. """
  10. pygame.init()
  11. pygame.display.set_mode((width,height),OPENGL | DOUBLEBUF)
  12. pygame.display.set_caption('OpenGL AR demo')
  13. # load camera data
  14. with open('ar_camera.pkl','r') as f:
  15. K = pickle.load(f)
  16. Rt = pickle.load(f)
  17. setup()
  18. draw_background('book_perspective.bmp')
  19. set_projection_from_camera(K)
  20. set_modelview_from_camera(Rt)
  21. draw_teapot(0.02)
  22. while True:
  23. event = pygame.event.poll()
  24. if event.type in (QUIT,KEYDOWN):
  25. break
  26. pygame.display.flip()

8c9b0cee48c89879c7187b6eb0d19351.png

首先,该脚本使用pickle子载入照相机标定矩阵,以及照相机矩阵中的旋转和平移部分。假设这些信息已经按照前几节的描述保存。setup()函数会初始化PyGame,将窗口设置为图像的大小,绘制图像区域为两倍的OpenGL窗口缓存大小。接下来载入背景图像,使其与窗口相符。然后设定照相机和模拟视图矩阵。最后,在正确的位置上绘制出茶壶。

在PyGame中,使用带有对所有变化进行定期询问的无限循环来处理事件。这些事件可以为键盘,鼠标,或者其他。在这个例子中,我们检查应用是否退出,或者是否按键下,如果是则推出这个循环,pygame.display.flip()命令将会在屏幕上绘制出物体。

程序的输出结果如上图所示。可以看到,物体的朝向使正确的。为了验证放置的正确性,你可以通过给size变量传递一个较小的值,将茶壶变小。茶壶应该放置在靠近立方体坐标为[0,0,0]的角上。

4.4.5 载入模型

接下来我们讲述最后一个细节:载入并显示三维模型。你可以在OBJFileLoader - pygame wiki 了解如何在Pygame中载入保存在.obj格式文件中的模型。下面使用一个基本的例子来说明其使用方法。我们将使用一个免费的玩具飞机模型,模型来自http://www.oyonale.com/modeles.php.2 。下载其.obj文件,然后保存为toyplane.obj。假设以及下载模型文件并命名为objloader.py,将下面的函数添加到茶壶的例子文件中:

  1. def load_and_draw_model(filename):
  2. """ Loads a model from an .obj file using objloader.py.
  3. Assumes there is a .mtl material file with the same name. """
  4. glEnable(GL_LIGHTING)
  5. glEnable(GL_LIGHT0)
  6. glEnable(GL_DEPTH_TEST)
  7. glClear(GL_DEPTH_BUFFER_BIT)
  8. # set model color
  9. glMaterialfv(GL_FRONT,GL_AMBIENT,[0,0,0,0])
  10. glMaterialfv(GL_FRONT,GL_DIFFUSE,[0.5,0.75,1.0,0.0])
  11. glMaterialf(GL_FRONT,GL_SHININESS,0.25*128.0)
  12. # load from a file
  13. import objloader
  14. obj = objloader.OBJ(filename,swapyz=True)
  15. glCallList(obj.gl_list)

和前面一样,我们首先设置模型的照明条件和颜色属性。接下来我们将模型载入一个obj对象中,然后在文件中执行OpenGL的调用。

你可以在相应的.mtl文件中设置纹理和材料的属性。实际上objloader模块需要一个含有材料设置的文件。我们采用仅创建一个小材料文件的实用方法,而不修改脚本。在这个例子中,我们仅指定了颜色信息。

使用下面的命令来创建toyplane.mtl文件中:

newmtl lightblue

Kd 0.5 0.75 1.0

illum 1

上面的命令将物体的漫反射颜色设置为灰蓝色。现在确保将.obj文件中的”usemtl“标签替换为usemtl lightblue

将上面例子中的draw_teapot()命令替换为

load_and_draw_model('toyplane.obj') 程序就会生成下图。

5e374c937e9e29149f02a133d1ed40ac.png
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/102942
推荐阅读
相关标签
  

闽ICP备14008679号