특징점 매칭(feature point matching, keypoint matching)

  • 두 영상에서 추출한 특징점 기술자를 비교하여 유사한 기술자끼리 선택하는 작업

  • 왼쪽 영상에서의 특징점과 오른쪽 영상에서의 특징점을 비교함

    • 왼쪽 영상의 3번째 특징점은 오른쪽 영상에서 찾을 수 없어서 그 중에서 가장 비슷한 특징점으로 표현함
    • 만약에 왼쪽에 1000개가 검출되고, 오른쪽에는 700개가 검출되었으면 700,000번의 비교가 필요함

image

  • 특징 벡터 유사도 측정 방법
    • 실수 특징 벡터 : L2 Norm 사용
    • 이진 특징 벡터 : 해밍 거리 사용
OpenCV 특징점 매칭 클래스

image

  • 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클래스

image

특징점 매칭 결과 영상 생성 함수

image

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개의 같은 특징점이 아니지만 그나마 비슷한 특징점에 매칭이되서 결과가 지저분하게 보임
    • 매칭된 것들 중에서 잘된 매칭들을 선별해서 사용할 필요가 있어 보임

image

좋은 매칭 선별 방법

좋은 매칭 선별 방법#1

  • 가장 좋은 매칭 결과에서 distance 값이 작은 것 N개를 사용
  • DMatch::distance 값을 기준으로 정렬 후 상위 N개 선 택
  • DMatch 클래에스 크기 비교 연산자(<) 오버로딩이 dstance 멤버 변수를 사용하도록 되어 있음

image

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도로 레나 이미지를 회전해도 매칭이 깨끗하게 잘되는 것을 확인할 수 있음

image

좋은 매칭 선별 방법#2

  • knnMatch()함수를 사용하여 두 개의 매칭 결과 반환
  • 가장 좋은 매칭 결과의 distance 값과 두 번째로 좋은 매칭 결과의 distance 값의 비율을 계산
    • 그 distance 값의 차이가 크면 잘 된 것으로 판단하고 distance값의 차이가 비슷하게 나오면 잘 안된 것으로 판단함
      • distance 값이 비슷하게 나왔다는 것은 둘다 아예 엄한 곳으로 2개가 매칭된 것임
  • 이 비율이 임계값(e.g. 0.7)보다 작으면 선택

image

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이 잘 되었지만, 일부 몇는 이상하게 매칭된 것을 확인할 수 있음

image

image