加速度センサーの3軸をタイムチャートでグラフにして見ました。
low-pass filterの効果がわかり易くなりましたね、数値ではやはりわかりにくい。
2021.2.1
Accelerometer
チャートはMPAndroidChartを使うことにしました。いろいろありますが、Androidの仕様変更に対応が比較的早いので(今の所ですが)。
チャートはLinerChartを使いました。こちら MPAndroidChart ライブラリーでグラフを描画 でも試しています。
また、SensorEvent | Android Developersにあるlow-pass filterも試してみます。
low-pass filter
GoogleのSensorEventには次のような式が載せてあります。
加速度センサーの生データは敏感に反応するため、急激な変動をある程度除去して、ゆっくりした動きを取り出す、つまりlow-passのフィルタリングをした方が人間にはわかりやすい場合があります。またある程度というのが味噌で、この場合alphaという定数によって変化が変わって来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public void onSensorChanged(SensorEvent event) { // alpha is calculated as t / (t + dT) // with t, the low-pass filter's time-constant // and dT, the event delivery rate final float alpha = 0.8; gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]; gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]; gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]; linear_acceleration[0] = event.values[0] - gravity[0]; linear_acceleration[1] = event.values[1] - gravity[1]; linear_acceleration[2] = event.values[2] - gravity[2]; } |
サンプルコード
早速実装してみます。加速度センサーの元になるのは、加速度センサー Accelerometer を使ってみるです。ここでは生データを扱っているので、それに上のlow-passを入れた場合をボタンで切り分けるようにして見ます。
グラフは、MPAndroidChart ライブラリーでグラフを描画で使ったLineChartです。
最初にライブラリーのgradle設定からです。
settings.gradle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
pluginManagement { ... } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url "https://jitpack.io" } } } ... |
build.gradle (Module…)
1 2 3 4 5 6 |
... dependencies { ... implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' } |
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
//package your.package.name import android.annotation.SuppressLint; import android.os.Bundle; import android.app.Activity; import android.content.pm.ActivityInfo; import android.graphics.Color; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineDataSet; import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; import java.util.Locale; public class MainActivity extends Activity implements SensorEventListener, View.OnClickListener { private SensorManager sensorManager; private Sensor accel; private TextView textView; private LineChart mChart; private final String[] labels = new String[]{ "linear_accelerationX", "linear_accelerationY", "linear_accelerationZ"}; private final int[] colors = new int[]{ Color.BLUE, Color.GRAY, Color.MAGENTA}; private boolean lineardata = true; @SuppressLint("SourceLockedOrientationActivity") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 縦画面 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); // Get an instance of the SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); // Get an instance of the TextView textView = findViewById(R.id.text_view); mChart = findViewById(R.id.chart); // インスタンス生成 mChart.setData(new LineData()); // no description text mChart.getDescription().setEnabled(false); // Grid背景色 mChart.setDrawGridBackground(true); // 右側の目盛り mChart.getAxisRight().setEnabled(false); Button buttonStart = findViewById(R.id.button_start); buttonStart.setOnClickListener(this); Button buttonStop = findViewById(R.id.button_stop); buttonStop.setOnClickListener(this); Button buttonChange = findViewById(R.id.button_change); buttonChange.setOnClickListener(this); } @Override public void onClick(View view) { if(view.getId() == R.id.button_start){ sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_NORMAL); } else if(view.getId() == R.id.button_stop){ sensorManager.unregisterListener(this); } else if(view.getId() == R.id.button_change){ lineardata = !lineardata; } } @Override protected void onResume() { super.onResume(); // Listenerの登録 accel = sensorManager.getDefaultSensor( Sensor.TYPE_ACCELEROMETER); sensorManager.registerListener(this, accel, SensorManager.SENSOR_DELAY_NORMAL); } // 解除するコードも入れる! @Override protected void onPause() { super.onPause(); // Listenerを解除 sensorManager.unregisterListener(this); } @Override public void onSensorChanged(SensorEvent event) { float[] gravity = new float[3]; float[] linear_acceleration = new float[3]; final float alpha = 0.6f; if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER ) { // alpha is calculated as t / (t + dT) // with t, the low-pass filter's time-constant // and dT, the event delivery rate gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]; gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]; gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]; linear_acceleration[0] = event.values[0] - gravity[0]; linear_acceleration[1] = event.values[1] - gravity[1]; linear_acceleration[2] = event.values[2] - gravity[2]; String accelero; if(!lineardata){ accelero = String.format(Locale.US, "X: %.3f\nY: %.3f\nZ: %.3f", event.values[0],event.values[1],event.values[2]); } else { accelero = String.format(Locale.US, "X: %.3f\nY: %.3f\nZ: %.3f", gravity[0],gravity[1],gravity[2]); } textView.setText(accelero); LineData data = mChart.getLineData(); if(data != null){ for(int i = 0; i < 3; i++){ ILineDataSet set3 = data.getDataSetByIndex(i); if(set3 == null){ LineDataSet set = new LineDataSet(null, labels[i]); set.setLineWidth(2.0f); set.setColor(colors[i]); // liner line set.setDrawCircles(false); // no values on the chart set.setDrawValues(false); set3 = set; data.addDataSet(set3); } // data update if(!lineardata){ data.addEntry(new Entry(set3.getEntryCount(), event.values[i]), i); } else{ data.addEntry(new Entry(set3.getEntryCount(), linear_acceleration[i]), i); } data.notifyDataChanged(); } mChart.notifyDataSetChanged(); // 表示の更新のために変更を通知する mChart.setVisibleXRangeMaximum(50); // 表示の幅を決定する mChart.moveViewToX(data.getEntryCount()); // 最新のデータまで表示を移動させる } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } } |
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" android:orientation="vertical" android:background="#dfe" tools:context=".MainActivity"> <LinearLayout android:orientation="horizontal" android:gravity="center" android:layout_margin="10dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/text_view" android:textColor="#00f" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/button_start" android:text="@string/start" android:layout_margin="5dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/button_stop" android:text="@string/stop" android:layout_margin="5dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/button_change" android:text="@string/change" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <com.github.mikephil.charting.charts.LineChart android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/chart"/> </LinearLayout> |
strings.xml
1 2 3 4 5 6 |
<resources> <string name="app_name">YourAppName</string> <string name="start">Start</string> <string name="stop">Stop</string> <string name="change">Change</string> </resources> |
ボタンでlow-pass filterを切り替えてその効果を確認してください。また定数alphaによる違いはコードで変更するとわかります。