Autoware 定位之数据稳定处理(十)

news2024/12/25 14:23:10

0. 简介

这一讲按照《Autoware 技术代码解读(三)》梳理的顺序,我们来说一说Autoware中的数据稳定处理操作,这一讲的内容比较多,主要分为:

  1. pose_instability_detector 节点,旨在监测 /localization/kinematic_state 的稳定性,该主题是扩展卡尔曼滤波器(EKF)的输出。
  2. pose2twist 节点从输入的姿态历史中计算速度。除了计算出的twist之外,该节点还输出线性-x和角度-z分量作为浮点消息,以简化调试。
  3. stop_filter 当这个功能不存在时,每个节点都使用不同的标准来确定车辆是否停止,导致一些节点在停车模式下运行,而另一些节点继续以驾驶模式运行。
  4. tree_structured_parzen_estimator 是一个用于黑盒优化的软件包。这个软件包没有节点,它只是一个库。
  5. twist2accel 这个软件包负责利用ekf_localizer的输出来估计加速度。它使用低通滤波器来减少噪音。

1. pose_instability_detector节点代码阅读

这段代码实现了一个姿态不稳定性检测器,用于监测机器人姿态变化是否超出设定阈值。在初始化函数中,节点名称和参数被设置,并创建了订阅器、定时器和发布器。订阅器用于接收里程计和速度信息,定时器用于定时触发回调函数,发布器用于发布调试信息和诊断信息。

当接收到里程计和速度信息时,会分别调用回调函数,将最新的信息存储起来。定时器触发的回调函数会计算姿态变化,并发布诊断信息和调试信息。首先判断是否有足够的数据进行计算,然后将四元数转换为欧拉角,比较姿态信息和最新的里程计信息,计算出姿态差异。接着根据设定的阈值对姿态差异进行判断,并发布诊断信息和调试信息。当中比较两个姿态:

通过在/localization/kinematic_state接收到的最后一条消息中积分得到的扭矩数值,积分的持续时间由interval_sec指定。
来自/localization/kinematic_state的最新姿态。
然后将比较结果输出到/diagnostics话题。

如果这个节点向/diagnostics输出警告消息,意味着EKF输出与积分得到的扭矩数值存在显著差异。这种差异表明可能存在估计姿态或输入扭矩方面的问题。

以下图表概述了这个过程的时间线是什么样子的:

在这里插入图片描述

/// @brief 初始化了节点名称和参数,并创建了订阅器、定时器和发布器
/// @param options 节点选项
PoseInstabilityDetector::PoseInstabilityDetector(const rclcpp::NodeOptions & options)
: Node("pose_instability_detector", options),
  threshold_diff_position_x_(this->declare_parameter<double>("threshold_diff_position_x")),
  threshold_diff_position_y_(this->declare_parameter<double>("threshold_diff_position_y")),
  threshold_diff_position_z_(this->declare_parameter<double>("threshold_diff_position_z")),
  threshold_diff_angle_x_(this->declare_parameter<double>("threshold_diff_angle_x")),
  threshold_diff_angle_y_(this->declare_parameter<double>("threshold_diff_angle_y")),
  threshold_diff_angle_z_(this->declare_parameter<double>("threshold_diff_angle_z"))
{
  odometry_sub_ = this->create_subscription<Odometry>(
    "~/input/odometry", 10,
    std::bind(&PoseInstabilityDetector::callback_odometry, this, std::placeholders::_1));//接收Odometry消息的订阅器

  twist_sub_ = this->create_subscription<TwistWithCovarianceStamped>(
    "~/input/twist", 10,
    std::bind(&PoseInstabilityDetector::callback_twist, this, std::placeholders::_1));//接收TwistWithCovarianceStamped消息的订阅器

  const double interval_sec = this->declare_parameter<double>("interval_sec");
  timer_ = rclcpp::create_timer(
    this, this->get_clock(), std::chrono::duration<double>(interval_sec),
    std::bind(&PoseInstabilityDetector::callback_timer, this));//定时触发回调函数

  diff_pose_pub_ = this->create_publisher<PoseStamped>("~/debug/diff_pose", 10);
  diagnostics_pub_ = this->create_publisher<DiagnosticArray>("/diagnostics", 10);
}

