0

OpenCVのhaar-like特徴分類器の確認ツール

3週間ぐらい前に作った、OpenCVのhaarcascadeで認識した部分に円を描画するツールがけっこう便利なので公開する。
Mac OSXおよびUbuntu用でOpenCV1.0とboostが入ってれば動くはず。

OpenCVをダウンロードして付いてくるサンプルにも似た物が入ってるけど、カメラや動画や画像を入力としてGUIで表示するものだった。

俺は大量の静止画を判定して振り分けたりしたかったので、認識した物体の位置とサイズをスクリプト言語で受け取るために標準出力したり、マーキングした後の画像をファイル名を指定して保存したり、プレビューウィンドウを出すか選べるように改造した。

それと今後のためにboostとOpenCVを同時に使ってみたかったというのもある。
パラメータの解析にはboost::program_optionsを使って、画像まわりは全部OpenCVで書いてある。


■ダウンロード

MacとUbuntu用のMakefileつき。それぞれportとaptでOpenCVとboostをインストールしてあればmakeできる


■実行例
ふつうに顔認識。プレビューウィンドウも出す。結果はresult.jpgに保存。
haartest -p -c ~/haarcascades/haarcascade_frontalface_default.xml -p -i ~/Pictures/faces/shokai-umbrella.jpg -o result.jpg
detect face


sourceforgeのOpenCVのリポジトリの開発版に、目にマッチするhaarcascadeファイルがあるので持ってきて使った。目に似ている部分がたくさんあるので誤認識した。
haartest -p -c ~/haarcascades/haarcascade_mcs_righteye.xml -p -i ~/Pictures/faces/emushi-2face.jpg -o --nogray
emushi-2face.jpg_haarcascade_mcs_mouth



■boost::program_optionsで引数を取る
引数なし、もしくは–helpで実行するとこんなのが出るようにしてある。
cascadeとinputが必要です
options:
-h [ --help ] ヘルプを表示
-c [ --cascade ] arg haarcascade設定ファイル
-i [ --input ] arg 入力画像ファイル名
-o [ --output ] arg 出力ファイル名
-p [ --preview ] プレビュー表示
-f [ --flip ] 左右反転した画像も判定(青でマーク)
--nogray グレースケール、ヒストグラム均一化せずに判定
–input shokai.jpgとか-i shokai.jpgとかで引数を指定。(inputもiも同じとみなすようにヒモ付けできる)
左右反転は、左右非対称なものを認識する時に使える。右手のひら判定で左手も検出したい時とか。


詳しくは下記へ

ちなみにprogram_optionsで取った引数をOpenCVに渡すときは
cvLoadImage( argmap["input"].as<string>().c_str() )
のような感じでC++のstring型からCのchar配列にcastして渡す。


■スクリプト言語から使う
Rubyのワンライナーからディレクトリ内のJPG画像をまとめて顔認識して結果をresultsディレクトリに保存する例
mkdir results
ruby -e 'Dir.glob("*.jpg").each{|f| `haartest -c ~/haarcascades/haarcascade_frontalface_default.xml -i #{f} -o results/#{f}`}'



■コード
haartest.cpp
#include "cv.h"
#include "highgui.h"
#include <boost/program_options.hpp>
#include <iostream>
using namespace boost;
using namespace std;

void detect_draw(IplImage *img, IplImage *dst, CvHaarClassifierCascade *cascade, CvScalar color, bool isFlip);

