cartographer 代码思想解读(7)-位姿估计器PoseExtrapolator实现

2023-10-28


cartographer 算法中提供了一个位置和姿态估计器,其主要作用是在前端匹配时作为预测位置的输入,即可认为是匹配的初始位置和姿态。在前面已经详细讲解了前端scan math的三种方法,其中前端的优化匹配和相关匹配一定程度上依赖于初始位姿,因此获取预测位置十分重要。
获取预测的位置方式有许多种,比如其他slam算法中常见的odometry转换、IMU积分、上刻位置近似等。而cartographer提供了一个3d预测估计器实现(包含2d实现),可自动融合历史位置、odometer和IMU数据。其调用接口目录为:
cartographer/mapping/pose_extrapolator_interface.h
接口内部为虚函数,具体有两种实现方式,目前2d使用一种PoseExtrapolator,其位姿估计器具体实现代码目录为:
cartographer/mapping/pose_extrapolator.h

pose_extrapolator类定义

  //时间参数,两次预测估计最小时间差
  const common::Duration pose_queue_duration_;
  // 带时间戳的位置结构体
  struct TimedPose {
    common::Time time;
    transform::Rigid3d pose;
  };
  // 带时间戳的位置队列
  std::deque<TimedPose> timed_pose_queue_;
  // 从位置队列估计的线速度和角速度
  Eigen::Vector3d linear_velocity_from_poses_ = Eigen::Vector3d::Zero();
  Eigen::Vector3d angular_velocity_from_poses_ = Eigen::Vector3d::Zero();

  // 重力加速度常数
  const double gravity_time_constant_;
  // Imu 原始data 队列,一般仅保留两个或最新预测时间之后的所有序列
  std::deque<sensor::ImuData> imu_data_;

  // 全局的航向推算器
  std::unique_ptr<ImuTracker> imu_tracker_;
  // 共享临时航向角推算器
  std::unique_ptr<ImuTracker> odometry_imu_tracker_;
  std::unique_ptr<ImuTracker> extrapolation_imu_tracker_;

  // 推算新的位置缓存
  TimedPose cached_extrapolated_pose_;

  // 里程计队列信息,一般仅保留两个或最新预测时间之后的所有序列
  std::deque<sensor::OdometryData> odometry_data_;
  // 通过里程计估计线速度和角速度
  Eigen::Vector3d linear_velocity_from_odometry_ = Eigen::Vector3d::Zero();
  Eigen::Vector3d angular_velocity_from_odometry_ = Eigen::Vector3d::Zero();

pose_extrapolator定义了预测估计器所使用所有变量和参数;其中主要对外方法接口为三种传感器数据的插入和获取预测位置和姿态。

  // 添加传感器数据
  void AddPose(common::Time time, const transform::Rigid3d& pose) override;
  void AddImuData(const sensor::ImuData& imu_data) override;
  void AddOdometryData(const sensor::OdometryData& odometry_data) override;
  
  // 推算出估计位置
  transform::Rigid3d ExtrapolatePose(common::Time time) override;

插入IMU数据 AddImuData()

// 添加imu data
void PoseExtrapolator::AddImuData(const sensor::ImuData& imu_data) {
  CHECK(timed_pose_queue_.empty() ||
        imu_data.time >= timed_pose_queue_.back().time);
  imu_data_.push_back(imu_data);
  TrimImuData();
}

由于IMU可直接提供角速度信息,因此无需进行一定预处理,即已获取imu的角速度信息;

插入里程计数据 AddOdometryData()