/// @brief 当接收到odometry消息时被调用,将最新的odometry消息存储在latest_odometry_中
/// @param odometry_msg_ptr 指向接收到的Odometry消息的指针
void PoseInstabilityDetector::callback_odometry(Odometry::ConstSharedPtr odometry_msg_ptr)
{
  latest_odometry_ = *odometry_msg_ptr;
}

/// @brief 当接收到twist消息时被调用,将最新的twist消息存储在twist_buffer_中
/// @param twist_msg_ptr 指向接收到的TwistWithCovarianceStamped消息的指针
void PoseInstabilityDetector::callback_twist(
  TwistWithCovarianceStamped::ConstSharedPtr twist_msg_ptr)
{
  twist_buffer_.push_back(*twist_msg_ptr);
}

/// @brief 定时器触发的回调函数,用于计算姿态变化并发布诊断信息和调试信息
void PoseInstabilityDetector::callback_timer()
{
    //首先判断是否有足够的数据进行计算,若无则返回
  if (latest_odometry_ == std::nullopt) {
    return;
  }
  if (prev_odometry_ == std::nullopt) {
    prev_odometry_ = latest_odometry_;
    return;
  }

//将四元数转换为欧拉角函数
  auto quat_to_rpy = [](const Quaternion & quat) {
    tf2::Quaternion tf2_quat(quat.x, quat.y, quat.z, quat.w);
    tf2::Matrix3x3 mat(tf2_quat);
    double roll{};
    double pitch{};
    double yaw{};
    mat.getRPY(roll, pitch, yaw);
    return std::make_tuple(roll, pitch, yaw);
  };

    //比较姿态信息和最新的odometry信息,计算出姿态差异。
  Pose pose = prev_odometry_->pose.pose;
  rclcpp::Time prev_time = rclcpp::Time(prev_odometry_->header.stamp);
  for (const TwistWithCovarianceStamped & twist_with_cov : twist_buffer_) {
    const Twist twist = twist_with_cov.twist.twist;

    const rclcpp::Time curr_time = rclcpp::Time(twist_with_cov.header.stamp);
    if (curr_time > latest_odometry_->header.stamp) {
      break;
    }

    const rclcpp::Duration time_diff = curr_time - prev_time;
    const double time_diff_sec = time_diff.seconds();
    if (time_diff_sec < 0.0) {
      continue;
    }

    // quat to rpy
    auto [ang_x, ang_y, ang_z] = quat_to_rpy(pose.orientation);

    // 更新欧拉角
    ang_x += twist.angular.x * time_diff_sec;
    ang_y += twist.angular.y * time_diff_sec;
    ang_z += twist.angular.z * time_diff_sec;
    tf2::Quaternion quat;
    quat.setRPY(ang_x, ang_y, ang_z);

    // Convert twist to world frame
    // 将twist转换到世界坐标系中
    tf2::Vector3 linear_velocity(twist.linear.x, twist.linear.y, twist.linear.z);
    linear_velocity = tf2::quatRotate(quat, linear_velocity);

    // update
    pose.position.x += linear_velocity.x() * time_diff_sec;
    pose.position.y += linear_velocity.y() * time_diff_sec;
    pose.position.z += linear_velocity.z() * time_diff_sec;
    pose.orientation.x = quat.x();
    pose.orientation.y = quat.y();
    pose.orientation.z = quat.z();
    pose.orientation.w = quat.w();
    prev_time = curr_time;
  }

  // 比较姿态信息和最新的odometry信息,计算出姿态差异。
  const Pose latest_ekf_pose = latest_odometry_->pose.pose;
  const Pose ekf_to_odom = tier4_autoware_utils::inverseTransformPose(pose, latest_ekf_pose);
  const geometry_msgs::msg::Point pos = ekf_to_odom.position;
  const auto [ang_x, ang_y, ang_z] = quat_to_rpy(ekf_to_odom.orientation);
  const std::vector<double> values = {pos.x, pos.y, pos.z, ang_x, ang_y, ang_z};

  const rclcpp::Time stamp = latest_odometry_->header.stamp;

  // 发布调试信息
  PoseStamped diff_pose;
  diff_pose.header = latest_odometry_->header;
  diff_pose.pose = ekf_to_odom;
  diff_pose_pub_->publish(diff_pose);

//阈值
  const std::vector<double> thresholds = {threshold_diff_position_x_, threshold_diff_position_y_,
                                          threshold_diff_position_z_, threshold_diff_angle_x_,
                                          threshold_diff_angle_y_,    threshold_diff_angle_z_};

  const std::vector<std::string> labels = {"diff_position_x", "diff_position_y", "diff_position_z",
                                           "diff_angle_x",    "diff_angle_y",    "diff_angle_z"};

  // 发布诊断信息
  DiagnosticStatus status;
  status.name = "localization: pose_instability_detector";
  status.hardware_id = this->get_name();
  bool all_ok = true;

  for (size_t i = 0; i < values.size(); ++i) {
    const bool ok = (std::abs(values[i]) < thresholds[i]);
    all_ok &= ok;
    diagnostic_msgs::msg::KeyValue kv;
    kv.key = labels[i] + ":threshold";
    kv.value = std::to_string(thresholds[i]);
    status.values.push_back(kv);
    kv.key = labels[i] + ":value";
    kv.value = std::to_string(values[i]);
    status.values.push_back(kv);
    kv.key = labels[i] + ":status";
    kv.value = (ok ? "OK" : "WARN");
    status.values.push_back(kv);
  }
  status.level = (all_ok ? DiagnosticStatus::OK : DiagnosticStatus::WARN);
  status.message = (all_ok ? "OK" : "WARN");

  DiagnosticArray diagnostics;
  diagnostics.header.stamp = stamp;
  diagnostics.status.emplace_back(status);
  diagnostics_pub_->publish(diagnostics);

  // prepare for next loop
  prev_odometry_ = latest_odometry_;
  twist_buffer_.clear();
}

