OpenCVで遊ぶ
Visual C++ 2005 Express Edition使用


開発準備

プラットフォームSDKのインストール(ファイル名:PSDK-x86.exe)

Microsoftからダウンロードできますので、適当に探してインストールします。途中で何か聞いてきても、すべてデフォでOKです。
インストールが完了したら、Visual C++ 2005 Express Editionを設定します。
1.[ツール]→[オプション]ダイアログを開き、左側のツリーから[プロジェクトおよびソリューション]→[VC++ディレクトリ]を選び、[ディレクトリを表示するプロジェクト]から[インクルードファイル]を選択し、下記を追加する。

  C:\Program Files\Microsoft Platform SDK\Include
2.[ディレクトリを表示するプロジェクト]から[ライブラリファイル]を選択し、下記を追加する。
  C:\Program Files\Microsoft Platform SDK\Lib
3.[ディレクトリを表示するプロジェクト]から[実行可能ファイル]を選択し、下記を追加する。
  C:\Program Files\Microsoft Platform SDK\Bin

OpenCVのインストール(ファイル名:OpenCV_1.0.exe)

SourceForgeからダウンロードできますので、適当に探してインストールします。やっぱり、すべてデフォでOKです。
1.[ツール]→[オプション]ダイアログを開き、左側のツリーから[プロジェクトおよびソリューション]→[VC++ディレクトリ]を選び、[ディレクトリを表示するプロジェクト]から[インクルードファイル]を選択し、下記を追加する。
  C:\Program Files\OpenCV\cv\include
  C:\Program Files\OpenCV\cvaux\include
  C:\Program Files\OpenCV\cxcore\include
  C:\Program Files\OpenCV\otherlibs\highgui
2.[ディレクトリを表示するプロジェクト]から[ライブラリファイル]を選択し、下記を追加する。
  C:\Program Files\OpenCV\lib

準備完了のテスト

1.[ファイル]→[新規作成]→[プロジェクト]ダイアログを開き、Win32コンソールアプリケーションを選択する。Win32アプリケーションウィザードが起動するので、[次へ]を選び、[空のプロジェクト]にチェックを入れる。
2.[alt+F7]でプロジェクトプロパティダイアログを開き、[構成プロパティ]→[リンカ]→[入力]で、[追加の依存ファイル]に、"highgui.lib"を追加する。
3.[ソリューションエクスプローラ]の[ソースファイル]で右クリックし、[追加]→[新しい項目]を選択する。[カテゴリ]="コード"、[テンプレート]="C++ファイル"を選択し、[ファイル名]のところに適当なファイル名を入れ、下のコードをコピペする。
#include "highgui.h"
int main(int, char) {
    char windowName[] = "OpenCV";
    cvNamedWindow(windowName, CV_WINDOW_AUTOSIZE);
    for (;;) {
        int key = cvWaitKey(-1);
        if (key=='q')
            break;
    }
    cvDestroyWindow(windowName);
    return 0;
}
4.[F5]で実行する。黒いウィンドウと、灰色のウィンドウが出ればOK。'q'キーで終了する。

速度計測

 テストで使用しているのは、WindowsXPで、Pentium4-3GHz(HT)のマシンです。
 まずは、こんなコードで関数呼び出し時の時間を計測してみました。
#pragma unmanaged
#include "cv.h"
#pragma managed

using namespace System;
using namespace System::Drawing;

void test(void) {
    ;
}
int main(array<System::String ^> ^args) {
    const int loopCoint = 1000000; 
    Diagnostics::Stopwatch sw;
    Threading::Thread::Sleep(5000);
    Console::WriteLine("start");

    sw.Start();
    for( int i = 0 ; i < loopCoint ; i ++ ) {
        test();
    }
    sw.Stop();
    Console::WriteLine(sw.ElapsedMilliseconds + "ms");

    Console::ReadLine();
    return 0;
}
 結果は1000000回ループで9msと表示されたので、約9nsということになります。
 これからのテストは、Test関数をいろいろと変えてみます。速度によっては、loopCount変数も変更します。また、表示部分を
