OpenCV-Python教程:48.核面几何

基础概念

当我们使用针孔摄像机拍摄图像时,我们会丢失重要信息,图像的深度。或者图像每个点离摄像机有多远,因为这是一个3D到2D的转换。所以我们是否能找到深度信息就变得很重要。回答是使用多个摄像机。我们的眼睛也是类似的方法,我们使用两个摄像机(双眼),这被叫做立体影像。所以我们看看OpenCV提供了什么。

在更进一步之前,我们先明白一些多视几何的基础概念。在本节里,我们会用核面几何处理。看下面的图像显示了两个摄像机对同一个场景拍摄图像的基本设置。

如果我们只用左边的摄像机,我们没法找到点x的对应3D点。因为线OX上的每一个点在图像平面都投影到同一个点。但是如果加上右边的摄像机,现在OX线上的不同点在右边的平面投影到了不同的点(x')。所以用两个图像,我们可以三角测量正确的3D点。这就是所有想法。

在右边平面上不同点投影出来的直线(l')。我们叫做点x对应的极线。它表示,要找到右边图像里的点x,沿着这根直线找就行。它应该在这条线上的某出(这么想,要找到在另一张图上的对应点,你不需要搜索整张图片,只需要在这根直线上找就行。所以它提供了更好的性能和准确性)。这被叫做极限约束。类似的所有的点都会在另一张图上有对应的极线。平面XOO'背景叫做偏斜面。

O和O'是摄像机中心,从上面可以看到右边摄像机的O'的投影可以在左边图片看到,e点。这被叫做极点。极点是通过两个摄像机中心的直线与图片平面的焦点。类似的,e'是左边摄像机的极点。在某些情况下,你没法在图像内定位极点,他们可能在图像外(这表示摄像机没法看见彼此)。

所有的极线都通过极点。所以要找到极点的位置,我们可以找多条极线,然后找他们的交点。

我们要找到极线和极点,需要两个东西,基础矩阵(F)和本质矩阵(E)。本质矩阵包含平移和旋转的信息,描述了第二个摄像机相对于第一个在全局坐标系里的位置。看下图:

基础矩阵包含了和本质矩阵一样的信息,另外还有两个摄像机内联信息,这样我们可以把两个摄像机在像素坐标系内关联起来。(如果我们使用修正过的图像并按焦距把点分开正规化了,F=E)。简单的说,基础矩阵F把一个图像里的点映射到另一个图里的线(极线)。这是通过两个图像里的匹配点计算的。最少需要8个点来得到基础矩阵(使用8点算法),最好能有多个点来使用RANSAC来得到更健壮得到结果。

编码:

所以我们需要找到两张图像里尽可能多的匹配来找基础矩阵。为了这个,我们使用基于FLANN匹配子的SIFT描述子和比率测试

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

img1 = cv2.imread('myleft.jpg',0)  #queryimage # left image
img2 = cv2.imread('myright.jpg',0) #trainimage # right image

sift = cv2.SIFT()

# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)

# FLANN parameters
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)

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 = cv2.findFundamentalMat(pts1,pts2,cv2.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 = 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

lines1 = cv2.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 = 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.show()

下面是我们得到的结果

你可以看到在左边的图像里所有的极线汇聚在图像外的点。那个汇聚点就是极点。

要得到更好的结果,应该用有好的分辨率的图像,有很多非平面的点

推荐阅读更多精彩内容

  • 转换 OpenCV提供了两个转换函数,cv2.warpAffine和cv2.warpPerspective,通过他...
    xxxss阅读 7,096评论 1 52
  • Brute-Force匹配器基础 Brute-Force匹配器很简单,它取第一个集合里一个特征的描述子并用第二个集...
    xxxss阅读 24,799评论 1 55
  • 基础 今天的针孔摄像头对图像做了很多扭曲,两个主要的扭曲是径向畸变和切向畸变。 由于径向畸变,直线会显示成曲线,当...
    xxxss阅读 11,853评论 0 52
  • 理论 傅里叶变换用来分析多种过滤器的频率特征。对于图片,2D离散傅里叶变换(DFT)用来找频率范围。一个快速算法叫...
    xxxss阅读 5,289评论 0 52
  • 图文/by Holly° 这一天即将十八岁的我,朋友圈选择了沉默,因为我在等,等一个不用你发圈提醒却仍然记...
    Holly丫阅读 385评论 0 2