あなたの歩き方は大丈夫?歩き方判定アプリを作ってみた【その2】

こんにちは!システム開発部 オープングループ の monma です。(部署異動しました!)

スマートフォン (iPhone) の加速度センサを利用して、『歩き方を判定できるアプリ』を作成する続きになります。(前回の記事はこちら

  1. 加速度センサの値を記録するアプリを作成する
    •  このアプリはどの方向で使っても良いようにキャリブレーション機能※1を搭載する
      • 直立キャリブレーション
      • お辞儀キャリブレーション
    • ローパスフィルタ※2をかける
    • 歩き方を識別できるラベル※3を付けられるように、テキストボックスを設ける。
    • Start ボタンと Stop ボタンがあり、Start から Stop までの加速度をファイルに保存できる
  2. いくつか認識したい歩き方ラベルを作成する。
  3. 歩き方ラベルごとの歩き方で記録を行う。記録方法は上記で記載した 3 ステップで。
  4. 加速度センサの値をラベルと突き合わせて観察し、パラメータ(特徴量)を作成する
  5. ラベルとパラメータを機械学習※4にかける
  6. で作成したロジックを 1 のアプリに組み込み、判定アプリを作る

前回に引き続き、いよいよパラメータを算出していきます。上記ステップの太文字の 4 番から始めていこうと思います。

 

パラメータを作成する

今回のパラメータ作成では、あまり高度な信号処理は行わずに、出来る範囲でパラメータを作っていきたいと思います。

 

X 軸・Y 軸・Z 軸の意味

大前提として、X 軸・Y 軸・Z 軸の意味を考えてみましょう。

以下の図にあるように、X 軸は人間の横方向にかかる力を指します。Y 軸は人間に対して垂直の方向、Z 軸は人間に対して前後方向の力になります。

(上図の画像素材は、ユニティちゃんを利用させていただきました)

それぞれの方向は、上図のように、X は左方向が正の値、Y 軸は上方向が正の値、Z 軸は後ろ方向が正の値となります。

 

仮説をいくつか立ててみる

認識したいラベルは以下のラベルです。

  • 普通
  • がに股
  • すり足
  • のけぞり
  • 内股
  • 左右にふらふら
  • 猫背

上記ラベルの中で、識別が簡単そうなラベルとして、『左右にふらふら』があります。

左右にふらふらしている場合、X 軸方向に力が働くことが大きくなるはずですので、X 軸を中心に見ていくことにします。ただし、力の大きさについては個人差がありますので、パワーだけでなく X 軸と Z 軸のベクトルの方向(角度)もパラメータにしていきます。

次に『のけぞり』『猫背』を考えてみたいと思います。

のけぞっていたり、猫背になっている場合、本来 Y 軸に対してかかるはずの重力が、Z 軸の前後に対して少しかかるようになるはずです。ただし、当然歩いていますので前に進んでいるという進行方向の力も同時に加わるため、その値だけで判別できるとは限りませんが、Z 軸方向のパラメータは有用そうだと考えられます。

『がに股』や『内股』に関しては、着地時の足の形が異なることから Y 軸の変化が通常時と異なるのでは?と考えました。

 

まずは波形全体のパラメータの作成

基準となるのは、『普通』の歩き方です。これを観察してみます。

普通の歩き方の加速度センサの値(以下波形)を観察してみたいと思います。青色が X 軸、赤色が Y 軸、緑色が Z 軸になります。

Y 軸には常に 1G の力が下方向にかかっているので、-1 が基本になります。歩く度に下方向に力がかかっているのがなんとなくわかります。

ただし、ある程度の規則性は見えますが、細かい山が邪魔でよくわかりません。

 

下図のようにローパスフィルタをかけた後のグラフを表示すると、高周波成分が取り除かれて、きれいな山が表示されます。とりあえずはローパスフィルタ後の波形が見やすいので、この波形を元に分析していきます。

 


まずは、ローパスフィルタ後の Y 軸方向のグラフを観察してみます。


普通

普通

猫背

猫背

内股

内股

がに股

がに股

すり足

すり足


上記を見ていくと、足の形に関係する「がに股」「すり足」「内股」と「普通」「猫背」の間には明らかな差が見えます。これらをパラメータ化するためには、山谷の算出が必要なのではと考えました。

山と谷のカウント方法は、単純に⊿(次の値と前の値の差)が正の値から負の値に転じた時を山、負の値から正の値に転じた時を谷としてカウントすることにしました。

上記のように波形とラベルを合わせて観察することで、ラベル間の差異をパラメータにしていきました。

さらに差異だけでなく、波形全体の「平均」「標準偏差」「⊿ 平均」などもパラメータにすることとします。


 

1 ステップごとのパラメータの作成

1 歩 = 1 ステップでのパラメータ算出も行うことにします。特に『左右ふらふら』等、左足から右足に移る時に重心が変化するようなラベルに関しては、1 ステップ毎の変化を捉えるようなパラメータが存在したほうが望ましいと考えます。

以下の図にあるように 1 歩 1 歩は、Y 軸の「着地」動作と「足をあげる」動作の 2 つの動作で成り立っていますので、「着地」と「着地」の間が 1 歩であると考えられます。

11.png

 

上図の谷を検出できれば、1 歩を認識できます。谷自体の認識は、ローパスフィルタ後の ⊿ Y (Y[n+1] - Y[n] ) が負の値から正の値に変わった地点を谷と認識させます。谷から谷を 1 ステップとします。

しかし、下図の赤丸のような、1 歩のタイミングではない谷もたくさんありますので、ある程度対象を絞らなくてはなりません。

 

色々なグラフを観察した結果、平均値以下の谷を 1 ステップとしたり、観察して平均値 - 0.06 の値を閾値としてそれ以下を谷として計測するような手法も行いました。

平均 -0.06 でも漏れが結構出てしまいましたので、ローパスフィルタ後の Y 軸の値に対して、回帰直線を引くことにし、さらにその回帰直線から -0.06 した値を下回る谷を 1 ステップの谷として検出することにしました。

 

以下の 2 つのグラフは、回帰直線を引き、回帰直線から -0.06 した直線を Y 軸のグラフに赤線で引いてみたものです。どちらも 9 ステップになります。

ステップの位置が決まったら、1 ステップ内で各種パラメータを算出していきます。このカウントはローパスフィルタ前のものとローパスフィルタ後のものと両方に対して行っていきます。

 

作成したパラメータの種類

上記ような仮説や計算できる要素を元にして、下表の 49 パラメータを作成しました(かなり多くなってしまいましたが。。。)。

大きくは『全体波形の平均や標準偏差を計算するもの』と『ステップごとの平均や標準偏差を計算するもの』に分かれています。

 

細かい説明は省きますが、あまり意味のなさそうなパラメータは最初から計算していないものもあります。

パラメータの選定については後ほど行っていきます。


パラメータ算出アプリを作成する

いちいちパラメータの算出のために iOS アプリで歩くのを繰り返していてはさすがに大変なので、iOS と全く同じアルゴリズムでパラメータを計算する Mac OSX 用のアプリケーションも作成しました。iOS アプリで出力したファイルを複数指定することができ、一括でパラメータを出力することが出来ます。

 

ラベルとパラメータを機械学習にかける

上記作成したパラメータを機械学習にかけていこうと思います。機械学習には、Weka というフリーソフトを使用しました。

Weka で決定木を作成する

Weka の細かい説明は色々なところでされているので省きますが、まずは arff ファイルというラベルとパラメータをセットにしたファイルを作成します。

 

param.arff


@RELATION walk

 @ATTRIBUTE	averageX REAL
 @ATTRIBUTE	stddevX REAL
 @ATTRIBUTE	averageZ REAL
 @ATTRIBUTE	stddevZ REAL
 ・
 ・
 ・
 @ATTRIBUTE	stddevRadianLpfXYStepDev REAL
 @ATTRIBUTE	stddevRadianLpfXZStepDev REAL
 @ATTRIBUTE	class{gani,suri,sayu,neko,uchi,noke,futs}

 @DATA
-0.008373,0.153831,-0.181813,0.139971,0.056191,0.000107,-0.000103,0.118505,0.151105,0.772868,0.126998,0.636857,-0.004336,-0.192572,0.15937,0.131334,-0.000277,0.09186,-0.00002,0.046591,0.11578,0.002949,1.573843,1.619,0.155593,0.76631,1.589455,1.635081,0.077964,0.399199,0.109051,0.838862,0.024345,0.01597,0.000409,0.013643,0.000409,0.041297,0.015452,0.000711,0.111546,0.563098,0.022309,0.066469,0.109413,0.557956,0.012521,0.032907,gani
 ・
 ・
 ・
 

セクションごとに説明すると以下のように記載しています。

  • @RELATION
    • ここはなんでもいいです。適当に walk にしました。
  • @ATTRIBUTE
    • パラメータの数だけ設定します。パラメータ名の後には REAL と記載して下さい。
    • 一番最後に記載されている class はラベルを表しています。
  • @DATA
    • パラメータ群をカンマ区切りで指定します。最後にはラベルを記載しています。

上記ファイルの準備が終わったら、Weka を起動します。

ランチャーが起動しますので、「エクスプローラー」を選択してください。

16.png

起動したら左上の「ファイルを開く」ボタンをクリックして、先ほどの param.arff ファイルを開きます。

 

左下の属性の項目からいらないパラメータを削除することが出来ます。一旦削除しない形で分類を行ってみます。

 

上部の「分類」タブを選択し、分類を行っていきます。

 

左上の「選択」ボタンをクリックして、J48 を選びます。J48 は、C4.5 というアルゴリズムでの決定木の生成が可能です。

※ 決定木とは if 文による木構造です。

 

また、テストオプションで交差検証を選択していますが、例えばフォールドに 10 が設定されていた場合、与えられたデータの 9/10 のデータを使って決定木を作成し、1/10 をテストデータとして、10 回繰り返して最適な結果を得る手法です。

 

さて、左側の結果リストの上にある開始ボタンをクリックすると、決定木が作成されます。

作成された決定木は、以下のようになりました。


=== 分類器モデル (学習セット) ===
 
J48 pruned tree
------------------

averageLpfAbsXStepAve <= 0.060591
|   averageRadianXZStepDev <= 0.219804
|   |   averageZ <= -0.230004: neko (12.0)
|   |   averageZ > -0.230004
|   |   |   averageXStepAve <= 0.006544: noke (7.0)
|   |   |   averageXStepAve > 0.006544: futs (17.0)
|   averageRadianXZStepDev > 0.219804: suri (14.0)
averageLpfAbsXStepAve > 0.060591
|   stddevRadianXZStepAve <= 0.691809
|   |   averageX <= 0.017333: sayu (15.0/1.0)
|   |   averageX > 0.017333: uchi (10.0)
|   stddevRadianXZStepAve > 0.691809: gani (12.0)

この決定木を用いて、どのくらい分類できたかが以下のようにマトリックスで出力されます。(数字の合計値が 8 より多いですが、少し多めにサンプルを与えています)


=== Confusion Matrix ===

  a  b  c  d  e  f  g   <-- classified as
  9  1  1  0  1  0  0 |  a = gani
  0 13  1  0  0  0  0 |  b = suri
  0  0 12  0  1  0  1 |  c = sayu
  0  0  0 10  0  0  2 |  d = neko
  1  0  0  0  9  0  0 |  e = uchi
  0  0  2  0  0  5  1 |  f = noke
  0  1  0  2  0  1 13 |  g = futs

横軸が分類しようとしたもの、縦軸が実際に分類した時にどこに割り振られたかになります。横軸の合計値が与えたサンプル数に等しくなります。

例えば、futs(=普通)の例を見ると 13 個の分類には成功したが、1 個 noke(=のけぞり)で 2 個が neko(=猫背)、1 個が suri(=すり足) に分類されてしまったといった感じです。

決定木の結果は、1 段階目にこのマトリックスや精度などを参考にして、出力された決定木を採用するかどうかを決めます。

出力結果を見ると特に問題ないように見えますので、2 段階目に移ります。

 

決定木をアプリに組み込んで検証する

決定木を前回作成した iOS アプリに実際に組み込んでみて、それなりの認識をするかどうかを確認します。もし認識しない場合、その決定木に含まれているパラメータが分類するのには適していないパラメータの可能性があります。

決定木は if 文ですので、今回出力されたものを変換すると以下のようになります。


    if(averageLpfAbsXStepAve <= 0.060591){
        if(averageRadianXZStepDev <= 0.219804){
            if(averageZ <= -0.230004){
                result = [NSString stringWithFormat:@"猫背"];
            }else{
                if(averageXStepAve <= 0.006544){
                    result = [NSString stringWithFormat:@"仰け反り"];
                }else{
                    result = [NSString stringWithFormat:@"普通"];
                }
            }
        }else{
            result = [NSString stringWithFormat:@"すり足"];
        }
    }else{
        if(stddevRadianXZStepAve <= 0.691809){
            if(averageX <= 0.017333){
                result = [NSString stringWithFormat:@"左右ふらふら"];
            }else{
                result = [NSString stringWithFormat:@"内股"];
            }
        }else{
            result = [NSString stringWithFormat:@"がに股"];
        }
    }

実際に組み込んでみて検証した結果、全体的にあまり認識が良くなかったので、あまり意味が無いと思っていた全体波形に関するパラメータを削り、ステップに関連したパラメータのみにしてみます。

averageLpfAbsXStepAve <= 0.060591

|   averageRadianXZStepDev <= 0.219804
|   |   stddevRadianLpfXZStepAve <= 0.19851: neko (12.0)
|   |   stddevRadianLpfXZStepAve > 0.19851
|   |   |   averageXStepAve <= 0.006544: noke (7.0)
|   |   |   averageXStepAve > 0.006544: futs (17.0)
|   averageRadianXZStepDev > 0.219804: suri (14.0)
averageLpfAbsXStepAve > 0.060591
|   stddevRadianXZStepAve <= 0.691809
|   |   averageXStepAve <= 0.027983: sayu (15.0/1.0)
|   |   averageXStepAve > 0.027983: uchi (10.0)
|   stddevRadianXZStepAve > 0.691809: gani (12.0)


上記決定木が出力されましたので、組み込んで一通り検証してみたところ、誤認識はありますが、かなりの確率で分類できるアプリケーションが出来上がりました。

(時間があまり無いため、詳細な検証確認ができず申し訳ありません。。)

 

まとめ

『実験人数 1 名』『同一の環境』で作業を行ったため、かなりの認識率を出すことができましたが、実際にはこう上手くはいかないと思います。環境に依存しない、頑健性を高めるためには、あらゆる環境でのサンプル数を増やすのが近道です。サンプルを増やすだけでなく、端末を腰につけるだとか装着の仕方を工夫することももちろんできます。

パラメータでは解析手法に FFTWavelet自己相関などの周期性を見る解析や、合成ベクトルの変化等は取り入れていませんので、まだまだ意味がありそうなパラメータを増やす余地が残っています。時系列での解析ができれば、さらにリアルタイムで認識させることも可能かもしれません。

また、分類手法も決定木という手法を用いましたが、距離の計算ができるような機械学習の手法であれば、何%の確率で『がに股』になっているという数値も出すこともできるかもしれません。

つまり、もっともっと完成度を高めることは可能だと思いますので、もしご興味がありましたらチャレンジしてみてください!(今回作成したアプリは github 上に上げてあります)

 

今回は簡単ではありますが、加速度センサで歩き方を判定するという切り口で認識機能を作成しました。しかし、今回解説した方法は加速度だけでなく、ほとんどのパラメータを認識するアルゴリズム作成でとる手法ですので、何らかの認識機能を作成しようという方の参考になれば幸いです。

 

このコンテンツは、『ユニティちゃんライセンス』で提供されています。