2011年2月28日月曜日

GT5用のDirectXフィルターの作成(1)

グランツーリスモ5のキャプチャー画面から車速とギアを認識するDirectXフィルターをつくってました。

まだ習作レベルです。
こちら

やり方ですが、OpenCV1.0についてくるproxytransフィルターを改造してつくります。
(現在OpenCVのバージョンは2.2? バージョンアップしたほうがいいのかしらん。。。)
proxytransフィルターの組み込み方は、僕の以前のブログ(lostworksの方)に書きましたが、ちょっとしたコツが必要です。
僕は、KMPlayerに組み込んでます。

画像からの文字認識は、あらかじめ文字を切りだしておいて下のような画像を作っておき、これを使ってテンプレートマッチング(cvMatchTemplate())で特徴量を出します。


文字が赤いのは、R画像だけ使うためです。
cvMatchTemplate()にはいくつかメソッドを選択できるのですが、CV_TM_SQDIFF_NORMEDだとうまくいかなかったのでCV_TM_CCOEFF_NORMEDを使いました。

キャプチャー動画ですが、生の動画サイズは640X426なんですね><てっきり720X480だと思っていたので、テンプレート画像をもう一度つくらないといけないです><

特徴量を出した後の判定処理ですが、最初は、CvKNearest()を使ってk-近傍法で判定できるかなぁっと作ってみたんだけど、文字が存在しない場合(100, 10の桁の数字がない場合)の判定方法の対処方法がわからなかったので、現状ではcvMinMaxLoc()でテンプレートマッチングした値の最大値となる位置を割り出して判定してます。最大値が0.5以下だったらそこは路面って判定です。

かなり冗長に作ったのですが、フレームレートは1/60?1/30? PCにとっては長い処理時間間隔なので余裕で処理できますね^^
本当は、動画フレームから画像を切りだしてテンプレートマッチング以外の特徴量ベクトルをだし、判別処理にぶち込むのがよいのでしょうが、その特徴量ベクトルを出す手法としてなにが最適なのか、よくわからなかったので今後の研究対象です。

また、上のリンクの動画の最後の方、路面に文字が書いてあるところを通過するときに、認識が失敗しているのですよね><原因調査と改良が必要です。

まだまだ使えるレベルには程遠いなぁ。。。

2011年2月27日日曜日

インセプション

序盤は夢の設計?夢の階層?と理解できなかったです^^;;;

VFXが、いかにもって雰囲気で習作なチープな感じがしたのと(夢の中の出来事だし、話の流れの中での説明場面なので、こうなったのでしょうか)、話が長いのとが難ですが、夢の中に入り込む動機、集められたメンバーと役割、作戦内容と展開が新鮮で面白かったです。あー、でもやっぱり時間が長い><最後飽きてきちゃったもん。。。

で、DVD観終わった後でwikipediaでいつもの復習w

~ホルヘ・ルイス・ボルヘスの短篇集「伝奇集」から着想を得て~

これって文章量としては結構短い、薄い本じゃなかったですか???大学の頃、気に入って持っていたような気がする~♪懐かしいなぁ。多分実家の物置の奥に入っているはず。発掘したいw(これが、このネタで書きたかったことだったりするw)

2011年2月22日火曜日

やっと元のタイムに、そしてタイム更新。

速い人のライブビューをじっくり見ていまさらながら気づいた^^;;;;;
俺ってどアンダーな車に乗ってたんだなwww
となると、セッティングとしてはこうだろうってすぐにわかってタイムもポンポン縮めていけたんだけど、まだまだクラッシュ多し><

それと平行して、OpenCVでの画像解析の準備を。。。
速度の数字は画像切り出して特徴量出して、でいいのだろうけど、回転数をどうしたものか。
針の位置から?

2011年2月20日日曜日

だめだ、まだ鼻くそほじれねぇw