2. pose2twist代码阅读

这里将位姿信息转换为速度信息。在构造函数中,初始化了ROS节点,并创建了发布者和订阅者。代码中包含了一些帮助函数,用于计算两个弧度之间的差值,获取位姿信息中的roll、pitch和yaw角度,并根据两个位姿消息计算线速度和角速度。回调函数处理接收到的位姿消息,计算并发布相应的速度消息。

当中twist.linear.x被计算为 s q r t ( d x ∗ d x + d y ∗ d y + d z ∗ d z ) / d t sqrt(d_x * d_x + d_y * d_y + d_z * d_z) / dt sqrt(dxdx+dydy+dzdz)/dt,而y和z字段中的值为零。twist.angular被计算为每个字段的d_roll / dt,d_pitch / dt和d_yaw / dt。

/// @brief 是类的构造函数,初始化了ROS节点,并创建了发布者和订阅者
Pose2Twist::Pose2Twist() : Node("pose2twist_core")
{
  using std::placeholders::_1;

  static constexpr std::size_t queue_size = 1;
  rclcpp::QoS durable_qos(queue_size);
  durable_qos.transient_local();

  twist_pub_ = create_publisher<geometry_msgs::msg::TwistStamped>("twist", durable_qos);
  linear_x_pub_ = create_publisher<tier4_debug_msgs::msg::Float32Stamped>("linear_x", durable_qos);
  angular_z_pub_ =
    create_publisher<tier4_debug_msgs::msg::Float32Stamped>("angular_z", durable_qos);
  // Note: this callback publishes topics above
  pose_sub_ = create_subscription<geometry_msgs::msg::PoseStamped>(
    "pose", queue_size, std::bind(&Pose2Twist::callbackPose, this, _1));
}

/// @brief 函数计算两个弧度之间的差值,确保结果在-pi到pi之间
/// @param lhs_rad 左边的弧度
/// @param rhs_rad 右边的弧度
/// @return 
double calcDiffForRadian(const double lhs_rad, const double rhs_rad)
{
  double diff_rad = lhs_rad - rhs_rad;//计算两个弧度之间的差值
  if (diff_rad > M_PI) {
    diff_rad = diff_rad - 2 * M_PI;
  } else if (diff_rad < -M_PI) {
    diff_rad = diff_rad + 2 * M_PI;
  }
  return diff_rad;
}

// x: roll, y: pitch, z: yaw
/// @brief 根据输入的位姿信息,返回roll、pitch和yaw角度。
/// @param pose 输入的位姿信息
/// @return 
geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::Pose & pose)
{
  geometry_msgs::msg::Vector3 rpy;
  tf2::Quaternion q(pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w);
  tf2::Matrix3x3(q).getRPY(rpy.x, rpy.y, rpy.z);
  return rpy;
}

