技適の認証を通過し、アプリ内部には日本語が追加されるなど、どんどん日本での販売が近づいてきているGoogle Glass。空港などでも活躍の場を増やしていますが、もっと身近な車との相性はどうだろうか?危なくて使えない?意外と使える?
そんな疑問を解決するべく実際に専用のGlasswareを開発して実験してみました。
■こんなアプリを作ってみた!
Google Glassは無線での通信ができませんので実際には、Google Glassでは、それらの情報を表示する3つの画面を持つアプリを作成しました。
スマートフォン⇔(無線)⇔PC⇔(有線)⇔Google Glass
という何ともオーバーヘッドの多い方法でやり取りしています。
1つめの画面は、加速度を表示する画面です。加速度は、円の中の赤い丸で表示します
2つめの画面は、コースと現在位置を表示する画面です。取得したGPSの情報を元に現在位置を赤い丸で表示します。
3つめの画面は、カメラの映像を表示する画面です。こちらは、ゲームの画面をイメージし、カメラ映像をベースにすべての情報を表示しています。
また、GPSの情報を元に、コースタイムを計測し、取得可能な速度も参考に表示しています。
3つの画面は、タッチパッドのスワイプで切り替え、二本指のタップで最初の画面戻る事が可能になっています。
・Google Glassの操作方法でいろいろ実験
車の操作中は、両手が塞がるため、Google Glassを操作する事ができません。
その為、タッチパットの操作以外に以下の2種類の操作を追加しました。
・音声でGoogle Glassを操作する
XE18.1よりアプリ実行中でも音声を認識するContextual voice commands機能が追加されました。
この機能を利用し、音声での操作を行います。
この機能を実装すると、画面の下に”ok glass”が表示されます。
この状態で、”ok glass”と話すと対応している音声一覧が表示されます。
音声一覧に表示されている単語を話す事で、音声コマンドが実行されます。
では、実際のソースを見ていきます。
Activity.onCreate()でFeatureにWindowUtils.FEATURE_VOICE_COMMANDSを設定します。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().requestFeature(WindowUtils.FEATURE_VOICE_COMMANDS); }
Activity.onCreatePanelMenu()でMenuを生成します。
今回は、menuリソースの情報を用いてメニューを生成しています。
android:titile部分の文字が音声コマンドとして使用されます。
<item
android:id="@+id/menu_stop"
android:title="@string/menu_stop"/>
@Override public boolean onCreatePanelMenu(int featureId, Menu menu) { if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS) { getMenuInflater().inflate(R.menu.main, menu); return true; } // Pass through to super to setup touch menu. return super.onCreatePanelMenu(featureId, menu); }
Activity.onPrepareOptionsMenu()で音声認識させる項目を条件に応じて有効・無効にする事も可能です。
@Override public boolean onPrepareOptionsMenu(Menu menu) { //条件に応じて、項目の有効、無効、表示、非表示を変更する setMenuState(menu.findItem(R.id.start), !mGameInfo.isTimeCount); setMenuState(menu.findItem(R.id.stop), mGameInfo.isTimeCount); return super.onPrepareOptionsMenu(menu); } private static void setMenuState(MenuItem menuItem, boolean enabled) { menuItem.setVisible(enabled); menuItem.setEnabled(enabled); }
Activity.onMenuItemSelected()に音声認識時の処理を記述します。
@Override public boolean onMenuItemSelected(int featureId, MenuItem item) { if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS) { switch (item.getItemId()) { case R.id.menu_start: break; case R.id.menu_stop: break; case R.id.menu_home: break; default: return true; } return true; } return super.onMenuItemSelected(featureId, item); }
・顔のジェスチャーで操作する
Google Glassの加速度センサーを利用して、画面の切り替えを行います。
今回のアプリでは右に振る事で画面の切り替え、下に頷く事で初期画面を表示します。
顔が振られたかどうかの判断は、かなりシンプルです。
指定された軸に一定以上の力が発生したかどうかで判断しています。
右方向に振られたかどうかはX軸、下に頷いたかどうかはY軸の情報をチェックします。
センサーの初期化および開始と終了の処理です。(Androidで加速度センサーを利用する方法と同じです)
加速度の情報をローパスフィルタを利用し、重力を除きます。
加速度の強さが一定以上の場合は、ジェスチャーが実行されたと判断し処理を実行します。
顔が振られたかどうかの判断は、かなりシンプルです。
指定された軸に一定以上の力が発生したかどうかで判断しています。
右方向に振られたかどうかはX軸、下に頷いたかどうかはY軸の情報をチェックします。
Y軸の頷いたかどうかのチェックは、タッチパットをタッチした場合にも同様の加速度が発生するため判断が難しく実際に使用するにはもう少し認識方法を考える必要があると思います。
センサーの初期化および開始と終了の処理です。(Androidで加速度センサーを利用する方法と同じです)
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.main); mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); } @Override protected void onResume(){ super.onResume(); if(mSensorManager != null){ List<sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER); if(sensors.size()>0){ mSensorManager.registerListener(this, sensors.get(0), SensorManager.SENSOR_DELAY_UI); } } } @Override protected void onPause(){ super.onPause(); if(mSensorManager != null){ mSensorManager.unregisterListener(this); } }
加速度の情報をローパスフィルタを利用し、重力を除きます。
加速度の強さが一定以上の場合は、ジェスチャーが実行されたと判断し処理を実行します。
今回は実験のため、緩めの数値にしています。値が高い場合、かなり強く振らないと認識しなくなるためです。ただ、弱すぎる場合は、思わぬ顔振りで画面が切り替わったり、Glassの取り外しで画面が変わってしまう事もありました。上手く使用するにはON/OFFの設定が必要かもしれません。
@Override public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { currentOrientationValues[0] = event.values[0] * 0.1f + currentOrientationValues[0] * (1.0f - 0.1f); currentOrientationValues[1] = event.values[1] * 0.1f + currentOrientationValues[1] * (1.0f - 0.1f); currentOrientationValues[2] = event.values[2] * 0.1f + currentOrientationValues[2] * (1.0f - 0.1f); float acceleration_x = event.values[0] - currentOrientationValues[0]; float acceleration_y = event.values[1] - currentOrientationValues[1]; float acceleration_z = event.values[2] - currentOrientationValues[2]; //x軸に4以上の加速度が発生した場合は、右振りと判断する if(acceleration_x > 4){ return; } //y軸に5.5以上の加速度が発生した場合は、頷きと判断する //タッチパッド操作の誤認識を避けるために右振りよりも少し数値を強めにしています。 if(acceleration_y > 5.5){ return; } } }
■実際に試してみました!
っということで、実際にサーキット場で車(ラジコン)を運転してみました!
ラジコンには、データを配信するためのスマートフォンを搭載します。
そのままだとカメラが写らないので、鏡を使って無理やり前を映しています。
また、落ちないようにテープでぐるぐる巻きにしています。
※鏡の位置がポイントです。ここに設置しないとボディーが閉まりません。
また、落ちないようにテープでぐるぐる巻きにしています。
※鏡の位置がポイントです。ここに設置しないとボディーが閉まりません。
残念ながら天気が悪く雨の中を走らせました。
ゆっくり走らせましたが最後は、水溜りにはまり帰らぬ車となりました。。
※スマホは防水のため無事でした。
・実際に走らせてみて
ラジコン用のコースでは、GPSの誤差による影響が大きく、コースから外れた場所を表示する事が多かったです。ただ、ある程度誤差を考慮に入れていたタイム測定は動いてくれました。正確な測定は難しいかもしれませんが、遊び程度には使えそうな気がします。
いろいろ実装した操作は、顔ジェスチャーが使いやすかったです。音声は、私の発音がダメでなかなか認識してくれませんでした。次回は、音声+ジェスチャーの組み合わせも試してみたいと思います。
期待していたカメラの映像は、個人的にはいい感じで、予想以上に車っぽい映像になっていました。
今回は、一台で試しましたが、複数台にこのシステム載せて走らせたら、リアルタイムでの順位や、前方の車までの距離の表示などまるでゲームのような表現も可能になるのではないかな?と考えています。
また、すべての車の映像を一括で表示すれば、観客にも今までとは違った楽しみ方を提案できるのではないかなと思います。
いろいろ実装した操作は、顔ジェスチャーが使いやすかったです。音声は、私の発音がダメでなかなか認識してくれませんでした。次回は、音声+ジェスチャーの組み合わせも試してみたいと思います。
期待していたカメラの映像は、個人的にはいい感じで、予想以上に車っぽい映像になっていました。
今回は、一台で試しましたが、複数台にこのシステム載せて走らせたら、リアルタイムでの順位や、前方の車までの距離の表示などまるでゲームのような表現も可能になるのではないかな?と考えています。
また、すべての車の映像を一括で表示すれば、観客にも今までとは違った楽しみ方を提案できるのではないかなと思います。
■最後に・・
今回の実験は、安全を考慮してラジコンという車を運転しました。
しかし、実際の車で運転する場合は、道路交通法の問題や、そもそも運転自体に危険が伴う可能性もあります。
公道での使用は、ご遠慮いただきますよう宜しくお願い致します。
しかし、実際の車で運転する場合は、道路交通法の問題や、そもそも運転自体に危険が伴う可能性もあります。
公道での使用は、ご遠慮いただきますよう宜しくお願い致します。
もし公道で使用したら・・
こんな怪しい人に追いかけられたり・・
こんな恐ろしい事故を起こすかもしれません。