做者:張小美html
室內定位技術的商業化必將帶來一波創新高潮,尤爲是在 O2O 領域,各類基於此技術的應用將出如今咱們的面前。咱們能夠想象一些比較常見的應用場景,好比在大型商場裏面藉助室內導航快速找到目標商鋪,商店根據用戶的具體位置向用戶推送更多關於商品的介紹等等,這些應用會極好的服務於 O2O,提升用戶體驗。android
目前室內定位技術有不少,如A-GPS、藍牙、超聲,紅外、信標、射頻、Wi-Fi、計算機視覺等,這些技術綜合比較,其中以基於 Wi-Fi 的室內定位技術最爲突出,不管從硬件投入、軟件投入、實施難度、可控性,仍是定位效果方面考察,都是有優點的。算法
本文描述了做者在美團總部從零開始構建基於 Wi-Fi 的室內定位系統的過程,具備普遍的借鑑意義。數據庫
美團總部於 2014 年 1 月搬入瞭望京科技園 3 期,新的辦公室地上共 4 層,建築面積一萬多平米,共部署有 86 臺無線 AP,覆蓋很充分,沒有死角,這爲良好的定位效果打下了基礎。apache
無線 AP 使用的是,ArubA AP-135,這是一款優秀的商用無線路由器,2.4-GHz/5-GHz 雙頻。api
第一步,創建 AP 的基礎數據庫是關鍵,至少須要以下信息:服務器
關於 AP 的物理位置,這裏由於範圍過小,加之沒法找到足夠精度的參考點,因此 AP 的物理位置沒法使用 GPS 座標,只能使用自定義座標系。app
這裏有 2 種選擇:函數
這裏選用了後一種方法,由於後一種方法容易測繪,大部分工做在電腦上操做便可;前一種方法須要更多的實地測繪工做。post
關於 AP 的 MAC 地址,從 IT 那裏要到了一個列表,如圖所示:
可是很不幸,這裏的 MAC 地址是路由器的 WAN 口的 MAC 地址,而咱們須要的是兩個無線模塊的 MAC 地址。
這裏只能本身測繪了,我寫了一小段 android 程序,能夠排序出最近的 AP 的 MAC 地址,而後挨個跑到各個 AP 下,運行程序,記下兩個 MAC 地址;同時記錄下 AP 的真實物理位置。
WifiManager wm = (WifiManager) getSystemService (Context.WIFI_SERVICE); wm.startScan (); //開始掃描 AP //等待一段時間,時間可長可短 List<ScanResult> results = wm.getScanResults ();
//拿到掃描的結果
Collections.sort (results,this); //this 是個 Comparator,按照 level 排序 //去掉非 sankuai 的 SSID //在 UI 線程中,顯示到界面上 int max=Math.min (30,results.size ()); for(int i=0;i<max;i++) { ScanResult one = results.get (i); text1.append ("\n"+one.BSSID+"\t\t"+one.level); }
圖中信號最強的就是當前 AP 的 MAC 地址,而後地址與它相近的是這個 AP 另外一個頻段的 MAC 地址,兩個 MAC 地址都是 0 結尾,尾數相差1,容易辨認。
MAC 地址後面的數字是信號強度,單位是 dBm,是個負數。
而後在底圖中標註好 AP 的準確的物理位置,圖中紅色圓點便是 AP 位置,其圓心的像素座標看成 AP 的座標。
測繪的數據應該存入數據庫,這裏設計了一個 POJO,服務器端程序可使用:
public class MtApLoc { private int id; //數字 ID 人工定,有必定含義
private String id1; //字符串 ID 從 IT 給表中來
private String mac1; //WAN MAC 地址,有線口的
private String sn; //AP 的 SN 從 IT 給表中來
private String sku; //資產編號 N 從 IT 給表中來
private String mac2; //無線 MAC 1 ,測繪得來
private String mac3; //無線 MAC 2 ,測繪得來
private int pn; //圖號 對應樓層
private float x; //物理座標 x 自定義座標系中
private float y; //物理座標 y 自定義座標系中 }
而後將測繪的數據錄入數據庫,最後獲得的數據如:
其中的x,y是此 AP 在對應樓層的測繪圖的圖片中的座標。
MAC2 和 MAC3 是 AP 的兩個 MAC 地址(這裏沒有區分 2.4G 和 5G),和上面的測繪客戶端的截圖比較,能看出當時我是站在 AP7 下的。
把全部 86 個 AP 的物理位置和 MAC 地址測繪收集全後,測繪過程完成。
這裏寫了一個 Demo 用的 android 客戶端,來測試定位結果,先看客戶端運行截圖:
點擊定位按鈕,系統會掃描 AP,而後把結果請求到服務器。
HttpPost post = new HttpPost (BaseUrl + "/gar/locate/ap-locate.html"); List<NameValuePair> parameters = new ArrayList<NameValuePair>(); for (ScanResult result : results) { parameters.add (new BasicNameValuePair ("mac", result.BSSID.toUpperCase ())); parameters.add (new BasicNameValuePair ("rssi", String.valueOf (result.level))); } post.setEntity (new UrlEncodedFormEntity (parameters, "UTF-8")); String res; synchronized (hc) { HttpResponse response = hc.execute (post); res = EntityUtils.toString (response.getEntity (), "UTF-8") .trim (); } Log.w (TAG, res);
服務器返回其所在位置,是一個 JSON 字符串
{"accuracy":0.0,"message":"ok Least Squares","pn":1,"status":0,"x":237.97249473061038,"y":1241.8270604002646}
而後客戶端顯示 pn 對應的底圖,而後在底圖的x,y位置上顯示定位到的標誌,即圖中跳動的紅心。
客戶端大部分代碼都是 UI 相關代碼,這裏不貼出了。
常見的室內定位的算法主要分爲兩類:基於測距技術的定位算法和距離無關的算法。基於測距技術的算法通常是經過節點之間的距離或者角度來計算出未知節點的位置,實際運用中常見的有:基於接收信號強度指示算法(RSSI)、到達角度算法(AOA)、到達時間算法(TOA)等。距離無關的算法有:質心法、APIT 算法、凸規劃算法等。這些算法都是利用節點之間的鄰近關係實現定位的。通常來講,基於測距技術的算法比無需測距的精度要高,這裏適合採用。
首先肯定一個信號強度和距離之間的關係,這須要瞭解電波傳播模型。在自由空間環境中,不考慮阻擋和多徑傳播,設發射端與接收端的距離爲d,則接收端的接收功率Pr可表示爲:
其中Pt爲發射功率;Gt和Gr分別爲發射和接收天線增益;λ爲電波波長;Pt和Pr的單位是瓦特;Gt和Gr無量綱。由上式能夠看出,在自由空間中,接收功率與距離d2成反比。
在實際環境中,因爲存在多徑、障礙物、繞射等隨機因素,無線電傳播損耗與上式相比仍是有較大變化。此時,常採用對數-常態分佈模型更爲合理:
其中Pr單位爲 dBm ,d0通常取1。在通常室內定位中,考慮到環境、成本、定位精度要求等因素,所使用的 RSSI 測距信號衰減模型進一步簡化爲:
d 爲定位節點與參考點之間的距離,單位m;A爲定位節點與參考點之間的距離d爲 1m 時測得的 RSSI 值;n爲信號衰減因子,範圍通常爲2~4。
在美團的環境中,咱們取A爲-50,n爲 2.1。
這樣根據信號強度,就能估算設備和 AP 之間的距離。
定位方法通常是根據幾何模型創建方程,而後求解方程獲得節點座標。
只有一個 AP 的狀況:
這裏目標點座標只能取 AP 的座標,精度取半徑
兩個 AP 的狀況:
這裏取 AB 的中間位置,精度取 AB 的長度。
三個 AP 的狀況:
這裏取三個圓的一個共同交點。
不過實際沒有這麼簡單,由於距離都有偏差,兩個 AP 時,多是這種狀況:
三個 AP 多是這種狀況
甚至這種:
這只是三個 AP,有更多 AP 時怎麼辦?
這裏考慮通常的狀況:
考慮通常的狀況,設有n個 AP,AP1,AP2,...,APn,座標是(xi,yi)。目標點到這n個 AP 的距離是di。
設目標點的座標是(X,Y),則可列一個方程組,有n個等式:
你們都減第一個等式,就消去了二次項,獲得另外一個方程組,有n-1 個等式:
常數項換個名字,獲得:
等式除以X的係數ai,變量換個名字,獲得:
等式有n-1 個,如今問題變成了:已知一組點(ui,vi)知足p+uq=v,求最合適的係數p,q,這是典型的最小二乘法
Java 裏能夠用 Apache Commons Math3 這個 library 來解決最小二乘法,文檔見SimpleRegression
這裏還有一個問題,AP 的座標(xi,yi)是像素座標,那di相應的須要是像素距離,須要作一個比例尺變換
比例很容易算,相關代碼:
public double getPicLen (double rssi) { double f=(-rssi-50)/22.0; return 41.785*Math.pow (10,f); }
經過上面的描述,服務器端代碼就很容易寫了,這裏給出主要代碼:
private String[] macs; //輸入 mac 地址
private float[] rssis; //輸入信號強度
private int pn; //輸出,樓層
private double x,y,accuracy; //輸出,定位到的座標和精度 List<MtApLoc> aps=new ArrayList<>(map.keySet ()); MtApLoc first=aps.get (0); //信號最強的那個 ap for (MtApLoc one : aps) {
//以信號最強的 ap 的樓層做爲最終樓層,由於可能搜到其它樓層的信號
if(one.getPn ()!=first.getPn ()) { //幹掉其它樓層的 ap
map.remove (one); } } aps.clear (); aps.addAll (map.keySet ()); size=aps.size (); this.pn=first.getPn (); if(size==1) { setStatus (0); setMessage ("ok one point"); this.x=first.getX (); this.y=first.getY (); this.accuracy=getPicLen (map.get (first) .floatValue ()); return JSON; } else if(size==2) { setStatus (3); setMessage ("to impl"); } else { float minRssi=-65; //信號強大要達到 -65 才參與運算
int min=4; //至少須要 4 個 ap,這個條件比上個條件優先 size=0; for(Iterator<MtApLoc> it = aps.iterator ();it.hasNext ();) { MtApLoc ap = it.next (); if(map.get (ap) .floatValue ()<minRssi && size>=min) { it.remove (); } else { size++; } } //map 的 key 以前是信號強度,如今變爲像素距離
aps.forEach (ap -> map.put (ap,getPicLen (map.get (ap) .floatValue ()))); double[][] ps=new double[size-1][4]; //看 size-1
double r1=map.get (first) .doubleValue (); r1=r1*r1; double r2=first.getX ()*first.getX () +first.getY ()*first.getY (); int n=0; for (MtApLoc ap : aps) { //生成數據 if(ap!=first) { ps[n][0]=ap.getX ()*ap.getX () +ap.getY ()*ap.getY ()-r2; ps[n][1]=2*(first.getX ()-ap.getX ()); ps[n][2]=2*(first.getY ()-ap.getY ()); double r=map.get (ap) .doubleValue (); ps[n][3]=r*r-r1; n++; } } assert n==(size-1); for(int i=0;i<n;i++) { //生成數據 double k=ps[i][1]; ps[i][1]=(ps[i][3]-ps[i][0])/k; ps[i][0]=ps[i][2]/k; } SimpleRegression reg=new SimpleRegression (true); //最小二乘法
reg.addData (ps); setStatus (0); setMessage ("ok Least Squares"); this.x=reg.getIntercept (); this.y=reg.getSlope (); }
系統完成了,這裏須要檢驗一下定位效果。爲了簡化過程,我是這樣操做的:
我選擇了一個固定點,就是個人座位(上面客戶端截圖中跳動的紅心所在的位置),而後用手機客戶端作 100 次定位操做,同時服務器作 log 記錄下 100 次的定位結果,而後作分析。
我座位這個點被 3 個 AP 包圍着,定位效果應該不錯,因此結論可能會偏樂觀,實際應該選擇不一樣的點。
不過選擇不一樣的點要記錄真實的點的座標,稍顯麻煩。後面作進一步改進和測試時,能夠選擇不一樣的點作測試,這算做一個 todo。
而後就獲得 100 個定位結果,而後能夠計算和真實點的誤差,結果如:
其中x、y是定位到的座標,單位是像素座標,diff 是計算出的誤差,單位是米。
而後按距離排序,獲得以下表,是所有數據:
從這個表能夠大體分析定位效果:
去掉 3 個失敗的點,剩下的 97 個點,能夠用 excel 畫一個分佈圖:
分析上面數據,以及實際測試過程,能發現,這個系統應該有一個系統偏差。就是測試中,定位結果老是分佈在距我大概 2 米處的某一點周圍,應該是系統編碼某個地方缺陷形成的。
這是待改進的 todo,預計找到問題解決後,重複上面的測試過程,定位效果能達到 95% 的點偏差小於 2 米的水平。
另外上面我選的點應該屬於定位效果較好的點,通常狀況的點的定位精度,得進一步詳細測試得出。這裏我拍腦殼估計,系統應該在 90% 的點偏差小於 5 米的水平。
整個系統正在應用到移動組開發的一個找會議室的手機應用「會議室」中,爲其增長定位自身的功能。
爲了完善系統,如今能想到的改進有:
這些改進,會逐步完善,敬請期待本系列的(下)篇。