5.【自动驾驶与机器人中的SLAM技术】2D点云的scan matching算法 和 检测退化场景的思路

2023-12-05

在这里插入图片描述

1. 基于优化的点到点/线的配准

这里实现了基于g2o优化器的优化方法。
图优化中涉及两个概念-顶点和边。我们的优化变量认为是顶点,误差项就是边。我们通过g2o声明一个图模型,然后往图模型中添加顶点和与顶点相关联的边,再选定优化算法(比如LM)就可以进行优化了。 想熟悉g2o的小伙伴们感兴趣的话,可以到这个链接看一下。
g2o的基本框架和编程套路如下图:
在这里插入图片描述
在这里插入图片描述
基于g2o的点对点的配准算法代码实现如下:

bool Icp2d::AlignWithG2oP2P(SE2& init_pose)
{
    double rk_delta = 0.8;    // 核函数阈值
    float max_dis2 = 0.01;    // 最近邻时的最远距离(平方)
    int min_effect_pts = 20;  // 最小有效点数

    // 构建图优化,先设定g2o
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<3, 2>> BlockSolverType;  // 每个误差项优化变量维度为3, 误差值维度是2
    typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType; // 线性求解器类型
    // 梯度下降方法,可以从GN, LM, DogLeg 中选
    auto solver = new g2o::OptimizationAlgorithmLevenberg(
            g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));

    g2o::SparseOptimizer optimizer;   // 图模型
    optimizer.setAlgorithm(solver);   // 设置求解器
    optimizer.setVerbose(false);       // 打开调试输出

    // 往图中添加顶点
    VertexSE2 *v = new VertexSE2(); // 新建SE2位姿顶点
    v->setId(0);                    // 设置顶点的id
    v->setEstimate(init_pose);      // 设置顶点的估计值为初始位姿
    optimizer.addVertex(v);         // 将顶点添加到优化器中

    // 往图中添加边
    int effective_num = 0;  // 有效点数
    // 遍历source
    for (size_t i = 0; i < source_scan_->ranges.size(); ++i) 
    {
        float range = source_scan_->ranges[i];
        if (range < source_scan_->range_min || range > source_scan_->range_max) 
        {
            continue;
        }

        float angle = source_scan_->angle_min + i * source_scan_->angle_increment;
        float theta = v->estimate().so2().log();
        Vec2d pw = v->estimate() * Vec2d(range * std::cos(angle), range * std::sin(angle));
        Point2d pt;
        pt.x = pw.x();
        pt.y = pw.y();

        // 最近邻
        std::vector<int> nn_idx;
        std::vector<float> dis;
        kdtree_.nearestKSearch(pt, 1, nn_idx, dis);

        if (nn_idx.size() > 0 && dis[0] < max_dis2) 
        {
            effective_num++;
            Vec2d qw = Vec2d(target_cloud_->points[nn_idx[0]].x, target_cloud_->points[nn_idx[0]].y);   // 当前激光点在目标点云中的最近邻点坐标
            auto *edge = new EdgeIcpP2P(range, angle, theta);   // 构建约束边
            edge->setVertex(0, v);                   // 设置连接的顶点
            edge->setMeasurement(qw);                // 观测,即最近邻
            edge->setInformation(Mat2d::Identity()); // 观测为2维点坐标,因此信息矩阵需设为2x2单位矩阵
            auto rk = new g2o::RobustKernelHuber;    // Huber鲁棒核函数
            rk->setDelta(rk_delta);                  // 设置阈值
            edge->setRobustKernel(rk);               // 为边设置鲁棒核函数
            optimizer.addEdge(edge);                 // 将约束边添加到优化器中
        }
    }

    if (effective_num < min_effect_pts) {
        return false;
    }

    // 执行优化
    optimizer.initializeOptimization(); // 初始化优化器
    optimizer.optimize(10);             // 优化

    init_pose = v->estimate();

    LOG(INFO) << "g2o estimated pose: " << v->estimate().translation().transpose() << ", theta: " << v->estimate().so2().log();
    return true;
}

