python实现——在图像上放置虚拟物

前言:虚拟物是指一些3D模型,在图像上放置虚拟物就是以一张图片为背景,在把绘制的3D图放置在指定的位置上。除此之外还可以用于视频中放置虚拟物,实现动图放置3D模型。在这个例子当中,是通过照相机标定获得图像的标定矩阵,再使用单应性矩阵投影出虚拟物映射图像。

配置环境:python3.7  windows10  64位 PyOpenGL-3.1.3b2、PyOpenGL-accelerate-3.1.3b2和pygame-1.9.4

编译工具:IDLE

目录

(一)配置安装PyOpenG和pygame

(二)实现平面物体放到背景图片中

1、使用平面物体作为标记物,来计算用于新视图投影矩阵例子。

2、使用pygame绘制一个红色茶壶放置在图像上

(三)遇到的问题

完整代码及图片



(一)配置安装PyOpenGL和pygame

1、根据自己下载python版本相对应的PyOpenGL和pygame版本下载:我的python是3.7的,所以下载为箭头指向的版本

下载连接:PyOpenGL https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyopengl

python实现——在图像上放置虚拟物_第1张图片

        下载连接:pygame  https://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame

 

python实现——在图像上放置虚拟物_第2张图片

2、win+R打开cmd命令窗口,分别输入一下语句导入安装:注意下载成功后要把以上文件放到pip.exe的目录下,否则会找不到文件  例如:D:\Python37\Scripts

python实现——在图像上放置虚拟物_第3张图片

pip install PyOpenGL-3.1.3b2-cp37-cp37m-win_amd64.whl

pip install PyOpenGL_accelerate-3.1.3b2-cp37-cp37m-win_amd64.whl

pip install pygame-1.9.4-cp37-cp37m-win_amd64.whl

安装成功的显示:

python实现——在图像上放置虚拟物_第4张图片

(二)实现平面物体放到背景图片中

实现在图像中放置虚拟物体,第一件事就是要将图像(打算放置虚拟物体的图像)作为背景添加进来。在opengl中是通过创建一个四边形的方式来完成,绘制出四边形同时投影和模拟视图矩阵重置。

1、使用平面物体作为标记物,来计算用于新视图投影矩阵例子。

实现代码:

from pylab import *
from PIL import Image

# If you have PCV installed, these imports should work
from PCV.geometry import homography, camera
import sift

"""
This is the augmented reality and pose estimation cube example from Section 4.3.
"""


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


def my_calibration(sz):
    """
    Calibration function for the camera (iPhone4) used in this example.
    """
    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



# compute features
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')


# 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)

# camera calibration
K = my_calibration((747, 1000))

# 3D points at plane z=0 with sides of length 0.2
box = cube_points([0, 0, 0.1], 0.1)

# project bottom square in first image
cam1 = camera.Camera(hstack((K, dot(K, array([[0], [0], [-1]])))))
# first points are the bottom square
box_cam1 = cam1.project(homography.make_homog(box[:, :5]))


# use H to transfer points to the second image
box_trans = homography.normalize(dot(H,box_cam1))

# compute second camera matrix from cam1 and 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)

# project with the second camera
box_cam2 = cam2.project(homography.make_homog(box))



# plotting
im0 = array(Image.open('book_frontal.JPG'))
im1 = array(Image.open('book_perspective.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实现——在图像上放置虚拟物_第5张图片

                                                                            图一

图二是以图一为标记物,经过估计的单应性矩阵变换得到投影图像

python实现——在图像上放置虚拟物_第6张图片

得到的单应性矩阵是将上图的标记物(即书本)上的点映射到另一幅图像中的对应点。

python实现——在图像上放置虚拟物_第7张图片

                                                                           图二

图三是使用计算出照相机矩阵变换得到的立方体图像,定义相应的三维坐标系,使标记物在X-Y平面上(Z=0),原点在标记物的某个位置上。为了验证单应性矩阵的正确性,绘制一个简单的立方体(正方形)来显示出效果。

python实现——在图像上放置虚拟物_第8张图片

                                                                    图三

2、使用pygame绘制一个红色茶壶放置在图像上

实现代码:


from pylab import *
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import pygame,pygame.image
from pygame.locals import *

from PCV.geometry import homography, camera
import sift
from PIL import Image
import pickle

def cube_points(c, wid):
    """ 创建用于绘制立方体的点列表 """
    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):
    """
    Calibration function for the camera (iPhone4) used in this example.
    """
    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)

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))

# 位于边长为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("book_perspective.bmp")
set_projection_from_camera(K)
set_modelview_from_camera(Rt)
draw_teapot(0.05)  # 显示红色茶壶 

pygame.display.flip()#则是由于双缓冲的原因,
#需要将整个display的surface对象更新到屏幕上去
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()#正常关闭窗口
            sys.exit()
            


运行结果:

其实,放置立方体还是茶壶以及其他三维物体都是一样的原理,利用照相机标定以二维的棋盘模式标记,把三维的转换为二维,以致于用二维的方法经过一些变量上的变换可以还原出三维的场景。如果还想把其他的模型放置在图像上,可以自己找资源下载模型,然后根据了解pygame如何载入模型。相关连接:https://en.wikipedia.org/wiki/Wavefront_.obj_file

(三)遇到的问题

(1)安装问题:Traceback (most recent call last):
  File "E:/Python37_course/test4/红色茶壶.py", line 16, in 
    glutInit()
  File "D:\Python37\lib\site-packages\OpenGL\GLUT\special.py", line 333, in glutInit
    _base_glutInit( ctypes.byref(count), holder )
  File "D:\Python37\lib\site-packages\OpenGL\platform\baseplatform.py", line 407, in __call__
    self.__name__, self.__name__,
OpenGL.error.NullFunctionError: Attempt to call an undefined function glutInit, check for bool(glutInit) before calling

出现上面的问题是因为直接导入pip install pyopengl自动安装的是32位的pyopengl,(按照上面的安装步骤执行)只要正确下载自己之前下载python对应版本的就可以解决这个错误。

(2)运行结束后出现闪退现象(IDLE不会报错,但是用pycharm和cmd编译运行会报错:freeglut  ERROR:  Function called without first calling 'glutInit'.),这是因为freeglut和glut共存且定义了相同的方法,存在动态链接库重叠问题。解决方法是:进入到相关安装opengl的路径:例如 D:\Python37\Lib\site-packages\OpenGL\DLLS 里删掉freeglut64.vc15.dll和gle64.vc15.dll,留下glut64.vc15.dll。就可以解决闪退问题。

python实现——在图像上放置虚拟物_第9张图片

 

完整代码及图片

下载连接:链接:https://pan.baidu.com/s/1NJPuJLqHp-o9__yjXl48PA 
提取码:1lar 

 

 

你可能感兴趣的:(计算机视觉)