int main(int argc, char* argv[]) {
  program_options::options_description opts("options");
  opts.add_options()
    ("help,h", "ヘルプを表示")
    ("cascade,c", program_options::value<string>(), "haarcascade設定ファイル")
    ("input,i", program_options::value<string>(), "入力画像ファイル名")
    ("output,o", program_options::value<string>(), "出力ファイル名")
    ("preview,p", "プレビュー表示")
    ("flip,f","左右反転した画像も判定(青でマーク)")
    ("nogray", "グレースケール、ヒストグラム均一化せずに判定");
  program_options::variables_map argmap;
  program_options::store(parse_command_line(argc, argv, opts), argmap);
  program_options::notify(argmap);
  if (argmap.count("help") || !argmap.count("cascade") || !argmap.count("input")) {
    cerr << "cascadeとinputが必要です" << endl;
    cerr << opts << endl;
    return 1;
  }
  
  CvHaarClassifierCascade *cascade;
  cascade = (CvHaarClassifierCascade*)cvLoad(argmap["cascade"].as<string>().c_str(), 0, 0, 0);
  if(!cascade){
    cerr << "error! Cascade not Found" << endl;
    return -1;
  }
  
  IplImage *image = cvLoadImage(argmap["input"].as<string>().c_str());
  IplImage *image_orig = cvCreateImage(cvSize(image->width, image->height), 8, 3);
  cvCopy(image, image_orig);
  if(!image){
    cerr << "error! Image File not Found" << endl;
    return -11;
  }

  if(!argmap.count("nogray")){
    IplImage* gray = cvCreateImage(cvSize(image->width, image->height), 8, 1);
    cvCvtColor(image, gray, CV_BGR2GRAY);
    cvEqualizeHist(gray, gray);
    image = gray;
  }
  
  if(argmap.count("preview")||argmap.count("output")){
    detect_draw(image, image_orig, cascade, CV_RGB(255, 0, 0), false);
    if(argmap.count("flip")){
      cvFlip(image, image);
      detect_draw(image, image_orig, cascade, CV_RGB(0, 0, 255), true);
    }
  }

  if(argmap.count("output")){
    string out_filename = argmap["output"].as<string>();
    cout << "save! " << out_filename << endl;
    cvSaveImage(out_filename.c_str(), image_orig);
  }
  
  if(argmap.count("preview")){
    char winName[] = "haarcascade test";
    cvNamedWindow(winName, CV_WINDOW_AUTOSIZE);
    cvShowImage(winName, image_orig);
    while (1) {
      if (cvWaitKey(1) == 'q') break;
    }
    cvDestroyWindow(winName);
  }
  
  cvReleaseImage(&image);
  cvReleaseImage(&image_orig);
  return 0;
}

void detect_draw(IplImage *img, IplImage *dst, CvHaarClassifierCascade *cascade, CvScalar color, bool isFlip = false){
  CvMemStorage *storage = 0;
  storage = cvCreateMemStorage(0);
  CvSeq* faces = cvHaarDetectObjects(img, cascade, storage,
                                     1.1, 2, CV_HAAR_DO_CANNY_PRUNING,
                                      cvSize(30, 30));

  for(int i = 0; i < faces->total; i++){
    CvRect *rect = (CvRect*)cvGetSeqElem(faces, i);
    cout << "x:" << rect->x << ", y:" << rect->y
         << ", width:" << rect->width << ", height:" << rect->height << endl;
    CvPoint center;
    center.x = rect->x + rect->width/2.0;
    center.y = rect->y + rect->height/2.0;
    if(isFlip){
      center.x = dst->width - center.x;
      center.y = dst->height - center.y;
    }
    int r = (rect->width + rect->height)/4.0;
    cvCircle(dst, center, r, color, 2, CV_AA, 0);
  }
}


Makefile (Mac用)
# Mac用Makefile
SRC = haartest.cpp
DST = haartest

prefix=/opt/local
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib

CV_LIBS= -lcv -lcvaux -lcxcore -lhighgui
BOOST_LIBS= $(LIBPATH)/libboost_program_options-mt.dylib

all:
g++ -O $(SRC) -o $(DST) -I$(INCPATH)/opencv -L. -L$(LIBPATH) $(CV_LIBS) -I$(INCPATH)/boost $(BOOST_LIBS)


Makefile (Linux用)
# Ubuntu用Makefile
SRC = haartest.cpp
DST = haartest

prefix=/usr
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib

CV_LIBS= -lcv -lcvaux -lcxcore -lhighgui
BOOST_LIBS= $(LIBPATH)/libboost_program_options-mt.a

all:
g++ -O $(SRC) -o $(DST) -I$(INCPATH)/opencv -L. -L$(LIBPATH) $(CV_LIBS) -I$(INCPATH)/boost $(BOOST_LIBS)

0

Playstation EyeをOpenCVで使う

プレステ3のカメラをWindowsのVisualStudio2008環境のOpenCVで使う方法をまとめる。

