Python计算机视觉-对极几何与基础矩阵

对极几何

多视图几何利用在不同视点拍摄的图像间关系研究相机或者特征之间的关系,而在多视图几何中最重要的基础就是双视图,如果已经有了一个场景的两个视图以及视图中的对应图像点,则依据相机位置关系,相机性质和三维场景点的位置可以得到图像点的几何关系约束,即对极几何。通过对极几何,我们可以在已知两幅图像中两点是对应关系的前提下,求解两个相机的相对位置和姿态。

若两个相机的光心分别为 CC ,空间中的点为 X ,则有 五点共面,关系如下:

Python计算机视觉-对极几何与基础矩阵
相关概念的定义:

基线:两相机光心连线;
对极点 = 基线与像平面相交点 = 光心在另一幅图像中的投影;
对极平面 = 包含基线的平面;
对极线 = 对极平面与像平面的交线

对极约束:点 xx ’ 一定位于平面 π 上,而平面 π 可以利用基线 CC’ 和图像点 x 的反投影射线确定,点 x’ 又是右侧图像平面上的点,所以点 x’ 一定位于平面 π 与右侧图像平面的交线 l’ 上,直线 l’ 为点 x 的对极线,即点 x 的对应点 x’ 一定位于它的对极线上:
Python计算机视觉-对极几何与基础矩阵
本质矩阵(essential matrix):

XC,C’ 坐标系中的相对坐标分别 p,p’R 为相机旋转矩阵,T 为光心位移向量,K 为相机内参矩阵:
Python计算机视觉-对极几何与基础矩阵
则有 p = R p’ + T ,又因 C, C, X 三点共面
可得 ( p - T ) T ( T × P ) = 0,将 p 代换可得:( RT p ) T ( T × p ) = 0
T × p = S p(叉积 S 矩阵的秩为2,为反对称矩阵)
于是 ( R T p ) T ( S p ) = 0
整理后有 ( pT R )( S p ) = 0 --> p E p = 0
我们将 E 称为本质矩阵(essential matrix),它描述了空间中的点在 两个坐标系中 的坐标对应关系

基础矩阵(fundamental matrix)

现在我们已知 p E p = 0,且两个相机内参为 K, K
有三维点到向屏幕坐标的转换:p = K -1 x, p = K-1 x
因此代换得:
( K-1 x ) T E ( K -1 x ) = 0 ==> xT F x = 0
化简后式中的 F 即 3 * 3, 秩为 2基础矩阵 ,它描述了空间中的点在 两个像平面中 的坐标对应关系,是对极几何的代数表达方式 ,在两幅图像中的任意匹配点对都满足上式。
正因基础矩阵的作用于此,我们可以将其用于图像特征点的 简化匹配,或者 去除错配特征

稳健估计基础矩阵

当存在噪声和不正确的匹配时,我们需要估计基础矩阵,我们采用 RANSAC八点法 结合进行尝试:

计算图像对的特征匹配,并估计基础矩阵;
制作一个包含三幅图像的数据集,计算一对图像中三维点和照相机矩阵,匹配特征到剩下的图像中以获得对应。然后利用对应的三维点使用后方交汇法计算其他图像的照相机矩阵。

源码如下:

from PIL import Image
from numpy import *
from pylab import *
import numpy as np

from PCV.geometry import camera
from PCV.geometry import homography
from PCV.geometry import sfm
from PCV.localdescriptors import sift
from importlib import reload
camera = reload(camera)
homography = reload(homography)
sfm = reload(sfm)
sift = reload(sift)

# Read features
im1 = array(Image.open('./img/5.jpg'))
sift.process_image('./img/5.jpg', 'im5.sift')

im2 = array(Image.open('./img/6.jpg'))
sift.process_image('./img/6.jpg', 'im7.sift')

l1, d1 = sift.read_features_from_file('im5.sift')
l2, d2 = sift.read_features_from_file('im6.sift')

matches = sift.match_twosided(d1, d2)

ndx = matches.nonzero()[0]
x1 = homography.make_homog(l1[ndx, :2].T)
ndx2 = [int(matches[i]) for i in ndx]
x2 = homography.make_homog(l2[ndx2, :2].T)

d1n = d1[ndx]
d2n = d2[ndx2]
x1n = x1.copy()
x2n = x2.copy()

figure(figsize=(16,16))
sift.plot_matches(im1, im2, l1, l2, matches, True)
show()

