😎 공부하는 징징알파카는 처음이지?

[v0.30]영상처리_특징 매칭(Feature Matching) 본문

👩‍💻 IoT (Embedded)/Image Processing

[v0.30]영상처리_특징 매칭(Feature Matching)

징징알파카 2022. 1. 16. 00:57
728x90
반응형

220116 작성

<본 블로그는 귀퉁이 서재님의 블로그를 참고해서 공부하며 작성하였습니다>

https://bkshin.tistory.com/entry/OpenCV-28-%ED%8A%B9%EC%A7%95-%EB%A7%A4%EC%B9%ADFeature-Matching?category=1148027 

 

OpenCV - 28. 특징 매칭(Feature Matching)

이번 포스팅에서는 특징 매칭에 대해 알아보겠습니다. 이번 포스팅 역시 '파이썬으로 만드는 OpenCV 프로젝트(이세우 저)'를 정리한 것임을 밝힙니다. 코드: github.com/BaekKyunShin/OpenCV_Project_Python/tree/

bkshin.tistory.com

 

 

 

 

 

1. 특징 매칭(Feature Matching)

: 서로 다른 두 이미지에서 특징점과 특징 디스크립터들을 비교해서 비슷한 객체끼리 짝짓는 것

 

matcher = cv2.DescriptorMatcher_create(matcherType)

: 매칭기 생성자

 

- matcherType : 생성할 구현 클래스의 알고리즘

- BruteForce : NORM_L2를 사용하는 BFMatcher

- BruteForce-L1 : NORM_L1을 사용하는 BFMatcher

- BruteForce-Hamming : NORM_HAMMING을 사용하는 BRMatcher

- BruteForce-Hamming(2) : NORM_HAMMING2를 사용하는 BFMatcher

- FlannBased : NORM_L2를 사용하는 FlannBasedMatcher

- 파라미터로 구현할 클래스의 알고리즘을 문자열로 전달해줘도 됨

 

: 생성된 특징 매칭기는 두 개의 디스크립터를 서로 비교하여 매칭 해주는 함수 가짐

: 3개의 함수가 있는데, match(), knnMatch(), radiusMatch()

: 모두 첫 번째 파라미터인 queryDescriptors를 기준으로 두 번째 파라미터인 trainDescriptors에 맞는 매칭을 찾음 

 

    • matches : matcher.match(queryDescriptors, trainDescriptors, mask)
      •  : 1개의 최적 매칭
      •  : queryDescriptors 한 개당 최적의 매칭을 이루는 trainDescriptors를 찾아 결과로 반환
      •  : 최적 매칭을 찾지 못하는 경우도 있음
      •  : 반환되는 매칭 결과 개수가 queryDescriptors의 개수보다 적을 수도 있음
      • - queryDescriptors : 특징 디스크립터 배열, 매칭의 기준이 될 디스크립터
      • - trainDescriptors : 특징 디스크립터 배열, 매칭의 대상이 될 디스크립터
      • - mask(optional) : 매칭 진행 여부 마스크matches: 매칭 결과, DMatch 객체의 리스트
    • matches = matcher.knnMatch(queryDescriptors, trainDescriptors, k, mask, compactResult)
      • : k개의 가장 근접한 매칭
      •  : queryDescriptors 한 개당 k개의 최근접 이웃 개수만큼 trainDescriptors에서 찾아 반환
      •  : k개의 최근접 이웃 개수만큼이라는 말은 가장 비슷한 k개만큼의 매칭 값을 반환
      •  : CompactResult에 default값이 False가 전달되면 매칭 결과를 찾지 못해도 결과에 queryDescriptors의 ID를 보관하는 행을 추가 (True가 전달되면 아무것도 추가하지 않음)
      • - k : 매칭할 근접 이웃 개수
      • - compactResult(optional) : True: 매칭이 없는 경우 매칭 결과에 불포함 (default=False)
  • matches = matcher.radiusMatch(queryDescriptors, trainDescriptors, maxDistance, mask, compactResult)
    •  : maxDistance 이내의 거리 매칭
    •  : queryDescriptors에서 maxDistance 이내에 있는 trainDescriptors를 찾아 반환
    • - maxDistance : 매칭 대상 거리

