[OpenCV] 07-3. Epipolar Geometry
🐍Python/OpenCV

[OpenCV] 07-3. Epipolar Geometry

728x90
반응형

< 7-3. Epipolar Geometry >

이번 장에서는

  • 다관점(multiview) 기하학의 기초에 대해 배워보고
  • epipole, epipolar 선, epipolar 제약에 대해서 알아볼 것이다.

Basic Concepts

핀홀 카메라를 사용해서 이미지를 얻으면, 이미지의 깊이와 같은 중요한 정보를 잃게된다. 또는 카메라에서부터 이미지의 각 점이 얼마나 멀리 떨어져있는지에 대한 정보도 해당한다. (3D에서 2D로의 변환이기 때문이다.) 그래서 카메라를 사용해서 깊이 정보를 얻어낼 수 있냐는 질문은 매우 중요하다. 그리고 답은 하나 이상의 카메라를 사용하는 것이다. 우리의 눈은 스테레오 비전이라고 불리는 두 개의 카메라를 사용하는 것과 유사하게 작동한다. 이제 OpenCV가 제공하는 것을 봐보자!

이미지의 깊이로 가기 전에, multiview geometry의 개념에 대해서 이해해보자. 이 섹션에서 epipolar geometry와 함께 다뤄볼 것이다. 아래의 이미지는 같은 이미지를 두 개의 다른 카메라가 바라보고있는 것을 보여준다.



만약 왼편의 카메라만 사용한다면, OX선 위의 모든 점이 이미지 평면에서 같은 점에 위치하기 때문에(일렬이다) 이미지에서 x 점에 대응하는 3D 점들을 찾지 못한다. 하지만 오른쪽의 이미지(카메라)도 같이 생각해보자. 이제 OX 선위의 서로 다른 점들이 오른쪽 평면에서 서로 다른 점(x)으로 투영되는 것을 볼 수 있다. 두 이미지를 가지고, 정확한 3D 점을 triangulate 할 수 있다. 이것이 아이디어의 전부이다!

선분 OX의 서로 다른 점이 투영된 오른쪽 평면(선 l)을 보자. x 점과 대응하는 선(l,l)을 epiline 이라고 부른다. epiline을 따라 검색하며, 오른쪽 이미지에서 x 점을 찾는 것을 의미한다. 이는 이 선(l)위에 있어야 한다(다른 이미지에 매칭되는 점을 찾기 위해, 이미지 전체를 이용하지 말고 epiline을 따라 찾아라. 그러면 더 나은 성능과 정확성을 제공한다.) 이를 Epipolar Constraint라고 부른다. 비슷하게 모든 점들은 다른 이미지에서 대응되는 epilines를 가질 것이다. 평면 XOO Epipolar Plane이라고 부른다.

O O는 카메라의 중앙이다. 위에 주어진 배치를 보면, 오른쪽 카메라 O의 투영된 것이 왼쪽 이미지에서는 점(e)으로 보이는 것을 볼 수 있다. 이 점을 Epipole 이라고 한다. Epipole은 카메라를 통과하는 선과 이미지 평면의 교차점이다. 비슷하게 e는 왼쪽 카메라의 epipole이다. 어떤 경우에는, 이미지내에서 epipole을 놔두지 않고, 이미지 밖에 놔둘 수 있다.(즉, 한 카메라가 다른 카메라를 보지못하는 상황을 말한다.)

모든 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가 한 쪽으로 수렴하는 것을 볼 수 있다.

728x90
반응형