计算机视觉——图像对的特征匹配及估计基础矩阵


基本矩阵F


基本矩阵是对极几何的代数表示,我们已经知道对于一幅视图上的点x,在另一视图上有一条对应的极线l’,该点的对应点x’必然在l’上,因此存在映射: x-> l’,基本矩阵F实际上就是表示这样的一种点到直线的射影映射
计算机视觉——图像对的特征匹配及估计基础矩阵_第1张图片
如图所示,考虑空间中不通过任何两个摄像机中心的平面pi,过第一个相机中心C和x的射线与该平面pi交于X,这个点再投影到第二幅图像上的点x’,这个过程通过平面pi的平移。由于X在对应与x的射线上,所以投影点x’必然在对应于这条射线的图像即极限l’上,点x和x’都是在一张平面上的3D点X的像,在第一幅图像上的所有点xi和对应点x’i的射影实际上是等价的,因为他们都射影等价于共面点集Xi,所以存在一个2D映射H,把每一个xi映射到x’i
给定点x’,通过x’和对极点e’的对极线I’,可以记为I’=e’ * x’ = [e’]x’,又因为x’=Hx,所以必然有
I’ = [e’]Hx = Fx

通过匹配点对估算基础矩阵

基础矩阵表示的是图像中的像点p1到另一幅图像对极线l2的映射,有如下公式:在这里插入图片描述
而和像点P1匹配的另一个像点p2必定在对极线l2上,所以就有:在这里插入图片描述
这样仅通过匹配的点对,就可以计算出两视图的基础矩阵F。
基础矩阵F是一个3×3的矩阵,有9个未知元素。然而,上面的公式中使用的齐次坐标,齐次坐标在相差一个常数因子下是相等,F也就只有8个未知元素,也就是说,只需要8对匹配的点对就可以求解出两视图的基础矩阵F。下面介绍下8点法 Eight-Point-Algorithm计算基础矩阵的过程。
假设一对匹配的像点p1=[u1,v1,1]T,p2=[u2,v2,1]T,带入式子中,得到:在这里插入图片描述
把基础矩阵F的各个元素当作一个向量处理在这里插入图片描述
那么上面式子可以写为在这里插入图片描述
对于其他的点对也使用同样的表示方法。这样将得到的所有方程放到一起,得到一个线性方程组
在这里插入图片描述
计算机视觉——图像对的特征匹配及估计基础矩阵_第2张图片
求解上面的方程组就可以得到基础矩阵各个元素了。当然这只是理想中的情况,由于噪声、数值的舍入误差和错误的匹配点的影响,仅仅求解上面的线性方程组得到的基础矩阵非常的不稳定,因此在8点法的基础上有各种改进方法。
在实际计算中,可以直接用ATA的分解来求解参数。
也可以用非线性优化,通过搜索f使得||Af||最小化,同时满足||f||=1的约束
上述求解后的F不一定能满足秩为2的约束,因此还要在F基础上加以约束。

图像坐标归一化

将上面公式中由匹配的点对坐标组成的矩阵记为系数矩阵A
Af=0
系数矩阵A是利用8点法求基础矩阵的关键,所以Hartey就认为,利用8点法求基础矩阵不稳定的一个主要原因就是原始的图像像点坐标组成的系数矩阵A不好造成的,而造成A不好的原因是像点的齐次坐标各个分量的数量级相差太大。基于这个原因,Hartey提出一种改进的8点法,在应用8点法求基础矩阵之前,先对像点坐标进行归一化处理,即对原始的图像坐标做同向性变换,这样就可以减少噪声的干扰,大大的提高8点法的精度。

预先对图像坐标进行归一化有以下好处:
能够提高运算结果的精度
利用归一化处理后的图像坐标,对任何尺度缩放和原点的选择是不变的。归一化步骤预先为图像坐标选择了一个标准的坐标系中,消除了坐标变换对结果的影响。
归一化操作分两步进行,首先对每幅图像中的坐标进行平移(每幅图像的平移不同)使图像中匹配的点组成的点集的形心(Centroid)移动到原点;接着对坐标系进行缩放是的点p=(x,y,w)T中的各个分量总体上有一样的平均值,各个坐标轴的缩放相同的,最后选择合适的缩放因子使点p到原点的平均距离是。概括起来变换过程如下:
对点进行平移使其形心位于原点。
对点进行缩放,使它们到原点的平均距离为
对两幅图像独立进行上述变换。
计算机视觉——图像对的特征匹配及估计基础矩阵_第3张图片
上图左边是原始图像的坐标,右边是归一化后的坐标,H是归一化的变换矩阵,可记为如下形式:在这里插入图片描述
其中,μ¯,ν¯是图像像点坐标两个分量的平均值在这里插入图片描述
S表示尺度,其表达式为:
在这里插入图片描述
这样,首先对原始的图像坐标进行归一化处理,再利用8点法求解基础矩阵,最后将求得的结果解除归一化,得到基础矩阵F,总结如下:

