プレステ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

で動くらしい。