:  match(), knnMatch(), radiusMatch() 함수의 반환 결과는 DMatch 객체 리스트

 

 

DMatch

: 매칭 결과를 표현하는 객체 

: DMatch 객체의 queryIdx와 trainIdx로 두 이미지의 어느 지점이 서로 매칭 되었는지 알 수 있음

: distnace로 얼마나 가까운 거리 인지도 알 수 있음

 

- queryIdx : queryDescriptors의 인덱스
- trainIdx : trainDescriptors의 인덱스
- imgIdx : trainDescriptor의 이미지 인덱스
- distance : 유사도 거리

 

cv2.drawMatches(img1, kp1, img2, kp2, matches, flags)

: 매칭점을 이미지에 표시

: 매칭 결과를 시각적으로 표현하기 위해 두 이미지를 하나로 합쳐서 매칭점끼리 선으로 연결하는 작업이 필요


- img1, kp1 : queryDescriptor의 이미지와 특징점
- img2, kp2 : trainDescriptor의 이미지와 특징점
- matches : 매칭 결과
- flags : 매칭점 그리기 옵션

  • cv2.DRAW_MATCHES_FLAGS_DEFAULT : 결과 이미지 새로 생성(default)
  • cv2.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG : 결과 이미지 새로 생성 안 함
  • cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS : 특징점 크기와 방향도 그리기
  • cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS : 한쪽만 있는 매칭 결과 그리기 제외

 

 

 

 

2. BFMatcher(Brute-Force Matcher)

: queryDescriptors와 trainDescriptors를 하나하나 확인해 매칭되는지 판단하는 알고리즘

 

matcher = cv2.BFMatcher_create(normType, crossCheck)
- normType : 거리 측정 알고리즘

  • cv2.NORM_L1
  • cv2.NORM_L2(default)
  • cv2.NORM_L2SQR
  • cv2.NORM_HAMMING
  • cv2.NORM_HAMMING2

- crosscheck : 상호 매칭이 되는 것만 반영 (default=False)

거리 측정 알고리즘을 전달하는 파라미터인 normType의 값 계산

: 세 가지 유클리드 거리 측정법과 두 가지 해밍 거리 측정법 중에 선택

: SIFT와 SURF 디스크립터 검출기의 경우 NORM_L1, NORM_L2가 적합

: ORB로 디스크립터 검출기의 경우 NORM_HAMMING이 적합하며,

NORM_HAMMING2는 ORB의 WTA_K가 3 혹은 4일 때 적합

: crosscheck가 True이면 양쪽 디스크립터 모두에게서 매칭이 완성된 것만 반영하므로 불필요한 매칭을 줄일 수 있지만 그만큼 속도가 느려진다

 

 

1) SIFT 디스크립터 검출기와 BFMatcher

# BFMatcher와 SIFT로 매칭
import cv2, numpy as np

img1 = cv2.imread('img/taekwonv1.jpg')
img2 = cv2.imread('img/figures.jpg')
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# SIFT 서술자 추출기 생성 ---①
detector = cv2.xfeatures2d.SIFT_create()
# 각 영상에 대해 키 포인트와 서술자 추출 ---②
kp1, desc1 = detector.detectAndCompute(gray1, None)
kp2, desc2 = detector.detectAndCompute(gray2, None)

# BFMatcher 생성, L1 거리, 상호 체크 ---③
matcher = cv2.BFMatcher(cv2.NORM_L1, crossCheck=True)
# 매칭 계산 ---④
matches = matcher.match(desc1, desc2)
# 매칭 결과 그리기 ---⑤
res = cv2.drawMatches(img1, kp1, img2, kp2, matches, None, \
                      flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)
# 결과 출력 
cv2.imshow('BFMatcher + SIFT', res)
cv2.waitKey()
cv2.destroyAllWindows()

SIFT 디스크립터 검출기와 BFMatcher

- SIFT 디스크립터 검출기를 생성

- 각 이미지에 대해 특징점과 디스크립터를 추출

- BFMatcher 객체를 생성한 뒤 이를 활용하여 두 이미지의 디스크립터로 매칭 계산

 

 

