2012年

9月

28日

OpenCVで動画入出力をしてみただけ

今回はC#でOpenCVをやってみました。

 

本来OpenCVはC/C++とPython用のライブラリらしいですが、JavaやC#のラッパーライブラリが公開されています。今回は日本人の方が作ったラッパーライブラリ「OpenCVSharp」を使ってみました。

 

環境は以下のとおり

・Windows XP sp3 x86

・VC#2010

環境設定

・OpenCVSharp

OpenCVのC#ラッパークラスライブラリ。

2012/09/28現在、最新版の「OpenCvSharp-2.4-x86-20120801.zip」をダウンロード。

http://code.google.com/p/opencvsharp/downloads/list

 

・OpenCV

コンピュータビジョン向けライブラリ。

2012/09/28現在最新版は2.4.2だが、OpenCVSharpに合わせて2.4.0をダウンロードする。

http://sourceforge.net/projects/opencvlibrary/files/opencv-win/

 

・TBB

Intel® Threading Building Blocksの略。

Intel社が公開してる並列プログラミング用のライブラリ。

OpenCV内で使われていて、画像処理を高速化することが出来る。

 

OpenCVに同梱されてるtbbは若干古いので、以下のURLから最新版をダウンロード。

2012/09/28現在、最新版は「tbb41_20120718oss_win.zip

http://threadingbuildingblocks.org/file.php?fid=77

 

それぞれダウンロードファイルを解凍すれば導入は完了です。

 

フォルダの配置は以下のようにしました。

C:\Program Files\OpenCV\OpenCVSharp-2.4

C:\Program Files\OpenCV\opencv2.4.0

C:\Program Files\OpenCV\tbb4.1

VC#プロジェクト設定

プロジェクトの参照で以下のOpenCVSharpのdllを追加する。

OpenCVSharp.dll

OpenCVSharp.Blob.dll

OpenCVSharp.CPlusPlus.dll

OpenCVSharp.DebuggerVisualizers.dll

OpenCVSharp.Extensions.dll

OpenCVSharp.MachineLearning.dll

OpenCVSharp.UserInterface.dll

 

これだけだと動かないので、プロジェクトのDebug・ReeaseフォルダにOpenCVとTBBのダイナミックライブラリをコピーしておく。

opencv_calib3d240.dll

opencv_contrib240.dll

opencv_core240.dll

opencv_features2d240.dll

opencv_ffmpeg240.dll

opencv_flann240.dll

opencv_gpu240.dll

opencv_highgui240.dll

opencv_imgproc240.dll

opencv_legacy240.dll

opencv_ml240.dll

opencv_nonfree240.dll

opencv_objdetect240.dll

opencv_photo240.dll

opencv_stitching240.dll

opencv_ts240.dll

opencv_video240.dll

opencv_videostab240.dll

tbb.dll

 

または、環境変数のPathにOpenCVのダイナミックライブラリのパスと、TBBのダイナミックライブラリのパスを追加する。

 

OpenCVのdllパス→opencvのフォルダ\build\x86\vc10\bin

TBBのdllパス→TBBのフォルダ\bin\ia32\vc10

ここまで出来たので、簡単なプログラムを書いてみた。

動画を読み込んで、1フレーム目の映像を画像に保存するだけです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System.IO;
using OpenCvSharp;

namespace OpenCVSampe
{
    static class Program
    {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            // 動画ファイル読み込み
            CvCapture capture = new CvCapture(Path.GetFullPath("movie.avi"));
            
            // フレームを更新して画像データを取得
            IplImage image = capture.QueryFrame();
            
            // 1フレーム目の画像を保存
            image.SaveImage(Path.GetFullPath("result.bmp"));
            
            // 開放
            Cv.ReleaseCapture(capture);
        }
    }
}

ところがこれを実行したところ、18行目の「QueryFrame」でエラーが出た。

最初はOpenCVSharpの不具合かと思い、C++側でも同様のプログラムを組んでみた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <opencv/cv.h>
#include <opencv/cxcore.h>
#include <opencv/highgui.h>
using namespace cv;

static LPCSTR WINDOW_TITLE = "OpenCVSample";

int main ()
{
    // メモリリーク探索用
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

    // 動画読み込み
    CvCapture* capture = cvCreateFileCapture("movie.avi");
    IplImage *image = cvQueryFrame(capture);

    // ウィンドウを作成
    char title[256] = {0};
    sprintf(title, "%s Version:%s", WINDOW_TITLE, CV_VERSION);
    cvNamedWindow (title, CV_WINDOW_AUTOSIZE);

    // ウィンドウに表示
    cvShowImage(title, image);
    cvWaitKey (0);

    // 画像を保存
    cvSaveImage("result.bmp", image);

    // 開放
    cvDestroyWindow (title);
    cvReleaseCapture(&capture);

    return 0;
}