やっぱ、インターフェースを2箇所も変えるとだめっす><全然乗れてないっす。
とりあえず、ステアリングセンター、ステアリング、アクセル、ブレーキのMAX位置を設定できるようにして、若干非線形カーブ(x^y (0 < x < 1) でyを1.0近辺でいじれるようにした)にして、パッドスピードはデフォルトの1のまま試走したんだけど、高速域で自分の感覚より大きいステアリング操作が必要になるのでクラッシュしまくりました><レスポンスが悪い(ラグがある)のかもしれないな。今までアクセルふみっぱ~でいけてたところでアクセル抜いておかないと道を外れてしまいます。
中低速コーナーは、今までと違いハーフスロットルが使えるのですぐにパッド操作のタイムに追いつきそう。
高速域ではステアリングの非線形カーブをキツメになるように係数変更すればいいのかもしれないけど、スムーズに切り替えるのは難しいし、そんなことよりまず練習して体で覚えこまないといけません。

ペダル操作は左足ブレーキを使うしかないです><ガスペダルとブレーキペダルとがちょっと近すぎるんですよね。同時に踏んでしまうことがあるのでやむなく。ただ、高速域でのアクセル踏んだままの左足ブレーキは使えるかもしれない。
実車では左足ブレーキなんてやったことないし、左足ブレーキだとシートバックに体をガッチリ固定しないとケツだけで体重を支えているような状態なので疲れることこの上ないです。背もたれのある椅子にすわってプレイしてるんじゃないんだもん^^;;;
ラジコン操作(トリガーでスロットル調整)も試してみたのですが、こっちの操作系は僕にはしっくりこないなぁ。

ハードウェアの用意はOK?

とりあえず、ハードウェアの準備までは到達しました。

僕の当初の構想では、DualShock3のスティックでステアリング操作、フットペダルでアクセル/ブレーキ操作、画像処理でドライブバイワイヤ化/AT制御、これでグランツーリスモ5を走る予定だったのですが、コントローラの重複使用は無理(できたとしてもコントローラのプロトコル的にいっておそらく不具合が出るかもしれないなぁ)なので、ラジコンのプロポでのステアリング操作に変更しました。。。要するにステアリングバイワイヤ化もやれっ、さらに自動運転もやれってことになってくると思うのですが、あまり制御内容が増えるととっちらかるので、ぼちぼち慣れながら遊んで行きましょうかね。

接続設定としては、
PS3 -> XFPS4.0 Force
XFP4.0 Force -> PC
PC -> Arduino
Arduino -> ラジコンプロポのコントローラ
Arduino -> フットペダル
って感じです。
Arduinoはラジコンプロポのステアリングとフットペダルの位置(アクセル/ブレーキ)をA/D変換して取得し、PCにUSB経由シリアル出力で渡す。
PCは、取得した値を整形してXFPS4.0 Forceのデバイスドライバ(HID)の用意するパイプに書き込む。























昔買った京商ミニッツレーサーのコントローラを引っ張り出してきました。
ステアリングと(今は使わないけど)スロットルのポテンショメータに配線。
左のトリムも活かそうかどうか迷ったけど、どうせステアリングバイワイア化するんだし必要ないかって省略しました。(回路を活かしつつ組み込むのがめんどくさかったともいうw)






フットペダルは、、、ハンコン買いましたよ><フットペダルのためだけに。
昔MadCatzのハンコン持ってたけど邪魔すぎて売っちゃったし。
買っても損しないようにPCでも使える汎用性の高いのをってことで、マルチレーシングコントローラSuperSports3Xってのを買いました。さすがにロジのハンコン(GTForceとか?)は、この遊びには高すぎて買えない、、、
ためしにこのハンコンでグランツーリスモ5やってみたけど、慣れの問題もあるのだろうけど走れるものじゃなかったですw
あぁ、ステアリングの方は捨てちゃおっかなぁ。でかくて邪魔だし。
これ、品質的に値段相応じゃないなぁって思いますね。上の京商のラジコンプロポですが、ミニッツレーサー用で安物ではあるんだけど、さすがに実走で遊ぶための品質は確保されていて、ポテンショメータの範囲調整がよく出来て出荷されてます。でもこのペダル、ポテンショメータの位置がいい加減でポテンショメータの測定範囲の半分しか使い切ってません。配線も2線のみ。リファレンス電圧に対して位置を求めてないで単純に抵抗値だけで位置を求めています。






















