ROS Costmap 插件
admin
2024-04-13 23:03:24
0

背景

公司 AVP demo 需要把 Tof 得到的点云放到 costmap 里避障用,但是出现了“残影”, 研究了一下应该是 raytrace 部分出现了问题,ROS 自带的 obtacle layer 里的 raytrace 不适合我们的 Tof,需要自己弄一个插件,又重新看了一下 navigation 的代码。

继承关系

move_base 里会有一个成员
costmap_2d::Costmap2DROS* planner_costmap_ros_, *controller_costmap_ros_;
这是一个 ROS 节点,这个节点的主要工作就是维护这个成员
LayeredCostmap* layered_costmap_;
而 LayeredCostmap 里有一个成员,用来装各种插件
std::vector > plugins_;
这些插件用来更新这个成员
Costmap2D costmap_;
这个就是我们最后要用到的代价地图
接下啦就是看每一个插件是怎么运行的, 这里以 obstaclelayer 为例
class ObstacleLayer : public CostmapLayer
他是继承自 CostmapLayer, ros 自己带的几个插件都是继承自这个类,这个类又继承自两个东西
class CostmapLayer : public Layer, public Costmap2D
其中 layer 规定了层的行为, Costmap2D 是存数据的

调用关系

Costmap2DROS 实例化以后会跑一个线程 mapUpdateLoop,这个线程里不断执行 updateMap() 这个函数
这个函数会首先更新机器人的位姿,然后调用 layered_costmap_ 的 updateMap
layered_costmap_ 里的 updateMap 就是遍历所有插件,
首先进行 updateBounds
这个过程中每个插件都会计算出自己要更新的 grid 的值(有的还会进行 raytrace 之类的操作)。和要更新的范围(不是整个地图都要更新,只更新一块 min max xy)
之后对综合所有插件得到的需要更新的 bound 里的代价值进行更新 updateCosts
把上一步里插件算出来的值更新到 costmap_ 成员里, 这里还可以选择更新方式,例如overwrite等,这些都是可以自定义的

修改

之前的雷达更新逻辑是观测数据末端到观测点连线之间的所有 grid 都会被置为 FREE, 但是障碍物移动出我们的 Tof 的 FOV 以后就没有观测数据了,就没法 raytrace 了。
这里直接把这个 raytrace 改成 FOV 的左右两条线和 localcostmap 外围框的围成的区域都 raytrace 掉, 然后在把当前帧看到的物体放到代价地图里。
比较绕的就是怎么算这个区域,自己整理了一个 FOV 小于 90° 时可以用的方法。
代码里用了许多 magic number,根据自己情况修改。