对图像1进行归一化处理,计算一个只包含平移和缩放的变换H1,将图像1中的匹配点集p1i变换到新的点集p1i^,新点集的形心位于原点(0,0)T,并且它们到原点的平均距离是√2。
对图像2,计算变换矩阵H2进行相同的归一化处理
使用8点法利用变换后的点集估计基础矩阵F^
根据变换F=HT2F^H1
使用归一化的坐标虽然能够在一定程度上消除噪声、错误匹配带来的影响,但还是不够的。

最小二乘法

仅仅对图像坐标进行归一化处理,能在一定程度上提高计算的精度,但在实践中还是不够的。8点法中,只使用8对匹配的点对估计基础矩阵,但通常两幅图像的匹配的点对远远多于8对,可以利用更多匹配的点对来求解上面的方程。
由于基础矩阵F在一个常量因子下是等价的,这样可以给基础矩阵F的元素组成的向量f施加一个约束条件。
在这里插入图片描述
这样由K≥8个匹配的点对,组合成一个矩阵QK×9,求解上面方程就变成了求解如下问题的最小二乘解在这里插入图片描述
其中,矩阵Q的每一行来自一对匹配点;f是基础矩阵F元素构成的待求解的向量,根据2-范数的定义在这里插入图片描述
将上式的中间部分提取出来得到矩阵M=QTQ,这是一个9×9的矩阵。基于拉格朗日-欧拉乘数优化定理,在∥f∥=1约束下,Qf=0的最小二乘解,为矩阵M=QTQ的最小特征值对应的特征向量。所以可以对矩阵Q进行奇异值分解(SVD)在这里插入图片描述
其中,Q是K×9的矩阵;U是K×K的正交阵;Σ是K×9的对角矩阵,对角线的元素是奇异值;VT是9×9的正交阵,每一个列向量对应着Σ中的奇异值。所以,最小二乘解就是VT的第9个列向量,也就是可由向量f=V9构造基础矩阵F。
基础矩阵F还有一个重要的性质,这里可以作为进一步的约束条件。那就是基础矩阵F的秩为2,在这里插入图片描述
上述使用列向量V9构造的基础矩阵的秩通常不为2,需要进一步的优化。在估计基础矩阵时,设其最小奇异值为0,对上面方法取得的基础矩阵进行SVD分解计算机视觉——图像对的特征匹配及估计基础矩阵_第4张图片
其最小特征值v3被设为0,以使得F的秩为2.这样得到
在这里插入图片描述
上述F就是最终得到的基础矩阵。
对上面进行总结,使用最小二乘法估算基础矩阵的步骤如下:
1、对取得两幅图像的匹配点进行归一化处理,转换矩阵分别是H1,H2
2、有对应的匹配点(K≥8)构造系数矩阵Q
3、对矩阵Q进行SVD分解,Q=UΣVT,由向量f=V9构造矩阵F^
4、对得到的矩阵F进行秩为2的约束,即对F进行SVD分解,在这里插入图片描述
令v3=0得到基础矩阵的估计在这里插入图片描述
5、对上一步得到的解进行反归一化处理,得到基础矩阵在这里插入图片描述

随机采样一致性 RANSAC