ポテンショメータの電圧のA/D変換は、Arduinoの端子につないでA/D変換し、PCにシリアル出力してるだけです。
簡単簡単♪

PC側のシリアル制御コードは、こちらを利用。これを使って、USBのCOM(115200bps)からRead()するごとにメッセージを飛ばすようにコーディング。なので、ちょいとラグが出ているかもしれませんね。メッセージ使う場合、即座に処理できるんだったっけ?1ms位のウェイト?まぁ、ラグが酷いようなら改善の余地はまだ少しあります。























画面はこんな感じ。右のダイアログは画像が潰れてしまってますが、各種ボタンを操作するパネルです。ゲーム画面はSD出力をキャプチャしてKMPlayerで表示させます。画質的には、一旦RGB24に変換しているためか、キャプチャをそのまま出力させるよりも見辛いかも。。。(例えばインターレース処理はキャプチャのダイレクト出力とは違和感があります)
まぁ、慣れの問題だと思うし、どうしても受け入れ難かったら普通にPS3から直接表示させればいいわけで。

これに自作のフィルターを組み込んで、画像処理をさせようって魂胆です。左の赤数字はサンプルとして昔作ったフィルターによるもの。次の目標は、フィルター内で得た画像データをOpenCVで画像解析して車速/回転数を認識することかな?

さて、とりあえず試走してみましたが、アクセル/ブレーキはまだしも、ステアリング操作の非線形さに慣れが必要です。もともとグランツーリスモ5のソフト側でスティックの値を操作してるはずで、その非線形曲線を把握しないと理想的な制御の非線形曲線は得られないのだろうなぁ><

でもまぁ、やっぱこれですよねぇ。グランツーリスモ5は、お気に入りの車に乗って楽な姿勢で鼻くそほじりながらw流して走りたいじゃないですかw

2011年2月19日土曜日

USBポート数たりね

僕のPC、WindowsXPで5年物なんだけど8ポートのUSBが全部うまってしまう。。。

キーボード、マウスで×2
USB音源で×1
外付けHDDで×2
BlueToothで×1
地デジ用がメイン用途のカードリーダで×1

残り1つしかないじゃん。。。キーボード/マウスで2つも使うのと、内蔵HDDの容量の無さから外付けHDDを使わざるをえないのがネック。

ArduinoとXFPSを使うには、、、BlueToothを外すしかないか、USBハブを買うか。

xfpsをアプリによってコントロールする

以前こちらでラフなコードを書きましたが、ちゃんとxfpsとパイプで通信できるように修正しました。

この関数で、xfpsのパイプのハンドルを取得します。


