代码浅析Point-LIO

news2024/11/25 5:18:15

0. 简介

对于最近出来的Point-LIO(鲁棒高带宽激光惯性里程计),本人还是非常该兴趣的,为此花了一些时间重点分析了Point-LIO的代码,并研究了它相较于Fast-LIO2的区别

1. laserMapping.cpp

第一部分就是实现对激光雷达视场角的图像分割。首先定义了一个BoxPointType类型的局部地图(LocalMap_Points)和一个bool类型的变量(Localmap_Initialized),表示是否已经初始化局部地图。然后,在lasermap_fov_segment()函数中,根据激光雷达的姿态计算出激光雷达的位置(pos_LiD),并根据移动阈值(MOV_THRESHOLD)判断是否需要移动局部地图。如果需要移动,则计算新的局部地图边界(New_LocalMap_Points),并将需要移除的框添加到cub_needrm中。最后,根据cub_needrm中的框删除点云,完成图像分割。

BoxPointType LocalMap_Points;
bool Localmap_Initialized = false;
void lasermap_fov_segment() //针对激光雷达视场角来完成图像分割
{
  cub_needrm.shrink_to_fit(); //将容量设置为容器的长度

  V3D pos_LiD;
  if (use_imu_as_input) {
    pos_LiD =
        kf_input.x_.pos + kf_input.x_.rot.normalized() *
                              Lidar_T_wrt_IMU; //计算激光雷达在当前位姿下的位置
  } else {
    pos_LiD =
        kf_output.x_.pos + kf_output.x_.rot.normalized() * Lidar_T_wrt_IMU;
  }
  if (!Localmap_Initialized) { //判断是否需要初始化局部地图
    //将局部地图的边界设置为当前位置的正方形区域,并将Localmap_Initialized设置为true
    for (int i = 0; i < 3; i++) {
      LocalMap_Points.vertex_min[i] = pos_LiD(i) - cube_len / 2.0;
      LocalMap_Points.vertex_max[i] = pos_LiD(i) + cube_len / 2.0;
    }
    Localmap_Initialized = true;
    return;
  }
  float dist_to_map_edge[3][2];
  bool need_move = false;
  //如果不需要初始化,则计算激光雷达当前位姿与局部地图边界的距离,并根据移动阈值判断是否需要移动局部地图
  for (int i = 0; i < 3; i++) {
    dist_to_map_edge[i][0] = fabs(pos_LiD(i) - LocalMap_Points.vertex_min[i]);
    dist_to_map_edge[i][1] = fabs(pos_LiD(i) - LocalMap_Points.vertex_max[i]);
    if (dist_to_map_edge[i][0] <= MOV_THRESHOLD * DET_RANGE ||
        dist_to_map_edge[i][1] <= MOV_THRESHOLD * DET_RANGE)
      need_move = true;
  }
  //如果需要移动,则计算新的局部地图边界
  if (!need_move)
    return;
  BoxPointType New_LocalMap_Points, tmp_boxpoints;
  New_LocalMap_Points = LocalMap_Points;
  float mov_dist = max((cube_len - 2.0 * MOV_THRESHOLD * DET_RANGE) * 0.5 * 0.9,
                       double(DET_RANGE * (MOV_THRESHOLD - 1)));
  for (int i = 0; i < 3; i++) {
    tmp_boxpoints = LocalMap_Points;
    if (dist_to_map_edge[i][0] <= MOV_THRESHOLD * DET_RANGE) {
      New_LocalMap_Points.vertex_max[i] -= mov_dist;
      New_LocalMap_Points.vertex_min[i] -= mov_dist;
      tmp_boxpoints.vertex_min[i] = LocalMap_Points.vertex_max[i] - mov_dist;
      cub_needrm.emplace_back(tmp_boxpoints); //将需要移除的框添加到cub_needrm中
    } else if (dist_to_map_edge[i][1] <= MOV_THRESHOLD * DET_RANGE) {
      New_LocalMap_Points.vertex_max[i] += mov_dist;
      New_LocalMap_Points.vertex_min[i] += mov_dist;
      tmp_boxpoints.vertex_max[i] = LocalMap_Points.vertex_min[i] + mov_dist;
      cub_needrm.emplace_back(tmp_boxpoints);
    }
  }
  LocalMap_Points = New_LocalMap_Points;

  points_cache_collect();
  if (cub_needrm.size() > 0)
    int kdtree_delete_counter = ikdtree.Delete_Point_Boxes(
        cub_needrm); //收集点云缓存,并根据cub_needrm中的框删除点云,返回删除的框数量。
}

