비전 신호 처리II_특징점 매칭

JuHwan Kim·2022년 4월 8일
0

Vision Signal Processing

목록 보기
4/6

특징점 매칭

특징점 매칭(matching)이란 두 영상에서 추출한 특징점 기술자를 비교하여 서로 비슷한 특징점을 찾는 작업을 의미함

특히 크기 불변 특징점으로부터 구한 기술자를 매칭하면 크기와 회전에 강인한 영상 매칭을 수행할 수 있음

크기 불변 특징점 : 크기가 커지거나, 작아져도 코너로 판별이 되는 특징점

DMatch 클래스는 한 장의 영상에서 추출한 특징점과 다른 한 장의 영상, 또는 여러 영상에서 추출한 특징점 사이의 매칭 정보를 표현할 수 있음

queryIdx : 질의 기술자 번호
trainIdx : 훈련 기술자 번호
imgIdx : 훈련 영상 번호
distance : 두 기술자 사이의 거리 (유클리드 거리 사용)

OpenCV의 특징점 매칭 클래스는 DescriptorMatcher 클래스를 상속받아 만들어짐

DescriptorMatcher 클래스는 match(), knnMatch(), radiusMatch() 등의 가상 멤버 함수를 가지고 있는 추상 클래스임

match() 함수는 가장 비슷한 기술자 쌍을 하나 찾고, knnMatch() 함수는 비슷한 기술자 쌍 k개를 찾음
radiusMatch() 함수는 지정한 거리 반경 안에 있는 기술자 쌍을 모두 찾아 반환함
BFMatcher : 질의 기술자 집합에 있는 모든 기술자와 훈련 기술자 집합에 있는 모든 기술자 사이의 거리를 계산 -> 이 중 가장 거리가 작은 기술자를 찾아 매칭하는 방식
FlannBasedMatcher : 근사화된 최근방 이웃 알고리즘

예제1_특징점 매칭(1)

실제 매칭을 수행하는 BFMatcher 또는 FlannBasedMatcher 클래스 객체를 생성하려면 각 클래스에 정의되어 있는 create() 정적 멤버 함수를 사용해야함
DescriptorMatcher::match() 함수는 desc1 에 포함된 각각의 기술자와 가장 유사한 기술자를 desc2에서 찾음
그 결과를 vector<DMatch> 타입의 변수 matches에 저장함
drawMatches() 함수는 두 매칭 입력 영상을 가로로 이어 붙임

void keypoint_matching()
{
	Mat src1 = imread("KIAPI_CAR_ROI.png", IMREAD_GRAYSCALE);
	Mat src2 = imread("KIAPI_CAR.png", IMREAD_GRAYSCALE);

	if (src1.empty() || src2.empty())
	{
		cerr << "Image load failed" << endl;
		return;
	}

	Ptr<Feature2D> feature = ORB::create();

	vector<KeyPoint> keypoints1, keypoints2;
	Mat desc1, desc2;
	feature->detectAndCompute(src1, Mat(), keypoints1, desc1); // 특징점 추출
	feature->detectAndCompute(src2, Mat(), keypoints2, desc2); // 특징점 추출
	cout << "desc1.size(): " << desc1.size() << endl;
	cout << "desc2.size(): " << desc2.size() << endl;

	Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING);

	vector<DMatch> matches;
	matcher->match(desc1, desc2, matches);

	Mat dst;
	drawMatches(src1, keypoints1, src2, keypoints2, matches, dst);
	imshow("dst", dst);
	waitKey();
	destroyAllWindows();
}

int main()
{
	keypoint_matching();
	return 0;
}

dst 창은 box.pngbox_in_scene.png 영상을 가로로 이어 붙인 영상 위에 특징점 매칭 결과를 다양한 색상의 직선으로 표시한 결과
확대 영상에서 추출한 모든 특징점 기술자에 대해 가장 유사한 원본 영상의 특징점 기술자를 찾아 직선을 그렸기 때문에 매칭 결과가 매우 복잡하게 나타냄
매칭 결과 중 상당수는 완전히 다른 특징점으로 잘못 매칭됨

기하학적 변형과 조명 변화 등 환경문제로 인하여 확대 영상에서 추출한 특징점이 모두 제대로 매칭되기는 어려움이 존재
전체 매칭 결과에서 잘못 매칭된 결과는 제외하고 제대로 매칭되었다고 판단되는 결과만 선별하여 사용해야 함
DMatch 클래스는 기술자 사이의 거리를 표현하는 distance를 멤버 변수로 가지고 있음
Distance 값이 작을 수록 매칭 결과가 좋으며, distance 값이 클 수록 매칭 결과가 나빠짐 => 정렬 함수를 이용하여 distance 값 정렬

예제2_특징점 매칭(2)

앞서 코드의 keypoint_matching() 함수에서 두 영상에서 추출한 기술자 desc1과 desc2를 매칭함
매칭 결과를 distance 값 기준으로 오름차순 정렬
정렬 후 matches에 저장된DMatch 객체들은 distance 값이 작은 순서대로 앞쪽에 위치하게 됨
matches의 범위를 설정하여 good_matches 변수에는 전체 매칭 결과 matches 중에서 매칭이 잘 된 50개의 매칭 결과만 저장

