openFrameworksを使ったフレーム編集

概要

動画からフレームを抜き出してOpenCVで加工する環境をopenFrameworksで準備してみました。
日本語パス周りで少しハマったのでメモ代わりにブログに残しておきます。

動作環境

完成図

  • 日本語の動画ファイルを読み込み、各フレームを反転して表示する
  • キーフレームの戻る、進むをキーボードから操作する

f:id:cflat-inc:20141003145832j:plain

作成手順

1. openframeworksをダウンロード

windows download openFrameworks for visual studioからダウンロード。
まずはopenFrameworksLibのコンパイルが必要です。 詳細はこちらのリンク先 を参照して下さい。
libs\openFrameworksCompiled\project\vs\openframeworksLib.vcxprojでプロジェクトを起動し、メニューからビルド>バッチビルドを選択しDebugとReleaseのビルドにチェックをいれてビルドを実行します。

2. プロジェクトの作成

今回作成するプロジェクト名をmyVideoとします。
projectGenerator\projectGenerator.exeをダブルクリックすると簡単にプロジェクトが作成出来ます。

f:id:cflat-inc:20141003145844j:plain

3. 動画再生

動画再生するにはofVideoPlayerを使います。
examples\video\videoPlayerExampleにサンプルがありますが、最小限のコードで動かすためには以下の設定で十分です。

ofApp.hに以下のように追記します。

class ofApp : public ofBaseApp{
public:
//...
private:
  ofVideoPlayer _player;
}

ofApp.cppに読み込む動画ファイルの指定、キーボードの矢印でフレームの移動が出来るように設定します。
動画はexamples\video\videoPlayerExample\bin\data\movies\fingers.movを使っています。

void ofApp::setup(){
  ofBackground(255,255,255);
  ofSetVerticalSync(true);
  _player.loadMovie("movies/fingers.mov");
  _player.play();
}

void ofApp::update(){
  _player.update();
}

void ofApp::draw(){
  _player.draw(0,0);
}

void ofApp::keyPressed(int key){
  switch(key){
  case 'f':
    // 連続再生 or フレームごとの再生
    _player.setPaused(!_player.isPaused());
    break;
  case OF_KEY_LEFT:
    // 前フレームの表示
    _player.previousFrame();
    break;
  case OF_KEY_RIGHT:
    // 次のフレームの表示
    _player.nextFrame();
    break;
  }
}

続いてキーボードの'o'を押した時にファイルダイアログを開いてGUIから動画ファイルを選択出来るようにしてみましょう。

void ofApp::setup(){
  ofBackground(255,255,255);
  ofSetVerticalSync(true);
  //_player.loadMovie("movies/fingers.mov");
  //_player.play();
}

void ofApp::keyPressed(int key){
  switch(key){
  case 'f':
    _player.setPaused(!_player.isPaused());
    break;
  case OF_KEY_LEFT:
    _player.previousFrame();
    break;
  case OF_KEY_RIGHT:
    _player.nextFrame();
    break;
  case 'o':
    {
      ofFileDialogResult openFileResult= ofSystemLoadDialog("Select a movie file");
      std::string file = openFileResult.getPath();
      if(!_player.loadMovie(file)){
        return;
      }
      _player.play();
    }
  }
}

このようにすると、'o'キーを押すことでファイル選択ダイアログが開き動画ファイルを指定する事が出来ます。
しかしファイル名が日本語の場合は以下のようなエラーが発生して読み込む事が出来ません。

[ error ] ofQuickTimePlayer: createMovoeFromPath(): couldn't load movie, OpenMov
ieFile failed: OSErr -50

デバッグして調べてみるとofSystemUtil.cppで文字化けが発生してしまっているようです。
仕方ないので こちらのページを参考に書き換えてしまいましょう。

ofSystemUtil.cppを以下のように書き換えます。