ゲーム用の画像解析用のカメラなので

  • 解像度は640×480もしくは320×240と高くは無いが、320×240の時は最大120FPSでキャプチャできる
  • レンズのモードを切り替える事が出来て、かなり広角になる
  • オートフォーカスが無いので画像処理しやすい
  • ゲインやホワイトバランスの調整が高速
  • 4000円前後で手に入る。性能の割に安い
などの利点がある。また、後述するWindows用のドライバを使うとゲインやホワイトバランスを任意の値にしたり、自動調整をオフにしたりできるので画像処理には便利。

この記事で使ったプログラムは全部OpenCV勉強リポジトリにアップロードした。


PLAYSTATION Eye(CEJH-15001)
PLAYSTATION Eye(CEJH-15001)
posted with amazlet at 09.07.07
ソニー・コンピュータエンタテインメント (2008-07-24)
売り上げランキング: 1418
おすすめ度の平均: 4.5
3 GAMEはあんまり・・・
3 まぁ、及第点
4 確実に使用できるWebカメラ
4 気になる点を
5 お得!WindowsXP, VISTAでも問題なく使用できましたよ!



■準備
詳しくはVS2008 OpenCVでPlaystation3 eyeを使うにまとめた。

Macではmacamを通せば普通のwebcamとして使えるし、LinuxでもgpscaにOV534というPS3Eyeが使っているチップのドライバを入れれば動くらしい

Windowsの場合、Alexさんという方が作ったドライバを入れれば使える。ただし普通のwebcamとして認識は出来ないのでFlashなどからは使えない。
ドライバと一緒にIPS3EyeLib.hが付いてきて、これを通すとDirectShowのFilterとして使えるようになる。
Visual Studio2008のC++でOpenCVを使いたいので、これもプロジェクトに読み込むようにした。

Visual Studio2008でのOpenCVのセットアップ自体はVC++2008にOpenCV環境セットアップ、カメラでキャプチャに書いた。


■キャプチャしてみる
IPS3EyeLib.hなどへのパスを通したら、プログラムを書いてみる。
dandelion’s log ? PS3EyeLibで遊んでみたに書いてあるコードが参考になった。

作ったのはこれ → PlayStation3 eyeでキャプチャして表示
このプログラムは、普通のwebcamでキャプチャして表示するだけプログラムのカメラ取得と画像取得データ部分をPS3Eye用に置き換えたもの。比較するとわかりやすいと思う。

違いは、カメラを取得するのが
cvCreateCameraCapture(0);
の代わりに
IPS3EyeLib::Create();
でカメラを取得するのと、
image = cvQueryFrame(capture);
で画像を取ってこれる所が
PBYTE capBuffer = new BYTE[(WIDTH * HEIGHT * DEPTH) / 8];
capture->GetFrame(capBuffer, DEPTH, false)
memcpy(image->imageData, capBuffer, image->imageSize);
という風に一度バッファに渡さないとならない。面倒。



■画像処理してみる
Teleshadowでも使っている、cvRunningAvgを使って背景統計を取り背景差分するのコードをPS3Eyeで動かしてみる。
背景差分して影を作る。写真の通り、かなり近づいてカメラの外側にいるのに画面にちゃんと体が写っていることから広角っぷりがわかる。
R0012068

コードはOpenCV Study: dcd37cb7a048 BgSubAvgPs3eye/bgsubAvgPs3eye.cppにアップロードした。

PS3Eyeはゲインやホワイトバランスの調整やオートゲイン調整のon/offができるんだけど、一度オートをoffにしてプログラムを終了させてから再度起動すると前にoffにした設定が引き継がれてしまって真っ暗になる。起動時に全部onにしなければならない。
今回は最初だけオートにしつつゲインだけ固定して、安定したら全部オートをoffにするようにした。これが一番安定して影が作れる。


bgsubAvgPs3eye.cpp
#include <stdio.h>
#include <highgui.h>
#include <cv.h>
#include <highgui.h>
#include <ctype.h>
#include <iostream>
#include <cstdlib>
#include <IPS3EyeLib.h>

#define WIDTH  320
#define HEIGHT 240
#define FPS    30
#define DEPTH  24

