第四章:照相机模子与加强实际

[复制链接]
发表于 2026-1-30 16:52:57 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
目次
1 针孔照相机模子
1.1 照相机矩阵
1.2 三维点的投影
1.3 照相机矩阵的分解
1.4 盘算照相机中心
2 照相机标定
3 以平面和标记物举行姿态估计
4 加强实际
4.1 PyGame和PyOpenGL
4.2 从照相机矩阵到OpenGL格式
4.3 在图像中放置物体


1 针孔照相机模子

针孔照相机模子是盘算机视觉中广泛利用的照相机模子。该相机从一个小孔收罗射到暗箱内部的光线。在真空照相机模子中,在光线投射到图像平面之前,从唯逐一个点颠末。

在图中示出了与针孔相机映射有关的多少形状。该图包罗以下根本对象:


  • 一个原点在O点的三维正交坐标系,这也是相机光圈的位置。坐标系的三个轴被称为X1、X2、X3。X3轴指向相机的观察方向,称为光轴、主轴或主射线。由轴X1和X2张成的平面是相机的正面,或称主平面。
  • 一个像平面,此中三维天下通过相机光圈举行投影。像平面平行于轴X1和X2,而且在X3轴的负方向上与原点O间隔为f,此中f是针孔相机的焦距。针孔相机的实际实现意味着像平面的位置应使与X3轴在坐标-f处相交,此中f>0。
  • 在光轴和像平面相交处的点R,该点称为主点或图像中心。
  • 真实天下中的某一点P,有相对于轴X1、X2、X3的坐标(x1,x2,x3)。点P在相机中的投影线。也就是图中穿过点P和点O的绿线。点P在像平面上的投影,记作点Q。这个点由投影线(绿色)与像平面的交点体现。在任何实际环境下,我们可以假设x3>0,这意味着交点是界说良好的。
  • 在像平面上也有一个二维坐标系:原点在R,轴Y1和Y2分别平行于X1和X2。点Q相对于这个坐标系的坐标是(y1,y2)。
1.1 照相机矩阵

照相机矩阵可以分解为:

此中,R是形貌照相机方向的旋转矩阵,t是形貌照相机中心位置的三维平移向量,内标定矩阵K形貌照相机的投影性子。标定矩阵仅和照相机自身的环境相干,通常环境下可以写成:

图像平面和照相机中心的间隔称为焦距f。当像素数组在传感器上偏斜的时间,须要用到倾斜参数s。在大多数环境下s可以设置为0。
纵横比例α是在像素元素非正方形的环境下利用的,通常环境下可以默认设置α=1。
除焦距之外,标定矩阵中剩余的唯一参数为光心,即主点,也就是光线坐标轴和图像平面的交点。由于光心通常在图像的中心,而且图像的坐标是从左上角开始盘算的,以是光心的坐标常靠近于图像高度和宽度的一半。
1.2 三维点的投影

创建一个照相机类,用来处置惩罚对照相机和投影建模所须要的全部操纵
  1. from scipy.import linalg
  2. class Camera(object):
  3.         def _init_(self, p):
  4.                 self.p = p
  5.                 self.K = None
  6.                 self.R = None
  7.                 self.t = None
  8.                 self.c = None
  9.                
  10.         def project(self, x):
  11.                 x = dot(self.P, X)
  12.                
  13.                 for i in range(3):
  14.                         x[i] /= x[2]
  15.                
  16.                 return x
复制代码
下边的例子将三维中的点投影到图像视图中:
  1. from pylab import *
  2. import camera
  3. points=loadtxt('house.p3d').T
  4. points=vstack((points,ones(points.shape[1])))
  5. P=hstack((eye(3),array([[0],[0],[-10]])))
  6. cam = camera.Camera(P)
  7. x=cam.project(points)
  8. figure()
  9. subplot(121)
  10. axis('off')
  11. plot(x[0],x[1],'k.')
  12. subplot(122)
  13. axis('off')
  14. r=0.05*random.rand(3)
  15. rot=camera.rotation_matrix(r)
  16. for t in range(20):
  17.     cam.P=dot(cam.P,rot)
  18.     x=cam.project(points)
  19.     plot(x[0],x[1],'k.')
  20. show()
