当前位置: 首页 > news >正文

cartographer_ros数据加载与处理

node_main.cc

  1. 坐标系的读取通过tf_buffer
  2. auto
  3. node类是cartographer_ros接收传感器数据,并传输到cartographer里,同时还会发布map,轨迹等
  4. node_options数据传给两个地方,一个是map_builder进行slam操作,一个是node做数据处理
  5. trajectory_options传给node,使用默认topic开始轨迹

**加载配置文件 **(LoadOptions)

  1. tube:元组 c++11: std::tie()函数可以将变量连接到一个给定的tuple上,生成一个元素类型全是引用的tuple

  2. 两个参数,NodeOptions、TrajectoryOptions

  3. LoadOptions函数的作用(step)

    输入两个参数:configuration_directory 配置文件所在目录

    ​ configuration_basename 配置文件的名字

    获取配置文件所在目录,存入file_resolver中

    读取配置文件内容到code中

    根据给定的字符串,生成一个lua字典

    将lua中的内容填充到NodeOptions和TrajectoryOptions(通过CreateNodeOptions函数和CreateTrajectoryOptions函数)并返回,返回的是tuple元组,存放NodeOptions和TrajectoryOptions

    1. CreateNodeOptions函数:读取lua文件内容,将lua文件内容赋值给NodeOptions

    2. CreateTrajectoryOptions函数:接收lua字典的地址,将lua文件内容赋值给TrajectoryOptions,并返回options(同上)

      首先判断是否有传感去数据输入

  4. 如何读取配置文件:在node_options.cc文件中引用ConfigurationFileResolver类

    继承FileResolver类

    构造函数中,configuration_files_directories_变量有两个元素,一个是launch文件读入的地址,一个是通过kconfigurationFilesDirectories变量从配置文件读取的地址(此配置文件通过编译之后自动生成,编译文件在common->config.h.cmake设置)

    GetFileContentOrDie将读取内容拷贝到code中,GetFullPathOrDie函数根据文件名查找是否在给定的文件夹中存在

Node类

四个大的框架:

  1. 声明需要发布的topic
  2. 声明发布对应名字的ROS服务, 并将服务的发布器放入到vector容器中
  3. 处理之后的点云发布器
  4. 进行定时器与函数绑定,定时发布数据

开始轨迹

​ 从node_main中传入数据node.StartTrajectoryWithDefaultTopics(trajectory_options);

​ 调用map_builder_bridge的AddTrajectory(expected_sensor_ids, options), 添加一个轨迹

新增一个位姿估计器AddExtrapolator(trajectory_id, options) imu与里程计的融合

​ 向位姿估计器(PoseExtrapolator)里面传入数据

新生成一个传感器数据采样器AddSensorSamplers(trajectory_id, options)

​ TrajectorySensorSamplers结构体,控制各个传感器的采样频率

​ FixedRatioSampler类,根据给定频率对数据进行抽样,假设给定1,每一帧数据都用,给定0.5则两帧数据用一个

订阅话题与注册回调函数LaunchSubscribers(options,trajectory_id)

​ 单线雷达的话题订阅与回调函数注册,HandleLaserScanMessage(回调函数)

​ 多回声波激光雷达话题订阅与回调函数注册,HandleMultiEchoLaserScanMessage(回调函数)

​ 点云话题订阅与回调函数注册,HandlePointCloud2Message(回调函数)

​ imu话题订阅与回调函数注册,HandleImuMessage(回调函数)

​ 里程计

​ GPS

​ landmarks

​ 回调函数参数皆为(trajectory_id,sensor_id,msg),在SubscribeWithHandler函数中使用lambda表达式传入。

定义一个定时器,三秒执行一次,检查设置的topic名称是否存在

​ 通过MaybeWarnAboutTopicMismatch函数检查topic名称是否存在

​ 最后一个参数为oneshot,等于true表示只是执行一次

将topic名字保存下来,用于之后的新建轨迹时检查topic名字是否重复

传感器数据的走向