2) SURF 디스크립터 검출기와 BFMatcher

# BFMatcher와 SURF로 매칭
import cv2
import numpy as np

img1 = cv2.imread('img/taekwonv1.jpg')
img2 = cv2.imread('img/figures.jpg')
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# SURF 서술자 추출기 생성 ---①
detector = cv2.xfeatures2d.SURF_create()
kp1, desc1 = detector.detectAndCompute(gray1, None)
kp2, desc2 = detector.detectAndCompute(gray2, None)

# BFMatcher 생성, L2 거리, 상호 체크 ---③
matcher = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)
# 매칭 계산 ---④
matches = matcher.match(desc1, desc2)
# 매칭 결과 그리기 ---⑤
res = cv2.drawMatches(img1, kp1, img2, kp2, matches, None, \
                     flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)

cv2.imshow('BF + SURF', res)
cv2.waitKey()
cv2.destroyAllWindows()

SURF 디스크립터 검출기와 BFMatcher

- 매칭 더 많이 됨

- 에러남

 

 

3) ORB 디스크립터 검출기와 BFMatcher

# BFMatcher와 ORB로 매칭
import cv2, numpy as np

img1 = cv2.imread('img/taekwonv1.jpg')
img2 = cv2.imread('img/figures.jpg')
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# SIFT 서술자 추출기 생성 ---①
detector = cv2.ORB_create()
# 각 영상에 대해 키 포인트와 서술자 추출 ---②
kp1, desc1 = detector.detectAndCompute(gray1, None)
kp2, desc2 = detector.detectAndCompute(gray2, None)

# BFMatcher 생성, Hamming 거리, 상호 체크 ---③
matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
# 매칭 계산 ---④
matches = matcher.match(desc1, desc2)
# 매칭 결과 그리기 ---⑤
res = cv2.drawMatches(img1, kp1, img2, kp2, matches, None, \
                     flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)

cv2.imshow('BFMatcher + ORB', res)
cv2.waitKey()
cv2.destroyAllWindows()

ORB  디스크립터 검출기와 BFMatcher

 

 

 

 

3. FLANN(Fast Library for Approximate Nearest Neighbors Matching)

: BFMatcher는 모든 디스크립터를 전수 조사하므로 이미지 사이즈가 클 경우 속도가 굉장히 느림

: FLANN은 모든 디스크립터를 전수 조사하기 보다 이웃하는 디스크립터끼리 비교

 

matcher = cv2.FlannBasedMatcher(indexParams, searchParams)

: 인덱스 파라미터로 indexParams를 전달받고 검색 파라미터로 searchParams를 전달받음

: 두 파라미터 모두 딕셔너리 형태

 

  • indexParams : 인덱스 파라미터 (딕셔너리)
    • algorithm : 알고리즘 선택 키, 선택할 알고리즘에 따라 종속 키를 결정하면 됨
      • FLANN_INDEX_LINEAR=0 : 선형 인덱싱, BFMatcher와 동일
      • FLANN_INDEX_KDTREE=1 : KD-트리 인덱싱 (trees=4: 트리 개수(16을 권장))
      • FLANN_INDEX_KMEANS=2 : K-평균 트리 인덱싱 (branching=32: 트리 분기 개수, iterations=11: 반복 횟수, centers_init=0: 초기 중심점 방식)
      • FLANN_INDEX_COMPOSITE=3 : KD-트리, K-평균 혼합 인덱싱 (trees=4: 트리 개수, branching=32: 트리 분기 새수, iterations=11: 반복 횟수, centers_init=0: 초기 중심점 방식)
      • FLANN_INDEX_LSH=6 : LSH 인덱싱 (table_number: 해시 테이블 수, key_size: 키 비트 크기, multi_probe_level: 인접 버킷 검색)
      • FLANN_INDEX_AUTOTUNED=255 : 자동 인덱스 (target_precision=0.9: 검색 백분율, build_weight=0.01: 속도 우선순위, memory_weight=0.0: 메모리 우선순위, sample_fraction=0.1: 샘플 비율)
  • searchParams: 검색 파라미터 (딕셔너리)
    • searchParams : 검색 파라미터 (딕셔너리)
      • checks=32 : 검색할 후보 수
      • eps=0.0 : 사용 안 함
      • sorted=True: 정렬해서 반환