复制代码
起首利用齐次坐标体现这些点,然后利用一个投影矩阵来创建Camera对象将这些三维点投影到图像平面并实行绘制操纵,上边的代码围绕这个随机的三维变量举行增量旋转的投影,利用rotation_matrix()函数创建了一个举行三维旋转的旋转矩阵,可以运行代码举行随机旋转并观察结果。得到的结果如下图所示:


1.3 照相机矩阵的分解

假如给定一个照相机矩阵P,须要规复内参数K以及照相机的位置t和姿势R,这种矩阵分块操纵称为因子分解。
下面的代码利用RQ因子分解的方法。但这种分解方法的结果并不是唯一的,分解的结果存在二义性。由于须要限定旋转矩阵R为正定的,以是假如须要可以在求解的结果中参加变更T来改变符号。
  1. def factor(self):
  2.         K, R = li.rq(self.P[:, :3])
  3.        
  4.         T = diag(sign(diag(K)))
  5.        
  6.         if li.det(T) < 0:
  7.                 T[1, 1] *= -1
  8.                
  9.         self.K = dot(K, T)
  10.         self.R = dot(T, R)
  11.         self.t = dot(li.inv(self.K), self.P[:, 3])
  12.        
  13.         return self.K, self.R, self.t
复制代码
运行下面的代码,观察矩阵分解的结果:
  1. K=array([[1000,0,500],[0,1000,300],[0,0,1]])
  2. tmp=camera.rotation_matrix([0,0,1])[:3,:3]
  3. Rt=hstack((tmp,array([[50],[40],[30]])))
  4. cam=camera.Camera(dot(K,Rt))
  5. print(K,Rt)
  6. print(cam.factor())
复制代码
 运行结果:

1.4 盘算照相机中心

给定照相机投影矩阵P,可以盘算出空间上照相机的所在位置。照相机中心为C,是一个三维点,满足束缚条件PC=0。对于投影矩阵为P=K[R|t]照相机可以通过下述式子盘算C:

下面的代码可以按照上述公式返回机选照相机的中心:
  1. def center(self):
  2.         if self.c is not None:
  3.                 return self.c
  4.         else:
  5.                 self.factor()
  6.                 self.c = -dot(self.R.T, self.t)
  7.                
  8.                 return self.c
复制代码
2 照相机标定

标定照相机是指盘算出该照相机的内参数。
具体步调如下:


  • 丈量你选定矩形标定物体的变长dX和dY;
  • 将照相机和标定物体放置在平面上,使得照相机的反面和标定物体平行,同时物体位于照相机图像视图的中心;
  • 丈量标定物体到照相机的间隔dZ;
  • 拍摄一幅图像来查验该设置是否准确,即标定物体的边要和图像的行和列对齐;
  • 利用像素数来丈量标定物体图像的宽度和高度。
丈量到的数据写入一个辅助函数:
  1. def my_calibration(sz):
  2.     row,col= sz
  3.     fx=2555*col/2592
  4.     fy=2586*row/1936
  5.     K=diag([fx,fy,1])
  6.     K[0,2]=0.5*col
  7.     K[1,2]=0.5*row
  8.     return K
复制代码
该函数的输入参数为体现图像巨细的元组,返回参数为标定矩阵。
3 以平面和标记物举行姿态估计

利用下面的代码提取两幅图像的SIFT特性,然后利用RANSAC算法妥当地估计单应性矩阵:
  1. sift1.process_image('qinghai1.jpg', 'im0.sift')
  2. l0, d0 = sift.read_features_from_file('im0.sift')
  3. sift1.process_image('qinghai2.jpg', 'im1.sift')
  4. l1, d1 = sift.read_features_from_file('im1.sift')
  5. matches = sift.match_twosided(d0, d1)
  6. ndx = matches.nonzero()[0]
  7. fp = homography.make_homog(l0[ndx, :2].T)
  8. ndx2 = [int(matches[i]) for i in ndx]
  9. tp = homography.make_homog(l1[ndx, :2].T)
  10. model = homography.RansacModel()
  11. H = homography.H_from_ransac(fp, tp, model)