在各个传感器的回调函数中处理

  1. 里程计数据走向

    首先通过脉冲函数判断是否暂停

    然后数据格式转换

    转换后的数据类型传入位姿推测器器(extrapolators_),进行位姿估计,ros中的原始数据(msg)传入HandleOdometryMessage(sensor_id,msg)

    1. imu数据走向

    首先通过脉冲函数判断是否暂停

    然后数据格式转换

    转换后的数据位姿推测器器(extrapolators_),进行位姿估计与重力方向的确定;ros中的原始数据(msg)传入HandleImuMessage(sensor_id, msg)进行imu数据处理

    1. 其他传感器数据同上,GPS数据只用原始数据直接传入handle…函数

MapBuilderBridge类

local frame global frame

carographer中存在两个地图坐标系, 分别为global frame与local frame

local frame是前端的坐标系, 这个坐标系与坐标系下的机器人的坐标, 一经扫描匹配之后就不会再被改变.

后端的坐标系, 回环检测与后端位姿图优化都不会对前端的任何坐标产生作用.

global frame是后端的坐标系, 是表达被回环检测与位姿图优化所更改后的坐标系.

当优化之后, 这个坐标系下的节点坐标(包括点云的位姿, submap的位姿)会发生变化.

但坐标系本身不会发生变化

三维刚体坐标变换

在**Rigid3 **这个类中,三个构造函数,一个无参数的默认构造函数,第二个传入平移和旋转的参数并赋值,第三个传入平移和轴角对translation_ 和 rotation_赋值。

供使用函数ToRigid3d

雷达数据的处理

位置:sensor_bridge.cc

void SensorBridge::HandleLaserScanMessage(const std::string& sensor_id, const sensor_msgs::LaserScan::ConstPtr& msg)

先转成点云数据,在传入trajectory_builder_

void SensorBridge::HandleLaserScanMessage(const std::string& sensor_id, const sensor_msgs::LaserScan::ConstPtr& msg) {carto::sensor::PointCloudWithIntensities point_cloud;//定义点云数据格式,带有强度和时间,carto::common::Time time;std::tie(point_cloud, time) = ToPointCloudWithIntensities(*msg);//转为点云数据(后面的多回声波雷达和点云数据处理都调用了这个函数,使用的函数重载)HandleLaserScan(sensor_id, time, msg->header.frame_id, point_cloud);
}

看看point_cloud的数据结构

struct PointCloudWithIntensities {TimedPointCloud points;std::vector<float> intensities;
};
using TimedPointCloud = std::vector<TimedRangefinderPoint>;
struct TimedRangefinderPoint {Eigen::Vector3f position;float time; // 相对点云最后一个点的时间, 最后一个点的时间为0, 其他点的时间都为负的
};
ToPointCloudWithIntensities(*msg)函数

进入ToPointCloudWithIntensities(*msg)函数

// 由ros格式的LaserScan转成carto格式的PointCloudWithIntensities
std::tuple<::cartographer::sensor::PointCloudWithIntensities,::cartographer::common::Time>
ToPointCloudWithIntensities(const sensor_msgs::LaserScan& msg) {return LaserScanToPointCloudWithIntensities(msg);
}

将ros下的激光雷达数据转成carto格式的点云数据,主要使用的就是LaserScanToPointCloudWithIntensities(msg)这个函数(多回声波和单线使用的都是调用这个同一个函数)