//ワイド文字列からマルチバイト文字列
//ロケール依存
static void narrow(const std::wstring &src, std::string &dest) {
  setlocale(LC_CTYPE, "");
  char *mbs = new char[src.length() * MB_CUR_MAX + 1];
  wcstombs(mbs, src.c_str(), src.length() * MB_CUR_MAX + 1);
  dest = mbs;
  delete [] mbs;
}

std::string
convertWideToNarrow( const wchar_t *s, char dfault = '?',
                     const std::locale& loc = std::locale() )
{
#if 0
  std::ostringstream stm;
  while( *s != L'\0' ) {
    stm << std::use_facet< std::ctype<wchar_t> >( loc ).narrow( *s++, dfault );
  }
  return stm.str();
#else
  std::wstring wstr(s);
  std::string ret;
  narrow(wstr, ret);
  return ret;
#endif
}

4. opencvの設定

次に動画から切り出したフレームをopencvで加工するための設定を行います。
openFrameworksにはopencvのラッパーであるofxCvというアドオンが存在しますが、内部で使用されているopencvのバージョンが古いのでそのままは使いたくありません。
Utilities.h/cppの2ファイルのみを拝借する事とし、別途最新のopencv(今回は2.4.9)を準備しましょう。

以下の2ファイルをプロジェクトに追加します。

  • ofxCv\libs\ofxCv\include\Utilities.h
  • ofxCv\libs\ofxCv\src\Utilities.cpp

またopencvが使えるように設定を行います。
opencvはvisual studio2012のコンパイル済みのものがこちらから手に入るのでこれを利用すると便利です。

visual studioからopencvを使用する方法は検索するといくらでも出てくるので詳細は省略します。
追加のインクルードディレクトリと追加のライブラリディレクトリの設定を済ませたら、main.cppファイルの先頭辺りに以下を記述します。

#include <opencv2/core/version.hpp>
#ifdef _MSC_VER
#define CV_NERSION_NO CVAUX_STR(CV_VERSION_EPOCH) CVAUX_STR(CV_VERSION_MAJOR) CVAUX_STR(CV_VERSION_MINOR)
#ifdef _DEBUG
//Debugモードの場合
#define OPENCV_LIB(name) "opencv_" #name CV_NERSION_NO "d.lib"
#else
//Releaseモードの場合
#define OPENCV_LIB(name) "opencv_" #name CV_NERSION_NO ".lib"
#endif // NDEBUG
#endif // _MSC_VER

#pragma comment(lib, OPENCV_LIB(core))
#pragma comment(lib, OPENCV_LIB(imgproc))
#pragma comment(lib, OPENCV_LIB(highgui))

これでopencvを使う準備が整いました。

5. 動画のフレームを変換

ofApp.cppファイルを次のように変更します。

#include "ofxCv/Utilities.h"

void ofApp::update(){
  _player.update();
  if(_player.isFrameNew()){
    ofPixelsRef pix = _player.getPixelsRef();
    cv::Mat m = ofxCv::toCv(pix).clone();
    // opencvはBGRなので変換が必要
    cv::cvtColor(m, m, CV_RGB2BGR);
    // 処理の一例
    cv::flip(m, m, 0); // 水平軸で反転(垂直反転)
    // フレームを反転させた画像を表示
    cv::imshow("frame", m);
  }
}

これで動画の各フレームに対して自由に処理を行う事が出来るようになりました。

おまけ

コーデックの関係か、動画ファイルによっては再生出来ないものがありました。
調べてみるとofDirectShowPlayerというアドオンがあり、こちらはDirectShowの実装を使えるようなのでwindowsの場合は良いかもしれません。
使い方は簡単で

void ofApp::setup(){
  _player.setPlayer( ofPtr <ofBaseVideoPlayer>(new ofDirectShowPlayer()));
}

とするだけです。
ただし、releaseビルドの実行時に時々ハングアップする現象が発生しました。
という事で本格的に動画ファイルを扱いたい場合にはFFmpeg等を使った方が良さそうです。
ただ少ないコード量で手軽に動画のフレーム編集を行いたい場合には今回の方法で十分かと思います。