// 添加 odometrydata
void PoseExtrapolator::AddOdometryData(
    const sensor::OdometryData& odometry_data) {
  CHECK(timed_pose_queue_.empty() ||
        odometry_data.time >= timed_pose_queue_.back().time);
  odometry_data_.push_back(odometry_data);
  TrimOdometryData();
  if (odometry_data_.size() < 2) {
    return;
  }
  // TODO(whess): Improve by using more than just the last two odometry poses.
  // Compute extrapolation in the tracking frame.
  const sensor::OdometryData& odometry_data_oldest = odometry_data_.front();
  const sensor::OdometryData& odometry_data_newest = odometry_data_.back();
  // 两次时间间隔
  const double odometry_time_delta =
      common::ToSeconds(odometry_data_oldest.time - odometry_data_newest.time);
  // 两次位置转移矩阵
  const transform::Rigid3d odometry_pose_delta =
      odometry_data_newest.pose.inverse() * odometry_data_oldest.pose;

  // 获取旋转速度
  angular_velocity_from_odometry_ =
      transform::RotationQuaternionToAngleAxisVector(
          odometry_pose_delta.rotation()) /
      odometry_time_delta;

  if (timed_pose_queue_.empty()) {
    return;
  }

  // 获取根据里程计转换直接获取最新线速度
  const Eigen::Vector3d
      linear_velocity_in_tracking_frame_at_newest_odometry_time =
          odometry_pose_delta.translation() / odometry_time_delta;

  // 提取里程计和当前真实位置的旋转转移矩阵
  // 其中odometry_imu_tracker_ 与 与刚添加历史位置时的imu_tracker_信息一致
  const Eigen::Quaterniond orientation_at_newest_odometry_time =
      timed_pose_queue_.back().pose.rotation() *
      ExtrapolateRotation(odometry_data_newest.time,
                          odometry_imu_tracker_.get());
  // 提取当前真实线速度                        
  linear_velocity_from_odometry_ =
      orientation_at_newest_odometry_time *
      linear_velocity_in_tracking_frame_at_newest_odometry_time;
}

仅根据odometer估计当前线速度和角速度信息;

UpdateVelocitiesFromPoses()

此函数则是根据历史最近的两帧位姿信息进行微分,获取线速度和角速度。

// 从历史位置估计当前的速度
void PoseExtrapolator::UpdateVelocitiesFromPoses() {
  if (timed_pose_queue_.size() < 2) {
    // We need two poses to estimate velocities.
    return;
  }
  CHECK(!timed_pose_queue_.empty());
  const TimedPose& newest_timed_pose = timed_pose_queue_.back();
  const auto newest_time = newest_timed_pose.time;
  const TimedPose& oldest_timed_pose = timed_pose_queue_.front();
  const auto oldest_time = oldest_timed_pose.time;
  // 计算最近两次位置的时间差
  const double queue_delta = common::ToSeconds(newest_time - oldest_time);
  // 时间间隔需满足超出一定阈值
  if (queue_delta < common::ToSeconds(pose_queue_duration_)) {
    LOG(WARNING) << "Queue too short for velocity estimation. Queue duration: "
                 << queue_delta << " s";
    return;
  }
  // 获取最近两次的位置
  const transform::Rigid3d& newest_pose = newest_timed_pose.pose;
  const transform::Rigid3d& oldest_pose = oldest_timed_pose.pose;
  // 计算线速度
  linear_velocity_from_poses_ =
      (newest_pose.translation() - oldest_pose.translation()) / queue_delta;
  // 计算角速度
  angular_velocity_from_poses_ =
      transform::RotationQuaternionToAngleAxisVector(
          oldest_pose.rotation().inverse() * newest_pose.rotation()) /
      queue_delta;
}

插入历史位置AddPose()

// 添加历史位置
void PoseExtrapolator::AddPose(const common::Time time,
                               const transform::Rigid3d& pose) {
  // 2d slam imu_tracker_ 没有初始化
  // 第一次加入历史位置时,需初始化
  if (imu_tracker_ == nullptr) {
    // 当前时间为初始时间
    common::Time tracker_start = time;
    // 如果有imu数据的话, 取imu和当前时间最早时间
    if (!imu_data_.empty()) {
      tracker_start = std::min(tracker_start, imu_data_.front().time);
    }
    // 初始化 角度推算器
    imu_tracker_ =
        absl::make_unique<ImuTracker>(gravity_time_constant_, tracker_start);
  }
  // 记录对应时间和位置,放入一个队列存储
  timed_pose_queue_.push_back(TimedPose{time, pose});
  // 确保仅保留两个,同时最新位置的间隔不超出参数阈值
  while (timed_pose_queue_.size() > 2 &&
         timed_pose_queue_[1].time <= time - pose_queue_duration_) {
    timed_pose_queue_.pop_front();
  }
  // 从位置中更新速度信息
  UpdateVelocitiesFromPoses();
  //将全局imu推算器执行推算,主要推算估计的航向信息
  AdvanceImuTracker(time, imu_tracker_.get());
  TrimImuData();
  TrimOdometryData();
  // 智能指针创建,并共享内容,无需delete
  // 即每次添加新的pose时同时开辟两个tracker
  odometry_imu_tracker_ = absl::make_unique<ImuTracker>(*imu_tracker_);
  extrapolation_imu_tracker_ = absl::make_unique<ImuTracker>(*imu_tracker_);
}

