Python과 OpenCV – 39 : 이미지의 특징점 매칭을 통한 Homography 분석

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_feature2d/py_feature_homography/py_feature_homography.html 입니다.

먼저 Homography의 정의는 한 평면을 다른 평면에 투영했을 때, 투영된 대응점들 사이에서의 변환 관계라고 합니다. 이를 이미지에서 설명하면, 2개의 이미지가 있다면 첫번째 이미지의 내용을 두번째 이미지에서 포함하고 있는데.. 첫번째 이미지가 두번째 이미지의 어느 위치로 투영 되었는가로 생각할 수 있습니다. 아래는 이 글에서 소개할 예제의 실행 결과입니다.

위의 이미지를 보면, 좌측와 우측에 2개의 이미지가 있습니다. 좌측 이미지는 우측 이미지의 어떤 일정 부분으로 투영되는데.. 바로 우측 이미지에 하얀색으로 표시된 사각형 영역입니다. 이 글은 바로 이 하얀색 사각형 영역을 이미지의 특징점 매칭을 통해 분석하는 글입니다.

이미 앞서 설명했던 이미지의 특징점을 구해 매칭을 했다면, cv2.findHomography()와 cv2.perspectiveTransform() 함수를 사용하여 Homography 분석을 통해 투영된 영역을 찾아낼 수 있습니다.

아래의 코드는 위의 이미지 결과에 대한 예제입니다.

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

MIN_MATCH_COUNT = 10

img1 = cv2.imread('./data/harleyQuinnA.jpg',0) # queryImage
img2 = cv2.imread('./data/harleyQuinnB.jpg',0) # trainImage

# Initiate SIFT detector
sift = cv2.xfeatures2d.SIFT_create()

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

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)

# store all the good matches as per Lowe's ratio test.
good = []
for m,n in matches:
    if m.distance < 0.3*n.distance:
        good.append(m)

if len(good)>MIN_MATCH_COUNT:
    src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
    dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)

    M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)
    matchesMask = mask.ravel().tolist()

    h,w = img1.shape
    pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
    dst = cv2.perspectiveTransform(pts,M)

    img2 = cv2.polylines(img2,[np.int32(dst)],True,255,3, cv2.LINE_AA)
else:
    print("Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT))
    matchesMask = None

draw_params = dict(matchColor = (0,255,0), # draw matches in green color
                   singlePointColor = None,
                   matchesMask = matchesMask, # draw only inliers
                   flags = 2)

img3 = cv2.drawMatches(img1,kp1,img2,kp2,good,None,**draw_params)

plt.imshow(img3, 'gray'),plt.show()

1-31번까지의 코드는 이미지의 특징점을 추출하고 이 중 가장 좋은 특징점을 28번의 if 문을 기준으로 선별합니다. 이렇게 선별된 특징점을 31-45번 코드를 통해 Homography를 분석하여 이미지에 그립니다. 47-54코드는 이렇게 그려진 Homography와 함께 두 이미지 사이의 동일한 특징점에 대한 선을 그려주고 화면에 표시합니다.

Python과 OpenCV – 38 : 이미지의 특징점 매칭(Feature Matching)

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_feature2d/py_matcher/py_matcher.html 입니다.

이제 두 개의 이미지에서 동일한 특징점을 찾아 매칭해 주는 내용을 살펴 보겠습니다. OpenCV에서는 이런 특징점 매칭을 Brute-Force 매칭이라고 하는데.. 이는 하나의 이미지에서 발견한 특징점을 다른 또 하나의 이미지의 모든 특징점과 비교해 가장 유사한 것이 동일한 특징점이라고 판별하는.. 단순하지만 다소 효율적이지 못한 방식이기 때문입니다.

Brute-Force 매칭을 위한, 즉 BF Matcher는 cv2.BFMatcher 함수를 통해 생성합니다. 이 함수는 2개의 선택적 인자를 받는데, 첫번째는 normType이며 거리 측정 방식을 지정합니다. 기본적으로는 cv2.NORM_L2이며 cv2.NORM_L1과 함께 키포인트와 특징점 기술자를 연산 방식인 SIFT, SURF에 좋습니다. ORB와 BRIEF, BRISK와 같은 2진 문자열 기반의 방식에서는 cv2.NORM_HAMMING가 사용되어져야 합니다. 만약 ORB가 VTA_K == 3 또는 4일 때, cv2.NORMHAMMING2가 사용되어져야 합니다. 두번재 인자는 crossCheck라는 Boolean 타입의 인자입니다. 기본값은 false이며 만약 True로 지정하면 A라는 이미지의 어떤 하나의 특징점을 B라는 이미지의 모든 특징점과 비교하는 것에서 끝나지 않고, 다시 B라는 이미지에서 찾은 가장 유사한 특징점을 A라는 모든 특징점과 비교하여 그 결과가 같은지를 검사하라는 옵션입니다. 보다 정확한 동일 특징점을 추출하고자 한다면 True를 지정하면 됩니다.

일반 BF Matcher 객체가 생성되면 match()와 knnMatch()라는 함수를 사용합니다. 첫번째는 가장 좋은 매칭 결과를 반환하고, 두번째 함수는 사용자가 지정한 k개의 가장 좋은 매칭 결과를 반환합니다. 특징점을 표시하는 cv2.drawKeypoints()와 같이, 2개의 이미지 간의 동일 특징점을 선으로 연결에 표시해 주는 cv2.drawMatches() 함수와 cv2.drawMatchesKnn() 함수가 있습니다.

이제 실제 예제 코드를 살펴 보겠습니다. 첫번째는 ORB 기술자를 사용한 특징점 비교, 두번째는 SIFT 기술자를 사용한 특징점 비교, 끝으로 세번째는 FLANN 방식의 특징점 비교입니다.

먼저 첫번째로 ORB 기술자를 사용한 특징점 비교에 대한 예제입니다.

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

img1 = cv2.imread('./data/harleyQuinnA.jpg',0)
img2 = cv2.imread('./data/harleyQuinnB.jpg',0)

orb = cv2.ORB_create()

kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)

bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

matches = bf.match(des1,des2)

matches = sorted(matches, key = lambda x:x.distance)

img3 = cv2.drawMatches(img1,kp1,img2,kp2,matches[:10],None,flags=2)

plt.imshow(img3),plt.show()

결과는 다음과 같았습니다. (아래 결과는 상당이 부정확한데, 다른 사이트의 실행을 보면 이와 상반된 결과를 볼 수 있습니다. 설치된 OpenCV의 버전에 따른 영향이 아닌가 생각됩니다)

13번 코드에서 BF Matcher 객체를 생성하고 있는데, ORB를 사용하므로 cv2.NORM_HAMMING를 지정하고 있습니다. 17번에서는 매칭 결과에서 거리에 따로 오름차순으로 정렬하였습니다. 거리값이 작을수록 더 좋은 결과입니다. 19번 코드에서는 이렇게 정렬된 것 중 10개만을 화면에 표시하고 있습니다.

BF Matcher의 match() 함수의 결과는 DMatch 객체의 리스트입니다. 이 객체는 다음과 같은 속성을 갖습니다.

  • DMatch.distance : 기술자(Descriptor) 간의 거리로써, 작을수록 더 좋은 결과임
  • DMatch.trainIdx : 연습 기술자 리스트에 저장된 인덱스(위 예제에서 img1에서 추출한 기술자가 연습 기술자임)
  • DMatch.queryIdx : 조회 기술자 리스트에 저장된 인덱스(위 예제에서 img2에서 추출한 기술자가 조회 기술자임)
  • DMatch.imgIdx : 연습 이미지의 인덱스

두번째 예제는 SIFT 기술자를 이용한 특징점 비교입니다.

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

img1 = cv2.imread('./data/harleyQuinnA.jpg',0)
img2 = cv2.imread('./data/harleyQuinnB.jpg',0)

sift = cv2.xfeatures2d.SIFT_create()

kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)

bf = cv2.BFMatcher()
matches = bf.knnMatch(des1,des2, k=2)

good = []
for m,n in matches:
    if m.distance < 0.3*n.distance:
        good.append([m])

img3 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,good,None,flags=2)

plt.imshow(img3),plt.show()

결과는 아래와 같습니다.

마지막 예제는 FLANN 입니다. FLANN은 Fast Library for Approximate Nearest Neighbors의 약자입니다. 대용량의 데이터셋과 고차원 특징점에 있어서 속도면에 최적화 되어 있습니다. 이는 앞서 살펴본 BF Matcher 방식보다 좀더 빠릅니다.

FLANN 기반 Matcher를 위해, 알고리즘 수행을 위한 2개의 dictionary가 필요합니다. 첫번째는 IndexParams 인데, SIFT나 SURF 등의 경우 아래처럼 생성할 수 있습니다.

index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)

ORB의 경우에는 다음처럼 생성됩니다.

index_params= dict(algorithm = FLANN_INDEX_LSH,
                   table_number = 6, # 12
                   key_size = 12,     # 20
                   multi_probe_level = 1) #2