HANDLE xfpsInitialize()
{
  HANDLE hPipe=INVALID_HANDLE_VALUE;

  GUID hidGuid;
  HDEVINFO hardwareDeviceInfo;

  HidD_GetHidGuid(&hidGuid);
  hardwareDeviceInfo=SetupDiGetClassDevs(&hidGuid, NULL, NULL, 0x12);

  SP_DEVICE_INTERFACE_DATA sp_devinterfacedata;
  sp_devinterfacedata.cbSize=sizeof(SP_DEVICE_INTERFACE_DATA);

  for(DWORD MemberIndex=0; ; MemberIndex++){
    int ret=SetupDiEnumDeviceInterfaces(hardwareDeviceInfo,
                                        NULL,
                                        &hidGuid,
                                        MemberIndex,
                                        &sp_devinterfacedata);
    if(ret==0) break;
  
    DWORD requiredLength, predictedLength;
    SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo,
                                    &sp_devinterfacedata,
                                    NULL,
                                    0,
                                    &requiredLength,
                                    NULL);
  
    predictedLength=requiredLength;
    PSP_DEVICE_INTERFACE_DETAIL_DATA psp_devinterfacedetail=
      (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(predictedLength);
    psp_devinterfacedetail->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

    SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo,
                                    &sp_devinterfacedata,
                                    psp_devinterfacedetail,
                                    predictedLength,
                                    &requiredLength,
                                    NULL);
  

    HANDLE hHIDDevice=CreateFile(psp_devinterfacedetail->DevicePath,
                                 0x40000000, // GENERIC_WRITE
                                 2,
                                 NULL, // no SECURITY_ATTRIBUTES
                                 3, // OPEN_EXISTING
                                 0x40000000, // FILE_FLAG_OVERLAPPED
                                 NULL);
  
    if(hHIDDevice!=INVALID_HANDLE_VALUE){
    
      HIDD_ATTRIBUTES HIDAttributes;
      HIDAttributes.Size=sizeof(ULONG);
      HidD_GetAttributes(hHIDDevice, &HIDAttributes);

      PHIDP_PREPARSED_DATA HIDPreparsedData;
      ret=HidD_GetPreparsedData(hHIDDevice, &HIDPreparsedData);
      if(ret){
        HIDP_CAPS HIDCaps;
        HidP_GetCaps(HIDPreparsedData, &HIDCaps);
        //    ...
        HidD_FreePreparsedData(HIDPreparsedData);
      }
      CloseHandle(hHIDDevice);

      char pipeFileName[MAX_PATH];
      if((HIDAttributes.VendorID==0x04b4)&&(HIDAttributes.ProductID==0x0811)){
        wsprintf(pipeFileName, "%s\\PIPE1", psp_devinterfacedetail->DevicePath);
        hPipe=CreateFile(pipeFileName,
                         0xc0000000, // GENERIC_WRITE|GENERIC_READ
                         3, // FILE_SHARE_READ|FILE_SHARE_WRITE
                         NULL,
                         3, // OPEN_EXISTING
                         0,
                         NULL);
      }
    }
    free(psp_devinterfacedetail);
  }
  SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
  return hPipe;
}

このパイプに書き込むデータですが、リバースエンジニアリングとこちらの情報から、以下の50(0x32)バイトを書きこめばよいことがわかります。

#pragma pack(push, 1)
typedef struct
{
  unsigned char Reserved0;
  unsigned char ReportType;
  unsigned char Reserved1;
  unsigned char ButtonState1;
  unsigned char ButtonState2;
  unsigned char PSButtonState;
  unsigned char Reserved2;
  unsigned char LeftStickX;
  unsigned char LeftStickY;
  unsigned char RightStickX;
  unsigned char RightStickY;
  unsigned char Reserved3[4];
  unsigned char PressureUp;
  unsigned char PressureRight;
  unsigned char PressureDown;
  unsigned char PressureLeft;
  unsigned char PressureL2;
  unsigned char PressureR2;
  unsigned char PressureL1;
  unsigned char PressureR1;
  unsigned char PressureTriangle;
  unsigned char PressureCircule;
  unsigned char PressureCross;
  unsigned char PressureSquare;
  unsigned char Reserved4[3];
  unsigned char Charge;
  unsigned char Power;
  unsigned char Connection;
  unsigned char Reserved5[9];
  unsigned short AccelerometerX; // big endian
  unsigned short Accelerometer;  // big endian
  unsigned short AccelerometerZ; // big endian
  unsigned short GyrometerX;     // big endian
}PS3ControllerData;
#pragma pack(pop)


Reserved5の9バイトは、xfpsが書き込んでる内容をそのまま使いましょう。
  rawdata->Reserved5[0]  =0x00;
  rawdata->Reserved5[1]  =0x00;
  rawdata->Reserved5[2]  =0x00;
  rawdata->Reserved5[3]  =0x00;
  rawdata->Reserved5[4]  =0x33;
  rawdata->Reserved5[5]  =0xfe;
  rawdata->Reserved5[6]  =0x77;
  rawdata->Reserved5[7]  =0x01;
  rawdata->Reserved5[8]  =0xc0;

ということで、自作アプリからPSボタンをプッシュ/リリースできることを確認しました。

さて、次はアナログインターフェースの作成ですね。
世の中のアナログ操作系は大抵はポテンショメータの抵抗値変化によって値を出しているので、AD変換して1チップマイコンに取り込むだけですぐできそうです。

さて、ケーブルを買いに行ってきます♪