插入历史位置进行更新估计为主要和必要方法,即使没有odometer和imu插入数据也可正常工作,因此AddPose()可为主要函数,即目的按照时间顺序插入历史真实位置(实际上为scan-match后较为准确的位置)。然后进行估计线速度和角速度。通过角速度再次进行积分获取航向变换信息。

航向角积分实现AdvanceImuTracker()

//imu 估计器更新,即迭代更新当前估计位置的航向信息, 估计器采用指针可调用不同具体估计器
// 
void PoseExtrapolator::AdvanceImuTracker(const common::Time time,
                                         ImuTracker* const imu_tracker) const {
  CHECK_GE(time, imu_tracker->time());
  // 当没有imu数据或者,更新的当前时间比imu数据时间早
  if (imu_data_.empty() || time < imu_data_.front().time) {
    // There is no IMU data until 'time', so we advance the ImuTracker and use
    // the angular velocities from poses and fake gravity to help 2D stability.
    // 计算出当前估计的朝向信息和重力加速度信息
    imu_tracker->Advance(time);
    // 无imu,则无线性加速度信息
    imu_tracker->AddImuLinearAccelerationObservation(Eigen::Vector3d::UnitZ());
    // 更新推算器中目前采用的角速度
    // 如果有里程计信息,显然更相信里程计信息
    imu_tracker->AddImuAngularVelocityObservation(
        odometry_data_.size() < 2 ? angular_velocity_from_poses_
                                  : angular_velocity_from_odometry_);
    return;
  }
  // 按照最新传感器时间进行更新航向角度信息
  if (imu_tracker->time() < imu_data_.front().time) {
    // Advance to the beginning of 'imu_data_'.
    imu_tracker->Advance(imu_data_.front().time);
  }
  auto it = std::lower_bound(
      imu_data_.begin(), imu_data_.end(), imu_tracker->time(),
      [](const sensor::ImuData& imu_data, const common::Time& time) {
        return imu_data.time < time;
      });
  // 将imudata中每一时刻进行估计更新
  while (it != imu_data_.end() && it->time < time) {
    imu_tracker->Advance(it->time);
    imu_tracker->AddImuLinearAccelerationObservation(it->linear_acceleration);
    imu_tracker->AddImuAngularVelocityObservation(it->angular_velocity);
    ++it;
  }
  // 最新更新
  imu_tracker->Advance(time);
}

由于已估计出目前的线速度和角速度,为获取估计姿态则需要对角速度进行积分,然后获取角度旋转矩阵。由于航向积分并非像平移一样线性加减即可,因此cartographer专门提供了一个用于角度积分的ImuTracker类。

ImuTracker类定义

代码目录如下:
cartographer/mapping/imu_tracker.h
类定义如下:

  const double imu_gravity_time_constant_;      // 重力加速度时间更新参数
  common::Time time_;                           // 积分最新时间戳
  common::Time last_linear_acceleration_time_;  // 上时刻更新重力加速度时间戳
  Eigen::Quaterniond orientation_;              // 积分器自身维持的航向值
  Eigen::Vector3d gravity_vector_;              // 重力加速度向量
  Eigen::Vector3d imu_angular_velocity_;        // 用于积分的角速度

其核心的积分方法为void Advance(common::Time time);其具体实现如下:

// 积分计算出当前估计的朝向信息
void ImuTracker::Advance(const common::Time time) {
  CHECK_LE(time_, time);
  // 当前时刻与上次时刻差
  const double delta_t = common::ToSeconds(time - time_);
  // 根据旋转速度计算旋转矩阵
  const Eigen::Quaterniond rotation =
      transform::AngleAxisVectorToRotationQuaternion(
          Eigen::Vector3d(imu_angular_velocity_ * delta_t));
  // 更新当前定位方向信息
  orientation_ = (orientation_ * rotation).normalized();
  // 根据旋转信息更新重力加速度方向
  // conjugate()表示共轭
  gravity_vector_ = rotation.conjugate() * gravity_vector_;
  time_ = time;
}

其中角度积分其出现关于重力加速度信息的,对于2d slam作用不是太大。其主要作用是判断激光雷达是否为真实水平,若有一定夹角(即重力加速度向量gravity_vector_),则需进行投影。即具体更新过程不再详细描述。

