增强现实
静态
增强现实技术(Augmented Reality,简称 AR),是一种实时地计算摄影机影像的位置及角度并加上相应图像、视频、3D模型的技术,这种技术的目标是在屏幕上把虚拟世界套在现实世界并进行互动。
我们可以把过程大致分为以下四步:
- 识别参考平面
- 特征提取
- 特征描述
- 特征匹配
- 计算单应性矩阵
- 转换坐标系
- 在图像中投影并描绘3D模型
在图像中投影并描绘3D模型
前三个部分在之前的博客都有提及
python计算机视觉编程(三)——Harris角点 SIFT 匹配地理标记图像.
python计算机视觉编程(四)图像到图像的映射.
所以主要讲述一下第四点
相机投影模型
相机成像的过程实际是将三维空间中的点映射到二维空间的过程,可以简单的使用小孔成像模型来描述该过程。
针孔相机模型
在描述小孔的成像过程前,首先来定义两个坐标系:
- 相机坐标系(三维坐标系)
相机的中心被称为光心,以光心c为原点和坐标轴X,Y,Z组成了相机坐标系 - 图像坐标系(二维坐标系)
成像平面中,以成像平面的中心像主点为原点和坐标轴x,y组成了图像坐标系。
根据图片中的三角形相似可以得到两个坐标系的对应关系
但是该映射z却不是线性的,引入新坐标线性化,得到
内参数
相机的内参数由下面的两部分组成:
- 射影变换本身的参数,相机的焦点到成像平面的距离,也就是焦距f。
- 从成像平面坐标系到像素坐标系的变换。上面推导中使用的像点坐标p=(x,y)是成像平面坐标系下,以成像平面的中心为原点。而实际像素点的表示方法是以像素来描述,坐标原点通常是图像的左上角,X轴沿着水平方向向左,Y轴竖直向下。像素是一个矩形块,所以像素坐标和成像平面坐标之间,相差了一个缩放和原点的平移。
假设像素坐标的水平方向的轴为x,竖直方向的轴为y,那么将一个成像平面的坐标(x,y)在水平方向上缩放a倍,在竖直方向上缩放b倍,同时平移(cx,cy),就可以得到像素坐标系的坐标(x’,y’),其公式如下: - x=a*x’+cx
- y=b*x’+cy
与上面得到的其次坐标联立,有
我们就能得到相机的内参矩阵
外参数
我们知道p=KP,其中p是图像坐标系的点,K是内参矩阵,P是相机坐标系。
而相机坐标系并不稳定,会变化,我们引进一个不变的世界坐标系。
设P’是相机坐标系的点,P是世界坐标系,R是旋转矩阵,t是一个平移向量,那么有P’=RP+t
将其写成齐次坐标的形式,我们可以得到 外参数T
最后我们得到
相机标定原理——张正友标定
张正友标定只考虑了径向畸变,没有考虑切向畸变
我们把世界坐标系放在平面上,即z=0,可以得到:
那么,关系可以改写为
λ 是任意标量。可知r1和r2是正交的,有:
相机标定解决办法
封闭解
B是对称的,定义一个六维向量
设h中第j行为
我们得到
其中vij为
写成其次形式
对n张图片有
式中v是一个2n*6的矩阵
b被估计出来以后,我们可以计算内参矩阵A,之后外部参数也能算出来。
最大似然估计
上面的解决办法,在一般情况下是没有物理意义的。
我们给定n张图片,每张m个角点。最大似然可以从下列公式的最小值得到
径向畸变的处理
径向畸变一般很小,简单的忽略径向畸变后,可以用最大似然估计的方法估计另外五个参数。一种策略是估计其他参数后在估计k1和k2。然后从上式中,对每幅图的每个点可以得到两个方程
n幅图像中共有m个点,迭代所有方程得到一个2mn的方程组,或者以矩阵形式
其中
由最小二乘法得到
k1和k2估计出来后,可以通过解最大似然估计来重新估计其他参数。
标定结果
拍摄设备:荣耀V8
棋盘如下,共拍摄15张照片
内参矩阵
k1 k2
外参矩阵
从OpenCV到OpenGL
了解了上面的内容,得到摄像机的内参K和外参[R|T]后,就可以开始考虑将虚拟物体添加进来了。
代码
结果如开头图片
import math
import pickle
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
from PCV.localdescriptors 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 = []
# 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