【OpenCV-Python】教程:6-3 Epipolar Geometry 对极几何

OpenCV Python Epipolar Geometry 对极几何

【目标】

  • 学习多视图几何
  • 学习极点、对极线、对极约束等等;

【理论】

当我们使用针孔相机拍摄图像时,我们会丢失一个重要的信息,即图像的深度。或者图像中的每个点距离摄像机有多远,因为这是一个3d到2d的转换。因此,我们能否利用这些相机找到深度信息是一个重要的问题。答案是使用多个相机。我们眼睛的工作原理类似于我们使用两个摄像头(两只眼睛),这被称为立体视觉。那么让我们来看看OpenCV在这个领域提供了什么。

在讨论深度图像之前,让我们先了解多视图几何中的一些基本概念。在这一节中,我们将讨论极面几何。请看下面的图片,它显示了两个相机拍摄同一场景的基本设置。

【OpenCV-Python】教程:6-3 Epipolar Geometry 对极几何_第1张图片

如果我们只使用左侧摄像机,我们无法在图像中找到点 x x x对应的3D点,因为直线 O X OX OX上的每个点都投影到图像平面上的同一点。但也要考虑正确的图像。现在直线 O X OX OX上的不同点投影到右平面上的不同点 x ′ x' x。有了这两张图像,我们就能三角测量出正确的三维点。这就是整个想法。

各点在 O X OX OX上的投影在右平面上形成一条直线(直线 l ’ l’ l)。我们称它为对应于点 x x x对极线,意思是,要找到右边图像上的点x,就沿着这个对极线搜索。它应该在这条线上的某个地方(这样想,要在其他图像中找到匹配点,你不需要搜索整个图像,只需要沿着对极线搜索。因此它提供了更好的性能和准确性)。这被称为外极约束。同样地,在另一张图中,所有点都有相应的对极线。平面 X O O ′ XOO' XOO被称为对极平面

O O O O ′ O' O是相机中心。从上面给出的设置中,你可以看到右边相机 O ′ O' O的投影在左边图像的点 e e e上,它被称为极点。极点是相机中心与图像平面的交点。同理, e ′ e' e是左边相机的极点。在某些情况下,你将无法在图像中定位极点,它们可能在图像之外(这意味着,一个相机看不到另一个)。

所有的极线都经过它的极点。因此,为了找到极点的位置,我们可以找到许多的极线并找到它们的交点。

在这节中,我们将重点寻找偶极线和偶极。但要找到它们,我们还需要两个要素,基本矩阵( F F F)和本质矩阵( E E E)。本质矩阵包含关于平移和旋转的信息,它们描述了第二个摄像机相对于第一个摄像机在全局坐标中的位置。

【OpenCV-Python】教程:6-3 Epipolar Geometry 对极几何_第2张图片

但是我们更喜欢用像素坐标进行测量,对吧?基本矩阵除了包含与本质矩阵相同的信息外,还包含了两个相机的内在信息,因此我们可以将两个相机在像素坐标上联系起来。(如果我们使用校正图像,并通过除以焦距归一化点,F=E)。简单地说,基本矩阵 F F F将一个图像中的点映射到另一个图像中的一条线(极线)。这是根据两幅图像中的匹配点计算出来的。至少需要8个这样的点来找到基本矩阵(使用8点算法)。更多的点更好,然后使用RANSAC得到更鲁棒的结果。

【代码】

【OpenCV-Python】教程:6-3 Epipolar Geometry 对极几何_第3张图片

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

# 读入图片
img1 = cv2.imread("assets/left.jpg", 0)
img2 = cv2.imread("assets/right.jpg", 0)

# 定义SIFT算子
sift = cv2.SIFT_create()

# 找到特征点和计算特征
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)

# FLANN 参数
FLANN_INDEX_KDTREE = 0
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)

# 选择匹配点中匹配度比较好的点
pts1 = []
pts2 = []
for i, (m,n) in enumerate(matches):
    if m.distance < 0.76*n.distance:
        pts2.append(kp2[m.trainIdx].pt)
        pts1.append(kp1[m.queryIdx].pt)

# 类型转换
pts1 = np.int32(pts1)
pts2 = np.int32(pts2)

# 找到基本矩阵
F, mask = cv2.findFundamentalMat(pts1,pts2,cv2.FM_LMEDS)

# 选择内部的点
pts1 = pts1[mask.ravel()==1]
pts2 = pts2[mask.ravel()==1]

