基礎概念
當我們使用針孔攝像機拍攝圖像時,我們會丟失重要信息,圖像的深度。或者圖像每個點離攝像機有多遠,因為這是一個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 pltimg1 = cv2.imread('myleft.jpg',0)? #queryimage # left image
img2 = cv2.imread('myright.jpg',0) #trainimage # right imagesift = 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 imagelines1 = 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()
下面是我們得到的結果
你可以看到在左邊的圖像里所有的極線匯聚在圖像外的點。那個匯聚點就是極點。
要得到更好的結果,應該用有好的分辨率的圖像,有很多非平面的點