void keypoint_matching()
{
	Mat src1 = imread("KIAPI_CAR_ROI.png", IMREAD_GRAYSCALE);
	Mat src2 = imread("KIAPI_CAR.png", IMREAD_GRAYSCALE);

	if (src1.empty() || src2.empty())
	{
		cerr << "Image load failed" << endl;
		return;
	}

	Ptr<Feature2D> feature = ORB::create();

	vector<KeyPoint> keypoints1, keypoints2;
	Mat desc1, desc2;
	feature->detectAndCompute(src1, Mat(), keypoints1, desc1);
	feature->detectAndCompute(src2, Mat(), keypoints2, desc2);
	cout << "desc1.size(): " << desc1.size() << endl;
	cout << "desc2.size(): " << desc2.size() << endl;

	Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING);

	vector<DMatch> matches;
	matcher->match(desc1, desc2, matches);
	sort(matches.begin(), matches.end());
	vector<DMatch> good_matches(matches.begin(), matches.begin() + 50);

	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();
}

int main()
{
	keypoint_matching();
	return 0;
}

정렬 후 결과가 많이 개선 됨을 확인할 수 있음

호모그래피와 영상 매칭

호모그래피는 3차원 공간상의 평면을 서로 다른 시점에서 바라봤을 때 획득되는 영상 사이의 관계를 나타내는 용어

호모그래피는 수학적으로 하나의 평면을 다른 평면으로 투시 변환(perspective transform)하는 것과 같은 관계에 있음

실제적인 연산 관점에서 호모그래피는 투시 변환과 같기 때문에 호모그래피는 3×3 실수 행렬로 표현할 수 있음
투시 변환을 구할 때와 마찬가지로 네 개의 대응되는 점의 좌표 이동 정보가 있으면 호모그래피 행렬을 구할 수 있음
특징점 매칭 정보로부터 호모그래피를 구하는 경우에는 서로 대응되는 점 개수가 네 개보다 훨씬 많음
이러한 경우에는 투시 변환 시 에러가 최소가 되는 형태의 호모그래피 행렬을 구해야 함
OpenCV는 두 영상 평면에서 추출된 특징점 매칭 정보로부터 호모그래피를 계산할 때 사용할 수 있는 findHomography() 함수를 제공

투시 변환 시 에러가 최소가 되는 형태의 호모그래피 행렬을 구해야 함
최소자승법, 최소 메디안 제곱(LMEDS), RANSAC, PROSAC 등 최소 오차를 위한 알고리즘 포함
호모그래피의 경우 원근 변환 등 많은 곳에서 적용 됨

예제3_호모그래피와 영상 매칭

find_homography() 함수는 확대 된 차량 영상과 전체 영상에서 추출한 ORB 특징점을 이용하여 매칭을 수행함
매칭 정보로부터 두 영상 사이의 호모그래피를 계산함
계산된 호모그래피를 이용하여 전체 영상에서 확대된 차량이 있는 위치를 검출함
해당 코드는 이전 good_matching 함수와 같음
findHomography() 함수를 이용하여 pts1 점들이 pts2 점들로 이동하는 호모그래피 행렬을 계산하여 H에 저장
RANSAC 알고리즘을 이용하여 호모그래피 행렬 H를 계산
확대 된 차량 영상의 네 모서리점을 corners1에 저장한 후 호모그래피 행렬을 이용하여 이 점들이 이동하는 위치를 계산

void find_homography() // 호모 그래피는 대응되는 쌍이 많을 수록 좋음 
{
	Mat src1 = imread("KIAPI_CAR_R_ROI.png", IMREAD_GRAYSCALE);
	Mat src2 = imread("KIAPI_CAR.png", IMREAD_GRAYSCALE);

	if (src1.empty() || src2.empty())
	{
		cerr << "Image load failed" << endl;
		return;
	}

	Ptr<Feature2D> feature = ORB::create();

	vector<KeyPoint> keypoints1, keypoints2;
	Mat desc1, desc2;
	feature->detectAndCompute(src1, Mat(), keypoints1, desc1); // 특징점 추출
	feature->detectAndCompute(src2, Mat(), keypoints2, desc2); // 특징점 추출

	Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING);

	vector<DMatch> matches;
	matcher->match(desc1, desc2, matches);

	sort(matches.begin(), matches.end());
	vector<DMatch> good_matches(matches.begin(), matches.begin() + 50); // 거리값이 작은 것만 50개 추림

	Mat dst;
	drawMatches(src1, keypoints1, src2, keypoints2, good_matches, dst, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
	vector<Point2f> pts1, pts2;
	for (size_t i = 0; i < good_matches.size(); i++)
	{
		pts1.push_back(keypoints1[good_matches[i].queryIdx].pt);
		pts2.push_back(keypoints2[good_matches[i].trainIdx].pt);
	}

	Mat H = findHomography(pts1, pts2, RANSAC); //오차 값을 줄이는 RANSAC 알고리즘을 사용

	vector<Point2f> corners1, corners2; // 4개의 점을 뽑아 호모그래피를 적용하면 바운딩 박스가 만들어 짐
	corners1.push_back(Point2f(0, 0));
	corners1.push_back(Point2f(src1.cols - 1.f, 0));
	corners1.push_back(Point2f(src1.cols - 1.f, src1.rows - 1.f));
	corners1.push_back(Point2f(0, src1.rows - 1.f));
	perspectiveTransform(corners1, corners2, H);

	vector<Point> corners_dst;
	for (Point2f pt : corners2)
	{
		corners_dst.push_back(Point(cvRound(pt.x + src1.cols), cvRound(pt.y)));
	}

	polylines(dst, corners_dst, true, Scalar(0, 255, 0), 2, LINE_AA);

	imshow("dst", dst);

	waitKey();
	destroyAllWindows();
}

