然间从GitHub上发现一个小项目,做的是一个基于UKF的状态估计,使用C++版本的mathplot做显示,编译之后却发现显示一直有问题,于是萌生了一个想法:能不能把它移植到ROS中?然后就有了接下来的学习过程,以此记录。如有问题,欢迎提出。

这是需要复写的代码:在GitHub上(project的核心代码是来自源码的,如有侵权,请联系我更改,谢谢。)
源码最终实现的效果如下:

同样也可参考UKF最初代码:GitHub_Self-Driving Car
同样也有相关博客对该代码进行了解析,仅供参考:UKF算法解析

复写完成的代码请看最后一篇博客。
在ROS中实现的效果如下:百度网盘视频或者YouTube视频

一、前提说明

首先解释一下,路标数据,里程计数据,激光数据各自的组成部分,以及project涉及的运动模型。

1.landmark路标

在程序中(在ukf_console.h的51行),用一个vector表示:

std::vector<Eigen::Vector3f> Landmarks;

各表示点的id号,点的x坐标以及y坐标。此处说明路标具有id标识的,也就决定了后续过程中数据关联的方式。
在param/world_data.txt中,写明了各个landmark的详细信息:

1 2 1
2 0 4
3 2 7
4 9 2
5 10 5
6 9 8
7 5 5
8 5 3
9 5 9

在程序中直接用C++的文件读取方法,将landmark信息读入vector数组中:

bool ukf_console::getLandmarks(const std::string& path)
{std::ifstream in_file(path, std::ifstream::in);if (!in_file.is_open()) {std::cerr << "Cannot open input file: " << (path) << std::endl;exit(EXIT_FAILURE);}std::string line;while(std::getline(in_file, line)) {std::istringstream ss(line);Eigen::Vector3f mp;ss>>mp(0);ss>>mp(1);ss>>mp(2);Landmarks.push_back(mp);if (Debug)std::cout << (Landmarks.back())(0) << ": " << (Landmarks.back())(1)  << ": " << (Landmarks.back())(2)  << std::endl;}if (in_file.is_open()) in_file.close();}

2.观测信息

这里的观测信息包括里程计信息和激光信息,在tool.h中定义了一组观测信息的格式:

struct Record {Eigen::Vector3f odom;                 //里程计数据: 起点夹角, 行走距离, 终点夹角std::vector<Eigen::Vector3f> radar;       //激光数据:路标id号, 射线距离, 射线角度};

在实际project中,会有多组观测信息,所以这里用一个vector提前保存了所有数据:

std::vector<Record> Measurements;

首先解释一下里程计数据的格式,其中的起点夹角和终点夹角是指dθt+1′d\theta _{t+1}^{'}dθt+1′​和dθt+1d\theta _{t+1}dθt+1​,行走距离应该是直线距离RRR。(可能这里的角度关系还是有点理解不对,,,)

激光数据主要是关于landmark的观测信息,这里出了观测距离ranges和观测角度φ\varphiφ,还有路标的特殊id代号。激光观测原理如下


实际的观测数据由param/sensor_data.txt文件读入,某一项数据格式如下:

ODOMETRY 0.100692392654 0.100072845247 0.000171392857486
SENSOR 1 1.89645381418 0.374031885671
SENSOR 2 3.85367751107 1.51951017943

同样,用C++的文件读取方法将信息读入vector中:

bool ukf_console::getMeasurements(const std::string& path)
{std::ifstream in_file(path, std::ifstream::in);if (!in_file.is_open()) {std::cerr << "Cannot open input file: " << path << std::endl;exit(EXIT_FAILURE);}std::string line;Record record;int index = 0;while(getline(in_file, line)) {std::string sensor_type;std::istringstream ss(line);ss >> sensor_type;//measurement type r1 t r2if (sensor_type.compare("ODOMETRY") == 0) {//end the first record;if (record.radar.size() != 0) {Measurements.push_back(record);record.radar.clear();if (Debug && index < 50)std::cout << index << "-----------" << std::endl;index++;}auto& odo = record.odom;ss >> odo(0);ss >> odo(1);ss >> odo(2);if (Debug && index < 50)std::cout << (record.odom)(0) << ": " << (record.odom)(1) << ": " << (record.odom)(2) << std::endl;} else if (sensor_type.compare("SENSOR") == 0) {auto& radars = record.radar;Eigen::Vector3f radarR;ss >> radarR(0);ss >> radarR(1);ss >> radarR(2);radars.push_back(radarR);if (Debug && index < 50)std::cout << (radars.back())(0) << ": " << (radars.back())(1) << ": " << (radars.back())(2) << std::endl;}}if (record.radar.size() != 0) Measurements.push_back(record);if (in_file.is_open()) in_file.close();
}

3.运动模型

项目中的小车可以认为是两轮差动运动模型,如上图所示,即很容易就能得出下面的运行模型:

{xt+1=xt+R⋅cos⁡(θt+Δθ)yt+1=yt+R⋅sin⁡(θt+Δθ)θt+1=θt+Δθ+Δθˊ\left\{ \begin{array}{c} x_{t+1}=x_t+R\cdot \cos \left( \theta _t+\varDelta \theta \right)\\ y_{t+1}=y_t+R\cdot \sin \left( \theta _t+\varDelta \theta \right)\\ {\theta _{t+1}=\theta _t+\varDelta \theta +\varDelta \acute{\theta}}\\ \end{array} \right. ⎩⎨⎧​xt+1​=xt​+R⋅cos(θt​+Δθ)yt+1​=yt​+R⋅sin(θt​+Δθ)θt+1​=θt​+Δθ+Δθˊ​

在程序(unscented_kf.cpp的90-93)中,这样实现:

double angle = Xsig(2,i);
Xsig(0, i) = Xsig(0, i) + t * cos(angle + r1);
Xsig(1, i) = Xsig(1, i) + t * sin(angle + r1);
Xsig(2, i) = Xsig(2, i) + r1 + r2;

二、相关Markers的显示

喜欢使用ROS的一个很重要的原因就是它的可视化界面了,RVIZ里的各种marker可以显示很多丰富的信息,令程序调试十分的方便。

1.landmark以及pose的显示

这里采用圆柱体进行显示,直接看landmark的代码,estimatePose的不看了:

void ukf_console::LandmarksPublish()
{visualization_msgs::MarkerArray ma;visualization_msgs::Marker marker;// 设置该标记的命名空间和ID,ID应该是独一无二的// 具有相同命名空间和ID的标记将会覆盖前一个marker.header.frame_id = "map";marker.ns = "2Dlandmarks";// 设置标记行为:ADD(添 加),DELETE(删 除)marker.action = visualization_msgs::Marker::ADD;// 设置标记类型,初始值为球体。在立方体、球体、箭头和 圆柱体之间循环marker.type = visualization_msgs::Marker::CUBE;// 设置初始形状为圆柱体marker.id = 0;marker.pose.position.z = 0;marker.pose.orientation.x = 0.0;marker.pose.orientation.y = 0.0;marker.pose.orientation.z = 0.0;marker.pose.orientation.w = 1.0;// 设置标记的比例,所有方向上尺度1表示1米marker.scale.x = marker_scale;marker.scale.y = marker_scale;marker.scale.z = marker_scale;//设置标记颜色,确保alpha(不透明度)值不为0, 紫色marker.color.r = 160.0/255.0;marker.color.g = 32.0/255.0;marker.color.b = 240.0/255.0;marker.color.a = 1.0;marker.lifetime = ros::Duration(0);int markers_size = Landmarks.size();for(int i=0 ; i < markers_size ; i++){// 设置帧 ID和时间戳marker.header.stamp = ros::Time::now();marker.id++;//设置标记位姿。 marker.pose.position.x = (Landmarks[i])(1);marker.pose.position.y = (Landmarks[i])(2);ma.markers.push_back(marker);}Landmarks_pub.publish(ma);
}

2.观测信息显示

这里采用的是线段类型LINE_STRIP进行显示,看代码:

void ukf_console::AssociationPublish(const std::vector< Eigen::Vector3f >& radars)
{// visualization of the data associationvisualization_msgs::MarkerArray ma;visualization_msgs::Marker line_strip;line_strip.header.frame_id = "map";line_strip.header.stamp = ros::Time::now();line_strip.id = 0;line_strip.ns = "data_association";line_strip.type = visualization_msgs::Marker::LINE_STRIP;line_strip.action = visualization_msgs::Marker::ADD;line_strip.lifetime = ros::Duration(0.1);line_strip.pose.position.x = 0;line_strip.pose.position.y = 0;line_strip.pose.position.z = 0;line_strip.pose.orientation.x = 0.0;line_strip.pose.orientation.y = 0.0;line_strip.pose.orientation.z = 0.0;line_strip.pose.orientation.w = 1.0;line_strip.scale.x = marker_scale*0.3;  // line uses only x componentline_strip.scale.y = marker_scale*0.3;line_strip.scale.z = marker_scale*0.3;line_strip.color.a = 1.0;line_strip.color.r = 255/255;line_strip.color.g = 0;line_strip.color.b = 0;// robot pose     geometry_msgs::Point pointRobotPose;pointRobotPose.x = (ukf_tool->Mu_x)(0);pointRobotPose.y = (ukf_tool->Mu_x)(1);pointRobotPose.z = 0;//draw observation linesint visible_num = radars.size();for(int i = 0; i < visible_num; i++) {int index = 0;for ( ; index < ukf_tool->LandmarkINmap.size() ; index++) {if(ukf_tool->LandmarkINmap[index] == (int)(radars[i])(0)) break;}line_strip.points.clear();line_strip.points.push_back(pointRobotPose);geometry_msgs::Point pointLm;pointLm.x = ukf_tool->Mu_x(2 * index + 3);pointLm.y = ukf_tool->Mu_x(2 * index + 4);pointLm.z = 0.0;line_strip.points.push_back(pointLm);ma.markers.push_back(line_strip);line_strip.id++;}   Assoc_pub.publish(ma);
}

3.误差的显示

这里对位姿估计的误差,landmark估计的误差都进行了显示,通常都是采用不确定椭圆进行显示,这里也不例外。首先需要对相应的协方差矩阵求解特征向量和特征值,在tool.cpp文件用下面这个函数实现了这样的功能:

bool tool::computeEllipseOrientationScale2D(Eigen::Matrix2d& eigenvectors, Eigen::Vector2d& eigenvalues, const Eigen::Matrix2d& covariance)
{eigenvectors.setZero(2, 2);eigenvalues.setIdentity(2, 1);Eigen::SelfAdjointEigenSolver<Eigen::Matrix2d> eigensolver(covariance);if (eigensolver.info() == Eigen::Success){eigenvalues = eigensolver.eigenvalues();eigenvectors = eigensolver.eigenvectors();}else{eigenvalues = Eigen::Vector2d::Zero();  // Setting the scale to zero will hide it on the screeneigenvectors = Eigen::Matrix2d::Identity();return false;}// Be sure we have a right-handed orientation systemmakeRightHanded(eigenvectors, eigenvalues);return true;
}

另外,为了确保是右手坐标系(因为ROS系统中的tf坐标系是右手坐标系),这里需要进行一次检查:

void tool::makeRightHanded(Eigen::Matrix2d& eigenvectors, Eigen::Vector2d& eigenvalues)
{Eigen::Vector3d c0;c0.setZero();c0.head(2) = eigenvectors.col(0);c0.normalize();Eigen::Vector3d c1;c1.setZero();c1.head(2) = eigenvectors.col(1);c1.normalize();Eigen::Vector3d cc = c0.cross(c1);if (cc(2) < 0){eigenvectors << c1.head(2), c0.head(2);double e = eigenvalues(0);eigenvalues(0) = eigenvalues(1);eigenvalues(1) = e;}elseeigenvectors << c0.head(2), c1.head(2);
}

然后,就可以用协方差矩阵进行不确定性椭圆的绘制了,看代码:

void ukf_console::UncertaintyEllipseShow()
{visualization_msgs::MarkerArray ma;visualization_msgs::Marker marker;marker.header.frame_id = "map";marker.id = 0;marker.ns = "ekf_predict";marker.type = visualization_msgs::Marker::SPHERE;marker.action = visualization_msgs::Marker::ADD;marker.lifetime = ros::Duration(0.5);marker.pose.position.z = 0.1;marker.color.r = 135/255.0;marker.color.g = 206/255.0;marker.color.b = 235/255.0;marker.color.a = 0.8;//设置标记颜色,确保alpha(不透明度)值为0marker.scale.x = marker_scale;marker.scale.y = marker_scale;marker.scale.z = marker_scale;// covariance ellipsestf::Quaternion orientation;tf::Matrix3x3 tf3d;Eigen::Matrix2d eigenvectors;Eigen::Vector2d eigenvalues;Eigen::Matrix2d covariance;// get the covariance matrix 2x2 for each ellipsoid including robot pose// Count the number of landmarks + robot poseunsigned int objs_counter = ((ukf_tool->Mu_x.rows())-3 )/2 + 1;unsigned int pose_id=0;double quantiles=1 ;double ellipse_scale_=5;for (size_t i = 0; i < objs_counter; i++){marker.id++;if( i != 0)pose_id = i * 2 + 1;covariance=ukf_tool->Sigma_y.block(pose_id, pose_id, 2, 2);if(tool::computeEllipseOrientationScale2D(eigenvectors, eigenvalues, covariance) == false)continue;// Rotation matrix around  z axistf3d.setValue(eigenvectors(0, 0), eigenvectors(0, 1), 0, eigenvectors(1, 0), eigenvectors(1, 1), 0, 0, 0, 1);// get orientation from rotation matrixtf3d.getRotation(orientation);marker.pose.position.x = (ukf_tool->Mu_x)(pose_id);marker.pose.position.y = (ukf_tool->Mu_x)(pose_id+1);marker.pose.position.z = 0;marker.pose.orientation.x = orientation.x();marker.pose.orientation.y = orientation.y();marker.pose.orientation.z = orientation.z();marker.pose.orientation.w = orientation.w();marker.scale.x = ellipse_scale_ * quantiles * sqrt(eigenvalues(0));marker.scale.y = ellipse_scale_ * quantiles * sqrt(eigenvalues(1));marker.scale.z = 0.00001;  // Z can't be 0, limitation of ROSma.markers.push_back(marker);}Uncertainty_pub.publish(ma);
}

三、主循环

在结束各项准备工作之后,就可以进入主循环了,主循环其实也就是两个工作,预测和更新,非常简单:

void ukf_console::run()
{//read the map data for all landmarksgetLandmarks(Worlddata_filepath);//read the measurements with odometry and radar datagetMeasurements(Sensordata_filepath);LandmarksPublish();ukf_tool = new unscented_kf(Landmarks.size());ros::Rate rat(10);for (const auto& record : Measurements) { if (!ros::ok())   break;ukf_tool->ProcessMeasurement(record);EstimatePosePublish((ukf_tool->Mu_x).head(3));AssociationPublish(record.radar);UncertaintyEllipseShow();rat.sleep();}}

大致思路就是,将观测数据逐条送入ukf系统,进行ukf估计,观测结束,也就预示着程序结束了。

下一篇将会重点说说ukf的估计过程,这里就告一段落了。

从程序中学习UKF-SLAM(一)相关推荐

  1. java 枚举学习--从小程序中学习

    java 枚举学习--从小程序中学习 Java 枚举类型 解析 简介:java中枚举是一个类 用之前我觉得还是要知道应该何时使用: 一条普遍的规律是,任何使用常量的地方,例如目前使用的switch 代 ...

  2. 从程序中学习EKF-SLAM(一)

    在一次课程的结课作业上,作业要求复写一个EKF-SLAM系统,我从中学到了好多知识.作为一个典型轻量级slam系统,这个小项目应该特别适合于slam系统入门,可以了解到经典卡尔曼滤波器在slam系统中 ...

  3. 从程序中学习UKF-SLAM(二)

    接下来具体说说UKF中的状态预测部分,将会从原理和代码两个方面来进行说明. 四.状态预测 1.UKF概述 概括起来说,就是指解决一个高斯分布经过非线性变换后,怎么用另一个高斯分布近似它.假设x=N(μ ...

  4. 从程序中学习EKF-SLAM(二)

    三.ekf预测之前 1.离线数据初始化 进入主循环的第一个部分,就是保存当前状态下的数据.这里顺带提一下系统里的数据格式.首先看数据离线化保存的方法: STATE temp; OffLine_data ...

  5. 在MVC 6 .NET核心应用程序中添加种子数据

    目录 目标 介绍 使用的组件 创建项目 DbInitializer类 结论 目标 就像MVC 5一样,我试图启用自动迁移和Seeding默认数据,但似乎MVC 6中的行为已经改变,所以我想分享这个提示 ...

  6. 创建react应用程序_通过创建食谱应用程序来学习在React中使用API

    创建react应用程序 Learn how to use external APIs with React and React Router in a full tutorial from Hamza ...

  7. 六一:如何在Datawhale开源学习小程序中管

    我们的组队学习马上就要开营了,本次组队学习与以往不同的是小程序中增加了队伍管理的功能. 为了方便大家组队,Datawhale的 六一同学 为大家准备了在Datawhale开源学习小程序中队伍管理的教程 ...

  8. 如何在Datawhale开源学习小程序中创建队伍?

    我们的组队学习马上就要开营了,本次组队学习与以往不同的是小程序中增加了组队的功能.为了方便大家组队,Datawhale的 六一同学 为大家准备了在Datawhale开源学习小程序中创建队伍的教程. S ...

  9. 综述|深度学习在SLAM定位与建图中的应用(近250篇参考文献)

    作者丨Jasper@知乎 编辑丨Realcat  计算机视觉SLAM " 近年来深度学习被广泛应用于定位与建图中.相比于传统的手动建模方法,深度学习提供了一种数据驱动的解决方案,并逐步发展成 ...

最新文章

  1. Android动画之帧动画和补间动画
  2. php padright,[扩展推荐] PHP 字符串操作类 Twine
  3. python中import与input_python : import详解。
  4. QT的QScriptValue类的使用
  5. 量子计算机理论基础,所谓量子计算机,是指建立在量子力学理论基础上的计算机...
  6. 测试综合技能的期末预测
  7. 大数据之Spark集群安装及简单使用
  8. 初识Node.js之Node.js与java作为后台服务器的对比
  9. 这样的家居选购界面让你忍不住剁手的冲动!
  10. python下载文件并改名_Python遍历文件夹并批量改名
  11. 最全数据指标体系集合!覆盖9个行业4个业务场景,全是干货
  12. linux mint下安装vnc,VNC远程连接Linux mint桌面
  13. SpringBoot, 启动类,使用「SpringBootApplication」标注
  14. 浅析ISO三体系标准认证对企业的作用
  15. 软件工程师成长为架构师必备的十项技能
  16. WDF框架系列:同步域,运行级别
  17. 微信小程序的所有scene场景值 2020-10-21
  18. 常用的正则字母大小写转换
  19. android dialog遮挡键盘,彻底解决软键盘遮挡DialogFragment
  20. 嵌入式 Linux平台 C程序 交叉编译技术

热门文章

  1. echarts旭日图
  2. git push如何强制提交
  3. 蓝牙耳机音质真的不好吗?初级发烧友应该选择什么样式蓝牙耳机
  4. TIM定时器_CNT_ARR_PSC_CRR
  5. android棉花糖,Android-6.0 棉花糖权限的那点事
  6. win10安装win7虚拟机记录
  7. WhbtomT(半路出家) 的每日英语 收集 (四)
  8. c语言自动售货机实验报告,c语言自动售货机实验报告
  9. #程序员的办公桌面是怎么样的?网友晒的真是逼格满满啊
  10. uni-app 开发App 口令弹窗