服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

反光板导航SLAM——VEnus代码浅析

日期: 来源:新机器视觉收集编辑:

点击下方卡片,关注“新机器视觉”公众号

重磅干货,第一时间送达

来源:古月居

通过研究具体的代码我们可以简单了解VEnus中对于反光柱定位的具体流程。



1、IntensityExtraction::Extract


IntensityExtraction::Extract(VEnus::Sensor::IntensityRange2D &cloud,                                  VEnus::Sensor::IntensityRange2D &candidate_cloud)


Extract函数的主要作用是从激光点云中提取出高反点,然后存储到对应的容器中。输入的数据类型为


VEnus::Sensor::IntensityRange2D


IntensityRange2D数据类型是定义在sensor/proto/sensor.proto文件内


message IntensityRange2D{    int64 timestamp = 1;    string frame_id = 2;    repeated IntensityPoint2D points = 3;}


IntensityPoint2D的数据格式如下:


message IntensityPoint2D{    float x = 1;    float y = 2;    float intensity = 3;}


repeated 类似于std的vector,可以用来存放N个相同类型的内容。所以这个函数的输入cloud是一系列带有强度信息的2D坐标点。函数的candidate_cloud代表的是匹配到的可能的反光柱点。所以它们数据结构是一样的。


然后是函数实现,这个函数其实很简单,它只是对整个点云进行了一次遍历,取连续的n个点,这个由一个参数intensity_median_filter_param决定,如果反光柱粗一点的或者激光分辨率高一点的话可以设置大一点,否则的话可以设置小一点,例如3。


然后对这些点进行判断,只要其中有一半的点云强度超过阈值intensity_threshold则认为这里存在一个反光柱,将其中的点云强度处于中间值的那个点存入到容器candidate_cloud。这里其实有点随机性在里面,因为没有把所有的点都加入到容器中,所以是存在一定遗漏的。



2、DBscanAssociation::Association


DBscanAssociation::Association(VEnus::Sensor::IntensityRange2D& candidate_cloud,                                    VEnus::Sensor::Feature2DList& feature_list)


前面我们提取出了反光柱的中心点,但是这里的点云中可能会出现很多个点代表同一个反光柱的情况。所以这个函数即是对刚才的这些点进行一次类似于聚类的操作。


candidate_cloud为第一步中选出来的高反点。feature_list为可能的反光柱中心。然后我们具体看一下函数实现:


