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

news2024/9/30 1:30:11

目录

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

在这里插入图片描述

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]; v1=data[0];
v 2 = d a t a [ 1 ] ; v2=data[1]; v2=data[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)]; v3=data[img.step/sizeof(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]; v4=data[img.step/sizeof(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]); v7=float((1xx)(1yy)data[0]+xx(1yy)data[1]+(1xx)yydata[img.step/sizeof(T)]+xxyydata[img.step/sizeof(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. 也欢迎大家来我的读书号–过千帆,学习交流。

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

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

相关文章

详解十大经典排序算法(四):希尔排序(Shell Sort)

算法原理 希尔排序是一种基于插入排序的排序算法&#xff0c;也被称为缩小增量排序。它通过将待排序的序列分割成若干个子序列&#xff0c;对每个子序列进行插入排序&#xff0c;然后逐步缩小增量&#xff0c;最终使整个序列有序。 算法描述 希尔排序&#xff08;Shell Sort&am…

【软考S01计算机系统知识】E01 中央处理单元

E01 中央处理单元 计算机系统硬件基本组成中央处理单元组成功能 多核 CPU 计算机系统硬件基本组成 计算机系统由硬件和软件组成&#xff0c;基本硬件系统由 运算器、控制器、存储器、输入设备 和 输出设备 5大部件组成&#xff1b; 中央处理单元&#xff1a; 运算器、控制器等…

【MySQL语言汇总[DQL,DDL,DCL,DML]以及使用python连接数据库进行其他操作】

MySQL语言汇总[DQL,DDL,DCL,DML] SQL分类1.DDL:操作数据库&#xff0c;表创建 删除 查询 修改对数据库的操作对表的操作复制表&#xff08;重点&#xff09;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 2.DML:增删改表中数据3.DQL&#xff1a;查询表中的记录…

hnust 湖科大 创业基础考察课程结课作业 创业计划书+路演PPT 资源下载

hnust 湖科大 创业基础考察课程结课作业 创业计划书 资源下载 资源详尽&#xff0c;图文并茂&#xff0c;开箱即用&#xff0c;附赠若干模板 资源预览图 创业计划书word 路演PPT 赠品 下载链接 链接&#xff1a;https://pan.baidu.com/s/1p1n6qwM5Jx6bB96ifAJmiw?pwd1111 …

Autosar MCAL-RH850P1HC Port配置

文章目录 PortPortGroupPortPin PortFilterGroupConfigDigital Filter Clock Selection0Digital Filter Clock Selection1 PortGeneralCritical Section ProtectionDev Error DetectDevice NameMax ModeSet Pin Default Modee ApiSet Pin Modee ApiSet to Dio Alt Modee ApiUse…

Ubuntu22.04 交叉编译mp4V2 for Rv1106

一、配置工具链环境 sudo vim ~/.bashrc在文件最后添加 export PATH$PATH:/opt/arm-rockchip830-linux-uclibcgnueabihf/bin 保存&#xff0c;重启机器 二、下载mp4v2 下载路径&#xff1a;MP4v2 | mp4v2 三、修改CMakeLists.txt 四、执行编译 mkdir build cd buildcmak…

ubuntu16.04升级openssl

Ubuntu16.04 默认带的openssl版本为1.0.2 查看&#xff1a;openssl version 1.下载openssl wget https://www.openssl.org/source/openssl-1.1.1.tar.gz 编译安装 tar xvf openssl-1.1.1.tar.gz cd openssl-1.1.1 ./config make sudo make install sudo ldconfig 删除旧版本 su…

springboot的常用注解

声明解释这个对象&#xff08;类或者其他&#xff09;组件相关 名称作用Controller用于修饰MVC中controller层的组件SpringBoot中的组件扫描功能会识别到该注解&#xff0c;并为修饰的类实例化对象&#xff0c;通常与RequestMapping联用&#xff0c;当SpringMVC获取到请求时会…

DistributionBalancedLoss

Distribution-Balanced Loss P I ( x k ) P^I(x^k) PI(xk) 1 C ∑ y i k 1 1 n i {1\over C}\sum\limits_{y_i^k1}{1\over{n_i}} C1​yik​1∑​ni​1​&#xff0c; P i C ( x k ) P^C_i(x^k) PiC​(xk) 1 C 1 n i {1\over C}{1\over{n_i}} C1​ni​1​ r i k _i^k ik​ P i …

目标检测——R-CNN系列检测算法总结

R-CNN系列算法详细解读文章&#xff1a; R-CNN算法解读SPPNet算法解读Fast R-CNN算法解读Faster R-CNN算法解读Mask R-CNN算法解读 目录 1、概述1.1 获取目标候选框1.2 候选框提取特征1.3 候选框分类及边框回归 2、R-CNN系列算法概述2.1 R-CNN算法2.2 SPPNet算法2.3 Fast R-CN…

C++作业5

完成沙发床的多继承&#xff08;有指针成员&#xff09; 代码&#xff1a; #include <iostream>using namespace std;class Bed { private:double *money; public:Bed(){cout << "Bed::无参构造函数" << endl;}Bed(double money):money(new doub…

http代理如何设置手机上网?http代理起到了哪些作用

本文将详细介绍如何设置手机上网使用HTTP代理&#xff0c;以及HTTP代理所起到的作用。 一、HTTP代理是什么&#xff1f; HTTP代理是一种网络协议&#xff0c;它允许客户端与服务器之间进行数据传输。它是一种常用的代理服务&#xff0c;可以帮助用户通过HTTP协议访问被封锁的网…

帆软报表不能增加SAP连接方式 通过插件一致性检测 同步至本地解决

帆软报表开发人员需要增加一个SAP数据连接方式&#xff1a;SAP_ECC_600环境 在服务器端不能直接增加&#xff0c;而在帆软报表设计器切换到远程模式时&#xff0c;又不能显示SAP连接&#xff0c;导致不能增加。 解决&#xff1a;重新进入帆软报表报计器时报以下错误&#xff0c…

Spring Cloud + Vue前后端分离-第2章 使用Maven搭建SpringCloud项目

第2章 使用Maven搭建SpringCloud项目 Maven两大核心功能&#xff1a; 依赖管理&#xff08;Jar包管理&#xff09; 构建项目&#xff08;项目打包&#xff09; 使用Eureka搭建注册中心 使用spring initializr创建spring cloud项目 SpringCloud和Maven简介 SpringBoot和Spr…

FacetWP Relevanssi Integration相关性集成插件

点击阅读FacetWP Relevanssi Integration相关性集成插件原文 FacetWP Relevanssi Integration相关性集成插件是FacetWP与用于高级搜索的 Relevanssi 插件的集成显着增强了您网站的搜索功能。这个强大的工具使您的用户能够轻松找到他们寻求的特定内容&#xff0c;无论他们的查询…

craco + webpack 4 升 5

craco webpack 4 升 5 更新包版本尝试build升级其他依赖库使用process插件打印进度信息到底需要多少内存分析构建产出添加 splitChunk总结记录一些好文章&#xff1a; 我的项目使用 craco react 开发 我的 package.json {// ......"dependencies": {"ant-desi…

如何在 Chrome 上调试文件打断点

1. 控制台进入 Source 2. CtrlP 输入文件名称 3. 在需要的位置手动打断点 4. 重新触发代码运行&#xff0c;触发断点

【android开发-14】android中fragment用法详细介绍

1&#xff0c;fragment是什么&#xff1f; Fragment是Android中的一种组件&#xff0c;它在Android 3.0&#xff08;API级别11&#xff09;及以后的版本中引入。Fragment可以用来在Activity中添加一个或多个具有自己的用户界面的片段。它们可以与Activity进行交互&#xff0c;并…

查找一维向量中大于或小于某一值的所有连续片段

文章目录 经常会遇到一种场景&#xff0c;那就是有一个一维向量&#xff0c;我们要找到其中所有大于设定阈值的片段。就好比电路中有高代电平&#xff0c;我们要找连续的高电平段或低电平&#xff1a; 如上图&#xff0c;我们只要标红的高电平&#xff0c;对应代码&#xff1…