WiiRemoteの限界と慣性

論文の締め切り当日だというのにまだプログラム書いている。
新規性を少しでも出したい。半ページでもいいから進捗を載せたい。
グラフがきれいに出れば片付くんだけれども。

WiiRemoteの加速度センサには、いくつか重要な限界がある。
そのひとつは「最小分解能」だとおもう。
データの上での分解能は8Bit=256Levelなんだけど、加速度自体の最小分解能、つまり1Levelが意味するところの最小の加速度が、思いのほか、低い。
精度の高い測定装置とかスペックシート(といってもデバイスの特性が手に入ってもこの場合あまり役に立たない)がないので、具体的な数字は実験的に算出するしかないのだけど、NOKIAの携帯に入っている加速度センサは12Bitで、机上静止の状態でノイズが載っているように見える。対するWiiRemoteの場合は、机上静止で、取得データの上では完全に静止させることができるので、やはり最小精度はずいぶんと違う。
いま、問題になっているのはWiiMedia:PPPのシェイクモードのように、完全に加速度センサだけで絶対位置を取得しようとした場合、この4bitの差がけっこう大きいってこと。机上でそーっと動かすと、入力値をほとんど変えずに移動させることができる時点でWiiRemoteの使い方にはかなり制約が出てくる。ちなみに値に変化がない場合、通信の上ではリポートを返さないのでΔt(測定時間)もどんどん溜まっていって危険。
たとえば、机上ではなく手で持つなら、XYZどれかの軸にかならずノイズ(=揺れ)が乗るのでゼロではなくなる。この場合はリポートも返るので微小時間も手に入る。しかしこうなると、いきなり複雑になるのは「ちょっと傾いたまま、そーっと移動」というシチュエーションだ。X軸だけ、とかいった傾き方ならいいんだけどYもZも混ざるような傾きだと、まずはWiiRemoteのPosture(姿勢)を取得する必要がある。これは重力の方向を推定して、加速度から重力分を引いてやることで、姿勢を求めることができる、はずである。実際には、微小な誤差がΔtごとに溜まっていくので、Z方向に墜落していくような絵になる。このあたりまでなら3月の時点で到達していたのだけど、なかなかいい解決法が見つからない。もちろん任天堂的には赤外線使えって話なんですけどね。
IVRCで青木君とともにSkyImage(これはTokinのモーションセンサを傘に実装して、空に絵を描く作品…近い。)を作った東大の南沢君も似たようなところではまっているらしく、彼は「歩行の場合は足が床についたタイミングを取得してリセット」という手法をとっているという。いいアイディアだけど。
それから、実際のデータを眺めて観察していると、気がつくのはこのアルゴリズムだと「慣性」がかなり大きい存在になってきてしまうこと。
慣性=その場にとどまろうとする力
WiiRemoteを手でもって移動させると加速度が発生し、速度が時間で積もり、移動する。…ここまではいい。
問題は「静止させたとき」に発生する。
静止させたときは、人間の手がWiiRemoteにブレーキをかける…つまり今来た方向と逆に力がかかる。腕やWiiRemoteの質量が変化しない場合、F=maはaの符号だけが異なる状態。WiiRemoteの出力データだけ見ると、まるで「同じところに戻ってきたように見える」という状態になる(実際には何か軌跡を描いていたとしても!)
それを防ぐにはどうしたらいいか?

まずは「時間を正確に測定すること」だと思う。

あとはWiiRemoteが移動中の加速度をどのレベルまで捉えられているのか、つまり「実用上の誤差」を実験的に算出することだと思う。

…で、今それをやっている(遅)。

なお、ストラップなどで「吊って軌跡を書く」場合は、また違った現象が観察できて非常に面白いです。
実際、ちゃんと回転力もふくめて測定できるといいんだけど、Tokinのようにコリオリの力を測定できるような回転加速度センサはWiiRemoteにはついてない。まあでもこれは値として大きいものではないので、位置と姿勢がばっちり出れば算出できるとは思うけど。
それから姿勢についても課題は多くて、斜めになった状態から「回転している様子」を表現できる式、つくれるはずなんだけど。
誰か挑戦してみませんか?

【補足】

 私の場合は「正確に時間を測定する」ために、マイクロ秒タイマーを使っています。

 以下ご参考。

 static LARGE_INTEGER last,current;
 static double dt = 0.0f; //delta time (msec)
・・・

  //Get delta time
  last = current;
  QueryPerformanceCounter( &current ); 
  QueryPerformanceFrequency( &freq );
  dt= (double)(current.QuadPart – last.QuadPart) / (double)freq.QuadPart; //microsec

 dtはグローバルで宣言してもいいかもしれない。

 ただ値の記録としてはミリ秒のほうが便利なときが多いです。

 普通の加速度だけReadReportすると、だいたい10~20ミリ秒間隔で値が取れるようです。

 これを「毎フレーム」と勘違いしてプログラミングを組むと、痛い目を食いますので要注意!