[2023-05-01] 3. 특징점 매칭
특징점 매칭(feature point matching, keypoint matching)
-
두 영상에서 추출한 특징점 기술자를 비교하여 유사한 기술자끼리 선택하는 작업
-
왼쪽 영상에서의 특징점과 오른쪽 영상에서의 특징점을 비교함
- 왼쪽 영상의 3번째 특징점은 오른쪽 영상에서 찾을 수 없어서 그 중에서 가장 비슷한 특징점으로 표현함
- 만약에 왼쪽에 1000개가 검출되고, 오른쪽에는 700개가 검출되었으면 700,000번의 비교가 필요함
- 특징 벡터 유사도 측정 방법
- 실수 특징 벡터 : L2 Norm 사용
- 이진 특징 벡터 : 해밍 거리 사용
OpenCV 특징점 매칭 클래스
- match() : 여러개 중 가장 비슷한 1개를 반환함
- knnMatch() : 가장 비슷한 K개를 반환함
-
radiusMatch() : Distance에 대한 Threshold를 지정하면 그 임계값보다 작은 모든 매칭된 결과를 반환함
- BF : Brute-Force(전수 조사)
- 왼쪽에 1000개 오른쪽에 700개가 있으면 총 700000개 비교하는 것이 전수 조사
- Flann : Fast Library for Approximate Nearest Neighbor
- 내부적으로 K-D Tree 자료 구조 알고리즘을 사용해서 완전히 정확한 매칭 결과는 반환하지 않지만 상당히 빠르게 동작하는 방법
- 찾고자하는 영상에서 특징점의 개수가 상당히 많아졌을 때(BF 방법이 부담될정도)일때 사용하는 것이 좋음
특징점 매칭 (최선의 매칭 반환)
void DescriptorMatcher::match(InputArray queryDescriptors, InputArray trainDescriptors, std::vector<DMatch>& matches, InputArray mask = noArray()) const
- queryDescriptors : 질의 기술자 집합
- trainDescriptors : 훈련 기술자 집합
- matches : 매칭 결과
- mask : 서로 매칭 가능한 질의 기술자와 훈련 기술자를 지정할 때 사용
특징점 매칭 (상위 k개 매칭 반환)
void DescriptorMatcher::knnMatch(InputArray queryDescriptors, InputArray trainDescriptors, std::vector<std::vector<DMatch>>& matches, int k InputArray mask = noArray()) const
- queryDescriptors : 질의 기술자 집합
- trainDescriptors : 훈련 기술자 집합
- matches : 매칭 결과. matches[i]는 k보다 작거나 같은 개수의 DMatch 결과를 저장
- k : 찾고자하는 최선의 매칭 결과 개수
- mask : 서로 매칭 가능한 질의 기술자와 훈련 기술자를 지정할 때 사용
- compactResult : mask 행렬이 비어있지 않을 때 사용되는 파라미터로 false 이면 matches 벡터의 크기는 queryDescriptors 행렬의 행 개수와 같음
특징점 매칭 결과 표현을 위한 DMatch클래스
특징점 매칭 결과 영상 생성 함수
void good_matching()
{
Mat src1 = imread(file1, IMREAD_GRAYSCALE);
Mat src2 = imread(file2, IMREAD_GRAYSCALE);
if (src1.empty() || src2.empty()) {
cerr << "Image load failed!" << endl;
return;
}
vector<KeyPoint> keypoints1, keypoints2;
Mat desc1, desc2;
feature->detectAndCompute(src1, Mat(), keypoints1, desc1);
feature->detectAndCompute(src2, Mat(), keypoints2, desc2);
Ptr<DescriptorMatcher> matcher = BFMatcher::create();
Mat dst;
drawMatches(src1, keypoints1, src2, keypoints2, good_matches, dst,
Scalar::all(-1), Scalar::all(-1), vector<char>(),
DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
- 왼쪽에서 1098개 오른쪽에서 732개의 특징점들이 검출되었음
- 왼쪽에 366개의 같은 특징점이 아니지만 그나마 비슷한 특징점에 매칭이되서 결과가 지저분하게 보임
- 매칭된 것들 중에서 잘된 매칭들을 선별해서 사용할 필요가 있어 보임
좋은 매칭 선별 방법
좋은 매칭 선별 방법#1
- 가장 좋은 매칭 결과에서 distance 값이 작은 것 N개를 사용
- DMatch::distance 값을 기준으로 정렬 후 상위 N개 선 택
- DMatch 클래에스 크기 비교 연산자(<) 오버로딩이 dstance 멤버 변수를 사용하도록 되어 있음
void good_matching()
{
Mat src1 = imread(file1, IMREAD_GRAYSCALE);
Mat src2 = imread(file2, IMREAD_GRAYSCALE);
if (src1.empty() || src2.empty()) {
cerr << "Image load failed!" << endl;
return;
}
vector<KeyPoint> keypoints1, keypoints2;
Mat desc1, desc2;
feature->detectAndCompute(src1, Mat(), keypoints1, desc1);
feature->detectAndCompute(src2, Mat(), keypoints2, desc2);
Ptr<DescriptorMatcher> matcher = BFMatcher::create();
vector<DMatch> matches;
matcher->match(desc1, desc2, matches);
std::sort(matches.begin(), matches.end());
vector<DMatch> good_matches(matches.begin(), matches.begin() + 80);
// distance값이 작은 상위 80개를 선택함
Mat dst;
drawMatches(src1, keypoints1, src2, keypoints2, good_matches, dst,
Scalar::all(-1), Scalar::all(-1), vector<char>(),
DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
- 150도로 레나 이미지를 회전해도 매칭이 깨끗하게 잘되는 것을 확인할 수 있음
좋은 매칭 선별 방법#2
- knnMatch()함수를 사용하여 두 개의 매칭 결과 반환
- 가장 좋은 매칭 결과의 distance 값과 두 번째로 좋은 매칭 결과의 distance 값의 비율을 계산
- 그 distance 값의 차이가 크면 잘 된 것으로 판단하고 distance값의 차이가 비슷하게 나오면 잘 안된 것으로 판단함
- distance 값이 비슷하게 나왔다는 것은 둘다 아예 엄한 곳으로 2개가 매칭된 것임
- 그 distance 값의 차이가 크면 잘 된 것으로 판단하고 distance값의 차이가 비슷하게 나오면 잘 안된 것으로 판단함
- 이 비율이 임계값(e.g. 0.7)보다 작으면 선택
void good_matching()
{
Mat src1 = imread(file1, IMREAD_GRAYSCALE);
Mat src2 = imread(file2, IMREAD_GRAYSCALE);
if (src1.empty() || src2.empty()) {
cerr << "Image load failed!" << endl;
return;
}
vector<KeyPoint> keypoints1, keypoints2;
Mat desc1, desc2;
feature->detectAndCompute(src1, Mat(), keypoints1, desc1);
feature->detectAndCompute(src2, Mat(), keypoints2, desc2);
Ptr<DescriptorMatcher> matcher = BFMatcher::create();
vector<vector<DMatch>> matches;
matcher->knnMatch(desc1, desc2, matches, 2);
vector<DMatch> good_matches;
for (const auto& m : matches) {
if (m[0].distance / m[1].distance < 0.7)
good_matches.push_back(m[0]);
}
cout << good_matches.size() << endl;
Mat dst;
drawMatches(src1, keypoints1, src2, keypoints2, good_matches, dst,
Scalar::all(-1), Scalar::all(-1), vector<char>(),
DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
- 454개의 matching이 된 것을 알 수 있음
- 전체적으로 matching이 잘 되었지만, 일부 몇는 이상하게 매칭된 것을 확인할 수 있음