// 将LaserScan与MultiEchoLaserScan转成carto格式的点云数据
template <typename LaserMessageType>
std::tuple<PointCloudWithIntensities, ::cartographer::common::Time>
LaserScanToPointCloudWithIntensities(const LaserMessageType& msg) {CHECK_GE(msg.range_min, 0.f);CHECK_GE(msg.range_max, msg.range_min);if (msg.angle_increment > 0.f) {CHECK_GT(msg.angle_max, msg.angle_min);} else {CHECK_GT(msg.angle_min, msg.angle_max);}PointCloudWithIntensities point_cloud;//接着是定义了这个变量,一直向这个变量里面填充东西。最后是把point_cloud带着时间戳给返回出去。float angle = msg.angle_min;for (size_t i = 0; i < msg.ranges.size(); ++i) {// c++11: 使用auto可以适应不同的数据类型const auto& echoes = msg.ranges[i];if (HasEcho(echoes)) {const float first_echo = GetFirstEcho(echoes);// 满足范围才进行使用if (msg.range_min <= first_echo && first_echo <= msg.range_max) {const Eigen::AngleAxisf rotation(angle, Eigen::Vector3f::UnitZ());//定义一个旋转向量rotation,以Z轴为旋转轴,angle为旋转角度得到的旋转向量。const cartographer::sensor::TimedRangefinderPoint point{rotation * (first_echo * Eigen::Vector3f::UnitX()), // positioni * msg.time_increment};                            // time// 保存点云坐标与时间信息point_cloud.points.push_back(point);
// 如果存在强度信息
if (msg.intensities.size() > 0) {CHECK_EQ(msg.intensities.size(), msg.ranges.size());// 使用auto可以适应不同的数据类型const auto& echo_intensities = msg.intensities[i];CHECK(HasEcho(echo_intensities));point_cloud.intensities.push_back(GetFirstEcho(echo_intensities));
} else {point_cloud.intensities.push_back(0.f);
}}
}
angle += msg.angle_increment;
}//从msg中获得时间戳::cartographer::common::Time timestamp = FromRos(msg.header.stamp);//获得一帧激光的总时长if (!point_cloud.points.empty()) {const double duration = point_cloud.points.back().time;// 以点云最后一个点的时间为点云的时间戳timestamp += cartographer::common::FromSeconds(duration);//第一个点的时间戳+ 总时长等于最后一个点的时间戳,也就是cartographer的时间戳// 让点云的时间变成相对值, 最后一个点的时间为0
for (auto& point : point_cloud.points) {point.time -= duration;
}}return std::make_tuple(point_cloud, timestamp);
}
}

首先函数开头使用的模板参数,便无需函数重载,使用函数重载代码量大,重复率高。

LaserScan与MultiEchoLaserScan的数据类型
 
$ rosmsg show sensor_msgs/LaserScan 
std_msgs/Header headeruint32 seq time stamp string frame_id
float32 angle_min 
float32 angle_max 
float32 angle_increment 
float32 time_increment 
float32 scan_time 
float32 range_min 
float32 range_max 
float32[] ranges 
float32[] intensities 
$ rosmsg show sensor_msgs/MultiEchoLaserScan 
std_msgs/Header headeruint32 seq time stamp string frame_id 
float32 angle_min 
float32 angle_max 
float32 angle_increment 
float32 time_increment 
float32 scan_time 
float32 range_min 
float32 range_max 
sensor_msgs/LaserEcho[] ranges float32[] echoes 
sensor_msgs/LaserEcho[] intensities float32[] echoes

单线雷达和多回声波雷达数据类型的区别在于 ranges 和 intensities数组类型的不同,单线激光雷达的ranges和intensities数据类型是float32[]类型,多回声波是sensor_msgs/LaserEcho[]类型,其中还包括了float32[] echoes

根据两种不同的数据类型,进行判断,分别处理

if (HasEcho(echoes))//判断是否有数据

HasEcho(echoes)使用函数重载,以适用两种不同的数据,函数定义如下:

// For sensor_msgs::LaserScan.
bool HasEcho(float) { return true; }// For sensor_msgs::MultiEchoLaserScan.
bool HasEcho(const sensor_msgs::LaserEcho& echo) {return !echo.echoes.empty();
}
const float first_echo = GetFirstEcho(echoes);//获取第一个数据,用于判断是否在设置的扫描范围之内

GetFirstEcho(echoes)也是有使用的函数重载,函数定义如下:

// 通过函数重载, 使得函数可以同时适用LaserScan与LaserEcho
float GetFirstEcho(float range) { return range; }float GetFirstEcho(const sensor_msgs::LaserEcho& echo) {return echo.echoes[0];
}

进入数据处理过程

