
笑えるぐらい分かんない
前の記事で加速度と地磁気の値は取得できた。で、方位角を求めるわけだけど、全然わからない。数学難しい。こう書けば取得できるよ!って記事はたくさんあるんだけど、なんでそうすると求められるのかが分からない…。分からないことを分からないとだけ書いてこう。
いろーんな記事に載ってる方位角求めるやつ

private static final int MATRIX_SIZE = 16;
float[] inR = new float[MATRIX_SIZE];
float[] outR = new float[MATRIX_SIZE];
float[] I = new float[MATRIX_SIZE];
float[] attitude = new float[3];
SensorManager.getRotationMatrix(inR, I, fAccell, fMagnetic);
SensorManager.remapCoordinateSystem(inR, SensorManager.AXIS_X, SensorManager.AXIS_Y, outR);
SensorManager.getOrientation(outR, attitude);
sb.append("\n\n方位角 _ ");
sb.append(Math.toDegrees(attitude[0]));
sb.append("\n傾斜角 _ ");
sb.append(Math.toDegrees(attitude[1]));
sb.append("\n回転角 _ ");
sb.append(Math.toDegrees(attitude[2]));
getRotationMatrix…回転行列ってなんですか。remapCoordinateSystem…世界座標系と端末座標ってなんですか。getOrientation…なんでこれで角度が取れるんですか。海は死にますか。山は死にますか。先人たちのサンプルコードの結果みたいにはなるのよ。でも中身何やってるのか全然わかんないの。
参考
https://seesaawiki.jp/w/moonlight_aska/d/%BC%A7%B5%A4/%B2%C3%C2%AE%C5%D9%A5%BB%A5%F3%A5%B5%A1%BC%A4%C7%CA%FD%B0%CC%B3%D1/%B7%B9%A4%AD%A4%F2%B8%A1%BD%D0%A4%B9%A4%EB
http://www.magicvox.net/archive/2015/02072148/
http://yoko-gb.blogspot.com/2012/10/android.html
https://wp.developapp.net/?p=3394
加速度と地磁気センサで取れる値ってなんだ
どっちにもX,Y,Zの軸があって、それぞれの値をとってる。
加速度センサー
単位はm/s^2。水平に端末を置いた場合、X=0,Y=0,Z=9.8になるはず。垂直に立てた場合、X=0,Y=9.8,Z=0になるはず。これを斜めにしてくと、0~9.8の値をとってくみたい(場所や状況によって超えたり、マイナスになったりはする)。そんで、そん時の値によって、どの方向にどれだけ傾いてるかが分かるみたい。
地磁気センサー
単位はμT。最大値、最小値は分からん。というよりかは各軸にどれだけの強度かが分かればいいようだ。強度が分かれば、その対角線みたいなやつ。よく見る図のやつね。の方向が北ってことらしい。Z軸で傾き補正ができるみたい。え、そしたら加速度いらなくね。ここらへんがよくわかんないんだよなー。んーと、地磁気は地面と平行なわけでも、端末と平行なわけでもなくて、地磁気のみの場合だと地磁気自体が傾いてるから、それを補正するために端末の傾きも必要になる。とか。地磁気センサが傾いてる場合、地磁気の傾きをセンサで補正したとしても、端末自体の傾きを考慮していない値になって、それを補正するために端末の傾きが必要になる。とか。二次元の画面に落とし込む際に、水平であった場合、の表示をしなければならないので、みたいな。ね、ほら、分かんない。
エクセル先生を利用して2軸で方位角を算出してみる

この為に百均でコンパス買ってきちゃった。コンパスを撮影してその向きでアプリ起動してスクショ撮ったのが上の画像。なるべく水平にしたつもり。
|
x |
y |
atan2 |
degrees |
90-degrees |
| 撮影時 |
-39.25 |
20.125 |
2.667806 |
152.854 |
-62.854 |
| 現時点 |
-55.75 |
22.3125 |
2.760893 |
158.1875 |
-68.1875 |
結果がこちらだけど、これは、合ってるのか…?微妙にずれてる気がしないわけでもない。90°ぐらいになると思ったんだけどな。
参考
https://www.sports-sensing.com/support/motionmesurement/motion_biomechanics/basis_accsensor.html
http://www.s3sensor.com/sens_a01/about/shikumi05.html
https://www.aichi-mi.com/home/%E9%9B%BB%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%91%E3%82%B9/%E6%96%B9%E4%BD%8D%E8%A8%88%E7%AE%97%E3%81%AE%E5%9F%BA%E7%A4%8E/
getRotationMatrixと回転行列と
ここら辺もう雰囲気だ。端末が傾いてると、地磁気センサの水平方向がちゃんと取れないらしい。なので、水平になるように補正してあげるそうで、その補正量、どれだけ傾きを戻してあげるのか、というのを得るために、getRotationMatrixを使って回転行列というものを得るのだと思う。
SensorManager.getRotationMatrix(inR, I, fAccell, fMagnetic);
これで言うと、第1引数に回転行列、第2引数にも回転行列ここに指定した変数に、計算結果が入るみたい。2つの違いは?と言われても分かりません。なんかIの方は地磁気の傾斜角を求める際に使うみたい。今使うのはRの方で、X軸が東、Y軸が北、Z軸が空を向いてる時の値に変換できるようなもんらしい。合ってる?第3引数に加速度センサの値、第4引数に地磁気センサの値。
なぜ加速度センサでの傾きが必要か
https://www.aichi-mi.com/home/%E9%9B%BB%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%91%E3%82%B9/%E6%96%B9%E4%BD%8D%E8%A8%88%E7%AE%97-%E5%8A%A0%E9%80%9F%E5%BA%A6%E3%82%BB%E3%83%B3%E3%82%B5%E3%81%AA%E3%81%97%E3%81%A7%E5%82%BE%E6%96%9C%E8%A3%9C%E6%AD%A3%E3%81%AF%E4%B8%8D%E5%8F%AF%E8%83%BD/
ここが分かりやすかった。
参考
https://www.aichi-mi.com/home/%E9%9B%BB%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%91%E3%82%B9/%E6%96%B9%E4%BD%8D%E8%A8%88%E7%AE%97%E3%81%AE%E5%82%BE%E6%96%9C%E8%A3%9C%E6%AD%A3/
https://developer.android.com/reference/android/hardware/SensorManager#getRotationMatrix(float[],%20float[],%20float[],%20float[])
remapCoordinateSystemやら世界座標系やらデバイス座標やら
分からんものを分からんまま分からん感じに理解しようとするからわけわかめだね。回転行列得られたんだからこれ何やってんの?と思ったんだけど、必要みたい。デバイスが縦持ち・横持ちの場合、アプリが画面内で回転したりする。そうすっと例えば横持ちにした場合、縦持ち時のX軸をY軸とみなさないと数値が変になるよね、ということらしい。たぶん。なので回転行列をデバイスの方向に回転させたげるみたい。
SensorManager.remapCoordinateSystem(inR, SensorManager.AXIS_X, SensorManager.AXIS_Y, outR);
このX,Yの値もなぜこれを設定するのかわかんないんだよなぁ…。一応AndroidDeveloperのサイトの例として、
- Using the camera (Y axis along the camera’s axis) for an augmented reality application where the rotation angles are needed:
remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR);
remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);
ってあるけど、イメージが掴めない。なんか図とか絵とかで解説してるとこないかなー。
参考
https://developer.android.com/reference/android/hardware/SensorManager#remapCoordinateSystem(float[],%20int,%20int,%20float[])
https://teratail.com/questions/13175
getOrientationと方位角と傾きと回転と
愛しさと切なさと心強さと。これなんでこんな引数少ないんだろう。
SensorManager.getOrientation(outR, attitude);
しかもこれ第2引数は結果保持用の変数だし。この回転行列は、端末がX軸が東、Y軸が北、X軸が空を向いてるようにするためのものなので、逆に言えばそのある意味デフォルトの位置からどれだけずれているかも求められる、ってことでいいのかな。どうなのかな。もうそんな感じでいいや。X,Y,Zそれぞれの回転角のことをロール、ピッチ、ヨーっていうのね。初めて知った。アジマスも初めて知った。おいしそう。attitudeに格納されてる値はラジアンなので見慣れた度数にするには変換が必要。
第2引数の説明を機械翻訳
- values [0]:方位角、-z軸を中心とした回転角。この値は、デバイスのy軸と磁北極の間の角度を表します。北向きの場合、この角度は0です。南向きの場合、この角度はπです。同様に、東を向いているとき、この角度はπ/ 2であり、西を向いているとき、この角度は-π/ 2です。値の範囲は-πからπです。
- values[1]:ピッチ、x軸周りの回転角度。この値は、デバイスの画面に平行な平面と地面に平行な平面の間の角度を表します。デバイスの下端がユーザの方を向き、画面が上を向いていると仮定して、デバイスの上端を地面に向かって傾けると、正のピッチ角が生じます。値の範囲は-πからπです。
- values[2]:ロール、y軸周りの回転角度。この値は、デバイスの画面に垂直な平面と地面に垂直な平面の間の角度を表します。デバイスの下端がユーザの方を向き、画面が上を向いていると想定して、デバイスの左端を地面に向かって傾けると、ロール角はプラスになります。値の範囲は-π/ 2からπ/ 2です。
参考
https://developer.android.com/reference/android/hardware/SensorManager#getOrientation(float[],%20float[])
https://www.aichi-mi.com/home/%E9%9B%BB%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%91%E3%82%B9/%E6%96%B9%E4%BD%8D%E8%A8%88%E7%AE%97%E3%81%AE%E5%82%BE%E6%96%9C%E8%A3%9C%E6%AD%A3/
https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%B8%E3%82%A2%E3%83%B3
加速度センサにはノイズが混じるし重力の影響が出る
センサで取得した値は時々すっ飛ぶことがあるようで、また静止状態だとしても微妙に振れが出てくるもの、ということで、それをできるだけ滑らかな変動にする必要があるみたい。で、その時に使うのがローパスフィルタというそうで、値をまろやかでコクのあるものにしてくれる。コクはない。ハイパスフィルタというものもあり、こっちは重力の影響を取り除いてくれるみたい。
ローパスフィルタのサンプル(Google)
final float alpha = 0.8;
// Isolate the force of gravity with the low-pass filter.
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];
前回値に0.8掛けて、現在値に0.2を掛けて、足し合わせる。指数移動平均というらしい。
エクセル先生で適当に検証
B列に=9.7+(10-9.7)*RAND()と、C列に=C2*$B$1+B3*(1-$B$1)。青が現在値で赤がフィルタ後の値。1000回やってみた。
B1=0.8(alpha=0.8)

B1=0.5(alpha=0.5)

へー、ほんとに滑らかになってるー。
ハイパスフィルタのサンプル(Google)
// Remove the gravity contribution with the high-pass filter.
linear_acceleration[0] = event.values[0] - gravity[0];
linear_acceleration[1] = event.values[1] - gravity[1];
linear_acceleration[2] = event.values[2] - gravity[2];
現在値からローパスフィルタ後の前回値を引く。これ結果が大体ゼロ近辺になるんだけどどゆこと?と思ったら、傾き検知とかに使用するんじゃなくて、振動とか運動を検知する時に有効みたい。たぶん。傾きの時にXYZ全部ゼロだったら困るもんね。ハイパスフィルタ後の値が変動する時は端末が動いてる時なんだよね、そういうことよね。たぶん。
参考
https://developer.android.com/guide/topics/sensors/sensors_motion#sensors-motion-accel
http://tomoima525.hatenablog.com/entry/2014/01/13/152559
http://yksris.hatenablog.com/entry/2012/10/09/170849
http://diylabo.sakura.ne.jp/tips/2014/06/accelero2.html
地磁気センサは較正が必要だし磁北と真北は違う
いい加減ゲロ吐きそうになってきたが、まだ考慮することがあるみたい。
較正・キャリブレーション
これが必要なのは分かる。各軸の最大値・最小値の中点をとって原点ズレを補正するって感じみたい。ただサンプルコードがどこにも見つからんのでどうにもこうにもできんのです、わからんのです。他の人はどうやってんだろ。実直に自分で実装してるんだろうか。すごいなぁ、いいなぁ。ハードウェアでやってくれればいいのになぁ。そういうわけにもいかんのだろうなぁ。
磁北と真北は違う
方位磁石が指し示す北と、北極点の位置は、微妙にずれているらしい。そのズレの角度を偏角といって、場所によって違うようだ。地磁気センサで取れる値はまぁ思ったとおり磁北であって、偏角で補正しないと正しい真北は得られない。センサによって偏角を取得することはできず、偏角を集めた表みたいなのを使って補正するみたい。で、それ用のメソッドがあるようで、
GeomagneticField(float gdLatitudeDeg, float gdLongitudeDeg, float altitudeMeters, long timeMillis)
の
getDeclination()
を使えばいいみたい。でもですよ、風水とかでは磁北を用いてるんですって。いや恵方巻きと風水は全然関係ないけども、あれ?あるの?どっち?ただ方位磁石とのズレは直したいよなぁ…見なかったことにしようかなぁ…。
参考
https://www.aichi-mi.com/home/%E9%9B%BB%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%91%E3%82%B9/%E7%A3%81%E6%B0%97%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC%E3%81%AE%E8%BC%83%E6%AD%A3%E4%B8%80%E8%88%AC/
https://www.aichi-mi.com/home/%E9%9B%BB%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%91%E3%82%B9/%E3%82%B3%E3%83%B3%E3%83%91%E3%82%B9%E3%81%AE%E8%BC%83%E6%AD%A3%E3%82%BD%E3%83%95%E3%83%88%E3%81%AE%E5%8E%9F%E7%90%86/
https://stackoverflow.com/questions/9462346/compass-give-me-crazy-data-is-calibration-needed-or-its-the-sensor-broken
http://www.gsi.go.jp/buturisokuchi/menu01_index.html
https://www.pasco.co.jp/recommend/word/word046/
https://www.aichi-mi.com/home/%E9%9B%BB%E5%AD%90%E3%82%B3%E3%83%B3%E3%83%91%E3%82%B9/%E5%9C%B0%E7%A3%81%E6%B0%97%E3%81%A8%E3%81%AF/
http://glassonion.hatenablog.com/entry/20091210/1260373724
https://developer.android.com/reference/android/hardware/GeomagneticField
こんなところかな…
まだなんかありそうな気がするけど見つけられん。もういいよ。パトラッシュ。次はいよいよ数値を元に画像を動かそう。できるかなぁ。