AMCL中odom的数据处理

最近在看AMCL的程序,想知道launch文件中关于odom的那些配置参数是怎么运行的,看完后做点笔记。

 

1.AMCL的launch文件关于odom的参数配置列表(以下讨论的都是差速模型):

odom参数列表
参数 默认值 描述
odom_model_type diff 里程计运动模型,种类有diff、ommi等
odom_alpha1 0.2 机器人旋转分量中的旋转噪音
odom_alpha2 0.2 机器人平移分量中的旋转噪音
odom_alpha3 0.2 机器人平移分量中的平移噪音
odom_alpha4 0.2 机器人旋转分量中的平移噪音
odom_frame_id odom 里程计用于哪个坐标系
base_frame_id base_link 机器人底盘坐标系
global_frame_id map 定位系统的参考坐标系
tf_broadcast true 是否发布map和odom坐标系之间的转换

 

2.里程计模型

假如对AMCL算法不清楚第一眼看过去大概就会有点懵。然后就会去翻AMCL的算法原理,一看发现原来在《概率机器人》中文版一书上讲过,但书上没有特别直接,所以还是得看程序具体怎么写的。但程序虽然是最直接的,为了理解程序最好还是要从算法原理上有一点基础的认识,所以这一小节简单概括一下里程计模型以及其采样算法,算法伪代码可以参考《概率机器人》书上103页。

里程计diff模型,也就是最常见的差速模型,两平行轮作驱动,另外附加一个万向轮作支撑。在AMCL算法中将机器人的每一步的广义运动进行了拆解,拆解成包含三个动作的序列,即:a.在起始点旋转,转向终止点的方向;b.沿着该方向做直线运动到终止点;c.在终止点进行旋转,转到目标方向,其运动示意图如下所示。学过机器人学大的会问,平面上的刚体运动可以拆分为一个旋转和一个平移,为什么这个要做3次运动。这个忽略了一个大前提,就是平面上的刚体有3个自由度:x、y、yaw,但是差速模型机器人虽然可以抵达平面上的任意位置和姿态,但是并不具备3个自由度的概念。想一下,差速模型的机器人只能直行和转弯,并不能沿着驱动轮的轴线进行运动吧?而且这用做的一大好处是可以提取相对距离便于定位校核的。

里程记测距模型:运动拆分

其中,\delta _{rot1}代表在起始点的旋转,\delta _{trans}代表着第二段平移过程,\delta _{rot2}代表着在终止点的旋转。

 

3.里程计噪音

要讨论里程计噪音,首先来讨论一下怎么样里程记没有噪声,也就是说,里程计读轮子的转圈和机器人的位移是精确对应的。当然理论上和仿真中可以做到:首先假设轮子和地面之间不打滑、地面绝对平整且光滑、轮子绝对圆润且不变形、里程计和机器人轮轴以及机器人轮子之间没有摩擦和迟滞可以做到完全时间同步...等等。当然现实中做不到,所以在差速模型的里程记计算中需要对机器人进行噪音估计,因为我们读到的直很可能不是真值。

在AMCL中,里程计是作为状态预测器存在的,通过接受当前的控制信号,从上一帧机器人状态对这一帧机器人状态进行预测,并与当前观测所得结果对当前预测进行加权打分。所以通过输入和输出我们知道,里程计在AMCL中的作用就是根据当前控制信号更新上一帧的能表征机器人状态的粒子集合。在AMCL中关于odom的伪代码如下所示:

AMCL算法中里程计预测模型

 

4.AMCL代码分析

在AMCL中,关于里程计在算法中做机器人位姿估计更新的代码,主要在amcl_odom.cpp中。主要执行函数是:bool AMCLOdom::UpdateAction(pf_t *pf, AMCLSensorData *data),这个函数输入是机器人上一帧位姿粒子状态pf和当前传感器信息data,程序运行完成就返回bool类型的true。

首先它利用switch...case...方法辨别里程计的模型,

然后,当model_type的值是 ODOM_MODEL_DIFF类型时,进行相应的机器人位姿更新:

case ODOM_MODEL_DIFF:
  {
    // Implement sample_motion_odometry (Prob Rob p 136)
    double delta_rot1, delta_trans, delta_rot2;
    double delta_rot1_hat, delta_trans_hat, delta_rot2_hat;
    double delta_rot1_noise, delta_rot2_noise;

    // Avoid computing a bearing from two poses that are extremely near each
    // other (happens on in-place rotation).
    if(sqrt(ndata->delta.v[1]*ndata->delta.v[1] + 
            ndata->delta.v[0]*ndata->delta.v[0]) < 0.01)
      delta_rot1 = 0.0;
    else
      delta_rot1 = angle_diff(atan2(ndata->delta.v[1], ndata->delta.v[0]),
                              old_pose.v[2]);
    delta_trans = sqrt(ndata->delta.v[0]*ndata->delta.v[0] +
 
#include <ros/ros.h> #include <nav_msgs/Odometry.h> #include <geometry_msgs/PoseStamped.h> #include <tf2_ros/transform_broadcaster.h> #include <tf2_geometry_msgs/tf2_geometry_msgs.h> class OdometryProcessor { public: OdometryProcessor() : nh_("~") { // 初始化发布器和订阅器 odom_pub_ = nh_.advertise<nav_msgs::Odometry>("odom", 10); amcl_sub_ = nh_.subscribe("amcl_pose", 1, &OdometryProcessor::amclCallback, this); // 初始化TF广播器 tf_broadcaster_ = std::make_unique<tf2_ros::TransformBroadcaster>(); } void amclCallback(const geometry_msgs::PoseWithCovarianceStampedConstPtr& msg) { // 接收AMCL定位数据 current_amcl_pose_ = msg->pose.pose; last_amcl_time_ = ros::Time::now(); } void publishOdom() { // 创建Odometry消息 nav_msgs::Odometry odom_msg; odom_msg.header.stamp = ros::Time::now(); odom_msg.header.frame_id = "odom"; odom_msg.child_frame_id = "base_link"; // 填充位姿和速度数据(此处需根据实际传感器数据填充) odom_msg.pose.pose.position.x = 0.0; // 实际应用中替换为真实值 odom_msg.pose.pose.orientation.w = 1.0; odom_msg.twist.twist.linear.x = 0.1; // 示例速度值 // 发布Odometry消息 odom_pub_.publish(odom_msg); // 发布odom->base_link的TF变换 publishOdomTransform(odom_msg); // 处理AMCL数据(如果已接收) if ((ros::Time::now() - last_amcl_time_).toSec() < 1.0) { processAmclData(odom_msg); } } private: void publishOdomTransform(const nav_msgs::Odometry& odom) { geometry_msgs::TransformStamped transform; transform.header = odom.header; transform.child_frame_id = odom.child_frame_id; transform.transform.translation.x = odom.pose.pose.position.x; transform.transform.translation.y = odom.pose.pose.position.y; transform.transform.translation.z = odom.pose.pose.position.z; transform.transform.rotation = odom.pose.pose.orientation; tf_broadcaster_->sendTransform(transform); } void processAmclData(const nav_msgs::Odometry& odom) { // 计算map->odom变换 geometry_msgs::TransformStamped map_to_odom; map_to_odom.header.stamp = ros::Time::now(); map_to_odom.header.frame_id = "map"; map_to_odom.child_frame_id = "odom"; // 计算变换关系:map->odom = map->base_link * base_link->odom tf2::Transform map_to_base, base_to_odom; tf2::fromMsg(current_amcl_pose_, map_to_base); tf2::fromMsg(odom.pose.pose, base_to_odom); // 计算逆变换:base_link->odom的逆是odom->base_link tf2::Transform odom_to_base = base_to_odom.inverse(); tf2::Transform map_to_odom_tf = map_to_base * odom_to_base; map_to_odom.transform = tf2::toMsg(map_to_odom_tf); tf_broadcaster_->sendTransform(map_to_odom); } ros::NodeHandle nh_; ros::Publisher odom_pub_; ros::Subscriber amcl_sub_; std::unique_ptr<tf2_ros::TransformBroadcaster> tf_broadcaster_; geometry_msgs::Pose current_amcl_pose_; ros::Time last_amcl_time_; }; int main(int argc, char** argv) { ros::init(argc, argv, "odometry_node"); OdometryProcessor processor; ros::Rate rate(20); // 20Hz while (ros::ok()) { ros::spinOnce(); processor.publishOdom(); rate.sleep(); } return 0; } 基于该代码完成发布odmo话题并接收acml模块的odmo数据并转换成base_like
06-21
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值