if (msg.range_min <= first_echo && first_echo <= msg.range_max) {const Eigen::AngleAxisf rotation(angle, Eigen::Vector3f::UnitZ());//定义一个旋转向量rotation,以Z轴为旋转轴,angle为旋转角度得到的旋转向量。const cartographer::sensor::TimedRangefinderPoint point{rotation * (first_echo * Eigen::Vector3f::UnitX()), // position,旋转乘以一个距离值,得到位置i * msg.time_increment};                            // time,i乘以时间增量// 保存点云坐标与时间信息point_cloud.points.push_back(point);//point,有点的距离,时间(和强度值)// 如果存在强度信息if (msg.intensities.size() > 0) {CHECK_EQ(msg.intensities.size(), msg.ranges.size());// 使用auto可以适应不同的数据类型const auto& echo_intensities = msg.intensities[i];CHECK(HasEcho(echo_intensities));point_cloud.intensities.push_back(GetFirstEcho(echo_intensities));} else {point_cloud.intensities.push_back(0.f);}}}angle += msg.angle_increment;//如果距离值不满足范围,角度加上一个增量,继续判断下一个值
//从msg中获得时间戳::cartographer::common::Time timestamp = FromRos(msg.header.stamp);//获得一帧激光的总时长if (!point_cloud.points.empty()) {const double duration = point_cloud.points.back().time;//duration 点云最后一个点的时间,也是一帧点云的总时长// 以点云最后一个点的时间为点云的时间戳timestamp += cartographer::common::FromSeconds(duration);//第一个点的时间戳+ 总时长等于最后一个点的时间戳,也就是cartographer的时间戳// 让点云的时间变成相对值, 最后一个点的时间为0for (auto& point : point_cloud.points) {point.time -= duration;}}return std::make_tuple(point_cloud, timestamp);
}

这一段代码通过模板参数,满足两种不同的数据格式的操作,使用了两个重载函数,完成了不同数据类型的判断,使用auto可以自适用不同的数据格式。

point_cloud2数据处理

ToPointCloudWithIntensities(const sensor_msgs::PointCloud2& msg)点云数据重载

主要分为以下四种情况处理(参考源码)

  1. 有强度,有时间:直接将强度和时间赋值
  2. 有强度,无时间:时间赋值0,强度赋值
  3. 无强度,有时间:时间赋值,强度赋值1
  4. 无强度,无时间:时间赋值0,强度赋值1
HandleLaserScan(sensor_id, time, msg->header.frame_id, point_cloud)函数
HandleLaserScan

void SensorBridge::HandleLaserScan(const std::string& sensor_id, const carto::common::Time time,const std::string& frame_id,const carto::sensor::PointCloudWithIntensities& points) {if (points.points.empty()) {return;}// CHECK_LE: 小于等于CHECK_LE(points.points.back().time, 0.f);// TODO(gaschler): Use per-point time instead of subdivisions.

// tag: 参数num_subdivisions_per_laser_scan_,在MapBuild_Bridge中由lua文件传入
// 意为一帧雷达数据被分成几次处理, 一般将这个参数设置为1
for (int i = 0; i != num_subdivisions_per_laser_scan_; ++i) {
const size_t start_index =
points.points.size() * i / num_subdivisions_per_laser_scan_;
const size_t end_index =
points.points.size() * (i + 1) / num_subdivisions_per_laser_scan_;

// 生成分段的点云
carto::sensor::TimedPointCloud subdivision(points.points.begin() + start_index, points.points.begin() + end_index);
if (start_index == end_index) {continue;
}
const double time_to_subdivision_end = subdivision.back().time;
// `subdivision_time` is the end of the measurement so sensor::Collator will
// send all other sensor data first.
const carto::common::Time subdivision_time =time + carto::common::FromSeconds(time_to_subdivision_end);auto it = sensor_to_previous_subdivision_time_.find(sensor_id);
if (it != sensor_to_previous_subdivision_time_.end() &&// 上一段点云的时间不应该大于等于这一段点云的时间it->second >= subdivision_time) {LOG(WARNING) << "Ignored subdivision of a LaserScan message from sensor "<< sensor_id << " because previous subdivision time "<< it->second << " is not before current subdivision time "<< subdivision_time;continue;
}
// 更新对应sensor_id的时间戳
sensor_to_previous_subdivision_time_[sensor_id] = subdivision_time;// 检查点云的时间
for (auto& point : subdivision) {point.time -= time_to_subdivision_end;
}
CHECK_EQ(subdivision.back().time, 0.f);
// 将分段后的点云 subdivision 传入 trajectory_builder_
HandleRangefinder(sensor_id, subdivision_time, frame_id, subdivision);

} // for
}

其中num_subdivisions_per_laser_scan_参数,在MapBuild_Bridge中由lua文件传入,意为一帧雷达数据被分成几次处理, 一般将这个参数设置为1