が、やはりcvQueryFrameで失敗し、aviファイルを読み込むことは出来なかった。

何度やってもダメなので、OpenCVライブラリをデバッグしてみた。

OpenCVのVCプロジェクト作成

ライブラリを再ビルドするために、OpenCVのVCソリューション(.sln)を作る必要があるので、CMakeで用意する。

 

CMake 2012/09/28現在、最新バージョンは2.8.9

http://www.cmake.org/cmake/resources/software.html

 

①「Where is the source code:」に「C:\Program Files\OpenCV\opencv2.4.0」、「Where to build the binaries:」に「C:\Program Files\OpenCV\opencv2.4.0\build」を指定して「Configure」を押す。

プロジェクトを聞かれるので、「Visual Studio 10」を選択して「Finish」を押す。

 

②「WITH_TBB」にチェックを入れて再度「Configure」を押す。

 

新たに「TBB_INCLUDE_DIRS」と言う項目が出てくるので、「C:\Program Files\OpenCV\tbb4.1\include」と指定して再度「Configure」を押す。

 

TBB_LIB_DIR」に「C:\Program Files\OpenCV\tbb4.1\lib\ia32\vc10」、「TBB_STDDEF_PATH」に「C:/Program Files/OpenCV/tbb4.1/include/tbb/tbb_stddef.h」と指定して再度「Configure」を押す。

最後に「Generate」を押す。これで「C:\Program Files\OpenCV\opencv2.4.0\build」に「OpenCV.sln」が作成される。

OpenCVライブラリの修正

作成したVCソリューションに、上記のC++のコードを組み込んで実行したところ、QueryFrameを呼び出したところでライブラリ側でエラーが生じた(以下35行目辺り)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*
 * cap_ffmpeg.cpp
 * 132~183行目
 */
 
class CvCapture_FFMPEG_proxy : public CvCapture
{
public:
    CvCapture_FFMPEG_proxy() { ffmpegCapture = 0; }
    virtual ~CvCapture_FFMPEG_proxy() { close(); }

    virtual double getProperty(int propId)
    {
        return ffmpegCapture ?
                icvGetCaptureProperty_FFMPEG_p(ffmpegCapture, propId):
                0;
    }
    virtual bool setProperty(int propId, double value)
    {
        return ffmpegCapture ?
                icvSetCaptureProperty_FFMPEG_p(ffmpegCapture, propId, value)!=0:
                false;
    }
    virtual bool grabFrame()
    {
        return ffmpegCapture ?
            icvGrabFrame_FFMPEG_p(ffmpegCapture)!=0:
            false;
    }
    virtual IplImage* retrieveFrame(int)
    {
        unsigned char* data = 0;
        int step=0, width=0, height=0, cn=0;
        
        // ここでエラーが発生 原因はicvRetrieveFrame_FFMPEG_p
        if(!ffmpegCapture ||
           !icvRetrieveFrame_FFMPEG_p(
                   ffmpegCapture,
                   &data,
                   &step,
                   &width,
                   &height,
                   &cn))

           return 0;
        
        cvInitImageHeader(&frame, cvSize(width, height), 8, cn);
        cvSetData(&frame, data, step);
        return &frame;
    }
    virtual bool open( const char* filename )
    {
        close();

        icvInitFFMPEG();
        if( !icvCreateFileCapture_FFMPEG_p )
            return false;
        ffmpegCapture = icvCreateFileCapture_FFMPEG_p( filename );
        return ffmpegCapture != 0;
    }
    virtual void close()
    {
        if( ffmpegCapture && icvReleaseCapture_FFMPEG_p )
            icvReleaseCapture_FFMPEG_p( &ffmpegCapture );
        assert( ffmpegCapture == 0 );
        ffmpegCapture = 0;
    }

protected:
    void* ffmpegCapture;
    IplImage frame;
};

CvCaptureクラスは動画ファイルを読み込む時に処理ソフトを選ぶようになっていて、「VFW」や「QuickTime」、その他のソフトに対応している。OpenCV2.4からは上記の「ffmpeg」が使用されるようになった。

 

今回、無圧縮aviを読み込ませるとソフトは「ffmpeg」が選択された。が、動画サイズやフレーム総数等の動画の基本情報は取得できてたが、何度やってもQueryFrameで失敗した。

 

ところが無圧縮avi出力を試したところ、「ffmpeg」ではなく「VFW」が選択された。コレは何かおかしい。

 

その後、mp4を読み込ませたら「ffmpeg」が選ばれ、QueryFrameも抜けて初めて正常に動作した。

 

