智能机器人(57):融合调试

robot-pose-ekf的配置参数

freq:
滤波器的输出频率。注意, 高的频率只是把合成odom输出的更频繁,并不会增加姿态评估的精度。

sensor_timeout:
等待某一路传感器消息的最大时间, 超过这个时间如果vo或者imu的传感器还没有新的消息到达,则滤波器不再等待。

ekf滤波器不要去所有sensor一直的同步在线, 缺一些没关系。例如t0时刻滤波器更新输出, t1时刻来一路odom数据, t2时刻来一路imu数据, 则滤波器向内差值产生t0~t1的imu数据, 最终输出姿态评估。
( https://chidambaramsethu.wordpress.com/2013/07/15/a-beginners-guide-to-the-the-ros-robot_pose_ekf-package/)

output_frame:
wiki的示例是有问题的、至少是不清楚的:
示例默认把”output_frame” 设为”odom”容易混淆, 这个坐标系最好命名为”odom_combined”并不会和输出topic: “odom_combined” 混淆。

源代码更清楚:
(http://docs.ros.org/kinetic/api/robot_pose_ekf/html/odom__estimation__node_8cpp_source.html)
76 // paramters
77 nh_private.param(“output_frame”, output_frame_, std::string(“odom_combined”));
78 nh_private.param(“base_footprint_frame”, base_footprint_frame_, std::string(“base_footprint”));
79 nh_private.param(“sensor_timeout”, timeout_, 1.0);
80 nh_private.param(“odom_used”, odom_used_, true);
81 nh_private.param(“imu_used”, imu_used_, true);
82 nh_private.param(“vo_used”, vo_used_, true);
83 nh_private.param(“gps_used”, gps_used_, false);
84 nh_private.param(“debug”, debug_, false);
85 nh_private.param(“self_diagnose”, self_diagnose_, false);
86 double freq;
87 nh_private.param(“freq”, freq, 30.0);

104 pose_pub_ = nh_private.advertise(“odom_combined”, 10);

434 odom_broadcaster_.sendTransform(StampedTransform(tmp, tmp.stamp_, output_frame_, base_footprint_frame_));

注意: obot_pose_ekf 的 “output_frame”对应 amcl的”odom_frame_id”。

智能机器人(55):数据融合

6. 数据融合 – EKF – robot_pose_ekf

此保融合odom/imu/ov等数据获得更精确的位姿信息。
并不要求各路传感器数据连续提供,某路例如imu可以的数据可以中断,不影响算法进行。
REF:
https://answers.ros.org/question/235228/how-is-the-orientation-of-frame-odom-initialized/

Subs:
* odom (nav_msgs/Odometry)
position and orientation of robot in ground plane
* imu_data (sensor_msgs/Imu)
RPY angles of robot base frame relative to a world reference frame.
R和P为绝对角度因为有重力 而Y为相对角度。
* vo (nav_msgs/Odometry)
the full position and orientation of robot
如果某传感器提供3D数据而只用到2D部分,则对不用部分赋值较大的协方差即可。

Pubs:
* robot_pose_ekf/odom_combined (geometry_msgs/PoseWithCovarianceStamped)
The output of the filter (the estimated 3D robot pose).

TFs:
odom_combined -> base_footprint
发布一个名为odom_combined的新的坐标系,
并发布一个 odom_combined->base_footprint的TF变换。

Launchs:

不确定度方面的几个实践问题

6.1 协方差这个参数的来源问题

ERROR: Covariance specified for measurement on topic xxx is zero
A:
Each measurement that is processed by the robot pose ekf ,needs to have a covariance associated with it.
The diagonal elements of the covariance matrix , can not be zero.
When one of the diagonal elements is zero, this error is shown.
Messages with an invalid covariance, will not be used to update the filter.
So, you should reating the covariance matrices for individual sensors:
*1. For IMU and Odometry, the covariance matrix can be formed from the datasheet.
*2. For Visual Odometry, covariance matrix may be obtained from the measurement equation that relates the measured variables to the pose coordinates.

6.2 IMU的协方差

if (imu_covariance_(1,1) == 0.0){
SymmetricMatrix measNoiseImu_Cov(3); measNoiseImu_Cov = 0;
measNoiseImu_Cov(1,1) = pow(0.00017,2); // = 0.01 degrees / sec
measNoiseImu_Cov(2,2) = pow(0.00017,2); // = 0.01 degrees / sec
measNoiseImu_Cov(3,3) = pow(0.00017,2); // = 0.01 degrees / sec
imu_covariance_ = measNoiseImu_Cov;
}

for example 0.00017 means 0101deg/sec, this is so good imu, increase them if you get poor odometry.
For optimal values, perform an odometry calibration alogirthm such as UMBMark.

IMU is:
self.imu_msg.orientation_covariance = [-1, 0, 0,
0, -1, 0,
0, 0, -1 ] #sensor doesn’t have orientation

源代码是这样检查的
$ vi robot_pose_ekf/src/odom_estimation.cpp
void OdomEstimation::addMeasurement(const StampedTransform& meas, const MatrixWrapper::SymmetricMatrix& covar)
{
// check covariance
for (unsigned int i=0; i<covar.rows(); i++){=”” if=”” (covar(i+1,i+1)=”=” 0){=”” ros_error(“covariance=”” specified=”” for=”” measurement=”” on=”” topic=”” %s=”” is=”” zero”,=”” meas.child_frame_id_.c_str());=”” return;=”” }=”” add=”” measurements=”” addmeasurement(meas);=”” (meas.child_frame_id_=”=” “wheelodom”)=”” odom_covariance_=”covar;” else=”” “imu”)=”” imu_covariance_=”covar;” “vo”)=”” vo_covariance_=”covar;” ros_error(“adding=”” a=”” an=”” unknown=”” sensor=”” %s”,=”” };=”” 6.3=”” odom的协方差=”” the=”” odometry=”” will=”” be=”” updated,=”” but=”” covariance=”” always=”” published=”” as=”” 0.=”” controller=”” don’t=”” include=”” information.=”” you=”” need=”” to=”” have=”” value=”” included=”” in=”” information=”” some=”” reason,=”” can=”” create=”” your=”” own=”” based=”” rosaria=”” and=”” value.=”” odom=”” 6×6=”” matrix,=”” because=”” 6=”” dof,=”” position=”” (x,=”” y,=”” z)=”” orientation=”” so=”” float[36]=”” matrix.=”” diagonal=”” terms=”” are=”” trust=”” each=”” dof.=”” estimate=”” or=”” algorithm=”” accuracy=”” with=”” experiment.=”” see=”” data=”” good=”” 1cm=”” translation=”” 0.1=”” radian=”” rotation=”” use=”” this=”” matrix:=”” [0.01=”” 0.0=”” 0.0,=”” 0.01=”” 0.1]=”” no=”” one=”” dof=”” put=”” huge=”” example=”” segway_rmp:=”” this-=””>odom_msg.pose.covariance[0] = 0.00001;
this->odom_msg.pose.covariance[7] = 0.00001;
this->odom_msg.pose.covariance[14] = 1000000000000.0;
this->odom_msg.pose.covariance[21] = 1000000000000.0;
this->odom_msg.pose.covariance[28] = 1000000000000.0;
this->odom_msg.pose.covariance[35] = 0.001;
above means, position z is not sure, ori of x and y is no tsure.</covar.rows();>

covariance
nav_msgs::Odometry odom;
odom.header.stamp = current_time;
odom.header.frame_id = “odom”;
//set the position
odom.pose.pose.position.x = od->position.positionx;
odom.pose.pose.position.y = od->position.positiony;
odom.pose.pose.position.z = 0.0;
odom.pose.pose.orientation = odom_quat;
//set the velocity
odom.child_frame_id = “base_link”;
odom.twist.twist.linear.x = od->speed.speedx;
odom.twist.twist.linear.y = od->speed.speedy;
odom.twist.twist.angular.z = od->speed.speedr;

// set stddev
odom.pose.covariance[0] = pos_x_stddev;
odom.pose.covariance[7] = pos_y_stddev;
odom.pose.covariance[14] = pos_z_stddev;
odom.pose.covariance[21] = rot_x_stddev;
odom.pose.covariance[28] = rot_y_stddev;
odom.pose.covariance[35] = rot_z_stddev;

double pos_x_stddev
double pos_x_stddev
double pos_z_stddev
double rot_x_stddev
double rot_y_stddev
double rot_z_stddev

ros::NodeHandle private_node_handle(“~”);
private_node_handle.param(“pos_x_stddev”, pos_x_stddev, 0.11);
private_node_handle.param(“pos_y_stddev”, pos_y_stddev, 0.12);
private_node_handle.param(“pos_z_stddev”, pos_z_stddev, 1000000000000.0);
private_node_handle.param(“rot_x_stddev”, rot_x_stddev, 1000000000000.0);
private_node_handle.param(“rot_y_stddev”, rot_y_stddev, 1000000000000.0);
private_node_handle.param(“ros_z_stddev”, ros_z_stddev, 0.15);

REF:
Covariance matrices with a practical example

智能机器人(53):定位调试

Q:
性能的平衡
A:
各sensor发布topic, amcl和robot_pose_ekf模块发布tf, 这些frequency需要和cpu性能进行trade-off, 搞折衷做平衡。

top查看系统性能,’1′ 查看多核的具体cpu, 节后’b’ ‘x’ ‘y’等指令, 并要关注average load指标:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
14706 ubuntu 20 0 296736 212296 5564 R 71.5 11.0 1:51.56 amcl
14245 ubuntu 20 0 79408 5592 4792 S 47.0 0.3 2:30.72 laser_node
14250 ubuntu 20 0 105876 5348 4616 S 3.0 0.3 0:09.73 lpms_imu_node
23344 ubuntu 20 0 91032 6960 6140 S 4.0 0.4 0:00.77 robot_pose_ekf
这个就好了。

如果配置不切实际, 类似arm板配置20Hz的transform frequency, 则资源受限出错:
$ roswtf
— —
ERROR: receiving transform from [/amcl] that differed from ROS time by 2.96s
这个时间,设置的越快会,大的越夸张。

此外还要关注内存情况:
$ free -m
— —

也要关注磁盘介质:
$ df -h

$ du -h –max-depth=1

Q:
amcl的node会die掉

$ rosnode ping amcl
— —
ERROR: connection refused to [http://192.168.0.40:38282/]

查看 ~/.ros/log/latest/… …/amcl-1.log:
amcl: /tmp/buildd/ros-indigo-amcl-1.12.4-0trusty-20150604-0435/src/amcl/pf/pf_kdtree.c:378: pf_kdtree_cluster: Assertion `node == pf_kdtree_find_node(self, self->root, node->key)’ failed.
Aborted (core dumped)

A:
原因是激光雷达被不恰当遮挡, pf程序没有足够健壮导致的。

Q:
ERROR: receiving transform from [/robot_state_publisher] that differed from ROS time by 12.34s
A:
如果是多机系统:
可能是没有进行时间同步。
$ install chrony
$ sudo ntpdate 192.168.1.1 <--- 如果是单机系统系统, 而且时间相差很大: 可能是/use_sime_time要为false。 如果是单机系统系统, 时间相差细微: 第一感就是降低某些重载模块的频率, 50Hz ---> 20Hz—>5Hz。
或者换个PC。

Q:
WARNING The following node subscriptions are unconnected:
* /amcl:
* /tf_static
* /robot_pose_ekf:
* /tf_static
A:
roswtf发出的警告,说amcl节点订阅了tf_static这个topic,但是却没有其他节点发布内容的topic。
这个警告正是所期望看到的:
因为我们没有使用tf_static来发布模型urdf,所以可以忽略这些警告。

智能机器人(51):建图和定位

3. 建图功能 – gmapping

$ sudo apt-get install ros-indigo-gmapping
$ rosrun gmapping slam_gmapping scan:=scan

gmapping实现基于激光的SLAM, in fact usinf OpenSlam的Gmapping。
make a 名为slam_gmapping的node -> 创建2D的occupancy grid map。

Subs:
* scan (sensor_msgs/LaserScan)

Pubs:
* map (nav_msgs/OccupancyGrid)
Get the map data from this topic, which is latched, and updated periodically
Services:
* dynamic_map (nav_msgs/GetMap)
Call this service to get the map data

TF required:
* odom -> base_link
usually provided by the odometry system, e.g., the driver for the mobile base
* base_link -> laser_link (base_laser)
usually broadcast periodically by robot_state_publisher, or from a tf static_transform_publisher.

TF provided:
* map -> odom
the current estimate of the robot’s pose in the map frame
既然是建图,肯定给出了odom在map中的位置,也就是此处的map->odom的tf变换。

4. 地图服务器 – map_server

$ sudo apt-get install ros-indigo-map-server

map_server包提供一个服务map_server和一个应用map_saver:
* map_server , a ROS Node, which offers map data as a ROS Service.
* map_saver , a command-line utility, which allows dynamically generated maps to be saved to file.
前者map_server提供地图服务,例如导航时。
后者map_saver用于保存地图,例如建图时。

4.1 在导航或者定位场景,加载地图,可以在c或python的node。
$ roslaunch funbot r_mapserver.launch
$ vi r_mapserver.launch
$ rosrun map_server map_server /home/funbot/map/map2017.yaml

Pubs:
* map (nav_msgs/OccupancyGrid)
you can receive the map via this latched topic.

Services:
* static_map (nav_msgs/GetMap)
Retrieve the map via this service.

4.2 在mapping场景,可以随时保存地图。
这个通过command-line或者在code里面system()。
$ rosrun map_server map_saver -f /home/funstep/map/map2017 y
$ rosrun map_server map_saver -f _map_file:=/home/funstep/map/map.yaml x

Subs:
* map (nav_msgs/OccupancyGrid)
Map will be retrieved via this latched topic.

5. 定位功能 – amcl

$ sudo apt-get install ros-indigo-amcl
rosrun amcl amcl scan:=scan

amcl是2D的基于概率的定位的一个包。

Subs:
* map (nav_msgs/OccupancyGrid)
If amcl parameter “use_map_topic=true” ,then AMCL subscribes this topic to retrieve map,
otherwise using service call.
* scan (sensor_msgs/LaserScan)
Laser scans
* initialpose (geometry_msgs/PoseWithCovarianceStamped)
Mean and covariance with which to (re-)initialize the particle filter

Pubs:
* amcl_pose (geometry_msgs/PoseWithCovarianceStamped)
Robot’s estimated pose in the map with covariance.

TF required:
* tf (tf/tfMessage)
提供odom -> base_link

TF supported:
* tf (tf/tfMessage)
发布map -> odom(间接的机器人在地图的位置)
注意:
不能有另外的map->odom在同时发布,例如在跑mapping.
实际上,mapping和amcl同时跑的需求不存在。
如果有,也使用hector_slam.

Paras:
三种类型的参数用来配置AMCL节点:filter整体的,laser模型的,odom模型。例如
Odometry model parameters:
* ~tf_broadcast (bool, default: true)
Set to false to prevent amcl from publishing the transform map -> odom.
可以关闭上述map->odom的tf。

注意:
*1. initialpose这个topic的存在,主要方便于在amcl运行时对姿态进行复位。
*2. initial_pose这个topic上如果发布赋值,实际就是初始化一个高斯分布。这个分布的协方差越大,说明粒子在地图上撒开的面积越大,越难定位。
*3. 不 对initial_pose直接赋值,而通过操作amcl的parameter参数,可以同样效果。
或者通过呼叫global_localization service,也可以达到把颗粒均匀洒在地图的目的。
4. 如果不赋值,则位置是默认地图的(0.0.0,0,0,0),但是默认的协方差很小,也就是说默认为确认给出的位置信息相当的准确。这个需要注意。