WiimoteLibでWii Balance Boardを使う 2009

アップデートシリーズぱーと2。
うちの日記の一番人気(?)、WiimoteLibを使ってWii Balance Boardの入力を受け取る方法についてです(・∀・)
昔の記事はコチラ。

やりかた

まずはWiimoteLibのダウンロードということで、こちらから(・∀・)
Managed Library for Nintendo's Wiimote
http://www.codeplex.com/WiimoteLib
今ではWiimoteLibも標準でバランスボードに対応しているので、自分が昔やっていたような対応は不要デス。


っで、Visual Studioでプロジェクトを作っていくわけですが、とりあえずサンプルソースの全体を載せちゃいます(・ω・)

public partial class MainForm : Form
{
    private Wiimote wiimote = new Wiimote();

    // 前の重心
    private float prevCenterOfGravityX;
    // 歩数
    private int passometer;
    // 方向
    private float direction;

    // フォームオープン時接続
    public MainForm()
    {
        InitializeComponent();

        wiimote.WiimoteChanged += OnWiimoteChanged;
        wiimote.Connect();
    }

    // フォームクローズ時切断
    private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        wiimote.Disconnect();
    }

    // 処理の同期化用
    private delegate void UpdateWiimoteStateDelegate(object sender, WiimoteChangedEventArgs args);

    // 状態更新ハンドラ
    private void OnWiimoteChanged(object sender, WiimoteChangedEventArgs e)
    {
        if( InvokeRequired )
        {
            BeginInvoke( new UpdateWiimoteStateDelegate(OnWiimoteChanged), sender, e );
            return;
        }

        // バランスボード
        switch( e.WiimoteState.ExtensionType )
        {
        case ExtensionType.BalanceBoard:
            BalanceBoardState bbs = e.WiimoteState.BalanceBoardState;

            // 各センサ
            lblTL.Text = bbs.SensorValuesKg.TopLeft.ToString();
            lblTR.Text = bbs.SensorValuesKg.TopRight.ToString();
            lblBL.Text = bbs.SensorValuesKg.BottomLeft.ToString();
            lblBR.Text = bbs.SensorValuesKg.BottomRight.ToString();

            // 体重
            lblWeight.Text = bbs.WeightKg.ToString();

            // 歩行
            if( this.prevCenterOfGravityX > 0 )
            {
                if( bbs.CenterOfGravity.X < -5 )
                {
                    // 左へ重心移動
                    this.passometer++;
                    this.prevCenterOfGravityX = bbs.CenterOfGravity.X;
                    lblPassometer.Text = this.passometer.ToString();

                    // 向きの更新
                    UpdateDirection( bbs );
                    lblDirection.Text = this.direction.ToString();
                }
            }
            else
            {
                if( bbs.CenterOfGravity.X > 5 )
                {
                    // 右へ重心移動
                    this.passometer++;
                    this.prevCenterOfGravityX = bbs.CenterOfGravity.X;
                    lblPassometer.Text = this.passometer.ToString();

                    // 向きの更新
                    UpdateDirection( bbs );
                    lblDirection.Text = this.direction.ToString();
                }
            }
            break;
        }
    }

    // 向きの更新
    private void UpdateDirection(BalanceBoardState bbs)
    {
        const int BSL = 43;

        float ktx = bbs.SensorValuesKg.TopLeft / ( bbs.SensorValuesKg.TopLeft + bbs.SensorValuesKg.TopRight );
        float kbx = bbs.SensorValuesKg.BottomLeft / ( bbs.SensorValuesKg.BottomLeft + bbs.SensorValuesKg.BottomRight );

        float tx = ( ( ktx - 1 ) / ( ktx + 1 ) ) * ( -BSL / 2 );
        float ty = ( ( kbx - 1 ) / ( kbx + 1 ) ) * ( -BSL / 2 );

        if( Math.Abs( ty - tx ) > 1 )
        {
            this.direction += ty - tx;
        }
    }
}

WiimoteLibから標準で取得できる4つの圧力センサ値と体重*1の表示と、後は、自前で歩数と方向の計算みたいな事をやっています(・∀・)
っで、以下簡単に解説。

初期化/終了処理

まず、Wiimoteクラスのインスタンスを用意します。

private Wiimote wiimote = new Wiimote();

