OpenCV-Python——第37章:对极几何(Epipolar Geometry)

目录

1 基本概念

2 代码


1 基本概念

在我们使用针孔相机时,我们会丢失大量重要的信息,比如说图像的深度,或者说图像上的点和摄像机的距离,因这是一个从 3D 到 2D 的转换。因此一 个重要的问题就产生了,使用这样的摄像机我们能否计算除深度信息呢?答案 就是使用多个相机。我们的眼睛就是这样工作的,使用两个摄像机(两个眼睛), 这被称为立体视觉。我们来看看 OpenCV 在这方面给我们都提供了什么吧。

(Gary Bradsky《Learning OpenCV》一书有大量相关知识。)

在进入深度图像之前,我们要先掌握一些多视角几何的基本概念。在本节中我们要处理对极几何。下图为使用两台摄像机同时对一个一个场景进行拍摄的示意图。

OpenCV-Python——第37章:对极几何(Epipolar Geometry)_第1张图片

 如果只是用一台摄像机我们不可能知道 3D 空间中的 X 点到图像平面的距离,因为 OX 连线上的每个点投影到图像平面上的点都是相同的。但是如果我 们也考虑上右侧图像的话,直线 OX 上的点将投影到右侧图像上的不同位置。 所以根据这两幅图像,我们就可以使用三角测量计算出 3D 空间中的点到摄像 机的距离(深度)。这就是整个思路。

直线 OX 上的不同点投射到右侧图像上形成的线 l′ 被称为与 x 点对应的极线。也就是说,我们可以在右侧图像中沿着这条极线找到 x 点。它可能在这条 直线上某个位置(这意味着对两幅图像间匹配特征的二维搜索就转变成了沿着极线的一维搜索。这不仅节省了大量的计算,还允许我们排除许多导致虚假匹配的点)。这被称为对极约束。与此相同,所有的点在其他图像中都有与之对应 的极线。平面 XOO' 被称为对极平面。

O 和 O' 是摄像机的中心。从上面的示意图可以看出,右侧摄像机的中心O' 投影到左侧图像平面的 e 点,这个点就被称为极点。极点就是摄像机中心连 线与图像平面的交点。因此点 e' 是左侧摄像机的极点。有些情况下,我们可能 不会在图像中找到极点,它们可能落在了图像之外(这说明这两个摄像机不能拍摄到彼此)。

所有的极线都要经过极点。所以为了找到极点的位置,我们可以先找到多条极线,这些极线的交点就是极点。 本节我们的重点就是找到极线和极点。为了找到它们,我们还需要两个元素,本征矩阵(E)和基础矩阵(F)。本征矩阵包含了物理空间中两个摄像机 相关的旋转和平移信息。如下图所示(本图来源自:Learning OpenCV)

OpenCV-Python——第37章:对极几何(Epipolar Geometry)_第2张图片

基础矩阵 F 除了包含 E 的信息外还包含了两个摄像机的内参数。由于 F 包含了这些内参数,因此它可以它在像素坐标系将两台摄像机关联起来。(如果 使用是校正之后的图像并通过除以焦距进行了归一化,F=E)。简单来说,基 础矩阵 F 将一副图像中的点映射到另一幅图像中的线(极线)上。这是通过匹 配两幅图像上的点来实现的。要计算基础矩阵至少需要 8 个点(使用 8 点算 法)。点越多越好,可以使用 RANSAC 算法得到更加稳定的结果。

 

2 代码

为了得到基础矩阵我们应该在两幅图像中找到尽量多的匹配点。我们可以使用 SIFT 描述符,FLANN 匹配器和比值检测。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt


img1 = cv.imread('myleft.jpg', 0)  # queryimage # left image
img2 = cv.imread('myright.jpg', 0)  # trainimage # right image
sift = cv.xfeatures2d.SIFT_create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
flann = cv.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
good = []
pts1 = []
pts2 = []
# ratio test as per Lowe's paper
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)

现在得到了一个匹配点列表,我们就可以使用它来计算基础矩阵了。