Association的第一部分主要是遍历了所有点,通过调用expand_cluster函数进行一个分类:


  for (; iter < dataset.end(); ++iter) {    if (iter->status == UNCLASSIFIED) {      //聚类,将所有高反点根据相互之间距离分类,分类完成的点status为CLASSIFIED,同时处于同一类的点ID一致。      if (expand_cluster(iter, cluster_id)) {        cluster_id++;      }    }  }


dataset里面存储的就是当前帧的高反点,来自于candidate_cloud,数据类型为:


PointDBSCAN(double px, double py) {      x = px;      y = py;      status = UNCLASSIFIED;      id = 0;    }


可以看到对于每一个高反点,都有坐标x/y、状态status以及id等几个属性。初始状态下状态都为UNCLASSIFIED未区分,id都为0。然后函数对于每个点调用expand_cluster函数。


这个函数的作用是:对于每一个candidate_cloud,找到所有candidate_cloud中的点与其接近的点(两者间距离小于一定阈值)这些点的status都会被设置为CLASSIFIED防止重复判断。id都会被设置为相同代表同一个反光柱。


通过这种方式可以将所有点都划分成一个个点的区域,每个区域代表了一个反光柱。


在划分完成后,当然是要对每个区域做处理:


//按照ID将所有点放到map容器中  unordered_map<int, vector<int> > feature_dict;  iter = dataset.begin();  for (; iter < dataset.end(); ++iter) {    if (iter->status == CLASSIFIED) {      feature_dict[iter->id].push_back(iter - dataset.begin());    }  }  //找到每一组点的中心点的坐标,存放到tmp_res里面  vector<pair<double, double> > tmp_res;  for (auto pr : feature_dict) {    double center_x = 0;    double center_y = 0;    for (int idx : pr.second) {      center_x += dataset.at(idx).x;      center_y += dataset.at(idx).y;    }    int sz = pr.second.size();    //ROS_INFO("center point,x = %f,y = %f",center_x / double(sz),center_y / double(sz));    tmp_res.push_back(std::make_pair(center_x / double(sz), center_y / double(sz)));  }  //判断每个中心点之间的距离,将距离过近的中心点视为代表同一个反光柱,更新容器中反光柱中心点坐标  merge_cluster(tmp_res);


首先这里通过map维护了每一个待选的反光柱信息,所有的同一个反光柱的信息放到一起。


进行一个划分,然后对每一类点集调用merge_cluster进行一个中心点的求取。求取的方式其实很简单就是简单相加求平均。这样子就可以知道了所有反光柱中心点所在的位置。



3、CartoMapping::InsertFeatureList


这个函数的功能还是挺多的,包括匹配,位姿估计,位姿优化等一系列其实都是在这个函数中实现的。详细看一下这个函数具体情况:


3.1 初始化


InsertFeatureList函数的第一步判断了feature_points容器是否为空,这里面存储的是全局坐标下的反光柱。这个容器为空说明目前没有已经确定的反光柱。则进行初始化。


if (feature_points.empty())


初始化的方式比较简单,将第一帧的反光柱坐标存储到feature_points中作为初始状态下反光柱的中心坐标,然后更新FeatureGraph。


注意到这里的函数:InsertToFeatureGraph,这个函数的意义是对于每一个待插入的点(反光柱),计算它与周围反光柱之间的距离,然后存储到feature_graph中。注意到feature_graph的数据类型<int, std::unordered_map>。


第一个int为对应的feature_points的ID,第二个std::unordered_map中的int为与这个feature_points相互关联的反光柱点的ID,后面的double类型数据为两个点之间的距离。


3.2 特征点匹配


SiftNewFeaturePoints(input_fpts, hit_id_pair, wait_insert);


在确认初始化完成的情况下,调用SiftNewFeaturePoints函数进行反光柱的匹配。这个函数有三个参数:input_fpts为前面识别出来的反光柱坐标,hit_id_pair为当前帧反光柱与世界坐标系下的反光柱匹配上的ID,wait_insert为当前帧中没能跟世界坐标系下反光柱所匹配上的点的ID。函数主要执行了以下工作:


首先,对于所有当前点,建立一个局部的local_feature_graph,记录当前点与点之间的距离。


然后,对于每一个当前帧的点,使用其局部local_feature_graph与全局点进行匹配。


注意这里不是点与点的匹配而是类似于线与线的匹配,如果一个点到其他点的距离与全局点中某个点到其周围点的距离基本一致,则认为这两个点是匹配上了的,这时候会把这一对ID存放到hit_id_pair中。这个方式其实应该是有问题的,如果两个点到周围点的距离都很接近,就可能发生误匹配。


最后,对于input_fpts中剩下没有匹配到的点,则会将其存放到wait_insert中,这个数据代表这里检测出一个反光柱但是在全局中没有,后面需要另外处理。


3.3 初始位姿估计


ComputeCurrentPose(localization_hit, pose)


这个函数emmm其实我没看,因为似乎它基本就没有正确计算出来过。不过也不要紧,直接看下一步


3.4 位姿优化


OptimizeCurrentPose(localization_hit, pose);


OptimizeCurrentPose中需要输入两个变量:localization_hit里面存放的是当前帧下匹配点全局点之间的坐标。


pose是机器人当前初步估计下的位姿,按照逻辑它应该会来自于步骤3,但是由于3总是出问题所以大部分情况下它来自于上一次估计出来的位姿。


然后根据输入的约束关系localization_hit以及初始位姿pose,调用ceres优化得到一个新的位姿:


  ceres::Problem problem;  double pose[3] = {robot_pose.x(), robot_pose.y(), robot_pose.theta()};
 for (auto fpt_pair : match_result) {    problem.AddResidualBlock(new ceres::AutoDiffCostFunction<FeaturePairCost, 2, 3>(                                 new FeaturePairCost(fpt_pair.second, fpt_pair.first)),                             new ceres::CauchyLoss(0.2), pose);  }
 ceres::Solver::Options options;  options.linear_solver_type = ceres::DENSE_QR;  options.minimizer_progress_to_stdout = false;
 ceres::Solver::Summary summary;  ceres::Solve(options, &problem, &summary);


最后这里输出得到的是一个新的优化后的位姿pose


3.5 更新反光柱信息


注意到前面3.2中还有一个东西没有处理,就是返回的wait_insert容器。


这个里面存放的是当时检测到的反光柱但是这个反光柱没能与地图上其他地方的反光柱相匹配上,说明它可能是一个新的反光柱,对于这些信息我们要将其更新到最新的反光柱信息中:


  Eigen::Isometry2d matrixT = Eigen::Isometry2d::Identity();  matrixT.pretranslate(Eigen::Vector2d(pose.x(), pose.y()));  matrixT.rotate(pose.theta());  //   Eigen::Isometry2d matrixTinv = matrixT.inverse();
 vector<pair<double, double> > new_fpt_wait_insert;  vector<int> new_fpt_assigned_id;  for (auto unhit : wait_insert) {    Eigen::Vector3d local_fpt(unhit.first, unhit.second, 1.0);    auto global_fpt = matrixT * local_fpt;
   auto new_fpt = make_pair(global_fpt[0], global_fpt[1]);    feature_points[feature_point_cnt] = new_fpt;    new_fpt_wait_insert.push_back(new_fpt);    new_fpt_assigned_id.push_back(feature_point_cnt);    feature_point_cnt++;  }


首先是通过矩阵matrixT将这些反光柱点转到世界坐标系下,然后再根据初始化时候的方式一样更新feature_points容器,存储新的反光柱信息。当然最后还要调用:


InsertToFeatureGraph(new_fpt_wait_insert, new_fpt_assigned_id);


来更新feature_graph,也就是反光柱之间的距离信息。


通过以上一系列步骤我们就可以得到一个连续的基于反光柱匹配的位姿估计算法。当然这个算法还有一点小问题,比如线匹配意味着每一个反光柱到周围反光柱之间的距离都要独一无二,否则就可能出现误匹配的问题。


匹配阈值也不能设计的太大,否则也会发生匹配错误,但是设计的太小就可能导致本来是一个地方的反光柱没有成功匹配上,最后该位置会生成出很多个反光柱,类似于这样:



这里先介绍完代码思路,具体的代码实现以及优化过程下一章单独展开介绍。


本文仅做学术分享,如有侵权,请联系删文。

—THE END—

相关阅读

  • 一组常用公式,效率提升2.3%

  • 1、根据出生年月计算年龄如下图所示,需要根据B列出生年月计算年龄,C2单元格公式为:=DATEDIF(B2,TODAY(),"y")TODAY函数返回系统当前的日期。DATEDIF函数以B2的出生年月作为开始
  • SUMPRODUCT,这个函数还不错

  • 哈喽,表亲们早上好,我是星光,咱们又见面了。今天和大家分享一个名字拖沓,但是应用非常广泛的函数——SUMPRODUCT。开门见山,咱们通过几个例子,一起来看下SUMPRODUCT具体能干什么样
  • 快转 kotlin,香

  • 自 2017 年 Kotlin 被 Google 认证为 Android 开发官方编程语言后,最常提及的一个问题:是否应该学习 Kotlin 进行 Android 开发?相比传统 Java 语言有什么优势? 如今答案十分清
  • C++ 面试:说说智能指针 unique_ptr 实现原理

  • C++ 智能指针 unique_ptr 原理与自定义实现先说其特点: 独享它指向的对象。也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,指向的对象也随即被销毁
  • Vue2与Vue3响应式原理与依赖收集详解

  • 前言继 Angular 和 React 之后,尤大在 2016 年发布了如今“前端三剑客”之一的 Vue 2.0,并凭借其简单易用、轻量高效的特点受到了广泛的欢迎,特别是在国内环境中。然而 Vue 2
  • 你知道初中级前端怎么突破技术瓶颈吗?

  • 模拟面试、简历指导可私信找我,最低的价格收获最高的指导~已帮助50+名同学完成改造!前言大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的
  • 我在函数式编程上犯下的几个错误

  • 【CSDN 编者按】提到编程思想,你首先想到的会是面向对象还是面向函数编程呢?本文作者分享了自己在函数式编程实践中踩过的一些坑,分享给大家,希望能对你有所帮助。原文链接:https
  • 详解Flask框架SSTI攻击的利用与绕过技巧

  • Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。Flask使用 BSD 授权。Flask也被称为 “microframework” ,因为
  • ​自动控制原理(深入理解自动控制框架)

  • 点击下方卡片,关注“新机器视觉”公众号重磅干货,第一时间送达编辑丨古月居1. 控制原理1.1 开环与闭环系统下面是开环系统与闭环系统的示例。以给水壶加热的过程举例,开环系统

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • ​人工智能技术解密——机器视觉技术及应用

  • 点击下方卡片,关注“新机器视觉”公众号重磅干货,第一时间送达来源:长虹AI实验室视觉是人类最敏感、最直接的感知方式,在不进行实际接触的情况下,视觉感知可以使得我们获取周围环
  • 反光板导航SLAM——VEnus代码浅析

  • 点击下方卡片,关注“新机器视觉”公众号重磅干货,第一时间送达来源:古月居通过研究具体的代码我们可以简单了解VEnus中对于反光柱定位的具体流程。1、IntensityExtraction::Ext