두번째 dictionary는 SearchParams이며 다음처럼 생성됩니다. 정밀도를 높이기 위해 더 높은 checks 갑을 지정할 수 있지만 시간이 더 소요 됩니다.

search_params = dict(checks=100)

전체 예제 코드는 아래와 같습니다.

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

img1 = cv2.imread('./data/harleyQuinnA.jpg',0)
img2 = cv2.imread('./data/harleyQuinnB.jpg',0)

# Initiate SIFT detector
sift = cv2.xfeatures2d.SIFT_create()

# 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)   # or pass empty dictionary

flann = cv2.FlannBasedMatcher(index_params,search_params)

matches = flann.knnMatch(des1,des2,k=2)

# Need to draw only good matches, so create a mask
matchesMask = [[0,0] for i in range(len(matches))]

# ratio test as per Lowe's paper
for i,(m,n) in enumerate(matches):
    if m.distance < 0.3*n.distance:
        matchesMask[i]=[1,0]

draw_params = dict(matchColor = (0,255,0),
                   singlePointColor = (255,0,0),
                   matchesMask = matchesMask,
                   flags = 0)

img3 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,matches,None,**draw_params)

plt.imshow(img3,),plt.show()

결과는 아래와 같습니다.

Python과 OpenCV – 37 : ORB (Oriented FAST and Rotated BRIEF)을 이용한 이미지의 특징점 추출

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_feature2d/py_orb/py_orb.html 입니다.

ORB는 다른 특징점 추출 알고리즘과는 다르게 OpenCV 연구소에서 개발되었습니다. 특허권이 걸린 SIFT와 SURF를 대신할 수 있는데, 계산비용과 매칭 속도가 더 좋다고 합니다. ORG는 기본적으로는 FAST의 키포인트 검출기와 BRIEF의 기술자의 혼합하면서 속도를 더 개선 시켰다고 합니다. 결과적으로 ORB는 SURF와 SIFT보다 더 빠르고 더 잘 작동합니다. ORB는 저전력의 단말기에서도 잘작동하며 파노라마 이미지 생성을 위한 이미지 붙이기 등의 기능에 적합하다고 합니다.

OpenCV에서 ORB에 대한 예제 코드는 아래와 같습니다.

import numpy as np
import cv2

filename = './data/butterfly.jpg'
img = cv2.imread(filename, cv2.COLOR_BGR2GRAY)
img2 = None

orb = cv2.ORB_create()
kp, des = orb.detectAndCompute(img, None)

img2 = cv2.drawKeypoints(img, kp, None, (255,0,0), flags=0)
cv2.imshow('img2', img2)
cv2.waitKey(0)
cv2.destroyAllWindows()

위 코드에 대한 실행 결과는 다음과 같습니다.

Python과 OpenCV – 36 : BRIEF (Binary Robust Independent Elementary Features)을 이용한 이미지의 특징점 추출

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_feature2d/py_brief/py_brief.html 입니다.

지금까지 살펴본 SIFT나 SURF 등은 이미지의 특징점을 검출하기 위해 제법 많은 메모리를 사용하는데, 자원에 제약이 심한 임베디드 장비에서는 메모리를 적게 사용하는 특징점 검출 방법이 필요하며, 그 방법이 바로 BRIEF입니다.

BRIEF는 특징점에 대한 기술자(Descriptor)일 뿐, 특징점을 검출하는 방법은 제공하지 않는데.. 이를 위해 다른 특징점을 검출하는 SIFT나 SURF 등을 사용해야 합니다. BRIEF에 대해 설명하는 논문에서는 CenSurE를 추천하고 있습니다. OpenCV에서는 이를 STAR라고도 합니다. 여튼 CenSurE는 빠르며 다른 방법보다 BRIEF에 더 최적화되어 있다고 합니다.

OpenCV에서 제공하는 BRIEF의 예제를 살펴보면 다음과 같습니다.

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

filename = './data/butterfly.jpg'
img = cv2.imread(filename, 0)

star = cv2.xfeatures2d.StarDetector_create()
brief = cv2.xfeatures2d.BriefDescriptorExtractor_create()

kp = star.detect(img,None)
kp, des = brief.compute(img, kp)

img2 = cv2.drawKeypoints(img, kp, None, (255,0,0))
cv2.imshow('img2', img2)
cv2.waitKey(0)
cv2.destroyAllWindows()

8번은 STAR 특징점 검출을 위한 객체를 생성합니다. 이렇게 생성된 STAR 검출기를 통해 특징점을 검출하고, 이 특징점을 이용해 9번 코드에서 생성한 BRIEF 객체를 통해 특징점의 기술자(Descriptor)를 계산하는 것입니다. 실행 결과는 다음과 같습니다.