Console::WriteLine(Convert::ToDouble(sw.ElapsedMilliseconds)/loopCount + "ms");
に書き換えました。

cvCreateImage/cvReleaseImageの速度

void test(void) {
    IplImage* ipl = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U, 1);
    cvReleaseImage(&ipl);
}
 結果は、5回実行して0.29~0.31msでした。約0.3msといったところでしょうか。

LockBits/UnlockBitsの速度

// 呼び出し元のBitmapもFormat8bppIndexedです。
void test(Bitmap^ bmp) {
    BitmapData^ bmpData = bmp->LockBits(
        Rectangle(0,0,bmp->Width,bmp->Height),
        	ImageLockMode::ReadWrite, PixelFormat::Format8bppIndexed);
    ;
    bmp->UnlockBits(bmpData);
}
 結果は、5回実行して0.0031~0.0037msでした。無視できる時間ですね。

Create-LockBits-UnlockBits-Releaseの速度

// 呼び出し元のBitmapもFormat8bppIndexedです。
void test(Bitmap^ bmp) {
    IplImage* ipl = cvCreateImage(
        cvSize(bmp->Width,bmp->Height), IPL_DEPTH_8U, 1);
    BitmapData^ bmpData = bmp->LockBits(
        Rectangle(0,0,bmp->Width,bmp->Height),
        	ImageLockMode::ReadWrite, PixelFormat::Format8bppIndexed);
    ;
    bmp->UnlockBits(bmpData);
    cvReleaseImage(&ipl);
}
 結果は、5回実行して0.29~0.33msでした。まあ、予想通り約0.3msですね。

本命!Bitmapで渡し、内部でOpenCVを使うときのオーバーヘッド

// 呼び出し元のBitmapもFormat8bppIndexedです。
Bitmap^ test(Bitmap^ bmp) {
    // 引数チェック
    if (bmp==nullptr) return nullptr;
    if (bmp->PixelFormat!=PixelFormat::Format8bppIndexed) return nullptr;
 
    IplImage* ipl = cvCreateImage(
        cvSize(bmp->Width,bmp->Height), IPL_DEPTH_8U, 1);
    BitmapData^ bmpData = bmp->LockBits(
        Rectangle(0,0,bmp->Width,bmp->Height),
        	ImageLockMode::ReadWrite, PixelFormat::Format8bppIndexed);
 
    // if (bmpData->Stride != ipl->widthStep)
    //    ラインごとのコピー処理

    // BitmapからIplImageへコピー
    memcpy_s(ipl->imageData, ipl->widthStep * ipl->height,
            bmpData->Scan0.ToPointer(), bmpData->Stride * bmpData->Height);
    //
    // ここでOpenCVを使う
    //

    // IplImageからBitmapへコピー
    memcpy_s(bmpData->Scan0.ToPointer(),bmpData->Stride * bmpData->Height,
            ipl->imageData, ipl->widthStep * ipl->height);     

    bmp->UnlockBits(bmpData);
    cvReleaseImage(&ipl);
    return bmp;
}
 結果は、5回実行して0.87~0.90msでした。約0.9msといったところでしょうか。

(おまけ)System::Drawing::Bitmapの生成の速度

void test(void) {
    Bitmap^ bmp2 = gcnew Bitmap(640, 480, Imaging::PixelFormat::Format8bppIndexed);
}
 結果は、約0.6msでした。

ラプラシアンフィルタの速度

 OpenCVのcvLaplaceで用いられているオペレータを、ソースから拾い出してみました。
