Feature matching
Peponi │ 6/29/2025 │ 12m
Feature matching
Peponi
1. Introduction
Feature matching
은 query, train 두 이미지에 존재하는 같은 객체를 찾아내는 기술이다. 특징점으로 표현되는 두 이미지 상의 핵심 지점을 수치적으로 표현한 뒤 가장 비슷한 쌍을 찾게 되며 객체 인식, 파노라마 이미지, SLAM 등에 이 기술이 활용된다. Feature matching의 주요 단계는 다음과 같이 표현할 수 있다.
- Keypoint detection
이미지에서 엣지, 코너, 블롭 등을 찾아낸다. - Descriptor extraction
각 keypoint 주변의 픽셀 정보를 벡터 형태로 표현한다. - Descriptor matching
query, train 이미지의 descriptor를 비교하여 가장 유사한 쌍을 찾는다.
이 문서에서는 feature detection 알고리즘 중 KAZE
를 이용하여 특징점 검출 및 디스크립터 생성을 수행한 후, BFMatcher, FlannBasedMatcher를 이용하여 feature matching을 수행하는 예시를 보여준다.
실습에 사용할 이미지는 다음과 같다.


2. BFMatcher
Brute-Force matcher는 query, train 이미지의 모든 디스크립터를 순회하며 연산을 수행한다. 벡터 형태로 표현되는 두 디스크립터를 수치로 비교하여 둘 사이의 거리가 작을수록 비슷한 쌍으로 판단한다. 전수 조사를 수행하기 때문에 정확한 매칭 결과를 얻을 수 있으나 연산 속도가 떨어지는 단점이 있다.
2.1. Match
DescriptorMatcher.Match()
는 기본적인 feature matching 방법으로, 각 query 디스크립터에 대해 가장 거리가 짧은 train 디스크립터를 매칭한다.
private void BFMatching(Mat image1, Mat image2)
{
using var kaze = KAZE.Create();
using var descriptor1 = new Mat();
using var descriptor2 = new Mat();
// 특징점 검출 및 디스크립터 생성
kaze.DetectAndCompute(image1, null, out var keyPoints1, descriptor1);
kaze.DetectAndCompute(image2, null, out var keyPoints2, descriptor2);
// Brute-Force Matcher 생성
// normType : 디스크립터 간의 거리를 측정하는 방법을 지정. 실수 디스크립터는 L2를, 이진 디스크립터는 Hamming을 주로 사용
// crossCheck : true인 경우 양쪽 매칭 결과가 일치하는 것만 남김. KnnMatch()와는 같이 사용할 수 없음
using var matcher = new BFMatcher(crossCheck: true);
// BFMatcher는 전수 조사 방식으로 매칭 수행
// 각 디스크립터에 대해 가장 유사한 디스크립터를 찾아 매칭
var matches = matcher.Match(descriptor1, descriptor2);
using var detected = new Mat();
// DrawMatchesFlags.NotDrawSinglePoints 를 설정하여 매칭된 포인트만 그림
Cv2.DrawMatches(image1, keyPoints1, image2, keyPoints2, matches, detected, Scalar.All(-1), Scalar.All(-1), null, DrawMatchesFlags.NotDrawSinglePoints);
Cv2.ImShow("BFMatching", detected);
}
2.2. KnnMatch
DescriptorMatcher.KnnMatch()
는 feature matching을 수행할 수 있는 중요한 방법으로 K-Nearest Neighbors 매칭을 수행한다. DescriptorMatcher.Match()
가 query 디스크립터당 1개의 매칭을 찾아주는 반면, DescriptorMatcher.KnnMatch()
는 가장 가까운 k개의 매칭을 찾아준다. 이를 Lowe's Ratio Test와 같은 필터링 기법과 결합하면 매칭 품질을 크게 향상시킬 수 있다.
private void BFKnnMatching(Mat image1, Mat image2)
{
using var kaze = KAZE.Create();
using var descriptor1 = new Mat();
using var descriptor2 = new Mat();
// 특징점 검출 및 디스크립터 생성
kaze.DetectAndCompute(image1, null, out var keyPoints1, descriptor1);
kaze.DetectAndCompute(image2, null, out var keyPoints2, descriptor2);
// Brute-Force Matcher 생성
// normType : 디스크립터 간의 거리를 측정하는 방법을 지정. 실수 디스크립터는 L2를, 이진 디스크립터는 Hamming을 주로 사용
// crossCheck : true인 경우 양쪽 매칭 결과가 일치하는 것만 남김. KnnMatch()와는 같이 사용할 수 없음
using var matcher = new BFMatcher();
// BFMatcher는 전수 조사 방식으로 매칭 수행
// k : 각 쿼리 디스크립터에 대해 가장 가까운 k개의 트레인 디스크립터를 찾음
var matches = matcher.KnnMatch(descriptor1, descriptor2, 2);
var matched = new List<DMatch>();
// Lowe's Ratio Test
// 가장 가까운 매칭이 두 번째로 가까운 매칭 거리 * threshold보다 작을 때 잘 매칭된 것으로 간주
foreach (var match in matches)
{
// 0.8 : Threshold value
if (match.Length > 1 && match[0].Distance < 0.8 * match[1].Distance)
matched.Add(match[0]);
}
using var detected = new Mat();
// DrawMatchesFlags.NotDrawSinglePoints 를 설정하여 매칭된 포인트만 그림
Cv2.DrawMatches(image1, keyPoints1, image2, keyPoints2, matched, detected, Scalar.All(-1), Scalar.All(-1), null, DrawMatchesFlags.NotDrawSinglePoints);
Cv2.ImShow("BFKnnMatching", detected);
}
3. FlannBasedMatcher
Fast Library for Approximate Nearest Neighbors Matcher, 이하 FLANN matcher는 앞선 BFMatcher의 전수 조사와 달리 근사 조사 방법을 이용하여 매칭 속도를 향상시킨다. 근사 최근접 이웃 (Approximate Nearest Neighbors) 탐색 알고리즘을 사용하는데, 이는 Nearest neighbor search의 변형이다.
FLANN matcher 사용하는 경우 특징점을 효율적으로 조사하기 위한 인덱싱 알고리즘을 선택해야 한다. 이 떄, 실수형 디스크립터에는 KDTreeIndexParams
, 이진 디스크립터에는 LshIndexParams
를 주로 활용한다.
FLANN matcher는 근사 탐색 방법을 이용하기 때문에 실시간 또는 대용량 데이터 처리에 유리하다. 동시에 근사 탐색 방법으로 인하여 정확한 매칭이 보장되지는 않으므로 정확성을 요구하는 곳에는 사용하기 어려울 수 있다.
3.1. Match
앞선 BFMatcher의 Match()
와 동일한 방법으로 FLANN matcher 또한 DescriptorMatcher.Match()
메서드를 사용할 수 있다. 메서드는 각 query 디스크립터에 대해 가장 거리가 짧은 train 디스크립터를 매칭하여 반환한다.
private void FLANNMatching(Mat image1, Mat image2)
{
using var kaze = KAZE.Create();
using var descriptor1 = new Mat();
using var descriptor2 = new Mat();
// 특징점 검출 및 디스크립터 생성
kaze.DetectAndCompute(image1, null, out var keyPoints1, descriptor1);
kaze.DetectAndCompute(image2, null, out var keyPoints2, descriptor2);
// 실수형 디스크립터에 사용
// trees : K-D 트리의 수를 지정. 값이 클수록 정확도 높아짐
var indexParams = new OpenCvSharp.Flann.KDTreeIndexParams(5);
// 이진 디스크립터에 사용
// tableNumber : 해시 테이블의 수. 클수록 정확도 높아짐
// keySize : 해시 함수의 길이. 값이 클수록 정확도 높아짐
// multiProbeLevel : Multi-probing 수준 지정. 클수록 정확도 높아짐
// var binaryIndexParams = new OpenCvSharp.Flann.LshIndexParams(20, 15, 2);
// 이웃 검색 시 사용할 사용할 파라미터
// checks : 확인할 인덱스 노드의 수. 클수록 정확도 높아짐
// eps : 근사 검색 설정값 (0 ~ 1). 값이 작을수록 더 정확한 검색을 수행
// sorted : 결과를 정렬 후 반환
var searchParams = new OpenCvSharp.Flann.SearchParams(50);
// Fast Library for Approximate Nearest Neighbors Matcher 생성
using var matcher = new FlannBasedMatcher(indexParams, searchParams);
// FlannBasedMatcher는 근사 최근접 이웃 탐색 알고리즘 사용
// 각 디스크립터에 대해 가장 유사한 디스크립터를 찾아 매칭
var matches = matcher.Match(descriptor1, descriptor2);
using var detected = new Mat();
// DrawMatchesFlags.NotDrawSinglePoints 를 설정하여 매칭된 포인트만 그림
Cv2.DrawMatches(image1, keyPoints1, image2, keyPoints2, matches, detected, Scalar.All(-1), Scalar.All(-1), null, DrawMatchesFlags.NotDrawSinglePoints);
Cv2.ImShow("FLANNMatching", detected);
}
3.2. KnnMatch
앞선 BFMatcher의 KnnMatch()
와 마찬가지로 FLANN matcher 또한 DescriptorMatcher.KnnMatch()
를 동일한 방법으로 사용할 수 있다.
private void FLANNKnnMatching(Mat image1, Mat image2)
{
using var kaze = KAZE.Create();
using var descriptor1 = new Mat();
using var descriptor2 = new Mat();
// 특징점 검출 및 디스크립터 생성
kaze.DetectAndCompute(image1, null, out var keyPoints1, descriptor1);
kaze.DetectAndCompute(image2, null, out var keyPoints2, descriptor2);
// 실수형 디스크립터에 사용
// trees : K-D 트리의 수를 지정. 값이 클수록 정확도 높아짐
var indexParams = new OpenCvSharp.Flann.KDTreeIndexParams(5);
// 이진 디스크립터에 사용
// tableNumber : 해시 테이블의 수. 클수록 정확도 높아짐
// keySize : 해시 함수의 길이. 값이 클수록 정확도 높아짐
// multiProbeLevel : Multi-probing 수준 지정. 클수록 정확도 높아짐
// var binaryIndexParams = new OpenCvSharp.Flann.LshIndexParams(20, 15, 2);
// 이웃 검색 시 사용할 사용할 파라미터
// checks : 확인할 인덱스 노드의 수. 클수록 정확도 높아짐
// eps : 근사 검색 설정값 (0 ~ 1). 값이 작을수록 더 정확한 검색을 수행
// sorted : 결과를 정렬 후 반환
var searchParams = new OpenCvSharp.Flann.SearchParams(50);
// Fast Library for Approximate Nearest Neighbors Matcher 생성
using var matcher = new FlannBasedMatcher(indexParams, searchParams);
// FlannBasedMatcher는 근사 최근접 이웃 탐색 알고리즘 사용
// k : 각 쿼리 디스크립터에 대해 가장 가까운 k개의 트레인 디스크립터를 찾음
var matches = matcher.KnnMatch(descriptor1, descriptor2, 2);
var matched = new List<DMatch>();
// Lowe's Ratio Test
// 가장 가까운 매칭이 두 번째로 가까운 매칭 거리 * threshold보다 작을 때 잘 매칭된 것으로 간주
foreach (var match in matches)
{
// 0.8 : Threshold value
if (match.Length > 1 && match[0].Distance < 0.8 * match[1].Distance)
matched.Add(match[0]);
}
using var detected = new Mat();
// DrawMatchesFlags.NotDrawSinglePoints 를 설정하여 매칭된 포인트만 그림
Cv2.DrawMatches(image1, keyPoints1, image2, keyPoints2, matched, detected, Scalar.All(-1), Scalar.All(-1), null, DrawMatchesFlags.NotDrawSinglePoints);
Cv2.ImShow("FLANNKnnMatching", detected);
}