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

目录

  • 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. 也欢迎大家来我的读书号–过千帆,学习交流。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023经典软件测试面试题

1、问&#xff1a;你在测试中发现了一个bug&#xff0c;但是开发经理认为这不是一个bug&#xff0c;你应该怎样解决&#xff1f; 首先&#xff0c;将问题提交到缺陷管理库里面进行备案。 然后&#xff0c;要获取判断的依据和标准&#xff1a; 根据需求说明书、产品说明、设计…

Figma安装指南:新手入门必看!

如果您想下载Figma客户端&#xff0c;可以直接在Figma官网Products>Downloads页面下载。 如果你不能访问Figma的官方网站&#xff0c;即使下载到客户端&#xff0c;你的网络环境也不能正常使用。 因为Figma的服务器在国外&#xff0c;在国内访问时经常会遇到网络不稳定的情…

如何制作教育培训小程序

教育培训行业近年来发展迅速&#xff0c;越来越多的机构开始意识到通过小程序来提供在线教育服务的重要性。小程序不仅可以为用户提供便捷的学习体验&#xff0c;还可以增加机构的知名度和品牌影响力。那么&#xff0c;如何制作一款教育培训小程序呢&#xff1f; 首先&#xff…

系列十三、SpringBoot的自动配置原理分析

一、概述 我们知道Java发展到现在功能十分的强大&#xff0c;生态异常的丰富&#xff0c;这里面离开不了Spring及其家族产品的支持&#xff0c;而作为Spring生态的明星产品Spring Boot可以说像王者一般的存在&#xff0c;那么的耀眼&#xff0c;那么的光彩夺目&#xff01;那么…

【开源】基于Vue.js的停车场收费系统

文末获取源码&#xff0c;项目编号&#xff1a; S 076 。 \color{red}{文末获取源码&#xff0c;项目编号&#xff1a;S076。} 文末获取源码&#xff0c;项目编号&#xff1a;S076。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 停车位模块2.2 车辆模块2.3 停车收费…

油气罐防雷和化工防雷综合解决方案

油气罐防雷和化工防雷是化工企业安全生产的重要内容&#xff0c;涉及到化工装置、储罐、管道、电气设施等多个方面。地凯科技将介绍油气罐防雷和化工防雷的方案和应用方案&#xff0c;以期为化工企业提供一些参考。 油气罐防雷 油气罐是储存可燃易爆物质的设施&#xff0c;一…

jmeter资料

1.jmeter介绍 Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试&#xff0c;它最初被设计用于Web应用测试&#xff0c;但后来扩展到其他测试领域。 它可以用于测试静态和动态资源&#xff0c;例如静态文件、Java 小服务程序、CGI 脚本、Java 对象…

侯捷C++八部曲(一,面向对象)

头文件和类的声明 inline inline修饰函数&#xff0c;是给编译器的一个建议&#xff0c;到底是否为inline由编译器来决定&#xff0c;inline修饰的函数在使用时是做简单的替换&#xff0c;这样就避免了一些函数栈空间的使用&#xff0c;从能提升效率。从另一种角度看&#xff…

将.tiff格式图片转换为可视化的png,jpg,bmp等格式(附代码)

目前常用的.tiff格式图片转png格式图片&#xff0c;搜了一下&#xff0c;多数都是第三方平台直接转换&#xff0c;需要米&#xff0c;其实大可不必&#xff0c;自己撸代码就可以直接转换。 TIFF&#xff08;Tagged Image File Format&#xff09;是一种灵活的位图格式&#xf…

Android 应用程序无响应定位ANR原因

废话不多说&#xff0c;直接上方案&#xff1a; 第一步&#xff1a; 执行adb命令 adb bugreport /Users/mac/Desktop/anr 解压后FS/data/anr下就会有相关anr文件 /Users/mac/Desktop/anr 是电脑存储文件的路径&#xff0c;可以随便定义&#xff0c;这个没有影响。我的电脑是…

做一个类似万师傅家政小程序需要有哪些功能?

