SAICP(模拟退火迭代最近点)的实现
SAICP(模拟退火迭代最近点)的实现
注: 本系列所有文章在github开源, 也是我个人的学习笔记, 欢迎大家去star以及fork, 感谢!
仓库地址: pointcloud-processing-visualization
总结一下上周的学习情况
ICP会存在局部最小值的问题, 这个问题可能即使是没有实际遇到过, 也或多或少会在各种点云匹配算法相关博客中看到,
于是我去查了一些资料, 发现可以通过模拟退火算法解决, 或者说有概率可以跳出局部最小值
最后将两个算法结合了起来, 写了这个demo, 同时因为我也是初学者, 因此将执行改成了单步运行,
同时将ICP以及模拟退火ICP(SAICP)的执行效果通过PCL Viewer逐步展示出来.
参考资料
@智能算法 模拟退火算法详解
@维基百科 模拟退火
@chatGPT 4.0 这个有点可惜, 已经找不到当时跟GPT的聊天记录了
模拟退火的实现
扰动的生成
在进行点云匹配时, 如果要实现模拟退火中的随机干扰, 我使用的思路是生成一组随机变换, 即一个Eigen::Matrix4f randTrans
这个变换随机生成, 包含x
, y
, z
三轴横移, 以及roll
, pitch
, yaw
三轴转动
通过温度值(temperature)来控制变换的幅度大小
对应代码块
// 根据温度生成随机的变换矩阵
Eigen::Matrix4f generateAnnealingTransformation(double temperature) {unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();boost::random::mt19937 gen(seed);double tras_scale = 0.15;double rot_scale = 0.17 * M_PI;boost::random::uniform_real_distribution<> rand_dis(0, 1);boost::random::uniform_real_distribution<> dis(-tras_scale * temperature, tras_scale * temperature);boost::random::uniform_real_distribution<> angle_dis(-rot_scale * temperature, rot_scale * temperature);Eigen::Matrix4f transform = Eigen::Matrix4f::Identity();transform(0, 3) = dis(gen); // X-axis translation disturbancetransform(1, 3) = dis(gen); // Y-axis translation disturbancetransform(2, 3) = dis(gen); // Z-axis translation disturbance// Rotation disturbance around Z-axisfloat angleZ = angle_dis(gen);Eigen::Matrix4f rotZ = Eigen::Matrix4f::Identity();rotZ(0, 0) = cos(angleZ);rotZ(0, 1) = -sin(angleZ);rotZ(1, 0) = sin(angleZ);rotZ(1, 1) = cos(angleZ);// Rotation disturbance around Y-axisfloat angleY = angle_dis(gen);Eigen::Matrix4f rotY = Eigen::Matrix4f::Identity();rotY(0, 0) = cos(angleY);rotY(0, 2) = sin(angleY);rotY(2, 0) = -sin(angleY);rotY(2, 2) = cos(angleY);// Rotation disturbance around X-axisfloat angleX = angle_dis(gen);Eigen::Matrix4f rotX = Eigen::Matrix4f::Identity();rotX(1, 1) = cos(angleX);rotX(1, 2) = -sin(angleX);rotX(2, 1) = sin(angleX);rotX(2, 2) = cos(angleX);// Combine the transformationstransform = transform * rotZ * rotY * rotX;return transform;
}// 在原有变换的基础上添加模拟退火的随机扰动
Eigen::Matrix4f annealing_transform = generateAnnealingTransformation(temperature);
Eigen::Matrix4f perturbed_transformation = saicp_result * annealing_transform;// 应用带有扰动的变换进行ICP迭代
saicp.align(*saicp_cloud, perturbed_transformation);
公式的实现
模拟退火时, 如果当前结果优于上次的结果, 就采纳当前结果,
如果当前结果比上次的结果差, 按以下公式来选择是否会选择差的结果(跳出局部最优的手段)
e R 1 − R 0 T > r a n d ( ) e^\frac{R_1-R_0}{T} > rand() eTR1−R0>rand()
其中R1
为这次的结果, R0
为上次的结果 T
为当前温度, rand()
是随机生成的一个数字
如果当前的结果比上次的差, 则R1-R0
为负数, 因此概率会随着温度的降低越来越小, 随着误差的增大也越来越小
在ICP中, 可以用icp.getFitnessScore()
作为误差的判断条件, 当前的fitnessscore
比上次小, 则采纳当前结果, 反之根据两次fitnessscore
的差值计算概率
对应代码块为
// 退火
double new_fitness_score = saicp.getFitnessScore();if (new_fitness_score < last_fitness_score)
{saicp_result = new_icp_result; // 接受更好的变换last_fitness_score = new_fitness_score; // 更新最新的fitness score
}
// 新值大于旧值说明结果差, 取反, 满足误差越大概率越小的条件
else if (exp((-(new_fitness_score - last_fitness_score)) / temperature) > ((double)rand() / RAND_MAX))
{saicp_result = perturbed_transformation; // 以一定概率接受较差的变换last_fitness_score = new_fitness_score; // 更新fitness score,即使它变差了
}
// 更新温度
temperature *= coolingRate;
完整代码
TODO: 计算两个变换之间的误差公式应该是有问题的, 即使匹配上输出的结果还是很差
main.cpp
#include <iostream>
#include <fstream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/common/transforms.h>
#include <pcl/registration/icp.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/filter.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <Eigen/Dense>
#include <boost/random.hpp>
#include <chrono>
#include <thread>
#include <filesystem>// 生成随机扰动(方便跳出循环)
Eigen::Matrix4f generateAnnealingTransformation(double temperature) {unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();boost::random::mt19937 gen(seed);double tras_scale = 0.15;double rot_scale = 0.17 * M_PI;boost::random::uniform_real_distribution<> rand_dis(0, 1);boost::random::uniform_real_distribution<> dis(-tras_scale * temperature, tras_scale * temperature);boost::random::uniform_real_distribution<> angle_dis(-rot_scale * temperature, rot_scale * temperature);Eigen::Matrix4f transform = Eigen::Matrix4f::Identity();transform(0, 3) = dis(gen); // X-axis translation disturbancetransform(1, 3) = dis(gen); // Y-axis translation disturbancetransform(2, 3) = dis(gen); // Z-axis translation disturbance// Rotation disturbance around Z-axisfloat angleZ = angle_dis(gen);Eigen::Matrix4f rotZ = Eigen::Matrix4f::Identity();rotZ(0, 0) = cos(angleZ);rotZ(0, 1) = -sin(angleZ);rotZ(1, 0) = sin(angleZ);rotZ(1, 1) = cos(angleZ);// Rotation disturbance around Y-axisfloat angleY = angle_dis(gen);Eigen::Matrix4f rotY = Eigen::Matrix4f::Identity();rotY(0, 0) = cos(angleY);rotY(0, 2) = sin(angleY);rotY(2, 0) = -sin(angleY);rotY(2, 2) = cos(angleY);// Rotation disturbance around X-axisfloat angleX = angle_dis(gen);Eigen::Matrix4f rotX = Eigen::Matrix4f::Identity();rotX(1, 1) = cos(angleX);rotX(1, 2) = -sin(angleX);rotX(2, 1) = sin(angleX);rotX(2, 2) = cos(angleX);// Combine the transformationstransform = transform * rotZ * rotY * rotX;return transform;
}Eigen::Matrix4f generateRandomTransformation() {unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();boost::random::mt19937 gen(seed); // 随机数生成器boost::random::uniform_real_distribution<> dis(-200, 200); // 位移范围boost::random::uniform_real_distribution<> angle_dis(-M_PI, M_PI); // 旋转范围Eigen::Matrix4f transform = Eigen::Matrix4f::Identity();transform(0, 3) = dis(gen); // X轴平移transform(1, 3) = dis(gen); // Y轴平移transform(2, 3) = dis(gen); // Z轴平移// Rotation disturbance around Z-axisfloat angleZ = angle_dis(gen);Eigen::Matrix4f rotZ = Eigen::Matrix4f::Identity();rotZ(0, 0) = cos(angleZ);rotZ(0, 1) = -sin(angleZ);rotZ(1, 0) = sin(angleZ);rotZ(1, 1) = cos(angleZ);// Rotation disturbance around Y-axisfloat angleY = angle_dis(gen);Eigen::Matrix4f rotY = Eigen::Matrix4f::Identity();rotY(0, 0) = cos(angleY);rotY(0, 2) = sin(angleY);rotY(2, 0) = -sin(angleY);rotY(2, 2) = cos(angleY);// Rotation disturbance around X-axisfloat angleX = angle_dis(gen);Eigen::Matrix4f rotX = Eigen::Matrix4f::Identity();rotX(1, 1) = cos(angleX);rotX(1, 2) = -sin(angleX);rotX(2, 1) = sin(angleX);rotX(2, 2) = cos(angleX);// Combine the transformationstransform = transform * rotZ * rotY * rotX;return transform;
}// 函数:随机选择一个.pcd文件
std::string selectRandomPCDFile(const std::string& directory) {std::vector<std::string> file_names;for (const auto& entry : std::filesystem::directory_iterator(directory)) {if (entry.path().extension() == ".pcd") {file_names.push_back(entry.path().filename().string());}}// 如果没有找到任何文件,则返回空字符串if (file_names.empty()) {return "";}// 随机选择一个文件srand(static_cast<unsigned int>(time(NULL))); // 初始化随机数生成器std::string selected_file = file_names[rand() % file_names.size()];// 返回完整的文件路径return directory + selected_file;
}void saveTransformation(const Eigen::Matrix4f &transform, const std::string &filename) {std::ofstream file(filename);if (file.is_open()) {file << transform;file.close();}
}struct TransformationError {float translationError;Eigen::Vector3f rotationError; // 存储绕X轴、Y轴和Z轴的旋转误差
};// 重载 << 运算符以打印 TransformationError
std::ostream& operator<<(std::ostream& os, const TransformationError& error) {os << "Translation Error: " << error.translationError << ", "<< "Rotation Error: [" << error.rotationError.transpose() << "]";return os;
}// 示例:计算两个变换矩阵之间的误差
TransformationError CalculateTransformationError(const Eigen::Matrix4f &matrix1, const Eigen::Matrix4f &matrix2) {TransformationError error;// 计算平移误差Eigen::Vector3f translation1 = matrix1.block<3,1>(0, 3);Eigen::Vector3f translation2 = matrix2.block<3,1>(0, 3);error.translationError = (translation2 - translation1).norm();// 计算旋转误差Eigen::Quaternionf quaternion1(matrix1.block<3,3>(0,0));Eigen::Quaternionf quaternion2(matrix2.block<3,3>(0,0));Eigen::Quaternionf deltaQuaternion = quaternion1.inverse() * quaternion2;Eigen::Vector3f deltaEulerAngles = deltaQuaternion.toRotationMatrix().eulerAngles(0, 1, 2); // X, Y, Zerror.rotationError = deltaEulerAngles.cwiseAbs(); // 绝对值误差return error;
}
Eigen::Matrix4f readMatrixFromFile(const std::string& filepath) {std::ifstream file(filepath);Eigen::Matrix4f matrix;if (file.is_open()) {for (int i = 0; i < 4; ++i) {for (int j = 0; j < 4; ++j) {if (!(file >> matrix(i, j))) {throw std::runtime_error("文件格式错误或数据不足以填充矩阵");}}}file.close();} else {throw std::runtime_error("无法打开文件: " + filepath);}return matrix;
}int main() {// 配置退火时的随机数种子srand(static_cast<unsigned int>(std::chrono::system_clock::now().time_since_epoch().count()));std::string directory = "/home/smile/Desktop/github/src/pointcloud-processing-visualization/pcd/";// 使用函数选择一个随机文件std::string file_to_load = selectRandomPCDFile(directory);// 加载点云pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in(new pcl::PointCloud<pcl::PointXYZ>);if (pcl::io::loadPCDFile<pcl::PointXYZ>(file_to_load, *cloud_in) == -1) {PCL_ERROR("Couldn't read file\n");return -1;}// 移除NaN值std::vector<int> indices;pcl::removeNaNFromPointCloud(*cloud_in, *cloud_in, indices);pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);// 进行体素滤波pcl::VoxelGrid<pcl::PointXYZ> voxel_grid;voxel_grid.setInputCloud(cloud_in);voxel_grid.setLeafSize(0.07f, 0.07f, 0.07f); // 设置体素大小voxel_grid.filter(*cloud_filtered);// 模拟退火参数double temperature = 5.2; // 初始温度double coolingRate = 0.985; // 冷却率// 全局变量double last_fitness_score = std::numeric_limits<double>::max(); // 初始设置为最大值// 生成变换并保存到文件Eigen::Matrix4f base_transformation = generateRandomTransformation();Eigen::Matrix4f base_transformation_normal = base_transformation;std::cout << "Base Transformation Matrix:\n" << base_transformation << std::endl;saveTransformation(base_transformation, "/home/smile/Desktop/github/src/pointcloud-processing-visualization/saicp/result.txt");// 应用初始变换pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_transformed(new pcl::PointCloud<pcl::PointXYZ>);pcl::transformPointCloud(*cloud_filtered, *cloud_transformed, base_transformation);// 设置SAICP实例pcl::IterativeClosestPoint<pcl::PointXYZ, pcl::PointXYZ> saicp, normal_icp;saicp.setInputSource(cloud_transformed);saicp.setInputTarget(cloud_filtered);saicp.setMaximumIterations(1); // 每次调用align时执行一次迭代normal_icp.setInputSource(cloud_transformed);normal_icp.setInputTarget(cloud_filtered);normal_icp.setMaximumIterations(1);// 初始化可视化pcl::visualization::PCLVisualizer viewer("SAICP demo");viewer.setBackgroundColor(0, 0, 0);viewer.addPointCloud<pcl::PointXYZ>(cloud_filtered, "cloud_filtered");viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud_filtered");viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1.0, 0.0, 0.0, "cloud_filtered");pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_saicp(new pcl::PointCloud<pcl::PointXYZ>(*cloud_transformed));pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_icp_normal(new pcl::PointCloud<pcl::PointXYZ>(*cloud_transformed));viewer.addPointCloud<pcl::PointXYZ>(cloud_saicp, "cloud_saicp");viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud_saicp");viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0.0, 1.0, 0.0, "cloud_saicp");viewer.addPointCloud<pcl::PointXYZ>(cloud_icp_normal, "cloud_icp_normal");viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud_icp_normal");viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0.0, 0.0, 1.0, "cloud_icp_normal");viewer.addCoordinateSystem(1.0);viewer.initCameraParameters();// 创建初始变换的矩阵, 先验位姿的是一个单位阵, 即无先验位姿Eigen::Matrix4f saicp_result = Eigen::Matrix4f::Identity();Eigen::Matrix4f normal_icp_result = Eigen::Matrix4f::Identity();// 计数器int saicp_cnt = 1; // icp迭代次数int normal_icp_cnt = 1; // 先验icp迭代次数bool saicp_fitness_reached = false; bool normal_icp_fitness_reached = false;int iteration_counter = 0; // 迭代频率计数器, 迭代的频率按照 10ms x iteration_counter 可以在下面的循环中修改int stop_iteration_cnt = 0;bool has_published = false;int bad_value_accetp_cnt = 0;while (!viewer.wasStopped()) {viewer.spinOnce(); // 图像化界面刷新频率10ms, 方便使用鼠标进行控制视角 std::this_thread::sleep_for(std::chrono::milliseconds(10));// 如果都完成了收敛, 则不再更新if((saicp_fitness_reached && normal_icp_fitness_reached) || (++stop_iteration_cnt >= 2000)) {if(!has_published){double icp_score = saicp.getFitnessScore();double icp_normal_score = normal_icp.getFitnessScore();std::cout << "SAICP迭代次数: " << saicp_cnt << " SAICP分数: " << icp_score <<std::endl;std::cout << "普通ICP迭代次数: " << normal_icp_cnt << " 普通ICP分数: " << icp_normal_score <<std::endl;std::cout << "迭代次数比率: " << (saicp_cnt-normal_icp_cnt)/normal_icp_cnt <<std::endl;std::cout << "分数差比率: " << std::abs((icp_score-icp_normal_score))/icp_normal_score <<std::endl;std::cout << "差值接收率: " << double(bad_value_accetp_cnt)/double(saicp_cnt) <<std::endl;std::cout << "[SAICP]变换矩阵 " << std::endl;std::cout << saicp.getFinalTransformation() << std::endl;std::cout << "[SAICP]误差 " << std::endl;Eigen::Matrix4f result = readMatrixFromFile("/home/smile/Desktop/github/src/pointcloud-processing-visualization/saicp/result.txt");std::cout << CalculateTransformationError(saicp.getFinalTransformation(),result) <<std::endl;std::cout << "-----------------------------------------------------------" << std::endl;std::cout << "[SAICP]变换矩阵 " <<std::endl;std::cout << normal_icp.getFinalTransformation() << std::endl;std::cout << "[SAICP]误差 " << std::endl;std::cout << CalculateTransformationError(normal_icp.getFinalTransformation(),result) <<std::endl;std::cout << "-----------------------------------------------------------" << std::endl;has_published = true;}continue;}// 创建icp之后的新点云pcl::PointCloud<pcl::PointXYZ>::Ptr saicp_cloud(new pcl::PointCloud<pcl::PointXYZ>);pcl::PointCloud<pcl::PointXYZ>::Ptr normal_icp_cloud(new pcl::PointCloud<pcl::PointXYZ>);// 每10ms x 100 = 1000ms = 1s 即每1秒做一次icp并更新点云if (++iteration_counter >= 2) {// 如果没有达到0.0001的分值, 则icp继续迭代if(!saicp_fitness_reached){// 在原有变换的基础上添加模拟退火的随机扰动Eigen::Matrix4f annealing_transform = generateAnnealingTransformation(temperature);Eigen::Matrix4f perturbed_transformation = saicp_result * annealing_transform;// 应用带有扰动的变换进行ICP迭代saicp.align(*saicp_cloud, perturbed_transformation);Eigen::Matrix4f new_icp_result = saicp.getFinalTransformation();// 检查是否收敛(肯定收敛, 因为最多迭代1次,所以每一次都会收敛)if (saicp.hasConverged()) {// 退火double new_fitness_score = saicp.getFitnessScore();if (new_fitness_score < last_fitness_score) {saicp_result = new_icp_result; // 接受更好的变换last_fitness_score = new_fitness_score; // 更新最新的fitness score} else if (exp((-(new_fitness_score - last_fitness_score)) / temperature) > ((double)rand() / RAND_MAX)){bad_value_accetp_cnt++; saicp_result = perturbed_transformation; // 以一定概率接受较差的变换last_fitness_score = new_fitness_score; // 更新fitness score,即使它变差了}// 更新温度temperature *= coolingRate;// std::cout << "======================================================="<<std::endl;// std::cout << "当前温度: " << temperature <<std::endl;// std::cout << "======================================================="<<std::endl;double fitness_score = saicp.getFitnessScore();if(saicp_fitness_reached) saicp_cnt=saicp_cnt;else saicp_cnt += 1;// std::cout << "[ICP] 分数为 " << fitness_score <<std::endl;// 获取最新一次的变换, 并将该变换应用到带先验的点云上, 更新该点云base_transformation = saicp.getFinalTransformation().cast<float>();pcl::transformPointCloud(*cloud_transformed, *saicp_cloud, base_transformation);viewer.updatePointCloud<pcl::PointXYZ>(saicp_cloud, "cloud_saicp");//真正的停止条件(收敛条件)if(fitness_score<=0.001){saicp_fitness_reached = true;std::cout << "======================================================="<<std::endl;std::cout << "[SAICP]完成收敛 " <<std::endl;std::cout << "[SAICP]迭代次数为 " << saicp_cnt <<std::endl;std::cout << "[SAICP]变换矩阵 " << std::endl;std::cout << saicp.getFinalTransformation() << std::endl;std::cout << "======================================================="<<std::endl;} }} // 普通icp if(!normal_icp_fitness_reached){normal_icp.align(*normal_icp_cloud, normal_icp_result);normal_icp_result = normal_icp.getFinalTransformation();// 同理, 这里并不是真正的停止条件if (normal_icp.hasConverged()) {double fitness_score_normal = normal_icp.getFitnessScore();if(normal_icp_fitness_reached) normal_icp_cnt = normal_icp_cnt;else normal_icp_cnt += 1;// std::cout << "[普通ICP] 分数为 " << fitness_score_normal <<std::endl;// 带先验的停止条件也是0.0001分以下终止if(fitness_score_normal<=0.001){normal_icp_fitness_reached = true;std::cout << "======================================================="<<std::endl;std::cout << "[普通ICP]完成收敛 " <<std::endl;std::cout << "[普通ICP]迭代次数为 " << normal_icp_cnt <<std::endl;std::cout << "[普通ICP]变换矩阵 " <<std::endl;std::cout << normal_icp.getFinalTransformation() << std::endl;std::cout << "======================================================="<<std::endl;}// 获取最新一次的变换, 并将该变换应用到带先验的点云上, 更新该点云base_transformation_normal = normal_icp.getFinalTransformation().cast<float>();pcl::transformPointCloud(*cloud_transformed, *normal_icp_cloud, base_transformation_normal);viewer.updatePointCloud<pcl::PointXYZ>(normal_icp_cloud, "cloud_icp_normal"); }}// 重置迭代计数器iteration_counter = 0;}}
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(saicp_example)# 设置 C++ 标准为 C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)find_package(PCL 1.8 REQUIRED)include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})add_executable(saicp_example main.cpp)# 链接 PCL 库和可能需要的 stdc++fs 库
target_link_libraries(saicp_example ${PCL_LIBRARIES})
相关文章:
SAICP(模拟退火迭代最近点)的实现
SAICP(模拟退火迭代最近点)的实现 注: 本系列所有文章在github开源, 也是我个人的学习笔记, 欢迎大家去star以及fork, 感谢! 仓库地址: pointcloud-processing-visualization 总结一下上周的学习情况 ICP会存在局部最小值的问题, 这个问题可能即使是没有实际遇到过, 也或多…...

FineBI实战项目一(23):订单商品分类词云图分析开发
点击新建组件,创建订单商品分类词云图组件。 选择词云,拖拽catName到颜色和文本,拖拽cat到大小。 将组件拖拽到仪表板。 结果如下:...
DOS命令
当使用DOS命令时,可以在命令提示符下输入各种命令以执行不同的任务。以下是一些常见DOS命令的详细说明: dir (Directory): 列出当前目录中的文件和子目录。 用法: dir [drive:][path][filename] [/p] [/w] cd (Change Directory): 更改当前目录。 用法: …...
【Python】torch中的.detach()函数详解和示例
在PyTorch中,.detach()是一个用于张量的方法,主要用于创建该张量的一个“离断”版本。这个方法在很多情况下都非常有用,例如在缓存释放、模型评估和简化计算图等场景中。 .detach()方法用于从计算图中分离一个张量,这意味着它创建…...

二级域名分发系统源码 对接易支付php源码 全开源
全面开源的易支付PHP源码分享:实现二级域名分发对接 首先,在epay的config.php文件中修改您的支付域名。 随后,在二级域名分发网站上做相应修改。 伪静态 location / { try_files $uri $uri/ /index.php?$query_string; } 源码下载&#…...

二分查找与搜索树的高频问题(算法村第九关白银挑战)
基于二分查找的拓展问题 山峰数组的封顶索引 852. 山脉数组的峰顶索引 - 力扣(LeetCode) 给你由整数组成的山脉数组 arr ,返回满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i 1] > ... > arr[arr.length - 1…...
Python爬虫快速入门
Python 爬虫Sutdy 1.基本类库 request(请求) 引入 from urllib import request定义url路径 url"http://www.baidu.com"进行请求,返回一个响应对象response responserequest.urlopen(url)读取响应体read()以字节形式打印网页源码 response.read()转码 编码 文本–by…...

部署MinIO
一、安装部署MINIO 1.1 下载 wget https://dl.min.io/server/minio/release/linux-arm64/minio chmod x minio mv minio /usr/local/bin/ # 控制台启动可参考如下命令, 守护进程启动请看下一个代码块 # ./minio server /data /data --console-address ":9001"1.2 配…...

RK3566环境搭建
环境:vmware16,ubuntu 18.04 安装依赖库: sudo apt-get install repo git ssh make gcc libssl-dev liblz4-tool expect g patchelf chrpath gawk texinfo chrpath diffstat binfmt-support qemu-user-static live-build bison flex fakero…...

精确掌控并发:滑动时间窗口算法在分布式环境下并发流量控制的设计与实现
这是《百图解码支付系统设计与实现》专栏系列文章中的第(15)篇,也是流量控制系列的第(2)篇。点击上方关注,深入了解支付系统的方方面面。 上一篇介绍了固定时间窗口算法在支付渠道限流的应用以及使用redis…...

Python展示 RGB立方体的二维切面视图
代码实现 import numpy as np import matplotlib.pyplot as plt# 生成 24-bit 全彩 RGB 立方体 def generate_rgb_cube():# 初始化一个 256x256x256 的三维数组rgb_cube np.zeros((256, 256, 256, 3), dtypenp.uint8)# 填充立方体for r in range(256):for g in range(256):fo…...

03 顺序表
目录 线性表顺序表练习 线性表(Linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串。。。 线性表在逻辑上时线性结构,是连续的一条直线。但在物理结…...

2023年全球软件开发大会(QCon北京站2023)9月:核心内容与学习收获(附大会核心PPT下载)
随着科技的飞速发展,全球软件开发大会(QCon)作为行业领先的技术盛会,为世界各地的专业人士提供了交流与学习的平台。本次大会汇集了全球的软件开发者、架构师、项目经理等,共同探讨软件开发的最新趋势、技术与实践。本…...
ChatGPT 和 文心一言 的优缺点及需求和使用场景
ChatGPT和文心一言是两种不同的自然语言生成模型,它们有各自的优点和缺点。 ChatGPT(Generative Pre-trained Transformer)是由OpenAI开发的生成式AI模型,它在庞大的文本数据集上进行了预训练,并可以根据输入生成具有上…...

架构师之超时未支付的订单进行取消操作的几种解决方案
今天给大家上一盘硬菜,并且是支付中非常重要的一个技术解决方案,有这块业务的同学注意自己尝试一把哈! 一、需求如下: 生成订单30分钟未支付,自动取消 生成订单60秒后,给用户发短信 对上述的需求,我们给…...

【容器固化】 OS技术之OpenStack容器固化的实现原理及操作
1. Docker简介 要学习容器固化,那么必须要先了解下Docker容器技术。Docker是基于GO语言实现的云开源项目,通过对应用软件的封装、分发、部署、运行等生命周期的管理,达到应用组件级别的“一次封装,到处运行”。这里的应用软件&am…...
设置 SSH 通过密钥登录
我们一般使用 PuTTY 等 SSH 客户端来远程管理 Linux 服务器。但是,一般的密码方式登录,容易有密码被暴力破解的问题。所以,一般我们会将 SSH 的端口设置为默认的 22 以外的端口,或者禁用 root 账户登录。其实,有一个更…...
1.6 面试经典150题 - 买卖股票的最佳时机
买卖股票的最佳时机 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易…...

locust快速入门--使用分布式提高测试压力
背景: 使用默认的locust启动命令进行压测时,尽管已经将用户数设置大比较大(400),但是压测的时候RPS一直在100左右。需要增加压测的压力。 问题原因: 如果你是通过命令行启动的或者参考之前文章的启动方式…...
K8s(三)Pod资源——pod亲和性与反亲和性,pod重启策略
目录 pod亲和性与反亲和性 pod亲和性 pod反亲和性 pod状态与重启策略 pod状态 pod重启策略 本文主要介绍了pod资源与pod相关的亲和性,以及pod的重启策略 pod亲和性与反亲和性 pod亲和性(podAffinity)有两种 1.podaffinity,…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

Razor编程中@Html的方法使用大全
文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...

抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...
学习一下用鸿蒙DevEco Studio HarmonyOS5实现百度地图
在鸿蒙(HarmonyOS5)中集成百度地图,可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API,可以构建跨设备的定位、导航和地图展示功能。 1. 鸿蒙环境准备 开发工具:下载安装 De…...