3×3
-1 -1 -1
-1 8 -1
-1 -1 -1
5×5
-1 -1 -1 -1 -1
-1 -2 -2 -2 -1
-1 -2 32 -2 -1
-1 -2 -2 -2 -1
-1 -1 -1 -1 -1
7×7
-1 -1 -1 -1 -1 -1 -1
-1 -2 -2 -2 -2 -2 -1
-1 -2 -3 -3 -3 -2 -1
-1 -2 -3 80 -3 -2 -1
-1 -2 -3 -3 -3 -2 -1
-1 -2 -2 -2 -2 -2 -1
-1 -1 -1 -1 -1 -1 -1
例によって、速度を計測してみます。計測環境は、P4-3GHz(HT)+1GB+WinXPです。実際の使用状態に近いように、640×480:8ビットグレースケールで、cvConvertScaleAbsを入れています。10回実行の平均時間です。
3×3の時、19.9ms 5×5の時、30.2ms 7×7の時、75.9ms
cvConvertScaleAbs単独は4.3msです。………かなり遅めですね。いろいろなフォーマットに対応しているせいもあるのでしょうけど。
 スミマセヌ。計測ミスです。コンソールアプリとしてテストプログラムを作成し、再計測しました。
 計測環境は、P4-3GHz(HT)+1GB+WinXPで、640×480:8ビットグレースケールです。
3×3の時、2.723ms
5×5の時、4.046ms
7×7の時、13.581ms
関係各位の方々、ご迷惑をおかけしましたm(_ _)m
#pragma unmanaged
#include "cv.h"
#pragma managed

using namespace System;
using namespace System::Drawing;

int main(array<System::String ^> ^args) {
    const int loopCount = 1000; 
    IplImage* ipl8U = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U,   1);
    IplImage* ipl16S = cvCreateImage(cvSize(640,480), IPL_DEPTH_16S, 1);

    Diagnostics::Stopwatch sw;
    Threading::Thread::Sleep(5000);
    Console::WriteLine("start");

    sw.Start();
    for( int i = 0 ; i < loopCount ; i ++ ) {
        cvLaplace(ipl8U, ipl16S, 7);  // 3 or 5 or 7
    }
    sw.Stop();
    Console::WriteLine(static_cast<double>(sw.ElapsedMilliseconds) / static_cast<double>(loopCount) + "ms");

    cvReleaseImage(&ipl8U);
    cvReleaseImage(&ipl16S);
    Console::ReadLine();
    return 0;
}

ガウシアンフィルタの速度

 一般的な画像処理ライブラリでは、3×3と5×5のみで、オペレータ固定が多いですが、OpenCVでは、σ(シグマ)を指定したり、オペレータのサイズからσ(シグマ)を決め、まじめに計算してオペレータを作っているようです。
σ = (n/2-1)*0.3 + 0.8 (n=オペレータのサイズ)
3×3のときは0.95、5×5のときは1.25となります。
 計測環境は、P4-3GHz(HT)+1GB+WinXPです。640×480:8ビットグレースケールです。10回実行の平均時間です。
3×3の時、5.6ms 5×5の時、8.1ms 7×7の時、13.6ms
 スミマセヌ。計測ミスです。コンソールアプリとしてテストプログラムを作成し、再計測しました。
 計測環境は、P4-3GHz(HT)+1GB+WinXPで、640×480:8ビットグレースケールです。
3×3の時、2.948ms
5×5の時、4.307ms
7×7の時、6.649ms
関係各位の方々、ご迷惑をおかけしましたm(_ _)m
#pragma unmanaged
#include "cv.h"
#pragma managed

using namespace System;
using namespace System::Drawing;

int main(array<System::String ^> ^args) {
    const int loopCount = 1000; 
    IplImage* ipl8U = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U,   1);

    Diagnostics::Stopwatch sw;
    Threading::Thread::Sleep(5000);
    Console::WriteLine("start");

    sw.Start();
    for( int i = 0 ; i < loopCount ; i ++ ) {
        	cvSmooth(ipl8U, ipl8U, CV_GAUSSIAN , 7);  // 3 or 5 or 7
    }
    sw.Stop();
    Console::WriteLine(static_cast<double>(sw.ElapsedMilliseconds) / static_cast<double>(loopCount) + "ms");

    cvReleaseImage(&ipl8U);
    Console::ReadLine();
    return 0;
}

メディアンフィルタの速度

 計測環境は、P4-3GHz(HT)+1GB+WinXPです。640×480:8ビットグレースケールです。10回実行の平均時間です。