获取预测位置ExtrapolatePose(const common::Time time)

//输出当前时间的估计的位置
transform::Rigid3d PoseExtrapolator::ExtrapolatePose(const common::Time time) {
  // 获取最新的即上次的位置和姿态信息
  const TimedPose& newest_timed_pose = timed_pose_queue_.back();
  // 当前时间更晚
  CHECK_GE(time, newest_timed_pose.time);
  // 和上次更新的时间一致,则可直接返回上次获取估计的位置
  // 否则进行换算
  if (cached_extrapolated_pose_.time != time) {
    // 获取估计的平移矩阵
    // 然后对上次的位置进行平移,获取估计位置
    const Eigen::Vector3d translation =
        ExtrapolateTranslation(time) + newest_timed_pose.pose.translation();
    // 获取估计旋转矩阵
    // 然后对上次的航向进行旋转,获取估计的航向
    // extrapolation_imu_tracker_ 与最新加入历史位置时的imu_tracker_一致
    const Eigen::Quaterniond rotation =
        newest_timed_pose.pose.rotation() *
        ExtrapolateRotation(time, extrapolation_imu_tracker_.get());
    cached_extrapolated_pose_ =
        TimedPose{time, transform::Rigid3d{translation, rotation}};
  }
  return cached_extrapolated_pose_.pose;
}

以上最终输出预测值接口,在上次真实位置基础上进行积分,即获取平移矩阵和旋转矩阵,进行转换获取最终估计位置和姿态;

其中位置平移矩阵积分较为简单,可参考代码即可。

//获取当前时间与插入最新位置的平移矩阵
Eigen::Vector3d PoseExtrapolator::ExtrapolateTranslation(common::Time time) {
  // 最新插入的位置信息
  const TimedPose& newest_timed_pose = timed_pose_queue_.back();
  // 计算时间差
  const double extrapolation_delta =
      common::ToSeconds(time - newest_timed_pose.time);

  // 根据线速度直接计算
  if (odometry_data_.size() < 2) {
    return extrapolation_delta * linear_velocity_from_poses_;
  }
  return extrapolation_delta * linear_velocity_from_odometry_;
}

而旋转转换矩阵,参考代码如下。由于专门ImuTracker角度积分器自己内部维持了个角度积分值,因此需要提取出最新的航向与上次航向的过程旋转矩阵。

// 获取当前时间与插入最新位置的旋转变换矩阵
Eigen::Quaterniond PoseExtrapolator::ExtrapolateRotation(
    const common::Time time, ImuTracker* const imu_tracker) const {
  CHECK_GE(time, imu_tracker->time());
  // 更新当前的航向信息
  AdvanceImuTracker(time, imu_tracker);
  // 获取全局imu_tracker_ 朝向,由于未进行更新,即为上一时刻的朝向
  const Eigen::Quaterniond last_orientation = imu_tracker_->orientation();
  // 求出上次到最新航向矩阵的转换矩阵
  return last_orientation.inverse() * imu_tracker->orientation();
}

思想总结

位姿估计器解决的是在实现前端scan-match之前的预测值,即激光新的一帧匹配前获取当前预测定位信息。同时假设传感器数据之间,slam本体以恒定线速度和恒定角速度移动。根据上次的scan-match的准确位置后进行积分,获取预测位置和姿态。
其中估计的恒定速度主要两种方式组成:采用两次scan-match位置进行微分获得;另一种通过里程计进行微分获得;由于里程计的更新速度更快,故里程计可计算最新结果时,优先使用;
估计的角速度则有三种方式组成:1.采用两次scan-match的航向进行微分获得,频率最低;2.采用里程计进行微分;3.采用IMU直接获取,优先级最高;

cartographer针对imu和odometer是否使用可进行配置,根据配置信息和时间序列进行选择具体方式,最后根据估计的线速度和角速度进行实时积分,获取预测估计位置。
最基本的配置odometry和imu均无,则估计器实际上仅是根据历史位置进行推算出线速度和角速度,进行估计位置。

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

