< 7-3. Epipolar Geometry >
이번 장에서는
- 다관점(multiview) 기하학의 기초에 대해 배워보고
- epipole, epipolar 선, epipolar 제약에 대해서 알아볼 것이다.
Basic Concepts
핀홀 카메라를 사용해서 이미지를 얻으면, 이미지의 깊이와 같은 중요한 정보를 잃게된다. 또는 카메라에서부터 이미지의 각 점이 얼마나 멀리 떨어져있는지에 대한 정보도 해당한다. (3D에서 2D로의 변환이기 때문이다.) 그래서 카메라를 사용해서 깊이 정보를 얻어낼 수 있냐는 질문은 매우 중요하다. 그리고 답은 하나 이상의 카메라를 사용하는 것이다. 우리의 눈은 스테레오 비전이라고 불리는 두 개의 카메라를 사용하는 것과 유사하게 작동한다. 이제 OpenCV가 제공하는 것을 봐보자!
이미지의 깊이로 가기 전에, multiview geometry의 개념에 대해서 이해해보자. 이 섹션에서 epipolar geometry와 함께 다뤄볼 것이다. 아래의 이미지는 같은 이미지를 두 개의 다른 카메라가 바라보고있는 것을 보여준다.
만약 왼편의 카메라만 사용한다면,
선분
모든 epiline들은 epipole을 통과한다. 그래서 epipole의 위치를 찾기 위해, 많은 epiline들을 찾고 이들의 교차점을 찾아야 한다.
그래서 이 섹션에서, epipolar lines와 epipoles를 찾는데 집중할 것이다. 하지만 이들을 찾으려면, Fundamental Matrix(F) 와 Essential Matrix(E)를 필요로 한다. Essential Matrix는 translation과 rotation에 대한 정보를 가지고 있다. 이 정보들은 전체적인 좌표에서 첫 번째 카메라와 비교한 두 번째 카메라의 위치를 설명한다. 아래 이미지를 보자
그리고 픽셀 좌표로 측정하는 것을 선호한다. Fundamental Matrix는 Essential Matrix와 같은 정보를 포함하지만, 추가적으로 두 개의 카메라들의 내적 정보들을 가지고 있어서 두 개의 카메라를 픽셀 좌표에서 연관지을 수 있다. (만약 조정된 이미지와 포인트를 focal length로 나누어 정규화하여 사용한다면 F=E이다.)
다시 말하자면, Fundamental Matrix F는 하나의 이미지에서의 포인트를 다른 이미지에서의 선(epiline)으로 매핑한다. 두 이미지로부터 매칭된 점들로 계산이 된다. 8-point 알고리즘을 사용한다면 Fundamental Matrix를 찾기 위해 최소 8개의 점이 필요하다. 포인트는 더 많을수록 좋고 RANSAC을 써서 더 강건한 결과를 얻을 수 있다!
Code
먼저, Fundamental Matrix를 찾기 위해 두 개의 이미지 사이의 가능한 매치들을 많이 찾는다. 이를 실행하기 위해, FLANN 기반의 매쳐와 Ratio test와 함께 SIFT 디스크립터를 사용한다.
import cv2
import numpy as np
import matplotlib.pyplot as plt
# queryImage = left image
img1 = cv2.imread('./images/front_sejong.jpg',0)
img1 = cv2.resize(img1,(400,400))
# trainImage = right image
img2 = cv2.imread('./images/side_sejong.jpg',0)
img2 = cv2.resize(img2,(400,400))
sift = cv2.xfeatures2d.SIFT_create()
# SIFT로 keypoints와 descriptors 찾기
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# FLANN 파라미터
FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE,
tress = 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
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)
이제 두 이미지로부터의 베스트 매치의 리스트를 얻었으니 이제 Fundamental Matrix를 찾아보자!
pts1 = np.int32(pts1)
pts2 = np.int32(pts2)
F, mask = cv2.findFundamentalMat(pts1,pts2,cv2.FM_LMEDS)
# inlier 포인트만 선택한다.
pts1 = pts1[mask.ravel()==1]
pts2 = pts2[mask.ravel()==1]
이제 Epilines를 찾자. 첫 번째 이미지의 점에 해당하는 Epilines가 두 번째 이미지에 그려진다. 여기에서 정확한 이미지에 대한 언급은 중요하다. 우리는 일련의 선들을 얻게 되었다. 이미지에 이 선들을 그리는 새 함수를 생성해야한다!
def drawlines(img1,img2,lines,pts1,pts2):
"img1 : img2의 점에 대한 epiline을 그리는 이미지"
"lines : 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
이제 epilines를 두 개의 이미지에서 찾고 이를 그리자
# 오른쪽 이미지의 포인트에 해당하는 epilines 찾기
# 왼쪽 이미지에 선 그리기
lines1 = cv2.computeCorrespondEpilines(pts2.reshape(-1,1,2),2,F)
lines1 = lines1.reshape(-1,3)
img5,img6 = drawlines(img1,img2,lines1,pts1,pts2)
# 왼쪽 이미지의 포인트에 해달하는 epilines 찾기
# 오른쪽 이미지에 선 그리기
lines2 = cv2.computeCorrespondEpilines(pts1.reshape(-1,1,2),1,F)
lines2 = lines2.reshape(-1,3)
img3,img4 = drawlines(img2,img1,lines2,pts2,pts1)
cv2.imshow('left image with line', img5)
cv2.imshow('left image points', img6)
cv2.imshow('right image with line', img3)
cv2.imshow('right image points', img4)
cv2.waitKey(0)
cv2.destroyAllWindows()
먼저 points들을 보자
그 다음 epilines이다.
각 이미지에 "epiline"들이 만나는 지점이 "epipole"이 된다.
더 나은 결과를 위해서는 이미지의 해상도가 좋아야하고 평면상의 아닌 점들이 많이 사용되어야 한다.
좌측 이미지와 우측 이미지를 합쳤을 때, 맨 위의 그림처럼 삼각형을 대강 그려볼 수 있다. 두 이미지 모두 epilines가 한 쪽으로 수렴하는 것을 볼 수 있다.