っで、ここではフォームロード時にConnect()で接続し、フォームクローズ時にDisconnect()で切断しています。

// フォームオープン時接続
public MainForm()
{
    InitializeComponent();

    wiimote.WiimoteChanged += OnWiimoteChanged;
    wiimote.Connect();
}

// フォームクローズ時切断
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    wiimote.Disconnect();
}

Wiimote.Connect()内で何をやっているかと言えば、HIDデバイスからVendorID = Nintendo, ProductID = Wiimoteなデバイスを見つけて、そのハンドルをオープンして非同期読み込みの開始〜というような事をやっています。
っで、読み込んだデータを解析してWiimoteChangedイベントを発生してくれます。

WiimoteChangedイベント

サンプルソースではWiimoteChangedイベントの処理としてOnWiimoteChanged()メソッドを用意しています。

// 状態更新ハンドラ
private void OnWiimoteChanged(object sender, WiimoteChangedEventArgs e)
{
    if( InvokeRequired )
    {
        BeginInvoke( new UpdateWiimoteStateDelegate(OnWiimoteChanged), sender, e );
        return;
    }

    // バランスボード
    switch( e.WiimoteState.ExtensionType )
    {
    case ExtensionType.BalanceBoard:
        BalanceBoardState bbs = e.WiimoteState.BalanceBoardState;
...

まず、最初にControl.InvokeRequiredで判定をしてBeginInvoke()しなおしていますが、これはWiimoteChangedイベントが発生するスレッドが非同期読み込みを行ったスレッド(スレッドプール内のスレッド)であるためで、処理内でForm上の項目を更新するためにこのようにしています(・ω・)
っで、その後の実際の処理についてですが、ここでは一応、接続したデバイスがバランスボードかどうかを見ています。

バランスボードの値取得

バランスボードの各センサ値については、WiimoteChangedEventArgs.WiimoteState.BalanceBoardStateの各メンバで参照ができます。
また、センサ値から計算された体重の値も取得できます。

BalanceBoardState bbs = e.WiimoteState.BalanceBoardState;

// 各センサ
lblTL.Text = bbs.SensorValuesKg.TopLeft.ToString();
lblTR.Text = bbs.SensorValuesKg.TopRight.ToString();
lblBL.Text = bbs.SensorValuesKg.BottomLeft.ToString();
lblBR.Text = bbs.SensorValuesKg.BottomRight.ToString();

// 体重
lblWeight.Text = bbs.WeightKg.ToString();

歩数の判定

っで、ここからは独自の処理として、バランスボード上で歩いた歩数と方向の判定なんかをやってみたいと思います(・∀・)


歩数にしても方向にしても、Wiimoteから取得できるオリジナルの値は圧力センサの値が4つだけなわけで、この値をなんらかの計算&閾値による判定を行うことにより、歩数と方向の計算をすることになります(・ω・)
っで、BalanceBoardStateにはセンサ値4つの他に、そこから計算された体重(WeightKg)と重心(CenterOfGravity)がメンバとして用意されているので、歩数の計算にはCenterOfGravityを使うことにしてみます。


バランスボード上で、どういうセンサ値の変化が起こったときに1歩進んだと見なせるかと言えば、重心が左右の反対方向に移ったときにそう見なせるのではないかと思います(・ω・)

// 歩行
if( this.prevCenterOfGravityX > 0 )
{
    if( bbs.CenterOfGravity.X < -5 )
    {
        // 左へ重心移動
        this.passometer++;
        this.prevCenterOfGravityX = bbs.CenterOfGravity.X;
...
    }
}
else
{
    if( bbs.CenterOfGravity.X > 5 )
    {
        // 右へ重心移動
        this.passometer++;
        this.prevCenterOfGravityX = bbs.CenterOfGravity.X;
...
    }
}

っということで、ここでは重心のX軸方向が、前回の判定結果とは左右逆で、かつ閾値5以上変化したら1歩進んだと見なし、歩数passometerをインクリメントする処理としています。

方向の判定

歩数の次に、向いている方向の判定を考えてみたいと思います。
その前準備として、歩数の判定の所で出てきたCenterOfGravityの計算方法と、閾値5の根拠について説明します。


まず、CenterOfGravityの計算式がどうなっているのかについて、WiimoteLibのソースより下記に抜粋します。

// length between board sensors
private const int BSL = 43;

// width between board sensors
private const int BSW = 24;


float Kx = (mWiimoteState.BalanceBoardState.SensorValuesKg.TopLeft + mWiimoteState.BalanceBoardState.SensorValuesKg.BottomLeft) /
           (mWiimoteState.BalanceBoardState.SensorValuesKg.TopRight + mWiimoteState.BalanceBoardState.SensorValuesKg.BottomRight);
float Ky = (mWiimoteState.BalanceBoardState.SensorValuesKg.TopLeft + mWiimoteState.BalanceBoardState.SensorValuesKg.TopRight) /
           (mWiimoteState.BalanceBoardState.SensorValuesKg.BottomLeft + mWiimoteState.BalanceBoardState.SensorValuesKg.BottomRight);

mWiimoteState.BalanceBoardState.CenterOfGravity.X = ((float)(Kx - 1) / (float)(Kx + 1)) * (float)(-BSL / 2);
mWiimoteState.BalanceBoardState.CenterOfGravity.Y = ((float)(Ky - 1) / (float)(Ky + 1)) * (float)(-BSW / 2);

要するに、センサ時の左右/前後の比率を計算して正規化し、バランスボードのサイズを元に値を出しているわけですね(・∀・)
この結果、X軸については-21〜21、Y軸については-12〜12くらいの値が取得できる事になります。
歩数計算時の閾値5というのも、X軸の上限20に対して、まあこんなもんかなということで適当に決めています(;´д`)


っで、進んでいる方向をどう判定するかについてですが(・ω・)
バランスボード上で、どういうセンサ値の変化が起こったときにまがって進んでいると見なせるかと言えば、前の重心と後の重心のX軸の差異が大きいときにそう見なせるのではないかと思います。

っで、前の重心と後の重心をどう計算するかについてですが、ここではCenterOfGravityの計算式を真似てこんな風にしてみました。

// 向きの更新
private void UpdateDirection(BalanceBoardState bbs)
{
    const int BSL = 43;

    float ktx = bbs.SensorValuesKg.TopLeft / ( bbs.SensorValuesKg.TopLeft + bbs.SensorValuesKg.TopRight );
    float kbx = bbs.SensorValuesKg.BottomLeft / ( bbs.SensorValuesKg.BottomLeft + bbs.SensorValuesKg.BottomRight );

    float tx = ( ( ktx - 1 ) / ( ktx + 1 ) ) * ( -BSL / 2 );
    float ty = ( ( kbx - 1 ) / ( kbx + 1 ) ) * ( -BSL / 2 );

    if( Math.Abs( ty - tx ) > 1 )
    {
        this.direction += ty - tx;
    }
}

歩数が増えたときに、このUpdateDirection()でどちらの方向に進んだかを判定しています。
閾値1以上差異があるときに方向を変化させていますが、これは小さな変化が毎回発生するのを防ぐためです(・ω・)
っで、こんな風な計算式で一応方向の判定はできますが、directionの増減1に対して実際にどれだけの処理を行うかについては、アプリケーション毎の処理になると思います。

その他

…っというわけで、WiimoteLibでWii Balance Boardを使う 2009度版はこんなカンジです(・∀・)
後は、C++でやりたい場合とか、バランスボード以外の使い方、歩行以外のアクションの判定方法についてですが。


C++(not CLI)でやる場合については、WiimoteLibを使おうとするんじゃなくて、自前でHIDデバイスをリードする処理を書いても良いかもです(・∀・)
結局の所、hid.dll、setupapi.dllのAPIを使ってWiimoteのHIDデバイスを特定して、そのハンドルをCreateFile()でオープンしてReadFile()、WriteFile()するだけなので。


バランスボード以外の使い方については、WiimotLibの中に入っているsamplesのソースを見れば一通りの事はわかると思うので、特に言うことは無かったりして(・ω・)


最後に、歩行以外のアクションの判定方法についてですが。
上の方でも書いている通り、バランスボードから取得できるオリジナルの値は、4点の圧力センサの値だけです。
なので、アクションの判定方法としては、あるセンサの値が他のセンサの値に比べて閾値以上に変化した時にトリガ、…っというのが基本になるかと思います(・ω・)
TopRightのみ値が増加したら攻撃、TopLeftのみ値が増加したら防御、っとか(・∀・)

*1:WiimoteLib内でSUM(センサ値) / 4しているだけです。