#define INIT_TIME 200
#define BG_RATIO 0.02 // 背景領域更新レート
#define OBJ_RATIO 0.005 // 物体領域更新レート
#define Zeta 10.0

int main(int argc, char** argv)
{
        bool isStop = false;
        IplImage *img = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_8U, 3);
        IPS3EyeLib *capture = IPS3EyeLib::Create();
        capture->AutoAGC(false); // ゲイン固定
        capture->SetGain(1);
        capture->AutoAWB(true);
        capture->AutoAEC(true);

        if(capture == NULL){
                printf("capture device not found!");
                return -1;
        }

        IplImage *imgAverage = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_32F, 3);
        IplImage *imgSgm = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_32F, 3);
        IplImage *imgTmp = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_32F, 3);
        IplImage *img_lower = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_32F, 3);
        IplImage *img_upper = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_32F, 3);
        IplImage *imgSilhouette = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_8U, 1);
        IplImage *imgSilhouetteInv = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_8U, 1);
        IplImage *imgResult = cvCreateImage(cvSize(WIDTH, HEIGHT), IPL_DEPTH_8U, 3);

        capture->SetFormat(IPS3EyeLib::GetFormatIndex(WIDTH, HEIGHT, FPS));
        PBYTE capBuffer = new BYTE[(WIDTH * HEIGHT * DEPTH) / 8];
        capture->StartCapture();

        printf("背景初期化中...¥n");
        cvSetZero(imgAverage);
        for(int i = 0; i < INIT_TIME; i++){
                if(capture->GetFrame(capBuffer, DEPTH, false)){
                        memcpy(img->imageData, capBuffer, img->imageSize);
                }
                cvAcc(img, imgAverage);
                printf("輝度平均 %d/%d¥n", i, INIT_TIME);
        }
        cvConvertScale(imgAverage, imgAverage, 1.0 / INIT_TIME);
        cvSetZero(imgSgm);
        for(int i = 0; i < INIT_TIME; i++){
                if(capture->GetFrame(capBuffer, DEPTH, false)){
                        memcpy(img->imageData, capBuffer, img->imageSize);
                }
                cvConvert(img, imgTmp);
                cvSub(imgTmp, imgAverage, imgTmp);
                cvPow(imgTmp, imgTmp, 2.0);
                cvConvertScale(imgTmp, imgTmp, 2.0);
                cvPow(imgTmp, imgTmp, 0.5);
                cvAcc(imgTmp, imgSgm);
                printf("輝度振幅 %d/%d¥n", i, INIT_TIME);
        }
        cvConvertScale(imgSgm, imgSgm, 1.0 / INIT_TIME);
        printf("背景初期化完了¥n");

        capture->AutoAWB(false); // ホワイトバランス固定
        capture->AutoAEC(false);

        char winNameCapture[] = "Capture";
        char winNameSilhouette[] = "Silhouette";
        cvNamedWindow(winNameCapture, 0);
        cvNamedWindow(winNameSilhouette, 0);

        int waitKey = 0;
        while(1){
                if(!isStop){
                        if(capture->GetFrame(capBuffer, DEPTH, false)){
                                memcpy(img->imageData, capBuffer, img->imageSize);
                        }
                        if(img == NULL) break;
                        cvConvert(img, imgTmp);

                        // 輝度範囲
                        cvSub(imgAverage, imgSgm, img_lower);
                        cvSubS(img_lower, cvScalarAll(Zeta), img_lower);
                        cvAdd(imgAverage, imgSgm, img_upper);
                        cvAddS(img_upper, cvScalarAll(Zeta), img_upper);
                        cvInRange(imgTmp, img_lower, img_upper, imgSilhouette);

                        // 輝度振幅
                        cvSub(imgTmp, imgAverage, imgTmp);
                        cvPow(imgTmp, imgTmp, 2.0);
                        cvConvertScale(imgTmp, imgTmp, 2.0);
                        cvPow(imgTmp, imgTmp, 0.5);

                        // 背景領域を更新
                        cvRunningAvg(img, imgAverage, BG_RATIO, imgSilhouette);
                        cvRunningAvg(imgTmp, imgSgm, BG_RATIO, imgSilhouette);

                        // 物体領域を更新
                        cvNot(imgSilhouette, imgSilhouetteInv);
                        cvRunningAvg(imgTmp, imgSgm, OBJ_RATIO, imgSilhouetteInv);

                        cvErode(imgSilhouette, imgSilhouette, NULL, 1); // 収縮
                        cvDilate(imgSilhouette, imgSilhouette, NULL, 2); // 膨張
                        cvErode(imgSilhouette, imgSilhouette, NULL, 1); // 収縮

                        cvMerge(imgSilhouette, imgSilhouette, imgSilhouette, NULL, imgResult);
                        cvShowImage(winNameCapture, img);
                        cvShowImage(winNameSilhouette, imgResult);
                }
                waitKey = cvWaitKey(33);
                if(waitKey == 'q') break;
                if(waitKey == ' '){
                        isStop = !isStop;
                        if(isStop) printf("stop¥n");
                        else printf("start¥n");
                }
        }

        delete [] capBuffer;
        capture->StopCapture();
        cvDestroyWindow(winNameCapture);
        cvDestroyWindow(winNameSilhouette);
        return 0;
}