私は動画関係には詳しくないのでよくわからないが、「ffmpeg」は無圧縮のaviファイルを扱うには向いていないらしい。対応して無いのにもかかわらず無圧縮aviを「ffmpeg」で読み込もうとするので失敗するらしい。

 

処理ソフトを選択する処理は以下のようになっている。

デフォルトでffmpegを優先的に使用するようになっている。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
 * cap.cpp
 * 336~372行目
 */
 
CV_IMPL CvCapture * cvCreateFileCapture (const char * filename)
{
    CvCapture * result = 0;

    if (! result)
        result = cvCreateFileCapture_FFMPEG_proxy (filename);

#ifdef HAVE_XINE
    if (! result)
        result = cvCreateFileCapture_XINE (filename);
#endif

#ifdef HAVE_GSTREAMER
    if (! result)
        result = cvCreateCapture_GStreamer (CV_CAP_GSTREAMER_FILE, filename);
#endif

#ifdef HAVE_QUICKTIME
    if (! result)
        result = cvCreateFileCapture_QT (filename);
#endif
    
#ifdef HAVE_AVFOUNDATION
    if (! result)
        result = cvCreateFileCapture_AVFoundation (filename);
#endif

#ifdef HAVE_OPENNI
    if (! result)
        result = cvCreateFileCapture_OpenNI (filename);
#endif
    
    if (! result)
        result = cvCreateFileCapture_Images (filename);

    return result;
}

 

cvCreateFileCapture_FFMPEG_proxyメソッドはこのような処理になっている。

まずffmpegで開けるか確認し、開けない場合VFWに切り替えるようになっている。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/*
 * cap_ffmpeg.cpp
 * 186~197行目
 */
 
CvCapture* cvCreateFileCapture_FFMPEG_proxy(const char * filename)
{
    CvCapture_FFMPEG_proxy* result = new CvCapture_FFMPEG_proxy;
    if( result->open( filename ))
        return result;
    delete result;
#if defined WIN32 || defined _WIN32
    return cvCreateFileCapture_VFW(filename);
#else
    return 0;
#endif
}

 

無圧縮aviが指定された場合、本来読み込めないはずなのにresult->open( filename )で読み込めると判定されて、結果QueryFrameで画像を取得できずにエラーが出ていた。

 

このままだと無圧縮aviは読み込めないので、この部分の処理を以下のように修正した。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
 * cap_ffmpeg.cpp
 * 186~207行目
 */
 
CvCapture* cvCreateFileCapture_FFMPEG_proxy(const char * filename)
{
        CvCapture* result = NULL;

#if defined WIN32 || defined _WIN32
    result = cvCreateFileCapture_VFW(filename);
#else
    return 0;
#endif

        if(!result)
        {
                CvCapture_FFMPEG_proxy* ffmpg_result = new CvCapture_FFMPEG_proxy;

                if( ffmpg_result->open( filename ))
                        result = ffmpg_result;
                else
                        delete ffmpg_result;
        }

        return result;
}

 

WindowデフォルトのVFWを優先的に使用させるようにした。mp4の場合、VFWで読み込まずにffmpegへ処理が流れるようにもなっている。

 

再ビルドしたライブラリをC++とOpenCVSharpの両方で確認したところ、エラーが出ることなく無圧縮aviを読み込みと出力できるようになった。

今回、GTAViewerMMDFromColladaの動画出力処理にOpenCVを取り入れようと思い、このような記事を書いてみました。上の記事ですが、もしかするとffmpegでも無圧縮コーデックのaviを読み込める方法があるかもしれません。

 

OpenCVは基本的な画像や動画の処理だけではなくWebカメラのキャプチャ、Kinectにも対応しているそうです。内部ではDirectShowOpenNIが使われています。Kinectには興味があったのでちょっと手を出してみました。

 

ところで、無圧縮aviの入出力にVFWを使ってますが、VFWは古い形式なのでAVI2.0規格(2GB越えのaviファイル)には対応してません。DirectShowならAVI2.0の動画入出力も可能なんですが、これだと2GB以上の動画を読み込むことが出来ません。なるべく無圧縮+aviで操作したいので、DirectShowが使えれば解決するんですが、私にはDirectShowの知識が無いので一から作ることも出来ません・・・・・・あれは難しいです。

 

OpenCVDirectShowは、見たところカメラ操作にしか対応してないようですが、ファイル入出力の方にも対応してくれないかな?OpenCVの今後に期待しておきます。

コメントをお書きください

コメント: 1
  • #1

    しいたけ (金曜日, 16 11月 2012 17:38)

    おなじようなところでつまずいていたので参考になりました!
    研究用だったのでaviの圧縮フォーマットを変えることで回避しました><