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

で動くらしい。

0

Mac版Arduino IDEがlibrxtxSerial.jnilibが見つからないエラーで起動しないのを対処

起動時にこんなエラーが出た。環境はMacbook pro 17インチ OSX LeopardでXCodeなどもたぶん全部入れてあるメイン開発環境。

java.lang.UnsatisfiedLinkError: /Applications/arduino/Arduino.app/Contents/Resources/Java/librxtxSerial.jnilib: no suitable image found.



arduino error


原因は64bit環境で実行されようとしているかららしい。
対処法は2つ。


■Arduinoを32bit版で起動させる
Arduino実行ファイルを右クリックして「情報を見る」
「32ビットモードで開く」にチェック入れる
(hitoriblogさんより)


■64bit版rxtxSerial.jnilibをインストールする
http://iharder.sourceforge.net/current/java/librxtxSerial.jnilib
/Arduino 16.app/Contents/Resources/Java/
の中にある librxtxSerial.jnilib と入れ替える。

これはTechnobabble: Arduino: Problems With librxtxSerial.jnilibに書いてあった方法。


両方ともIDEの起動までは試した。
Arduinoの実機が今ないので動作は試していない。まあどっちかで動くだろう。



他にもフォーラムでも同様の質問が出ていて、こちらでは
アプリケーション/ユーティリティ/Java Preferences
でJavaアプリケーションの方でJ2SE5.0の32bit版が優先されるように上の方にもってこいという指示が出ていた。質問者はこれで解決したらしいけど俺の環境では駄目だった。
これはArduinoがJ2SE6向けに作られていないため。

0

twitter上で親しい人のタイムラインを動的に生成するYahoo Pipes

修論を出した後のテンションでまたYahooPipesを作った。

Pipes: twitter dynamic timeline


親しい人で動的にタイムラインを作るpipes。
しくみは単純で、最近favを付けたり付けられたり、replyしたりされたりした相手でタイムラインを作る。
自分の発言内容やそれに対する相手の反応によってメンバーが動的に入れ替わる。

これの面白いのは、キーワードでの振り分けと異なりごく普通にtwitterを使っているだけでタイムラインが親しいメンツに変わっていく。
一見流動性が無いように見えるが、自分をふぁぼったりreplyしてきた人は入ってくるし、その人にreplyしたりfavし返せばしばらくはdynamic timelineに入り続ける。相手をfollowする必要すらない。

忙しくてtwitter見れない時は、これだけ見ておけば親しい人のpostはだいたいチェックできるかもしれない。


RSSにはusericons.relucks.orgを使ってアイコンを表示した。ユーザ名やURLもリンクになるようにしてある。
twitter dynamic timeline


pipes全体図
twitter dynamic timeline


一番上ですぐ分岐させて、左から順に

という具合。

YahooPipesは処理時間が長くなりすぎるか、pipesから相手へのアクセスが多くなりすぎるとエラーが出てしまう。

処理時間が長くならないようにするには並列化。pipeを縦につなげないようにする。
重要なのはpipesから外部サイトへのHTTPリクエストからレスポンスまでの時間だと思う。米yahooの強力なサーバーなので正規表現でページ解析する処理時間はたぶん無視していい。

今回上の方で4つに分けているように、早めにsplitモジュールで分けてそれぞれでHTTPリクエストを発行させてレスポンス待ちにすると速い。splitで分けられたそれぞれと、Loopモジュール内でFetch PageやFetch Feedモジュールは自動的にpipes内で非同期処理になってそうな気がする。
※この時どれかで取得した結果を他のsplit先に使おうとすると、結局縦に1本につなげているのと変わらなくなってしまうので注意しましょう

pipesから他のサイトへのアクセスについては、twitterとyahooが地理的に遠くないからなのか制限が緩くなっているのかよくわからないが、3秒で100アクセスぐらいしてるのにエラーはでない。

0

bitbucket.orgのユーザのcommit logをYahoo Pipesでまとめる

mercurial版githubであるbitbucketで、ユーザ名を指定してそのユーザの全リポジトリのcommit logをまとめてRSSで見れるpipesを作った。


Pipes: bitbucket.org user’s all commit log


これまで、bitbucketにはリポジトリ単位でのRSSはあった。でもせっかくほかのユーザをfollowしてもその人が新しく始めたプロジェクトをRSSで知る方法が無かった。
このpipesを使えばユーザ名いれてRSSリーダに登録しておけばすぐわかる。
あるいは自分の全リポジトリマージFeedを作ってfriendsfeedに読み込ませたりできる。


それなりに大きいpipesになった
pipes: bitbucket.org user's all commit log

よく使うモジュールの組み合わせ

  • Loop+FetchPage or Loop+FetchFeed → Regex → Filter(permit matches regex)
    • スクレイピングの定番。取得して切り出してフィルタリング。
  • Rename(copy)→Regex→Loop+StringBuilder
    • itemのtitleや本文を他のプロパティから値を集めて自作する
  • UserInput→StringBuilder(正規表現生成)→Regex
    • ユーザに結果を絞り込ませたい時とか
最近ようやくpipesが楽しくなってきた。


俺のbitbucketでのアカウントhttp://bitbucket.org/shokai/

0

FT232RLのUSB要求電流量を最大にする

石澤さんに教えてもらったのだが、USBデバイスはホストに「自分が必要な電流量」を申告するようになっているらしい。
ArduinoやGainerやmoxaにも使われていているFT232RLというUSBシリアル変換チップもその設定ができるようになっている。
俺も秋月のFT232RL 基盤実装済みキットを使う事が多い。


■電流量の設定をする
まずFTDIのサイトからMProg(Windows用のみ)をダウンロードしてきてインストールする。
FT232シリーズのドライバももちろんインストールして、仮想COMポートとして認識しているのを確認。


MProgを起動して、
[Device]→[Scan]
[Tools]→[Read and Parse]
で接続しているFT232のデバイス設定が読み込める。
これを一旦ファイルとして保存してから、[File]→[Edit]して編集。
右上のMax Bus PowerをUSBの最大電流量である500mAに設定し、ファイルに保存。最後に[Device]→[Program]で書き込む。
FT232RLでUSB要求電力を最大にする


これでUSBケーブルを刺し直すとドライバが再インストールされ、デバイスとしての要求電流量が増えている。