今回は320×240で30FPSで動かしたが、
dandelion’s log PS3EyeLibで遊んでみたより

320×240@15fps
320×240@30fps
320×240@60fps
320×240@75fps
320×240@100fps
320×240@125fps
640×480@15fps
640×480@30fps
640×480@40fps
640×480@50fps
640×480@60fps
640×480@75fps

で動くらしい。

3

C++/CLIで動かしているOpenCVのCvImageをC#.NETのBitmapオブジェクトにして読み込む

橋本商会: Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC# Formアプリから使う の続き。
FormアプリからOpenCVのHighGUIを別ウィンドウで開いていたが、Bitmapオブジェクトにキャストする事で.NETアプリのPictureBoxなどに読み込む事が出来る。
これにより画像処理だけ高速にC++で行い、アプリケーションのUIやネットワーク部分はC#.NETで実装するといういいとこどりが出来る。


■出来た物
FormにLoadImageボタンとPictureBoxを追加。
LoadButtonを押すとShadowCamera.dllから呼び出しているOpenCV側のCvImageが変換されてBitmapになって出てくる関数を作った。
C++/OpenCVのCvImageを.NETのBitmapに変換できた

今回作っているShadowCameraは背景を蓄積させて統計を取り、差分で人物の影だけを抜き出すライブラリなので、毎フレーム計算し続ける必要がある。runCapture()の最後でwhileループさせて実現している。
なので、1つBitmapオブジェクトを用意しておき、whileループの最後で処理結果を毎回保存しておいてそれをC#側から適当なタイミングで読みに行く実装にした。
DLLであるC++/OpenCV側のループを毎フレーム回し続けながら、それをブロックする事なくC# Formアプリ側から取得できる。


例によってOpenCV勉強用mercurialリポジトリchanges19としてcommitしてある。


なお、VisualStudioはC#はutf8なのにC++はshift_jisで保存されてしまい、utf8にするとビルドできなくなってしまうのでリポジトリビューアでは文字化けしてしまっている。悲しい。
文字化けしている部分には大したこと書いてないので無視していいし、必要ならリポジトリの左側からzipでダウンロードできる。


ちなみに今回の方法は、3年以上前の開発合宿の記事に匿名様が
コメントで教えてくれたSharperCVのCvImage型をSystem.Drawing.Bitmap型に変換する方法が書かれていてそれを元にした。
やはりできなかった事とかも書いてみるものだなと思った。もちろん助けをアテにしちゃいけないが


■ShadowCamera.dll側の変更
C++プロジェクトのShadowCameraに、System.Drawingへの参照を追加する。Bitmapクラスを使うため。
C++/OpenCVのCvImageを.NETのBitmapに変換できた


「新しい参照の追加」ボタンを押す
C++/OpenCVのCvImageを.NETのBitmapに変換できた


.NETタブの下の方にスクロールしていって、System.Drawing を追加する。
C++/OpenCVのCvImageを.NETのBitmapに変換できた


ShadowCamera.hにコードを書き加える

OpenCV Study: c9320e90f1be ShadowCamera/ShadowCamera.h
DLLへの参照を追加して

using namespace System::Drawing;


