아재 개그이지만, '코너'라는 단어가 보이자마자 왜 '코너 맥그리거'가 생각났는지 모르겠다. 이번에는 코너와 같이 각 영상에서의 특징점을 검출하는 기법에 대해 다룰 것이다.
cv2.cornerHarris(src, blockSize, ksize, k, dst=None, borderType=None)
-> dst
1) src : 입력 단일 채널 8비트 또는 실수형 영상
2) blockSize : 코너 응답 함수 계산에서 고려할 이웃 픽셀 크기. 보통 2~5 (3 정도면 무난하게 사용 가능)
3) ksize : (미분을 위한) 소벨 연산자를 위한 커널 크기. 보통 3
4) k : 해리스 코너 검출 상수 (보통 0.04~0.06)
5) dst : 해리스 코너 응답 계수. src와 같은 크기의 행렬
6) borderType : 가장자리 픽셀 확장 방식. 기본값은 cv2.BORDER_DEFAULT
cv2.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance,
corners=None, mask=None, blockSize=None,
useHarrisDetector=None, k=None)-> corners
1) image : 8비트 또는 32비트 실수, 단일 채널 영상
2) maxCorners : 최대 코너 개수. maxCorners ≤ 0이면 무제한
3) qualityLevel : 코너점 결정을 위한 값. 보통 0.01 ~ 0.1
4) minDistance : 코너점 사이의 최소 거리
5) corners : 검출된 코너점 좌표 → 출력하려면 int type으로 전환한 뒤 출력해야 함 (기본형이 float32이기 때문)
6) mask : 마스크 영상
7) blockSize : 코너 검출을 위한 블록 크기. 기본값은 3
8) useHarrisDetector : 해리스 코너 방법 사용 여부. 기본값은 False
9) k : 해리스 코너 검출 시 사용할 k 값
cv2.FastFeatureDetector_create(, threshold=None,
nonmaxSuppression=None, type=None) -> retval
cv2.FastFeatureDetector.detect(image) -> keypoints
1) threshold : 중심 픽셀 값과 주변 픽셀 값과의 차이 임계값. 기본 값은 10 (ex 16개 중 10개 이상의 픽셀이 기준점 P보다 밝으면 이 점 P를 Corner로 판단) … 30~60이 가장 이상적
2) nonmaxSuppression : 비최대 억제 수행 여부. 기본 값은 True
3) type : 코너 검출 방법. 기본값은 cv2.FAST_FEATURE_DETECTOR_TYPE_9_16
→ type은 변경하지 않는게 좋음
4) retval : FastFeatureDetector 객체
5) image : (입력) 그레이스케일 영상
6) keypoints : (출력) 검출된 코너점 정보. cv2.KeyPoint 객체를 담은 리스트. cv2.KeyPoint의 pt 멤버를 이용하여 코너 좌표를 추출
pt[0]은 x좌표, pt[1]은 y좌표
import sys
import numpy as np
import cv2
src = cv2.imread('building.jpg', cv2.IMREAD_GRAYSCALE)
if src is None:
print('Image load failed!')
sys.exit()
tm = cv2.TickMeter()
# GFTT
tm.start()
corners = cv2.goodFeaturesToTrack(src, 400, 0.01, 10)
tm.stop()
print('GFTT: {}ms.'.format(tm.getTimeMilli()))
dst1 = cv2.cvtColor(src, cv2.COLOR_GRAY2BGR)
# 코너 부분을 빨간색 원으로 표시
if corners is not None:
for i in range(corners.shape[0]):
pt = (int(corners[i, 0, 0]), int(corners[i, 0, 1]))
cv2.circle(dst1, pt, 5, (0, 0, 255), 2)
# FAST
tm.reset() # reset을 하지 않으면 위에서 측정한 시간이 추가됨
tm.start()
fast = cv2.FastFeatureDetector_create(60)
keypoints = fast.detect(src)
tm.stop()
print('FAST: {}ms.'.format(tm.getTimeMilli()))
dst2 = cv2.cvtColor(src, cv2.COLOR_GRAY2BGR)
for kp in keypoints:
pt = (int(kp.pt[0]), int(kp.pt[1])) # pt[0], pt[1]은 각각 x좌표, y좌표
cv2.circle(dst2, pt, 5, (0, 0, 255), 2)
cv2.imshow('src', src)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()
cv2.destroyAllWindows()
>>>
GFTT: 21.5232ms.
FAST: 0.5976ms
cv2.SIFT_create(,...) -> retval
cv2.KAZE_create(,...) -> retval
cv2.AKAZE_create(,...) -> retval
cv2.ORB_create(,...) -> retval
...
1) retval : 각 특징점 검출 알고리즘 객체
2) 참고사항
- 각각의 알고리즘은 고유한 파라미터를 인자로 받을 수 있음
- 대부분의 인자는 기본값을 가지고 있으므로 함수 인자 없이 호출 가능
cv2.Feature2D.detect(image, mask=None) -> keypoints
1) image : 입력 영상
2) mask : 마스크 영상
3) keypoints : 검출된 특징점 정보. cv2.KeyPoints 객체의 리스트
→ pt (x좌표, y좌표 float type), size, angle 정보 포함
cv2.drawKeypoints(image, keypoints, outImage, color=None, flags=None)
-> outImage
1) image : 입력 영상
2) keypoints : 검출된 특징점 정보. cv2.KeyPoint 객체의 리스트
3) outImage : 출력 영상
4) color : 특징점 표현 색상. 기본값은 (-1,-1,-1,-1)이며 이 경우 임의의 색상으로 표현
5) flags : 특징점 표현 방법
cv2.DRAW_MATCHES_FLAGS_DEFAULT | cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS |
---|---|
특징점 위치만을 표현하는 작은 크기의 원 | 특징점의 크기와 방향을 반영한 원 |
# keypoints.py
import sys
import numpy as np
import cv2
# 영상 불러오기
src1 = cv2.imread('graf1.png', cv2.IMREAD_GRAYSCALE)
src2 = cv2.imread('graf3.png', cv2.IMREAD_GRAYSCALE)
if src1 is None or src2 is None:
print('Image load failed!')
sys.exit()
# 특징점 알고리즘 객체 생성 (KAZE, AKAZE, ORB 등)
feature = cv2.KAZE_create()
# feature = cv2.AKAZE_create()
# feature = cv2.ORB_create()
# 특징점 검출
kp1 = feature.detect(src1)
kp2 = feature.detect(src2)
print('# of kp1:', len(kp1))
print('# of kp2:', len(kp2))
# 검출된 특징점 출력 영상 생성
dst1 = cv2.drawKeypoints(src1, kp1, None,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
dst2 = cv2.drawKeypoints(src2, kp2, None,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()
cv2.destroyAllWindows()
>>>
# of kp1: 3159
# of kp2: 3625
: 특징점 근방의 부분 영상을 표현하는 실수 또는 이진 벡터
cv2.Feature2D.compute(image, keypoints, descriptors=None)
->keypoints, descriptors
1) image : 입력 영상
2) mask : 마스크 영상
3) keypoints : 검출된 특징점 정보. cv2.KeyPoint 객체의 리스트
4) descriptors : 특징점 기술자 행렬
cv2.Feature2D.detectAndCompute(image, mask=None, descriptors=None)
-> keyPoints, descriptors
1) image : 입력 영상
2) mask : 마스크 영상
3) keypoints : 검출된 특징점 정보. cv2.KeyPoint 객체의 리스트
4) descriptors : 특징점 기술자 행렬
import sys
import numpy as np
import cv2
# 영상 불러오기
src1 = cv2.imread('graf1.png', cv2.IMREAD_GRAYSCALE)
src2 = cv2.imread('graf3.png', cv2.IMREAD_GRAYSCALE)
if src1 is None or src2 is None:
print('Image load failed!')
sys.exit()
# 특징점 알고리즘 객체 생성 (KAZE, AKAZE, ORB 등)
# feature = cv2.SIFT_create()
feature = cv2.KAZE_create()
# feature = cv2.AKAZE_create()
# feature = cv2.ORB_create()
# 특징점 검출 및 기술자 계산
kp1 = feature.detect(src1)
_, desc1 = feature.compute(src1, kp1)
kp2, desc2 = feature.detectAndCompute(src2, None) # 위의 두 줄을 한꺼번에 연산
print('desc1.shape:', desc1.shape)
print('desc1.dtype:', desc1.dtype)
print('desc2.shape:', desc2.shape)
print('desc2.dtype:', desc2.dtype)
# 검출된 특징점 출력 영상 생성
dst1 = cv2.drawKeypoints(src1, kp1, None,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
dst2 = cv2.drawKeypoints(src2, kp2, None,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()
cv2.destroyAllWindows()
>>>
[KAZE]
desc1.shape: (3159, 64)
desc1.dtype: float32
desc2.shape: (3625, 64)
desc2.dtype: float32
[AKAZE]
desc1.shape: (2418, 61)
desc1.dtype: uint8
desc2.shape: (2884, 61)
desc2.dtype: uint8
# AKAZE의 column은 무조건 61개
[ORB]
desc1.shape: (500, 32)
desc1.dtype: uint8
desc2.shape: (500, 32)
desc2.dtype: uint8
오른쪽 2개의 푸른색 막대 그래프를 보면, 왼쪽은 정확도에 관한 그래프이고, 오른쪽은 시간에 대한 그래프이다. 분석을 해보면 SIFT와 AKAZE의 정확도가 높고, 시간도 적게 걸린다는 것을 알 수 있다. 그에 반해 KAZE는 정확도는 높은 편이지만 시간이 오래걸리고, ORB는 시간은 거의 소모되지 않지만 정확도가 낮기 때문에 ORB, KAZE 보다는 SIFT나 AKAZE 알고리즘을 AKAZE보다는 SIFT 알고리즘을 사용하는 것이 조금 더 효율적인 성능을 보일 것으로 예상된다.
: 두 영상에서 추출한 특징점 기술자를 비교하여 서로 유사한 기술자(descriptor)를 찾는 작업
특징 벡터 유사도 측정 방법
- 실수 특징 벡터 : L2-Norm 사용
OpenCV 특징점 매칭 클래스
1) BF : Brute-Force (전수 조사)
2) Flann : Fast Library for Approximate Nearest Neighbor (K-D Tree 사용)
-> 근사화를 하였기 때문에 완전 정확한 값은 아니지만, 연산 속도가 빠르다.
cv2.BFMatcher_create(, normType=None, crossCheck=None)
-> retval
1) normType : 특징점 기술자 거리 계산 방식 지정. 기본값은 cv2.NORM_L2
normType | 의미 |
---|---|
cv2.NORM_L1 | L1 norm |
cv2.NORM_L2 | L2 norm |
cv2.NORM_HAMMING | 해밍 거리 사용 |
cv2.NORM_HAMMING2 | 두 비트를 한 단위로 취급하여 해밍거리 계산 |
2) crossCheck : 이 값이 True이면 양방향 매칭 결과가 같은 경우만 반환. 기본값은 False
cv2.DescriptorMatcher.match(queryDescriptors, trainDescriptors, mask=None)
-> matches
1) queryDescriptors : (기준 영상 특징점) 질의 기술자 행렬
2) trainDescriptors : (대칭 영상 특징점) 학습 기술자 행렬
3) mask : 매칭 진행 여부를 지정하는 행렬 마스크
4) matches : 매칭 결과. cv2.DMatch 객체의 리스트
cv2.DescriptorMatcher.match(queryDescriptors, trainDescriptors, k,
mask=None, compactResult=None)
-> matches
1) queryDescriptors : (기준 영상 특징점) 질의 기술자 행렬
2) trainDescriptors : (대칭 영상 특징점) 학습 기술자 행렬
3) k : 질의 기술자에 대해 검출할 매칭 개수
4) mask : 매칭 진행 여부를 지정하는 행렬 마스크
5) compactResult : mask가 None이 아닐 때 사용되는 파라미터. 기본값은 False이며, 이 경우 결과 matches는 기준 영상 특징점과 같은 크기를 가짐
6) matches : 매칭 결과. cv2.DMatch 객체의 리스트
cv2.drawMatches(img1, keypoints1, img2, keypoints2, matches1to2, outImg,
matchColor=None, singlePointColor=None, matchesMask=None,
flags=None) -> outImg
1) img1, keypoints1 : 기준 영상과 기준 영상에서 추출한 특징점 정보
2) img2, keypoints2 : 대상 영상과 대상 영상에서 추출한 특징점 정보
3) matches1to2 : 매칭 정보. cv2.DMatch의 리스트
4) outImg : 출력 영상 → None 지정
5) matchColor : 매칭된 특징점과 직선 색상 → None 지정하면 Random 색상으로 출력
6) singlePointColor : 매칭되지 않은 특징점 색상
7) matchesMask : 매칭 정보를 선택하여 그릴 때 사용할 마스크
8) flags : 매칭 정보 그리기 방법. 기본값은 cv2.DRAW_MATCHES_FLAGS_DEFAULT
# matching.py
import sys
import numpy as np
import cv2
# 영상 불러오기
src1 = cv2.imread('graf1.png', cv2.IMREAD_GRAYSCALE)
src2 = cv2.imread('graf3.png', cv2.IMREAD_GRAYSCALE)
if src1 is None or src2 is None:
print('Image load failed!')
sys.exit()
# 특징점 알고리즘 객체 생성 (KAZE, AKAZE, ORB 등)
feature = cv2.KAZE_create()
# feature = cv2.AKAZE_create()
# feature = cv2.ORB_create()
# 특징점 검출 및 기술자 계산
kp1, desc1 = feature.detectAndCompute(src1, None)
kp2, desc2 = feature.detectAndCompute(src2, None)
# 특징점 매칭
matcher = cv2.BFMatcher_create()
#matcher = cv2.BFMatcher_create(cv2.NORM_HAMMING)
matches = matcher.match(desc1, desc2)
print('# of kp1:', len(kp1))
print('# of kp2:', len(kp2))
print('# of matches:', len(matches))
# 특징점 매칭 결과 영상 생성
dst = cv2.drawMatches(src1, kp1, src2, kp2, matches, None)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
>>>
# of kp1: 3159
# of kp2: 3625
# of matches: 3159
: 위에서 특징점 매칭 결과를 보면 알겠지만, 모든 점들을 이었기 때문에 원본 영상이 안보일 뿐더러 어떤점이 매칭이 되었는지 확인하기가 매우 힘들다. 따라서 좋은 매칭 선별 방법을 사용해서 매칭들 중 일부만 매칭해서 특징점을 선별한다.
import sys
import numpy as np
import cv2
# 영상 불러오기
src1 = cv2.imread('graf1.png', cv2.IMREAD_GRAYSCALE)
src2 = cv2.imread('graf3.png', cv2.IMREAD_GRAYSCALE)
if src1 is None or src2 is None:
print('Image load failed!')
sys.exit()
# 특징점 알고리즘 객체 생성 (KAZE, AKAZE, ORB 등)
feature = cv2.KAZE_create()
# feature = cv2.AKAZE_create()
# feature = cv2.ORB_create()
# 특징점 검출 및 기술자 계산
kp1, desc1 = feature.detectAndCompute(src1, None)
kp2, desc2 = feature.detectAndCompute(src2, None)
# 특징점 매칭
matcher = cv2.BFMatcher_create()
#matcher = cv2.BFMatcher_create(cv2.NORM_HAMMING)
matches = matcher.match(desc1, desc2)
# 좋은 매칭 결과 선별
matches = sorted(matches, key=lambda x: x.distance)
good_matches = matches[:80]
print('# of kp1:', len(kp1))
print('# of kp2:', len(kp2))
print('# of matches:', len(matches))
print('# of good_matches:', len(good_matches))
# 특징점 매칭 결과 영상 생성
dst = cv2.drawMatches(src1, kp1, src2, kp2, good_matches, None)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
>>>
# of kp1: 3159
# of kp2: 3625
# of matches: 3159
# of good_matches: 80
import sys
import numpy as np
import cv2
# 영상 불러오기
src1 = cv2.imread('graf1.png', cv2.IMREAD_GRAYSCALE)
src2 = cv2.imread('graf3.png', cv2.IMREAD_GRAYSCALE)
if src1 is None or src2 is None:
print('Image load failed!')
sys.exit()
# 특징점 알고리즘 객체 생성 (KAZE, AKAZE, ORB 등)
feature = cv2.KAZE_create()
#feature = cv2.AKAZE_create()
#feature = cv2.ORB_create()
# 특징점 검출 및 기술자 계산
kp1, desc1 = feature.detectAndCompute(src1, None)
kp2, desc2 = feature.detectAndCompute(src2, None)
# 특징점 매칭
matcher = cv2.BFMatcher_create()
#matcher = cv2.BFMatcher_create(cv2.NORM_HAMMING)
matches = matcher.knnMatch(desc1, desc2, 2)
# 좋은 매칭 결과 선별
good_matches = []
for m in matches:
if m[0].distance / m[1].distance < 0.7:
good_matches.append(m[0])
print('# of kp1:', len(kp1))
print('# of kp2:', len(kp2))
print('# of matches:', len(matches))
print('# of good_matches:', len(good_matches))
# 특징점 매칭 결과 영상 생성
dst = cv2.drawMatches(src1, kp1, src2, kp2, good_matches, None)
cv2.namedWindow('dst', cv2.WINDOW_NORMAL)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
: 두 평면 사이의 투시 변환 (Perspective transform)
cv2.findHomography(srcPoints, dstPoints, method=None,
ransacReprojThreshold=None, mask=None, maxIters=None,
confidence=NOne) -> retval, mask
1) srcPoints : 입력 점 좌표
2) dstPoints : 결과 점 좌표
3) method : 호모그래피 행렬 계산 방법. 0, LMEDS, RANSAC, RHO 중 선택. 기본값은 0이며, 이상치가 있을 경우 RANSAC, RHO 방법 권장 [cv2.RANSAC]
4) ransacReprojThreshold : RANSAC 재투영 에러 허용치. 기본값은 3
- RANSAC : RANdom SAmple Consensus
= 이상치 (Outlier)가 많은 원본 데이터로부터 모델 파라미터를 예측하는 방법