效果展示
在这里插入图片描述
基于g2o的点对线的配准算法代码实现如下:

bool Icp2d::AlignWithG2oP2Plane(SE2& init_pose)
{
    double rk_delta = 0.8;    // 核函数阈值
    float max_dis2 = 0.01;    // 最近邻时的最远距离(平方)
    int min_effect_pts = 20;  // 最小有效点数

    // 构建图优化,先设定g2o
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<3, 1>> BlockSolverType;  // 每个误差项优化变量维度为3, 误差值维度是1
    typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType; // 线性求解器类型
    // 梯度下降方法,可以从GN, LM, DogLeg 中选
    auto solver = new g2o::OptimizationAlgorithmLevenberg(
            g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));

    g2o::SparseOptimizer optimizer;   // 图模型
    optimizer.setAlgorithm(solver);   // 设置求解器
    optimizer.setVerbose(true);       // 打开调试输出

    // 往图中添加顶点
    VertexSE2 *v = new VertexSE2(); // 新建SE2位姿顶点
    v->setId(0);                    // 设置顶点的id
    v->setEstimate(init_pose);      // 设置顶点的估计值为初始位姿
    optimizer.addVertex(v);         // 将顶点添加到优化器中

    // 往图中添加边
    int effective_num = 0;  // 有效点数
    // 遍历source
    for (size_t i = 0; i < source_scan_->ranges.size(); ++i) 
    {
        float range = source_scan_->ranges[i];
        if (range < source_scan_->range_min || range > source_scan_->range_max) 
        {
            continue;
        }

        float angle = source_scan_->angle_min + i * source_scan_->angle_increment;
        float theta = v->estimate().so2().log();
        Vec2d pw = v->estimate() * Vec2d(range * std::cos(angle), range * std::sin(angle));
        Point2d pt;
        pt.x = pw.x();
        pt.y = pw.y();

        // 查找5个最近邻
        std::vector<int> nn_idx;
        std::vector<float> dis;
        kdtree_.nearestKSearch(pt, 5, nn_idx, dis);

        std::vector<Vec2d> effective_pts;  // 有效点
        for (int j = 0; j < nn_idx.size(); ++j) 
        {
            if (dis[j] < max_dis2) 
            {
                effective_pts.emplace_back(
                    Vec2d(target_cloud_->points[nn_idx[j]].x, target_cloud_->points[nn_idx[j]].y));
            }
        }

        if (effective_pts.size() < 3) 
        {
            continue;
        }

        // 拟合直线,组装J、H和误差
        Vec3d line_params;
        if (math::FitLine2D(effective_pts, line_params)) {
            effective_num++;
            auto *edge = new EdgeIcpP2Plane(range, angle, theta, line_params);   // 构建约束边
            edge->setVertex(0, v);                   // 设置连接的顶点
            edge->setInformation(Eigen::Matrix<double, 1, 1>::Identity()); // 误差为1维
            auto rk = new g2o::RobustKernelHuber;    // Huber鲁棒核函数
            rk->setDelta(rk_delta);                  // 设置阈值
            edge->setRobustKernel(rk);               // 为边设置鲁棒核函数
            optimizer.addEdge(edge);                 // 将约束边添加到优化器中
        }
    }

    if (effective_num < min_effect_pts)
    {
        return false;
    }

    // 执行优化
    optimizer.initializeOptimization(); // 初始化优化器
    optimizer.optimize(10);             // 优化10次

    init_pose = v->estimate();

    LOG(INFO) << "g2o estimated pose: " << v->estimate().translation().transpose() << ", theta: " << v->estimate().so2().log();
    return true;
}

其中,直线拟合的代码为: 其中,直线拟合的代码为: 其中,直线拟合的代码为:

template <typename S>
bool FitLine2D(const std::vector<Eigen::Matrix<S, 2, 1>>& data, Eigen::Matrix<S, 3, 1>& coeffs) {
    if (data.size() < 2) {
        return false;
    }

    Eigen::MatrixXd A(data.size(), 3);
    for (int i = 0; i < data.size(); ++i) {
        A.row(i).head<2>() = data[i].transpose();
        A.row(i)[2] = 1.0;
    }

    Eigen::JacobiSVD svd(A, Eigen::ComputeThinV);
    coeffs = svd.matrixV().col(2);
    return true;
}

效果展示
在这里插入图片描述

2. 对似然场图像进行插值,提高匹配精度

高博书中分别实现了基于g2o和手写高斯牛顿两种方法,其中手写高斯牛顿法中没有使用插值,在g2o方法中用到了插值。这里直接将其挪用到手写高斯牛顿法中。所以这里不做赘述,只来介绍一下双线性插值的过程。

// bilinear interpolation
template <typename T>
inline float GetPixelValue(const cv::Mat& img, float x, float y) {
    // boundary check
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    if (x >= img.cols) x = img.cols - 1;
    if (y >= img.rows) y = img.rows - 1;
    const T* data = &img.at<T>(floor(y), floor(x));
    float xx = x - floor(x);
    float yy = y - floor(y);
    return float((1 - xx) * (1 - yy) * data[0] + xx * (1 - yy) * data[1] + (1 - xx) * yy * data[img.step / sizeof(T)] +
                 xx * yy * data[img.step / sizeof(T) + 1]);
}

双线性插值的过程如图:
在这里插入图片描述

对应到程序中:
a = y y ; a=yy; a = yy ;
b = x x ; b=xx; b = xx ;
v 1 = d a t a [ 0 ] ; v1=data[0]; v 1 = d a t a [ 0 ] ;
v 2 = d a t a [ 1 ] ; v2=data[1]; v 2 = d a t a [ 1 ] ;
v 3 = d a t a [ i m g . s t e p / s i z e o f ( T ) ] ; v3=data[img.step / sizeof(T)]; v 3 = d a t a [ im g . s t e p / s i zeo f ( T )] ;
v 4 = d a t a [ i m g . s t e p / s i z e o f ( T ) + 1 ] ; v4=data[img.step / sizeof(T) + 1]; v 4 = d a t a [ im g . s t e p / s i zeo f ( T ) + 1 ] ;
===================================>可得:
v 7 = f l o a t ( ( 1 − x x ) ∗ ( 1 − y y ) ∗ d a t a [ 0 ] + x x ∗ ( 1 − y y ) ∗ d a t a [ 1 ] + ( 1 − x x ) ∗ y y ∗ d a t a [ i m g . s t e p / s i z e o f ( T ) ] + x x ∗ y y ∗ d a t a [ i m g . s t e p / s i z e o f ( T ) + 1 ] ) ; v7=float((1 - xx) * (1 - yy) * data[0] + xx * (1 - yy) * data[1] + (1 - xx) * yy * data[img.step / sizeof(T)] + xx * yy * data[img.step / sizeof(T) + 1]); v 7 = f l o a t (( 1 xx ) ( 1 yy ) d a t a [ 0 ] + xx ( 1 yy ) d a t a [ 1 ] + ( 1 xx ) yy d a t a [ im g . s t e p / s i zeo f ( T )] + xx yy d a t a [ im g . s t e p / s i zeo f ( T ) + 1 ]) ;

效果展示
在这里插入图片描述

3. 对二维激光点云中会对SLAM功能产生退化场景的检测

视频中高博讲到对于2DSLAM来说,可以对点云进行直线拟合,比较场景中直线方向是否“大体一致”。
具体思路:可以对场景中的点云进行聚类,然后对每个类簇中的点进行直线拟合,保存所有直线的系数。参考提示中的条件判断实现该功能。

在这里插入图片描述
实现代码如下:

bool Icp2d::IsDegeneration()
{
    if (target_cloud_->empty()) 
    {
        LOG(ERROR) << "cannot load cloud...";
        return false;
    }

    int point_size = target_cloud_->size();
    if(point_size < 500) return true; // 点数太少,空旷退化场景

    LOG(INFO) << "traget_cloud size = " << point_size;

    PointCloudType::Ptr target_cloud(new PointCloudType());
    // 构建点云
    for (size_t i = 0; i < target_scan_->ranges.size(); ++i)
    {
        if (target_scan_->ranges[i] < target_scan_->range_min || target_scan_->ranges[i] > target_scan_->range_max) 
        {
            continue;
        }

        double real_angle = target_scan_->angle_min + i * target_scan_->angle_increment;

        PointType p;
        p.x = target_scan_->ranges[i] * std::cos(real_angle);
        p.y = target_scan_->ranges[i] * std::sin(real_angle);
        p.z = 1.0;
        target_cloud->points.push_back(p);
    }

    pcl::search::KdTree<PointType>::Ptr kdtree; // (new pcl::search::KdTree<PointType>)
    kdtree = boost::make_shared<pcl::search::KdTree<PointType>>();
    // 构建kdtree
    kdtree->setInputCloud(target_cloud);
        
    pcl::EuclideanClusterExtraction<PointType> clusterExtractor;
    // 创建一个向量来存储聚类的结果
    std::vector<pcl::PointIndices> cluster_indices;
    clusterExtractor.setClusterTolerance(0.02);        // 设置聚类的距离阈值
    clusterExtractor.setMinClusterSize(10);            // 设置聚类的最小点数
    clusterExtractor.setMaxClusterSize(1000);          // 设置聚类的最大点数 
    clusterExtractor.setSearchMethod(kdtree);         // 使用kdtree树进行加速
    clusterExtractor.setInputCloud(target_cloud);             // 设置点云聚类对象的输入点云数据
    clusterExtractor.extract(cluster_indices);         // 执行点云聚类
    LOG(INFO) << "cluster size: " << cluster_indices.size();

    // 创建可视化对象
    pcl::visualization::PCLVisualizer viewer("Cluster Viewer");
    viewer.setBackgroundColor(1.0, 1.0, 1.0);
    viewer.addPointCloud<PointType>(target_cloud, "cloud");
    
    int clusterNumber = 0;  // 输出聚类结果
    std::vector<Eigen::Vector3d> line_coeffs_vector;

    for (const auto& indices : cluster_indices) 
    {
        LOG(INFO) << "Cluster " << clusterNumber << " has " << indices.indices.size() << " points.";
        pcl::PointCloud<PointType>::Ptr cluster(new pcl::PointCloud<PointType>);
        pcl::copyPointCloud(*target_cloud, indices, *cluster);

        // 拟合直线
        std::vector<Eigen::Vector2d> pts;
        pts.reserve(cluster->size());

        for (const PointType& pt : *cluster)
        {
            pts.emplace_back(Eigen::Vector2d(pt.x, pt.y));
        }
            
        // 拟合直线,组装J、H和误差
        Eigen::Vector3d line_coeffs;
        // 利用当前点附近的几个有效近邻点,基于SVD奇异值分解,拟合出ax+by+c=0 中的最小直线系数 a,b,c,对应公式(6.11)
        if (math::FitLine2D(pts, line_coeffs)) 
        {
            line_coeffs_vector.push_back(line_coeffs);
        }

        // 为聚类分配不同的颜色
        double r = static_cast<double>(rand()) / RAND_MAX;
        double g = static_cast<double>(rand()) / RAND_MAX;
        double b = static_cast<double>(rand()) / RAND_MAX;

        // 将聚类点云添加到可视化对象中
        std::string clusterId = "cluster_" + std::to_string(clusterNumber);
        viewer.addPointCloud<PointType>(cluster, clusterId);
        viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, r, g, b, clusterId);
        clusterNumber++;
    }

    int lines_size = line_coeffs_vector.size();
    LOG(INFO) << "clusterNumber = " << clusterNumber << " lines_size = " << lines_size;
    if(clusterNumber == lines_size)
    {
        if(clusterNumber == 1) return true; // 如果仅有一条直线,就是退化场景
        float max_score = 0;
        for(int i = 0; i < lines_size - 1; ++i)
        {
            for(int j = i+1; j < lines_size; ++j)
            {
                float current_score = line_coeffs_vector[i].cross(line_coeffs_vector[j]).norm();
                LOG(INFO) << "current_score = " << current_score;
                max_score = current_score > max_score ? current_score : max_score;
            }
        }

        LOG(INFO) << "mas_score = " << max_score;
        if(max_score < 0.3) return true; // 叉积小于阈值,代表是退化场景,直线差不多都平行
    }

    if (!viewer.wasStopped()) 
    {
        viewer.spinOnce(0.001);
		sleep(1);  //延时函数,不加的话刷新太快会看不到显示效果
        viewer.close();
    }
    return false;
}

