アーカイブ
Playstation EyeをOpenCVで使う
プレステ3のカメラをWindowsのVisualStudio2008環境のOpenCVで使う方法をまとめる。
ゲーム用の画像解析用のカメラなので
- 解像度は640×480もしくは320×240と高くは無いが、320×240の時は最大120FPSでキャプチャできる
- レンズのモードを切り替える事が出来て、かなり広角になる
- オートフォーカスが無いので画像処理しやすい
- ゲインやホワイトバランスの調整が高速
- 4000円前後で手に入る。性能の割に安い
この記事で使ったプログラムは全部OpenCV勉強リポジトリにアップロードした。
売り上げランキング: 1418

GAMEはあんまり・・・
まぁ、及第点
確実に使用できるWebカメラ
気になる点を
お得!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で動かしてみる。
背景差分して影を作る。写真の通り、かなり近づいてカメラの外側にいるのに画面にちゃんと体が写っていることから広角っぷりがわかる。

コードは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
で動くらしい。
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
してリポジトリを複製してください。
- OpenCV Study: 7537970cfc6b /ShadowCamera/ : 影カメラライブラリ
- OpenCV Study: 7537970cfc6b /ShadowCameraSample/ : 影カメラライブラリを使うFormアプリケーション
影カメラライブラリの方は、OpenCV Study: 7537970cfc6b BgSubAvg/bgsubAvg.cppをDLL化したもので、cvRunningAvgを使って背景統計を取って背景差分して人型の影を作るもの → cvRunningAvgを使って背景統計を取り背景差分する – 橋本詳解
完成図はこれ。C#で作ったFormアプリで、ボタンを押すとDLLに書いてあるOpenCVの処理が起動してウィンドウが開き画像処理結果が表示/毎フレーム更新される。

■C++でDLLを作る
まず空のソリューションを作る。
ソリューション名を右クリックして新しいプロジェクト追加

VC++のクラスライブラリを作成する。名前はShadowCameraにした

できたShadowCameraプロジェクトを右クリックしてプロパティを編集

OpenCV用のライブラリを読み込む。
すべての構成->入力->追加の依存ファイル に
$(NoInherit) highgui.lib cxts.lib cv.lib cxcore.lib ml.lib cvaux.lib cvhaartraining.lib
を書く

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を参照。

なお、同一Solution内ならプロジェクト参照が便利だけど、
ReleaseにShadowCamera.dllができているのでそれを他の人に配布して、[ファイルを参照]してもらってdllから呼び出してもらっても同じ機能が使えます
ボタンを1つ置く。buttonStartという名前にして、ダブルクリックしてクリックイベントの雛形を作る

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のウィンドウが起動するはず。

openFrameworksでサンプルプロジェクトのコンパイル
WindowsでVisualStudio2008 Professional C++で、openFrameworks v0.05のサンプルをコンパイルする所まで。
今のところDirectX SDKは必要ないみたいだ。
openFrameworks: downloadの「visual studio FAT」をダウンロード。ライブラリと、サンプルプロジェクトのソースコードが入っている。
同じ場所にある「win32 example apps」にはコンパイル済みの実行ファイルが入っている。動的に波形を生成して音を出したり、3Dを回したりカメラの画像にエフェクトをかけるサンプルがたくさん入っている。
まず適当なソリューション(.slnファイル)を開いてビルドする。
エラーがでる。GLaux.libが見つからないらしい。
OpenGL関係のライブラリらしくて、
C:\Program Files\Microsoft SDKs\Windows\v5.0\Lib\IA64
で発見したので
\libs\glu
にコピーして置いた。
VS2003の頃はPlatform SDKの下にあるらしいけど、俺はMicrosoft SDKの下にあった。無いと言っている人もいる。
VS2005も過去にこのマシンにインストールしていたから、この位置にあるのかな?
どうしても見つからない人は連絡ください。
再びビルド。今度はrtAudioD.libでエラーが起こる。
libs\rtAudio\rtAudioD.libのバージョンが古いらしい。
http://www.openframeworks.cc/files/rtaudio-vs2008.zip
をダウンロードしてきて
Release\rtAudio.libをrtAudioD.libにリネームして
\libs\rtAudio
に置く。
これでコンパイル通って動く。
文字コードがCP932だよというwarningが出るので、後で文字コードは後でUTF-8に統一しよう
openCVのサンプルを動かしてみた。背景差分とblob判定してる。

ofVideoGrabberのサンプル。processing並にカメラの画像取得のコードが短い。

VS2008で単体テスト
VisualStudio2008 professionalからIDE標準で単体テスト機能(ブラックボックステスト)が付いているので使ってみた。
俺は今まで単体テストはRSpecぐらいしかやったことがなかったし、何かライブラリを作った時はそれを使うGUI付きサンプルアプリケーションを作ることでしか動作確認してこなかったけど、これは次回からどんどん使っていく事にした。
この種のテストをブラックボックステストと呼ぶらしい。テスト対象のクラスの関数1つずつをブラックボックスと見るテストで、入出力の値に注目する。ある値を関数に入れてみて、予想した値が返ってくるかチェックすることで、関数が仕様を満たしているかチェックする。
今回はBenchmarkCounter.NETの中の処理時間を計るためのOrg.Shokai.Util.Benchmark.Counterクラスのテストを作った。Startメソッドで計測開始し、Stopメソッドで停止、その間の処理時間をミリ秒で保存できる。Start/Stopを複数回行った場合、その間の平均処理時間も求める事が出来る。
テストでは、出力値が正しい値になっているかどうかチェックする。このBenchmarkCounterのテストでは
- 処理時間を取得するメソッドの場合
→ 時間の値が0以上でなければエラー - 計測回数を取得するメソッドの場合
→ 何回か計測してみて、計測回数が合わなければエラー - 計測中かどうか?フラグの場合
→ Startメソッドにfalseだったらエラー
→ Stopメソッド後にtrueだったらエラー
続きを読む…
条件付きコンパイルシンボルで実行ファイルを色々作る
VisualStudioではコンパイル時にコンパイラに渡す定数を定義しておいて、コード上のプリプロセッサで実行ファイルにその部分を含めるか定義する事が出来る事をさっき知った。
こういうコードだと、定数ZANMAIが定義されていればざんまい!!されていなければかず助になる。実行中に条件分岐するのではなく、実行ファイルに丸ごと含まれるか/含まれないかが、コンパイル構成プロファイル毎に変化する。#ifZANMAI
MessageBox.Show("ざんまい!!");
#else
MessageBox.Show("かず助!!!!");
#endif
プロジェクトのプロパティのビルドで、条件付きコンパイルシンボルをNIKUプロファイル固有の定数として追加しておく

この部分を含んでコンパイルするか、含まない実行ファイルを作るかを解釈するようになる。
動作の違う実行ファイルをそれぞれ書き出せるようになると、それだけ設定画面などを作る必要がなくなるのでうまく使えば便利そう



最近のコメント