5) maxIters : RANSAC 최대 반복 횟수. 기본값은 2000
6) retval : 호모그래피 행렬
7) mask : 출력 마스크 행렬. RANSAC, RHO 방법 사용시 Inlier로 사용된 점들을 1로 표시한 행렬
# homography.py
import sys
import numpy as np
import cv2
# Load two images to be used for homography
src1 = cv2.imread('box.png', cv2.IMREAD_GRAYSCALE)
src2 = cv2.imread('box_in_scene.png', cv2.IMREAD_GRAYSCALE)
# Check if images are loaded successfully
if src1 is None or src2 is None:
print('Image load failed!')
sys.exit()
# Create a feature point algorithm object (KAZE, AKAZE, ORB, etc.)
feature = cv2.KAZE_create()
# Alternatively, you can use AKAZE or ORB instead of KAZE
# feature = cv2.AKAZE_create()
# feature = cv2.ORB_create()
# Detect feature points and calculate descriptors for each image
kp1, desc1 = feature.detectAndCompute(src1, None)
kp2, desc2 = feature.detectAndCompute(src2, None)
# Use a feature point matcher (Brute-Force Matcher) to find matches between descriptors
matcher = cv2.BFMatcher_create()
matches = matcher.match(desc1, desc2)
# Sort matches based on their distances and select the top 80 matches
matches = sorted(matches, key=lambda x: x.distance)
good_matches = matches[:80]
# Print information about the number of feature points and matches
print('# of kp1:', len(kp1))
print('# of kp2:', len(kp2))
print('# of matches:', len(matches))
print('# of good_matches:', len(good_matches))
# Extract coordinates of feature points from the good matches
pts1 = np.array([kp1[m.queryIdx].pt for m in good_matches]
).reshape(-1, 1, 2).astype(np.float32)
pts2 = np.array([kp2[m.trainIdx].pt for m in good_matches]
).reshape(-1, 1, 2).astype(np.float32)
# Calculate homography matrix using RANSAC
H, _ = cv2.findHomography(pts1, pts2, cv2.RANSAC)
# Display matching results and draw the reference image area using homography
dst = cv2.drawMatches(src1, kp1, src2, kp2, good_matches, None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# Get the dimensions of the reference image
(h, w) = src1.shape[:2]
# Define the four corners of the reference image
corners1 = np.array([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]
).reshape(-1, 1, 2).astype(np.float32)
# Transform the corners using the calculated homography matrix
corners2 = cv2.perspectiveTransform(corners1, H)
# Shift the transformed corners to match the second image
corners2 = corners2 + np.float32([w, 0])
# Draw the polygon representing the reference image area in the second image
cv2.polylines(dst, [np.int32(corners2)], True, (0, 255, 0), 2, cv2.LINE_AA)
# Display the result
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
: 동일 장면의 사진을 자연스럽게 붙여서 한 장의 사진으로 만드는 기술
cv2.Stitcher_create(, mode=None) -> retval
1) mode : 스티칭 모드. cv2.PANORAMA 또는 cv2.SCANS 중 하나 선택. 기본값은 cv2.PANORAMA
2) retval : cv2.Stitcher 클래스 객체
cv2.Stitcher.stitch(images, pano=None) -? retval, pano
1) images : 입력 영상 리스트
2) retval : 성공하면 cv2.Stitcher_OK
3) pano : 파노라마 영상
# stitching.py
import sys
import numpy as np
import cv2
img_names = ['img1.jpg', 'img2.jpg', 'img3.jpg']
imgs = []
for name in img_names:
img = cv2.imread(name)
if img is None:
print('Image load failed!')
sys.exit()
imgs.append(img)
stitcher = cv2.Stitcher_create()
status, dst = stitcher.stitch(imgs)
if status != cv2.Stitcher_OK:
print('Stitch failed!')
sys.exit()
cv2.imwrite('output.jpg', dst)
cv2.namedWindow('dst', cv2.WINDOW_NORMAL) # 창 크기 조절
for i in range(3):
cv2.imshow('img{}'.format(i+1),imgs[i])
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()