参考引用
- GMapping ROS-Wiki
- 从零开始搭二维激光SLAM
- GMapping 漫谈
- 小白学移动机器人
- 机器人工匠阿杰
- wpr_simulation
移动机器人激光SLAM导航(文章链接汇总)
概率论相关公式
- 1. 条件概率公式: p ( x , y ) = p ( x ∣ y ) p ( y ) = p ( y ∣ x ) p ( x ) p(x,y)=p(x|y)p(y)=p(y|x)p(x) p(x,y)=p(x∣y)p(y)=p(y∣x)p(x)
- 2. 贝叶斯公式: p ( x ∣ y ) = p ( y ∣ x ) p ( x ) p ( y ) = η p ( y ∣ x ) p ( x ) , η 为归一化常数 p(x|y)=\frac{p(y|x)p(x)}{p(y)}=\eta p(y|x)p(x), \eta为归一化常数 p(x∣y)=p(y)p(y∣x)p(x)=ηp(y∣x)p(x),η为归一化常数
- 3. 条件贝叶斯公式: p ( x ∣ y , z ) = p ( y ∣ x , z ) p ( x ∣ z ) p ( y ∣ z ) = η p ( y ∣ x , z ) p ( x ∣ z ) p(x|y,z)=\frac{p(y|x,z)p(x|z)}{p(y|z)}=\eta p(y|x,z)p(x|z) p(x∣y,z)=p(y∣z)p(y∣x,z)p(x∣z)=ηp(y∣x,z)p(x∣z)
- 4. 条件联合概率公式: p ( x , y ∣ z ) = p ( x ∣ y , z ) p ( y ∣ z ) p(x,y|z)=p(x|y,z)p(y|z) p(x,y∣z)=p(x∣y,z)p(y∣z)
p ( x 1 : t , m ∣ z 1 : t , u 1 : t − 1 ) = p ( m ∣ x 1 : t , z 1 : t ) ⋅ p ( x 1 : t ∣ z 1 : t , u 1 : t − 1 ) . \begin{matrix}p(x_{1:t},m\mid z_{1:t},u_{1:t-1})=\\p(m\mid x_{1:t},z_{1:t})\cdot p(x_{1:t}\mid z_{1:t},u_{1:t-1}).\end{matrix} p(x1:t,m∣z1:t,u1:t−1)=p(m∣x1:t,z1:t)⋅p(x1:t∣z1:t,u1:t−1).
问题:建图对机器人位姿要求较高,对任意一个粒子,仅仅依靠运动模型采样(里程计)的结果构建地图,误差会非常大,通过运动模型得到的提议分布(proposal)太过于分散,与真实分布相差过大,则需要较多的粒子才能更好的表示机器人位姿的估计,这样将会造成内存使用与计算量急剧上升
方案 1:采用极大似然估计
方案 2:激光雷达的观测模型
总结如下
问题:GMapping 采用粒子滤波算法对移动机器人轨迹进行估计,必然少不了粒子重采样过程,但随着重采样次数的增多,会出现所有粒子都从一个粒子复制而来,粒子的多样性会消失
N e f f = 1 ∑ i = 1 N ( w ~ ( i ) ) 2 N_{\mathrm{eff}}=\frac1{\sum_{i=1}^N\left(\tilde{w}^{(i)}\right)^2} Neff=∑i=1N(w~(i))21
对于 200 x 200 米的范围,如果栅格分辨率是 5cm,每个栅格占用一个字节内存,那么每个粒子携带的地图都要 16M 的内存,如果是 100 粒子就是 1.6G 内存
有关 TF 基础请查看往期博客:ROS学习5:ROS常用组件
必要的 TF 变换
发布的 TF 变换
本小节使用 移动机器人激光SLAM导航(二):运动控制与传感器篇 中安装的测试环境 wpr_simulation(GMapping 已在此环境中通过脚本安装)
<launch>
<include file="$(find wpr_simulation)/launch/wpb_stage_robocup.launch" />
<node pkg="gmapping" type="slam_gmapping" name="slam_gmapping" />
<node pkg="rviz" type="rviz" name="rviz" args="-d $(find slam_pkg)/rviz/slam.rviz" />
<node pkg="wpr_simulation" type="keyboard_vel_ctrl" name="keyboard_vel_ctrl" />
launch>
$ cd ~/wpr_ws
$ catkin_make
$ source devel/setup.bash
$ roslaunch slam_pkg gmapping.launch
#include
#include
#include
#include
static float linear_vel = 0.1;
static float angular_vel = 0.1;
static int k_vel = 3;
int GetCh() {
static struct termios oldt, newt;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
int c = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return c;
}
int main(int argc, char** argv) {
ros::init(argc, argv, "keyboard_vel_cmd");
printf("键盘控制 WPR 机器人: \n");
printf("w - 向前加速 \n");
printf("s - 向后加速 \n");
printf("a - 向左加速 \n");
printf("d - 向右加速 \n");
printf("q - 左旋加速 \n");
printf("e - 右旋加速 \n");
printf("空格 - 刹车 \n");
printf("x - 退出 \n");
printf("------------- \n");
ros::NodeHandle n;
ros::Publisher cmd_vel_pub = n.advertise<geometry_msgs::Twist>("/cmd_vel", 10);
geometry_msgs::Twist base_cmd;
base_cmd.linear.x = 0;
base_cmd.linear.y = 0;
base_cmd.angular.z = 0;
while(n.ok()) {
int cKey = GetCh();
if (cKey == 'w') {
base_cmd.linear.x += linear_vel;
if (base_cmd.linear.x > linear_vel*k_vel)
base_cmd.linear.x = linear_vel*k_vel;
cmd_vel_pub.publish(base_cmd);
printf(" - linear.x= %.2f linear.y= %.2f angular.z= %.2f \n",base_cmd.linear.x,base_cmd.linear.y,base_cmd.angular.z);
} else if (cKey == 's') {
base_cmd.linear.x += -linear_vel;
if (base_cmd.linear.x < -linear_vel*k_vel)
base_cmd.linear.x = -linear_vel*k_vel;
cmd_vel_pub.publish(base_cmd);
printf(" - linear.x= %.2f linear.y= %.2f angular.z= %.2f \n",base_cmd.linear.x,base_cmd.linear.y,base_cmd.angular.z);
} else if (cKey == 'a') {
base_cmd.linear.y += linear_vel;
if (base_cmd.linear.y > linear_vel*k_vel)
base_cmd.linear.y = linear_vel*k_vel;
cmd_vel_pub.publish(base_cmd);
printf(" - linear.x= %.2f linear.y= %.2f angular.z= %.2f \n",base_cmd.linear.x,base_cmd.linear.y,base_cmd.angular.z);
} else if (cKey == 'd') {
base_cmd.linear.y += -linear_vel;
if (base_cmd.linear.y < -linear_vel*k_vel)
base_cmd.linear.y = -linear_vel*k_vel;
cmd_vel_pub.publish(base_cmd);
printf(" - linear.x= %.2f linear.y= %.2f angular.z= %.2f \n",base_cmd.linear.x,base_cmd.linear.y,base_cmd.angular.z);
} else if (cKey == 'q') {
base_cmd.angular.z += angular_vel;
if (base_cmd.angular.z > angular_vel*k_vel)
base_cmd.angular.z = angular_vel*k_vel;
cmd_vel_pub.publish(base_cmd);
printf(" - linear.x= %.2f linear.y= %.2f angular.z= %.2f \n",base_cmd.linear.x,base_cmd.linear.y,base_cmd.angular.z);
} else if (cKey == 'e') {
base_cmd.angular.z += -angular_vel;
if (base_cmd.angular.z < -angular_vel*k_vel)
base_cmd.angular.z = -angular_vel*k_vel;
cmd_vel_pub.publish(base_cmd);
printf(" - linear.x= %.2f linear.y= %.2f angular.z= %.2f \n",base_cmd.linear.x,base_cmd.linear.y,base_cmd.angular.z);
} else if (cKey == ' ') {
base_cmd.linear.x = 0;
base_cmd.linear.y = 0;
base_cmd.angular.z = 0;
cmd_vel_pub.publish(base_cmd);
printf(" - linear.x= %.2f linear.y= %.2f angular.z= %.2f \n",base_cmd.linear.x,base_cmd.linear.y,base_cmd.angular.z);
} else if (cKey == 'x') {
base_cmd.linear.x = 0;
base_cmd.linear.y = 0;
base_cmd.angular.z = 0;
cmd_vel_pub.publish(base_cmd);
printf(" - linear.x= %.2f linear.y= %.2f angular.z= %.2f \n",base_cmd.linear.x,base_cmd.linear.y,base_cmd.angular.z);
printf("退出! \n");
return 0;
} else {
printf(" - 未定义指令 \n");
}
}
return 0;
}
<launch>
<include file="$(find wpr_simulation)/launch/wpb_stage_robocup.launch" />
<node pkg="gmapping" type="slam_gmapping" name="slam_gmapping">
<param name="maxUrange" value="3.0" />
<param name="map_update_interval" value="0.5" />
<param name="linearUpdate" value="0.1" />
node>
<node pkg="rviz" type="rviz" name="rviz" args="-d $(find slam_pkg)/rviz/slam.rviz" />
<node pkg="wpr_simulation" type="keyboard_vel_ctrl" name="keyboard_vel_ctrl" />
launch>
使用 map_server 软件包实现占据栅格地图的保存与加载
GMapping 建图
$ cd ~/wpr_ws
$ source devel/setup.bash
$ roslaunch slam_pkg gmapping.launch
保存地图
$ rosrun map_server map_saver -f mymap
通过上述保存地图指令会在 /home 目录生成 mymap.pgm(地图图片)和 mymap.yaml(地图信息) 两个文件
mymap.yaml
image: mymap.pgm # 到包含占用数据的图像文件的路径;可以是绝对的,也可以是相对于 YAML 文件的位置
resolution: 0.050000 # 地图分辨率,单位:米/像素
origin: [-100.000000, -100.000000, 0.000000] # 地图中左下方像素的二维姿态为 (x, y, 偏航),偏航为逆时针旋转(偏航 = 0表示无转动)
negate: 0 # 白/黑的空闲/被占用 语义是否应该颠倒 (阈值的解释不受影响)
occupied_thresh: 0.65 # 占据概率大于该阈值的像素被认为是完全占据的
free_thresh: 0.196 # 占有率小于这个阈值的像素被认为是完全自由的
$ roscore
$ cd ~
$ rosrun map_server map_server mymap.yaml
$ rosrun rviz rviz