/// @brief 调用上面的getRPY函数,获取传入位姿消息指针对应的roll、pitch和yaw角度。
/// @param pose 位姿消息指针
/// @return 
geometry_msgs::msg::Vector3 getRPY(geometry_msgs::msg::PoseStamped::SharedPtr pose)
{
  return getRPY(pose->pose);
}

/// @brief 根据两个位姿消息计算得到线速度和角速度,然后封装成TwistStamped消息返回
/// @param pose_a 位置a
/// @param pose_b 位置b
/// @return 
geometry_msgs::msg::TwistStamped calcTwist(
  geometry_msgs::msg::PoseStamped::SharedPtr pose_a,
  geometry_msgs::msg::PoseStamped::SharedPtr pose_b)
{
  const double dt =
    (rclcpp::Time(pose_b->header.stamp) - rclcpp::Time(pose_a->header.stamp)).seconds();

  if (dt == 0) {
    geometry_msgs::msg::TwistStamped twist;
    twist.header = pose_b->header;
    return twist;
  }

  const auto pose_a_rpy = getRPY(pose_a);
  const auto pose_b_rpy = getRPY(pose_b);

  geometry_msgs::msg::Vector3 diff_xyz;
  geometry_msgs::msg::Vector3 diff_rpy;

//计算两个位姿之间的差值
  diff_xyz.x = pose_b->pose.position.x - pose_a->pose.position.x;
  diff_xyz.y = pose_b->pose.position.y - pose_a->pose.position.y;
  diff_xyz.z = pose_b->pose.position.z - pose_a->pose.position.z;
  diff_rpy.x = calcDiffForRadian(pose_b_rpy.x, pose_a_rpy.x);
  diff_rpy.y = calcDiffForRadian(pose_b_rpy.y, pose_a_rpy.y);
  diff_rpy.z = calcDiffForRadian(pose_b_rpy.z, pose_a_rpy.z);

  geometry_msgs::msg::TwistStamped twist;
  twist.header = pose_b->header;
  //计算线速度和角速度
  twist.twist.linear.x =
    std::sqrt(std::pow(diff_xyz.x, 2.0) + std::pow(diff_xyz.y, 2.0) + std::pow(diff_xyz.z, 2.0)) /
    dt;
  twist.twist.linear.y = 0;
  twist.twist.linear.z = 0;
  twist.twist.angular.x = diff_rpy.x / dt;
  twist.twist.angular.y = diff_rpy.y / dt;
  twist.twist.angular.z = diff_rpy.z / dt;

  return twist;
}
/// @brief 回调函数,处理接收到的位姿消息,计算并发布相应的速度消息。其中包括计算两次位姿之间的变化量,更新上一次的位姿消息,计算速度消息并发布。
/// @param pose_msg_ptr 指向接收到的位姿消息的指针
void Pose2Twist::callbackPose(geometry_msgs::msg::PoseStamped::SharedPtr pose_msg_ptr)
{
  // TODO(YamatoAndo) check time stamp diff
  // TODO(YamatoAndo) check suddenly move
  // TODO(YamatoAndo) apply low pass filter

  geometry_msgs::msg::PoseStamped::SharedPtr current_pose_msg = pose_msg_ptr;
  static geometry_msgs::msg::PoseStamped::SharedPtr prev_pose_msg = current_pose_msg;
  //计算两次位姿之间的变化量
  geometry_msgs::msg::TwistStamped twist_msg = calcTwist(prev_pose_msg, current_pose_msg);
  prev_pose_msg = current_pose_msg;
  twist_msg.header.frame_id = "base_link";
  twist_pub_->publish(twist_msg);

  tier4_debug_msgs::msg::Float32Stamped linear_x_msg;
  linear_x_msg.stamp = this->now();
  linear_x_msg.data = twist_msg.twist.linear.x;
  linear_x_pub_->publish(linear_x_msg);

  tier4_debug_msgs::msg::Float32Stamped angular_z_msg;
  angular_z_msg.stamp = this->now();
  angular_z_msg.data = twist_msg.twist.angular.z;
  angular_z_pub_->publish(angular_z_msg);
}

3. stop_filter代码阅读

