본문 바로가기

study/영상처리 & opencv

[opencv] 3개의 사진 Panorama(image stitching)

 

Panorama 구현 과정

1. Image stitching을 수행할 이미지들을 획득한 후 로드 한다. Grayscale로 변환해서 사용한다.

2. SURF 알고리즘을 사용하여 각 이미지의 특징점을 추출한 후 이미지들 간의 특징점을 비교하여 좋은 특징점들끼리 매칭시킨다.

3. 이미지들을 하나의 평면에 투영시키기 위해서 이미지 간의 변환관계(Homography)를 계산한다. 이때 모델 파라미터의 예측을 방해하는 대응점(Outlier)로부터 정확한 변환관계를 구축하기 위해 RANSAC 알고리즘을 사용한다.

4. 앞에서 계산한 Homography를 사용하여 이미지에 회전 변환을 적용하여 Image stitching을 실행한다.

 

주요 알고리즘 이론 : SURF, RANSAC, Homography

 

환경 : opencv version 3.4.3, C++

 

이미지는 동일 스마트폰(iphone8)로 같은 자리에서 몸을 돌리며 겹치는 부분이 있게 촬영

image stitching을 진행할 이미지 사진 3개 (우리집 거실 찍찍)

2번째 사진(가운데 위치한 사진)이 중심이 되므로 이 사진을 기준으로 양 옆에 1번째 사진과 3번째 사진을 stitching한다.

코드는 왼쪽 사진을 기준으로 Homography를 사용하여 오른쪽 사진에 회전변환을 적용시킨다.

따라서 1번째 사진과 2번째 사진을 실행할 때에는 두 사진을 좌우반전 시켜서 코드를 실행시킨 후 다시 좌우반전 시켜주었다. 

이 이후 1, 2번째 사진을 합친 이미지와 3번째 사진의 stitching과정을 진행했다.

 

1. Image stitching을 수행할 이미지들을 획득한 후 로드

이미지 로드

2. SURF 알고리즘을 사용하여 각 이미지의 특징점을 추출, 이미지들 간의 특징점을 비교하여 좋은 특징점들끼리 매칭

good matches를 선택하기 위해서 두 개의 특징점의 distance값을 구한다.(실제 유클리디언 거리가 아닌 특징점의 descriptor(기술자) 정보의 거리라고 한다.) distance가 작을수록 매칭이 잘 된 것이다.

이때 내가 설정한 기준은 min의 값의 3배 또는 good_matches.size() <= 60 둘 중 어느 한 기준을 만족한다면 goodmatch로 인정해주었다.

SURF 를 활용한 특징점 추출
추출한 특징점끼리 매칭한 모든 선
good matching 찾기

3. 이미지들을 하나의 평면에 투영시키기 위해서 이미지 간의 변환관계(Homography)를 계산

이때 모델 파라미터의 예측을 방해하는 Outlier로부터 정확한 변환관계를 구축하기 위해 RANSAC 알고리즘 사용

구해진 Homography(Projective Transformation)

4. 앞에서 계산한 Homography를 사용하여 이미지에 회전 변환을 적용하여 Image stitching을 실행한다.

구해진 Homography를 가지고 이미지 wrap

+ 추가로 여백부분(검은색)을 없애기 위해 코드를 작성했다.

위의 wrap 사진에서도 보이듯이 사진의 윗부분이 변환을 하면서 잘리게 된다.

그 부분을 해결하지 못해서 아쉬웠다. 

 

중요한 코드

//SURF이용해서 특징점 추출
int nMinHessian = 300; // threshold (한계점)
Ptr<SurfFeatureDetector> Detector = SURF::create(nMinHessian);

vector <KeyPoint> vtKeypointsObject, vtKeypointsScene;

Detector->detect(matGrayLImage, vtKeypointsObject);
Detector->detect(matGrayRImage, vtKeypointsScene);

 

//descriptor(기술자)들 사이의 매칭 결과를 matches에 저장한다.
Ptr<SurfDescriptorExtractor> Extractor = SURF::create();

Mat matDescriptorsObject, matDescriptorsScene;

//descriptor 생성 
Extractor->compute(matGrayLImage, vtKeypointsObject, matDescriptorsObject);
Extractor->compute(matGrayRImage, vtKeypointsScene, matDescriptorsScene);


FlannBasedMatcher Matcher; //kd트리를 사용하여 매칭을 빠르게 수행
vector <DMatch> matches;
Matcher.match(matDescriptorsObject, matDescriptorsScene, matches);

 

//good matching 고르기

// 두 개의 keypoint 사이에서 min distance를 계산
	for (int i = 0; i < matDescriptorsObject.rows; i++) {
		dDistance = matches[i].distance;
		if (dDistance < dMinDist) dMinDist = dDistance;
	}

//match의 distance 값이 작을수록 matching이 잘 된 것
//min의 값의 3배 또는 good_matches.size() > 60 까지만 goodmatch로 인정해준다.
	vector<DMatch>good_matches;
	int distance = 10;
	do {
		vector<DMatch>good_matches2;
		for (int i = 0; i < matDescriptorsObject.rows; i++) {
			if (matches[i].distance < distance * dMinDist)
				good_matches2.push_back(matches[i]);
		}
		good_matches = good_matches2;
		distance -= 1;
	} while (distance != 2 && good_matches.size() > 60);

 

// goodmatch에서의 keypoint를 저장
	for (int i = 0; i < good_matches.size();i++) {
		obj.push_back(vtKeypointsObject[good_matches[i].queryIdx].pt);
		scene.push_back(vtKeypointsScene[good_matches[i].trainIdx].pt);
	}
    
 // Homography 
 ////RANSAC기법을 이용하여 첫 번째 매개변수와 두번째 매개변수 사이의 3*3 크기의 투영행렬변환 H를 구한다
	Mat HomoMatrix = findHomography(scene, obj, CV_RANSAC);
    
//Homograpy matrix를 사용하여 이미지를 삐뚤게
	Mat matResult;
	warpPerspective(matRightImage, matResult, HomoMatrix, Size(matLeftImage.cols*2, matLeftImage.rows*1.2), INTER_CUBIC);

panorama 전체 코드는 아래의 github에 있다.

 

mingxoxo/open-cv

Contribute to mingxoxo/open-cv development by creating an account on GitHub.

github.com

 

참고 : 책 <컴퓨터 비전 Computer Vision>, 저자 오일석