6.【自动驾驶与机器人中的SLAM技术】鲁邦核函数的含义和应用

目录

  • 1. 给ICP和NDT配准添加柯西核函数
    • 1.1 代码实现
  • 2. 将第1部分的robust loss引入IncNDTLO和LooselyLIO,给出实现和运行效果
  • 3. 从概率层面解释NDT残差和协方差矩阵的关系,说明为什么NDT协方差矩阵可以用于最小二乘
  • 4. 为LOAM like LO设计一个地面点云提取流程,并单独为这些点使用点面ICP
    • 4.1 代码实现
    • 4.2 对地面点进行ICP
  • 5. 也欢迎大家来我的微信公众号--过千帆,来读书,提高我们的认知。

在这里插入图片描述

1. 给ICP和NDT配准添加柯西核函数

鲁邦核函数的意义

在实际优化过程中,很可能会出现误匹配的项,这些误匹配的项被算法当做要降低的误差对待,由于误匹配的“误差”会很大,降低它能够明显使总的误差降低,但是显然这些项不应该被当做误差项对待,因为他会抹平其它正确边的影响,使得优化算法专注于调整一个错误的值。鲁邦核函数正好可以解决这个问题。核函数保证每条边的误差不会大的没边而掩盖其它的边。具体的方式就是,把原先误差的二范数度量替换成一个增长没那么快的函数,同时保证自己的光滑性质。

下面是柯西核函数在最小二乘问题上的应用 下面是柯西核函数在最小二乘问题上的应用 下面是柯西核函数在最小二乘问题上的应用: 此处可以看一下这篇博文,里面讲了非线性最小二乘问题以及核函数。
在这里插入图片描述
在这里插入图片描述
g2o中柯西函数的实现:
在这里插入图片描述

可以去看看g2o是如何使用核函数进行优化的源码,通过看g2o源码对核函数的实现可以粗略总结出,要做的工作分两部分,
1.实现柯西核函数(控制阈值c选择, 残差在正态分布情况下选择2.3849,非正态分布下没有一定值,这里直接设置为1.0);
2. 根据上面的公式推导拼正规方程。

1.1 代码实现

point2point:

auto H_and_err = std::accumulate(index.begin(), index.end(), std::pair<Mat6d, Vec6d>(Mat6d::Zero(), Vec6d::Zero()),[&jacobians, &errors, &effect_pts, &total_res, &effective_num](const std::pair<Mat6d, Vec6d>& pre,int idx) -> std::pair<Mat6d, Vec6d> {if (!effect_pts[idx]) {return pre;} else {double e2 = errors[idx].dot(errors[idx]);total_res += e2;effective_num++;// Cauchy 鲁棒核函数double delta =  1.0;                    // 控制阈值设置为1.0double delta2 = delta * delta;double delta2_inv = 1.0 / delta2;double aux = delta2_inv * e2 + 1.0;Vec3d rho;rho[0] = delta2  * log(aux);            // Cauchy核函数rho[1] = 1.0 / aux;                     // Cauchy核函数的一阶导数rho[2] = -delta2_inv * pow(rho[1],2);   // Cauchy核函数的二阶导数Mat3d weighted_infos = rho[1] * Mat3d::Identity() + 2 * rho[2] * errors[idx] * errors[idx].transpose();return std::pair<Mat6d, Vec6d>(pre.first + jacobians[idx].transpose() * weighted_infos * jacobians[idx],pre.second - rho[1] * jacobians[idx].transpose() * errors[idx]);}});

几种配准方法代码大同小异这里只贴出point2point的吧。其他ICP类型和NDT可以遵照point2point实现。以下是四种配准方法加了柯西核函数之后的表现。

在这里插入图片描述

2. 将第1部分的robust loss引入IncNDTLO和LooselyLIO,给出实现和运行效果

这里在看LooselyLIO代码时,发现其调用的方法就是IncNDTLO,而IncNDTLO在AddCloud中的配准方法就是IncNdt3d。只需要修改src/ch7/ndt_inc.cc文件中的AlignNdt()函数即可。修改和第一题中的代码一致。
运行效果:(加核函数前后表现差异不大)
在这里插入图片描述
在这里插入图片描述

3. 从概率层面解释NDT残差和协方差矩阵的关系,说明为什么NDT协方差矩阵可以用于最小二乘

在这里插入图片描述

4. 为LOAM like LO设计一个地面点云提取流程,并单独为这些点使用点面ICP

论文参考:Fast segmentation of 3D point clouds: A paradigm on LiDAR data for autonomous vehicle applications
地面提取思路:一帧3D点云包含的点数众多,我们要提取地面,可以只选出接近地面的一些点拿来拟合平面,可以降低大量计算。

论文中的LPR算法流程如下:(种子点可以理解为近地点,不过种子点的提取要求雷达与地面大体垂直,不然无法按照论文中思路提取出种子点,因为近地点提取原理就是对激光点云排序,选Z值小的点,如果激光雷达正对着地面,此方法失效。–当然大多数雷达不会这么做。)
在这里插入图片描述

4.1 代码实现

void FeatureExtraction::ExtractGroundPlane(CloudPtr point_input_xyz, CloudPtr pc_ground)
{int lpr_max_iters = 100; // lpr算法 最大迭代次数double lpr_least_gpoints_rate = 0.3; // 用于计算近地点阈值的一个比率,按照整帧点云点数来确定点数计算近地点阈值,这个阈值用来提取种子点double lpr_fit_dist_thre = 0.05; // 到平面距离小于此阈值的点就属于平面点// find the lpr planeCloudPtr sort_cloud = point_input_xyz; // 对点云按Z值排序std::sort(sort_cloud->points.begin(), sort_cloud->points.end(),[&](const PointType& p1, const PointType& p2) { return p1.z < p2.z; });// extract init ground seedsdouble lpr_avg_height = 0;size_t lpr_num = static_cast<size_t>(lpr_least_gpoints_rate *sort_cloud->points.size());for (size_t i = 0; i < lpr_num; i++) {lpr_avg_height += sort_cloud->points[i].z;}lpr_avg_height /= lpr_num;CloudPtr pc_for_ground(new sad::PointCloudType);for (size_t i = 0; i < sort_cloud->points.size(); i++) {if (sort_cloud->points[i].z <= lpr_avg_height) {pc_for_ground->points.emplace_back(sort_cloud->points[i]);} else {break;}}// ransac fitting iterativelyPlaneParam plane(Eigen::Vector3d::Zero(), 0);PlaneParam last_pp(Eigen::Vector3d::Zero(), 0);for (int iter_cnt = 0; iter_cnt < lpr_max_iters; iter_cnt++) {// fitting plane{// calculate the mean and covstd::vector<Eigen::Vector3d> points;Eigen::Vector3d mean(0.0, 0.0, 0.0);for (size_t j = 0; j < pc_for_ground->points.size(); j++) {PointType point = pc_for_ground->points[j];Eigen::Vector3d temp(point.x, point.y, point.z);mean += temp;points.emplace_back(temp);}mean = mean / pc_for_ground->points.size();Eigen::Matrix3d cov = Eigen::Matrix3d::Zero();for (size_t j = 0; j < points.size(); j++) {Eigen::Vector3d temp = points[j] - mean;cov = cov + temp * temp.transpose();}// svdEigen::JacobiSVD<Eigen::MatrixXd> svd(cov, Eigen::DecompositionOptions::ComputeFullU);plane.normal = (svd.matrixU().col(2));plane.intercept = -(plane.normal.transpose() * mean)(0, 0);}pc_ground->points.clear();for (size_t j = 0; j < pc_for_ground->points.size(); j++) {PointType point = pc_for_ground->points[j];double point_to_plane_dis = std::fabs(plane.normal(0) * point.x + plane.normal(1) * point.y +plane.normal(2) * point.z + plane.intercept);if (point_to_plane_dis <= lpr_fit_dist_thre) {pc_ground->points.emplace_back(point);} }// convergence checkEigen::Vector3d dlt_norm = plane.normal - last_pp.normal;double dlt_intcpt = plane.intercept - last_pp.intercept;if (dlt_norm.norm() < 0.001 && dlt_intcpt < 0.01 && iter_cnt > lpr_max_iters / 2){LOG(INFO) << "ExtractGroundPlane success!!!";break;}last_pp = plane;}return;
}

地面提取效果: 地面提取效果: 地面提取效果:
在这里插入图片描述

4.2 对地面点进行ICP

相关变量声明:
在这里插入图片描述
在这里插入图片描述
核心代码实现:

if (options_.use_ground_points_) {std::for_each(std::execution::par_unseq, index_ground.begin(), index_ground.end(), [&](int idx) {Vec3d q = ToVec3d(ground->points[idx]);Vec3d qs = pose * q;// 检查最近邻std::vector<int> nn_indices;kdtree_ground_.GetClosestPoint(ToPointType(qs), nn_indices, 5);effect_ground[idx] = false;if (nn_indices.size() == 5) {std::vector<Vec3d> nn_eigen;for (auto& n : nn_indices) nn_eigen.emplace_back(ToVec3d(local_map_ground_->points[n]));// 点面残差Vec4d n;if (!math::FitPlane(nn_eigen, n)) return;double dis = n.head<3>().dot(qs) + n[3];if (fabs(dis) > options_.max_plane_distance_) return;effect_ground[idx] = true;// build residualEigen::Matrix<double, 1, 6> J;J.block<1, 3>(0, 0) = -n.head<3>().transpose() * pose.so3().matrix() * SO3::hat(q);J.block<1, 3>(0, 3) = n.head<3>().transpose();jacob_ground[idx] = J;errors_ground[idx] = dis;}});}for (const auto& idx : index_ground) {if (effect_ground[idx]) {H += jacob_ground[idx].transpose() * jacob_ground[idx];err += -jacob_ground[idx].transpose() * errors_ground[idx];effective_num++;total_res += errors_ground[idx] * errors_ground[idx];}}

实现效果:
①仅使用提取的地面点来进行定位:(可以看到效果非常不好,地面提取算法可能还需要优化,也有可能本身只使用地面点来配准就效果很差,这个可以继续尝试优化。)
在这里插入图片描述
②使用角点和地面点一起定位:(明显角点的匹配很好的把地面匹配的结果矫正了,但是一开始部分还是不行。)在这里插入图片描述

5. 也欢迎大家来我的微信公众号–过千帆,来读书,提高我们的认知。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/226320.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从菜鸟到专业人士:来自真实 PRD 写作经验的 5 个改变游戏规则的产品管理课程

产品管理是一个复杂且具有挑战性的角色。PRD&#xff08;即产品需求文档&#xff09;就像构建产品的蓝图&#xff0c;可将其视为指导开发团队创造令人惊叹的产品的详细路线图。本文除了介绍产品经理如何写好PRD&#xff0c;也将进一步阐述产品经理应该学习哪些技能&#xff1f;…

玩转树莓派之系统安装篇

介绍 树莓派是树莓派基金会下的一个明星产品&#xff08;单板计算机&#xff09;&#xff0c;已经迭代到第五代了&#xff1b;它性能强大、开源、拓展性强、体积小&#xff0c;搞物联网开发的人基本都听说过这个玩意&#xff01;笔者手上刚好有一块4B的板子&#xff0c;让我们…

python封装执行cmd命令的方法

一、前置说明 在自动化时&#xff0c;经常需要使用命令行工具与系统进行交互&#xff0c;因此可以使用python封装一个执行cmd命令的方法。 二、代码实现 import subprocess import timefrom common.exception import RunCMDError from common.logger import loggerclass Cmd…

STM32储存器和总线构架

一、引言 本篇文章旨在介绍STM32小容量、中容量和大容量的储存器和系统构架&#xff0c;文中涉及到一些专有名词和概念较为抽象和陌生&#xff0c;建议读者能够查阅相关资料和知识加深了解。 二、正文 &#xff08;一&#xff09;、系统构架 在小容量、中容量和 大容量产品中…

在qemu平台使用gdb调试程序

1、使用gdb在qemu上调试程序 1.1、第一步&#xff1a;在qemu上运行程序并开启gdb server qemu-system-riscv64 -nographic -machine virt -m 128M -smp 1 -kernel …/bin/test.elf -s -S 1.2、第二步&#xff1a;使用gdb客户端连接gdb server -x&#xff1a;指定gdb的配置文件…

jmeter 压测需要的部分配置

修改jmeter 目录的bin目录下的jmeter.properties文件 解除KeepAlive设置 修改接口的高级中的实现和超时 解除httpclient4.retrycount前的注释符并将0修改为1 即修改为&#xff1a;httpclient4.retrycount1 解除httpclient4.idletimeout前的注释符并修改为合适间隔 即修改为…

创建型模式之工厂方法模式

一、概述 1、工厂方法模式&#xff1a;定义一个用于创建对象的接口&#xff0c;让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类 2、工厂方法模式&#xff1a;不再提供一个按钮工厂类来统一负责所有产品的创建&#xff0c;而是将具体的按钮创建过程交…

电子电工企业品牌网站建设的作用是什么

电子电工企业在市场中有较高的需求度&#xff0c;比如电子元件、电子产品等&#xff0c;这些都属于高信任度产品&#xff0c;对需求方来说&#xff0c;需要查看商家全部信息、包括资质、产品/服务内容、案例等&#xff0c;因此对电子电工企业来讲&#xff0c;需要贯通品牌路径&…

解决RuntimeError: CUDA error: invalid device ordinal

步骤 首先查看自己设备的cuda版本 #如下linux指令都可以&#xff0c;主要还是以nvidia-smi为主 nvidia-smi nvcc -V用的python版本是3.8 torch版本用的1.12.1cu113 torch网址&#xff1a;https://pytorch.org/get-started/previous-versions/ 安装完后发现出现如下问题&#…

前端路由钩子的神奇之处:你真的了解它们吗?(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

CSS 基础

文章目录 CSS 常见的属性CSS 常见样式行内样式内嵌样式导入样式 CSS 选择器标签选择器id选择器类选择器全局选择器属性选择器组合选择器 CSS 常见应用表格列表导航栏下拉菜单提示工具图片廊 CSS (Cascading Style Sheets&#xff0c;层叠样式表&#xff09;&#xff0c;是一种用…

数据库连接池Druid

在 Spring Boot 项目中&#xff0c;数据库连接池已经成为标配&#xff0c;然而&#xff0c;我曾经遇到过不少连接池异常导致业务错误的事故。很多经验丰富的工程师也可能不小心在这方面出现问题。 在这篇文章中&#xff0c;我们将探讨数据库连接池&#xff0c;深入解析其实现机…

【卡塔尔世界杯数据可视化与新闻展示】

卡塔尔世界杯数据可视化与新闻展示 前言数据获取与处理可视化页面搭建功能实现新闻信息显示详情查看登录注册评论信息管理 创新点结语 前言 随着卡塔尔世界杯的临近&#xff0c;对于足球爱好者来说&#xff0c;对比赛的数据分析和新闻报道将成为关注的焦点。本文将介绍如何使用…

openmediavault debian linux安装配置企业私有网盘(三 )——raid5与btrfs文件系统无损原数据扩容

一、适用环境 1、企业自有物理专业服务器&#xff0c;一些敏感数据不外流时&#xff0c;使用openmediavault自建NAS系统&#xff1b; 2、在虚拟化环境中自建NAS系统&#xff0c;用于内网办公&#xff0c;或出差外网办公时&#xff0c;企业内的文件共享&#xff1b; 3、虚拟化环…

jmeter配置使用(mac)

前言 这篇文件就是一个笔记&#xff0c;非mac用户不用看了&#xff0c;我这是换了mac&#xff0c;要用jmeter的倒腾。 一、下载 二、使用步骤 1.解压 tgz格式的直接用tar命令就行 tar -zxvf 包名2.启动 一种是进入解压包的bin目录启动 这种方式启动的就是命令框不能关闭&am…

正则表达式详解

什么是正则表达式 正则表达式&#xff0c;又称规则表达式&#xff0c;通常被用来检索、替换那些符合某个模式(规则)的文本。 正则表达式是对字符串操作的一种逻辑公式&#xff0c;就是用事先定义好的一些特定字符、及这些特定字符的组合&#xff0c;组成一个"规则字符串…

小区生活污水处理需要哪些设备和工艺

在小区生活中&#xff0c;污水处理是一个非常重要的环节&#xff0c;它关乎到环境的保护和居民的生活质量。因此&#xff0c;了解小区生活污水处理所需要的设备和工艺是至关重要的。 首先&#xff0c;在小区生活污水处理中&#xff0c;需要用到的设备包括污水收集系统、初级沉淀…

多微信聚合聊天:一款简便的管理工具帮你摆脱微信繁琐之困!

在这个信息飞速传递的时代&#xff0c;微信已经成为了人们不可或缺的通信工具之一。然而&#xff0c;由于个人、工作等各种原因&#xff0c;我们可能会拥有多个微信号&#xff0c;而每天切换不同的账号进行聊天&#xff0c;无疑是一项繁琐的任务。 近日&#xff0c;我一位好友向…

互联网加竞赛 python+opencv+机器学习车牌识别

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于机器学习的车牌识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;4分工作量&#xff1a;4分创新点&#xff1a;3分 该项目较为新颖&#xff0c;适…

算法:程序员的数学读书笔记

目录 ​0的故事 ​一、按位计数法 二、不使用按位计数法的罗马数字 三、十进制转二进制​​​​​​​ ​四、0所起到的作用​​​​​​​ 逻辑 一、为何逻辑如此重要 二、兼顾完整性和排他性 三、逻辑 四、德摩根定律 五、真值表 六、文氏图 七、卡诺图 八、逻…