这段代码是一个停止过滤器的类,它订阅里程计消息,并根据预设的速度和角速度阈值来判断车辆是否停止。在构造函数中,它初始化了节点并创建了订阅器、发布器以及其他参数。当收到里程计消息时,回调函数会判断车辆的线速度和角速度是否小于预设的阈值,如果是,则将车辆的速度和角速度设为0,并发布一个布尔型的停止标志消息以及处理后的里程计消息。

…详情请参照古月居

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

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

相关文章

无人机协同作业中的多网融合技术详解

无人机协同作业中的多网融合技术是一种复杂且高效的技术体系&#xff0c;它旨在通过整合多种通信网络和技术&#xff0c;实现多架无人机之间的无缝协同作业&#xff0c;从而提升任务执行效率、增强系统可靠性和扩展应用场景。以下是对该技术的详细解析&#xff1a; 一、多网融…

Leetcode 11.乘最多水的容器(字节,快手面试题)

题目链接&#xff1a;11. 盛最多水的容器 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线&#xff0c;使得它们与 x 轴共同…

python-ds:Python 中的数据结构库(适用于面试的数据结构和算法合集)

在软件开发中&#xff0c;数据结构是组织和存储数据的方式&#xff0c;对算法的效率和程序的性能至关重要。Python 提供了许多内置的数据结构&#xff0c;但在一些复杂的应用场景中&#xff0c;原生数据结构可能无法满足特定需求。这时&#xff0c;一个功能强大、易于使用的数据…

STM32 实现 UDP 广播通信

目录 一、引言 二、准备工作 1.硬件准备 2.软件准备 三、LWIP 协议栈的配置与初始化 1.添加 LWIP 源文件 2.配置 LWIP 3.初始化 LWIP 四.创建 UDP 广播套接字 1.创建 UDP 控制块 2.绑定本地端口 五、设置 UDP 广播选项 1.设置广播地址 2.设置广播选项 六、发…

白内障分类数据集 3.4G

用于白内障检测的白内障和正常眼睛图像分类数据集。 名称 白内障分类数据集 规模 数据量&#xff1a;3.4GB图像数量&#xff1a;未明确提供&#xff0c;但通常这类数据集包含数千张图像。 类别 正常眼&#xff1a;无白内障的眼睛早期白内障&#xff1a;轻度白内障的眼睛中…

IDEA在git提交时添加忽略文件

在IntelliJ IDEA中&#xff0c;要忽略target目录下所有文件的Git提交&#xff0c;你可以通过设置.gitignore文件来实现。以下是步骤和示例代码&#xff1a; 1、打开项目根目录下的.gitignore文件。也可以先下载这个.ignore插件。 2、如果不存在&#xff0c;利用上面的插件新建…

Docker笔记-Docker磁盘空间清理

无用的容器指的是已经停止运行且处于非活跃状态的容器。无用的镜像包括没有被任何容器使用的镜像&#xff0c;或者是被标记为"<none>"的镜像&#xff0c;通常是构建过程中产生的无标签镜像。 通过执行 docker container ls -a 和 docker image ls -a 命令&…

GEE开发之Modis_NDWI数据分析和获取

GEE开发之Modis_NDWI数据分析和获取 0 数据介绍NDWI介绍MOD09GA介绍 1 NDWI天数据下载2 NDWI月数据下载3 NDWI年数据下载 前言&#xff1a;本文主要介绍Modis下的NDWI数据集的获取。归一化差异水指数 (NDWI) 对植被冠层液态水含量的变化很敏感。它来自近红外波段和第二个红外波…

云计算 Cloud Computing

文章目录 1、云计算2、背景3、云计算的特点4、云计算的类型&#xff1a;按提供的服务划分5、云计算的类型&#xff1a;按部署的形式划分 1、云计算 定义&#xff1a; 云计算是一种按使用量付费的模式&#xff0c;这种模式提供可用的、便捷的、按需的网络访问&#xff0c;进入可…

在Linux中进行OpenSSH升级(编译安装在openssh目录)

由于OpenSSH有严重漏洞&#xff0c;因此需要升级OpenSSH到最新版本。 注意&#xff1a;在OpenSSH升级过程中千万不要断开服务器连接&#xff0c;不然的话&#xff0c;会出现断开后连接不了服务器的情况。 第一步&#xff0c;查看当前的OpenSSH服务版本。 命令&#xff1a;ss…

SpringMVC源码-AbstractUrlHandlerMapping处理器映射器将实现Controller接口的方式定义的路径存储进去