## 画极线
def drawepilines(img1, img2, lines, pts1, pts2):
    r,c = img1.shape 
    img1_temp = cv2.cvtColor(img1, cv2.COLOR_GRAY2BGR)
    img2_temp = 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_temp = cv2.line(img1_temp,(x0,y0),(x1,y1),color,1)
        img1_temp = cv2.circle(img1_temp, tuple(pt1), 5, color, -1)
        img2_temp = cv2.circle(img2_temp, tuple(pt2), 5, color, -1)
    return img1_temp, img2_temp


# 根据右图的点找到左图的极线
lines1 = cv2.computeCorrespondEpilines(pts2.reshape(-1,1,2),2,F)
lines1 = lines1.reshape(-1,3)
img5, img6 = drawepilines(img1,img2,lines1,pts1,pts2)

# 根据左图的点找到右图的极线
lines2 = cv2.computeCorrespondEpilines(pts1.reshape(-1,1,2),1,F)
lines2 = lines2.reshape(-1,3)
img3, img4 = drawepilines(img2,img1,lines2,pts2,pts1)

res = np.hstack((img5, img3))
cv2.imshow("res", res)


# res1 = np.hstack((img5,img6))
# res2 = np.hstack((img4,img3))

# cv2.imshow("res1", res1)
# cv2.imshow("res2", res2)


cv2.waitKey(0)
cv2.destroyAllWindows()

NOTE: 这里极线的计算与选择的特征点有非常大的关系,要有足够好的特征来表明是同一个位置。

【接口】

  • findFundamentalMat
cv2.findFundamentalMat(	points1, points2, method, ransacReprojThreshold, confidence, maxIters[, mask]	) ->	retval, mask
cv2.findFundamentalMat(	points1, points2[, method[, ransacReprojThreshold[, confidence[, mask]]]]	) ->	retval, mask
cv2.findFundamentalMat(	points1, points2, params[, mask]	) ->	retval, mask

通过两个图像中对应的点,计算基本矩阵

  • points1: 第一幅图像的 N 个点集合,点的坐标必须是浮点数,单精度或双精度都可以
  • points2: 第二幅图像的 N 个点集合
  • method: 计算基本矩阵的方法
    • FM_7POINT: 7 个点的算法. N=7
    • FM_8POINT: 8 个点的算法. N≥8
    • FM_RANSAC: RANSAC 算法 N≥8
    • FM_LMEDS: LMedS 算法 N≥8
  • ransacReprojThreshold: RANSAC 方法的参数. 它是一个点到极线的最大距离(单位为像素),超过这个距离的点被认为是一个离群值,不用于计算最终的基本矩阵。它可以设置为1-3,这取决于点定位的精度、图像分辨率和图像噪声。
  • confidence: 仅用于RANSAC和lmed方法。它指定了估计矩阵正确的理想置信水平(概率)。
  • [out] mask: 可选的输出 mask
  • maxIters: 最多迭代次数

对极几何是这么描述的:

[ p 2 ; 1 ] T F [ p 1 ; 1 ] = 0 [p_2;1]^TF[p_1;1]=0 [p2;1]TF[p1;1]=0

F F F 是基础矩阵, p 2 p_2 p2 p 1 p_1 p1 分别对应第二张和第一张图像的点;

  • computeCorrespondEpilines
cv2.computeCorrespondEpilines(	points, whichImage, F[, lines]	) ->	lines

通过一个图像中的点找到另一幅图像中的极线

  • points: 输入的点集, N × 1 N×1 N×1 1 × N 1×N 1×N,类型为 CV_32FC2或者 Point2f的vector都可以
  • whichImage: 包含那些点的图像的索引(1或2)
  • F: 基础矩阵,用 findFundamentalMat 或 stereoRectify 计算;
  • lines: 输出的极线, a x + b y + c = 0 ax+by+c=0 ax+by+c=0, 直线方程通过 ( a , b , c ) (a,b,c) (a,b,c)描述。

【参考】

  1. OpenCV: Epipolar Geometry
  2. OpenCV: Camera Calibration and 3D Reconstruction
  3. One important topic is the forward movement of camera. Then epipoles will be seen at the same locations in both with epilines emerging from a fixed point. See this discussion.
  4. Fundamental Matrix estimation is sensitive to quality of matches, outliers etc. It becomes worse when all selected matches lie on the same plane. Check this discussion.
  5. 对极几何原理_阿升1990的博客-CSDN博客_对极几何
  6. 对极几何与基本矩阵 - 知乎 (zhihu.com)
  7. 从零开始一起学习SLAM | 不推公式,如何真正理解对极约束?_计算机视觉life的博客-CSDN博客_从零开始一起学习slam | 不推公式,如何真正理解对极约束?

你可能感兴趣的:(#,OpenCV-Python,教程,opencv,python,计算机视觉,对极几何)