retval, mask=cv.findFundamentalMat(points1, points2, method, ransacReprojThreshold, confidence, mask)

  • points1:从第一张图片开始的N个点的数组。点坐标应该是浮点数(单精度或双精度)。
  • points2:与点1大小和格式相同的第二图像点的数组。
  • method:计算基本矩阵的方法。
  • cv2.FM_7POINT for a 7-point algorithm. N=7
  • cv2.FM_8POINT for an 8-point algorithm. N≥8
  • cv2.FM_RANSAC (默认) for the RANSAC algorithm. N≥8
  • cv2.FM_LMEDS for the LMedS algorithm. N≥8
  • ransacReprojThreshold:仅用于RANSAC方法的参数,默认3。它是一个点到极线的最大距离(以像素为单位),超过这个点就被认为是一个离群点,不用于计算最终的基本矩阵。根据点定位、图像分辨率和图像噪声的准确性,可以将其设置为1-3左右。
  • confidence:仅用于RANSAC和LMedS方法的参数,默认0.99。它指定了一个理想的置信水平(概率),即估计矩阵是正确的。
  • mask:输出,极外几何描述如下

                                                                                    [p_2; 1]^T F [p_1; 1] = 0

pts1 = np.int32(pts1)
pts2 = np.int32(pts2)
F, mask = cv.findFundamentalMat(pts1, pts2, cv.FM_LMEDS)
# We select only inlier points
pts1 = pts1[mask.ravel() == 1]
pts2 = pts2[mask.ravel() == 1]

下一步我们要找到极线。我们会得到一个包含很多线的数组。所以我们要 定义一个新的函数将这些线绘制到图像中。

def drawlines(img1, img2, lines, pts1, pts2):
    ''' img1 - image on which we draw the epilines for the points in img2
        lines - corresponding epilines '''
    r, c = img1.shape
    img1 = cv.cvtColor(img1, cv.COLOR_GRAY2BGR)
    img2 = cv.cvtColor(img2, cv.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 = cv.line(img1, (x0, y0), (x1, y1), color, 1)
        img1 = cv.circle(img1, tuple(pt1), 5, color, -1)
        img2 = cv.circle(img2, tuple(pt2), 5, color, -1)
    return img1, img2

现在我们两幅图像中计算并绘制极线。

 

lines = cv.computeCorrespondEpilines(points, whichImage, F, lines)

  • points:输入点。类型为CV_32FC2N×1或1×N矩阵。
  • whichImage:包含点的图像(1或2)的索引。
  • F:基本矩阵,可使用findFundamentalMat或stereoRectify 进行估计。
  • lines:对应于另一幅图像中点的极线的输出向量(a,b,c)表示直线ax+by+c=0。
# Find epilines corresponding to points in right image (second image) and
# drawing its lines on left image
lines1 = cv.computeCorrespondEpilines(pts2.reshape(-1, 1, 2), 2, F)
lines1 = lines1.reshape(-1, 3)
img5, img6 = drawlines(img1, img2, lines1, pts1, pts2)
# Find epilines corresponding to points in left image (first image) and
# drawing its lines on right image
lines2 = cv.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.show()

整个程序:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt


def drawlines(img1, img2, lines, pts1, pts2):
    ''' img1 - image on which we draw the epilines for the points in img2
        lines - corresponding epilines '''
    r, c = img1.shape
    img1 = cv.cvtColor(img1, cv.COLOR_GRAY2BGR)
    img2 = cv.cvtColor(img2, cv.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 = cv.line(img1, (x0, y0), (x1, y1), color, 1)
        img1 = cv.circle(img1, tuple(pt1), 5, color, -1)
        img2 = cv.circle(img2, tuple(pt2), 5, color, -1)
    return img1, img2


img1 = cv.imread('myleft.jpg', 0)  # queryimage # left image
img2 = cv.imread('myright.jpg', 0)  # trainimage # right image
sift = cv.xfeatures2d.SIFT_create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
flann = cv.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
good = []
pts1 = []
pts2 = []
# ratio test as per Lowe's paper
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)

pts1 = np.int32(pts1)
pts2 = np.int32(pts2)
F, mask = cv.findFundamentalMat(pts1, pts2, cv.FM_LMEDS)
# We select only inlier points
pts1 = pts1[mask.ravel() == 1]
pts2 = pts2[mask.ravel() == 1]

# Find epilines corresponding to points in right image (second image) and
# drawing its lines on left image
lines1 = cv.computeCorrespondEpilines(pts2.reshape(-1, 1, 2), 2, F)
lines1 = lines1.reshape(-1, 3)
img5, img6 = drawlines(img1, img2, lines1, pts1, pts2)
# Find epilines corresponding to points in left image (first image) and
# drawing its lines on right image
lines2 = cv.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.show()

结果如下,我用手机拍的,效果果然很差(・ω・`ll)

OpenCV-Python——第37章:对极几何(Epipolar Geometry)_第3张图片

OpenCV-Python——第37章:对极几何(Epipolar Geometry)_第4张图片

你可能感兴趣的:(—OpenCV,opencv,对极几何,python,双目相机)