在这里插入图片描述
上图为聚类结果,下面是程序运行时效果。
在这里插入图片描述
效果展示
在这里插入图片描述

4. 在诸如扫地机器人等这样基于2D激光雷达导航的机器人,如何处理悬空/低矮物体

在这里插入图片描述
在二维SLAM方案中,如果场景中存在其他高度的障碍物或物体形状随着高度变化的情况,可以采取一些方法来处理悬空物体和低矮物体,以避免机器人与它们发生碰撞。以下是几种思路:

①可以给机器人添加三维传感器,比如RGB-D相机,将相机构建的三维地图点投影到激光雷达构建的二维栅格地图中,使得二维地图中包含高度信息。具体做法如文献[1]孙健, 刘隆辉, 李智, 杨佳玉, & 申奥. (2022). 基于rgb-d相机和激光雷达的传感器数据融合算法. 湖南工程学院学报:自然科学版, 32(1), 7.中描述的“首先将RGB-D相机获取的深度图像转换为三维点云,剔除地面点云后进行投影得到模拟2D激光数据,然后与二维激光雷达数据进行点云配准,以获取两个传感器之间的位姿关系,最后通过扩展卡尔曼滤波(EKF)将两组激光数据进行融合.实验结果表明该方法能综合利用相机和激光雷达传感器优势,有效提高机器人环境感知的完整性和建图精度. ”
②由于2D SLAM生成的占据栅格地图,是基于像素的黑白灰三色地图,我们可以人工对此地图进行“加工”,对于特定场景中存在的要躲避的三维物体预先建模,在二维栅格地图中标注出他们的位置以及高度信息,来帮助机器人更好的躲避他们。
③尝试使用普通相机对环境中的物体进行语义识别,然后把需要躲避的三维物体投影到二维平面,“告诉”机器人前面有个“物体”阻挡,不具备通过的条件。

5. 也欢迎大家来我的读书号–过千帆,学习交流。

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