现如今人们生活节奏不断加快&#xff0c;自然很少有时间去处理生活中的琐事&#xff0c;恰好家政维修保洁小程序开发则能给线下用户提供方便。 家政保洁小程序应该具备哪些功能&#xff1f; 1、提供家政行业资讯&#xff0c;方便用户在选择家政保洁前了解行业动态。 2、分类搜…

【Linux】more命令使用

more 是linux的一个命令&#xff0c;类似cat命令&#xff0c;会以一页一页的显示&#xff0c;方便使用者逐页阅读。 More是一个过滤器&#xff0c;用于一次一屏地对文本进行分页。这个版本特别原始。用户应该意识到&#xff0c;less&#xff08;1&#xff09;提供了更多的模拟…

算法分析复习重点

目录 复习重点 子集数 01背包 排列树 &#xff08;可以求出所有的解&#xff0c;但是是残缺的&#xff09; n-皇后 n的全排列 回溯法 就是对隐式图的深度优先搜索 算法 &#xff08;勤劳或许也是一种诅咒&#xff09; 八皇后回溯的过程 解空间 结点的扩展规则 搜…

大模型技术的发展与实践

一、大模型的概念 大型语言模型&#xff0c;也称大语言模型、大模型&#xff08;Large Language Model&#xff0c;LLM&#xff1b;Large Language Models&#xff0c;LLMs) 。 大语言模型是一种深度学习模型&#xff0c;特别是属于自然语言处理&#xff08;NLP&#xff09;的…

【皇帝的新装】像管理产品一样,来管理自己

在前进的路上需要不时的回头看&#xff0c;看自己来时的脚步&#xff0c;是杂乱无章&#xff0c;还是方向一致。善于从经验中总结可以让我们少走许多弯路&#xff0c;降低我们的消耗。 偶然间&#xff0c;回头看看&#xff0c;入行产品经理已经三年有余。沉迷在各种具体事务中&…

类与对象的概念:创建及调用方法

掌握类和创建对象的关系 定义类 定义对象 定义和调用方法 编程思想&#xff1a;面向过程编程&#xff0c;面向对象编程 系统提供数据类型【String&#xff0c;char&#xff0c;double】&#xff0c;我们也可以自己定义类型&#xff1a;根据自定义类型所衍生出来的变量就是…

【Cadence Allegro17.4】

Cadence Allegro17.4 1. Cadence Allegro17.42. pcb工程文件的介绍3.4.5.6. 1. Cadence Allegro17.4 常用的工具 2. pcb工程文件的介绍 新建工程 &#xff1a; 飞行器 要创建的文件夹 GERBER光会文件&#xff0c;发出去制版。 DXF结构文件&#xff0c; pcb文件 lib 封装库 sc…

2023博思高科技智慧车行、人行专项研讨会成功召开

来源&#xff1a;智安物联网 11月30日&#xff0c;深圳市博思高科技有限公司&#xff08;以下简称“博思高科技”&#xff09;在其总部成功举办了智慧车行、人行专项研讨会议。本次会议邀请了来自国家发改委综合运输研究所的程世东主任&#xff0c;中国安全防范产品行业协会原…

BFS求树的宽度——结合数组建树思想算距离

二叉树最大宽度 https://leetcode.cn/problems/maximum-width-of-binary-tree/description/ 1、考虑树的宽度一定是在一层上的所以进行BFS&#xff0c;树的BFS不建议直接使用队列&#xff0c;每次add/offer然后poll/remove&#xff0c;这样子层级关系不好显示。我们可以定义…

基于现代学徒制的大数据技术与应用人才培养模式探讨

学生学徒制的实施旨在解决当前新技术企业招聘技能人才难和青年就业难的结构性矛盾&#xff0c;通过生态链链主企业携手院校共同解决毕业年度学生就业问题&#xff0c;按照学生个人意愿&#xff0c;建立以就业导向的学生学徒制关系&#xff0c;签订学徒培养协议确定学生就业岗位…