基于匹配点对估算两视图的基础矩阵,唯一的已知条件就是匹配的点对坐标。在实践中,点对的匹配肯定是存在误差的,主要有两种类型的误差:
1、不精确的测量点位置引起的系统误差,通常服从高斯分布
2、错误匹配引起的误差,这些不匹配的点被称为外点,通常不服从高斯分布
对于基础矩阵的估算,不匹配的点能够造成很大的误差,即使是只有一对错误的匹配都能使估算值极大的偏离真实值。因此,需要找到一种方法,从包含错误点(外点)的匹配点对集合中,筛选出正确的匹配点(内点)。
RANSAC(Random Sample Consensus)随机采样一致性从一组含有外点的数据集中,通过迭代的方式估计出符合该数据集的数学模型的参数。因此,它也可以用来检测出数据集中的外点。
RANSAC有两个基本的假设:
1、数据集中包含的内点,内点的分布符合一个数学模型;而数据集中的外点不复合该数学模型
2、能够一组内点(通常很少)集合能够估计出其符合的数据模型
RANSAC的具体思想是:给定N个数据点组成的集合P,假设集合中大多数的点都是可以通过一个模型来产生的,且最少通过n个点可以拟合出模型的参数,则可以通过以下的迭代方式拟合该参数:
(1)从集合P中随机选择n个点
(2)使用这n个点估计出一个模型M
(3)对P中剩余的点,计算每个点与模型M的距离,距离超过阈值则认为是外点;不超过阈值则认为是内点,并记录该模型M所对应的内点的个数mi

将上面步骤重复k次,选择最大mi所对应的模型M作为最终结果。
在迭代次数k的选择是一个关键,可以通过理论的方式计算出k的取值。在选择n个点估计模型时,要保证选择的n的个点都是内点的概率足够的高。假设,从数据集中选择一个点为内点的概率为ϖ,则选择的n个点都是内点的概率为ϖn;则1−ϖn表示选择的n个点至少有一个是外点,用包含外点估算的模型显然是不正确的,则迭代k次均得不到正确模型的概率为(1−ϖn)k。设p表示k次迭代中至少有一次选择的点都是内点的概率,也就是估计出了正确的模型,则1−p就表示k次跌点都得到正确的模型,所以有在这里插入图片描述
两边同时取对数,则有在这里插入图片描述
一般要求p大于95%即可

使用RANSAC估算基础矩阵时,首先需要确定判断点是内点还是外点的依据。通过上一篇的两视图的对极几何可知,像点总是在对极线,因此可以选择像点到对极线的距离作为判断该点是内点还是外点的依据,设点到对极的距离的阈值为d。则使用RANSAC的方法估算基础矩阵的步骤:

(1)从匹配的点对中选择8个点,使用8点法估算出基础矩阵Fi
(2)计算其余的点对到其对应对极线的距离dn,如果dn≤d则该点为内点,否则 为外点。记下符合该条件的内点的个数为mi
(3)迭代k次,或者某次得到内点的数目mi占有的比例大于等于95%,则停止。选择mi最大的基础矩阵作为最终的结果。

RANSAC能够不依赖于任何额外信息的情况下,将数据划分为内点和外点。但也有其相应的缺点,RANSAC并不能保证得到正确的结果,需要提高迭代的次数;另一个是,内点外点的判断需要设置阈值。

实现代码

# -*- coding: utf-8 -*-
"""
Created on Wed Apr 17 15:19:28 2019

@author: Lenovo
"""

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


# Read features
# 载入图像,并计算特征
im1 = array(Image.open('E:/pycv/images/ch5/1.jpg'))
sift.process_image('E:/pycv/images/ch5/1.jpg', 'im1.sift')
l1, d1 = sift.read_features_from_file('im1.sift')

im2 = array(Image.open('E:/pycv/images/ch5/2.jpg'))
sift.process_image('E:/pycv/images/ch5/2.jpg', 'im2.sift')
l2, d2 = sift.read_features_from_file('im2.sift')

# 匹配特征
matches = sift.match_twosided(d1, d2)
ndx = matches.nonzero()[0]

# 使用齐次坐标表示,并使用 inv(K) 归一化
x1 = homography.make_homog(l1[ndx, :2].T)
ndx2 = [int(matches[i]) for i in ndx]
x2 = homography.make_homog(l2[ndx2, :2].T)

x1n = x1.copy()
x2n = x2.copy()
print(len(ndx))

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

# Don't use K1, and K2

#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.geometry import ransac
    data = np.vstack((x1, x2))
    d = 20 # 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 E through RANSAC
# 使用 RANSAC 方法估计 E
model = sfm.RansacModel()
F, inliers = F_from_ransac(x1n, x2n, model, maxiter=5000, match_threshold=1e-4)

print(len(x1n[0]))
print(len(inliers))