3×3の時、11.8ms 5×5の時、42.5ms 7×7の時、57.6ms
 スミマセヌ。計測ミスです。コンソールアプリとしてテストプログラムを作成し、再計測しました。
 計測環境は、P4-3GHz(HT)+1GB+WinXPで、640×480:8ビットグレースケールです。
3×3の時、6.775ms
5×5の時、25.695ms
7×7の時、33.042ms
関係各位の方々、ご迷惑をおかけしましたm(_ _)m
#pragma unmanaged
#include "cv.h"
#pragma managed

using namespace System;
using namespace System::Drawing;

int main(array<System::String ^> ^args) {
    const int loopCount = 1000; 
    IplImage* ipl8U = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U,   1);

    Diagnostics::Stopwatch sw;
    Threading::Thread::Sleep(5000);
    Console::WriteLine("start");

    sw.Start();
    for( int i = 0 ; i < loopCount ; i ++ ) {
        	cvSmooth(ipl8U, ipl8U, CV_MEDIAN , 7);  // 3 or 5 or 7
    }
    sw.Stop();
    Console::WriteLine(static_cast<double>(sw.ElapsedMilliseconds) / static_cast<double>(loopCount) + "ms");

    cvReleaseImage(&ipl8U);
    Console::ReadLine();
    return 0;
}

cvConvertScaleAbsの速度

 エッジ抽出と一緒に使うことの多い、cvConvertScaleAbsの速度を計測して見ました。
 結果は、2.189(0で埋めた)~4.099(ランダムで-500~500で埋めた)でした。内部で条件分岐が起こるようにすると、見事に遅くなりました。
 計測環境は、P4-3GHz(HT)+1GB+WinXPで、640×480:8ビットグレースケールです。
#pragma unmanaged
#include "cv.h"
#pragma managed

using namespace System;
using namespace System::Drawing;

int main(array<System::String ^> ^args) {
    const int loopCount = 1000; 
    IplImage* ipl8U = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U,   1);
    IplImage* ipl16S = cvCreateImage(cvSize(640,480), IPL_DEPTH_16S, 1);

    Random^ rand = gcnew Random();
    short* p16S = reinterpret_cast<short*>(ipl16S->imageData);
    for (int i = 0 ; i < ipl16S->widthStep/2 * ipl16S->height ; i++ ) {
        *(p16S++) = Convert::ToInt16(rand->Next(-500,500));     // !どちらか!
//      *(p16S++) = 0;                                          // !どちらか!
    }

    Diagnostics::Stopwatch sw;
    Threading::Thread::Sleep(5000);
    Console::WriteLine("start");

    sw.Start();
    for( int i = 0 ; i < loopCount ; i ++ ) {
        	cvSmooth(ipl8U, ipl8U, CV_MEDIAN , 7);  // 3 or 5 or 7
    }
    sw.Stop();
    Console::WriteLine(static_cast<double>(sw.ElapsedMilliseconds) / static_cast<double>(loopCount) + "ms");

    cvReleaseImage(&ipl8U);
    cvReleaseImage(&ipl16S);
    Console::ReadLine();
    return 0;
}

小技


時間計測

大まかな処理時間の計測ができる。
int64 st = cvGetTickCount();
// 時間計測したい処理をここに書く
sprintf_s("%f ms",(cvGetTickCount()-st)/cvGetTickFrequency()/1000.0); // 単位1/1000秒

マネージコードといっしょに使う

プロジェクトプロパティの共通言語サポートを、純粋MSIL言語サポート(/clr:pure)から、共通言語サポート(/clr)に変更する。
"cv.h"をインクルードすると、下記のワーニングが出たので、
1>c:\program files\opencv\cxcore\include\cxtypes.h(212) : warning C4793: '__asm' : 関数 'int cvRound(double)' 用にネイティブ コードの生成が発生します
1>c:\program files\opencv\cxcore\include\cxtypes.h(205) : 'cvRound' の宣言を確認してください。
以下のようにして回避した。
#pragma unmanaged
#include "cv.h"
#pragma managed
最終更新:2007年11月19日 13:58