int main()
{
	find_homography();
	return 0;
}

확대 된 차량 영상의 네 모서리 점의 위치가 전체 영상에서 어디로 이동하는지를 찾아내어 녹색 실선으로 표시함
일부 잘못 매칭된 특징점들이 여럿 있음에도 스낵 박스 위치를 제대로 찾아내는 것을 확인할 수 있음

템플릿 매칭

템플릿(template)은 찾고자 하는 대상이 되는 작은 크기의 영상을 의미함

입력 영상에서 작은 크기의 부분 영상 위치를 찾아내고 싶은 경우에 주로 템플릿 매칭(template matching)기법을 사용함
템플릿 매칭은 작은 크기의 템플릿 영상을 입력 영상 전체 영역에 대해 이동하면서 가장 비슷한 위치를 수치적으로 찾아내는 방식임

템플릿 영상을 입력 영상 전체 영역에 대해 이동하면서 템플릿 영상과 입력 영상 부분 영상과의
유사도(similarity) 또는 비유사도(dissimilarity)를 계산

유사도 : 비슷한 영역일수록 값이 커짐, 비유사도 : 비슷한 영역일수록 값이 작아짐

OpenCV에서는 matchTemplate() 함수를 사용하여 템플릿 매칭을 수행할 수 있음

유사도 계산 방법
TM_SQDIFF(제곱차 매칭방법) : 두 영상이 완벽하게 일치하면 0, 유사하지 않으면 0보다 큰 양수
TM_CCORR(상관관계 매칭방법) : 두 영상이 유사하면 큰 양수, 유사하지 않으면 작은 값
TM_CCOEFF(상관계수 매칭방법) : 두 영상이 유사하면 큰 양수, 유사하지 않으면 에 가까운 양수 혹은 음수
TM_SQDIFF_NORMED (NORMED : 정규화, 밝기 차이 영향을 줄여 줌)
TM_CCORR_NORMED : 0에서 1 사이의 실수
TM_CCOEFF_NORMED : -1에서 1사이의 실수, CCORR_NORMED와 함께 1에 가까울수록 유사도 ↑
TM_CCOEFF_NORMED가 제일 많이 사용됨

예제4_템플릿 매칭

template_matching() 함수는 imread() 함수로 두 장의 영상을 불러와서 템플릿 매칭을 수행
유사도 계산 결과와 템플릿 매칭 결과를 화면에 출력
유사도 계산을 위해 정규화된 상관계수 매칭방법 적용
유사도 최대 값 위치 확인 (minMaxLoc())
템플릿 매칭 결과 확인

void template_matching()
{
	Mat img = imread("KIAPI_CAR.png", IMREAD_COLOR);
	Mat templ = imread("KIAPI_tire.png", IMREAD_COLOR);
	
	if (img.empty() || templ.empty())
	{
		cerr << "Image load failed" << endl;
		return;
	}

	img = img + Scalar(50, 50, 50);

	Mat noise(img.size(), CV_32SC3);
	randn(noise, 0, 10);
	add(img, noise, img, Mat(), CV_8UC3);

	Mat res, res_norm;
	matchTemplate(img, templ, res, TM_CCOEFF_NORMED);
	normalize(res, res_norm, 0, 255, NORM_MINMAX, CV_8U);

	double maxv;
	Point maxloc;
	minMaxLoc(res, 0, &maxv, 0, &maxloc);
	cout << "maxv : " << maxv << endl;

	rectangle(img, Rect(maxloc.x, maxloc.y, templ.cols, templ.rows), Scalar(0, 0, 255), 2);

	imshow("templ", templ);
	imshow("res_norm", res_norm);
	imshow("img", img);

	waitKey(0);
	destroyAllWindows();
}

int main()
{
	template_matching();
	return 0;
}

영상을 50 밝게하고 표준 편차가 10인 가우시안 잡음을 추가한 후 템플릿 매칭 수행
res_norm에서 가장 밝게 빛나는 부분이 템플릿 영상과 가장 유사한 영역 (maxv : 0.991502)
영상이 밝아지고 잡음이 추가되었지만 타이어부분이 잘 검출됨을 확인 가능

profile
소소한 개발자

0개의 댓글