(笔记)(10)AMCL包源码分析 | amcl_node.cpp (三)
这一讲将详细阐述AMCL包接收初始位姿或者全局定位服务,无运动更新服务后进行粒子滤波器初始化,以实现粒子位姿初始化的原理。
1.接收处理OccupancyGrid格式的地图
1.1 requestMap()函数&&mapReceived()函数
使用GetMap服务请求地图,得到地图后进入handleMapMessage()函数进行处理。
void AmclNode::requestMap()
{
boost::recursive_mutex::scoped_lock ml(configuration_mutex_);
// get map via RPC GetMap服务
nav_msgs::GetMap::Request req;
nav_msgs::GetMap::Response resp;
ROS_INFO("Requesting the map...");
while(!ros::service::call("static_map", req, resp))
{
ROS_WARN("Request for map failed; trying again...");
ros::Duration d(0.5);
d.sleep();
}
handleMapMessage( resp.map );
}
void AmclNode::mapReceived(const nav_msgs::OccupancyGridConstPtr& msg)
{
if( first_map_only_ && first_map_received_ ) {
return;
}
handleMapMessage( *msg );
first_map_received_ = true;
}
1.2 handleMapMessage()函数
进入handleMapMessage()函数,首先使用convertMap()函数将OccupancyGrid msg格式的地图转换成mapt格式的地图。然后在map_t格式的地图上,遍历找出空闲的栅格,将它们以(i,j)的形式存放到free_space_indices容器里。
接下来是创建一个粒子滤波器,以及使用updatePoseFromServer()函数将粒子滤波器初始化。下一步便是实例化 AmclNode类里的里程计运动模型,激光观测模型,并为它们妥善选择合适的类型。
最后便是调用applyInitialPose()函数,进入神秘的位姿初始化过程,稍后详细分析applyInitialPose()函数。
void AmclNode::handleMapMessage(const nav_msgs::OccupancyGrid& msg)
{
boost::recursive_mutex::scoped_lock cfl(configuration_mutex_);
ROS_INFO("Received a %d X %d map @ %.3f m/pix\n",
msg.info.width,
msg.info.height,
msg.info.resolution);
if(msg.header.frame_id != global_frame_id_)
ROS_WARN("Frame_id of map received:'%s' doesn't match global_frame_id:'%s'. This could cause issues with reading published topics",
msg.header.frame_id.c_str(),
global_frame_id_.c_str());
freeMapDependentMemory();
// Clear queued laser objects because they hold pointers to the existing
// map, #5202.
lasers_.clear();
lasers_update_.clear();
frame_to_laser_.clear();
map_ = convertMap(msg);
#if NEW_UNIFORM_SAMPLING
// Index of free space
free_space_indices.resize(0);
for(int i = 0; i < map_->size_x; i++)
for(int j = 0; j < map_->size_y; j++)
if(map_->cells[MAP_INDEX(map_,i,j)].occ_state == -1)
free_space_indices.push_back(std::make_pair(i,j));
#endif
// Create the particle filter
pf_ = pf_alloc(min_particles_, max_particles_,
alpha_slow_, alpha_fast_,
(pf_init_model_fn_t)AmclNode::uniformPoseGenerator,
(void *)map_);
pf_set_selective_resampling(pf_, selective_resampling_);
pf_->pop_err = pf_err_;
pf_->pop_z = pf_z_;
// Initialize the filter
updatePoseFromServer();
pf_vector_t pf_init_pose_mean = pf_vector_zero();
pf_init_pose_mean.v[0] = init_pose_[0];
pf_init_pose_mean.v[1] = init_pose_[1];
pf_init_pose_mean.v[2] = init_pose_[2];
pf_matrix_t pf_init_pose_cov = pf_matrix_zero();
pf_init_pose_cov.m[0][0] = init_cov_[0];
pf_init_pose_cov.m[1][1] = init_cov_[1];
pf_init_pose_cov.m[2][2] = init_cov_[2];
pf_init(pf_, pf_init_pose_mean, pf_init_pose_cov);
pf_init_ = false;
// Instantiate the sensor objects
// Odometry
delete odom_;
odom_ = new AMCLOdom();
ROS_ASSERT(odom_);
odom_->SetModel( odom_model_type_, alpha1_, alpha2_, alpha3_, alpha4_, alpha5_ );
// Laser
delete laser_;
laser_ = new AMCLLaser(max_beams_, map_);
ROS_ASSERT(laser_);
if(laser_model_type_ == LASER_MODEL_BEAM)
laser_->SetModelBeam(z_hit_, z_short_, z_max_, z_rand_,
sigma_hit_, lambda_short_, 0.0);
else if(laser_model_type_ == LASER_MODEL_LIKELIHOOD_FIELD_PROB){
ROS_INFO("Initializing likelihood field model; this can take some time on large maps...");
laser_->SetModelLikelihoodFieldProb(z_hit_, z_rand_, sigma_hit_,
laser_likelihood_max_dist_,
do_beamskip_, beam_skip_distance_,
beam_skip_threshold_, beam_skip_error_threshold_);
ROS_INFO("Done initializing likelihood field model.");
}
else
{
ROS_INFO("Initializing likelihood field model; this can take some time on large maps...");
laser_->SetModelLikelihoodField(z_hit_, z_rand_, sigma_hit_,
laser_likelihood_max_dist_);
ROS_INFO("Done initializing likelihood field model.");
}
// In case the initial pose message arrived before the first map,
// try to apply the initial pose now that the map has arrived.
applyInitialPose();
}
1.3 freeMapDependentMemory()函数
将已分配内存的map_,pf_,odom_,laser_销毁掉。
void AmclNode::freeMapDependentMemory()
{
if( map_ != NULL ) {
map_free( map_ );
map_ = NULL;
}
if( pf_ != NULL ) {
pf_free( pf_ );
pf_ = NULL;
}
delete odom_;
odom_ = NULL;
delete laser_;
laser_ = NULL;
}
1.4 convertMap()函数
将OccupancyGrid 格式的msg转换成内存表达形式的map。
/**
* Convert an OccupancyGrid map message into the internal
* representation. This allocates a map_t and returns it.
*/
map_t* AmclNode::convertMap( const nav_msgs::OccupancyGrid& map_msg )
{
map_t* map = map_alloc();
ROS_ASSERT(map);
map->size_x = map_msg.info.width;
map->size_y = map_msg.info.height;
map->scale = map_msg.info.resolution;
map->origin_x = map_msg.info.origin.position.x + (map->size_x / 2) * map->scale;
map->origin_y = map_msg.info.origin.position.y + (map->size_y / 2) * map->scale;
// Convert to player format
map->cells = (map_cell_t*)malloc(sizeof(map_cell_t)*map->size_x*map->size_y);
ROS_ASSERT(map->cells);
for(int i=0;i<map->size_x * map->size_y;i++)
{
if(map_msg.data[i] == 0)
map->cells[i].occ_state = -1;//占据
else if(map_msg.data[i] == 100)
map->cells[i].occ_state = +1;//空闲
else
map->cells[i].occ_state = 0;//未知
}
return map;
}
关于地图部分的函数就讨论到这里了,那么这里用到 updatePoseFromServer()函数是干什么用的呢?我们接下来进行讨论。
2.接收处理外部设置的初始位姿
2.1 initialPoseReceived()函数
一般进入这个函数处理,通常是在rviz上人为使用pose estimate工具给机器人设置一个初始位姿,就是人主动估计机器人在这个地图上的某个地方。需要人工介入。
void AmclNode::initialPoseReceived(const geometry_msgs::PoseWithCovarianceStampedConstPtr& msg)
{
handleInitialPoseMessage(*msg);
}
2.2 handleInitialPoseMessage()函数
外界传入的位姿以geometrymsgs::PoseWithCovarianceStamped 格式的msg进入handleInitialPoseMessage()函数。然后对传入的这个位姿进行小小的改造,变成pose_new。然后从pose_new那里使用tf2的相关函数,如getOrigin()等,将其值赋给pf_init_pose_mean;这算是帮助粒子滤波器再次初始化了。再将传入位姿的协方差赋值给pf_init_pose_cov; 新建一个amcl_hyp_t格式的 initial_pose_hyp_ ,充分初始化后进入applyInitialPose()函数处理。
void
AmclNode::handleInitialPoseMessage(const geometry_msgs::PoseWithCovarianceStamped& msg)
{
boost::recursive_mutex::scoped_lock prl(configuration_mutex_);
if(msg.header.frame_id == "")
{
// This should be removed at some point
ROS_WARN("Received initial pose with empty frame_id. You should always supply a frame_id.");
}
// We only accept initial pose estimates in the global frame, #5148.
else if(stripSlash(msg.header.frame_id) != global_frame_id_)
{
ROS_WARN("Ignoring initial pose in frame \"%s\"; initial poses must be in the global frame, \"%s\"",
stripSlash(msg.header.frame_id).c_str(),
global_frame_id_.c_str());
return;
}
// In case the client sent us a pose estimate in the past, integrate the
// intervening odometric change.
geometry_msgs::TransformStamped tx_odom;
try
{
ros::Time now = ros::Time::now();
// wait a little for the latest tf to become available
tx_odom = tf_->lookupTransform(base_frame_id_, msg.header.stamp,
base_frame_id_, ros::Time::now(),
odom_frame_id_, ros::Duration(0.5));
}
catch(tf2::TransformException e)
{
// If we've never sent a transform, then this is normal, because the
// global_frame_id_ frame doesn't exist. We only care about in-time
// transformation for on-the-move pose-setting, so ignoring this
// startup condition doesn't really cost us anything.
if(sent_first_transform_)
ROS_WARN("Failed to transform initial pose in time (%s)", e.what());
tf2::convert(tf2::Transform::getIdentity(), tx_odom.transform);
}
tf2::Transform tx_odom_tf2;
tf2::convert(tx_odom.transform, tx_odom_tf2);
tf2::Transform pose_old, pose_new;
tf2::convert(msg.pose.pose, pose_old);
pose_new = pose_old * tx_odom_tf2;
// Transform into the global frame
ROS_INFO("Setting pose (%.6f): %.3f %.3f %.3f",
ros::Time::now().toSec(),
pose_new.getOrigin().x(),
pose_new.getOrigin().y(),
tf2::getYaw(pose_new.getRotation()));
// Re-initialize the filter
pf_vector_t pf_init_pose_mean = pf_vector_zero();
pf_init_pose_mean.v[0] = pose_new.getOrigin().x();
pf_init_pose_mean.v[1] = pose_new.getOrigin().y();
pf_init_pose_mean.v[2] = tf2::getYaw(pose_new.getRotation());
pf_matrix_t pf_init_pose_cov = pf_matrix_zero();
// Copy in the covariance, converting from 6-D to 3-D
for(int i=0; i<2; i++)
{
for(int j=0; j<2; j++)
{
pf_init_pose_cov.m[i][j] = msg.pose.covariance[6*i+j];
}
}
pf_init_pose_cov.m[2][2] = msg.pose.covariance[6*5+5];
delete initial_pose_hyp_;
initial_pose_hyp_ = new amcl_hyp_t();
initial_pose_hyp_->pf_pose_mean = pf_init_pose_mean;
initial_pose_hyp_->pf_pose_cov = pf_init_pose_cov;
applyInitialPose();
}
3.内部初始化位姿工具
3.1 uniformPoseGenerator()函数
这里初始化的原理是利用遍历地图上的空闲栅格,然后随机产生位姿,但是这些位姿是要遍布地图上的空闲处的。这个函数有助于全局定位服务使用。
pf_vector_t AmclNode::uniformPoseGenerator(void* arg)
{
map_t* map = (map_t*)arg;
#if NEW_UNIFORM_SAMPLING
unsigned int rand_index = drand48() * free_space_indices.size();
std::pair<int,int> free_point = free_space_indices[rand_index];
pf_vector_t p;
p.v[0] = MAP_WXGX(map, free_point.first);
p.v[1] = MAP_WYGY(map, free_point.second);
p.v[2] = drand48() * 2 * M_PI - M_PI;
return p;
}
3.2 updatePoseFromServer()函数
这里是初始化位姿的前身,还处于设置均值和协方差的阶段:
void AmclNode::updatePoseFromServer()
{
init_pose_[0] = 0.0;
init_pose_[1] = 0.0;
init_pose_[2] = 0.0;
init_cov_[0] = 0.5 * 0.5;
init_cov_[1] = 0.5 * 0.5;
init_cov_[2] = (M_PI/12.0) * (M_PI/12.0);
// Check for NAN on input from param server, #5239
double tmp_pos;
private_nh_.param("initial_pose_x", tmp_pos, init_pose_[0]);
if(!std::isnan(tmp_pos))
init_pose_[0] = tmp_pos;
else
ROS_WARN("ignoring NAN in initial pose X position");
private_nh_.param("initial_pose_y", tmp_pos, init_pose_[1]);
if(!std::isnan(tmp_pos))
init_pose_[1] = tmp_pos;
else
ROS_WARN("ignoring NAN in initial pose Y position");
private_nh_.param("initial_pose_a", tmp_pos, init_pose_[2]);
if(!std::isnan(tmp_pos))
init_pose_[2] = tmp_pos;
else
ROS_WARN("ignoring NAN in initial pose Yaw");
private_nh_.param("initial_cov_xx", tmp_pos, init_cov_[0]);
if(!std::isnan(tmp_pos))
init_cov_[0] =tmp_pos;
else
ROS_WARN("ignoring NAN in initial covariance XX");
private_nh_.param("initial_cov_yy", tmp_pos, init_cov_[1]);
if(!std::isnan(tmp_pos))
init_cov_[1] = tmp_pos;
else
ROS_WARN("ignoring NAN in initial covariance YY");
private_nh_.param("initial_cov_aa", tmp_pos, init_cov_[2]);
if(!std::isnan(tmp_pos))
init_cov_[2] = tmp_pos;
else
ROS_WARN("ignoring NAN in initial covariance AA");
}
3.3 savePoseToServer()函数
这里只是把最新的odom位姿转换成最新的地图位姿去存储,即初始位姿map_pose,我们也对last_published_pose取其协方差,作为初始位姿map_pose的协方差。记住last_published_pose很重要的哦!它一般来源于上一次粒子滤波算法估计出的机器人位置。
void AmclNode::savePoseToServer()
{
// We need to apply the last transform to the latest odom pose to get
// the latest map pose to store. We'll take the covariance from
// last_published_pose.
tf2::Transform odom_pose_tf2;
tf2::convert(latest_odom_pose_.pose, odom_pose_tf2);
tf2::Transform map_pose = latest_tf_.inverse() * odom_pose_tf2;
double yaw = tf2::getYaw(map_pose.getRotation());
ROS_DEBUG("Saving pose to server. x: %.3f, y: %.3f", map_pose.getOrigin().x(), map_pose.getOrigin().y() );
private_nh_.setParam("initial_pose_x", map_pose.getOrigin().x());
private_nh_.setParam("initial_pose_y", map_pose.getOrigin().y());
private_nh_.setParam("initial_pose_a", yaw);
private_nh_.setParam("initial_cov_xx",
last_published_pose.pose.covariance[6*0+0]);
private_nh_.setParam("initial_cov_yy",
last_published_pose.pose.covariance[6*1+1]);
private_nh_.setParam("initial_cov_aa",
last_published_pose.pose.covariance[6*5+5]);
}
3.4 applyInitialPose()函数
这个函数是连接均值和协方差输入,最后实现粒子滤波器初始化的好工具,至此,粒子滤波器接收均值和协方差顺利初始化。在pf文件夹里我们讨论过粒子滤波器初始化的方式,比如说接收均值和协方差进行初始化。
/**
* If initial_pose_hyp_ and map_ are both non-null, apply the initial
* pose to the particle filter state. initial_pose_hyp_ is deleted
* and set to NULL after it is used.
*/
void AmclNode::applyInitialPose()
{
boost::recursive_mutex::scoped_lock cfl(configuration_mutex_);
if( initial_pose_hyp_ != NULL && map_ != NULL ) {
pf_init(pf_, initial_pose_hyp_->pf_pose_mean, initial_pose_hyp_->pf_pose_cov);
pf_init_ = false;
delete initial_pose_hyp_;
initial_pose_hyp_ = NULL;
}
}
经历如此种种之后,粒子滤波器得到初始化,也就是粒子得到了新的位姿。
3.5 globalLocalizationCallback()函数
这里使用了pf_init_model模型来进行初始化,所以不需要传入一个位姿这样的操作。
bool AmclNode::globalLocalizationCallback(std_srvs::Empty::Request& req,
std_srvs::Empty::Response& res)
{
if( map_ == NULL ) {
return true;
}
boost::recursive_mutex::scoped_lock gl(configuration_mutex_);
ROS_INFO("Initializing with uniform distribution");
//这里用到uniformPoseGenerator函数
pf_init_model(pf_, (pf_init_model_fn_t)AmclNode::uniformPoseGenerator,
(void *)map_);
ROS_INFO("Global initialisation done!");
pf_init_ = false;
return true;
}
3.6 nomotionUpdateCallback()函数
这里是将m_force_update 置为true。这个m_force_update参数在AmclNode类里已包含。它将应用在laserReceived()函数里,那里用于粒子滤波器的初始化和更新。
// force nomotion updates (amcl updating without requiring motion)
bool
AmclNode::nomotionUpdateCallback(std_srvs::Empty::Request& req,
std_srvs::Empty::Response& res)
{
m_force_update = true;
//ROS_INFO("Requesting no-motion update");
return true;
}
4.总结
综合来看,用好粒子滤波算法估计出的机器人位姿p作为last_publishedpose存储备用,或者接收外界rviz工具poseestimate人为设置的pose,经过initialPoseReceived函数的处理,也是一种办法。抛开单一的位姿作为输入,转换成均值和协方差让粒子滤波器进行初始化。初始化类型为:void pf_init(pf_t *pf, pf_vector_t mean, pf_matrix_t cov);
// Initialize the filter using a guassian
void pf_init(pf_t *pf, pf_vector_t mean, pf_matrix_t cov);
还有使用别的方法,比如说全局定位服务,是使用pf_init_model模型来进行初始化,利用了栅格地图上的空闲栅格再加入随机数,产生了遍布地图空闲区域的位姿集。初始化类型为:void pf_init_model(pf_t *pf, pf_init_model_fn_t init_fn, void *init_data);
// Initialize the filter using some model
void pf_init_model(pf_t *pf, pf_init_model_fn_t init_fn, void *init_data);
又如使用nomotionupdate服务,仅是设置了一个强制更新标志符m_force_update,用到粒子滤波器是否更新的条件update里,也能完成粒子滤波器的更新,产生新的粒子集。这里是更新粒子集的相关模型:
// Update the filter with some new action
void pf_update_action(pf_t *pf, pf_action_model_fn_t action_fn, void *action_data);
// Update the filter with some new sensor observation
void pf_update_sensor(pf_t *pf, pf_sensor_model_fn_t sensor_fn, void *sensor_data);
总结完以上三种方式,都是通过影响粒子滤波器,从而根据我们的需求初始化位姿initial_pose或者位姿集合。顺着粒子滤波算法继续推演,粒子滤波器经过一番来自激光数据和里程计数据的洗礼,输出了机器人最可能在地图上的唯一位姿p。在其中,AMCL维护的tf树时刻发挥着它的作用。
转自:10.AMCL包源码分析 | amcl_node.cpp (三) - 知乎 (zhihu.com)
posted on 2022-09-14 13:59 tdyizhen1314 阅读(293) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具