《自动驾驶与机器人中的SLAM技术》ch7:基于 ESKF 的松耦合 LIO 系统
目录
基于 ESKF 的松耦合 LIO 系统
1 坐标系说明
2 松耦合 LIO 系统的运动和观测方程
3 松耦合 LIO 系统的数据准备
3.1 CloudConvert 类
3.2 MessageSync 类
4 松耦合 LIO 系统的主要流程
4.1 IMU 静止初始化
4.2 ESKF 之 运动过程——使用 IMU 预测
4.3 使用 IMU 预测位姿进行运动补偿
4.4 松耦合系统的配准部分
基于 ESKF 的松耦合 LIO 系统
这里我们实现一个相对简单的案例:使用第 3 章介绍的 ESKF,配合 7.3.2 中的增量 NDT 里程计,实现松耦合的 LIO 系统。 整个系统的流程如下图所示,可以观察到其中的滤波器部分和点云配准部分是相对解耦的!
1 坐标系说明
2 松耦合 LIO 系统的运动和观测方程
由于整个 LIO 运行在 IMU 坐标系中,状态变量的运动方程与《自动驾驶与机器人中的SLAM技术》ch3:惯性导航与组合导航 中介绍的 ESKF 的运动方程保持一致,我们不再展开叙述。同时,雷达里程计(LO)的输出位姿,可直接视为对状态变量 ,
的观测。这个过程实际和第 3 章的 ESKF 中谈到的 GNSS 观测是一样的。
LO 对 的观测可以直接写成对误差状态
的观测,从而省去前面的链式法则推导,简化整个线性化过程。LO 的旋转观测方程为(类似于 把误差状态归入名义状态 的方程):
其中 为该时刻的名义状态,
为误差状态,
为观测位姿。由于在观测过程中,名义状态
是确定的。我们 不妨将
直接视为对
的观测。我们对该方程稍作变换,可以写为:
此时, 是对
的直接观测,即
,所以
关于
的雅可比矩阵,即旋转部分的雅可比矩阵为单位阵:
LO 的平移观测方程为(类似于 把误差状态归入名义状态 的方程):
类似的,我们对该方程稍作变换,可以写为:
因此平移部分的雅可比矩阵也为单位阵:
由于 LO 观测数据为 6 维 的 ,故
矩阵的维度为
,具体形式如下:
这样就避免了再从名义状态到误差状态进行转换的过程,可以直接得到对误差状态的雅可比矩阵。注意当我们这样做时,原本 ESKF 中 部分的更新量(innovation)
也应该写成流形的形式:
即得到:
该式表明了从直观上来看,LO 的 ,
主要是在观测阶段通过卡尔曼增益
作用于误差状态变量中。
3 松耦合 LIO 系统的数据准备
松耦合的代码实现主要分为三个部分:
- 我们需要将 IMU 数据与激光数据进行同步。激光通常使用 10Hz 的频率,而 IMU 通常是更高的 100Hz。我们希望能够统一处理两个激光数据之间的那 10 个 IMU 数据。
- 我们需要处理激光的运动补偿,而运动补偿需要有激光测量时间内的位姿数据来源,正好可以用 ESKF 对每个 IMU 数据的预测值。
- 我们应该从 ESKF 中拿到预测的位姿数据,交给里程计算法,再将里程计配准之后的位姿放入 ESKF 中。
3.1 CloudConvert 类
CloudConvert 类负责将各种格式的点云转化为 PCL 格式的点云。以 livox_ros_driver::CustomMsg 类型点云为例,输入 msg, 输出时间单位为毫秒(ms)、跳点处理后的 PCL 格式点云 pcl_out。代码如下:
/// 带ring, range等其他信息的全量信息点云
struct FullPointType {PCL_ADD_POINT4D; //宏定义来自 PCL,为 FullPointType 添加了四维坐标点(x,y,z,w)。其中,前三个是空间坐标,而 w 是填充位(通常为 1.0,用于齐次坐标)。float range = 0; //点的距离(通常是到传感器的距离)float radius = 0; //点的半径(有时可以表示与传感器的水平距离,具体视应用而定)uint8_t intensity = 0; //点的强度值(反射强度)uint8_t ring = 0; //点的扫描线编号uint8_t angle = 0; //点的角度值double time = 0; //点的时间戳float height = 0; //点的高度信息inline FullPointType() {}EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};
void CloudConvert::Process(const livox_ros_driver::CustomMsg::ConstPtr &msg, FullCloudPtr &pcl_out) {AviaHandler(msg);*pcl_out = cloud_out_;
}void CloudConvert::AviaHandler(const livox_ros_driver::CustomMsg::ConstPtr &msg) {cloud_out_.clear();cloud_full_.clear();int plsize = msg->point_num;cloud_out_.reserve(plsize);cloud_full_.resize(plsize);std::vector<bool> is_valid_pt(plsize, false); // 标记哪些点是有效的std::vector<uint> index(plsize - 1);for (uint i = 0; i < plsize - 1; ++i) {index[i] = i + 1; // 从1开始}std::for_each(std::execution::par_unseq, index.begin(), index.end(), [&](const uint &i) {// 0x30: 00110000// 0x10: 00010000、0x00: 00000000// 只保留置信度优和中的点if ((msg->points[i].line < num_scans_) &&((msg->points[i].tag & 0x30) == 0x10 || (msg->points[i].tag & 0x30) == 0x00)) {// 跳点比例if (i % point_filter_num_ == 0) {cloud_full_[i].x = msg->points[i].x;cloud_full_[i].y = msg->points[i].y;cloud_full_[i].z = msg->points[i].z;cloud_full_[i].intensity = msg->points[i].reflectivity;cloud_full_[i].time = msg->points[i].offset_time / float(1000000); // mid360 时间戳单位为 ns,转换为 msif ((abs(cloud_full_[i].x - cloud_full_[i - 1].x) > 1e-7) ||(abs(cloud_full_[i].y - cloud_full_[i - 1].y) > 1e-7) ||(abs(cloud_full_[i].z - cloud_full_[i - 1].z) > 1e-7)) {is_valid_pt[i] = true;}}}});for (uint i = 1; i < plsize; i++) {if (is_valid_pt[i]) {cloud_out_.points.push_back(cloud_full_[i]);}}
}
3.2 MessageSync 类
MessageSync 类负责将 IMU 数据与点云进行同步。该类接收 ROS 数据包中原始的 IMU 消息与激光雷达消息,通过 Sync 函数将它们组装成一个 MeasureGroup 实例对象,再将它传递给回调函数。我们后续的松耦合、紧耦合算法就只需要考虑如何处理 MeasureGroup 实例对象,而不必再操心数据准备、同步的实现代码了。
这里需要注意:
① bag 包中雷达 msg 的时间戳为一帧雷达数据中首个点的时间戳!
② 每个 MeasureGroup 实例对象中存储的 IMU 消息的时间戳均小于其存储的 LIDAR 消息的 lidar_end_time_。MeasureGroup 实例对象中一般存储 1 帧雷达消息和 10 帧 IMU 消息。
/// IMU 数据与雷达同步
struct MeasureGroup {MeasureGroup() { this->lidar_.reset(new FullPointCloudType()); };double lidar_begin_time_ = 0; // 雷达包的起始时间double lidar_end_time_ = 0; // 雷达的终止时间FullCloudPtr lidar_ = nullptr; // 雷达点云std::deque<IMUPtr> imu_; // 上一时时刻到现在的IMU读数
};
bool MessageSync::Sync() {if (lidar_buffer_.empty() || imu_buffer_.empty()) {return false;}// MeasureGroup 中是否存在 单帧 LiDAR 数据。若不存在,进入该 ifif (!lidar_pushed_) {measures_.lidar_ = lidar_buffer_.front();measures_.lidar_begin_time_ = time_buffer_.front(); // lidar 数据中的时间戳是 lidar_begin_time_lidar_end_time_ = measures_.lidar_begin_time_ + measures_.lidar_->points.back().time / double(1000); // 以 s 为单位measures_.lidar_end_time_ = lidar_end_time_;lidar_pushed_ = true;}// imu_buffer_ 最后一个 imu 数据的时间戳要大于等于 lidar_end_time_,确保 imu 数据覆盖当前的 lidar 时间范围(lidar_begin_time_ ~ lidar_end_time_)if (last_timestamp_imu_ < lidar_end_time_) {return false;}double imu_time = imu_buffer_.front()->timestamp_;measures_.imu_.clear();while ((!imu_buffer_.empty()) && (imu_time < lidar_end_time_)) {imu_time = imu_buffer_.front()->timestamp_;if (imu_time > lidar_end_time_) {break;}measures_.imu_.push_back(imu_buffer_.front());imu_buffer_.pop_front();}// 将已处理的 LiDAR 数据从 lidar_buffer_ 和时间缓存 time_buffer_ 中移除,为下一轮同步准备lidar_buffer_.pop_front();time_buffer_.pop_front();lidar_pushed_ = false;if (callback_) {callback_(measures_);}return true;
}
4 松耦合 LIO 系统的主要流程
松耦合 LooselyLIO 类持有一个 IncrementalNDTLO(增量 NDT 里程计)对象,一个 ESKF 对象,一个 MessageSync 对象 处理同步之后的点云和 IMU。该类处理流程非常简单:当 MeasureGroup 到达后,在 IMU 未初始化时,使用第 3 章的静止初始化来估计 IMU 零偏。初始化完毕后,先使用 IMU 数据进行预测,再用预测数据对点云去畸变,最后对去畸变的点云做配准。
void LooselyLIO::ProcessMeasurements(const MeasureGroup &meas) {LOG(INFO) << "call meas, imu: " << meas.imu_.size() << ", lidar pts: " << meas.lidar_->size();measures_ = meas;if (imu_need_init_) {// 初始化IMU系统TryInitIMU();return;}// 利用IMU数据进行状态预测Predict();// 对点云去畸变Undistort();// 配准Align();
}
4.1 IMU 静止初始化
IMU 的静止初始化与《自动驾驶与机器人中的SLAM技术》ch3:惯性导航与组合导航 中介绍的大体一致。当 MeasureGroup 到达后,在 IMU 未初始化时,调用 StaticIMUInit::AddIMU() 函数进行 IMU的静止初始化。当 IMU 初始化成功时,在当前 MeasureGroup 中完成 ESKF 中 Q, V, b_g, b_a, g_w, P 的初始化。
void LooselyLIO::TryInitIMU() {for (auto imu : measures_.imu_) {imu_init_.AddIMU(*imu);}if (imu_init_.InitSuccess()) {// !!! 下面 4 行代码即完成了 Q, V, b_g, b_a, g_w, P 的初始化// 读取初始零偏,设置ESKFsad::ESKFD::Options options;// 噪声由初始化器估计options.gyro_var_ = sqrt(imu_init_.GetCovGyro()[0]);options.acce_var_ = sqrt(imu_init_.GetCovAcce()[0]);eskf_.SetInitialConditions(options, imu_init_.GetInitBg(), imu_init_.GetInitBa(), imu_init_.GetGravity());imu_need_init_ = false;LOG(INFO) << "IMU初始化成功";}
}
IMU 静止初始化结果如下:
I0112 15:59:51.430646 354274 loosely_lio.cc:54] call meas, imu: 10, lidar pts: 3601
I0112 15:59:51.430662 354274 static_imu_init.cc:86] mean acce: -0.00215149 00.00016898 000.0978879
I0112 15:59:51.430694 354274 static_imu_init.cc:109] IMU 初始化成功,初始化时间= 9.99018, bg = -0.00259592 00.00176906 0.000707638, ba = 000.213411 -0.0167615 00-9.70973, gyro sq = 5.96793e-05 4.42613e-05 3.58264e-05, acce sq = 9.71749e-07 1.85436e-06 2.14871e-07, grav = 000.215562 -0.0169305 00-9.80762, norm: 9.81
I0112 15:59:51.430707 354274 static_imu_init.cc:113] mean gyro: -0.00259592 00.00176906 0.000707638 acce: 000.213411 -0.0167615 00-9.70973
imu try init true time:1547714610.30704498
I0112 15:59:51.430723 354274 loosely_lio.cc:100] IMU初始化成功
4.2 ESKF 之 运动过程——使用 IMU 预测
IMU 预测部分与先前介绍的 GINS 中预测部分类似。上一个 MeasureGroup 中完成了 IMU 的静止初始化,当前 MeasureGroup 中的 IMU 数据就开始参与 ESKF 的运动过程了。std::vector<NavStated> 类型的 imu_states_ 的大小为 MeasureGroup 中的(IMU 数量 +1),其存储上一 IMU 时刻 ESKF 的名义转态变量和当前 MeasureGroup 中每一个 IMU 数据预测后的 ESKF 的名义转态变量,用来插值进行点云的去畸变。
void LooselyLIO::Predict() {imu_states_.clear();imu_states_.emplace_back(eskf_.GetNominalState());std::cout << "current_time_: " << eskf_.GetTime() << std::endl;/// 对IMU状态进行预测for (auto &imu : measures_.imu_) {eskf_.Predict(*imu);imu_states_.emplace_back(eskf_.GetNominalState());std::cout << "current_time_: " << eskf_.GetTime() << std::endl;}
}
4.3 使用 IMU 预测位姿进行运动补偿
其原理简单来说就是通过固定的世界坐标系,结合每个时刻的插值结果 ,将一帧雷达中所有时刻的点全部转移到雷达扫描结束时刻。
假设比例 计算公式如下,其中
为待插值的时刻,
为起始时刻,
为结束时刻:
- 旋转部分插值:四元数球面线性插值 (SLERP),确保旋转路径在旋转空间中的弧长最短。
- 平移部分插值:平移向量线性插值 (LERP)
注意:这种去畸变的方法前提是滤波器本身有效。如果滤波器失效或位姿发散,去畸变算法也就随之发散了。
SE3 pose_first;
SE3 pose_next;// 计算旋转插值结果(使用球面线性插值 - SLERP)
Eigen::Quaterniond result_R = pose_first.unit_quaternion().slerp(s, pose_next.unit_quaternion());// 计算平移插值结果(使用线性插值 - LERP)
Eigen::Vector3d result_p = pose_first.translation() * (1 - s) + pose_next.translation() * s;// 检查结果(仅用于调试)
std::cout << "Interpolated Rotation (Quaternion): " << result_R.coeffs().transpose() << std::endl;
std::cout << "Interpolated Translation: " << result_p.transpose() << std::endl;
void LooselyLIO::Undistort() {auto cloud = measures_.lidar_;auto imu_state = eskf_.GetNominalState(); // 最后时刻的状态SE3 T_end = SE3(imu_state.R_, imu_state.p_);if (options_.save_motion_undistortion_pcd_) {sad::SaveCloudToFile("/home/wu/slam_in_autonomous_driving/data/ch7/undist/before_undist.pcd", *cloud);}/// 将所有点转到最后时刻状态上std::for_each(std::execution::par_unseq, cloud->points.begin(), cloud->points.end(), [&](auto &pt) {SE3 Ti = T_end; // 只是为了初始化NavStated match;// 根据pt.time查找时间,pt.time是该点打到的时间与雷达开始时间之差,单位为毫秒// 插值结果为 Timath::PoseInterp<NavStated>(measures_.lidar_begin_time_ + pt.time * 1e-3, imu_states_, [](const NavStated &s) { return s.timestamp_; },[](const NavStated &s) { return s.GetSE3(); }, Ti, match);Vec3d pi = ToVec3d(pt);Vec3d p_compensate = TIL_.inverse() * T_end.inverse() * Ti * TIL_ * pi;pt.x = p_compensate(0);pt.y = p_compensate(1);pt.z = p_compensate(2);});scan_undistort_ = cloud;if (options_.save_motion_undistortion_pcd_) {sad::SaveCloudToFile("/home/wu/slam_in_autonomous_driving/data/ch7/undist/after_undist.pcd", *cloud);}
}
/*** pose 插值算法* @tparam T 数据类型* @tparam C 数据容器类型* @tparam FT 获取时间函数* @tparam FP 获取pose函数* @param query_time 查找时间* @param data 数据容器* @param take_pose_func 从数据中取pose的谓词,接受一个数据,返回一个SE3* @param result 查询结果* @param best_match_iter 查找到的最近匹配** NOTE 要求query_time必须在data最大时间和最小时间之间(容许0.5s内误差)* data的map按时间排序* @return*/
template <typename T, typename C, typename FT, typename FP>
inline bool PoseInterp(double query_time, C&& data, FT&& take_time_func, FP&& take_pose_func, SE3& result,T& best_match, float time_th = 0.5) {if (data.empty()) {LOG(INFO) << "cannot interp because data is empty. ";return false;}// 如果 query_time 超过最大时间,但在容许阈值 time_th 范围内,此时插值的结果直接使用最后一条数据的位姿。// rbegin() 返回的是反向迭代器,指向容器的最后一个元素(从后往前遍历的起点)double last_time = take_time_func(*data.rbegin());if (query_time > last_time) {if (query_time < (last_time + time_th)) {// 尚可接受result = take_pose_func(*data.rbegin());best_match = *data.rbegin();return true;}return false;}auto match_iter = data.begin();for (auto iter = data.begin(); iter != data.end(); ++iter) {auto next_iter = iter;next_iter++;if (take_time_func(*iter) < query_time && take_time_func(*next_iter) >= query_time) {match_iter = iter;break;}}auto match_iter_n = match_iter;match_iter_n++;double dt = take_time_func(*match_iter_n) - take_time_func(*match_iter);double s = (query_time - take_time_func(*match_iter)) / dt; // s=0 时为第一帧,s=1时为next// 出现了 dt为0的bugif (fabs(dt) < 1e-6) {best_match = *match_iter;result = take_pose_func(*match_iter);return true;}SE3 pose_first = take_pose_func(*match_iter);SE3 pose_next = take_pose_func(*match_iter_n);// 旋转部分使用了四元数的球面线性插值(Slerp)。Slerp(Spherical Linear Interpolation) 是一种在两四元数之间进行插值的方式。与普通的线性插值不同,Slerp 能够保持旋转的路径最短,给出的旋转角度总是通过球面路径最优化。// s 是插值的参数。当 s 在 0 和 1 之间时,结果是 pose_first 和 pose_next 之间的旋转的插值。// 平移部分使用线性插值,y = (1-s)*y_0 + s*y_1result = {pose_first.unit_quaternion().slerp(s, pose_next.unit_quaternion()),pose_first.translation() * (1 - s) + pose_next.translation() * s};best_match = s < 0.5 ? *match_iter : *match_iter_n;return true;
}
4.4 松耦合系统的配准部分
前文已经得到了去畸变的点云,这里只需将其传递给增量 NDT 里程计,并使用滤波器预测得到的先验位姿作为增量 NDT 里程计的初始位姿,经过迭代计算后得到优化后的位姿后再返回给滤波器,滤波器进行观测过程。在这个过程中滤波器部分和点云配准部分是解耦的。
void LooselyLIO::Align() {FullCloudPtr scan_undistort_trans(new FullPointCloudType);pcl::transformPointCloud(*scan_undistort_, *scan_undistort_trans, TIL_.matrix()); // 将 lidar 坐标系下的点云转换到 imu 坐标系下// scan_undistort_ 为 imu 坐标系下 无畸变的点云scan_undistort_ = scan_undistort_trans;auto current_scan = ConvertToCloud<FullPointType>(scan_undistort_);// voxel 降采样pcl::VoxelGrid<PointType> voxel;voxel.setLeafSize(0.5, 0.5, 0.5);voxel.setInputCloud(current_scan);CloudPtr current_scan_filter(new PointCloudType);voxel.filter(*current_scan_filter);/// 处理首帧雷达数据if (flg_first_scan_) {SE3 pose;inc_ndt_lo_->AddCloud(current_scan_filter, pose);flg_first_scan_ = false;return;}/// 从EKF中获取预测pose,放入LO,获取LO位姿,最后合入EKFSE3 pose_predict = eskf_.GetNominalSE3();inc_ndt_lo_->AddCloud(current_scan_filter, pose_predict, true); // 第 3 个参数为 true, 即不使用匀速运动假设推测位姿pose_of_lo_ = pose_predict;eskf_.ObserveSE3(pose_of_lo_, 1e-2, 1e-2);if (options_.with_ui_) {// 放入UIui_->UpdateScan(current_scan, eskf_.GetNominalSE3()); // 转成Lidar Pose传给UIui_->UpdateNavState(eskf_.GetNominalState());}frame_num_++;
}
I0112 21:54:14.812438 383071 ndt_inc.cc:124] aligning with inc ndt, pts: 1532, grids: 819
I0112 21:54:14.812675 383071 ndt_inc.cc:222] iter 0 total res: 2003.41, eff: 960, mean res: 2.08689, dxn: 0.00164213, dx: -0.000169117 00.000230697 00.000262647 00.000125452 0-0.00158076 00.000176706
I0112 21:54:14.812845 383071 ndt_inc.cc:222] iter 1 total res: 1981.36, eff: 954, mean res: 2.0769, dxn: 0.000790319, dx: 07.83699e-06 02.29818e-05 09.93558e-05 -0.000364279 -0.000640747 00.000266245
I0112 21:54:14.812858 383071 ndt_inc.cc:227] converged, dx = 07.83699e-06 02.29818e-05 09.93558e-05 -0.000364279 -0.000640747 00.000266245
相关文章:
《自动驾驶与机器人中的SLAM技术》ch7:基于 ESKF 的松耦合 LIO 系统
目录 基于 ESKF 的松耦合 LIO 系统 1 坐标系说明 2 松耦合 LIO 系统的运动和观测方程 3 松耦合 LIO 系统的数据准备 3.1 CloudConvert 类 3.2 MessageSync 类 4 松耦合 LIO 系统的主要流程 4.1 IMU 静止初始化 4.2 ESKF 之 运动过程——使用 IMU 预测 4.3 使用 IMU 预测位姿进…...
day07_Spark SQL
文章目录 day07_Spark SQL课程笔记一、今日课程内容二、Spark SQL函数定义(掌握)1、窗口函数2、自定义函数背景2.1 回顾函数分类标准:SQL最开始是_内置函数&自定义函数_两种 2.2 自定义函数背景 3、Spark原生自定义UDF函数3.1 自定义函数流程&#x…...
【LC】2270. 分割数组的方案数
题目描述: 给你一个下标从 0 开始长度为 n 的整数数组 nums 。 如果以下描述为真,那么 nums 在下标 i 处有一个 合法的分割 : 前 i 1 个元素的和 大于等于 剩下的 n - i - 1 个元素的和。下标 i 的右边 至少有一个 元素,也就是…...
Docker 容器通信的网络模式详解
Docker 的网络模式是容器化技术中非常重要的一部分,它决定了容器之间以及容器与外部世界如何通信。Docker 提供了多种网络模式,每种模式都有其特定的使用场景和优势。本文将深入探讨 Docker 的网络模式,包括桥接模式、主机模式、覆盖网络模式…...
Apache和PHP:构建动态网站的黄金组合
在当今的互联网世界,网站已经成为了企业、个人和机构展示自己、与用户互动的重要平台。而在这些动态网站的背后,Apache和PHP无疑是最受开发者青睐的技术组合之一。这一组合提供了高效、灵活且可扩展的解决方案,帮助您快速搭建出强大的网站&am…...
一个简单的html5导航页面
一个简单的 HTML5 导航页面的示例代码: html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><ti…...
积木仪表盘 出现 “没有权限,请联系管理员分配权限“ 解决方法
目录 前言1. 问题所示2. 解决方法前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 原先写过报表的错误!但错误解决方式不一样:jmreport测试数据库出现 权限不足,此功能需要分配角色 解决方法 1. 问题所示 出现 没有权限,请联系管理员分配权限 的…...
C++语言的循环实现
C语言中的循环实现 引言 在程序设计中,循环是一个至关重要的概念。它允许我们在满足某种条件时重复执行某段代码,从而实现复杂的逻辑和算法。C作为一种强大的编程语言,提供了多种循环结构来满足不同的需求。本文将深入探讨C中的循环实现&am…...
高级java每日一道面试题-2025年01月13日-框架篇[Spring篇]-Spring 是怎么解决循环依赖的?
如果有遗漏,评论区告诉我进行补充 面试官: Spring 是怎么解决循环依赖的? 我回答: 在Java高级面试中,Spring框架如何解决循环依赖是一个重要且常见的问题。以下是对Spring解决循环依赖的详细解释: 循环依赖的定义与类型 循环依赖是指两个或多个Bea…...
.Net Core Record 类型
public class Person { public string id {get;init;} public string code {get;init;} public string name {get;init;} } //Person 属性不可单独赋值,相当于使用record定义 public record Person string id,string code,string name) //record类…...
GitLab CI/CD使用runner实现自动化部署前端Vue2 后端.Net 7 Zr.Admin项目
1、查看gitlab版本 建议安装的runner版本和gitlab保持一致 2、查找runner 执行 yum list gitlab-runner --showduplicates | sort -r 找到符合gitlab版本的runner,我这里选择 14.9.1版本 如果执行出现找不到下载源,添加官方仓库 执行 curl -L &quo…...
重邮+数字信号处理实验七:用 MATLAB 设计 IIR 数字滤波器
一、实验目的 1 、加深对窗函数法设计 FIR 数字滤波器的基本原理的理解。 2 、学习用 Matlab 语言的窗函数法编写设计 FIR 数字滤波器的程序。 3 、了解 Matlab 语言有关窗函数法设计 FIR 数字滤波器的常用函数用法。 4 、掌握 FIR 滤波器的快速卷积实现原理。…...
CES 2025:INAIR 推出“另类”AR电脑,重新定义移动计算体验
在2025年国际消费类电子产品展览会(CES)上,INAIR公司凭借其创新的AR电脑产品吸引了众多目光。这款设备不仅融合了增强现实(AR)技术与传统个人电脑的功能,还通过独特的设计理念为用户带来了前所未有的移动计算体验。本文将详细介绍INAIR AR电脑的特点、技术创新及其对未来…...
了解 ASP.NET Core 中的中间件
在 .NET Core 中,中间件(Middleware) 是处理 HTTP 请求和响应的核心组件。它们被组织成一个请求处理管道,每个中间件都可以在请求到达最终处理程序之前或之后执行操作。中间件可以用于实现各种功能,如身份验证、路由、…...
数据结构与算法之链表: LeetCode 234. 回文链表 (Ts版)
回文链表 https://leetcode.cn/problems/palindrome-linked-list/description/ 描述 给你一个单链表的头节点 head ,请你判断该链表是否为回文链表如果是,返回 true ;否则,返回 false 示例 1 输入:head [1,2,2,1]…...
DVWA靶场CSRF漏洞通关教程及源码审计
目录标题 CSRFlow源码审计 medium源码审计 high源码审计 impossible源码审计 CSRF low 先修改密码 看到地址栏 复制在另一个网页打开 成功登录 源码审计 没有任何过滤措施,很危险,并且采用了不安全的md5加密 <?phpif( isset( $_GET[ Change ] )…...
支持Google Analytics快捷添加的CMS:费用与部署形式详解
CMS 的费用和部署形式是选择平台的重要参考因素,不同的业务需求需要不同的解决方案。本文将从费用和部署形式两个角度,详细分析支持 Google Analytics 快捷集成的 CMS 和工具,帮助您更好地了解这些平台的特点。 1. BigCommerce 费用ÿ…...
Kibana操作ES基础
废话少说,开干!!!!!!!!!!!!截图更清晰,复制在下面 #库操作#创建索引【相当于数据库的库】 PUT /first_index#获…...
如何在Ubuntu上安装和配置Git
版本控制系统(VCS)是软件开发过程中不可或缺的工具之一,它帮助开发者跟踪代码变更、协作开发以及管理不同版本的项目。Git作为当前最流行的分布式版本控制系统,因其高效性和灵活性而广受青睐。本文将指导你如何在Ubuntu操作系统上…...
基于springboot+vue+微信小程序的宠物领养系统
基于springbootvue微信小程序的宠物领养系统 一、介绍 本项目利用SpringBoot、Vue和微信小程序技术,构建了一个宠物领养系统。 本系统的设计分为两个层面,分别为管理层面与用户层面,也就是管理者与用户,管理权限与用户权限是不…...
HTB:Driver[WriteUP]
目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 将靶机TCP开放端口号提取并保存 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 使用smbclient尝…...
Require:利用MySQL binlog实现闪回操作
1,闪回原理 【binlog】MySQL binlog以event的形式,记录了MySQL server从启用binlog以来所有的变更信息,能够帮助重现这之间的所有变化。MySQL引入binlog主要有两个目的:一是为了主从复制;二是某些备份还原操作后需要重…...
黑马linux笔记(03)在Linux上部署各类软件 MySQL5.7/8.0 Tomcat(JDK) Nginx RabbitMQ
文章目录 实战章节:在Linux上部署各类软件tar -zxvf各个选项的含义 为什么学习各类软件在Linux上的部署 一 MySQL数据库管理系统安装部署【简单】MySQL5.7版本在CentOS系统安装MySQL8.0版本在CentOS系统安装MySQL5.7版本在Ubuntu(WSL环境)系统…...
FFmpeg入门
在音视频处理领域,有一款神器级的工具横扫开发者圈,那就是 FFmpeg。它被誉为“音视频处理的瑞士军刀”,凭借强大的功能和开源的特性成为众多开发者和媒体从业者的首选。今天,我们就来聊聊 FFmpeg 的入门使用,带你轻松开…...
如何将 sqlserver 数据迁移到 mysql
文章目录 前言一、导出SQL Server 数据二、转换数据格式为MySQL兼容格式三、导入数据到MySQL数据库五、使用ETL工具六、通过 navicat 工具七、总结 前言 将 SQL Server 数据迁移到 MySQL 是一个常见的数据库迁移任务,通常涉及以下几个关键步骤:导出 SQL…...
【leetcode 13】哈希表 242.有效的字母异位词
原题链接 题解链接 一般哈希表都是用来快速判断一个元素是否出现集合里。 当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。 数组 set (集合) map(映射) 如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景…...
git - 用SSH方式迁出远端git库
文章目录 git - 用SSH方式迁出远端git库概述笔记以gitee为例产生RSA密钥对 备注githubEND git - 用SSH方式迁出远端git库 概述 最近一段时间,在网络没问题的情况下,用git方式直接迁出git库总是会失败。 失败都是在远端, 显示RPC错误。 但是git服务器端…...
21天学通C++——9.5复制构造函数
浅复制 复制类对象时只是单纯的复制所有的值,如指针只会复制指针的大小,而不会再开辟同一空间大小的内存,即两个指针指向同一片内存空间。 伪代码: class MyString { private:char*buffer; public:MyString(const char* initStri…...
GPT 系列论文精读:从 GPT-1 到 GPT-4
学习 & 参考资料 前置文章 Transformer 论文精读 机器学习 —— 李宏毅老师的 B 站搬运视频 自监督式学习(四) - GPT的野望[DLHLP 2020] 來自猎人暗黑大陆的模型 GPT-3 论文逐段精读 —— 沐神的论文精读合集 GPT,GPT-2,GPT-3 论文精读【论文精读】…...
【python】OpenCV—Local Translation Warps
文章目录 1、功能描述2、原理分析3、代码实现4、效果展示5、完整代码6、参考 1、功能描述 利用液化效果实现瘦脸美颜 交互式的液化效果原理来自 Gustafsson A. Interactive image warping[D]. , 1993. 2、原理分析 上面描述很清晰了,鼠标初始在 C,也即…...
elasticsearch集群部署
一、创建 elasticsearch-cluster 文件夹 创建 elasticsearch-7.6.2-cluster文件夹 修改服务es服务文件夹为node-001 修改config/elasticsearch.yml 配置文件 # Elasticsearch Configuration # # NOTE: Elasticsearch comes with reasonable defaults for most settings. # …...
python调用window库全屏截图生成bmp位图学习
import io import time import struct import ctypes s time.time() gdi32 ctypes.windll.gdi32 user32 ctypes.windll.user32# 定义常量 SM_CXSCREEN 0 SM_CYSCREEN 1# 缩放比例 zoom 1 screenWidth int(user32.GetSystemMetrics(SM_CXSCREEN) * zoom) screenHeight i…...
Wireshark使用
1.抓包过滤器--BPF语法 类型Type:主机(host)、网段(net)、端口(port) 方向Dir:源地址(src)、目标地址(dst) 协议Proto:各种…...
FLASK 上传文件
HTML form enctype"multipart/form-data" 编码类型说明application/x-www-form-urlencoded表单数据编码为名称/值对。 这是标准编码格式。multipart/form-data表单数据编码为消息,页面上每个控件都有单独的部分。text/plain表单数据以纯文本编码&#x…...
卷积神经网络
卷积神经网络 随着输入数据规模的增大,计算机视觉的处理难度也大幅增加。 64 64 3 64 \times 64 \times 3 64643 的图片特征向量维度为12288,而 1000 1000 3 1000 \times 1000 \times 3 100010003 的图片数据量达到了300万。随着数据维度的增加&am…...
SparrowRTOS系列:链表版本内核
前言 Sparrow RTOS是笔者之前写的一个极简性RTOS,初代版本只有400行,后面笔者又添加了消息队列、信号量、互斥锁三种IPC机制,使之成为一个较完整、堪用的内核,初代版本以简洁为主,使用数组和表作为任务挂载的抽象数据…...
【redis初阶】环境搭建
目录 一、Ubuntu 安装 redis 二、Centos7 安装 redis 三、Centos8 安装 redis 四、redis客户端介绍 redis学习🥳 一、Ubuntu 安装 redis 使用 apt 安装 apt install redis -y 查看redis版本 redis-server --version 支持远程连接…...
OpenCV相机标定与3D重建(54)解决透视 n 点问题(Perspective-n-Point, PnP)函数solvePnP()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 根据3D-2D点对应关系找到物体的姿态。 cv::solvePnP 是 OpenCV 库中的一个函数,用于解决透视 n 点问题(Perspective-n-Po…...
shell脚本回顾1
1、shell 脚本写出检测 /tmp/size.log 文件如果存在显示它的内容,不存在则创建一个文件将创建时间写入。 一、 ll /tmp/size.log &>/dev/null if [ $? -eq 0 ];then cat /tmp/size.log else touch /tmp/size.log echo date > /tmp/size.log fi二、 if …...
HarmonyOS命令行工具
作为一个从Android转过来的鸿蒙程序猿,在开发过程中不由自主地想使用类似adb命令的命令行工具去安装/卸载应用,往设备上推或者拉去文件,亦或是抓一些日志。但是发现在鸿蒙里边,华为把命令行工具分的很细,种类相当丰富 …...
V少JS基础班之第四弹
一、 前言 第四弹内容是操作符。 本章结束。第一个月的内容就完成了, 是一个节点。 下个月我们就要开始函数的学习了。 我们学习完函数之后。很多概念就可以跟大家补充说明了。 OK,那我们就开始本周的操作符学习 本系列为一周一更,计划历时6…...
从前端视角看设计模式之创建型模式篇
设计模式简介 "设计模式"源于GOF(四人帮)合著出版的《设计模式:可复用的面向对象软件元素》,该书第一次完整科普了软件开发中设计模式的概念,他们提出的设计模式主要是基于以下的面向对象设计原则ÿ…...
网络应用技术 实验七:实现无线局域网
一、实验简介 在 eNSP 中构建无线局域网,并实现全网移动终端互相通信。 二、实验目的 1 、理解无线局域网的工作原理; 2 、熟悉无线局域网的规划与构建过程; 3 、掌握无线局域网的配置方法; 三、实验学时 2 学时 四、实…...
Kotlin 循环语句详解
文章目录 循环类别for-in 循环区间整数区间示例1:正向遍历示例2:反向遍历 示例1:遍历数组示例2:遍历区间示例3:遍历字符串示例4:带索引遍历 while 循环示例:计算阶乘 do-while 循环示例…...
B+树的原理及实现
文章目录 B树的原理及实现一、引言二、B树的特性1、结构特点2、节点类型3、阶数 三、B树的Java实现1、节点实现2、B树操作2.1、搜索2.2、插入2.3、删除2.4、遍历 3、B树的Java实现示例 四、总结 B树的原理及实现 一、引言 B树是一种基于B树的树形数据结构,它在数据…...
ArkTS 基础语法:声明式 UI 描述与自定义组件
1. ArkTS 简介 ArkTS 是 HarmonyOS 应用开发中的一种编程语言,它结合了 TypeScript 的类型检查和声明式 UI 描述方式,帮助开发者更高效地构建用户界面。 2. 声明式 UI 描述 ArkTS 使用声明式语法来定义 UI 结构,通过组件、属性和事件配置实…...
list的模拟实现详解
文章目录 list的模拟实现list的迭代器begin()和end() list的模拟实现 #pragma once #include<iostream> #include<list>using namespace std;namespace wbc {// 类模版template<class T>struct list_node // 链表的节点{T _data;list_node<T>* _next;…...
图解Git——分支的新建与合并《Pro Git》
⭐分支的新建与合并 先引入一个实际开发的工作流: 开发某个网站。为实现某个新的需求,创建一个分支。在这个分支上开展工作。 正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。你将按照如下方式来处理: 切换到你…...
SQLite 语法快速入门
SQLite 是一个软件库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。 提供一个免费的在线SQLite编辑器 (0)常用命令 # 格式化 .header on .mode column .timer on# 查看表格 .tables# 查看表结构(建表语句) .schema …...
高速光电探测器设计 PIN APD铟镓砷TIA放大脉冲误码测试800-1700nm
高速光电探测器PIN APD铟镓砷TIA放大脉冲误码测试800-1700nm (对标:索雷博APD431A) (对标:索雷博APD431A) (对标:索雷博APD431A) 规格参数: 波长范围:800-1700nm 输出带宽:DC-400MHz(-3dB&…...