下面我们来看看怎么使用这个函数的,下面的代码主要实现了以下的操作:

1.对激光雷达采集到的点云进行空间下采样和时间压缩;

2.初始化地图kd-tree;

3.使用迭代最近点算法(ICP)和卡尔曼滤波更新地图。其中,ICP主要用于点云配准,卡尔曼滤波则用于对机器人位姿进行估计和更新。

lasermap_fov_segment();
      /*** downsample the feature points in a scan ***/
      t1 = omp_get_wtime();
      if (space_down_sample) { //空间下采样
        downSizeFilterSurf.setInputCloud(feats_undistort);
        downSizeFilterSurf.filter(*feats_down_body);
        sort(feats_down_body->points.begin(), feats_down_body->points.end(),
             time_list); //按时间排序
      } else {
        feats_down_body = Measures.lidar;
        sort(feats_down_body->points.begin(), feats_down_body->points.end(),
             time_list);
      }
      time_seq = time_compressing<int>(feats_down_body); //时间压缩
      feats_down_size = feats_down_body->points.size();  //点云数量

      /*** initialize the map kdtree ***/
      if (!init_map) {
        if (ikdtree.Root_Node == nullptr) //
        // if(feats_down_size > 5)
        {
          ikdtree.set_downsample_param(filter_size_map_min); //设置滤波参数
        }

        feats_down_world->resize(feats_down_size); //初始化点云
        for (int i = 0; i < feats_down_size; i++) {
          pointBodyToWorld(&(feats_down_body->points[i]),
                           &(feats_down_world->points[i])); //转换到世界坐标系
        }
        for (size_t i = 0; i < feats_down_world->size(); i++) {
          init_feats_world->points.emplace_back(
              feats_down_world
                  ->points[i]); //将转换到世界坐标西的点云加入到init_feats_world
        }
        if (init_feats_world->size() < init_map_size) //等待构建地图
          continue;
        ikdtree.Build(init_feats_world->points); //构建地图
        init_map = true;
        publish_init_kdtree(pubLaserCloudMap); //(pubLaserCloudFullRes);
        continue;
      }
      /*** ICP and Kalman filter update ***/
      normvec->resize(feats_down_size);
      feats_down_world->resize(feats_down_size);

      Nearest_Points.resize(feats_down_size);

      t2 = omp_get_wtime(); //初始化t2为当前时间

      /*** iterated state estimation ***/
      crossmat_list.reserve(feats_down_size);
      pbody_list.reserve(feats_down_size);
      // pbody_ext_list.reserve(feats_down_size);

      //对于每个点,将其坐标转换为V3D类型的point_this
      for (size_t i = 0; i < feats_down_body->size(); i++) {
        V3D point_this(feats_down_body->points[i].x,
                       feats_down_body->points[i].y,
                       feats_down_body->points[i].z);
        pbody_list[i] = point_this;
        //如果使用外参估计
        if (extrinsic_est_en) {
          if (!use_imu_as_input) {
            //对于每个点,使用卡尔曼滤波估计出的外参对其进行坐标变换
            point_this = kf_output.x_.offset_R_L_I.normalized() * point_this +
                         kf_output.x_.offset_T_L_I;
          } else {
            point_this = kf_input.x_.offset_R_L_I.normalized() * point_this +
                         kf_input.x_.offset_T_L_I;
          }
        } else {
          // 使用Lidar_R_wrt_IMU和Lidar_T_wrt_IMU对其进行变换
          point_this = Lidar_R_wrt_IMU * point_this + Lidar_T_wrt_IMU;
        }
        M3D point_crossmat;
        point_crossmat << SKEW_SYM_MATRX(point_this); //根据当前点生成矩阵
        crossmat_list[i] = point_crossmat;
      }

      if (!use_imu_as_input) {
        bool imu_upda_cov = false; //是否需要更新imu的协方差
        effct_feat_num = 0;
        /**** point by point update ****/

        double pcl_beg_time =
            Measures
                .lidar_beg_time; //首先设置pcl_beg_time为Measures.lidar_beg_time,idx为-1
        idx = -1;
        for (k = 0; k < time_seq.size(); k++) {
          PointType &point_body = feats_down_body->points[idx + time_seq[k]];

          time_current =
              point_body.curvature / 1000.0 +
              pcl_beg_time; //找到对应的点并计算出当前时间time_current

          if (is_first_frame) {
            if (imu_en) { //如果是第一帧,且启用了IMU,那么需要找到最近的IMU数据
              while (time_current > imu_next.header.stamp.toSec()) {
                imu_last = imu_next;
                imu_next = *(imu_deque.front());
                imu_deque.pop_front();
                // imu_deque.pop();
              }
              //计算出对应的角速度和加速度
              angvel_avr << imu_last.angular_velocity.x,
                  imu_last.angular_velocity.y, imu_last.angular_velocity.z;
              acc_avr << imu_last.linear_acceleration.x,
                  imu_last.linear_acceleration.y,
                  imu_last.linear_acceleration.z;
            }
            is_first_frame = false;
            imu_upda_cov = true;
            time_update_last = time_current;
            time_predict_last_const = time_current;
          }
          if (imu_en) {
            bool imu_comes = time_current > imu_next.header.stamp.toSec();
            // 如果启用了IMU,那么需要在当前时间之前的IMU数据都进行卡尔曼滤波更新
            while (imu_comes) {
              imu_upda_cov = true;
              //将IMU数据中的角速度和线性加速度分别存储到angvel_avr和acc_avr中
              angvel_avr << imu_next.angular_velocity.x,
                  imu_next.angular_velocity.y, imu_next.angular_velocity.z;
              acc_avr << imu_next.linear_acceleration.x,
                  imu_next.linear_acceleration.y,
                  imu_next.linear_acceleration.z;

              /*** 对协方差进行更新 ***/
              imu_last = imu_next; //将当前IMU数据存储为imu_last
              imu_next = *(imu_deque.front()); //将下一个IMU数据存储为imu_next
              imu_deque.pop_front();
              double dt = imu_last.header.stamp.toSec() -
                          time_predict_last_const; //接着计算时间差dt
              kf_output.predict(dt, Q_output, input_in, true,
                                false); //通过kf_output.predict函数进行预测
              time_predict_last_const =
                  imu_last.header.stamp.toSec(); // big problem
              imu_comes = time_current > imu_next.header.stamp.toSec();
              // if (!imu_comes)
              {
                double dt_cov = imu_last.header.stamp.toSec() -
                                time_update_last; //就计算时间差dt_cov

                if (dt_cov > 0.0) {
                  time_update_last = imu_last.header.stamp.toSec();
                  double propag_imu_start = omp_get_wtime();

                  kf_output.predict(dt_cov, Q_output, input_in, false,
                                    true); //行卡尔曼滤波预测

                  propag_time += omp_get_wtime() - propag_imu_start;
                  double solve_imu_start = omp_get_wtime();
                  kf_output.update_iterated_dyn_share_IMU(); //进行eskf迭代更新
                  solve_time += omp_get_wtime() - solve_imu_start;
                }
              }
            }
          }

          double dt = time_current - time_predict_last_const;
          double propag_state_start = omp_get_wtime();
          if (!prop_at_freq_of_imu) {
            double dt_cov = time_current - time_update_last;
            if (dt_cov > 0.0) {
              kf_output.predict(dt_cov, Q_output, input_in, false, true);
              time_update_last = time_current;
            }
          }
          kf_output.predict(dt, Q_output, input_in, true, false);
          propag_time += omp_get_wtime() - propag_state_start;
          time_predict_last_const = time_current;
          // if(k == 0)
          // {
          //     fout_imu_pbp << Measures.lidar_last_time - first_lidar_time <<
          //     " " << imu_last.angular_velocity.x << " " <<
          //     imu_last.angular_velocity.y << " " <<
          //     imu_last.angular_velocity.z \
                    //             << " " << imu_last.linear_acceleration.x << " " <<
          //             imu_last.linear_acceleration.y << " " <<
          //             imu_last.linear_acceleration.z << endl;
          // }

          double t_update_start = omp_get_wtime();

          if (feats_down_size < 1) {
            ROS_WARN("No point, skip this scan!\n");
            idx += time_seq[k];
            continue;
          }
          if (!kf_output.update_iterated_dyn_share_modified()) {
            idx = idx + time_seq[k];
            continue;
          }

          if (prop_at_freq_of_imu) {
            double dt_cov = time_current - time_update_last;
            if (!imu_en &&
                (dt_cov >=
                 imu_time_inte)) // //需要在当前时间之前的时间间隔大于imu_time_inte时进行卡尔曼滤波协方差更新
            {
              double propag_cov_start = omp_get_wtime();
              kf_output.predict(
                  dt_cov, Q_output, input_in, false,
                  true); //对于每个时间片中的点,进行卡尔曼滤波更新,并将点转换到世界坐标系中
              imu_upda_cov = false;
              time_update_last = time_current;
              propag_time += omp_get_wtime() - propag_cov_start;
            }
          }

          solve_start = omp_get_wtime();

2. Estimator.cpp

除此以外,其他的内容差不多,比较具有区别性的还有就是在Estimator.cpp里面加入了单点激光优化的操作部分

esekfom::esekf<state_input, 24, input_ikfom>
    kf_input; // EKF的输入,针对IMU优化
esekfom::esekf<state_output, 30, input_ikfom>
    kf_output; // EKF的输出,针对单点激光雷达

该代码定义了一个函数process_noise_cov_output(),用于生成一个30x30的矩阵cov,表示系统的噪声协方差矩阵。以及获得输入的f矩阵值

Eigen::Matrix<double, 30, 30> process_noise_cov_output() {
  Eigen::Matrix<double, 30, 30> cov;
  cov.setZero(); //创建一个30x30的矩阵cov,并将其所有元素初始化为0
  cov.block<3, 3>(12, 12).diagonal() << vel_cov, vel_cov,
      vel_cov; //从(12,12)开始的一个3x3的块,表示速度的协方差
  cov.block<3, 3>(15, 15).diagonal() << gyr_cov_output, gyr_cov_output,
      gyr_cov_output; //表示角速度的噪声协方差
  cov.block<3, 3>(18, 18).diagonal() << acc_cov_output, acc_cov_output,
      acc_cov_output; //表示加速度计的噪声协方差
  cov.block<3, 3>(24, 24).diagonal() << b_gyr_cov, b_gyr_cov,
      b_gyr_cov; // bg的噪声协方差
  cov.block<3, 3>(27, 27).diagonal() << b_acc_cov, b_acc_cov,
      b_acc_cov; // ba的噪声协方差
  // MTK::get_cov<process_noise_output>::type cov =
  // MTK::get_cov<process_noise_output>::type::Zero();
  // MTK::setDiagonal<process_noise_output, vect3, 0>(cov,
  // &process_noise_output::vel, vel_cov);// 0.03
  // MTK::setDiagonal<process_noise_output, vect3, 3>(cov,
  // &process_noise_output::ng, gyr_cov_output); // *dt 0.01 0.01 * dt * dt 0.05
  // MTK::setDiagonal<process_noise_output, vect3, 6>(cov,
  // &process_noise_output::na, acc_cov_output); // *dt 0.00001 0.00001 * dt *dt
  // 0.3 //0.001 0.0001 0.01 MTK::setDiagonal<process_noise_output, vect3,
  // 9>(cov, &process_noise_output::nbg, b_gyr_cov);   //0.001 0.05 0.0001/out
  // 0.01 MTK::setDiagonal<process_noise_output, vect3, 12>(cov,
  // &process_noise_output::nba, b_acc_cov);   //0.001 0.05 0.0001/out 0.01
  return cov;
}

//这段代码的作用是将输入的IMU数据转化为状态转移方程中的输入向量f。其中状态向量s包含了位置、速度、姿态、陀螺仪bias和加速度计bias等信息,in包含了IMU测量得到的角速度和线性加速度数据。
Eigen::Matrix<double, 24, 1> get_f_input(state_input &s,
                                         const input_ikfom &in) {
  Eigen::Matrix<double, 24, 1> res = Eigen::Matrix<double, 24, 1>::Zero();
  vect3 omega;
  in.gyro.boxminus(
      omega,
      s.bg); //通过in.gyro.boxminus函数将in.gyro和s.bg进行运算,得到角速度向量omega
  vect3 a_inertial =
      s.rot.normalized() *
      (in.acc -
       s.ba); //计算惯性系下的加速度向量a_inertial,首先通过s.rot.normalized()将s.rot单位化,再通过乘法得到加速度向量,最后减去s.ba得到相对于惯性系的加速度。
  for (int i = 0; i < 3; i++) {
    res(i) = s.vel[i];
    res(i + 3) = omega[i];
    res(i + 12) = a_inertial[i] + s.gravity[i];
  }
  return res;
}
//这段代码的作用是将输入的单点激光数据转化为状态转移方程中的输入向量f。其中状态向量s包含了位置、速度、姿态、陀螺仪bias和加速度计bias等信息,in包含了IMU测量得到的角速度和线性加速度数据,没用到
Eigen::Matrix<double, 30, 1> get_f_output(state_output &s,
                                          const input_ikfom &in) {
  Eigen::Matrix<double, 30, 1> res = Eigen::Matrix<double, 30, 1>::Zero();
  vect3 a_inertial =
      s.rot.normalized() *
      s.acc; //计算惯性系下的加速度向量a_inertial,首先通过s.rot.normalized()将s.rot单位化,再通过乘法得到加速度向量。
  for (int i = 0; i < 3; i++) {
    res(i) = s.vel[i];
    res(i + 3) = s.omg[i];
    res(i + 12) = a_inertial[i] + s.gravity[i];
  }
  return res;
}

这部分对应了原文的4.2和5.2的部分。该函数用于计算当前时刻下所有满足匹配条件的特征点的观测值和雅可比矩阵,并将其保存到ekfom_data中。主要流程如下:

  1. 遍历当前时刻下所有的特征点,依次计算每个特征点的法向量和对应的观测值。
  2. 对于每个特征点,首先将其从机体系转换到世界系,并计算其在世界系下的位置和在机体系下的位置。
  3. 通过ikdtree.Nearest_Search函数在地图点云中搜索与当前特征点最近的NUM_MATCH_POINTS个点,并计算它们之间的平面方程pabcd。
  4. 如果搜索到的点数量小于NUM_MATCH_POINTS或者最远点距离大于5,则认为该特征点不在地图中,将point_selected_surf[idx+j+1]标记为false。
  5. 如果搜索到的点数量大于等于NUM_MATCH_POINTS且最远点距离小于等于5,则认为该特征点在地图中,计算其与地图平面的距离pd2。
  6. 判断其是否满足匹配条件(即p_body.norm() > match_s * pd2 * pd2),如果满足,则将point_selected_surf[idx+j+1]标记为true,并将该特征点的法向量normvec->points[j]保存到normvec中。
  7. 如果当前时刻下没有满足匹配条件的特征点,则将ekfom_data.valid设为false,并直接返回。
  8. 否则,对于每个满足匹配条件的特征点,计算其对应的观测值z和雅可比矩阵H。
  9. 将所有满足匹配条件的特征点的观测值z和雅可比矩阵H保存到ekfom_data中。
  10. 最后将effect_num_k加到effct_feat_num中,表示当前时刻下满足匹配条件的特征点的数量。

…详情请参照古月居

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

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

相关文章

Day01 项目简介分布式基础概念

最近在改进公司开发的商城项目&#xff0c;看到了尚硅谷的谷粒商城&#xff0c;就快速学习了下&#xff0c;因为之前的Kafka,Redis都是在这学习的&#xff0c;还有大数据的Flink。所以感觉一定不错&#xff0c;就开始了。 这里做一下学习笔记 一、项目简介 1 、项目背景 1 &…

UIAutomatorViewer工具的使用

目录 前言&#xff1a; 一、uiautomatorviewer介绍 二、启动uiautomatorviewer 三、uiautomatorviewer界面 四、连接手机 前言&#xff1a; UIAutomatorViewer是Android SDK提供的一个可视化界面分析工具&#xff0c;可以用于查看Android应用的UI界面结构、属性信息以及布…

元宇宙在技术大爆炸时代迎来链游新世界

元宇宙是从虚拟游戏、虚拟艺术收藏品开始兴起&#xff0c;然后逐步扩展到社交和金融领域的。元宇宙的终极形态就是一种“无限游戏”&#xff0c;也即打破边界、颠覆规则、不断迭代和进化发展的新世界。 政策落地&#xff0c;元宇宙未来才能充满潜力 2021 年以来&#xff0c;元…

SSM在线学习平台-计算机毕设 附源码85204

SSM在线学习平台 摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;在线学习平台当然也不能排除在外。在线学习平台是以实际运用为开发背景&#xff0c;运用软件工程原理和…

数字非洲,沐光而行

“华为是什么公司&#xff1f;我们不相信中国企业能有先进的通信技术&#xff01;你们也不要总是来找我&#xff01;”1998年&#xff0c;华为人刚踏上非洲所遇到的&#xff0c;不是来自阳光大陆的热情&#xff0c;而是来自刚果&#xff08;金&#xff09;客户冷冰冰的拒绝。 2…

国产崛起,继操作系统装机500w,又有软件平台用户突破600w

国产操作系统行业部署超500W 这次&#xff0c;国产操作系统终于扬眉吐气了&#xff01; 国产的操作系统装机已达到500万&#xff0c;国产崛起的时刻已来&#xff01; 近日&#xff0c;国产操作系统的佼佼者——统信UOS&#xff0c;召开见面会&#xff0c;宣布&#xff1a;目…

7DGroup性能实施项目日记2

壬寅年 己酉月 己卯日 秋高气爽 天气转凉 昨天大风昨天看到项目计划中已经有了很多内容。 也就是说计划感觉已经写得差不多了。我仔细看了一下之后觉得这个计划不可行&#xff0c;就是样子挺像。 然后我问管理组&#xff0c;这个是你们商量之后写的吗&#xff1f;他们说不…

Java互联网工程师1100题(总结最全面的面试题)

本来已经在为去大厂工作摩拳擦掌的 Java 朋友&#xff0c;社招又是需要 5 年以上的&#xff0c;今年显得格外艰难&#xff1a; 就业人数高达 874 万&#xff01;人才竞争加剧&#xff01; 大多数公司的招聘需求缩减&#xff01;对社招来说&#xff0c;人才招聘要求愈来愈高&am…

Spring核心思想之IOC和AOP

IOC和AOP不是Spring提出的&#xff0c;在spring之前就已经存在&#xff0c;只不过更偏向于理论化&#xff0c;Spring在技术层次把这两个思想做了⾮常好的实现&#xff08;Java&#xff09;。 Spring核心思想 IOC(Inversion of Control)什么是IoC&#xff1f;IoC解决了什么问题…

unittest单元测试框架

目录 前言&#xff1a; 一、unittest模块的各个属性说明 二、使用unittest框架编写测试用例思路 三、使用unittest框架编写测试用例实例 前言&#xff1a; unittest是Python自带的单元测试框架&#xff0c;可以用于编写和执行单元测试。 一、unittest模块的各个属性说明 …

Linux内核4.14版本——drm框架分析(9)——DRM_IOCTL_MODE_GETCONNECTOR(drm_mode_getconnector)

目录 1. drm_mode_getconnector分析 1.1 找到connector 1.2 计算该connector的encoder数量 1.3 把connector支持的encoder和数量返回给用户 1.4 把找到的connector的参数返回给应用 1.5 填充mode&#xff08;很重要&#xff09; 1.6 把找到的connector的参数返回给应用 …

excel爬虫相关学习2:excel 和 vba 爬虫相关xmlhttp方法

目录 前言&#xff1a;vba 爬虫相关xmlhttp的方法 1 什么是xmlhttp 1.1 定义 1.2 特点 定义XMLHTTP对象&#xff1a; XMLHTTP方法&#xff1a; open(bstrMethod, bstrUrl, varAsync, bstrUser, bstrPassword) send(varBody) setRequestHeader(bstrHeader, bstrValue) …

Java 被挤出前三。。

TIOBE 2023 年 06 月份的编程语言排行榜已经公布&#xff0c;官方的标题是&#xff1a;Python 还会保持第一吗&#xff1f;&#xff08;Will Python remain number 1?&#xff09; 在过去的 5 年里&#xff0c;Python 已经 3 次获得 TIOBE 指数年度大奖&#xff0c;这得益于…

简述Spring中IOC核心流程

基础概念 IOC 和 DI IOC&#xff0c;即控制反转。是Spring的一种设计思想。传统程序设计中&#xff0c;我们创建一个对象是通过 new 关键字&#xff0c;是程序主动去创建依赖对象&#xff0c;而在spring中专门有一个容器来创建和管理这些对象&#xff0c;并将对象依赖的其他对…

rpm详解——原理,命令详细讲解

目录 三个问题 什么是RPM SRPM (Source RPM) RPM软件包 RPM 查询命令 查询已安装的RPM软件信息 查询文件/目录属于哪个RPM软件 查询未安装的RPM包文件 RPM 软件包的安装、卸载 安装 卸载 RPM 升级与更新 三个问题 先抛出三个问题&#xff0c;最后简单解释一下。 …

HiveSQL 电商场景TopK统计

数据准备 CREATE TABLE test.test2 ( user_id string, shop string ) ROW format delimited FIELDS TERMINATED BY \t; INSERT INTO TABLE test.test2 VALUES ( u1, a ), ( u2, b ), ( u1, b ), ( u1, a ), ( u3, c ), ( u4, b ), ( u1, a ), ( u2, c ), ( u5, b ), ( u4, b )…

基于人工智能的AI理发师能帮托尼老师做什么?

BarberGPT是一个人工智能理发师&#xff0c;它可以让您在照片上尝试不同的发型。您只需要上传您的照片&#xff0c;标记您的头发&#xff0c;然后就可以看到惊人的变化。BarberGPT使用了先进的深度学习技术&#xff0c;可以根据您的脸型、肤色和发质生成适合您的发型。BarberGP…

国产监控数据库产品lepus最新版学习和部署(V5.1)

目录 lepus是什么&#xff1f; 二进制安装&#xff08;最新v5.1&#xff09; 1.基础环境 2.安装NSQ 3.NSQ消息测试 4.安装lepus 5.初始化数据库 6.修改配置文件 7.启动服务组件 8.安装lepus-console 9.运行和访问控制台 lepus是什么&#xff1f; Lepus数据库监控系统…

Docker容器应用为工业连接提供了新选择

一 智能数据管理 Docker容器应用可灵活部署在异构计算平台上&#xff0c;且仅需占用少量的资源&#xff0c;这可为工业4.0提供一种新的数据集成方案。Docker容器应用程序是提供特定功能的小型软件模块&#xff0c;可在自动化领域中用于智能数据管理。而Softing推出的一个新产品…

简要介绍 | CUDA底层原理:加速高性能计算的关键技术

注1&#xff1a;本文系“简要介绍”系列之一&#xff0c;仅从概念上对CUDA的底层原理进行非常简要的介绍&#xff0c;不适合用于深入和详细的了解。 CUDA底层原理&#xff1a;加速高性能计算的关键技术 CUDA Refresher: The GPU Computing Ecosystem | NVIDIA Technical Blog 1…