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,…...
免费的域名要不要?
前言 eu.org的免费域名相比于其他免费域名注册服务,eu.org的域名后缀更加独特。同时,eu.org的域名注册也比较简单,只需要填写一些基本信息,就可以获得自己的免费域名。 博客地址 免费的域名要不要?-雪饼前言 eu.org…...
高通sm7250与765G芯片是什么关系?(一百八十一)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…...
[Python进阶] Python操作MySQL数据库:pymysql
7.7 操作MySQL数据库:pymysql 7.7.1 准备工作(创建mysql数据库) PHPStudy介绍: phpstudy是一款非常有用的PHP开发工具,旨在帮助开发者更加便捷地进行PHP程序的开发与调试。它提供了一个友好的图形用户界面,使得用户能够方便地进…...
Vue3实现带点击外部关闭对应弹出框(可共用一个变量)
首先,假设您在单文件组件(SFC)中使用了Vue3,并且有两个div元素分别通过v-if和v-else来切换显示一个带有.elpopver类的弹出组件。在这种情况下,每个弹出组件应当拥有独立的状态管理(例如:各自的isOpen变量)。…...
可视化试题(一)
1. 从可视化系统设计的角度出发,通常需要根据系统将要完成的任务的类型选择交互技术。按照任务类型分类可以将数据可视化中的交互技术分为选择、( 重新配置 )、重新编码、导航、关联、( 过滤 )、概览和细节等八…...
RHCE 【在openEuler系统中搭建基本论坛(网站)】
目录 网站需求: 准备工作: 1.基于域名[www.openlab.com](http://www.openlab.com)可以访问网站内容为 welcome to openlab!!! 测试: 2.给该公司创建三个子界面分别显示学生信息,教学资料和缴费网站,基于[www.openla…...
20240112让移远mini-PCIE接口的4G模块EC20在Firefly的AIO-3399J开发板的Android11下跑通【DTS部分】
20240112让移远mini-PCIE接口的4G模块EC20在Firefly的AIO-3399J开发板的Android11下跑通【DTS部分】 2024/1/12 16:20 https://blog.csdn.net/u010164190/article/details/79096345 [Android6.0][RK3399] PCIe 接口 4G模块 EC20 调试记录 https://blog.csdn.net/hnjztyx/artic…...
日志采集传输框架之 Flume,将监听端口数据发送至Kafka
1、简介 Flume 是 Cloudera 提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传 输的系统。Flume 基于流式架构,主要有以下几个部分组成。 主要组件介绍: 1)、Flume Agent 是一个 JVM 进程…...
关于Vue前端接口对接的思考
关于Vue前端接口对接的思考 目录概述需求: 设计思路实现思路分析1.vue 组件分类和获取数值的方式2.http 通信方式 分类 如何对接3.vue 组件分类和赋值方式, 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your p…...
【设计模式之美】SOLID 原则之三:里式替换(LSP)跟多态有何区别?如何理解LSP中子类遵守父类的约定
文章目录 一. 如何理解“里式替换原则”?二. 哪些代码明显违背了 LSP?三. 回顾 一. 如何理解“里式替换原则”? 子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。 里氏替换原则…...
余姚做轴承网站/新闻头条最新消息今日头条
使用图(graphs)来表示计算任务 在被称之为会话(Session)的上下文(context)中被执行 使用tensor表示数据 通过变量(Variable)维护状态 使用feed和fetch可以为任意的操作赋值或者从…...
怎么建设一个手机网站/进入百度搜索网站
9月27日,云与大数据安全技术厂商亚信安全与全球性AI及医疗大数据平台零氪科技(LinkDoc)达成战略合作,双方将在医疗行业混合云安全、移动应用安全防护、数据隐私保护、态势感知平台、数据脱敏及攻防演练、渗透测试等方面开展深入合…...
如何做网站店铺的模板/网站怎么提升关键词排名
前言递归是算法中一种非常重要的思想,应用也很广,小到阶乘,再在工作中用到的比如统计文件夹大小,大到 Google 的 PageRank 算法都能看到,也是面试官很喜欢的考点最近看了不少递归的文章,收获不小,不过我发现…...
网站宣传与推广的指导思想/免费二级域名分发平台
题目链接https://code.mi.com/problem/list/view?id3 描述 两个长度超出常规整形变量上限的大数相减,请避免使用各语言内置大数处理库,如 Java.math.BigInteger 等。 输入 有 N 行测试数据,每一行有两个代表整数的字符串 a 和 b…...
wordpress 旅行社/百度教育官网
我们现在说Recursive backtracking: 迷宫的初始状态是墙壁都存在。选择一个开始区域。随机得选择一个没有访问过的邻接区域,并打通与它之间的墙壁。此邻接区域称为当前区域。如果所有周围的区域都是访问过的,则退回上一个区域进行挖据墙壁&am…...
网站和网页的设计原则/百度投诉中心在线申诉
– Start 点击此处观看本系列配套视频 废话少说,直接上代码。 package shangbo.kafka.example9;import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class App {…...