勝手に書き換えられたら嫌なので、privateでグローバル変数 shadow を作っておく
private:
    System::Drawing::Bitmap^ shadow;


DLL利用側のためのアクセサを用意する
public:
    System::Drawing::Bitmap^ ShadowImage(void){
        return this->shadow;
    }
これでDLL内のshadowというBitmapオブジェクトを、DLL外から読み出す事が出来るようになった。
いまいち ^ や ~ などをつけるルールを把握しきれていないけど、他のC++/CLIアプリがそうやっていたので先人の作法に倣うことにする。


あとはrunCapture()の最後のwhileループ内で、毎回作られるimgResultというIplImage(CvImage互換)オブジェクトをdynamic_castしてshadowに保存する。
this->shadow = dynamic_cast<System::Drawing::Bitmap^>
    (gcnew System::Drawing::Bitmap(w,h,w*3, 
    System::Drawing::Imaging::PixelFormat::Format24bppRgb,
    static_cast<System::IntPtr>(imgResult->imageDataOrigin)));
長いので変換部分のみ抜粋。ShadowCamera.hの85~125行目がwhileループです

dynamic_castやstatic_castの違いはC++の新しいキャストが参考になった。
オブジェクトの代入のためのcastは.NETの上で動いているからdynamic_castで、unmanaged領域と行き来するSystem::IntPtrと*char間のcastはstatic_castになるという理解でいいのかな?



■ShadowCamera.dllを使うC# Formアプリ側の変更
ボタン(buttonLoadImage)と画像を表示するためのPictureBox(pictureBoxResult)を配置。
PictureBoxはSizeModeをStretchImageにしておくと自動リサイズされて便利。
C++/OpenCVのCvImageを.NETのBitmapに変換できた


で、ボタン押下イベントで
pictureBoxResult.Image = cam.ShadowImage();
BitmapをPictureBoxのImageプロパティに突っ込むだけで表示できる。楽ちんだ
C++/OpenCVのCvImageを.NETのBitmapに変換できた

終わり。

ボタンじゃなくてThreadなどで適当にsleepを入れながら読み込み続ければふつうに影カメラになる。

2

Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC# Formアプリから使う

重い画像処理をC++の画像処理ライブラリであるOpenCVでやって、ネットワーク処理は慣れ親しんだC#.NETでやりたい。
そこで、色々試行錯誤した結果、C#からは「処理し終わった画像を表示してくれるカメラ」に見えるような、OpenCVの処理部分を含んだDLLをC++/CLIで作ることにした。
(OpenCVそのものについてと開発環境構築は橋本商会 ? OpenCVをはじめたに書いた)


他にも連動のさせかたはSharperCVというC#ラッパーを使うとか、SocketでOpenCVで処理した結果の画像だけ送るとか色々とやり方はあるけど、
前者は2003年でプロジェクトが止まっていてcvPow等の配列関連の関数が無く、後者は俺のMicrosoft SDKのWinUser.hの6373行目あたりから文字化けしていてWinSocketが使えなかったりしたのでDLLを作るやり方に落ち着いた。


この方法のメリットは、まず納得のいくOpenCVの処理をC++単体のプロジェクトでいつもどおり書いて、それをほぼコピペすればDLLができてしまうところ。
ShaperCVを使う場合は全て関数を照らし合わせてC#で書き直さなければならないので面倒。
Socketを使う方はよく知らない。


DLLの作り方はかせいさんとこが参考になった。CLIを使う場合と使わない場合が解説されている。(今回は楽する為にCLIを使う)



■今回つくるもの
mercurialリポジトリをアップロードした。左のバーからzipでダウンロードするか、

hg clone http://shokai.org/projects/opencv-study/ opencv-study

してリポジトリを複製してください。

の2つを作ります。
影カメラライブラリの方は、OpenCV Study: 7537970cfc6b BgSubAvg/bgsubAvg.cppをDLL化したもので、cvRunningAvgを使って背景統計を取って背景差分して人型の影を作るもの → cvRunningAvgを使って背景統計を取り背景差分する – 橋本詳解


完成図はこれ。C#で作ったFormアプリで、ボタンを押すとDLLに書いてあるOpenCVの処理が起動してウィンドウが開き画像処理結果が表示/毎フレーム更新される。
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う