cartographer 代码思想解读(7)-位姿估计器PoseExtrapolator实现 的相关文章

  • 大师兄!SLAM 为什么需要李群与李代数?

    from https mp weixin qq com s sVjy9kr 8qc9W9VN78JoDQ 作者 electech6 来源 计算机视觉life 编辑 Tony 很多刚刚接触SLAM的小伙伴在看到李群和李代数这部分的时候 都有点
  • slam数学基础——最小二乘

    一 前言 1 最小二乘是一类特殊形式的最优化求解问题 相比于其他优化问题 求解方式比较简洁 2 最小二乘被广泛应用于各种实际问题的建模 在求解实际工程问题中有着广泛的应用 例如 slam 中随处可见最小二乘的声影 二 线性最小二乘法 1 预
  • 速腾聚创雷达最新驱动安装(包含ring和timestamp)运行lio-sam

    记录一下搞slam的过程 ring和timestamp 最近想跑lio sam 需要用到ring和timestamp两个参数 lio sam作者用的velodyne雷达是带这两个参数的 但是rs雷达的老版驱动录制的点云包没有这两个参数 在g
  • Sophus使用记录

    sophus库是一个基于Eigen的C 李群李代数库 可以用来方便地进行李群李代数的运算 头文件 主要用到以下两个头文件 include
  • 视觉SLAM实践入门——(20)视觉里程计之直接法

    多层直接法的过程 1 读图 随机取点并计算深度 2 创建图像金字塔 相机内参也需要缩放 并计算对应点的像素坐标 3 应用单层直接法 使用G N L M等方法 或者使用g2o ceres库 进行优化 源码中有一些地方会引起段错误 修改方法见下
  • 从零开始一起学习SLAM(9)不推公式,如何真正理解对极约束?

    文章目录 对极几何基本概念 如何得到极线方程 作业 此文发于公众号 计算机视觉life 原文链接 从零开始一起学习SLAM 不推公式 如何真正理解对极约束 自从小白向师兄学习了李群李代数和相机成像模型的基本原理后 感觉书上的内容没那么难了
  • 【SLAM】libQGLViewer:VS 2019 + Qt 5.14.2 + Win 10 配置

    libQGLViewer 2 7 2 VS 2019 Qt 5 14 2 Win 10 配置 注意 这次配置没有完全成功 编译25个成功 一个失败 失败的是 qglviewerplugin qglviewerplugin 是一个可选控件 不
  • [SLAM四元数基础系列一] 四元数定义 Hamilton vs JPL

    四元数定义 Hamilton vs JPL 简介 四种区分方式 Hamilton vs JPL 引用 不管是卡尔曼滤波或者BA优化形式的SLAM或者VIO系统中 都需要用到单位四元数 Quaternion 来表示旋转 主要是单位四元数表示旋
  • 深度相机Kinect2.0三维点云拼接实验(一)

    文章目录 摘要 Kinect2 0简介 工作原理 RGB相机成像原理 深度相机成像原理 总结 参考文献 摘要 Kinect2 0是微软推出的一款RGB D相机 它即支持普通相机的拍摄 也支持脉冲测量深度信息 本系列文章基于该传感器给出基本的
  • 图像匹配算法

    图像匹配算法分为3类 基于灰度的匹配算法 基于特征的匹配算法 基于关系的匹配算法 1 基于灰度的模板匹配算法 模板匹配 Blocking Matching 是根据已知模板图像到另一幅图像中寻找与模板图像相似的子图像 基于灰度的匹配算法也称作
  • Sophus安装踩坑

    装SLAM十四讲第二版提供的Sophus Eigen版本3 4 0 报错 home ch 下载 Sophus 13fb3288311485dc94e3226b69c9b59cd06ff94e test core test so2 cpp 9
  • Ubuntu20.04安装各种库----简洁版

    目录 Eigen3 Sophus Pangolin Ceres g2o 建议先装anaconda再装ros python opencv啥该有的都有了 下面仅仅安装ros没有的库 Eigen3 作用 线性代数开源库 提供了有关线性代数 矩阵和
  • Ceres Solver从零开始手把手教学使用

    目录 一 简介 二 安装 三 介绍 四 Hello Word 五 导数 1 数值导数 2解析求导 六 实践 Powell函数 一 简介 笔者已经半年没有更新新的内容了 最近学习视觉SLAM的过程中发现自己之前学习的库基础不够扎实 Ceres
  • 舒尔补-边际概率-条件概率

    margin求边际概率的时候喜欢通过舒尔补的形式去操作信息矩阵 如p b c 求积分p a b c da 从上图可知 边缘概率直接看协方差矩阵比较方便 边际概率的方差就是取对应联合分布中相应的协方差块 信息矩阵是由舒尔补的形式计算 此形式也
  • LeGO-LOAM中的数学公式推导

    LeGO LOAM是一种在LOAM之上进行改进的激光雷达建图方法 建图效果比LOAM要好 但是建图较为稀疏 计算量也更小了 本文原地址 wykxwyc的博客 github注释后LeGO LOAM源码 LeGO LOAM NOTED 关于代码
  • 二.全局定位--开源定位框架livox-relocalization实录数据集测试

    相关博客 二十五 SLAM中Mapping和Localization区别和思考 goldqiu的博客 CSDN博客 二十五 SLAM中Mapping和Localization区别和思考 goldqiu的博客 CSDN博客 基于固态雷达的全局
  • Object SLAM: An Object SLAM Framework for Association, Mapping, and High-Level Tasks 论文解读

    是一篇来自机器人顶刊T RO的文章 发表于2023 5 An Object SLAM Framework for Association Mapping and High Level Tasks 论文 An Object SLAM Fram
  • LIO-SAM运行自己数据包遇到的问题解决--SLAM不学无数术小问题

    LIO SAM 成功适配自己数据集 注意本文测试环境 Ubuntu18 04 ROS melodic版本 笔者用到的硬件以简单参数 激光雷达 速腾聚创16线激光雷达 RS Lidar 16 IMU 超核电子CH110型 9轴惯导 使用频率1
  • ORB_SLAM2运行官方数据集/自己数据集

    官方数据集运行结果 WeChat 20230210194425 可以正常运行 自己数据集运行结果 自己的数据集 主要是用手机摄像头采集的实验室进行了一下简单的运行 可以成功运行 但是由于查看的相关程序的是死循环不能像运行官方数据集那样完整保
  • KITTI校准文件中参数的格式

    我从以下位置访问了校准文件KITTI 的部分里程计 http www cvlibs net datasets kitti eval odometry php 其中一个校准文件的内容如下 P0 7 188560000000e 02 0 000

