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

2023-12-17

在这里插入图片描述

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.0
                    double 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 plane
    CloudPtr 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 seeds
    double 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 iteratively
    PlaneParam 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 cov
            std::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();
            }
            // svd
            Eigen::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 check
        Eigen::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 residual
                    Eigen::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. 也欢迎大家来我的微信公众号–过千帆,来读书,提高我们的认知。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 题解 | #复制部分字符串#

    拒了华为 重回0 offer 目前在大三 寒假想找个实习 退役大学生 如题 uu们帮忙看看 25届 没有实习过 没有背过八股文 心里感觉很不稳 下学期想去暑期实习 uu们 德赛西威鸽 在中国电信公司工作一年后 我提桶跑路 东北辽宁就业求职好
  • 24届双非一本想转行测试,不知道从哪开始学,求佬指教

    避雷西安华为海思某部门 四大行软开校招值得去吗 细节见品格 北京下大雪后的各大厂动作 回暖分析 战绩结算 on 赛文X 选offer 找实习需要实习经历 华为小奖状 夸夸我导师 别羡慕我 嘿嘿 华为od前端技术面 华为海思本科14级 国家计
  • Deep learning 八

    2 使用预训练的词嵌入 有时可用的训练数据很少 以至于只用手头数据无法学习适合特定任务的词嵌人 那么可以从预计算的嵌人空间中加载嵌入向量 这个嵌人空间是高度结构化的 并且具有有用的属性 即抓住了语言结构的一般特点 而不是在解决问题的同时学习
  • Go 语言中切片的使用和理解

    切片与数组类似 但更强大和灵活 与数组一样 切片也用于在单个变量中存储相同类型的多个值 然而 与数组不同的是 切片的长度可以根据需要增长和缩小 在 Go 中 有几种创建切片的方法 使用 datatype values 格式 从数组创建切片
  • 开题报告-基于SpringBoot的求职招聘系统的设计与实现

    一 选题的根据 由于临近毕业季 同学们也即将踏上自己的岗位 择业也成为了同学们当下最为关心的问题 为了能够更加方便的服务同学们找工作 最快最有效率的方式莫过于计算机网络 所以我就因此开发了这一个求职招聘系统 为广大求职者和各企业的人事单位提
  • 24届双非一本想转行测试,不知道从哪开始学,求佬指教

    避雷西安华为海思某部门 四大行软开校招值得去吗 细节见品格 北京下大雪后的各大厂动作 回暖分析 战绩结算 on 赛文X 选offer 找实习需要实习经历 华为小奖状 夸夸我导师 别羡慕我 嘿嘿 华为od前端技术面 华为海思本科14级 国家计
  • 介绍一下傻傻分不清的两个兄弟:过滤器和拦截器之拦截器

    那么拦截器又是什么呢 它跟过滤器又有什么区别呢 实际上 拦截器可以被视为一种对过滤器的封装 在Spring框架中 拦截器提供了更加灵活和强大的功能 可以与Spring MVC等框架无缝集成 并且通常用于处理请求的前置和后置逻辑 拦截器可以实
  • FLStudio20最新2024年中文汉化版

    FLStudio21 0 2 3中文版完整下载是最好的音乐开发和制作软件也称为水果循环 它是最受欢迎的工作室 因为它包含了一个主要的听觉工作场所 最新 FL 有不同的功能 如它包含图形和音乐音序器 帮助您使完美的配乐在一个美妙的方式 此程序
  • 讯飞AI算法挑战大赛-校招简历信息完整性检测挑战赛-三等奖方案

    前言 本文公开了 讯飞AI算法挑战大赛 校招简历信息完整性检测挑战赛 赛道的技术方案和代码 本次比赛主要采用 pdf解析 和 特征工程 的方法 通过使用 lightgbm 的树模型10折交叉验证进行 二分类 的任务 最终取得三等奖的成绩 一
  • 【万字长文】搭建企业级知识库检索增强的大模型对话系统

    01 背景 ChatGPT和通义千问等大语言模型 LLM 凭借其强大的自然语言处理能力 正引领着人工智能技术的革命 但LLM在生成回复时 在 事实性 实时性 等方面存在天然的缺陷 很难直接被用于客服 答疑等一些需要精准回答的领域知识型问答场
  • React脚手架搭建

    React脚手架 脚手架 可以快速构建项目的基本架构 脚手架安装命令 可全局安装脚手架 创建项目 来到当前目录下 create react app 项目名 不要大写字母 运行项目 进到项目里 在项目目录下 执行 npm start 启动完项
  • 62_Spring整合SpringMVC

    Spring整合SpringMVC Configuration ComponentScan basePackages com wnx springmvc useDefaultFilters false includeFilters Comp
  • 中国知网职称论文查重官网入口 papergpt

    大家好 今天来聊聊中国知网职称论文查重官网入口 希望能给大家提供一点参考 以下是针对论文重复率高的情况 提供一些修改建议和技巧 可以借助此类工具 中国知网职称论文查重官网入口 背景介绍 作为中国最大的学术文献数据库 中国知网 CNKI 提供
  • 揭秘光耦合器继电器:了解技术奇迹

    光耦合器继电器 是现代电子产品的关键部件 在确保电路安全和效率方面发挥着关键作用 了解它们的功能和意义对于工程师和爱好者理解它们的应用至关重要 本文旨在揭开光耦合器继电器技术方面的神秘面纱 深入了解其功能 应用以及在电子领域的重要性 什么是
  • Webpack5

    一 Webpack基础 打包工具 将框架 React Vue ES6 Less Sass等语法编译成浏览器能识别的JS CSS 压缩代码 兼容性处理 提升代码性能等 一 entry 入口 指示Webpack 从哪个文件开始打包 二 outp
  • GEE 24:基于GEE实现高空间分辨率物种分布模型的模拟

    高分辨率物种分布模型模拟 1 加载数据并定义网格大小和范围 2 预测变量 3 定义模型拟合和交叉验证的空间块 使用未分类的卫星图像作为预测变量 以高空间分辨率对物种分布进行建模 1 加载数据并定义网格大小和范围 对于本例 我们从 GBIF获
  • 计算机毕设项目 - 停车位租赁管理系统(含源码+论文)

    文章目录 1 项目简介 2 实现效果 2 1 界面展示 3 设计方案 3 1 概述 3 2 系统业务流程 3 3 系统结构设计 4 项目获取
  • 59_Spring整合MyBatisPlus

    Spring整合MyBatisPlus
  • 数据结构与算法之递归: LeetCode 93. 复原 IP 地址 (Typescript版)

    复原 IP 地址 https leetcode cn problems restore ip addresses 描述 有效 IP 地址 正好由四个整数 每个整数位于 0 到 255 之间组成 且不能含有前导 0 整数之间用 分隔 例如 0
  • 6.【自动驾驶与机器人中的SLAM技术】鲁邦核函数的含义和应用

    目录 1 给ICP和NDT配准添加柯西核函数 1 1 代码实现 2 将第1部分的robust loss引入IncNDTLO和LooselyLIO 给出实现和运行效果 3 从概率层面解释NDT残差和协方差矩阵的关系 说明为什么NDT协方差矩阵