复制代码
该单应性矩阵将一幅图像中标记物上的点映射到另一幅图像中的对应点。界说相应的三维坐标系,使标记物在X-Y平面上,原点在标记物的某位置上。
为了查验单应性矩阵结果的准确性,须要将一些简朴的三维物体放置在标记物上,下边的函数用来产生立方体上的点:
  1. def cube_points(c, wid):
  2.         p = []
  3.         p.append([c[0] - wid, c[1] - wid, c[2] - wid])
  4.         p.append([c[0] - wid, c[1] + wid, c[2] - wid])
  5.         p.append([c[0] + wid, c[1] + wid, c[2] - wid])
  6.         p.append([c[0] + wid, c[1] - wid, c[2] - wid])
  7.         p.append([c[0] - wid, c[1] - wid, c[2] - wid])
  8.        
  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])
  12.         p.append([c[0] + wid, c[1] - wid, c[2] + wid])
  13.         p.append([c[0] - wid, c[1] - wid, c[2] + wid])
  14.        
  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])
  18.         p.append([c[0] + wid, c[1] + wid, c[2] - wid])
  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.        
  23.         return array(p).T
复制代码
接下来可以实现两个视图间的相对变更:
  1. K = my_calibration((747, 1000))
  2. box = cube_points([0, 0, 0.1], 0.1)
  3. cam1 = camera.Camera(hstack((K, dot(K, array([[0], [0], [-1]])))))
  4. box_cam1 = cam1.project(homography.make_homog(box[:, :5]))
  5. box_trans = homography.normalize(dot(H,box_cam1))
  6. cam2 = camera.Camera(dot(H, cam1.P))
  7. A = dot(linalg.inv(K), cam2.P[:, :3])
  8. A = array([A[:, 0], A[:, 1], cross(A[:, 0], A[:, 1])]).T
  9. cam2.P[:, :3] = dot(K, A)
  10. box_cam2 = cam2.project(homography.make_homog(box))
复制代码
第一个产生的标定矩阵就是在该图像分辨率巨细下的标定矩阵,cube_points()函数产生的前五个点对应与立方体底部的点。
利用下边的代码来可视化这些投影后的点:
  1. im0 = array(Image.open('1.jpg'))
  2. im1 = array(Image.open('2.jpg'))
  3. figure()
  4. imshow(im0)
  5. plot(box_cam1[0, :], box_cam1[1, :], linewidth=3)
  6. title('2D projection of bottom square')
  7. axis('off')
  8. figure()
  9. imshow(im1)
  10. plot(box_trans[0, :], box_trans[1, :], linewidth=3)
  11. title('2D projection transfered with H')
  12. axis('off')
  13. figure()
  14. imshow(im1)
  15. plot(box_cam2[0, :], box_cam2[1, :], linewidth=3)
  16. title('3D points projected in second image')
  17. axis('off')
  18. 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 加强实际

加强实际(Augmented Reality,简称 AR),是一种实时土地算拍照机影像的位置及角度并加上相应图像的技能。它把原来在实际天下的肯定时间空间范围内很难体验到的实体信息(视觉信息,声音,味道,触觉等),通过科学技能模拟仿真后再叠加到实际天下被人类感官所感知,从而到达逾越实际的感官体验。
4.1 PyGame和PyOpenGL

PyGame好坏常盛行的游戏开发工具包,它可以简朴的处置惩罚实际窗口、输入装备、变乱,以及其他内容。
PyOpenGL是OpenGL图形编程的Python绑定接口。OpenGL可以安装在险些全部的体系上,而且具有很好的图形性能。OpenGL具有跨平台性,可以或许在差别的操纵体系之间工作。
4.2 从照相机矩阵到OpenGL格式