# 计算照相机矩阵(P2 是 4 个解的列表)
P1 = array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]])
P2 = sfm.compute_P_from_fundamental(F)

# 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()
imshow(im1)
gray()
plot(x1p[0], x1p[1], 'o')
#plot(x1[0], x1[1], 'r.')
axis('off')

figure()
imshow(im2)
gray()
plot(x2p[0], x2p[1], 'o')
#plot(x2[0], x2[1], 'r.')
axis('off')
show()

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

imshow(im3)

cols1 = im1.shape[1]
rows1 = im1.shape[0]
for i in range(len(x1p[0])):
    if (0<= x1p[0][i]0: #plot([locs1[i][0],locs2[m][0]+cols1],[locs1[i][1],locs2[m][1]],'c')
        x1=int(l1[i][0])
        y1=int(l1[i][1])
        x2=int(l2[int(m)][0])
        y2=int(l2[int(m)][1])
        # p1 = array([l1[i][0], l1[i][1], 1])
        # p2 = array([l2[m][0], l2[m][1], 1])
        p1 = array([x1, y1, 1])
        p2 = array([x2, y2, 1])
        # Use Sampson distance as error
        Fx1 = dot(F, p1)
        Fx2 = dot(F, p2)
        denom = Fx1[0]**2 + Fx1[1]**2 + Fx2[0]**2 + Fx2[1]**2
        e = (dot(p1.T, dot(F, p2)))**2 / denom
        x1e.append([p1[0], p1[1]])
        x2e.append([p2[0], p2[1]])
        ers.append(e)
x1e = array(x1e)
x2e = array(x2e)
ers = array(ers)

indices = np.argsort(ers)
x1s = x1e[indices]
x2s = x2e[indices]
ers = ers[indices]
x1s = x1s[:20]
x2s = x2s[:20]

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

imshow(im3)

cols1 = im1.shape[1]
rows1 = im1.shape[0]
for i in range(len(x1s)):
    if (0<= x1s[i][0]

运行后一直出现下面这个错误:
在这里插入图片描述
这是因为 .sift文件没有正确生成,导致读入有问题。

制作一个包含三幅或更多图像的数据集。挑选一对图像,计算三维点和照相机矩阵,匹配特征剩下的图像中以获得对应。然后利用这些对应的三维点使用后方交汇法,计算其他图像的照相机矩阵,绘制这些三维点和照相机的位置。
代码实现:

#!/usr/bin/env python
# coding: utf-8

# In[1]:


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


# In[2]:


import camera
#import homography
from PCV.geometry import homography, camera
import sfm
import sift
import imp
camera = imp.reload(camera)
homography = imp.reload(homography)
sfm = imp.reload(sfm)
sift = imp.reload(sift)


# In[3]:


# Read features
im1 = array(Image.open('E:/pycv/images/ch5/5.jpg'))
sift.process_image('E:/pycv/images/ch5/5.jpg', 'im1.sift')

im2 = array(Image.open('E:/pycv/images/ch5/6.jpg'))
sift.process_image('E:/pycv/images/ch5/6.jpg', 'im2.sift')


# In[4]:


l1, d1 = sift.read_features_from_file('im1.sift')
l2, d2 = sift.read_features_from_file('im2.sift')


# In[5]:


matches = sift.match_twosided(d1, d2)


# In[6]:


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


# In[7]:


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


# In[26]:


#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-3):
    """ 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. """

    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']


# In[27]:


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


# In[28]:


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


# In[29]:


print (P2)
print (F)


# In[30]:


# P2, F (1e-4, d=20)
# [[ -1.48067422e+00   1.14802177e+01   5.62878044e+02   4.74418238e+03]
#  [  1.24802182e+01  -9.67640761e+01  -4.74418113e+03   5.62856097e+02]
#  [  2.16588305e-02   3.69220292e-03  -1.04831621e+02   1.00000000e+00]]
# [[ -1.14890281e-07   4.55171451e-06  -2.63063628e-03]
#  [ -1.26569570e-06   6.28095242e-07   2.03963649e-02]
#  [  1.25746499e-03  -2.19476910e-02   1.00000000e+00]]


# In[31]:


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


# In[32]:


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


# In[33]:


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]

你可能感兴趣的:(计算机视觉——图像对的特征匹配及估计基础矩阵)