void TofObstacleLayer::raytraceFreespace(const Observation& clearing_observation, double* min_x, double* min_y,double* max_x, double* max_y, double robot_yaw)
{double ox = clearing_observation.origin_.x;double oy = clearing_observation.origin_.y;  pcl::PointCloud < pcl::PointXYZ > cloud = *(clearing_observation.cloud_);unsigned int x0, y0;if (!worldToMap(ox, oy, x0, y0)) // 观测原点的世界坐标减去局部地图原点的世界坐标 / 分辨率,得到的是局部坐标啊{ROS_WARN_THROTTLE(1.0, "The origin for the sensor at (%.2f, %.2f) is out of map bounds. So, the costmap cannot raytrace for it.",ox, oy);return;}double origin_x = origin_x_, origin_y = origin_y_;double map_end_x = origin_x + size_x_ * resolution_;double map_end_y = origin_y + size_y_ * resolution_;touch(ox, oy, min_x, min_y, max_x, max_y);// for each point in the cloud, we want to trace a line from the origin and clear obstacles along it// printf("getting points num: %d\n", cloud.points.size());// for (unsigned int i = 0; i < cloud.points.size(); ++i)// {//   // 世界坐标//   double wx = cloud.points[i].x;//   double wy = cloud.points[i].y;//   // now we also need to make sure that the enpoint we're raytracing//   // to isn't off the costmap and scale if necessary//   double a = wx - ox; // 点到原点差值//   double b = wy - oy;//   // the minimum value to raytrace from is the origin//   // 这里就是把地图外的点截到地图边界上//   if (wx < origin_x) // origin-x 应该是局部地图的起点(世界坐标)//   {//     double t = (origin_x - ox) / a;//     wx = origin_x;//     wy = oy + b * t;//   }//   if (wy < origin_y)//   {//     double t = (origin_y - oy) / b;//     wx = ox + a * t;//     wy = origin_y;//   }//   // the maximum value to raytrace to is the end of the map//   if (wx > map_end_x)//   {//     double t = (map_end_x - ox) / a;//     wx = map_end_x - .001;//     wy = oy + b * t;//   }//   if (wy > map_end_y)//   {//     double t = (map_end_y - oy) / b;//     wx = ox + a * t;//     wy = map_end_y - .001;//   }//   // now that the vector is scaled correctly... we'll get the map coordinates of its endpoint//   unsigned int x1, y1;//   // check for legality just in case//   if (!worldToMap(wx, wy, x1, y1)) // 这里是要知道到 map 是局部的还是全局的//     continue;unsigned int cell_raytrace_range = cellDistance(clearing_observation.raytrace_range_);MarkCell marker(costmap_, FREE_SPACE);// and finally... we can execute our trace to clear obstacles along that line// raytraceLine(marker, x0, y0, x1, y1, cell_raytrace_range); // 传感器原点x0 y0, 到观测点 x1 y1,进行 raytrace,都是地图坐标(格子)//   updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);// }// 1. 根据 yaw 算出两个交点// float half_fov = horizontal_fov_ / 2;float left_angle = robot_yaw + horizontal_fov_ / 2;float right_angle = robot_yaw - horizontal_fov_ / 2;// 算出left的交点,我的地图用的是 60x60, 29 + 31 = 60, 左下角是 (-29,-29) 右上角是 (31,31)float x1_f, y1_f;float left_line_a = tan(left_angle);if(left_angle > 0.785 && left_angle <2.355){ // case 1 pi/4 至 pi 3/4 与 y = 31 相交x1_f = 31 / left_line_a;y1_f = 31;}else if(left_angle > -0.785 && left_angle < 0.785){ // case 2 -pi/4 至 +pi/4  与 x = 31 相交x1_f = 31;y1_f = 31 * left_line_a;}else if(left_angle < -0.785 && left_angle > -2.355){ // case3  -pi/4 至 -3pi/4 与 y = -29 相交x1_f = -29 / left_angle;y1_f = -29;}else if(left_angle > 2.355 || left_angle < -2.355){// 因为 yaw 计数规则,这个象限被拆开了,与 x = -29 相交 x1_f = -29;y1_f = -29 * left_line_a;}// (注意要平移 + 29)x1_f = x1_f + 29;y1_f = y1_f + 29;unsigned int x1 = (unsigned int)x1_f;unsigned int y1 = (unsigned int)y1_f;// 机器人右边 FOV 边界,不管在哪里都是逆时针去 tracefloat x2_f, y2_f;float right_line_a = tan(right_angle);if(right_angle > 0.785 && right_angle <2.355){ // pi/4 至 pi 3/4 与 y = 31 相交x2_f = 31 / right_line_a;y2_f = 31;}else if(right_angle > -0.785 && right_angle < 0.785){ // -pi/4 至 +pi/4  与 x = 31 相交x2_f = 31 ;y2_f = 31 * right_line_a;}else if(right_angle < -0.785 && right_angle > -2.355){ // -pi/4 至 -3pi/4 与 y = -29 相交x2_f = -29 / right_line_a;y2_f = -29;}else if(right_angle > 2.355 ||  right_angle < -2.355){ // 与 x = -29 相交 x2_f = -29;y2_f = -29 * right_line_a;}// (注意要平移 + 29)x2_f = x2_f + 29;y2_f = y2_f + 29;unsigned int x2 = (unsigned int)((int)x2_f);unsigned int y2 = (unsigned int)y2_f;// 以left和哪条线相交作为判断起点switch (y1){case 60: // left 与上方边框相交switch (y2){case 60: // left right 都与上方边框相交 (y1 == 60 && y2 == 60)// 那么从 x1 trace 到 x2, 因为 x2 在 x1 顺时针方向, x2 大for(unsigned xi = x1; xi < x2; xi ++){raytraceLine(marker, x0, y0, xi, y1, cell_raytrace_range);// 这里还要计算出 xi,y1 对应的世界坐标,替换 wx,wydouble wx = origin_x +  xi * resolution_;double wy = origin_y +  y1 * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);}break;default: // left 与上方边框相交,right 与右侧边框相交 (y1 == 60 && x2 == 60)// Fov 分别与两条边框相交,分两段 trace// left 顺时针 trace x 到60for(unsigned xi = x1; xi < 60; xi ++){raytraceLine(marker, x0, y0, xi, y1, cell_raytrace_range);double wx = origin_x +  xi * resolution_;double wy = origin_y +  y1 * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);}// right 逆时针 trace 意味着 y 增到到 60for(unsigned yi = y2; yi < 60; yi++){raytraceLine(marker, x0, y0, x2, yi, cell_raytrace_range);double wx = origin_x +  x2 * resolution_;double wy = origin_y +  yi * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);} break;}break;case 0: // left 与下方边框相交switch (y2){case 0: // left right 都与下方边框相交 ( y1 == 0 && y2 == 0)// 那么从 x1 trace 到 x2, 因为 x2 在 x1 顺时针方向, x2 小for(unsigned xi = x2; xi < x1; xi ++){ // 下方水平线,从左侧端点 traceraytraceLine(marker, x0, y0, xi, y1, cell_raytrace_range);double wx = origin_x +  xi * resolution_;double wy = origin_y +  y1 * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);}break;default: // left 与下方边框相交, right 与左侧边框相交 ( y1 == 0 && x2 == 0)// 顺时针 trace 意味着 x 是减小到0的for(unsigned xi = 0; xi < x1; xi ++){ // 下方水平线,从左侧端点 traceraytraceLine(marker, x0, y0, xi, y1, cell_raytrace_range);double wx = origin_x +  xi * resolution_;double wy = origin_y +  y1 * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);}// 逆时针 trace 意味着 y 是减小到0的for(unsigned yi = 0; yi < y2 ; yi++){ // 左侧竖直线,从下往上 traceraytraceLine(marker, x0, y0, x2, yi, cell_raytrace_range);double wx = origin_x +  x2 * resolution_;double wy = origin_y +  yi * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);}break;}break;default: // left 与左右侧边框相交switch (x1){case 60: // left 与右侧边框相交switch (x2){case 60: // left right 都与右侧边框相交 (x1 == 60 && x2 == 60)for(unsigned yi = y2; yi < y1; yi++){raytraceLine(marker, x0, y0, x2, yi, cell_raytrace_range);double wx = origin_x +  x2 * resolution_;double wy = origin_y +  yi * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);}break;default: // left 与右侧边框相交,right 与下方边框相交 (x1 == 60 && y2 == 0)// Fov 分别与两条边框相交,分两段 trace// left 顺时针 trace y 到 0for(unsigned yi = 0; yi < y1 ; yi++){ raytraceLine(marker, x0, y0, x1, yi, cell_raytrace_range);double wx = origin_x +  x1 * resolution_;double wy = origin_y +  yi * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);}// right 逆时针 trace x 增到到 60for(unsigned xi = x2; xi < 60; xi ++){raytraceLine(marker, x0, y0, xi, y2, cell_raytrace_range);double wx = origin_x +  xi * resolution_;double wy = origin_y +  y2 * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);}break;}break;case 0: // left 与左侧边框相交switch (x2){case 0: // left right 都与左侧边框相交 (x1 == 0 && x2 == 0)for(unsigned yi = y1; yi < y2 ; yi++){ // 左侧竖直线,从下往上 traceraytraceLine(marker, x0, y0, x2, yi, cell_raytrace_range);double wx = origin_x +  x2 * resolution_;double wy = origin_y +  yi * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);}break;default: // left 与左侧边框相交, right 与上方边框相交 (x1 == 0 && y2 == 60)for(unsigned yi = y1; yi < 60 ; yi++){ // 下方水平线,从左侧端点 traceraytraceLine(marker, x0, y0, x1, yi, cell_raytrace_range);double wx = origin_x +  x1 * resolution_;double wy = origin_y +  yi * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);}// right 逆时针 trace  意味着 x 减小到 0for(unsigned xi = 0; xi < x2; xi ++){ // 左侧竖直线,从下往上 traceraytraceLine(marker, x0, y0, xi, y2, cell_raytrace_range);double wx = origin_x +  xi * resolution_;double wy = origin_y +  y2 * resolution_;updateRaytraceBounds(ox, oy, wx, wy, clearing_observation.raytrace_range_, min_x, min_y, max_x, max_y);}break;}break;}}
}