for (int i = 0; i != num_subdivisions_per_laser_scan_; ++i) {const size_t start_index =points.points.size() * i / num_subdivisions_per_laser_scan_;const size_t end_index =points.points.size() * (i + 1) / num_subdivisions_per_laser_scan_;

for循环计算点云开始到结束的索引,若参数num_subdivisions_per_laser_scan_值等于1,结束的索引就是点云大小,不等于1,点云大小乘以(i+1)/num_subdivisions_per_laser_scan_

假设num_subdivisions_per_laser_scan_=2,点云数据量为100,那么点云就会被分成0-50和50-100。

HandleRangefinder

雷达相关的数据最终的处理函数
先将数据转到tracking坐标系下,再调用trajectory_builder_的AddSensorData进行数据的处理

需要传入的数据,数据的话题sensor_id,点云的时间戳time,点云的frameframe_id,雷达坐标系下的TimedPointCloud格式的点云ranges

void SensorBridge::HandleRangefinder(const std::string& sensor_id, const carto::common::Time time,const std::string& frame_id, const carto::sensor::TimedPointCloud& ranges) {if (!ranges.empty()) {CHECK_LE(ranges.back().time, 0.f);}const auto sensor_to_tracking =tf_bridge_.LookupToTracking(time, CheckNoLeadingSlash(frame_id));// 以 tracking 到 sensor_frame 的坐标变换为TimedPointCloudData 的 origin// 将点云的坐标转成 tracking 坐标系下的坐标, 再传入trajectory_builder_if (sensor_to_tracking != nullptr) {trajectory_builder_->AddSensorData(sensor_id, carto::sensor::TimedPointCloudData{time, sensor_to_tracking->translation().cast<float>(),// 将点云从雷达坐标系下转到tracking_frame坐标系系下carto::sensor::TransformTimedPointCloud(ranges, sensor_to_tracking->cast<float>())} ); // 强度始终为空}
}

TransFromTimedPointCloud函数,返回值是坐标变换后的点云

输入值为:点云数据point_cloud,旋转变换矩阵transform,

TimedPointCloud TransformTimedPointCloud(const TimedPointCloud& point_cloud,const transform::Rigid3f& transform) {TimedPointCloud result;result.reserve(point_cloud.size());for (const TimedRangefinderPoint& point : point_cloud) {result.push_back(transform * point);}return result;
}

主要的代码就是在for循环中,变换矩阵乘以点云坐标result.push_back(transform * point);

Cartographer_ros中的传感器数据的传递过程总结

里程计与IMU数据的走向

里程计数据从Node类的回调函数进来, 有两个走向, 一个是Node类中的位姿估计器, 一个是

SensorBridge::HandleOdometryMessage, 再从SensorBridge传递给cartographer.

IMU数据的走向同理.

GPS与Landmark数据的走向

GPS数据从Node类的回调函数进来只有1个走向, 就是传入到SensorBridge::HandleNavSatFixMessage()函数中,

再从SensorBridge传递给cartographer.

Landmark数据的走向同理.

雷达数据的走向

存在3种雷达数据, 一种是单线点云, 一种是多回声波点云, 一种是多线雷达点云, 都是直接传入到SensorBridge中.

单线点云与多回声波雷达点云都是先经过ToPointCloudWithIntensities()函数, 处理成carto格式的点云, 然后在传

入HandleLaserScan()函数中.

根据参数配置, 将点云是否分段, 将分段的点云传入到HandleRangefinder()函数中.

多线点云是先经过ToPointCloudWithIntensities()函数, 处理成carto格式的点云, 然后在传入HandleRangefinder()

函数中.

HandleRangefinder() 函数将点云点云从雷达坐标系下转到tracking_frame坐标系系下, 并转成

数据的走向同理.

GPS与Landmark数据的走向

GPS数据从Node类的回调函数进来只有1个走向, 就是传入到SensorBridge::HandleNavSatFixMessage()函数中,

再从SensorBridge传递给cartographer.

Landmark数据的走向同理.

雷达数据的走向

存在3种雷达数据, 一种是单线点云, 一种是多回声波点云, 一种是多线雷达点云, 都是直接传入到SensorBridge中.

单线点云与多回声波雷达点云都是先经过ToPointCloudWithIntensities()函数, 处理成carto格式的点云, 然后在传

入HandleLaserScan()函数中.

根据参数配置, 将点云是否分段, 将分段的点云传入到HandleRangefinder()函数中.

多线点云是先经过ToPointCloudWithIntensities()函数, 处理成carto格式的点云, 然后在传入HandleRangefinder()

函数中.

HandleRangefinder() 函数将点云点云从雷达坐标系下转到tracking_frame坐标系系下, 并转成

TimedPointCloudData格式的点云, 然后才传入到cartographer中

相关文章:

cartographer_ros数据加载与处理

node_main.cc 坐标系的读取通过tf_bufferautonode类是cartographer_ros接收传感器数据&#xff0c;并传输到cartographer里&#xff0c;同时还会发布map&#xff0c;轨迹等node_options数据传给两个地方&#xff0c;一个是map_builder进行slam操作&#xff0c;一个是node做数据…...

设计模式-7种结构型模式

适配器模式&#xff1a; 将一个类的接口转换成用户希望得到的另一种接口。它使原本不相容的接口得以协同调用。 桥接模式&#xff1a; 将类的抽象部分和他的实现部分分离开来。是他们可以独立的变化。 它是用组合关系代替继承关系来实现&#xff0c;从而降低了抽象和实现这两…...

华为李鹏:加速5G商业正循环,拥抱更繁荣的5.5G(5G-A)

2023年10月10日&#xff0c;在华为主办的第十四届全球移动宽带论坛上&#xff0c;华为高级副总裁、运营商BG总裁李鹏面向来自全球的运营商和产业伙伴&#xff0c;提出抓住网络需求和趋势的力量——“面向后天的业务&#xff0c;积极规划明天的网络&#xff0c;加速5G商业正循环…...

Marin说PCB之CoilcraftBourns POC 电感的性能对比

十一小长假本来是一件美好事情。可是天有不测风云&#xff0c;小编我却有祸兮来了。本来是公司的硬件同事强哥要回以色列了&#xff0c;最近他们国家那边都在打仗&#xff0c;强哥本着舍身为国的精神回国抗战去了。小编我就想着在他回国之前搞了篮球比赛送别一下他呢&#xff0…...

聊聊Maven的依赖传递、依赖管理、依赖作用域

1. 依赖传递 在Maven中&#xff0c;依赖是会传递的&#xff0c;假如在业务项目中引入了spring-boot-starter-web依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>…...

centos6/7 SOCKS5 堆溢出漏洞修复(RPM方式)curl 8.4 CVE-2023-38545 CVE-2023-38546

引用 https://darkdark.top/update-curl.html centos6 rpm 升级包下载&#xff1a;https://download.csdn.net/download/sinat_24092079/88425840 yum update libcurl-8.4.0-1.el6.1.x86_64.rpm curl-8.4.0-1.el6.1.x86_64.rpmcentos7 rpm 升级包下载&#xff1a;https://down…...

C#,数值计算——数据建模Proposal的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { public class Proposal { public Normaldev gau { get; set; } null; private double logstep { get; set; } public Proposal(int ranseed, double lstep) { this.gau…...

如何使用命令生成动态链接库.dll文件(保姆级教学)

如何使用命令生成动态链接库.dll文件 /*** file 如何使用命令生成动态链接库.dll文件* author jUicE_g2R(qq:3406291309)* * brief 教学演示* tool visual studio2022&#xff08;2019也适用&#xff09;* * copyright 2023.10* COPYR…...

Qt之模块介绍

Qt提供了很多功能模块&#xff0c;我们需要知道的是这些模块有些加入了标准库&#xff0c;有一些并没有加入到标准库。至于为什么没有加入到标准库通过chatgpt得到的答案如下&#xff1a; Qt 是一个强大的跨平台 C 框架&#xff0c;它包括了很多核心模块和功能&#xff0c;以支…...

Socks5代理和代理IP

在数字时代&#xff0c;网络工程师必须不断掌握新技术&#xff0c;以解决跨界电商、爬虫数据采集、出海业务扩展、网络安全保护以及游戏性能优化等各种技术挑战。本文将深入探讨Socks5代理和代理IP技术&#xff0c;它们在各个领域中的应用&#xff0c;如何为网络工程师提供了强…...

计算机指令、机器码

目录 背景 在软硬件接口中&#xff0c;CPU 帮我们做了什么事&#xff1f; 从编译到汇编&#xff0c;代码怎么变成机器码&#xff1f; 解析指令和机器码 总结延伸 背景 上大学的时候&#xff0c;我们系里教 C 语言程序设计的老师说&#xff0c;他们当年学写程序的时候&…...

MyLife - Docker安装Consul

Docker安装Consul 个人觉得像consul之类的基础设施在线上环境直接物理机安装使用可能会好些。但是在开发测试环境用docker容器还是比较方便的。这里学习下docker安装consul使用。 1. Consul 镜像库地址 Consul 镜像库地址&#xff1a;https://hub.docker.com/r/hashicorp/consu…...

Leetcode刷题笔记--Hot61-70

1--课程表&#xff08;207&#xff09; 主要思路&#xff1a; 用 in 记录每一门课程剩余的先修课程个数&#xff0c;当剩余先修课程个数为0时&#xff0c;将该课程加入到队列q中。 每修队列q中的课程&#xff0c;以该课程作为先修课程的所有课程&#xff0c;其剩余先修课程个数…...

python特别篇—github基本操作手册

一、开始使用 1.1 “Hello world” 1.1.1 github介绍 GitHub是一个基于Git版本控制系统的代码托管平台。它提供了一个在线的代码仓库&#xff0c;使开发者可以将自己的代码存储在云端&#xff0c;并与其他开发者进行协作。GitHub不仅仅是一个代码托管平台&#xff0c;还提供了…...

tiktok直播websocket序列化与反序列化

系列文章目录 websocket训练地址:https://www.qiulianmao.com,正在搭建中 基础-websocket逆向基础-http拦截基础-websocket拦截基础-base64编码与解码基础-protobuf序列化与反序列化视频号直播弹幕采集tiktok protobuf序列化与反序列化实战一:Http轮询更新中tikto...

微信picker弹出之后 , 背景变成灰色是怎么做的

微信小程序在弹出picker组件时&#xff0c;会将页面背景变为半透明的灰色&#xff0c;这是通过设置一个全屏的蒙层来实现的。 具体实现方法如下&#xff1a; 在WXML文件中&#xff0c;添加一个view元素作为蒙层&#xff0c;并设置其样式和属性&#xff1a; <view class&q…...

通用考勤后台管理系统

考勤后台系统&#xff0c;包括待办事项、人员管理、任务中心、任务详情、我的任务、客户管理、考勤功能几大功能&#xff0c;本后台系统以考勤打卡为主要功能&#xff0c;采用分屏布局的方式&#xff0c;简洁大方&#xff0c;使用方便...

LeetCode75——Day5

文章目录 一、题目二、题解 一、题目 345. Reverse Vowels of a String Given a string s, reverse only all the vowels in the string and return it. The vowels are ‘a’, ‘e’, ‘i’, ‘o’, and ‘u’, and they can appear in both lower and upper cases, more t…...

面向C++模块的开源 IFC SDK

早在 VS2019 v16.10 版本的时候&#xff0c;我们就官宣了对 C 模块(以及几乎所有其他 C 20 特性)的全面支持&#xff0c;包括 MSVC 编译器工具集&#xff0c;静态分析&#xff0c;智能感知和调试器等&#xff0c;而实现模块需要将 C 代码实现为一种内部的临时表示形式。 今天&…...

Docker开启远程访问+idea配置docker+dockerfile发布java项目

一、docker开启远程访问 1.编辑docker服务文件 vim /usr/lib/systemd/system/docker.servicedocker.service原文件如下&#xff1a; [Unit] DescriptionDocker Application Container Engine Documentationhttps://docs.docker.com Afternetwork-online.target docker.socke…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

五年级数学知识边界总结思考-下册

目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解&#xff1a;由来、作用与意义**一、知识点核心内容****二、知识点的由来&#xff1a;从生活实践到数学抽象****三、知识的作用&#xff1a;解决实际问题的工具****四、学习的意义&#xff1a;培养核心素养…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...