: 인덱스 파라미터는 결정해야 할 값이 너무 많아 복잡, 아래 권장

# <SIFT나 SURF를 사용하는 경우>

FLANN_INDEDX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
# <ORB를 사용하는 경우>

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

 

 

1) FLANNMatcher와 SIFT로 매칭

# FLANNMatcher와 SIFT로 매칭
import cv2, numpy as np

img1 = cv2.imread('img/taekwonv1.jpg')
img2 = cv2.imread('img/figures.jpg')
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# SIFT 생성
detector = cv2.xfeatures2d.SIFT_create()
# 키 포인트와 서술자 추출
kp1, desc1 = detector.detectAndCompute(gray1, None)
kp2, desc2 = detector.detectAndCompute(gray2, None)

# 인덱스 파라미터와 검색 파라미터 설정 ---①
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)

# Flann 매처 생성 ---③
matcher = cv2.FlannBasedMatcher(index_params, search_params)
# 매칭 계산 ---④
matches = matcher.match(desc1, desc2)
# 매칭 그리기
res = cv2.drawMatches(img1, kp1, img2, kp2, matches, None, \
                flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)

cv2.imshow('Flann + SIFT', res)
cv2.waitKey()
cv2.destroyAllWindows()

FLANNMatcher와 SIFT로 매칭

 

 

2) FLANNMatcher와 SURF로 매칭

# FLANNMatcher와 SURF로 매칭
import cv2, numpy as np

img1 = cv2.imread('img/taekwonv1.jpg')
img2 = cv2.imread('img/figures.jpg')
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# SURF 생성
detector = cv2.xfeatures2d.SURF_create()
# 키 포인트와 서술자 추출
kp1, desc1 = detector.detectAndCompute(gray1, None)
kp2, desc2 = detector.detectAndCompute(gray2, None)

# 인덱스 파라미터와 검색 파라미터 설정 ---①
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)

# Flann 매처 생성 ---③
matcher = cv2.FlannBasedMatcher(index_params, search_params)
# 매칭 계산 ---④
matches = matcher.match(desc1, desc2)
# 매칭 그리기
res = cv2.drawMatches(img1, kp1, img2, kp2, matches, None, \
                flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)

cv2.imshow('Flann + SURF', res)
cv2.waitKey()
cv2.destroyAllWindows()

FLANNMatcher와 SURF로 매칭... 호메..

- 얘만 SURF 만! 에러남..

 

 

 

3) FLANNMatcher와 ORB로 매칭

# FLANNMatcher와 ORB로 매칭
import cv2, numpy as np

img1 = cv2.imread('img/taekwonv1.jpg')
img2 = cv2.imread('img/figures.jpg')
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# ORB 추출기 생성
detector = cv2.ORB_create()
# 키 포인트와 서술자 추출
kp1, desc1 = detector.detectAndCompute(gray1, None)
kp2, desc2 = detector.detectAndCompute(gray2, None)

# 인덱스 파라미터 설정 ---①
FLANN_INDEX_LSH = 6
index_params= dict(algorithm = FLANN_INDEX_LSH,
                   table_number = 6,
                   key_size = 12,
                   multi_probe_level = 1)
# 검색 파라미터 설정 ---②
search_params=dict(checks=32)
# Flann 매처 생성 ---③
matcher = cv2.FlannBasedMatcher(index_params, search_params)
# 매칭 계산 ---④
matches = matcher.match(desc1, desc2)
# 매칭 그리기
res = cv2.drawMatches(img1, kp1, img2, kp2, matches, None, \
            flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)
# 결과 출력            
cv2.imshow('Flann + ORB', res)
cv2.waitKey()
cv2.destroyAllWindows()

FLANNMatcher와 ORB로 매칭

 

- 셋 다 잘못된 매칭 정보가 많다

- 태권브이는 태권브이만 매칭되어야되는뎅...

 

 

728x90
반응형
Comments