Python OpenCV:极线几何
对极几何的一般设置。灰色区域是核平面。橙色线是基线,而两条蓝色线是极线。
通常在多视图几何中,多个相机、一个 3D 点以及该点在每个相机图像平面中的投影之间存在有趣的关系。将相机、3D 中的点和相应的观察相关联的几何称为立体对的对极几何。
标准的对极几何设置涉及两个摄像机观察相同的 3D 点 P,其在每个图像平面中的投影分别位于 p 和 p'。摄像机中心位于 O1 和 O2,它们之间的线称为基线。我们称由两个相机中心和 P 定义的平面为对极平面。基线与两个图像平面相交的位置称为极点 e 和 e'。最后,由核平面和两个图像平面的交点定义的线称为核线。核线具有它们在图像平面中的各个核处与基线相交的特性。
当两个像平面平行时,极点 e 和 e' 位于无穷远处。请注意,核线平行于每个图像平面的 u 轴。
图 4 显示了一个有趣的对极几何案例,它发生在图像平面彼此平行时。当图像平面彼此平行时,极点 e 和 e' 将位于无穷远处,因为连接中心 O1、O2 的基线平行于图像平面。这种情况的另一个重要副产品是核线平行于每个图像平面的轴。这种情况特别有用,将在随后的图像校正部分中更详细地介绍。
然而,在现实世界的情况下,我们没有得到 3D 位置 P 的确切位置,但可以确定其在图像平面 p 之一中的投影。我们还应该能够知道相机的位置、方向和相机矩阵。我们可以用这些知识做什么?借助相机位置 O1、O2 和图像点 p 的知识,我们可以定义对极平面。有了这个核平面,我们就可以确定核线1。根据定义,P 在第二幅图像中的投影 p0 必须位于第二幅图像的核线上。因此,对极线几何的基本了解使我们能够在图像对之间创建强约束,而无需了解场景的 3D 结构。
用于确定基本矩阵和基本矩阵的设置,有助于跨视图绘制点和极线。
我们现在将尝试开发无缝的方法来绘制跨视图的地图点和极线。如果我们采用原始对极几何框架中给出的设置(图 5),那么我们将进一步将 M 和 M0 定义为将 3D 点映射到它们各自的 2D 图像平面位置的相机投影矩阵。让我们假设世界参考系统通过旋转 R 和平移 T 与第一个相机和第二个相机 oset 相关联。这个种类的相机投影矩阵是:
M = K[I 0] M' = K'[R T]
现在我们找到基本矩阵 (F)和基本矩阵 (E) 。基本矩阵包含有关平移和旋转的信息,它描述了第二个摄像机在全局坐标中相对于第一个摄像机的位置。
Fundamental Matrix 包含与 Essential Matrix 等效的信息,此外还包含有关两个相机的内在特性的知识,以便我们将 2 个相机与像素坐标相关联。 (如果我们使用校正后的图像并通过除以焦距来归一化该点,F=E)。简而言之,基本矩阵 F 将一幅图像中的某种程度映射到另一幅图像中的一条线(epiline)。这是根据两张图片的匹配点计算得出的。至少需要 8 个这样的点来寻找元素矩阵(使用 8 点算法时)。更多的点是首选,并使用 RANSAC 来敦促更稳健的结果。
所以首先我们需要在两幅图像之间找到尽可能多的匹配来找到基本矩阵。为此,我们使用 SIFT 描述符和基于 FLANN 的匹配器和比率测试。
import numpy as np
import cv2
from matplotlib import pyplot as plt
# Load the left and right images
# in gray scale
imgLeft = cv2.imread('image_l.png',
0)
imgRight = cv2.imread('image_r.png',
0)
# Detect the SIFT key points and
# compute the descriptors for the
# two images
sift = cv2.xfeatures2d.SIFT_create()
keyPointsLeft, descriptorsLeft = sift.detectAndCompute(imgLeft,
None)
keyPointsRight, descriptorsRight = sift.detectAndCompute(imgRight,
None)
# Create FLANN matcher object
FLANN_INDEX_KDTREE = 0
indexParams = dict(algorithm=FLANN_INDEX_KDTREE,
trees=5)
searchParams = dict(checks=50)
flann = cv2.FlannBasedMatcher(indexParams,
searchParams)
# Apply ratio test
goodMatches = []
ptsLeft = []
ptsRight = []
for m, n in matches:
if m.distance < 0.8 * n.distance:
goodMatches.append([m])
ptsLeft.append(keyPointsLeft[m.trainIdx].pt)
ptsRight.append(keyPointsRight[n.trainIdx].pt)
左图
右图
让我们注意基本的矩阵。
ptsLeft = np.int32(ptsLeft)
ptsRight = np.int32(ptsRight)
F, mask = cv2.findFundamentalMat(ptsLeft,
ptsRight,
cv2.FM_LMEDS)
# We select only inlier points
ptsLeft = ptsLeft[mask.ravel() == 1]
ptsRight = ptsRight[mask.ravel() == 1]
接下来,我们找到外延线。在第二幅图像上绘制与第一幅图像中的点相对应的线。所以在这里提到正确的图像很重要。我们得到一系列行。所以我们定义了一个新函数来在图像上绘制这些线条。
def drawlines(img1, img2, lines, pts1, pts2):
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
现在我们在两个图像中找到外延线并绘制它们。
# Find epilines corresponding to points
# in right image (second image) and
# drawing its lines on left image
linesLeft = cv2.computeCorrespondEpilines(ptsRight.reshape(-1,
1,
2),
2, F)
linesLeft = linesLeft.reshape(-1, 3)
img5, img6 = drawlines(imgLeft, imgRight,
linesLeft, ptsLeft,
ptsRight)
# Find epilines corresponding to
# points in left image (first image) and
# drawing its lines on right image
linesRight = cv2.computeCorrespondEpilines(ptsLeft.reshape(-1, 1, 2),
1, F)
linesRight = linesRight.reshape(-1, 3)
img3, img4 = drawlines(imgRight, imgLeft,
linesRight, ptsRight,
ptsLeft)
plt.subplot(121), plt.imshow(img5)
plt.subplot(122), plt.imshow(img3)
plt.show()
输出: