ユニファ開発者ブログ

ユニファ株式会社システム開発部メンバーによるブログです。

ビーコン信号による位置推定(入門)

研究開発部の浅野です。ユニファ2019年アドベントカレンダー10日目の記事は、BLEビーコンを使った位置推定について書きたいと思います。無線信号を活用して位置情報を取得するための方法には様々なアプローチがあります。今回はその中で最もシンプルでわかりやすいビーコン信号強度を利用する方法を紹介します。


構成

ビーコンを活用したサービスは、インドアナビゲーションや近接マーケティングなどのようにビーコン信号を送信(アドバタイズ)する機器が固定されていて移動する人が持つスマホなどでそれを受信する形で応用されていることが多いと思います。しかし、保育園で園児の位置を把握することを考えると、ネットワーク接続が必要になる受信部を壁や遊具に固定して小型の送信装置を園児の名前バッジや衣服などに装着する方が現実的です。

f:id:unifa_tech:20191208112151j:plain:w300:right それを考慮して今回は右のような構成で実験を行います。MacbookからiBeacon信号を送信し、Bluetoothが内蔵されているRaspberry Pi Zero WHを受信装置として3箇所(座標は既知)で信号強度を測定。3つの受信信号強度からそれぞれの距離(d1, d2, d3)を推定してそこからMacBookの位置を割り出します。なお、MacBookからのビーコン信号の送信にはNode.jsのblenoライブラリを使用しました。そして、Raspberry Piでの受信には姉妹ライブラリであるnobleとnobleをバックエンドとして動くnode-beacon-scannerを組み合わせて使いました。


信号強度から距離を推定する

iBeacon受信信号にはTxPowerとRSSI(Received Signal Strength Indication)というフィールドが含まれます。TxPowerはビーコンが発する信号の強さ(実際にはビーコンから1m離れた距離での受信信号強度、単位はdBm)であり、送信側で設定する値です。今回はMacBookから1m離れた地点での実測値(-54.5)を設定しています。一方RSSIは受信した信号の強度(dBm)です。この2つから送信地点と受信地点の距離dを次のように求めることができます(参考)。


d = 10^{\,(TxPower-RSSI)\,/\,(10*n)}

ここで、nはビーコン信号が遮蔽などなく理想的に伝達する空間の場合は2で、通常は環境に応じて最適化する必要があります。今回の実験ではn=2 としています。また、iBeaconはdefaultでは100msごとに信号をアドバタイズしますが、RSSIの値は比較的変動するため各地点とも数秒間の平均をとりました。その結果MacBookと各受信地点との距離(m)は次のように求まりました。


d1 = 1.084, \, d2 = 1.135, \, d3 = 3.055


距離から位置を推定する

各受信地点までの距離が正確に求まる場合は、各地点を中心として半径がそれぞれの距離である円を描くと3つの円が1点で交わります。その点が送信位置です。しかし実際には必ず誤差がのるためそのような幾何的な手法で求めることはできません。そこで、送信位置をxとして、各受信地点との距離の平均二乗誤差を最小にする最適化問題として解いていきます。

import numpy as np

def distance(pos1, pos2): #二点間の距離
    return np.sqrt(sum( (np.array(pos1)-np.array(pos2))**2 ))

def mse(x, locations, distances): #距離の平均二乗誤差
    sum_sqerr = 0.0
    for loc, dist in zip(locations, distances):
        dist_calculated = distance(x, loc)
        sum_sqerr += (dist_calculated - dist)**2
    return sum_sqerr / len(locations)

def midpoint(*args): #中点
    return np.mean(np.array(*args), axis=0)

上のmseが最小にしたい関数です。このように定式化することで受信箇所の数が3より大きい場合にも同様に扱うことができます。最適化問題は例えば下記のようにscipyに含まれるminimize関数を使って解くことができます。

from scipy.optimize import minimize

rec1 = [0, 0]      #受信1の座標
rec2 = [0.1, 1.9]  #受信2の座標
rec3 = [3.65, 2.1] #受信3の座標
d1 = 1.084
d2 = 1.135
d3 = 3.055

locations = [rec1, rec2, rec3]
distances = [d1, d2, d3]
initial_loc = midpoint(locations)

result = minimize(mse, initial_loc, (locations, distances))

f:id:unifa_tech:20191208112229j:plain:w300:right 上のコードで3つの受信地点の座標とそこまでの距離を入力として、距離の平均二乗誤差が最小になる送信地点を求めています。このとき最適化における初期値として3つの受信位置の中点を使っています。この結果求められた送信位置は(0.74, 0.91)でした。正解は(1.2, 1.0)ですのでぴったりではないですがおおよその場所は捉えられています。


まとめ

ビーコンの信号強度を使って送信位置を推定する方法を実装して悪くない結果を得ることができました。今回の実験では送信と受信の間には何も置かず、まわりにもBluetooth機器がそれほどなかったので比較的整った環境でしたが、それでも50cm弱の位置推定誤差になりました。実際の環境では壁、天井、人や物による反射や遮蔽、あるいは他のBluetooth信号との混線などもっとたくさんの誤差要因があります。そういった状況では上述のnの最適化だけでは不十分なことが多く、精度を保つために様々な手法が開発されています。実際のサービスに組み込んでいくためには求められる精度の理解とそれを実現するための必要十分な技術を見極めていくことが重要です。