自宅ウォーキングでバーチャル観光をしてみよう【アプリ開発】
新型ウイルスの影響で余儀なく強いられている自宅生活、様々なストレスが溜まっているのではないでしょうか。
ストレスになる原因として、運動不足と外出自粛があげられると思います。
慢性的な倦怠感に悩まされ、観光・旅行・登山・散歩などのレジャーも楽しめない方が数多くいらっしゃることと思います。
そうしたことを踏まえて、今回は、運動不足+外出自粛のストレスを解消するために
【自宅ウォーキングでバーチャル観光】
をするためのアプリ開発をご紹介したいと思います。
はじめに
今回必要となるのは、
・androidスマートフォン(iPhoneは対象外とします)
・Google Chromecast(デバイス)
・Google Home(アプリ)
・TV(家庭用の一般的なTVです)
です。
ご注意いただきたい点として、
今回開発するアプリはgoogle play storeで公開されていません。
ご自身で開発して自宅で楽しむという趣旨でご紹介しております。
1. 自宅ウォーキングでバーチャル観光 とはどんなものか?
仕組みを説明しますと、
1. スマートフォンを握る、もしくはポケットに入れてその場でウォーキングをする
2. スマートフォンのアプリがウォーキングの動きと歩数を感知
3. 観光地のパノラマ映像が動きに連動してTVに映し出される
となります。
TVに観光地を映し出してウォーキングすると、あたかも観光地を散策している気分になれます。
もう少し詳しく説明をしていきたいと思います。
自宅でできるウォーキングについて
運動不足の解消の1つに、有酸素運動をすることがあげられます。実際に、自宅でウォーキングするだけで、有酸素運動ができるのでしょうか?
松本大学大学院 根本賢一教授による「その場足踏み」 で効果的な方法が紹介されています。
ポイントは、
太ももをゆっくりと高く上げる
手の指先はしっかり伸ばし、後ろに大きく引くように腕を振る
とのことです。
実際にこのやり方で5分ほど足踏みしていると、じんわりと汗がにじみ代謝が良くなっていると感じました。有酸素運動として効果があるように感じられます。
この動きをスマートフォンで感知することで、運動不足に効果的なウォーキングができそうです。
バーチャル観光について
バーチャル観光のツールとして、google street viewを使います。
360度パノラマ映像が使われているので、画面越しですがリアルな観光体験ができます。
例 : エッフェル塔付近の散策
ただし、マウスや指での操作(タッチやスクロール)が必要となるので、このままの状態だと今回の用途には適していません。
ウォーキングの動きと歩数に応じて、画面が連動して動く仕組みを作る必要があります。
2. 自宅ウォーキングでバーチャル観光をするためのアプリの開発方法
さて、ここからどのようにアプリを開発していくか説明をしていきたいと思います。
今回のアプリ開発でポイントとなるのは、
・ウォーキングの動作と歩数をスマートフォンで感知する
・横に曲がる・周囲を見渡す動作を感知する
・動きと歩数に応じてパノラマ映像(google street view)を動かす
となります。上記3点について細かく説明していきます。
ウォーキングの動作と歩数をスマートフォンで感知する
スマートフォンには、様々な動きを感知するセンサーが備わっています。上下に動かしたり、回転させたり、移動したことを感知できる仕組みを搭載しております。
今回は、ウォーキングの動作を感知するために上下・左右の動きを組み合わせて感知する方法を採用します。
具体的には、「歩数計」の仕組みを取り入りれたいと思います。
一般的なスマートフォンの歩数計アプリは、歩いた時の動作のみ感知するように作られています。単純な振動のみでは、歩行の動作として感知されず歩数もカウントされません。
これは、スマートフォンの加速度センサーが役割を担っています。加速度センサーとは、上下の動き、左右の動き、前後の動き(奥行か手前か)を感知する仕組みです。これらを組み合わせて、最も歩行に近い動作を感知してカウントしていきます。
詳しい実装方法については後述します。
横に曲がる・周囲を見渡す動作を感知する
前述の歩数計の仕組みでは、歩行の前進の動きのみ感知できます。横へ曲がったり、周囲を見渡す動作は感知できません。
そこで、そうした動作を感知するためにジャイロセンサーを使います。ジャイロセンサーとは、回転や向きを感知するセンサーです。
ジャイロセンサーを使うことで、スマートフォンを回転させて左右に向いたり、周囲を見渡す動作を感知することができます。
動きと歩数に応じてパノラマ映像(google street view)を動かす
加速度センサーとジャイロセンサーで感知した動きを、パノラマ映像と連動させて動かすことが必要です。
ウォーキングの動作と歩数によって前進、回転させることで左折・右折・360度見渡す、という一連の動作を伝えて映像を連動させます。
・・・
ここからは、実際にプログラムを見ながら実装する方法をご紹介していきます。
開発環境は下記となります。
・android studio 3.6.3
・android 9.0
・API レベル 28
3.歩数計の実装について
歩数計のプログラムに関しては、google/simple-pedometer を参照しながらご紹介していきます。
前述したとおり、加速度センサーを用いてウォーキングの動作をカウントできます。
それでは、どのように動作を検知するのか説明していきます。
A. 加速度センサーの3軸データ取得
加速度センサーは、x軸(横の動き)、y軸(上下の動き)、z軸(前後の動き)の3軸で動きを感知します。ウォーキングしたときの振動に近い動きを3軸の動きから判定していきます。
B. 3軸ベクトルの合計値を算出
ウォーキングの動きを感知するためには、x,y,z軸の動きを複合的にとらえる必要があります。
実際にスマートフォンをポケットに入れて歩いてみるとわかるのですが、上下、左右、前後の振動がスマートフォンに伝わります。どれか1つだけの動きに限定されることはありません。
従って、xyz軸の加速度(以下3軸のベクトルと呼びます)の内、どの軸が反応しても判定できるように、3軸のベクトルの合計値を算出します。
3軸のベクトルの合計値は、下記の式で算出できます。
C.ノイズの除去
加速度センサーは、わずかな振動に対しても反応します。
わずかな振動をすべて感知すると、ウォーキングの歩数を正確に測定できなくなります。
そのような不要な振動をノイズと呼び、そのノイズを除去する必要があります。
ノイズを除去するためには、下記の方法があげられます。
・デジタルローパスフィルター
・n回ごとの平均値(3軸のベクトル合計平均)が閾値を超えるか?
デジタルローパスフィルターとは、
Y(t) = a*Y(t-1) + (1-a)*x(t)
Y=フィルターした値、x=生データ、t=時間軸、a=定数
という式でノイズを除去する方法です。
簡単に表すと下記の通りになります。
フィルターデータ = 定数 * ひとつ前のフィルターデータ + (1-定数) * 生データ
よく使われる手法ではあるのですが、今回は次に紹介する方法を採用します。
n回ごとの平均値(3軸のベクトル合計平均)が閾値を超えるかどうか
n回ごとに3軸ベクトルの合計の平均値を求めて、平均値が一定の閾値を超えないものに関してはノイズと判定して除去する方法です。
n回とは、加速度センサーが反応する回数なのですが、大きければ大きいほど余計なノイズは拾わない一方、回数が溜まるまで時間がかかるため計測が遅くなるというデメリットもあります。
ここで算出した平均値が一定の値(閾値)を超えると、ウォーキングの1歩としてカウントされます。
今回は、運動不足に効果的なウォーキングの動作を感知したいため、より大きな動きを拾うように閾値を高めに設定します。
D.遅延の考慮
最後に、遅延について説明します。
ウォーキングの動作には、1歩カウントした後、次の1歩をカウントするまでに一定の時間の間隔があります。
その間隔をプログラムで感知するために、遅延を導入します。
遅延のロジックでは、一歩を感知した後、一定の時間が経過するまで振動に反応させないように実装していきます。
それでは実際にプログラムを見ていきます。
加速度センサーのインスタンスを取得します。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textView = new TextView(this);
textView.setTextSize(30);
setContentView(textView);
// Get an instance of the SensorManager
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
accel = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
simpleStepDetector = new SimpleStepDetector();
simpleStepDetector.registerListener(this);
}
onPauseは、アプリがバックグラウンドになるときの処理です。加速度センサーを登録解除して、電池の消耗を防ぎます。
@Override
public void onPause() {
super.onPause();
sensorManager.unregisterListener(this);
}
onResumeは、アプリがバックグラウンドから再開したときの処理です。再度加速度センサーを登録します。
@Override
public void onResume() {
super.onResume();
numSteps = 0;
textView.setText(TEXT_NUM_STEPS + numSteps);
sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_FASTEST);
}
onSensorChangedは、加速度センサーが反応した時の処理です。simpleStepDetector.updateAccelが呼び出されてウォーキングの動作と歩数を判定します。
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
simpleStepDetector.updateAccel(
event.timestamp, event.values[0], event.values[1], event.values[2]);
}
}
それでは、SimpleStepDetector.javaをみていきます。
public void updateAccel(long timeNs, float x, float y, float z) {
float[] currentAccel = new float[3];
currentAccel[0] = x;
currentAccel[1] = y;
currentAccel[2] = z;
// First step is to update our guess of where the global z vector is.
accelRingCounter++;
accelRingX[accelRingCounter % ACCEL_RING_SIZE] = currentAccel[0];
accelRingY[accelRingCounter % ACCEL_RING_SIZE] = currentAccel[1];
accelRingZ[accelRingCounter % ACCEL_RING_SIZE] = currentAccel[2];
// point A
float[] worldZ = new float[3];
worldZ[0] = SensorFilter.sum(accelRingX) / Math.min(accelRingCounter, ACCEL_RING_SIZE);
worldZ[1] = SensorFilter.sum(accelRingY) / Math.min(accelRingCounter, ACCEL_RING_SIZE);
worldZ[2] = SensorFilter.sum(accelRingZ) / Math.min(accelRingCounter, ACCEL_RING_SIZE);
// point B
float normalization_factor = SensorFilter.norm(worldZ);
worldZ[0] = worldZ[0] / normalization_factor;
worldZ[1] = worldZ[1] / normalization_factor;
worldZ[2] = worldZ[2] / normalization_factor;
float currentZ = SensorFilter.dot(worldZ, currentAccel) - normalization_factor;
velRingCounter++;
velRing[velRingCounter % VEL_RING_SIZE] = currentZ;
float velocityEstimate = SensorFilter.sum(velRing);
// point C
if (velocityEstimate > STEP_THRESHOLD && oldVelocityEstimate <= STEP_THRESHOLD
&& (timeNs - lastStepTimeNs > STEP_DELAY_NS)) {
listener.step(timeNs);
lastStepTimeNs = timeNs;
}
oldVelocityEstimate = velocityEstimate;
}
コメントに記載してあるPoint別に説明していきます。
Point A
この個所は、ノイズ除去のためにn回ごとの平均値を取得しています。ここはベクトル合計でなく、3軸ごとの加速度であることに注意してください。
Point B
ここで、3軸ベクトルの合計値を算出しています。
Point C
ここで、ベクトルの合計値が閾値を超えているかどうかチェックしています。また遅延も考慮されています。運動不足に効果的なウォーキングを感知したいため、より大きな動きを拾うようSTEP_THRESHOLDは高めに設定します。
このプログラムは、その他にも様々な手法で加速度データを加工しており、ウォーキングの動きと歩数を正確に感知する工夫がなされています。
最後に、Activity classに戻って、ウォーキングの動きを感知したときの歩数をカウントします。
@Override
public void step(long timeNs) {
// count walking step
numSteps++;
}
4.横に向く・周りを見渡す動作の実装について
横に向く・周りを見渡す動作は、回転を感知するジャイロセンサーを使います。
A. ジャイロセンサーの3軸を中心とした回転データ取得
ジャイロセンサーは、3軸を中心とした回転を感知します。
x軸中心の回転
y軸中心の回転
z軸中心の回転
今回は、スマートフォンを垂直に立てて回転しているとき(y軸中心の回転しているとき)に、横に向く・周囲を見渡す動作として判定するようにします。
B. 3軸ベクトルの合計値を算出
加速度センサーと同様、ジャイロセンサーも3軸の回転の合計値を算出します。3軸ベクトルの合計値は、下記の式で算出できます。
C.ノイズの除去
ここも加速度センサーと同様、ノイズを除去するために、n回ごとの平均値(3軸のベクトル合計平均)が閾値を超えるか判定をしていきたいと思います。
詳しくは、前述した加速度センサーのノイズの除去をご覧ください。
D.遅延の考慮
こちらも加速度センサーと同様に、遅延を考慮します。
ここから実際にプログラムを見ていきます。
ソースコードは、google/simple-pedometer に存在しないので自作します。考え方は加速度センサーと同じです。
SimplePedometerActivity.java にジャイロセンサーを加えます。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textView = new TextView(this);
textView.setTextSize(30);
setContentView(textView);
// Get an instance of the SensorManager
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
accel = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
simpleStepDetector = new SimpleStepDetector();
simpleStepDetector.registerListener(this);
// Add gyro sensor
gyro = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
simpleLotateDetector = new SimpleLotateDetector();
simpleLotateDetector .registerListener(this);
}
onResumeでジャイロセンサーの登録をします。
@Override
public void onResume() {
super.onResume();
numSteps = 0;
textView.setText(TEXT_NUM_STEPS + numSteps);
sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_FASTEST);
// register gyro
sensorManager.registerListener(this, gyro, SensorManager.SENSOR_DELAY_FASTEST);
}
ジャイロセンサーが反応した時の処理です。simpleLotateDetector.updateGyroが呼び出されて横を見る、周囲を見渡す動作を感知します。
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
simpleStepDetector.updateAccel(
event.timestamp, event.values[0], event.values[1], event.values[2]);
}
if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
simpleLotateDetector.updateGyro(
event.timestamp, event.values[0], event.values[1], event.values[2]);
}
}
SimpleLotateDetector.java を新しく作成します。ベースは、SimpleStepDetector.javaとほぼ同じですが、右向きか左向きかの感知を追加します。スマートフォンを縦に持った時(y軸中心に回転したとき)に、反時計回りに回す( y軸の回転 > 0)と左向きに、 時計周りに回すと右向きに感知します。
float absVelocityEstimate = Math.abs(velocityEstimate);
if (absVelocityEstimate > STEP_THRESHOLD && oldVelocityEstimate <= STEP_THRESHOLD
&& (timeNs - lastStepTimeNs > STEP_DELAY_NS)) {
// check if direction is right or left
type = (y > 0) ? TYPE_LEFT : TYPE_RIGHT ;
listener.lotate(timeNs, type);
lastStepTimeNs = timeNs;
}
周囲を見渡す動作は、同じ方向に回転させ続けることで実現できます。
最後に、Activity classに戻って、上記動作の感知をしたときの処理を記載します。
@Override
public void lotate(long timeNs, String type) {
// call lotating street view
}
5.動きと歩数に応じてパノラマ映像(google street view)を動かすための実装
ここから、スマートフォンが感知した動きとパノラマ映像(以下google street view)を連動させるプログラムを説明します。
A. Maps SDK for android API を有効にする
google street viewをandroidで動かすために、Maps SDK for android のAPIを有効にします。
https://console.cloud.google.com/
上記のコンソール画面から、左メニューの「APIとサービス > ライブラリ」を選択すると「Maps SDK for android」が表示されるのでクリックして有効にしてください。
有効にした後、「APIとサービス > ダッシュボード」に有効になったAPIが表示されるので選択してください。
その後「認証情報」を選択すると、「認証情報を作成」ができるので選択してください。
APIキーが取得できるので保存しておいてください。後ほど、google street viewを呼び出すときに使用します。
B.google street viewを呼び出す
次に、スマートフォンにgoogle street viewを呼び出します。
サンプルソースがありますので、こちらに沿ってご説明していきます。
こちらの手順に沿って、取得したAPI keyをプログラムに追加してください。
https://github.com/googlemaps/android-samples/blob/master/ApiDemos/java/README.md
2.Create a file in the ApiDemos/java directory called secure.properties (this file should NOT be under version control to protect your API key)
3.Add a single line to ApiDemos/java/secure.properties that looks like MAPS_API_KEY=YOUR_API_KEY, where YOUR_API_KEY is the API key you obtained in the first step
4.Build and run
その後、onCreate でgoogle street viewを呼び出します。
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.street_view_panorama_navigation_demo);
SupportStreetViewPanoramaFragment streetViewPanoramaFragment =
(SupportStreetViewPanoramaFragment)
getSupportFragmentManager().findFragmentById(R.id.streetviewpanorama);
streetViewPanoramaFragment.getStreetViewPanoramaAsync(
new OnStreetViewPanoramaReadyCallback() {
@Override
public void onStreetViewPanoramaReady(StreetViewPanorama panorama) {
mStreetViewPanorama = panorama;
// Only set the panorama to SYDNEY on startup (when no panoramas have been
// loaded which is when the savedInstanceState is null).
if (savedInstanceState == null) {
mStreetViewPanorama.setPosition(SYDNEY);
}
}
});
mCustomDurationBar = (SeekBar) findViewById(R.id.duration_bar);
}
C.矢印に沿って直進する
google street viewを見ると、矢印があるのがわかります。ここをクリックすると矢印の方向に直進します。
ここの挙動のプログラムは下記となります。
カメラが向いている方向の矢印を見つけ出し、その矢印の方向に進めるというものです。
public void onMovePosition(View view) {
StreetViewPanoramaLocation location = mStreetViewPanorama.getLocation();
StreetViewPanoramaCamera camera = mStreetViewPanorama.getPanoramaCamera();
if (location != null && location.links != null) {
StreetViewPanoramaLink link = findClosestLinkToBearing(location.links, camera.bearing);
mStreetViewPanorama.setPosition(link.panoId);
}
}
ウォーキングを感知したときに、歩数に応じて上記のプログラムを呼び出せば、google street viewと連動して前進できます。
このプログラムを加速度センサーのコードに追加します。n歩(MOVE_COUNT)ごとに画面を前進させています。
@Override
public void step(long timeNs) {
// count walking step
numSteps++;
if ( numSteps % MOVE_COUNT == 0 ){
// call moving forward street view function.
}
}
D.横に曲がる、周囲を見渡す
google street viewを見ると、道が分岐して矢印がいくつか表示されることがあります。直進以外の矢印に進みたい場合、いったん横に向く必要があります。
横に向くためには、どの方向に向くか、どの程度回転するかを感知して、その情報をgoogle street viewに反映させます。
左に向く場合は回転角度をマイナスに、右に向く場合はプラスに指定します。
public void onPanLeft(View view) {
if (!checkReady()) {
return;
}
mStreetViewPanorama.animateTo(
new StreetViewPanoramaCamera.Builder().zoom(
mStreetViewPanorama.getPanoramaCamera().zoom)
.tilt(mStreetViewPanorama.getPanoramaCamera().tilt)
.bearing(mStreetViewPanorama.getPanoramaCamera().bearing - PAN_BY_DEG)
.build(), getDuration());
}
public void onPanRight(View view) {
if (!checkReady()) {
return;
}
mStreetViewPanorama.animateTo(
new StreetViewPanoramaCamera.Builder().zoom(
mStreetViewPanorama.getPanoramaCamera().zoom)
.tilt(mStreetViewPanorama.getPanoramaCamera().tilt)
.bearing(mStreetViewPanorama.getPanoramaCamera().bearing + PAN_BY_DEG)
.build(), getDuration());
}
横に向いた後は、向いている方角の矢印に前進することができます。
上記プログラムを前述したジャイロセンサーと組み合わせれば、スマートフォンをy軸中心に回転させることで、横に曲がったり周囲を見渡す(360度回転する)ことが可能になります。
@Override
public void lotate(long timeNs, String type) {
numLotate++;
// lotate on street view
if ( type == LotateDetector.TYPE_RIGHT ){
// call onPanRight function
}
else if ( type == LotateDetector.TYPE_LEFT ) {
// call onPanLeft function
}
}
E.スマートフォンを横画面に固定にする
TVに映し出すためにスマートフォンの画面を横固定にします。縦画面のままTVにキャストするとTV画面の比率に合わずに画面が崩れてしまうためです。
AndroidManifest.xml に、android:screenOrientation="landscape" を指定します。
<activity
android:name=".MainActivity"
android:label="maps"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
これで一通りプログラムの実装が完了です。
6. Chromecastとgoogle homeでTVにキャストする
ここまでの作業で、スマートフォンのアプリでウォーキングとgoogle street viewを連動させることができました。
次に、TVの画面に反映させるために、Chromecast(デバイス)とgoogle home(アプリ)を使用します。
Chromecastとgoogle homeについてはこちらをご覧ください。
画面にキャストする方法については、こちらをご覧ください。
7. 実際にウォーキングでバーチャル観光を試してみる
これで、ひととおり準備が整いました。
それでは実際にやってみましょう。
1. google Homeで、TVにスマートフォンの画面をキャストしてください。
2. アプリを起動して、アプリの画面がTVにキャストされていることを確認してください。
3.スマートフォンを握るか、もしくはズボンのポケットに入れてTVに向かって「その場足踏み」法でウォーキングします。
ウォーキングするときは、次のことを心がけてください。
太ももをゆっくりと高く上げる
手の指先はしっかり伸ばし、後ろに大きく引くように腕を振る
4. 上記のウォーキングの動作をスマートフォンが感知すると、TVの画面も連動します。
5. 横に曲がりたいときは、いったんストップしてスマートフォンを回転させてください。左に曲がりたいときは反時計回りに、右に曲がりたいときは時計回りに回転させます。
6. 回転に合わせてTVの画面も連動します。同じ方向に回転し続けることで、周囲を見渡すことができます。進みたい方向に回転したら、ウォーキングして前に進んでください。
色々な観光地へ行ってみよう
こちらのサイトを参考に、人気のある観光地をピックアップしてみました。
Ala Moana Regional Park(ハワイ アラモアナビーチパーク)
海を臨みながら心地よくウォーキングできます。
Canali di Venezia(ベネチア)
水上を歩きながらベネチアの街並みを観光できます。
富士山
富士山にバーチャル登山することもできます。
セントラル・パーク
ミラノ
Galapagos(ガラパゴス諸島)
・・・
いかがでしたでしょうか?
自粛生活によるストレス解消に、少しでもお役に立てましたら幸いです。
お付き合いいただきまして、ありがとうございました。