DispatcherServlet的initStrategies方法用来初始化SpringMVC的九大内置组件 initStrategies protected void initStrategies(ApplicationContext context) {// 初始化 MultipartResolver:主要用来处理文件上传.如果定义过当前类型的bean对象&#xff0c;那么直接获取&#xff0…

[每周一更]-(第117期):硬盘分区表类型:MBR和GPT区别

文章目录 1. **支持的磁盘容量**2. **分区数量**3. **引导方式**4. **冗余和数据恢复**5. **兼容性**6. **安全性**7. **操作系统支持**8. 对比 国庆假期前补一篇 在一次扫描机械硬盘故障的问题&#xff0c;发现我本机SSD和机械硬盘的分类型不一样&#xff0c;分别是GPT和MBR&a…

茶树中丝氨酸羧肽酶样酰基转移酶基因家族的全基因组分析及其在没食子酰化儿茶素生物合成中相关酶的进化和特征分析-文献精读55

Genome-Wide Analysis of Serine Carboxypeptidase-Like Acyltransferase Gene Family for Evolution and Characterization of Enzymes Involved in the Biosynthesis of Galloylated Catechins in the Tea Plant (Camellia sinensis) 茶树&#xff08;Camellia sinensis&…

瑜伽馆预约小程序,在线瑜伽课程预约系统

在全民健身时代下&#xff0c;瑜伽作为一项修身养性的运动&#xff0c;深受大众的热爱。随着瑜伽的流行&#xff0c;同时也推动了瑜伽馆的发展&#xff0c;各种大大小小的瑜伽馆在市场中出现&#xff0c;给大众带来了多样化的瑜伽选择。 瑜伽行业的快速发展要加剧了市场竞争&a…

牛客->除2!

1.题目&#xff1a; 2.解析&#xff1a; 解法&#xff1a; 步骤一&#xff1a;创建大根堆&#xff0c;把筛选出偶数数据&#xff0c;然后全部放入大根堆&#xff1b; 步骤二&#xff1a;拿出堆顶元素&#xff0c;从sum中减掉, 减小的值&#xff0c;最后返回sum. 代码&#xff1…

65.【C语言】联合体

目录 目录 1.定义 2.格式 3.例题 答案速查 分析 4.练习 答案速查 分析 5.相同成员的联合体和结构体的对比 6.联合体的大小计算 2条规则 答案速查 分析 练习 答案速查 分析 7.联合体的优点 8.匿名联合体 1.定义 和结构体有所不同,顾名思义:所有成员联合使用同…

Artec Leo 3D扫描助力Illumaesthetic公司加速汽车定制

挑战&#xff1a;Illumaesthetic公司正在寻求一种方法&#xff0c;对汽车改装的测量技术进行现代化改造。 解决方案&#xff1a;Artec Leo, Artec Eva, Artec Studio, Geomagic 效果&#xff1a;从手工雕刻、成型&#xff0c;转向3D扫描和3D打印&#xff0c;提高了公司设计和原…

QT——初识

目录 前言 1.创建一个QT项目 2.查看生成的文件 3.打印一条hello world&#xff01; ①使用控件实现 ②使用代码实现 4.Qt的编码格式 5.信号和槽 6.Qt中的坐标系 前言 QT是一款可跨平台的电脑客户端开发软件&#xff0c;本文将介绍一些有关QT使用的基础内容。 1.创建一个…

“大数据+高职”:VR虚拟仿真实训室的发展前景

随着信息技术的迅猛发展&#xff0c;大数据技术与虚拟现实&#xff08;VR&#xff09;的融合正在为高等教育&#xff0c;尤其是高等职业教育&#xff08;高职&#xff09;带来革命性的变革。VR虚拟仿真实训室作为这一技术融合的典型应用&#xff0c;正逐步展现其在提升教育质量…

热补丁反调试API Hook—上跳/下跳

以 IsDebuggerPresent 函数为例&#xff0c;可以看到可以上跳&#xff08;简单&#xff09;&#xff0c;也可以下跳&#xff08;复杂&#xff09;。 上跳&#xff1a; BYTE NewCodes[2] { 0xEB,0xF9 }; BYTE JmpCode[5] { 0xE9,0 }; BYTE oldCodes[2] { 0 };BOOL Mydebug() …