OpenGL 利用4×4 的矩阵来体现变更(包罗三维变更和投影)。这和我们利用 的 3×4 照相机矩阵略有差别。但是,照相机与场景的变更分成了两个矩阵,GL_PROJECTION 矩阵和GL_MODELVIEW 矩阵GL_PROJECTION 矩阵处置惩罚图像成像的性子,等价于我们的内标定矩阵 K。GL_MODELVIEW 矩阵处置惩罚物体和照 相机之间的三维变更关系,对应于我们照相机矩阵中的R 和 t 部分。一个差别之处是,假设照相机为坐标系的中心,GL_MODELVIEW 矩阵实际上包罗了将物体放置 在照相机前面的变更。
假设我们已经得到了标定好的照相机,即已知标定矩阵 K,下面的函数可以将照相机参数转换为 OpenGL 中的投影矩阵:
  1. def set_projection_from_camera(K):
  2.    """从照相机标定矩阵中获得视图"""
  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.    # 定义近的和圆的裁剪平面
  10.    near = 0.1       far = 100.0
  11.    gluPerspective(fovy, aspect, near, far)
  12.    glViewport(0, 0, width, height)
复制代码
4.3 在图像中放置物体

  1. import pickle
  2. from pylab import *
  3. from OpenGL.GL import *
  4. from OpenGL.GLU import *
  5. from OpenGL.GLUT import *
  6. import pygame, pygame.image
  7. from pygame.locals import *
  8. from PCV.geometry import homography, camera
  9. from PCV.localdescriptors import sift
  10. import cv2
  11. #绘制立方体
  12. def cube_points(c, wid):
  13.     p = []
  14.     # bottom
  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])
  18.     p.append([c[0]+wid, c[1]-wid, c[2]-wid])
  19.     p.append([c[0]-wid, c[1]-wid, c[2]-wid]) #same as first to close plot   
  20.     # top
  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]) #same as first to close plot   
  26.     # vertical sides
  27.     p.append([c[0]-wid, c[1]-wid, c[2]+wid])
  28.     p.append([c[0]-wid, c[1]+wid, c[2]+wid])
  29.     p.append([c[0]-wid, c[1]+wid, c[2]-wid])
  30.     p.append([c[0]+wid, c[1]+wid, c[2]-wid])
  31.     p.append([c[0]+wid, c[1]+wid, c[2]+wid])
  32.     p.append([c[0]+wid, c[1]-wid, c[2]+wid])
  33.     p.append([c[0]+wid, c[1]-wid, c[2]-wid])
  34.     return array(p).T
  35. def my_calibration(sz):
  36.     row, col = sz
  37.     fx = 758*col/640
  38.     fy = 752*row/480
  39.     K = diag([fx, fy, 1])
  40.     K[0, 2] = 0.5*col
  41.     K[1, 2] = 0.5*row
  42.     return K
  43. def set_projection_from_camera(K):
  44.     glMatrixMode(GL_PROJECTION)
  45.     glLoadIdentity()
  46.     fx = K[0,0]
  47.     fy = K[1,1]
  48.     fovy = 2*math.atan(0.5*height/fy)*180/math.pi
  49.     aspect = (width*fy)/(height*fx)
  50.     near = 0.1
  51.     far = 100.0
  52.     gluPerspective(fovy,aspect,near,far)
  53.     glViewport(0,0,width,height)
  54. def set_modelview_from_camera(Rt):
  55.     glMatrixMode(GL_MODELVIEW)
  56.     glLoadIdentity()
  57.     Rx = np.array([[1,0,0],[0,0,-1],[0,1,0]])
  58.     R = Rt[:,:3]
  59.     U,S,V = np.linalg.svd(R)
  60.     R = np.dot(U,V)
  61.     R[0,:] = -R[0,:]
  62.     t = Rt[:,3]
  63.     M = np.eye(4)
  64.     M[:3,:3] = np.dot(R,Rx)
  65.     M[:3,3] = t
  66.     M = M.T
  67.     m = M.flatten()
  68.     glLoadMatrixf(m)
  69. def draw_background(imname):
  70.     bg_image = pygame.image.load(imname).convert()
  71.     bg_data = pygame.image.tostring(bg_image,"RGBX",1)
  72.     glMatrixMode(GL_MODELVIEW)
  73.     glLoadIdentity()
  74.         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
  75.         glEnable(GL_TEXTURE_2D)
  76.         glBindTexture(GL_TEXTURE_2D,glGenTextures(1))
  77.         glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,bg_data)
  78.     glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST)
  79.     glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST)
  80.     glBegin(GL_QUADS)
  81.     glTexCoord2f(0.0,0.0); glVertex3f(-1.0,-1.0,-1.0)
  82.     glTexCoord2f(1.0,0.0); glVertex3f( 1.0,-1.0,-1.0)
  83.     glTexCoord2f(1.0,1.0); glVertex3f( 1.0, 1.0,-1.0)
  84.     glTexCoord2f(0.0,1.0); glVertex3f(-1.0, 1.0,-1.0)
  85.     glEnd()
  86.     glDeleteTextures(1)
  87. def draw_teapot(size):
  88.     glEnable(GL_LIGHTING)
  89.     glEnable(GL_LIGHT0)
  90.     glEnable(GL_DEPTH_TEST)
  91.     glClear(GL_DEPTH_BUFFER_BIT)
  92.     glMaterialfv(GL_FRONT,GL_AMBIENT,[0,0,0,0])
  93.         glMaterialfv(GL_FRONT,GL_DIFFUSE,[0.5,0.0,0.0,0.0])
  94.         glMaterialfv(GL_FRONT,GL_SPECULAR,[0.7,0.6,0.6,0.0])
  95.         glMaterialf(GL_FRONT,GL_SHININESS,0.25*128.0)
  96.         glutSolidTeapot(size)
  97. def drawFunc(size):  # 白色茶壶
  98.     glRotatef(0.5, 5, 5, 0)  # (角度,x,y,z)
  99.     glutWireTeapot(size)
  100.     # 刷新显示
  101.     glFlush()
  102. width, height = 1000, 747
  103. def setup():  # 设置窗口和pygame环境
  104.     pygame.init()
  105.     pygame.display.set_mode((width, height), OPENGL | DOUBLEBUF)
  106.     pygame.display.set_caption("OpenGL AR demo")
  107. sift.process_image('D:\\123\图像处理\Image Processing\Image Processing\Chapter 4\\book_frontal.JPG', 'im0.sift')
  108. l0, d0 = sift.read_features_from_file('im0.sift')
  109. sift.process_image('D:\\123\图像处理\Image Processing\Image Processing\Chapter 4\\book_perspective.JPG','im1.sift')
  110. l1, d1 = sift.read_features_from_file('im1.sift')
  111. matches = sift.match_twosided(d0, d1)
  112. ndx = matches.nonzero()[0]
  113. fp = homography.make_homog(l0[ndx, :2].T)
  114. ndx2 = [int(matches[i]) for i in ndx]
  115. tp = homography.make_homog(l1[ndx2, :2].T)
  116. model = homography.RansacModel()
  117. H, inliers = homography.H_from_ransac(fp, tp, model)
  118. K = my_calibration((747, 1000))
  119. box = cube_points([0, 0, 0.1], 0.1)
  120. cam1 = camera.Camera(hstack((K, dot(K, array([[0], [0], [-1]])))))
  121. box_cam1 = cam1.project(homography.make_homog(box[:, :5]))
  122. box_trans = homography.normalize(dot(H, box_cam1))
  123. cam2 = camera.Camera(dot(H, cam1.P))
  124. A = dot(linalg.inv(K), cam2.P[:, :3])
  125. A = array([A[:, 0], A[:, 1], cross(A[:, 0], A[:, 1])]).T
  126. cam2.P[:, :3] = dot(K, A)
  127. #使用第二个照相机矩阵投影
  128. box_cam2 = cam2.project(homography.make_homog(box))
  129. Rt = dot(linalg.inv(K), cam2.P)
  130. setup()
  131. draw_background("D:\\123\图像处理\Image Processing\Image Processing\Chapter 4\\book_perspective.bmp")
  132. set_projection_from_camera(K)
  133. set_modelview_from_camera(Rt)
  134. draw_teapot(0.05)  # 显示红色茶壶
  135. #drawFunc(0.05)  # 显示白色空心茶壶
  136. pygame.display.flip()
  137. while True:
  138.     for event in pygame.event.get():
  139.         if event.type == pygame.QUIT:
  140.             sys.exit()
复制代码

 
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表