5.【自动驾驶与机器人中的SLAM技术】2D点云的scan matching算法 和 检测退化场景的思路 的相关文章

  • 【Leetcode】49. 字母异位词分组

    49 字母异位词分组 题目链接 代码一 代码二 题目链接 Leetcode 49 字母异位词分组 代码一 func groupAnagrams strs string string 存放字典序相同的字符串切片 hash map string
  • 蒙牛×每日互动合作获评中国信通院2023“数据+”行业应用优秀案例

    当前在数字营销领域 品牌广告主越来越追求品效协同 针对品牌主更注重营销转化的切实需求 数据智能上市企业每日互动 股票代码 300766 发挥自身数据和技术能力优势 为垂直行业的品牌客户提供专业的数字化营销解决方案 颇受行业认可 就在不久前举
  • 高翔博士Faster-LIO论文和算法解析

    说明 题目 Faster LIO 快速激光IMU里程计 参考链接 Faster LIO 快速激光IMU里程计 iVox Faster Lio 智行者高博团队开源的增量式稀疏体素结构 Faster Lio是高翔博士在Fast系列的新作 对标基
  • 工业异常检测AnomalyGPT-Demo试跑

    写在前面 如果你有大的cpu和gpu可以使用 直接根据官方的安装说明就可以 如果没有 可以点进来试着看一下我个人的安装经验 一 试跑环境 NVIDIA4090显卡24g cpu内存33G 交换空间8g 操作系统ubuntu22 04 试跑过
  • D - Loong and Takahashi (经典模拟绕圈)

    题目 https atcoder jp contests abc335 tasks abc335 d 思想 令 flag 0 1 2 3 分别代表四个方向右 下 左 上 然后判断下一步是否超过边界或者被填充过 如果是 就换方向 最后输出 代
  • CCF模拟题 202309-1 坐标变换(其一)

    问题描述 试题编号 202309 1 试题名称 坐标变换 其一 时间限制 1 0s 内存限制 512 0MB 问题描述 对于平面直角坐标系上的坐标 x y 小P定义了一个包含n个操作序列T t1 t2 tn 其中每个操作ti 1 lt i
  • 华为OD机试真题-求满足条件的最长子串的长度-2023年OD统一考试(C卷)

    题目描述 给定一个字符串 只包含字母和数字 按要求找出字符串中的最长 连续 子串的长度 字符串本身是其最长的子串 子串要求 1 只包含1个字母 a z A Z 其余必须是数字 2 字母可以在子串中的任意位置 如果找不到满足要求的子串 如全是
  • 用通俗易懂的方式讲解:大模型 RAG 在 LangChain 中的应用实战

    Retrieval Augmented Generation RAG 是一种强大的技术 能够提高大型语言模型 LLM 的性能 使其能够从外部知识源中检索信息以生成更准确 具有上下文的回答 本文将详细介绍 RAG 在 LangChain 中的
  • 基于粒子群算法的电动汽车充电动态优化策略研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 数据
  • 【固定翼飞机】基于最优控制的固定翼飞机着陆控制器设计研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码及文章
  • 【C++入门】C++ STL中string常用函数用法总结

    目录 前言 1 string使用 2 string的常见构造 3 string类对象的访问及遍历 迭代器遍历 访问 4 string类对象的容量操作 4 1 size和length 4 2 clear empty和capacity 4 3
  • 2024年华为OD机试真题-小明找位置-Java-OD统一考试(C卷)

    题目描述 小朋友出操 按学号从小到大排成一列 小明来迟了 请你给小明出个主意 让他尽快找到他应该排的位置 算法复杂度要求不高于nLog n 学号为整数类型 队列规模 lt 10000 输入描述 1 第一行 输入已排成队列的小朋友的学号 正整
  • 【路径规划】基于A*算法路径规划研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码实现
  • 华为OD机试2024年最新题库(C++)

    我是一名软件开发培训机构老师 我的学生已经有上百人通过了华为OD机试 学生们每次考完试 会把题目拿出来一起交流分享 重要 2024年1月 5月 考的都是OD统一考试 C卷 题库已经整理好了 命中率95 以上 这个专栏使用 C 解法 问1 考
  • 矩阵基本操作

    问题描述 已知一个n n的矩阵 方阵n lt 100 把矩阵主副对角线上的元素值加上x 然后输出这个新矩阵 输入格式 一行两个变量 用空格隔开 代表n和x 接下来的n行每行n列 表示矩阵的数值 用空格隔开 输出格式 输出新矩阵 每个数字5个
  • 机器学习算法实战案例:Informer实现多变量负荷预测

    文章目录 机器学习算法实战案例系列 答疑 技术交流 1 实验数据集 2 如何运行自己的数据集 3 报错分析 机器学习算法实战案例系
  • 「优选算法刷题」:移动零

    嗨 这个假期罗根开始接触了算法 在为今年的蓝桥杯做准备 所以 开个新专栏 记录记录自己做算法题时的心得 一 题目 给定一个数组 nums 编写一个函数将所有 0 移动到数组的末尾 同时保持非零元素的相对顺序 请注意 必须在不复制数组的情况下
  • 『力扣刷题本』:逆波兰表达式求值

    大家好久不昂 最近 1 个多月罗根一直在备考期末 文章发的很少 现在已经放寒假啦 学习自然也不能拉下 毕竟 4 月份就要去参加蓝桥杯了 先给自己定个小目标 日更 2 篇 咳咳 下面马上开始讲题 一 题目 给你一个字符串数组 tokens 表
  • 史上最全自动驾驶岗位介绍

    作者 自动驾驶转型者 编辑 汽车人 原文链接 https zhuanlan zhihu com p 353480028 点击下方 卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 点击进入 自动驾驶之心 求职交流 技术交流群 本
  • 5_机械臂运动学基础_矩阵

    上次说的向量空间是为矩阵服务的 1 学科回顾 从科技实践中来的数学问题无非分为两类 一类是线性问题 一类是非线性问题 线性问题是研究最久 理论最完善的 而非线性问题则可以在一定基础上转化为线性问题求解 线性变换 数域 F 上线性空间V中的变

