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)를 계산하는 것입니다. 실행 결과는 다음과 같습니다.

Python과 OpenCV – 35 : FAST(Features from Accelerated Segment Test)을 이용한 이미지의 특징점 추출

이 글의 원문은 https://docs.opencv.org/3.0-beta/doc/py_tutorials/py_feature2d/py_fast/py_fast.html 입니다.

이미지에서 특징점을 추출해 내는 다양한 방법이 있지만, 그 속도면에서 아쉬움이 있었고 정확도를 희생하는 대신 빠른 속도로 특징점을 추출하는 방법으로 2006년에 FAST가 처음 제안되었습니다. 이 방법은 인공지능의 기계학습에서도 활용된다고 합니다. 이 방법은 특정 화소 인근의 화소값을 16개 뽑고 특정 화소의 화소값이 인근의 16개의 화소값에 임계치값(t)을 더한 값보다 크거나 임계치값을 뺀 값보다 작은 인근화소의 개수에 따라 특징점인지를 결정하는 매우 단순한 방식입니다. 여기에 더욱 속도를 향상시키기 위해 16개의 인근화소가 아닌 4개의 인근화소를 활용한다거나, 특징점이 비슷한 부분에서 너무 많이 추출되는 것을 방지하기 위해 억제(Non-maximal Suppression)하는 등이 절차가 적용되기도 합니다. 다른 방식에 비해 특징점 추출의 정확도를 떨어지지만 실시간에서 활용할 수 있는 속도를 제공하면서 어느 정도의 특징점 추출 방법으로 사용된다고 합니다.

OpneCV에서 이 FAST 특징점 추출에 대한 API 예제는 다음과 같습니다.

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

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

fast = cv2.FastFeatureDetector_create()

kp = fast.detect(img,None)
img2=cv2.drawKeypoints(img,kp,None)

print("Threshold: ", fast.getThreshold())
print("nonmaxSuppression: ", fast.getNonmaxSuppression())
print("neighborhood: ", fast.getType())
print("Total Keypoints with nonmaxSuppression: ", len(kp))

cv2.imshow('img2', img2)

cv2.waitKey()
cv2.destroyAllWindows()

실행결과는 아래와 같습니다.

콘솔에 출력된 결과는 아래와 같은데요.

Threshold:  10
nonmaxSuppression:  True
neighborhood:  2
Total Keypoints with nonmaxSuppression:  4992


인근 픽셀 화소값 비교를 위한 임계치 기본값은 10이고 비슷한 지점에서 너무 많은 특징점이 추출되는 것을 방지하기 위한 nonmaxSuppression가 True로 지정되어 있습니다. 시각적으로 이러한 특징점을 줄이기 위해 임계값을 변경하는 예제는 다음과 같습니다.

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

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

fast = cv2.FastFeatureDetector_create()

fast.setThreshold(150)
#fast.setNonmaxSuppression(False)

kp = fast.detect(img,None)
img2=cv2.drawKeypoints(img,kp,None)

print("Threshold: ", fast.getThreshold())
print("nonmaxSuppression: ", fast.getNonmaxSuppression())
print("neighborhood: ", fast.getType())
print("Total Keypoints with nonmaxSuppression: ", len(kp))

cv2.imshow('img2', img2)

kp = fast.detect(img,None)

cv2.waitKey()
cv2.destroyAllWindows()

결과는 다음과 같습니다.