针孔照相机模型是计算机视觉中广泛使用的照相机模型。该相机从一个小孔采集射到暗箱内部的光线。在真空照相机模型中,在光线投射到图像平面之前,从唯一一个点经过。
具体相关信息参考:https://zhuanlan.zhihu.com/p/124298599;https://blog.csdn.net/lsh_2013/article/details/47615309
照相机矩阵可以分解为:
其中,R是描述照相机方向的旋转矩阵,t是描述照相机中心位置的三维平移向量,内标定矩阵K描述照相机的投影性质。标定矩阵仅和照相机自身的情况相关,通常情况下可以写成:
图像平面和照相机中心的距离称为焦距f。当像素数组在传感器上偏斜的时候,需要用到倾斜参数s。在大多数情况下s可以设置为0。
纵横比例α是在像素元素非正方形的情况下使用的,通常情况下可以默认设置α=1。
除焦距之外,标定矩阵中剩余的唯一参数为光心,即主点,也就是光线坐标轴和图像平面的交点。因为光心通常在图像的中心,并且图像的坐标是从左上角开始计算的,所以光心的坐标常接近于图像高度和宽度的一半。
创建一个照相机类,用来处理对照相机和投影建模所需要的全部操作:
from scipy.import linalg
class Camera(object):
def _init_(self, p):
self.p = p
self.K = None
self.R = None
self.t = None
self.c = None
def project(self, x):
x = dot(self.P, X)
for i in range(3):
x[i] /= x[2]
return x
下边的例子将三维中的点投影到图像视图中:
from PIL import Image
from pylab import *
from numpy import *
import camera
points = loadtxt('house.p3d').T
points = vstack((points, ones(points.shape[1])))
P = hstack((eye(3), array([[0], [0], [-10]])))
cam = camera.Camera(P)
x = cam.project(points)
figure()
plot(x[0], x[1], 'k.')
show()
首先使用齐次坐标表示这些点,然后使用一个投影矩阵来创建Camera对象将这些三维点投影到图像平面并执行绘制操作,得到的结果如下图所示:
如果给定一个照相机矩阵P,需要恢复内参数K以及照相机的位置t和姿势R,这种矩阵分块操作称为因子分解。
下面的代码使用RQ银子分解的方法。但这种分解方法的结果并不是唯一的,分解的结果存在二义性。由于需要限制旋转矩阵R为正定的,所以如果需要可以在求解的结果中加入变换T来改变符号。
def factor(self):
K, R = li.rq(self.P[:, :3])
T = diag(sign(diag(K)))
if li.det(T) < 0:
T[1, 1] *= -1
self.K = dot(K, T)
self.R = dot(T, R)
self.t = dot(li.inv(self.K), self.P[:, 3])
return self.K, self.R, self.t
给定照相机投影矩阵P,可以计算出空间上照相机的所在位置。照相机中心为C,是一个三维点,满足约束条件PC=0。对于投影矩阵为P=K[R|t]照相机可以通过下述式子计算C:
下面的代码可以按照上述公式返回机选照相机的中心:
def center(self):
if self.c is not None:
return self.c
else:
self.factor()
self.c = -dot(self.R.T, self.t)
return self.c
标定照相机是指计算出该照相机的内参数。
具体步骤如下:
测量到的数据写入一个辅助函数:
def my_calibration(sz):
row, col = sz
fx = 3158 * col / 4032
fy = 3019 * row / 3024
K = diag([fx, fy, 1])
K[0, 2] = 0.5 * col
K[1, 2] = 0.5 * row
return K
该函数的输入参数为表示图像大小的元组,返回参数为标定矩阵。
使用下面的代码提取两幅图像的SIFT特征,然后使用RANSAC算法稳健地估计单应性矩阵:
sift1.process_image('qinghai1.jpg', 'im0.sift')
l0, d0 = sift.read_features_from_file('im0.sift')
sift1.process_image('qinghai2.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[ndx, :2].T)
model = homography.RansacModel()
H = homography.H_from_ransac(fp, tp, model)
该单应性矩阵将一幅图像中标记物上的点映射到另一幅图像中的对应点。定义相应的三维坐标系,使标记物在X-Y平面上,原点在标记物的某位置上。
为了检验单应性矩阵结果的正确性,需要将一些简单的三维物体放置在标记物上,下边的函数用来产生立方体上的点:
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
接下来可以实现两个视图间的相对变换:
K = my_calibration((747, 1000))
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]))
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(homography.make_homog(box))
第一个产生的标定矩阵就是在该图像分辨率大小下的标定矩阵,cube_points()函数产生的前五个点对应与立方体底部的点。
使用下边的代码来可视化这些投影后的点:
im0 = array(Image.open('qinghai1.jpg'))
im1 = array(Image.open('qinghai2.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()
为了能够之后再次使用,可以使用Pickle将这些照相机矩阵保存起来:
import pickle
with open('ar_camera.pkl', 'w') as f:
pickle.dump(K, f)
pickle.dump(dot(linalg.inv(K), cam2.P), f)
这一部分从计算给定平面场景物体的照相机矩阵入手,结合特征匹配和单应性矩阵以及照相机标定技术,完成在一幅图像上放置一个立方体。有了照相机姿态估计技术,就可以学习如何创建简单增强现实应用的基本技术。