- 본 문제의 가정: 사진은 직접 코드에 입력해준다.
본코드
#pragma warning(disable:4700);
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/nonfree/features2d.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/flann/flann.hpp>
#include <opencv2/opencv.hpp>
#include <stdio.h>
#include <iostream>
using namespace cv;
Mat MakePano(Mat *imgArray, int num);
int main() {
Mat result;
int num = 3;
Mat imgArray[3];
imgArray[0] = imread("C:/Users/Administrator/desktop/ex/0.jpg");
imgArray[1] = imread("C:/Users/Administrator/desktop/ex/00.jpg");
imgArray[2] = imread("C:/Users/Administrator/desktop/ex/000.jpg");
result = MakePano(imgArray, num);
imshow("res", result);
waitKey();
return 0;
}
Mat MakePano(Mat *imgArray, int num)
{
Mat mainPano = imgArray[0];
char buf[1000];
for (int i = 1; i < num; i++)
{
Mat gray_mainImg, gray_objImg;
cvtColor(mainPano, gray_mainImg, COLOR_RGB2GRAY);
cvtColor(imgArray[i], gray_objImg, COLOR_RGB2GRAY);
SiftFeatureDetector detector(0.3);
vector<KeyPoint> point1, point2;
Mat pointmat1, pointmat2;
detector.detect(gray_mainImg, point1);
detector.detect(gray_objImg, point2);
SiftDescriptorExtractor extractor;
Mat descriptor1, descriptor2;
extractor.compute(gray_mainImg, point1, pointmat1);
extractor.compute(gray_objImg, point2, pointmat2);
FlannBasedMatcher matcher;
vector<DMatch> matches;
matcher.match(pointmat1, pointmat2, matches);
double mindistance = 5000;
double distance;
for (int i = 0; i < pointmat1.rows; i++) {
distance = matches[i].distance;
if (mindistance > distance)mindistance = distance;
}
vector<DMatch>goodmatch;
for (int i = 0; i < pointmat1.rows; i++) {
if (matches[i].distance < 5 * mindistance)
goodmatch.push_back(matches[i]);
}
Mat matGoodMatcges;
sprintf(buf, "imgcheck%d.jpg", i);
drawMatches(mainPano, point1, imgArray[i], point2, goodmatch, matGoodMatcges, Scalar::all(-1), Scalar(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow(buf, matGoodMatcges);
sprintf(buf, "img%d.jpg", i);
vector<Point2f> obj;
vector<Point2f> scene;
for (int i = 0; i < goodmatch.size(); i++) {
obj.push_back(point1[goodmatch[i].queryIdx].pt);
scene.push_back(point2[goodmatch[i].trainIdx].pt);
}
Mat homomatrix = findHomography(scene, obj, CV_RANSAC);
Mat warp;
warpPerspective(imgArray[i], warp, homomatrix, Size(imgArray[i].cols + mainPano.cols, imgArray[i].rows), INTER_CUBIC);
Mat matPanorama;
matPanorama = warp.clone();
Mat matROI(matPanorama, Rect(0, 0, mainPano.cols, mainPano.rows));
mainPano.copyTo(matROI);
vector<Point> nonBlackList;
nonBlackList.reserve(warp.rows *warp.cols);
int max = 0;
for (int i = 0; i < matPanorama.cols; ++i)
{
// if not black: add to the list
if (matPanorama.at<Vec3b>(matPanorama.rows / 2, i) != Vec3b(0, 0, 0))
{
if (max < i)max = i;
}
Mat img = matPanorama(Range(0, matPanorama.rows), Range(0, max));
mainPano = img;
}
imshow(buf, mainPano);
}
return mainPano;
}
코드 설명
- main 함수
main 함수에서 파노라마를 만들길 원하는 사진을 넣고 MakePano함수를 실행시켜 파노라마를 만든 후 보여주게 출력을 보여 주게 됩니다.
Mat result;
이 부분은 MakePano 함수를 실행 시킨 뒤 나오는 파노라마를 Mat형식으로 저장하는 변수를 선언해줍니다.
int num = 3;
Mat imgArray[3];
imgArray[0] = imread("C:/Users/Administrator/desktop/ex/0.jpg");
imgArray[1] = imread("C:/Users/Administrator/desktop/ex/00.jpg");
imgArray[2] = imread("C:/Users/Administrator/desktop/ex/000.jpg");
여기에서는 파노라마를 만들기 원하는 사진의 개수를 num에 넣어 주고 그 수에 맞게 왼쪽부터 순서대로 이미지의 경로를 써주시면 됩니다.
result = MakePano(imgArray, num);
imshow("res", result);
imshow를 통해 result에 저장된 파노라마가 보여지게 됩니다. "res" 부분은 파노라마가 보여지게 되는 창의 제목을 지정해줍니다.
- MakePano함수
함수의 인자는 처음 입력 이미지들의 경로를 담고 있는 배열 포인터와 그 이미지의 수를 받아야합니다.
파노라마를 만드는 이 함수가 동작하는 과정은 위처럼 왼쪽부터 오른쪽으로 하나씩 추가하며 같은점을 찾아 붙여가는 것입니다.(imgArray 배열의 0번 인덱스부터 누적시켜가며 붙여갑니다.)
Mat mainPano = imgArray[0];
mainPano는 처음에는 imgArray 배열의 첫번째 값을 받고 있고 붙여나가는 작업이 계속 되어가면서 누적되어 붙여진 사진들이 저장되어지는 변수입니다.
char buf[1000];
buf배열은 나중에 사진을 띄워주는 창의 이름에 번호를 매기기 위해 선언한 배열입니다.
for (int i = 1; i < num; i++)
이 배열은 함수 맨아래 반환하기 전까지 둘러싸고 있으며 총 받은 사진의 개수-1 만큼 작동하게 됩니다. (하나씩 누적하며 붙여나가므로)
Mat gray_mainImg, gray_objImg;
cvtColor(mainPano, gray_mainImg, COLOR_RGB2GRAY);
cvtColor(imgArray[i], gray_objImg, COLOR_RGB2GRAY);
붙일 두 사진인 mainPano, imgArray[i]를 특징점찾기 용이하게 회색으로 바꿔 gray_mainImg와 gray_objImg에 각각 저장해줍니다.
SiftFeatureDetector detector(0.3);
vector<KeyPoint> point1, point2;
Mat pointmat1, pointmat2;
detector.detect(gray_mainImg, point1);
detector.detect(gray_objImg, point2);
SiftDescriptorExtractor extractor;
Mat descriptor1, descriptor2;
extractor.compute(gray_mainImg, point1, pointmat1);
extractor.compute(gray_objImg, point2, pointmat2);
SiftFeatureDetector를 통해 Sift에서의 detector를 선언해줍니다. dectector안의 숫자는 임계값(threshold)입니다. point 1, 2 는 회색 영상에서 코너점이나 엣지같은 식별이 용이한 특징점을 detector.detect를 통해 찾아 선택해 각각 저장하는 변수입니다. SiftDescriptorExtractor를 통해서 Sift에서의 extractor를 선언해줍니다. 이는 아래 그림과 같이 detector를 통해 선택한 특징점들을 4x4블록으로 나누고 각 블록에 속한 gradient 방향과 크기에 대한 히스토그램을 구한 후 이 히스토그램 bin 값들을 일렬로 쭉 연결한 128차원 벡터를 pointmat 1,2에 저장합니다.(4x4 블록에 8개의 gradient 방향이라 128차원)
FlannBasedMatcher matcher;
vector<DMatch> matches;
matcher.match(pointmat1, pointmat2, matches);
FlanBasedMatcher를 통해 pointmat 1,2에 저장되어 있는 점들을 매치시키고 그 매치된 정보를 matches 변수에 저장합니다.
double mindistance = 5000;
double distance;
for (int i = 0; i < pointmat1.rows; i++) {
distance = matches[i].distance;
if (mindistance > distance)mindistance = distance;
}
매치시킨 점들 사이의 최소 가로 거리를 mindistance를 구합니다.
vector<DMatch>goodmatch;
for (int i = 0; i < pointmat1.rows; i++) {
if (matches[i].distance < 5 * mindistance)
goodmatch.push_back(matches[i]);
}
mindistance값의 5배가 되는 위치에 위치한 점들을 제외하고 goodmatch변수에 넣습니다.(파노라마를 만들 때 거리가 멀어 붙이지 않아도 되는 점 ,outlier제거)
Mat matGoodMatcges;
sprintf(buf, "imgcheck%d.jpg", i);
drawMatches(mainPano, point1, imgArray[i], point2, goodmatch, matGoodMatcges, Scalar::all(-1), Scalar(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow(buf, matGoodMatcges);
goodmatch변수에 있는 점들을 이어 창을 띄워 보여줍니다. sprintf함수는 큰 반복문이 돌 때마다 띄워주는 창의 이름을 바꾸기 위해 사용했습니다.
vector<Point2f> obj;
vector<Point2f> scene;
for (int i = 0; i < goodmatch.size(); i++) {
obj.push_back(point1[goodmatch[i].queryIdx].pt);
scene.push_back(point2[goodmatch[i].trainIdx].pt);
}
obj와 scene에 goodmatch변수에 있는 점들을 저장
Mat homomatrix = findHomography(scene, obj, CV_RANSAC);
두 사진의 공통 특징점사이의 변형정도를 알 수 있는 H matrix를 구합니다.
Mat warp;
warpPerspective(imgArray[i], warp, homomatrix, Size(imgArray[i].cols + mainPano.cols, imgArray[i].rows), INTER_CUBIC);
Mat matPanorama;
matPanorama = warp.clone();
붙일 이미지를 구한 H matrix를 적용해 변형시킵니다. 그리고 그값을 matPanorama에 붙여 넣습니다.
Mat matROI(matPanorama, Rect(0, 0, mainPano.cols, mainPano.rows));
mainPano.copyTo(matROI);
mainPano와 matPanorama를 붙여줍니다.
int max = 0;
for (int i = 0; i < matPanorama.cols; ++i)
{
if (matPanorama.at<Vec3b>(matPanorama.rows / 2, i) != Vec3b(0, 0, 0))
{
if (max < i)max = i;
}
Mat img = matPanorama(Range(0, matPanorama.rows), Range(0, max));
mainPano = img;
}
붙여주게되면 검은색 부분이 많이 남게 되는데 이 부분을 일정부분 잘라내어 줍니다.
sprintf(buf, "img%d.jpg", i);
imshow(buf, mainPano);
만들어진 사진을 보여줍니다.