简介:这是一套实现基于WiFi信号的室内定位完整系统源码,面向计算机、物联网等专业毕业设计、期末大作业与课程设计场景。项目采用Go语言编写,按网关、定位、公共模块等分层组织,界面美观、操作简单、功能齐全,代码注释详细,新手也能快速理解并二次开发。资源包共38个文件,主体为26个Go源文件,实现核心业务逻辑;配合Dockerfile、docker-compose.yml、Makefile等配置,支持容器化部署与一键构建,另有JSON数据文件、环境变量文件及说明文档辅助使用,压缩后仅43KB,结构紧凑、便于部署。已有169人学习下载,项目经过严格调试,简单配置即可运行,拿来即可作为毕设或期末大作业的高分方案,也可用于学习WiFi室内定位技术的工程实现。
1. 基于wifi的室内定位项目源码到底在解决什么问题
你站在写字楼一层大厅,手机显示GPS信号弱,要找的公司却在17层最里头的角落——室内定位要做的,就是在没有卫星信号覆盖的环境里,靠身边已经存在的WiFi热点信号强度,把位置猜出来。基于wifi的室内定位项目源码,指的就是一套从扫描AP(无线接入点)、记录RSSI信号强度、建立指纹库、在线匹配,到最终输出坐标的完整代码实现,毕业设计版本通常还带一个可视化地图或管理页面。它解决的问题不是“哪个房间有人”,而是“手机现在具体在哪”。计算机、物联网、网络工程专业的同学都能拿它做实验并完成系统演示,算是麻雀虽小但五脏齐全的工程样板。
2. WiFi室内定位的地基:信号模型与方案选型
很多人拿到源码第一件事是找地图文件,但我建议你先把信号模型和数据流看明白。源码能跑通只是起点,定位误差从5米压到2米,靠的才是选型和对参数的理解。
2.1 为什么RSSI能当坐标用:信号衰减模型与室内多径
WiFi信号在空气中传播时,接收端测到的RSSI与距离不是线性关系,而是服从一个带随机波动的衰减模型:RSSI = A - 10n·lg(d) + Xσ。A是在1米处测到的信号强度,n是路径损耗系数,室内一般取2.5~4.0,Xσ是由多径、阴影效应造成的随机噪声。如果走三角定位,你得先估计A和n,再反推手机到每台AP的距离;如果走指纹定位,你根本不需要把这个公式落地,直接把每个参考点测到的RSSI向量记录下来,当作一张“信号地图”。
很多教程把指纹法包装得很玄学,其实它就是查表。教学楼走廊1.5米一个点,每个点记录周围可见的十几台AP强度,测试时就拿当前手机扫到的这组信号和表里的每行比较,谁最像,位置就落在谁附近。RSSI波动再大,只要样本采集得够密、表建得够扎实,误差能控制到2~5米,这对室内导航、人员定位、车库找车来说已经能用。
如果你在源码里看到CSI、LQI这类词,说明项目没有停留在RSSI,而是在读网卡底层的信道状态信息。CSI能测到相位和幅度,精度有机会做到亚米级,但普通笔记本的WiFi网卡多半封锁底层数据,本科毕业设计拿它当核心复杂度会失控。RSSI虽粗,但每台手机和网卡都能拿到,配套代码也最简单。所以市面上能跑通的毕业设计源码,九成以上基于RSSI,这个选择不是因为技术最先进,而是最可控。
2.2 三角定位和指纹定位,源码常见的两条路线
打开一份WiFi定位源码,最先要判断它走的是三角定位还是指纹定位。三角定位的思路像GPS:已知各台AP的位置坐标后,用距离公式根据RSSI算出手机到每台AP的距离,再以AP为圆心、距离为半径画圆,取三圆交点。它的计算开销小,模型移植性强,但前提是AP位置要精确部署,而且房间里的人、桌椅、金属门窗都会让测距失真,稍微一乱,三圆就不相交了。
三角定位的公式推导很漂亮,落地却会撞上三件事:AP坐标图纸和现场对不上,很多教室的吸顶AP装在哪不是设计图说了算;RSSI到距离的误差越小,实际上越远越失真;墙体和电梯造成的非视距传播会让圆方程直接无解。因此,它更适合空旷、AP部署规则的环境,比如仓库和展厅。
指纹定位则分成采集和匹配两段:离线阶段拿手机在网格点上采RSSI,在线阶段用当前信号匹配指纹库。它绕开了路径损耗模型,把环境当作一个黑匣子,只看“位置-信号”的对应关系。它的缺点也很明显:换会议室就得重新建模。一张表可以对比清楚:
| 对比项 | 三角定位(传播模型) | 指纹定位 |
|---|---|---|
| 需要硬件 | 必须知道AP坐标 | 仅需可扫描的热点,无需坐标 |
| 定位精度 | 3~8米,受多径影响明显 | 2~5米,取决于采样密度 |
| 换环境成本 | 低,重新校准A和n | 高,需要整层重新采集 |
| 源码复杂度 | 低,纯解析计算 | 中,需要数据库和匹配算法 |
| 毕业设计论文侧重点 | 数学模型推导 | 数据采集流程和工程完整性 |
判断源码属于哪条路线,直接看代码里的关键词。指纹定位源码通常有一个非常清晰的“采集脚本 + csv指纹库 + predict.py”结构,里面会出现fingerprint、train.csv、scan、online这些词;三角定位源码则会出现rssi_to_distance、trilateration、minimize等词。如果源码说是三角定位,但采集脚本只记录了信号强度没记录方向,那基本是个残缺品;如果是指纹定位但没对每个点做多次采样平均,那精度大概率不稳,拿到手第一步就要改。
2.3 拿到源码后先看哪几个文件和调用链
一份合格的毕业设计源码,我会按这个顺序检查。先看配置文件,常见的是config.yaml或config.ini,里面常常藏着AP过滤阈值、扫描间隔、定位算法参数,这些参数决定了你后续要调什么。再看目录结构,有没有data、src、docs三层;data下面至少有offline_scan.csv和online_scan.csv,这种结构说明作者把采集和定位拆开了。最后看入口文件,多数Python项目用main.py或app.py串全流程,你只需要跑通采集和在线定位两步。
# 先跑数据采集,--point 指定网格坐标文件 python main.py --mode collect --floor 3 --point grid_3f.csv # 再跑在线定位测试,--test 指定在线扫描数据 python main.py --mode online --test online_scan.csv这段命令里,--floor指定楼层编号,--point指定网格坐标文件,每个参考点一行,包含x、y坐标,建议用相对坐标原点,把楼层平面图划成0.5米×0.5米的网格,坐标直接记厘米。很多源码作者会在README里写清点位文件格式,照着建CSV就行。如果源码里没有point文件,那多半是让用户在采集页面里手动标点,这也没问题,但你要确认保存到指纹库里的字段名是x、y还是column、row,否则定位结果会跟地图方向差一个旋转角。
定位算法的核心调用链通常是:获取当前RSSI向量 → 按MAC地址对齐指纹库的列 → 计算距离 → 取前k个近邻坐标 → 加权输出最终位置。之后无论是Web界面还是串口屏,都只是在消费这个最终坐标。理解这条链之后,你改参数才能有的放矢。WiFi工具脚本代码里最常出现的坑,是采集时用csv模块直接写入、没有统一编码,换一台电脑读数据就乱码,所以拿到源码第一步最好把数据文件统一改成utf-8编码。
3. 跑通一个最小可用的WiFi室内定位系统
无论源码包装得多漂亮,最后都逃不过三件事:采集、建库、匹配。下面这条路径可以照抄,它正好对应绝大多数毕业设计源码的底层逻辑。
3.1 采集端设计:扫描周围AP并把RSSI落库
在普通笔记本上,最简单的采集方式是用Python调用系统命令扫描周围热点。下面是一个在Linux上能跑的采集脚本,每次扫描间隔2秒,同一个坐标点重复5次,取到的数据直接追加进CSV。
import csv import time import subprocess import re def scan_wifi(): # Linux下使用 iw 命令扫描周围的WiFi热点 result = subprocess.run(['iw', 'dev', 'wlan0', 'scan'], capture_output=True, text=True) blocks = result.stdout.split('BSS ') aps = [] for block in blocks[1:]: bssid = re.search(r'([0-9a-fA-F:]{17})', block) signal = re.search(r'signal: (-?\d+) dBm', block) ssid = re.search(r'SSID: (.+)', block) if bssid and signal: aps.append({ 'bssid': bssid.group(1), 'ssid': ssid.group(1) if ssid else '', 'rssi': int(signal.group(1)) }) return aps def collect_point(x, y, repeat_times=5, output_file='fingerprints.csv'): with open(output_file, 'a', newline='', encoding='utf-8') as f: writer = csv.writer(f) for _ in range(repeat_times): time.sleep(2) # 给iw留出扫描缓冲时间 for ap in scan_wifi(): writer.writerow([time.time(), x, y, ap['bssid'], ap['ssid'], ap['rssi']]) print(f'collected {x},{y}') if __name__ == '__main__': collect_point(100, 100, repeat_times=5)逻辑说明:脚本用subprocess调用系统iw命令扫描无线热点,再用正则从返回文本里抓BSSID、SSID和信号值。每次扫描之间sleep 2秒,同一个坐标点重复5次,这样能抵消一部分信号抖动。这个“2秒和5次”不是随便拍的:手机常见的扫描节流周期是2.4秒,PC网卡完整扫描一次也要1~2秒,间隔太短会拿到缓存数据;重复5次是为了后面的均值更稳定。如果你在Windows上跑,把命令换成netsh wlan show networks mode=bssid,解析格式略有不同,但数据结构完全一致。
如果你拿到的源码是用Android的WifiManager扫描,别在真机里高频调assoc命令,底层在5分钟内会限制主动扫描次数,返回的全是缓存。把单次扫描间隔控制在3秒以上,每个采样点停留20秒左右,采集出来的数据才可用。
3.2 指纹库结构与位置标签格式
上面脚本落成的是长表:同一位置、同一时刻下有多行,每行是一台AP的信号。但定位算法更习惯宽表:一行是一个位置点,每列是一台AP的强度值。所以建库的时候要做一个行转列操作。
import pandas as pd # 读取原始采集文件,长表格式 df = pd.read_csv('fingerprints.csv', names=['ts', 'x', 'y', 'bssid', 'ssid', 'rssi']) # 同一个位置点,先按bssid取均值,再转成宽表 df_group = df.groupby(['x', 'y', 'bssid'])['rssi'].mean().reset_index() pivot = df_group.pivot_table(index=['x', 'y'], columns='bssid', values='rssi') pivot.to_csv('fingerprint_db.csv') print(pivot.head())逻辑说明:groupby按位置和BSSID求均值,pivot_table把均值表转成宽表。缺失的单元格表示采集时那台AP没被扫到,pivot会自动填NaN,定位时我们把它补成-100。这张fingerprint_db.csv就是核心资产,论文里叫离线指纹库。建议保留原始长表、聚合宽表和带标签宽表三份,后面做数据清洗和对比实验都用得上。
如果源码本身用SQLite存指纹,建表语句通常长这样:reference_point表存x、y、floor,fingerprint表存bssid和rssi,中间用point_id关联。表和表之间用外键连接的好处是,可以只更新某个点的指纹而不动其他数据;坏处是,一旦MAC地址数量超过几十个,关联查询会变慢,对本科项目来说,CSV宽表反而更直观。
3.3 kNN定位算法实现(核心代码)
拿到指纹库后的在线定位,最常见的做法是k近邻算法。它没有训练过程,却能在教学楼场景做到2~5米,秘诀全在指纹库密度上。
import numpy as np import pandas as pd # 载入指纹库,索引是x, y,列是AP,缺失值补-100 db = pd.read_csv('fingerprint_db.csv', index_col=[0, 1]).fillna(-100) db_vals = db.values def locate(current_rssi, db, k=3): """ current_rssi: dict, 例如 {'00:11:22:33:44:55': -45, 'aa:bb:cc:dd:ee:ff': -60} db: DataFrame,一行一个参考点,一列一个AP 返回预测的 (x, y) """ # 把当前信号对齐到指纹库的列,缺失AP补-100 vec = np.array([current_rssi.get(col, -100) for col in db.columns], dtype=float) # 计算当前向量与所有指纹点的欧氏距离 dists = np.linalg.norm(db_vals - vec, axis=1) # 取前k个距离最近的参考点 idx = np.argsort(dists)[:k] # 用距离倒数做加权平均,越近权重越大 weights = 1.0 / (dists[idx] + 1e-6) coords = db.index[idx] x = np.average([c[0] for c in coords], weights=weights) y = np.average([c[1] for c in coords], weights=weights) return x, y if __name__ == '__main__': test = {'00:11:22:33:44:55': -45, 'aa:bb:cc:dd:ee:ff': -60} print(locate(test, db))逻辑说明:先把当前扫到的RSSI向量对齐到指纹库的列,缺失的AP补-100,然后计算欧氏距离,取距离最小的前k个参考点,最后用距离倒数加权平均得到预测坐标。两个参数决定误差:一是缺失填充值,补-100代表“不可见”,如果补0,会把“没扫到”当成“信号满格”,结果一定偏;二是加权方式,我一般用距离倒数,如果相等权重,离群的近邻会把坐标拖偏,换成高斯权也可以,但倒数更省事。
如果你的指纹库有几千个点,逐行算距离会越来越慢。常见做法是用scikit-learn的NearestNeighbors包先建索引,然后查最近邻;但毕业设计数据集通常只有几百个参考点,用numpy直接算完全够,没必要引入额外依赖。
3.4 画一张可视化热力图或坐标点图
毕业设计答辩时,没有一张图是吃亏的。先把定位结果画成散点,再糊上一层环境平面图,就能直接放进演示PPT。
import matplotlib.pyplot as plt # 假设predictions是预测坐标列表 pred = [(3.5, 2.1), (6.2, 3.8), (9.1, 5.0)] plt.scatter([p[0] for p in pred], [p[1] for p in pred], c='red', marker='o') plt.xlabel('X/m') plt.ylabel('Y/m') plt.title('WiFi Indoor Positioning Result') plt.grid(alpha=0.3) plt.show()把预测点用红色圆点画出来,如果有真实位置,再用蓝色标记,最后画一个误差圈,半径就是预测点到真实点的距离,答辩老师一眼就能看出效果。更进阶一点,把指纹库所有参考点用背景色画成热力图,可以直观看到信号强度在空间里的分布。常见做法是用imshow把每列AP的RSSI画出来,或者用seaborn的kdeplot画点密度图,但论文里更正式的还是误差饼图和累计分布函数。可视化只是包装,别让它占用你太多开发时间。
4. 让定位更准的三个必调参数
同样的源码,有人定位结果漂得像酒鬼,有人稳如老狗,差别就在这几个参数上。
4.1 RSSI平滑窗口:拿均值还是做滤波
RSSI波动是室内定位最大的玄学来源。相邻两次扫描相差10dBm都正常,所以平滑不是可选项,是必选项。最简单的平滑就是滑动均值,窗口大小取5次,也可以取中位数,后者对突发干扰更不敏感。
# 滑动均值平滑 def smooth_rssi(seq, window=5): # seq: 原始RSSI序列 # 返回与原始序列等长的平滑序列,窗口越界时按有效数据平均 smoothed = [] for i in range(len(seq)): start = max(0, i - window // 2) end = min(len(seq), i + window // 2 + 1) smoothed.append(sum(seq[start:end]) / (end - start)) return smoothed逻辑说明:窗口里的中心点取前几个和后几个数据的平均。窗口长度5能抹掉突发抖动,窗口太长又会把位置变化“磨平”,人走两步轨迹直接滞后半拍。还有一种常见做法是卡尔曼滤波,但卡尔曼对过程噪声和观测噪声的协方差很敏感,调起来容易陷入黑匣子。毕业设计里我建议先用滑动均值或中位数,结果不稳定再加卡尔曼。调试时记住一条:如果定位点静止时还来回跳,第一反应调大窗口,而不是怀疑匹配算法。
窗口到底取多少,要和你线上扫描间隔联动。如果你每2秒扫一次,窗口5代表10秒的平滑,人正常步速能走5米远,位置会明显滞后;这种情况就得把扫描频率提上来,或者把窗口缩到3。最稳妥的做法是:先定扫描频率,再按“人半秒能移动多少距离”反推窗口大小一里,走廊内定位滞后不太会被感知。
4.2 AP选择:哪些热点该进指纹库,哪些该丢
不是所有AP都值得录入指纹库。手机开的热点可以被带着到处走;某些AP功率不稳定,只是污染匹配结果。AP过滤我一般按两个条件:一是在所有参考点上覆盖比例不足60%的AP丢掉;二是同一台AP在每个参考点的RSSI标准差超过8dBm且没有规律的丢掉。
# 按稳定性和覆盖率筛选AP def select_ap(df, min_ratio=0.6, max_std=8.0): # df: 长表,包含 x, y, bssid, rssi stats = df.groupby('bssid')['rssi'].agg(['std', 'count']) total_points = df[['x', 'y']].drop_duplicates().shape[0] stats['coverage'] = stats['count'] / total_points keep = stats[(stats['coverage'] >= min_ratio) & (stats['std'] <= max_std)] return keep.index.tolist()逻辑说明:coverage代表这个AP在所有参考点上被扫到的比例,覆盖率太低说明它在空间里分布太稀疏;std代表同一位置下信号的标准差,超过8dBm说明它波动太大。两个条件都满足的AP才留下。
如果你们场地里经常只能扫到3~4台AP,指纹法的可区分性会明显不足。这时有两个办法:把参考点间距从1米缩到0.8米,让每个位置携带的组合特征更多;或者把RSSI离散化成“强、中、弱”三个等级,减少波动干扰。别指望AP数量少时还能靠复杂算法翻盘,指纹定位本质上是特征的丰富度,没有特征,神仙算法也白搭。
4.3 定位算法里的k值、距离度量和阈值
以kNN为例,k通常是3~5,不是越大越好。k=1只体现最近邻,精度上限高但抗噪差;k=7以上,多楼层、多房间的指纹都被拉进来,边界会糊。选择k可以做一个离线交叉验证:把指纹库每个点留出来,用它自己的指纹和其他点匹配,算平均误差,k从1试到10画一条误差曲线,选曲线变平的位置。这个脚本十来行就能写完,比空口争论k值有说服力得多。
欧氏距离配合原始RSSI使用时,所有维度单位都是dBm,看似公平,但不同AP的方差差异会影响距离。波动大的AP会霸占距离项,导致这些AP一变化,整个匹配结果就偏。解决办法是把每列除以各自的标准差做标准化,或者换成马氏距离:
# 用标准化欧氏距离替代原始欧氏距离 from scipy.spatial.distance import cdist def locate_std(current_vec, db_vals, coords, k=3): # 对指纹库每列做z-score,缓存均值和标准差 mean = db_vals.mean(axis=0) std = db_vals.std(axis=0) + 1e-6 db_norm = (db_vals - mean) / std vec_norm = (current_vec - mean) / std # 计算标准化欧氏距离 dists = cdist([vec_norm], db_norm).ravel() idx = np.argsort(dists)[:k] # 输出k个近邻坐标的平均值 x, y = coords[idx].mean(axis=0) return x, y逻辑说明:指纹库每列先减均值再除以标准差,把不同AP的波动尺度拉到同一水平。vec_norm必须使用和指纹库相同的mean和std,否则标准化失去意义。这个方法不一定比原始欧氏更好,但在AP信号忽高忽低的场景下,它能把波动大的AP降权,误差会更稳。
最后一个必调参数是置信度阈值:当最近邻的距离都大于某个上限,比如欧氏距离超过300时,说明今天的环境和指纹库差异太大,这时候不该硬给坐标。很多源码忽略了这一点,导致你在角落掏出手机,系统也照样给你指到楼下去。加上一个简单的if判断,返回“未匹配”,让用户重新走两步再试,比给出错误坐标更靠谱。
5. 室内定位避坑指南:五个让项目翻车的细节
下面这五条坑,是我和同行在学生项目里反复遇到过的。每条按现象、原因、解决三个部分写,你可以直接对照排查。
5.1 采集时站着不动,测试时手机甩着走,误差翻倍
现象:离线采集时每个点坐1分钟,RSSI均值非常稳定;线上测试时拿着手机边走边看,返回的坐标像打摆子,一会儿在东一会儿在西。
原因:指纹库把静止采样的信号特征记成了“平滑平均值”,而在线场景是动态采样的快照。手部姿态变化、人体遮挡天线会让信号向量偏离该位置的平均值分布,此时kNN拿一个速度和姿态都不同的向量去匹配,最近邻就变成了旁边的点。
解决:在线定位必须沿用离线时的平滑规则,同时离线采集时不要只采静态值。建议在每个参考点周围画一个0.5米半径的圆,沿边走一圈再停下采集,让指纹库包含多种天线朝向和身体遮挡情况。如果源码允许,可以每次位置至少采集8个方向的样本,再做均值;不一定要很多,但千万别只在胸口举着手机一动不动。
5.2 AP的MAC会变或失效,指纹库必须清理
现象:上午定位好好的,下午就偏到隔壁教室,一看测试数据,指纹库里所有MAC都换了一批。
原因:商用AP可能开启了MAC随机化或频段切换;手机热点重启后BSSID会变;部分AC网关在AP重新拨号后也会更换MAC。指纹库按BSSID匹配,一变就全对不上。
解决:采集脚本里同时记录SSID和频段信息,线上匹配时优先选择稳定的SSID字段做辅助关联。至少每两天重扫一次现场,把已经消失的MAC从指纹库里移除。如果定位Web页面显示AP列表,你可以直接看到哪些热点还活着;如果发现某个AP的SSID没有变但MAC变了,就在配置里先把它固定下来。源头解决比事后删数据更省心。
5.3 Android电话的权限和扫描节流,真机采集必须开定位开关
现象:在Android真机上跑采集App,明明连着WiFi,返回的扫描列表却只有2~3个AP,或者每隔几分钟列表就空了。
原因:Android 6.0以上扫描WiFi需要位置权限,系统同时会对WiFi扫描做节流限制。很多国产系统还把“WiFi扫描”和“定位服务”绑定,你关掉GPS开关,系统就不再返回新的扫描结果。
解决:在系统设置里同时开启“定位服务”和“WiFi扫描”选项,所有权限弹窗都允许,扫描间隔控制在3秒以上,避免触发节流。尽量别用模拟器,模拟器对WiFi扫描支持很差。如果你改用esp8266-01串口转WiFi模块做采集,就没有这类权限问题,但自己得实现扫描缓冲和UART串口协议,把结果通过串口送到上位机。毕业设计里两种路线都可行,看你是想省时间还是想玩硬件。
5.4 二维指纹定位在楼层的“幽灵楼层”
现象:一楼定位正常,上到二楼一测,坐标没有变化,但楼层标签在1和2之间反复横跳。
原因:楼层板隔不断WiFi信号,一楼的AP在二楼收到的RSSI和二楼本层AP可能差不多。指纹库只训练了二维坐标,没有把楼层作为特征维度建库,预测时自然在两层之间摇摆。
解决:指纹库标签加上楼层字段,定位时先根据“能看到哪些AP”做一次粗判级;比如强信号中包含二楼独有几个AP,就优先匹配二楼指纹库。常见做法是每楼层单独建一个指纹模型,在线定位先用最强AP列表判断楼层,再到对应楼层模型里匹配。不要省略这一步,跨楼层误差能轻松超过10米,演示时非常尴尬。
5.5 坐标系不统一:采集坐标和地图坐标差一个旋转角
现象:定位结果在图上看起来位置都对,但所有点整体顺时针转了15度,或者Y轴越远误差越大。
原因:采集用的坐标原点在地图边角,但Web页面加载地图后又叠加了一层经纬度或像素坐标换算;也可能网格采集线和地图画布上的横纵轴没对齐。
解决:拿到源码后先确认指纹库坐标的值域。如果坐标都是几百以内的整数,那多半是像素坐标;如果只有x、y没带楼层和角度,投影前要在配置里加rotation偏移和scale比例。测试方法很简单:在房间四个角落各采一个测试点,预测出来的四个点应该还在四个角落;如果整体偏转,调偏航角;如果整体偏移,调平移量。这个坑别拖到答辩前才发现,因为它的现象和算法误差非常像,但修起来只要一次配置。
6. 进阶玩法与精度验证:从“能动”到“能用”
6.1 用平均误差和CDF曲线量化定位效果
毕业设计答辩最怕被问“准不准”,别用“感觉挺准”回答,拿出平均误差和累计误差分布曲线。累计误差分布就是CDF曲线,横轴是误差距离,纵轴是有多少比例的测试点误差小于这个距离。
import numpy as np import matplotlib.pyplot as plt # 假设errors是一组测试点到真实点的距离 errors = [1.2, 2.1, 3.4, 2.8, 4.5, 1.9, 2.6, 3.0] err_arr = np.sort(errors) cdf = np.arange(1, len(err_arr) + 1) / len(err_arr) plt.plot(err_arr, cdf) plt.xlabel('Error/m') plt.ylabel('CDF') plt.title('Positioning Error CDF') plt.axhline(y=0.8, color='r', linestyle='--') plt.show()“平均误差2.5米,80%样本的误差在3米以内”,这两句话比贴十个流程图都有说服力。测试时在每个已知点踩点记录预测坐标,算真实距离,不要在同一个点反复测十次,要沿着路径走,把动态误差也暴露出来。
6.2 加一个惯导/步频辅助的松耦合
如果你手里还有手机的方向传感器和加速度计,可以做一个经典的进阶:行人航位推算。每一步根据加速度计估算步长,方向计估算方向,再和WiFi输出做一个松耦合融合。用卡尔曼滤波时,WiFi坐标当作观测值,PDR的位移当作过程量,两个都要配好噪声协方差。不要一上来就尝试紧耦合,本科阶段调状态方程会非常耗时,容易把项目拖成烂尾。
6.3 把定位数据推到浏览器画实时轨迹
Web界面是源码加分最多的部分。常见做法是Flask后端每秒输出一次预测坐标,前端用定时器拉取,再在平面图上打点。如果你拿到的源码没有Web端,自己写一个薄接口也不难:
from flask import Flask, jsonify, request app = Flask(__name__) current_pos = {'x': 0, 'y': 0} @app.route('/pos') def pos(): # 前端每秒拉一次坐标 return jsonify(current_pos) @app.route('/update', methods=['POST']) def update(): global current_pos data = request.get_json() current_pos = {'x': data['x'], 'y': data['y']} return '', 204逻辑说明:/pos接口用于前端读取最新坐标,/update接口用于定位线程把预测结果推给Web端。把current_pos放到全局字典,更新时直接替换。这个接口不加鉴权,只适合实验环境,正式部署再加token。
我做室内定位项目最大的教训是,别把重心全押在算法公式上,数据采集和现场标定花的时间才是误差的主要来源。源码可以在一晚上跑通,但想让它在答辩现场稳定不翻车,最好提前一小时到现场重新扫一遍关键点,把今天变化的AP清掉。这个习惯救过我很多次,希望帮到你。
本文还有配套的精品资源,点击获取