空撮動画からカメラ位置を推定

画像を表示する

最終的な目的はカメラの移動の推定ですが、まずは動画を再生して表示するだけのプログラムを作成します。動画へのパスは各々で動画の置いてある場所に合わせてください。

#include "opencv/cv.h"
#include "opencv/highgui.h"
using namespace cv;

int main(){
    Mat img;
    VideoCapture cap("aerial.mp4");
    int max_frame=cap.get(CV_CAP_PROP_FRAME_COUNT);
    for(int i=0; i<max_frame;i++){ cap>>img;
        imshow("Video",img);
        waitKey(1);
    }
    return 0;
}

テンプレートマッチングにより画像の移動を推定する

カメラが少しづつ移動しているなら、n番目のフレームの画像とn+1番目のフレームの画像は少しだけずれていることになります。ここで、カメラの移動がそれほど速くなく、しかも回転が僅かであると仮定します。この仮定の元ではn番目のフレームの画像の中心付近の部分と似たような領域が、n+1番目のフレームの画像の中央付近にも存在するということが言え、さらに回転が僅かであるという仮定のもとで平行移動したものとすることができ、高度一定の飛行なら拡大縮小もないとすることができます。このような制限付きで似たような画像を見つけるにはテンプレートマッチングを使います。

ROIを設定する

「画像の中心付近の部分」というのを、今回は上下左右に20%の余白を取った中央付近の領域ということにします。OpenCVではこのように設定した領域をROI(関心領域:Region Of Interest)として扱うと便利です。ROIはMatから一部分を切り取ることで作ることができるMatです。切り取る矩形はRect(矩形左上X座標,矩形左上Y座標,幅,高さ)の形式で指定します。下の例ではimgから左に200、上に100の余白を空けた、一片400の正方形領域を切り取ったものをroi_imgとして保持するようなコードです。

Mat roi_img(img,Rect(200, 100, 400, 400));

matchTemplate関数でテンプレートマッチングを行う

OpenCVではmatchTemplate関数を使うことで、簡単にテンプレートマッチングを実装できます。この関数は、第一引数に大きなMat、第二引数に小さなMatを指定して、小さなMatが大きなMatのどの位置に似ているかということを計算します。下のコードではmax_ptに最もうまくマッチした場所の大きなMat上での座標(矩形の左上座標)を保持するようにしています。なお、#define文でROIの位置と大きさを設定しています。#define AAAA BBBB という文は、AAAAをBBBBに置き換えるという意味になります。

aerial0
#include "opencv/cv.h"
#include "opencv/highgui.h"
using namespace cv;

#define ROI_MARGIN_X (0.2*v_w)
#define ROI_MARGIN_Y (0.2*v_h)
#define ROI_SIZE_X (v_w-ROI_MARGIN_X*2)
#define ROI_SIZE_Y (v_h-ROI_MARGIN_Y*2)

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_img;
    
    //初回でのテンプレートマッチング用のROIを作成
    cap>>img ;
    Mat last_roi_img(img,Rect(ROI_MARGIN_X,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 ;
        
        //テンプレートマッチング
        matchTemplate(img,last_roi_img,result_img, CV_TM_CCOEFF_NORMED);
        Point max_pt;
        double maxVal;
        minMaxLoc(result_img, NULL, &maxVal, NULL, &max_pt);
        
        //探索結果の場所に矩形を描画
        Rect roi_rect(0, 0, ROI_SIZE_X, ROI_SIZE_Y);
        roi_rect.x = max_pt.x;
        roi_rect.y = max_pt.y;
        rectangle(img, roi_rect, Scalar(0,255,255), 1);
        
        //次フレームでのテンプレートマッチング用のROIを保存
        Mat roi_img(img,Rect(ROI_MARGIN_X,ROI_MARGIN_Y,ROI_SIZE_X,ROI_SIZE_Y));
        last_roi_img=roi_img.clone();
        
        imshow("Video",img);
        waitKey(1);
    }
    return 0;
}