#def F_from_ransac(x1, x2, model, maxiter=5000, match_threshold=1e-6):
def F_from_ransac(x1, x2, model, maxiter=5000, match_threshold=1e-6):
    """ Robust estimation of a fundamental matrix F from point
    correspondences using RANSAC (ransac.py from
    http://www.scipy.org/Cookbook/RANSAC).

    input: x1, x2 (3*n arrays) points in hom. coordinates. """

    from PCV.tools import ransac
    data = np.vstack((x1, x2))
    d = 10 # 20 is the original
    # compute F and return with inlier index
    F, ransac_data = ransac.ransac(data.T, model,
                                   8, maxiter, match_threshold, d, return_all=True)
    return F, ransac_data['inliers']

# find F through RANSAC
model = sfm.RansacModel()
F, inliers = F_from_ransac(x1n, x2n, model, maxiter=5000, match_threshold=1e-1)
print("F:", F)

P1 = array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]])
P2 = sfm.compute_P_from_fundamental(F)

print("P2:", P2)

# triangulate inliers and remove points not in front of both cameras
X = sfm.triangulate(x1n[:, inliers], x2n[:, inliers], P1, P2)

# plot the projection of X
cam1 = camera.Camera(P1)
cam2 = camera.Camera(P2)
x1p = cam1.project(X)
x2p = cam2.project(X)

figure(figsize=(16, 16))
imj = sift.appendimages(im1, im2)
imj = vstack((imj, imj))

imshow(imj)

cols1 = im1.shape[1]
rows1 = im1.shape[0]
for i in range(len(x1p[0])):
    if (0<= x1p[0][i]<cols1) and (0<= x2p[0][i]<cols1) and (0<=x1p[1][i]<rows1) and (0<=x2p[1][i]<rows1):
        plot([x1p[0][i], x2p[0][i]+cols1],[x1p[1][i], x2p[1][i]],'c')
axis('off')
show()

d1p = d1n[inliers]
d2p = d2n[inliers]

# Read features
im3 = array(Image.open('./img/7.jpg'))
sift.process_image('./img/7.jpg', 'im7.sift')
l3, d3 = sift.read_features_from_file('im7.sift')

matches13 = sift.match_twosided(d1p, d3)

ndx_13 = matches13.nonzero()[0]
x1_13 = homography.make_homog(x1p[:, ndx_13])
ndx2_13 = [int(matches13[i]) for i in ndx_13]
x3_13 = homography.make_homog(l3[ndx2_13, :2].T)

figure(figsize=(16, 16))
imj = sift.appendimages(im1, im3)
imj = vstack((imj, imj))

imshow(imj)

cols1 = im1.shape[1]
rows1 = im1.shape[0]
for i in range(len(x1_13[0])):
    if (0<= x1_13[0][i]<cols1) and (0<= x3_13[0][i]<cols1) and (0<=x1_13[1][i]<rows1) and (0<=x3_13[1][i]<rows1):
        plot([x1_13[0][i], x3_13[0][i]+cols1],[x1_13[1][i], x3_13[1][i]],'c')
axis('off')
show()

P3 = sfm.compute_P(x3_13, X[:, ndx_13])

print("P3:", P3)

print("P1:", P1)
print("P2:", P2)
print("P3:", P3)

对于近景图像:

Python计算机视觉-对极几何与基础矩阵Python计算机视觉-对极几何与基础矩阵Python计算机视觉-对极几何与基础矩阵
结果如下:
Python计算机视觉-对极几何与基础矩阵

对于远景图像:

Python计算机视觉-对极几何与基础矩阵Python计算机视觉-对极几何与基础矩阵Python计算机视觉-对极几何与基础矩阵
结果如下:

Python计算机视觉-对极几何与基础矩阵

对于室内图像:

Python计算机视觉-对极几何与基础矩阵Python计算机视觉-对极几何与基础矩阵Python计算机视觉-对极几何与基础矩阵Python计算机视觉-对极几何与基础矩阵
实验小结:

  1. RANSAC 的稳健估计对于远近不同的图像集可能会最终遇到无法找到适配点对的情况,需要在调用时多次尝试对阈值(match_threshold=1e-3)进行调整以适用当前图像集;
  2. 对于较远的目标适配点对数量大,越容易增加错配无法去除的点对数量,对于较近的目标则相反;
  3. 错配情况时常发生且没有被剔除,甚至保留了错配很明显的点对而将较好的适配点剔除,这种现象与几乎完全相同的物体结构外观相对应,其中还有因追求时间效率将高分辨率图像进行了压缩处理的影响。