随机推荐

  • 牙髓干细胞实验室建设

    科技的不断进步 干细胞研究已经逐渐成为生物医学领域的研究热点 其中 牙髓干细胞因其独特的生物特性和巨大的应用潜力 受到了越来越多科学家的关注 SICOLAB喜格 为了满足这一研究需求 牙髓干细胞实验室的建设显得尤为重要 一 牙髓干细胞实验室
  • 压缩docker在主机的虚拟磁盘容量

    我们在windows里使用docker时会发现 即使我们已经删除了无用的镜像和容器 主机里挂在docker虚拟磁盘的那个盘 可用空间也没有增加 这是因为虚拟磁盘不会自动缩小 这里我分享一个可用的解决方案 1 先通过docker回收空间 do
  • 使用Draw.io制作泳道图

    使用Draw io制作泳道图 一 横向泳道图 1 有标题泳道图 2 无标题泳道图 3 横纵向扩展泳道 二 纵向泳道图
  • 大火的占用网络究竟如何落地?国内首个面向工业级占用网络全栈教程!

    2022年9月的Tesla AI Day上 一种称之为Occupancy Network的占用模型突然出现到大家的视野中 轻语义重几何 更好地辅助自动驾驶系统感知Driverable space 自动驾驶在动静态障碍物感知领域的发展大概分为
  • vscode调试mit6s081Lab

    环境 mit6 s081的实验环境 gdb multiarch 用于gdb调试 vscode调试实质上就是提供个图形化页面 底层还是这个 安装 gdb multiarch sudo apt get install gdb multiarch
  • Kotlin Idioms:提升代码质量与开发效率的秘诀

    Create DTOs POJOs POCOs data class Customer val name String val email String provides a Customer class with the followin
  • 【网络安全】虚假IP地址攻击如何防范?

    在当今的网络时代 虚假IP地址攻击已成为一种新型的网络攻击方式 给网络安全带来了极大的威胁 那么 什么是虚假IP地址攻击 又如何进行溯源和防范呢 本文将为您揭开这一神秘面纱 一 虚假IP地址攻击概述 虚假IP地址攻击是指在网络通信过程中 攻
  • 一份完整的机房管理制度,永远绕不开这些内容

    你们好 我的网工朋友 作为网工人 你很难能忽视机房 机柜 机架这仨玩意儿 初阶网工的日常工作环境之一就是机房了 但是 机柜中设备的放置 电源线的引入 网络线和通信线的管理 对新手老手来说 都是一件繁琐的事情 整理线路是日常工作的重点 毕竟不
  • 2023年湖南理工学院程序设计竞赛新生赛题解

    在大城市求学 求职也是一种幸运 了解校招 分享校招知识的学长来了 这两天看到一个热搜 出生在大城市已经是一种幸运了 那么 180 线的小镇做题家们 能在大城市求学 求职也是另一种幸运 努力 Java线程池的使用和最佳实践 第1章 引言处理并
  • js数组合并的方法,以及各方法的区别

    需求 array1 1 2 3 array2 4 5 6 合并俩数组 打印结果为 1 2 3 4 5 6 思路 方法一 使用concat 方法 concat 方法可以将多个数组合并成一个新数组 不会修改原始数组 优点 它可以合并任意数量的数
  • 2024艾奇KTV电子相册制作软件(只需三步)懒人专用电子相册制作神器

    艾奇KTV电子相册视频制作软件是一款超级强大的电子相册和MV视频制作软件 你可以把你的照片和视频配上音乐和歌词字幕 做成各种格式的视频电子相册 只需三个简单的步骤 您就可以输出各种分辨率和清晰度的高质量视频相册 如4K 1080P高清和手机
  • Sui生态DeFi将参加Builder DAO举办的活动,为期三天畅谈如何Build on Sui

    LeadUp the Night是一个定期举办的MeetUp活动 由Builder DAO邀请区块链各方项目开发者 VC担任本活动的讲师 这个活动旨在促进区块链技术的发展和应用 让参与者有机会开发创新的区块链应用 探索区块链技术的潜力 12
  • 了解拼图软件哪个好用吗?这篇文章带你了解

    当你回忆起童年时光 是否还记得那些用色彩斑斓的拼图组装起来的欢乐时刻呢 那种将碎片聚合在一起的成就感和对完整画面的渴望 仿佛成为了我们生活中最美妙的拼图 而如今 科技进步非常的快速 我们已经不再需要纸板和拼图片 而是可以通过电子设备来进行拼
  • Java多线程并行读取多个文件(二)

    目录 一 Java多线程并行读取多个文件 二 AsynchronousFileChannel 详解 三 异步文件读取 一 Java多线程并行读取多个文件 在考虑性能问题时 多线程并行读取多个文件的实现需要注意一些关键因素 以充分发挥多线程并
  • 文件搜索神器—Everything,结合内网穿透秒变在线搜索神器!

    Everything cpolar搭建在线资料库 实现随时随地访问 文章目录 Everything cpolar搭建在线资料库 实现随时随地访问 前言 1 软件安装完成后 打开Everything 2 登录cpolar官网 设置空白数据隧道
  • Latex正文引用图片编号,防止某张图片删除或调整导致正文序号对应错误

    一 背景 Latex真的是非常好用的论文排版工具 虽然不像word一样是 所见即所得 的可视化方式 但完全不用管格式 包括图片的排版 文字的缩进等等 这在word里调整起来真的是非常麻烦 特别是某个段落 图片修改后 又要重新调整格式 非常的
  • Ubuntu20.04安装向日葵、开机自启、解决windows系统远程黑屏(笔记)

    这里写目录标题 动机 1 Ubuntu20 04 安装向日葵 2 设置开机自启 3 解决windows不可远程的问题 4 大公告成 动机 办公室有个工作站 要比我的笔记本的CPU稍微好一点 用来跑陆面过程 我信心满满的装了个Ubuntu20
  • 什么是离岸公司?有什么作用?

    离岸公司是泛指在离岸法区内依据其离岸公司法规范成立的有限责任公司或股份有限公司 这些公司不能在注册地经营 而主要是在离岸法区以外的地方开展业务活动 离岸公司的主要特点包括高度保密性 无外汇管制和减免税务负担 离岸公司的作用主要有以下几个方面
  • 销售人员一定要知道的6种获取电话号码的方法

    对于销售来说 电话销售是必须要知道的销售方法 也是销售生涯中的必经之路 最开始我们并不清楚这么电话是从哪里来的 也不清楚是通过哪些方法渠道获取 那么今天就来分享给各位销售人员获取客户电话号码的方法 1 打印自己的名片 在工作当中少不了接触其
  • 5.【自动驾驶与机器人中的SLAM技术】2D点云的scan matching算法 和 检测退化场景的思路

    目录 1 基于优化的点到点 线的配准 2 对似然场图像进行插值 提高匹配精度 3 对二维激光点云中会对SLAM功能产生退化场景的检测 4 在诸如扫地机器人等这样基于2D激光雷达导航的机器人 如何处理悬空 低矮物体 5 也欢迎大家来我的读书号