動画からパノラママップを作成する
さて、いよいよ今回の空撮動画の解析も最終回です。総仕上げとして、パノラマのようなマップを作ります。下のコードでは事前にどれくらいの大きさのマップになるかを計算しておく必要があります。今回の動画ではX座標は-1500~1500、y座標は-3000~1000の大きさがあれば十分であることがわかっているものとします。したがって、幅3000、高さ4000のマップになります。重なりの部分については次々と画像を上書きしていくことで合成します。
処理の流れ
下のコードでは、まず、前回までのチュートリアルで計算したように2つのROIからカメラのワールド座標と初期状態からの回転角度を求めています。今回、付け加えられた処理はパノラマ作成におけるモザイキング処理、すなわち、画像同士の貼り合わせ処理です。様々な実装手法がありえますが、今回は画像をカメラの角度に応じて回転させた画像を、ワールド座標の位置に順次重ねる処理を行います。ただし、回転させた画像は撮影領域の周りに黒の背景がついたものになっているため、そのまま重ねていくとうまくいきません。したがって、重ね合わせの時に黒色の部分を除いて重ね合わせています。具体的には回転画像を低い閾値で2値化してマスク画像を作成しています。これでも重ね合わせる画像の境界部分に黒色がわずかに入り込むことがあるため、余裕をもたせるためにマスク画像に収縮処理を行っています。
#include "opencv/cv.h"
#include "opencv/highgui.h"
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
#define ROI_MARGIN_X1 (0.2*v_w)
#define ROI_MARGIN_X1_X2 (0.2*v_w)
#define ROI_MARGIN_Y (0.2*v_h)
#define ROI_SIZE_X ((v_w-ROI_MARGIN_X1*2-ROI_MARGIN_X1_X2)/2)
#define ROI_SIZE_Y (v_h-ROI_MARGIN_Y*2)
#define ROI_MARGIN_X2 (ROI_MARGIN_X1+ROI_SIZE_X+ROI_MARGIN_X1_X2)
#define map_offset_x 1500
#define map_offset_y 3000
double r(double x1,double y1,double x2,double y2){
return pow((pow((x1-x2),2)+pow((y1-y2),2)),0.5);
}
int main()
{
VideoCapture cap("aerial.mp4");
int v_w=cap.get(CV_CAP_PROP_FRAME_WIDTH);
int v_h=cap.get(CV_CAP_PROP_FRAME_HEIGHT);
Mat img;
Mat result_img1;
Mat result_img2;
int d_x=0;
int d_y=0;
int position_x=0;
int position_y=0;
double angle=0; //右回りが正
Mat map_img = Mat::zeros(4000, 3000, CV_8UC3);
Mat dst_img= Mat(r(v_h,v_w,0,0),r(v_h,v_w,0,0), CV_8UC3);
Rect dst_ROI(r(v_h,v_w,0,0)/2-v_w/2, r(v_h,v_w,0,0)/2-v_h/2, v_w, v_h);
Mat element = Mat::ones(3,3,CV_8UC1);
//初回でのテンプレートマッチング用のROIを作成
cap>>img ;
Mat last_roi_img1(img,Rect(ROI_MARGIN_X1,ROI_MARGIN_Y,ROI_SIZE_X,ROI_SIZE_Y));
Mat last_roi_img2(img,Rect(ROI_MARGIN_X2,ROI_MARGIN_Y,ROI_SIZE_X,ROI_SIZE_Y));
int max_frame=cap.get(CV_CAP_PROP_FRAME_COUNT);
for(int i=1; i<max_frame;i++){ cap>>img;
//テンプレートマッチング1----------------------------------------------
matchTemplate(img,last_roi_img1,result_img1, CV_TM_CCOEFF_NORMED);
Point max_pt1;
double maxVal1;
minMaxLoc(result_img1, NULL, &maxVal1, NULL, &max_pt1);
//1つ目ここまで-----------------------------------------------------
//テンプレートマッチング2----------------------------------------------
matchTemplate(img,last_roi_img2,result_img2, CV_TM_CCOEFF_NORMED);
Point max_pt2;
double maxVal2;
minMaxLoc(result_img2, NULL, &maxVal2, NULL, &max_pt2);
//2つ目ここまで-----------------------------------------------------
//次フレームでのテンプレートマッチング用のROIを保存1
Mat roi_img1(img,Rect(ROI_MARGIN_X1,ROI_MARGIN_Y,ROI_SIZE_X,ROI_SIZE_Y));
last_roi_img1=roi_img1.clone();
//次フレームでのテンプレートマッチング用のROIを保存2
Mat roi_img2(img,Rect(ROI_MARGIN_X2,ROI_MARGIN_Y,ROI_SIZE_X,ROI_SIZE_Y));
last_roi_img2=roi_img2.clone();
angle-=atan((double)(max_pt1.y-max_pt2.y)/(max_pt1.x-max_pt2.x));
//位置推定
d_x=(ROI_MARGIN_X1-max_pt1.x)/2+(ROI_MARGIN_X2-max_pt2.x)/2;
d_y=(ROI_MARGIN_Y-max_pt1.y);
position_x+=d_x*cos(angle)-d_y*sin(angle);
position_y+=d_x*sin(angle)+d_y*cos(angle);
cout <<i<<" / " <<max_frame<<" (" << position_x << ", " << position_y << ") angle = "<<angle<<endl;
//ここからパノラマ作成
Mat rot_dst_img(dst_img,dst_ROI);
img.copyTo(rot_dst_img);
// 回転: [deg] と スケーリング: 1.0 [倍]
float angle_d = -angle*180.0/3.141592, scale = 1.0;
// 中心:画像中心
cv::Point2f center(dst_img.cols*0.5, dst_img.rows*0.5);
// 2次元の回転行列を計算
const cv::Mat affine_matrix = cv::getRotationMatrix2D( center, angle_d, scale );
warpAffine(dst_img, rot_dst_img, affine_matrix, dst_img.size());
Mat map_roi_img(map_img,Rect(position_x+map_offset_x-dst_img.cols/2,position_y+map_offset_y-dst_img.rows/2,dst_img.cols,dst_img.rows));
Mat mask=rot_dst_img.clone();
cvtColor(mask,mask,CV_BGR2GRAY);
threshold(mask,mask,1,255,THRESH_BINARY);
erode(mask,mask, element, Point(-1,-1), 1);
rot_dst_img.copyTo(map_roi_img,mask);
imshow("Map",map_img);
waitKey(1);
}
imwrite("map.png", map_img);
imshow("Map",map_img);
waitKey(0);
return 0;
}