opencv的极线几何

一、理论介绍

当我们使用针孔相机拍摄图像时,我们会丢失一个重要的信息,即图像的深度。一个解决方案如我们的眼睛的方式使用两个相机(两只眼睛),这就是所谓的立体视觉。 

opencv的极线几何_第1张图片

PO1O2为极平面,l1和l2为极线,e1和e2为极点。

opencv的极线几何_第2张图片

原因:左边的摄像机,我们无法找到与图像中的点x相对应的3D点,因为线OX上的每个点都投影到图像平面上的相同点(没有一一对应),即图O中的一个点因为深度不确定。但是考虑右边的摄像机得到的图像。现在OX线上的不同点投射到右侧的不同点(xx')。 所以对于这两幅图像,我们可以对正确的三维点进行三角测量。

极线以及极线约束:OX上不同点的投影在右平面上形成一条线(I')。 我们称之为对应于点x的极线。 这意味着,要找到右侧图像上的点x,只需沿着这个极线搜索。它应该在这一条线上的某个地方(可以不用全图找,只需沿着极线搜索,增加效率准确性),这被称为极线约束,同样,所有的点在其他图像中都会有相应的极线。

极点:O和O'是相机中心,看到右侧相机O' 的投影在左侧图像上的点e处出现。 它被称为极点。 极点是通过相机中心和图像平面的交叉点。 类似地,e' 是左侧相机的圆心。在某些情况下,您将无法找到图像中的圆点,它们可能在图像之外(也就是说,一个相机看不到另一个)。

所有的极线都穿过它的极点。所以要找到极点的位置,我们可以找到许多极线并找到它们的交点。

Fundamental矩阵(F)和Essential矩阵:寻找极线和极点。但要找到它们,我们还需要两个东西:Fundamental矩阵(F)和Essential矩阵(E)。Essential矩阵包含有关平移和旋转的信息,这些信息描述了第二个摄像机相对于全局坐标中第一个摄像机的位置。图如下所示:

opencv的极线几何_第3张图片

我们更喜欢在像素坐标系中进行测量Fundamental矩阵包含与Essential矩阵相同的信息,另外再加上两个相机的内在信息,以便我们可以将两个相机的像素坐标关联起来。简而言之,Fundamental矩阵F将一个图像中的一个点映射到另一个图像中的一条线(极线)。

就是左视图上一点x1到右视图上极线L2的关系,如下公式: L2=Fx1

对极原理来说,左视图上一点x1在右视图上的匹配点 x2 一定在极线L2上,也就是:x2TL2= x2TFx1=0

F从两个图像的匹配点计算出来的。找到基本矩阵(使用8点算法时)至少需要8个这样的点。

二、代码实现步骤如下:

一、找到两个图像之间尽可能多的匹配点来找到Fundamental矩阵,用SIFT来进行特征提取,然后用FLANN进行匹配并筛选好的匹配点。

先讲解一下,有关以下代码的一些知识点以及函数:

1、关键点和描述子:特征点包含了关键点和描述子,关键点指的特征点在图像中的位置,而描述子是指的是关键点的朝向和周围像素信息。相同特征点他们的描述子相似。

2、FlannBasedMatcher():从字面意思可知它是一种近似法,算法更快但是找到的是最近邻近似匹配,所以当我们需要找到一个相对好的匹配但是不需要最佳匹配的时候往往使用FlannBasedMatcher。当然也可以通过调整FlannBasedMatcher的参数来提高匹配的精度或者提高算法速度,但是相应地算法速度或者算法精度会受到影响。

参数:

index_params:采用的什么算法(值为1就是计算机自行采用),随机kd树,平行搜索。默认trees=4。

search_params:迭代的次数

3、knnMatch()

参数:描述子1,描述子2,查找最优的几个匹配点

返回值参数:匹配点,DMatch数据类型

4、DMatch数据类型:

queryIdx query描述子的索引,即特征点在训练图像中检测出的特征点集中的下标号

trainIdx train描述子的索引,即特征点在匹配图像中检测出的特征点集中的下标号

imgIdx  进行匹配图像的索引,如已知一幅图像的sift描述子,与其他十幅图像的描述子进行匹配,找最相似的图像,则imgIdx此时就有用了。      
distance 对应特征点之间的欧氏距离,越小表明匹配度越高

5、KeyPoint 数据结构:

angle 关键点的方向,值为0~360,负值表示不使用。如SIFT算法中为了保证方向不变形,
          通过对关键点周围邻域进行梯度运算,求得该点方向。(初值为-1)
         
octave 表示的是关键点所在的图像金字塔的层组

pt 关键点的坐标。pt.x为横坐标,pt.y为纵坐标。

reponse 响应程度,代表了该点是特征点的稳健度,可以用于后续处理中特征点排序

class_id 用于聚类的id,即当要对图片进行分类时,我们可以用class_id对每个关键点进行区分,默认为-1,也可自己设定
            
size 关键点邻域直径  

import cv2
import numpy as np
from matplotlib import pyplot as plt
 
img1 = cv2.imread('left.png', 0)  # queryimage # 左侧图片
img2 = cv2.imread('right.png', 0)  # trainimage # 右侧图片
 
sift = cv2.xfeatures2d.SIFT_create()
 
# 用SIFT查找关键点和描述子
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
 
# FLANN参数
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
 
flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1, des2, k=2)
 
good = []
pts1 = []
pts2 = []
 
# 按照Lowe的论文进行比率测试
for i, (m, n) in enumerate(matches):
    if m.distance < 0.8*n.distance:
        good.append(m)
        pts2.append(kp2[m.trainIdx].pt)
        pts1.append(kp1[m.queryIdx].pt)

 二、现在我们有两张图片的最佳匹配点的列表。让我们找到Fundamental矩阵。

FlannBasedMatcher():从两个图像中的对应点计算基本矩阵。

参数:

points1  包含第一张图像的 N 个点的数组(匹配点);
points2  与 points1 具有相同大小和格式的第二个图像点的数组;
method  计算基本矩阵的方法;

输出:

基本矩阵;

mask:输出包含N个元素的数组,其中的每个元素对于异常值都设置为 0,对于其他点设置为1。 该数组仅在 RANSAC 和 LMedS 方法中计算.对于其他方法,它设置为全1;

pts1 = np.int32(pts1)
pts2 = np.int32(pts2)
F, mask = cv2.findFundamentalMat(pts1, pts2, cv2.FM_LMEDS)
 
# 我们只使用inlier点
pts1 = pts1[mask.ravel() == 1]
pts2 = pts2[mask.ravel() == 1]

三、我们定义一个新的函数来在图像上绘制极线,第一图像中的点对应的极线的线条会在第二图像上绘制。

def drawlines(img1, img2, lines, pts1, pts2):
    ''' img1 - 我们要绘制到的图像
        lines - 相应的极线 '''
    r, c = img1.shape
    img1 = cv2.cvtColor(img1, cv2.COLOR_GRAY2BGR)
    img2 = cv2.cvtColor(img2, cv2.COLOR_GRAY2BGR)
    for r, pt1, pt2 in zip(lines, pts1, pts2):
        color = tuple(np.random.randint(0, 255, 3).tolist())
        x0, y0 = map(int, [0, -r[2]/r[1]])
        x1, y1 = map(int, [c, -(r[2]+r[0]*c)/r[1]])
        img1 = cv2.line(img1, (x0, y0), (x1, y1), color, 1)
        img1 = cv2.circle(img1,tuple(pt1), 5, color, -1)
        img2 = cv2.circle(img2,tuple(pt2), 5, color, -1)
    return img1, img2

 四、这两个图像中找到这些极线,调用函数画出来

computeCorrespondEpilines:找出极线的方法

参数:

     points :输入点,类型为CV_32FC2N × 1或1 × N矩阵。

     whichImage :包含点的图像(1或2)的索引。

      F :基本矩阵

     输出:对应于另一幅幅图像中点的极线的输出矢量( a , b , c )表示直线ax + by + c = 0 。 

# 找到右边图像(第二张图像)中的点对应的极线,在左边图像上画出来
lines1 = cv2.computeCorrespondEpilines(pts2.reshape(-1, 1, 2), 2, F)
lines1 = lines1.reshape(-1, 3)
img5, img6 = drawlines(img1, img2, lines1, pts1, pts2)
 
# 找到左边图像(第一张图像)中的点对应的极线
# 在右边图像上画出来
lines2 = cv2.computeCorrespondEpilines(pts1.reshape(-1, 1, 2), 1, F)
lines2 = lines2.reshape(-1, 3)
img3, img4 = drawlines(img2, img1, lines2, pts2, pts1)
 
plt.subplot(121), plt.imshow(img5)
plt.subplot(122), plt.imshow(img3)
plt.savefig('sift_left_right.png')
plt.show()

你可能感兴趣的:(计算机视觉,人工智能)