室内行人定位轨迹的校正方法与可视化系统的设计与开发
1.引言
1.1 项目背景
1.1.1 室内定位
随着联网移动设备的流行,以及交通的快速发展,人们对个人定位的需求越来越高。基于位置的服务也有广泛的应用,并被广泛应用于导航、追踪和导游等方面。的关键技术是导航定位,包括室外到室内的定位。传统的全球定位系统和移动通信等技术方法都在室外环境中有着较为成熟的算法和较高的精度,但是其在室内环境中的表现则不尽人意。近几十年来,室内定位技术也在不断发展进步,但是各种技术都各有优缺,如何保证行人定位的准确度和连续性,是室内定位技术研究的重点。
随着智能设备的普及,基于位置的服务(LBS)成为人们日常生活中必不可 少的一部分。现代人有 80%的时间是在室内度过的。如何在室内环境中准确地确 定用户的位置,成为近年来的一个热门研究课题。
在室外空间,以 GPS、北斗为代表的卫星定位系统能够满足人们在户外开阔 环境中的定位需求;但在室内环境中,由于建筑结构、室内障碍物对卫星信号的 阻挡和反射,卫星定位系统会产生较大的定位误差。
因此,研究者们提出了多种基于无线信号的室内定位方案,利用的信号包括 Wi-Fi、蓝牙、超声波、超宽带、惯性传感器、红外线、声信号等。
1.1.2 行人行位推算
在行人定位领域 [7],行人航位推算 (PDR[2]) 是一种主流方法。传统的 PDR 算法依赖惯性测量单元 (Inertia Measurement Unit,IMU) 收集行人的加速度、角速度等信息,进而推算行人的运动轨迹,见图 1-1。
PDR 算法主要包括步频检测、步长估计、航向估计三个任务
1.1.3 基础PDR算法
• 步频检测: 波峰测频法
被测人员走动且手持终端时,随着人的周期性运动,三轴加速度传感器的波形也会产生周期性的变化,见图 1-2,其中以竖直方向(Z 轴)上的加速度变化最为明显。当一个有效波峰出现,即可认为移动了一步。按此原理即可估计出行人的步频。
• 步长估计:Weinberg 算法
步长估计利用 Weinberg 算法实现,与最大加速度和最小加速度相关。
其中, 是第 n 步的步长;n 和 分别表示第 n 步的 Z 轴加速度最大值和最小值;K 为常数。
• 航向估计: 角速度积分
航向估计通常使用陀螺仪和电子罗盘这两种传感器。我们这里以基于陀螺仪的航向估计算法为例。陀螺仪可以测量人运动时的三轴角速度,对角速度进行积分即可计算出运动航向的变化角度值。如果已知航向的初始值,就可以计算出当前时刻的绝对航向。
1.1.4 定位效果及评价指标
评价一个定位算法的定位精度,我们通常有如下几个指标:
-
平均定位误差:计算所有测试点的定位误差(估计位置与真实位置之间的物理距离),求平均值。
-
误差累积分布曲线(CDF):各测试点定位误差的累计分布函数,它的横坐标表示定位误差,对应的纵坐标表示误差小于这个值的测试点的出现的频率。即
- 百分位误差:百分位误差指在 CDF 图中,纵坐标取一定百分比时,对应横坐标表示的定位误差。我们常用的百分位误差有 50% 误差,75% 误差,90% 误差等。
1.2 系统描述
1.2.1 系统功能
基本要求
• 合理设计网页布局,包括:室内地图区、信息显示区、定位指标计算区等;设计一个菜
单作为初始界面。
• 在室内地图区,需要呈现室内平面图、行人真实轨迹和定位轨迹。用户能够通过点击定位轨迹中的点,查看不同位置的传感器信息(三轴加速度、角速度等)和行人航位信息(位移和航向),并在信息显示区显示。
• 在定位指标计算区,需要实时计算并展示当前定位轨迹的平均定位误差、百分位定位误差、CDF 曲线等定位指标。
• 系统需通过数据库实现信息的存储与交互。
• 定位轨迹至少需要实现两种
– 直接定位点的轨迹
– 使用一个直接定位点作为初始点,实现 PDR 的基础算法来估计行人轨迹。
• 软件系统可以通过上传 csv 文件(格式见数据文件),实时计算 PDR 算法的定位结果,绘制定位轨迹,显示定位指标
1.2.2 系统架构
该软件的 系统架构为 B/S 架构,前端负责网页设计,后端负责编写 API 接口,调用设计的算法并利用数据库中的数据并来实现对室内行人定位轨迹的校正,并在网页上实现轨迹的可视化。后端通过 API 接口平台,测试对应的 API 接口; 前端利用 API 接口,测试网页显示内容是否正确,交互是否成功。算法模型通过 CDF 曲线以及平均定位误差来判断优劣; 联调测试: 前后端联调,观察软件是否达到理想的效果,是否满足了所有的用户需求。
1.3 模型描述
1.3.1 前后端交互模型
1.3.2 算法模型
•波峰检测: 我们称波宽是一个波峰的周期,最简单的波峰检测是按照固定波宽的大小进行匹配,如果一个点比一个波宽内的点都高,那么就认为他是一个波峰。在本次问题中,我们会发现波峰的周期性并没有和很好,不同组的数据存在一些差别,而且会有伪波峰的存在,既存在波宽不相同的问题,也存在单调性不那么强,因此直接匹配的方法不可取。经过文献查找,查询到一种高效且鲁棒性较强的波峰检测算法,即 AMPD 算法 (自动多尺度峰值查找算法) [3],这个方法的优势是:(1)算法本身参数较少,对信号具有良好的自适应性,唯一的假设是信号的波峰是周期的或者准周期的(在我们本次的项目中,可以近似认为);(2)抗噪能力强,对周期性的要求也不是很高。原理上来说:首先计算一个寻找局部最大值图,即对于不同的周期,来匹配寻找极大值,并且记录不同的周期对应极大值个数,对于个数最多的,我们可以认为是周期最明显的主要部分,在这里,即代表人移动时造成的波峰。我们找到局部最大值最多的作为窗口长度(window length)在窗口长度下,匹配出来的局部最大值认为是波峰。因为原论文里面有随机数,且计算较复杂。另找到一篇改进的方法,于是在此基础上进行修改 [4] 开始效果不错,但是在测试集上效果欠佳,因为波峰不明显,出现了平顶波峰,会影响窗口长度 = 3 的局部极大值图。于是对代码进行了修改,增加了一个参数,为最大值长度,如果识别的最大值对应的窗口长度过大,会自动降低到阈值,此阈值是根据采样周期近似为 100ms,在平持状态设置为 3,在摇摆状态设置为 5,之后波峰检测效果很好,基本不会漏步。
•步长估计:步长估计的方法主要有两种,第一种是对加速度求积分算出行进距离,第二种是利用加速度本身的峰峰值来计算。这里需要用到是否静止的信息来代表人的速度。但是在本问题中,有一组只有两个 stay = 1 是静止的点,很难利用速度信息。因此我们采取别的第二种方法进行处理。我们采取的是 Weinberg 算法计算方法见公式 (1-2)。K 通过近似估算,取了 0.45。在波峰检测的时候,我们对波谷和波峰都进行了检测。我们根据波峰和波谷之间的峰峰值,即可计算对应步长,我们考虑实际情况,在转弯的情况下,步长有时候会有较大浮动,因此我们对每个峰峰值单独计算步长,而不是求平均值计算一个整体的步长,平均的方法更加理想化,其实可以通过指数记忆的方式进行平均,这样既减少了步长的高频误差,同时也能考虑实际每一步本身的特性,本质还是一个混合滤波。
•航向估计:航向估计是 PDR 算法中也是各种遥感、飞行器航迹等最难的一部分,对此的研究也较为深入,包括很多种方法。有欧拉角法、方向余弦法、三角函数法、Rodrigues 参数法、四元数法、等效旋转矢量法、投影向量法等各种方法。本次课程设计主要研究并实现了 Rodrigues 参数法、分解向量法、四元数等方法。
2.软件设计
2.1 模块层次
2.2 技术选型
在为各模块选择实现方法时,主要考虑到学习难度、相关资源是否丰富和技术是否成熟等。前端和后端选用业界开发最常用的 vue+spring 组合,这套轻量级框架学习资源较多,能够大大简化开发难度而且技术成熟,相关支持较多,因此是比较适合的选择。算法部分采用 python语言,因为它有很多数学计算库可以方便调用。因为本人主要负责前端web开发,因此主要介绍前端技术部分。
•Vue框架:Vue.js是一套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。Vue 的核心库只关注视图层,并且非常容易学习,非常容易与其它库或已有项目整合。另一方面,Vue 完全有能力驱动采用单文件组件和Vue生态系统支持的库开发的复杂单页应用。且Vue.js 是一个提供了 MVVM 风格双向数据绑定的 Javascript 库(无依赖别的js库,直接引入一个js文件就可以使用,跟jquery差不多),专注于View 层。它的核心是 MVVM 中的 VM,也就是 ViewModel。 ViewModel负责连接 View 和 Model,保证视图和数据的一致性,这种轻量级的架构让前端开发更加高效、便捷。[1]
•调用的外部库:Echarts、Canvas、axios
•ECharts,一个纯 Javascript 的图表库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器,底层依赖轻量级的 Canvas 类库 ZRender,提供直观,生动,可交互,可高度个性化定制的数据可视化图表,拥有丰富的图表类型,可以满足动态数据的可视化实现。
•Canvas:是HTML5提供的一种新标签,可以理解为一个矩形区域的画布,可以用Javascript控制每一个像素在上面绘画。canvas 标签使用 JavaScript 在网页上绘制图像,本身不具备绘图功能。并且canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。
•Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中,主要是用于向后台发起请求的,还有在请求中做更多是可控功能。
2.3 前端设计
前端设计,根据前述系统需求分析,以 Vue 作为前端框架,Vue-cli 脚手架构建基本前端项目,并规划路由,主要划分为主菜单(HomeView.vue)和信息显示 (AboutView.vue) 两个页面。其中主菜单功能包括介绍团队信息、进入软件系统等功能,实现较为简单,不做赘述。
信息显示页面包括室内地图区、信息显示区、定位指标计算区等三个子地图区。因为我们小组对所有的 6 组数据都进行了处理,因此我们的页面可以显示 6 种不同的情况。通过导入数据,给后端传递需要的数据文件,后端经过数据处理,再给前端传递回所需要的数据进行可视化操作,并且我们可以通过鼠标点击获取相应点的数据信息,具体的可视化界面如图2.3所示。
下面从 AboutView 这一 vue 页面设计具体介绍前端设计思路:
2.3.1 页面路由设计
考虑到对室内行人系统的三大地图区设计任务要求不同,室内地图区需要呈现室内平面图、行人真实轨迹和定位轨迹; 定位指标计算区,需要实时计算并展示当前定位轨迹的平均定位误差、百分位定位误差、CDF 曲线等定位指标;信息显示区需要通过点击定位轨迹中的点,查看不同位置的传感器信息。因此在该 vue 页面中需要分模块进行相应功能的实现。随后设计导入文件接口,将数据文件传递给后端;设计 1-6 六个按钮,绑定触发事件,实现后端传输六组数据的切换。导入文件设计如下所示:
// 控制传递哪一类型的csv文件
modeUpload: function (item) {
// console.log(item.file);
this.mode = item.file
if(this.updateData == 1){
this.uploadT();
}else if(this.updateData == 2){
this.uploadP();
}else if(this.updateData == 3){
this.uploadR();
}else{
console.log("modeUpload error");
}
},
uploadP: function () {
console.log("uploadP function");
let fd = new FormData()
fd.append('file', this.mode)
axios.post('http://localhost:8080/impt/position', fd, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(response => {
console.log(response.data);
})
},
uploadR: function () {
console.log("uploadR function");
let fd = new FormData()
fd.append('file', this.mode)
axios.post('http://localhost:8080/impt/running', fd, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(response => {
console.log(response.data);
})
},
uploadT: function () {
console.log("uploadT function");
let fd = new FormData()
fd.append('file', this.mode)
axios.post('http://localhost:8080/impt/ground', fd, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(response => {
console.log(response.data);
})
},
2.3.2 室内地图区设计
室内地图区需要应用后端传递的数据,在地图上显示真实的行人轨迹、定位轨迹以及预测的行人轨迹。首先由于地图的像素坐标与在网页中显示的地图像素坐标不一致,因此需要将后端传递的位置数据进行相应的坐标变化,即通过获取原图像(0,0)与(-1.0,3.4)两点坐标所对应的现网页中的像素值,即两点对应两组新的像素坐标,进行二元一次方程组的计算,即可完成坐标变换。坐标变换后,使用 canvas 可视化工具库,在地图上渲染相应信息,成功将所需要的三条轨迹图画了出来。
doDraw() {
// xy
// let arr = JSON.parse(JSON.stringify(this.da));
// let arr = this.da;
console.log(this.mockData);
this.ctx.setLineDash([]);
this.ctx.beginPath();
this.ctx.moveTo(this.mockData[0].x * 73 + 397, 383 - 64 * this.mockData[0].y);
for (let i = 1; i < this.mockData.length; i++) {
let x = this.mockData[i].x * 73 + 397;
let y = 383 - 64 * this.mockData[i].y;
this.ctx.lineTo(x, y);
}
this.ctx.strokeStyle = "black";
this.ctx.lineWidth = 3;
this.ctx.stroke();
for (let i = 0; i < this.mockData.length; i++) {
this.ctx.beginPath();
this.ctx.fillStyle = 'black';
this.ctx.strokeStyle = "white";
let x = this.mockData[i].x * 73 + 397;
let y = 383 - 64 * this.mockData[i].y;
this.ctx.arc(x, y, 3, 0, 2 * Math.PI);
this.ctx.stroke();
this.ctx.fill();
}
2.3.3 指标计算区设计
通过对后端传递误差数据进行相应的数据处理,然后利用 echart 模块进行可视化设计,将 CDF 曲线较好地在该地图区显示出来并在网页中显示相应50%、75%,90%误差所对应的数据信息。
updateError(){
this.error=[];
var sorted = [];
for(let i = 0;i < this.mockData.length;i++){
sorted.push(this.mockData[i].error);
}
sorted.sort(function (a,b){
return a-b;
});
this.error50 = (sorted[7]+sorted[7])/2;
this.error75 = sorted[10];
this.error90 = (sorted[12]+sorted[12])/2;
this.errorAve = sorted.reduce((a, b) => a + b) / sorted.length;
for(let i = 0;i < this.mockData.length;i++){
this.error.push([sorted[i],(i+1)/this.mockData.length]);
}
console.log(this.error);
var option = {
xAxis: {
},
yAxis: {
},
series: [
{
data: this.error,
type: 'line'
}
]
};
this.myChart.setOption(option,true);
},
2.3.4 信息显示区设计
信息展示区左上方为室内地图区,正上方为指标计算区。再经过坐标变换后,通过 mousedown方法,获取鼠标点击时的像素值,通过 v-if 和点击事件绑定数据判断与后端传递过来的数据文件中的数据点是否足够接近,若足够接近,则可认为鼠标点击的即为该数据点,即将该数据点的位置、加速度、角速度等信息显示在该地图区,不占用空间且提高了用户体验。
setPoint(event) {
let pos = {
x: event.clientX,
y: event.clientY
}
console.log(pos);
for (let i = 0; i < this.mockData.length; i++) {
let x = this.mockData[i].x * 73 + 397;
let y = 383 - 64 * this.mockData[i].y;
if (Math.sqrt(Math.pow(pos.x - x, 2) + Math.pow(pos.y - y, 2)) < 27) {
this.pos = this.mockData[i];
}
}
2.3.5 前端接口设计
利用axios方法,设置get与post来封装前端接口,与后端进行连接,方便后端的调用,因为本项目涉及多个服务器的接口,域名不一样,所以要进行多个代理设置,代码块实现如下所示[2]。
/**
* api接口统一管理
*/
import {get, post, upload } from './request.js'
// 登录
export const login = p => post('/API/Admin/Login', p);
export const query = g => get('http://localhost:8080/predict/batch/'+g);
export const query_position= g => get('http://localhost:8080/position/batch/'+g)
export const Query = g =>get('http://localhost:8080/predict/batch/'+g);
export const cmpt_hold = g =>get('http://localhost:8080/cmpt/predict/'+g+'/0');
export const cmpt_swing = g =>get('http://localhost:8080/cmpt/predict/'+g+'/1');
export const query_position_file = g =>get('http://localhost:8080/cmpt/position/'+g);
/**
* post方法,对应post请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
export function post(url, params) {
return new Promise((resolve, reject) => {
axios.post(url, params)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err.data)
})
});
}
/**
* get方法,对应get请求
* @param {String} url [请求的url地址]
* @param {Object} params [请求时携带的参数]
*/
export function get(url, params) {
return new Promise((resolve, reject) => {
axios.get(url, {
params: params
}).then(res => {
resolve(res.data);
}).catch(err => {
reject(err.data)
})
});
}
3.总结与建议
我们今年因为疫情的影响,软件课设推迟到了开学验收,因此我们的项目主要是寒假进行完成的。在这次的软件课程设计中,我们小组三人针对这一软件项目的实现进行了分工,其中我主要负责前端网页的设计以及和前后端的连接,在着手前端的设计之前自己从来没有接触前端方面的知识。首先我进行了html、css以及JavaScript基础三件套的学习,对前端的web开发有了一个基本的了解。其中这个过程是比较困难的,首先第一个问题,网页页面如何设计得既符合要求,可实现所有功能,且足够美观?这一问题经过我们小组的讨论,使用ps软件自行设计出了我们小组的网页页面,于是这一问题得到解决。其次如何将代码写的简洁高校并可较为方便地与后端进行数据交互是令一个遇到的问题。这一过程中,我发现三件套可能并不能很好地满足我们小组的想法,于是我进一步学习了Vue这一轻量级框架,方便代码的管理与整体的交互。然而开始在我写好两个页面之后,出现了新的问题,如何画图?首先我学习了axios的使用,利用post与get方法封装前端的接口,再完成与后端的数据库连接后,可以正常接收到来自后端的处理过的数据。数据问题解决了,那图像问题怎么办呢?因此我又进行了echart和canvas的学习,最终理解了图形可视化的原理。但是因为在页面上坐标的显示与像素点一一对应,会有一个页面显示的偏差,而后端传递过来的数据是在原图的坐标基础上计算得到的,因此需要通过一定的数学方法进行坐标变换,经过我们小组的讨论与参数调节之后,图形可视化问题得到完美的解决。在这个过程中我从一个从未接触过前端web开发的小白逐渐了解到这方面很多的前端知识以及实现原理,了解了正常软件开发中的一些过程,如何做好前后端的交互更是有了比较清晰的认识,更加明白了从实践中来、在实践中学习的含义。在软件开发的过程中,我感受到的不仅是个人能力的提高,在与小组队友之间的讨论与学习之中,更是意识到了团队协作的重要性,我们一起进行接口的一一调试时。很快就将项目完成。我们了解到彼此之间相互学习的重要性,最后,我们对自己的结果也比较满意,也希望今后能够通过团队合作进一步提高自己的能力!对于这门软件课程设计的一些建议,个人认为包括,希望每个人都能前端、后端、算法都会是合理的,但是一般情况下,一个项目的分工是不能出现两个人同时负责某一个部分,这样不利于项目的进展,作为学生,我更倾向于每人负责一部分,因为如果我没有什么基础的话,我需要同时学习其他部分的开发还是有一定的困难。第二个是希望测试数据多给一些,在本次实验中就是在测试的六组数据中效果很好,但是在验收的时候就出现了一些 bug,因为我们小组错把局部现象当作了一般情况,最后当场调试代码出现了一些问题,结果验收结束这一问题我们小组也没能很好的解决。总而言之,软件课程设计这门课程任务的存在确实是能够让我们的能力得到提高的,同时也对一个软件的完整框架有所了解。在最后也是希望软件课设这门课程也能越来越好!
4.参考资料
[1] 穆瑾轩.Vue详细介绍及使用.https://blog.csdn.net/xiaoxianer321/article/details/111560355,2021-01-28
[2] shenroom.axios请求配置.https://blog.csdn.net/qq_41772754/article/details/88075391,2019-03-02
[3] Felix Scholkmann, Jens Boss, and Martin Wolf. An efficient algorithm for automatic peak detection in noisy periodic and quasi-periodic signals. Algorithms, 5(4):588–603, 2012.