■C++でDLLを作る
まず空のソリューションを作る。
ソリューション名を右クリックして新しいプロジェクト追加
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う


VC++のクラスライブラリを作成する。名前はShadowCameraにした
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う


できたShadowCameraプロジェクトを右クリックしてプロパティを編集
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う


OpenCV用のライブラリを読み込む。
すべての構成->入力->追加の依存ファイル に

$(NoInherit) highgui.lib cxts.lib cv.lib cxcore.lib ml.lib cvaux.lib cvhaartraining.lib

を書く
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う

ShadowCamera.hのClass名をCameraに変更、さらにint runCapture()関数を書く。
この関数の中にOpenCVのmain()を全部突っ込んでしまえばそのまま動く。今回はbgsubAvg.cppのmain()をコピペしたけど他にも赤色重心追跡や肌色検出、単純なカメラ入力など色々作ったので好きなものをコピペしてよい。
OpenCV関連のヘッダを#includeで読み込むのを忘れずに。
(試しにint asdf()という単に数値を返すだけの関数も作っておいた。これがC#から呼べればちゃんとDLLとして読み込めているはず)

OpenCV Study: 7537970cfc6b ShadowCamera/ShadowCamera.h

// ShadowCamera.h
#include <cv.h>
#include <highgui.h>
#include <ctype.h>
#include <stdio.h>

#pragma once

using namespace System;

namespace ShadowCamera {

    public ref class Camera
    {
        // TODO: このクラスの、ユーザーのメソッドをここに追加してください。
    public:
        int asdf(void){
            return 15;
        }

    public:
        int runCapture(void){

            bool isStop = false;
            const int INIT_TIME = 50;
            const double BG_RATIO = 0.02; // 背景領域更新レート
            const double OBJ_RATIO = 0.005; // 物体領域更新レート
            const double Zeta = 10.0;
            IplImage *img = NULL;

            CvCapture *capture = NULL;
            capture = cvCreateCameraCapture(0);
            //capture = cvCaptureFromAVI("test.avi");
            if(capture == NULL){
                printf("capture device not found!!");
                return -1;
            }

            img = cvQueryFrame(capture);
            int w = img->width;
            int h = img->height;

            IplImage *imgAverage = cvCreateImage(cvSize(w, h), IPL_DEPTH_32F, 3);
            IplImage *imgSgm = cvCreateImage(cvSize(w, h), IPL_DEPTH_32F, 3);
            IplImage *imgTmp = cvCreateImage(cvSize(w, h), IPL_DEPTH_32F, 3);
            IplImage *img_lower = cvCreateImage(cvSize(w, h), IPL_DEPTH_32F, 3);
            IplImage *img_upper = cvCreateImage(cvSize(w, h), IPL_DEPTH_32F, 3);
            IplImage *imgSilhouette = cvCreateImage(cvSize(w, h), IPL_DEPTH_8U, 1);
            IplImage *imgSilhouetteInv = cvCreateImage(cvSize(w, h), IPL_DEPTH_8U, 1);
            IplImage *imgResult = cvCreateImage(cvSize(w, h), IPL_DEPTH_8U, 3);

            printf("背景初期化中…¥n");
            cvSetZero(imgAverage);
            for(int i = 0; i < INIT_TIME; i++){
                img = cvQueryFrame(capture);
                cvAcc(img, imgAverage);
                printf("輝度平均 %d/%d¥n", i, INIT_TIME);
            }
            cvConvertScale(imgAverage, imgAverage, 1.0 / INIT_TIME);
            cvSetZero(imgSgm);
            for(int i = 0; i < INIT_TIME; i++){
                img = cvQueryFrame(capture);
                cvConvert(img, imgTmp);
                cvSub(imgTmp, imgAverage, imgTmp);
                cvPow(imgTmp, imgTmp, 2.0);
                cvConvertScale(imgTmp, imgTmp, 2.0);
                cvPow(imgTmp, imgTmp, 0.5);
                cvAcc(imgTmp, imgSgm);
                printf("輝度振幅 %d/%d¥n", i, INIT_TIME);
            }
            cvConvertScale(imgSgm, imgSgm, 1.0 / INIT_TIME);
            printf("背景初期化完了¥n");

            char winNameCapture[] = "Capture";
            char winNameSilhouette[] = "Silhouette";
            cvNamedWindow(winNameCapture, 0);
            cvNamedWindow(winNameSilhouette, 0);

            while(1){
                if(!isStop){
                    img = cvQueryFrame(capture);
                    if(img == NULL) break;
                    cvConvert(img, imgTmp);

                    // 輝度範囲
                    cvSub(imgAverage, imgSgm, img_lower);
                    cvSubS(img_lower, cvScalarAll(Zeta), img_lower);
                    cvAdd(imgAverage, imgSgm, img_upper);
                    cvAddS(img_upper, cvScalarAll(Zeta), img_upper);
                    cvInRange(imgTmp, img_lower, img_upper, imgSilhouette);

                    // 輝度振幅
                    cvSub(imgTmp, imgAverage, imgTmp);
                    cvPow(imgTmp, imgTmp, 2.0);
                    cvConvertScale(imgTmp, imgTmp, 2.0);
                    cvPow(imgTmp, imgTmp, 0.5);

                    // 背景領域を更新
                    cvRunningAvg(img, imgAverage, BG_RATIO, imgSilhouette);
                    cvRunningAvg(imgTmp, imgSgm, BG_RATIO, imgSilhouette);

                    // 物体領域を更新
                    cvNot(imgSilhouette, imgSilhouetteInv);
                    cvRunningAvg(imgTmp, imgSgm, OBJ_RATIO, imgSilhouetteInv);

                    cvErode(imgSilhouette, imgSilhouette, NULL, 1); // 収縮
                    cvDilate(imgSilhouette, imgSilhouette, NULL, 2); // 膨張
                    cvErode(imgSilhouette, imgSilhouette, NULL, 1); // 収縮

                    cvMerge(imgSilhouette, imgSilhouette, imgSilhouette, NULL, imgResult);
                    cvShowImage(winNameCapture, img);
                    cvShowImage(winNameSilhouette, imgResult);
                }
                int waitKey = cvWaitKey(33);
                if(waitKey == ‘q’) break;
                if(waitKey == ‘ ‘){
                    isStop = !isStop;
                    if(isStop) printf("stop¥n");
                    else printf("start¥n");
                }
            }

            cvReleaseCapture(&capture);
            cvDestroyWindow(winNameCapture);
            cvDestroyWindow(winNameSilhouette);

            return 0;
        }
    };
}



■C#でFormアプリを作る
新しいプロジェクトを作る。
ソリューションエクスプローラで[参照設定]→[プロジェクト参照]でShadowCameraを参照。
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う
なお、同一Solution内ならプロジェクト参照が便利だけど、
ReleaseにShadowCamera.dllができているのでそれを他の人に配布して、[ファイルを参照]してもらってdllから呼び出してもらっても同じ機能が使えます


ボタンを1つ置く。buttonStartという名前にして、ダブルクリックしてクリックイベントの雛形を作る
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う



ShadowCameraを読み込んで

using ShadowCamera;


新しいCameraクラスのインスタンスを作って、startButtonイベント内でさっき作ったrunCapture関数を呼ぶようにする
OpenCV Study: 7537970cfc6b ShadowCameraSample/ShadowCameraForm.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ShadowCamera;

namespace ShadowCameraSample
{
    public partial class ShadowCameraForm : Form
    {
        private Camera cam;
        public ShadowCameraForm()
        {
            InitializeComponent();
            cam = new Camera();
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            cam.runCapture();
        }
    }
}



ShadowCameraSampleを(右クリックして)スタートアッププロジェクトに設定し、
ビルドするとShadowCameraFormが起動する。ボタンを押すとしばらくカメラを読み込んで、数秒後にOpenCVのHighGUIのウィンドウが起動するはず。
Cで書いたOpenCVのコードをC++/CLIで.NET用DLLにしてC#から使う

0

暗い所でも背景差分できる

プログラムはこれ
OpenCV Study: 5af15ced343f BgSubAvg/bgsubAvg.cpp

R0011318

ノシ ノシ できる



ハードをもう一つ作るぞ

(エイプリルフールだけどコラではないです)