随机推荐

  • java连接数据库的查询方法(一个方法查任意表,任意字段)

    目录 前言 1 普通查询方法 1 1实现步骤 1 1 1预加载 1 1 2通过驱动管理器获取Connection对象 1 1 2通过Connection对象来创建命令对象 1 1 3通过命令对象获取结果集 2 不普通的查询方法 2 1获取类
  • 原型的价值与注意事项

    原型的价值 对于产品经理来讲在工作中原型的价值主要体现以下三个部分 在原型设计阶段花费的时间 可以避免额外的工作 节省总体时间 精力和成本 原型是展示 讲述 体验之源 常用于做早期评审 以确保你的想法符合目标市场 相对于文档 产品原型更加有
  • codemirror 部分配置信息 中文解释

    介绍 CodeMirror是一款在线的支持语法高亮的代码编辑器 官网 http codemirror net 下载后 解压开到的文件夹中 lib下是放的是核心库和核心css 模式下放的是各种支持语言的语法定义 主题目录下是支持的主题样式 一
  • r语言练习题3

    r语言练习题3 选择题练习 1 创建R 的数据框的函数 2 R中和 或非分别表示为 3 round 3 475 digits 2 返回值为 4 用R语言绘制直方图的命令 4 1用R语言绘制柱形图的命令 4 2用R语言绘制饼图的命令 5 R中
  • 如何写好技术方案

    一 前言 作为一个技术开发者 特别是高级 资深开发 架构师等 往往会遇到根据需求撰写技术方案 那么如何撰写一篇好的技术方案设计 我们今天就来聊一聊这个话题 二 技术方案是否有必要 答案是肯定的 我见过太多由于前期规划不到位 甚至是没有技术方
  • WebGL解决中文字体过大的问题

    最近公司项目要转成Webgl格式 打包测试后发现会把字体文件一同打包进去 导致打包文件很大 打包了一个空场景 使用的字体是Window自带的微软雅黑 字体占用空间很大 后来发现Unity有一个自带的TextMesh Pro 也可以进行文字输
  • 在已安装windows11环境中利用EasyBCD引导安装Ubuntu22.04(无需U盘)

    一 下载 1 下载Ubuntu 放到C盘 官网下载https ubuntu com download desktop 2 下载EasyBCD 链接 https pan baidu com s 1 KDr6kmVKH2u43W6XKYURg
  • 随笔篇----比特的传输

    琐碎的知识又增加了 这是我觉的比较有意思的科普 解开了一些生活中的小疑问 例如 电话 网页 电视 这些信号怎么传递的 人们怎么就成了千里眼 顺风耳 本文目的为 简单科普我们生活周围的信息是如何传递 如果学过通信的朋友可以略过啦哈 关键词 A
  • 软件测试阶段的风险

    1 需求风险 2 测试用例风险 3 缺陷风险 4 代码质量风险 5 测试环境风险 6 测试技术风险 7 回归测试风险 8 沟通协调风险 9 其他不可预计的风险 软件测试常见的风险 主要有以下几点 1 需求风险 对软件需求理解不够准确 导致测
  • pyinstaller打包执行文件报错NameError: name ‘defaultParams‘ is not defined问题解决方案

    老猿Python博文目录 https blog csdn net LaoYuanPython 一 问题 最近在执行以前打包的一个PyQT程序时报错 错误信息如下 F coffeDog videoToGif dist video2Gif gt
  • 本地资源加载不了 file:// net::ERR_UNKNOWN_URL_SCHEME

    本地资源加载不了 file net ERR UNKNOWN URL SCHEME 解决 开发环境使用tsFile 生产环境使用file
  • 用git创建空白分支

    用git创建空白分支 许多时候 需要添加的分支的代码与原来的代码没有一点关系 若是我们创建分支的话 则会继承master原来的东西 此时可以创建一个空白的分支 git checkout orphan test 这个时候会有许多无关文件存在
  • arp攻击实验(一)用一条指令让对方瞬间无法上网

    前言 本实验主要是利用局域网主机在进行2 3层通信时 协议上的漏洞 利用ARP欺骗 造成局域网内主机通信的失败 本文摘自 泰泰博客 www taitaiblog com 更多相关资源可访问该网站 实现原理 其主要原理是局域网内的 攻击机 通
  • centos 7 中的对文件的基本操作

    在文件夹中创造文件 touch 未创建前 在当前文件夹创建后并查看 创造文件夹 mkdir 未创建前 创建之后并查看 如果需要一次创造多个文件夹时 需要用到参数 p 递归创建文件夹 并用命令tree查看文件的结构 文件的复制 cp 复制te
  • [转]OpenSynergy的COQOS Hypervisor SDK-实现仪表和车载信息娱乐系统共享显示

    如果你认为本系列文章对你有所帮助 请大家有钱的捧个钱场 点击此处赞助 赞助额0 1元起步 多少随意 声明 本文只用于个人学习交流 若不慎造成侵权 请及时联系我 立即予以改正 锋影 email 174176320 qq com OpenSyn
  • python3[爬虫实战] 使用selenium,xpath爬取京东手机(上)

    当然了 这个任务也是从QQ群里面接过来的 主要是想提升自己的技术 一接过来是很开心的 但是 接完之后 写了又写 昨晚写了3小时 前提晚上写了2小时 搞的有些晚了 搞来搞去就卡在一个地方了 希望懂的大神们多帮忙指点一下 使用selenium
  • chatgpt赋能python:Python重写父类方法:在OOP编程中的应用

    Python重写父类方法 在OOP编程中的应用 在Python的面向对象编程范式中 继承是一种非常重要的概念 当我们声明一个类时 我们可以通过继承来扩展类的功能并避免重复编写代码 在这个过程中 很可能你会碰到需要重写父类方法的情况 本篇文章
  • java输出1-100之间不能被5整除的数,每五个一行

    public class HomeWork06 public static void main String args int count 0 for int i 1 i lt 100 i if i 5 0 System out print
  • 剑指 Offer II 033. 变位词组&剑指 Offer II 035. 最小时间差 -做题总结和心得(剑指offer进阶-哈希表部分)

    剑指 Offer II 033 变位词组 该题解法是hash sort 答案是评论区大佬写的 大体思路 创建一个哈希表 再将每个字符串变成字符数组 排序后若哈希表里不存在排序后的字符串 则创建索引 然后将原字符串放进对应索引的数组里 cla
  • cartographer 代码思想解读(7)-位姿估计器PoseExtrapolator实现

    cartographer 代码思想解读 7 位姿估计器PoseExtrapolator实现 pose extrapolator类定义 插入IMU数据 AddImuData 插入里程计数据 AddOdometryData UpdateVelo