相关内容

热门资讯

4家银行AIC现身存储巨头股东... 近日,资本市场热度颇高的两家存储巨头长鑫科技集团股份有限公司(以下简称“长鑫科技”)、长江存储控股股...
8元无限续杯、0元看电影、老字... 城市的烟火暖意,藏在亲民的消费场景里,也藏在老地标的新生蜕变中。粤汉码头火车旁新开竹林茶馆,8元就能...
2026年水利工程新趋势,这些... 随着全球气候变化和城市化进程的加速,水利工程在保障水资源供给、改善生态环境以及提升人民生活质量中的作...
原创 发... 这几年,身边越来越多人开始换一种活法:不急着买房,不执着“上车”,反而愿意把钱拿去租一套更舒服、更体...
小红书入场Skill分发,B站... 来源:界面新闻 文丨AI价值官 星野 编辑丨美圻 过去半年,Skill 这个词在AI圈的出现...
2026年福州企业门户网站建设... 本篇将回答的核心问题 在数字化转型加速的2026年,企业门户网站建设应遵循哪些核心评估标准,以确保投...
原创 今... 今日金价:2026年5月22日注意了!黄金或现历史类似回调走势 5月22日,金市又热闹起来了,咱们看...
雷军发布YU7 GT、YU7标... 5月21日,小米人车家全生态新品发布会在北京举办,小米集团创始人、董事长兼CEO雷军正式发布小米YU...
留神峪煤矿瓦斯爆炸事故发布会:... 昨晚,山西留神峪煤矿发生瓦斯爆炸,造成重大人员伤亡。今天,当地召开新闻发布会,现场全体默哀。会上介绍...
原创 修... 修复资产负债表,日本花了几十年。 自上世纪90年代初泡沫经济破裂后,日本陷入了长达三十年的通缩螺...
2026年小红书效果化种草白皮... 2026 年小红书正式迈入种草效果化时代,这是品牌追求预算确定性回报与平台升级为消费决策、用户信任场...
连续18年获“全国文化企业30... 南都讯 记者钟欣5月21日,第二十二届中国(深圳)国际文化产业博览交易会开幕。展会期间,光明日报社和...
荣耀确认IPO未终止!开放员工... 5月22日,荣耀因股改满一年未完成IPO,按约定正式开放员工持股退出通道。据《财闻》报道称,当日16...
易方达蓝筹精选有新变动:增聘2... 《每日经济新闻》记者获悉,继景顺长城、中欧等多家基金公司旗下百亿基金经理产品调整后,易方达基金也迎来...
光储龙头,又翻倍了 去年海外光储赛道最受关注的公司,毫无疑问是阳光电源,市值重回巅峰,风光无限。 但今年一季度业绩突然失...
中企出海报告在静安发布,七成受... 来源:滚动播报 (来源:上观新闻) 昨天,在上海静安举办的澳洲会计师公会出海论坛暨澳洲注册会计师颁...
京蒙协作延链强链 科右中旗牛产... 初夏时节,走进内蒙古华阳牛业科技集团有限公司屠宰加工车间,自动化生产线高效运转。作为京蒙协作产业帮扶...
原创 中... 最近发布了一份有关新一线城市魅力的榜单。榜单按照商业资源聚集度、城市枢纽性、城市人活跃度这五个方面来...
突然,全线跳水!超16万人爆仓 来源:宁波晚报 5月23日,被视作反映市场风险偏好指标的加密货币持续跳水。 截至发稿,比特币大跌3....
基民懵了!说好的科技行情,结果... 每经记者:叶峰 每经编辑:赵云 本周股指冲高回落,沪深两市股票型ETF和跨境型ETF合计净流出729...