5.【自动驾驶与机器人中的SLAM技术】2D点云的scan matching算法 和 检测退化场景的思路

目录

  • 1. 基于优化的点到点/线的配准
  • 2. 对似然场图像进行插值,提高匹配精度
  • 3. 对二维激光点云中会对SLAM功能产生退化场景的检测
  • 4. 在诸如扫地机器人等这样基于2D激光雷达导航的机器人,如何处理悬空/低矮物体
  • 5. 也欢迎大家来我的读书号--过千帆,学习交流。

在这里插入图片描述

1. 基于优化的点到点/线的配准

这里实现了基于g2o优化器的优化方法。
图优化中涉及两个概念-顶点和边。我们的优化变量认为是顶点,误差项就是边。我们通过g2o声明一个图模型,然后往图模型中添加顶点和与顶点相关联的边,再选定优化算法(比如LM)就可以进行优化了。想熟悉g2o的小伙伴们感兴趣的话,可以到这个链接看一下。
g2o的基本框架和编程套路如下图:
在这里插入图片描述
在这里插入图片描述
基于g2o的点对点的配准算法代码实现如下:

bool Icp2d::AlignWithG2oP2P(SE2& init_pose)
{double rk_delta = 0.8;    // 核函数阈值float max_dis2 = 0.01;    // 最近邻时的最远距离(平方)int min_effect_pts = 20;  // 最小有效点数// 构建图优化,先设定g2otypedef g2o::BlockSolver<g2o::BlockSolverTraits<3, 2>> BlockSolverType;  // 每个误差项优化变量维度为3, 误差值维度是2typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType; // 线性求解器类型// 梯度下降方法,可以从GN, LM, DogLeg 中选auto solver = new g2o::OptimizationAlgorithmLevenberg(g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));g2o::SparseOptimizer optimizer;   // 图模型optimizer.setAlgorithm(solver);   // 设置求解器optimizer.setVerbose(false);       // 打开调试输出// 往图中添加顶点VertexSE2 *v = new VertexSE2(); // 新建SE2位姿顶点v->setId(0);                    // 设置顶点的idv->setEstimate(init_pose);      // 设置顶点的估计值为初始位姿optimizer.addVertex(v);         // 将顶点添加到优化器中// 往图中添加边int effective_num = 0;  // 有效点数// 遍历sourcefor (size_t i = 0; i < source_scan_->ranges.size(); ++i) {float range = source_scan_->ranges[i];if (range < source_scan_->range_min || range > source_scan_->range_max) {continue;}float angle = source_scan_->angle_min + i * source_scan_->angle_increment;float theta = v->estimate().so2().log();Vec2d pw = v->estimate() * Vec2d(range * std::cos(angle), range * std::sin(angle));Point2d pt;pt.x = pw.x();pt.y = pw.y();// 最近邻std::vector<int> nn_idx;std::vector<float> dis;kdtree_.nearestKSearch(pt, 1, nn_idx, dis);if (nn_idx.size() > 0 && dis[0] < max_dis2) {effective_num++;Vec2d qw = Vec2d(target_cloud_->points[nn_idx[0]].x, target_cloud_->points[nn_idx[0]].y);   // 当前激光点在目标点云中的最近邻点坐标auto *edge = new EdgeIcpP2P(range, angle, theta);   // 构建约束边edge->setVertex(0, v);                   // 设置连接的顶点edge->setMeasurement(qw);                // 观测,即最近邻edge->setInformation(Mat2d::Identity()); // 观测为2维点坐标,因此信息矩阵需设为2x2单位矩阵auto rk = new g2o::RobustKernelHuber;    // Huber鲁棒核函数rk->setDelta(rk_delta);                  // 设置阈值edge->setRobustKernel(rk);               // 为边设置鲁棒核函数optimizer.addEdge(edge);                 // 将约束边添加到优化器中}}if (effective_num < min_effect_pts) {return false;}// 执行优化optimizer.initializeOptimization(); // 初始化优化器optimizer.optimize(10);             // 优化init_pose = v->estimate();LOG(INFO) << "g2o estimated pose: " << v->estimate().translation().transpose() << ", theta: " << v->estimate().so2().log();return true;
}

效果展示
在这里插入图片描述
基于g2o的点对线的配准算法代码实现如下:

bool Icp2d::AlignWithG2oP2Plane(SE2& init_pose)
{double rk_delta = 0.8;    // 核函数阈值float max_dis2 = 0.01;    // 最近邻时的最远距离(平方)int min_effect_pts = 20;  // 最小有效点数// 构建图优化,先设定g2otypedef g2o::BlockSolver<g2o::BlockSolverTraits<3, 1>> BlockSolverType;  // 每个误差项优化变量维度为3, 误差值维度是1typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType; // 线性求解器类型// 梯度下降方法,可以从GN, LM, DogLeg 中选auto solver = new g2o::OptimizationAlgorithmLevenberg(g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));g2o::SparseOptimizer optimizer;   // 图模型optimizer.setAlgorithm(solver);   // 设置求解器optimizer.setVerbose(true);       // 打开调试输出// 往图中添加顶点VertexSE2 *v = new VertexSE2(); // 新建SE2位姿顶点v->setId(0);                    // 设置顶点的idv->setEstimate(init_pose);      // 设置顶点的估计值为初始位姿optimizer.addVertex(v);         // 将顶点添加到优化器中// 往图中添加边int effective_num = 0;  // 有效点数// 遍历sourcefor (size_t i = 0; i < source_scan_->ranges.size(); ++i) {float range = source_scan_->ranges[i];if (range < source_scan_->range_min || range > source_scan_->range_max) {continue;}float angle = source_scan_->angle_min + i * source_scan_->angle_increment;float theta = v->estimate().so2().log();Vec2d pw = v->estimate() * Vec2d(range * std::cos(angle), range * std::sin(angle));Point2d pt;pt.x = pw.x();pt.y = pw.y();// 查找5个最近邻std::vector<int> nn_idx;std::vector<float> dis;kdtree_.nearestKSearch(pt, 5, nn_idx, dis);std::vector<Vec2d> effective_pts;  // 有效点for (int j = 0; j < nn_idx.size(); ++j) {if (dis[j] < max_dis2) {effective_pts.emplace_back(Vec2d(target_cloud_->points[nn_idx[j]].x, target_cloud_->points[nn_idx[j]].y));}}if (effective_pts.size() < 3) {continue;}// 拟合直线,组装J、H和误差Vec3d line_params;if (math::FitLine2D(effective_pts, line_params)) {effective_num++;auto *edge = new EdgeIcpP2Plane(range, angle, theta, line_params);   // 构建约束边edge->setVertex(0, v);                   // 设置连接的顶点edge->setInformation(Eigen::Matrix<double, 1, 1>::Identity()); // 误差为1维auto rk = new g2o::RobustKernelHuber;    // Huber鲁棒核函数rk->setDelta(rk_delta);                  // 设置阈值edge->setRobustKernel(rk);               // 为边设置鲁棒核函数optimizer.addEdge(edge);                 // 将约束边添加到优化器中}}if (effective_num < min_effect_pts){return false;}// 执行优化optimizer.initializeOptimization(); // 初始化优化器optimizer.optimize(10);             // 优化10次init_pose = v->estimate();LOG(INFO) << "g2o estimated pose: " << v->estimate().translation().transpose() << ", theta: " << v->estimate().so2().log();return true;
}

其中,直线拟合的代码为: 其中,直线拟合的代码为: 其中,直线拟合的代码为:

template <typename S>
bool FitLine2D(const std::vector<Eigen::Matrix<S, 2, 1>>& data, Eigen::Matrix<S, 3, 1>& coeffs) {if (data.size() < 2) {return false;}Eigen::MatrixXd A(data.size(), 3);for (int i = 0; i < data.size(); ++i) {A.row(i).head<2>() = data[i].transpose();A.row(i)[2] = 1.0;}Eigen::JacobiSVD svd(A, Eigen::ComputeThinV);coeffs = svd.matrixV().col(2);return true;
}

效果展示
在这里插入图片描述

2. 对似然场图像进行插值,提高匹配精度

高博书中分别实现了基于g2o和手写高斯牛顿两种方法,其中手写高斯牛顿法中没有使用插值,在g2o方法中用到了插值。这里直接将其挪用到手写高斯牛顿法中。所以这里不做赘述,只来介绍一下双线性插值的过程。

// bilinear interpolation
template <typename T>
inline float GetPixelValue(const cv::Mat& img, float x, float y) {// boundary checkif (x < 0) x = 0;if (y < 0) y = 0;if (x >= img.cols) x = img.cols - 1;if (y >= img.rows) y = img.rows - 1;const T* data = &img.at<T>(floor(y), floor(x));float xx = x - floor(x);float yy = y - floor(y);return float((1 - xx) * (1 - yy) * data[0] + xx * (1 - yy) * data[1] + (1 - xx) * yy * data[img.step / sizeof(T)] +xx * yy * data[img.step / sizeof(T) + 1]);
}

双线性插值的过程如图:
在这里插入图片描述

对应到程序中:
a = y y ; a=yy; a=yy;
b = x x ; b=xx; b=xx;
v 1 = d a t a [ 0 ] ; v1=data[0]; v1=data[0];
v 2 = d a t a [ 1 ] ; v2=data[1]; v2=data[1];
v 3 = d a t a [ i m g . s t e p / s i z e o f ( T ) ] ; v3=data[img.step / sizeof(T)]; v3=data[img.step/sizeof(T)];
v 4 = d a t a [ i m g . s t e p / s i z e o f ( T ) + 1 ] ; v4=data[img.step / sizeof(T) + 1]; v4=data[img.step/sizeof(T)+1];
===================================>可得:
v 7 = f l o a t ( ( 1 − x x ) ∗ ( 1 − y y ) ∗ d a t a [ 0 ] + x x ∗ ( 1 − y y ) ∗ d a t a [ 1 ] + ( 1 − x x ) ∗ y y ∗ d a t a [ i m g . s t e p / s i z e o f ( T ) ] + x x ∗ y y ∗ d a t a [ i m g . s t e p / s i z e o f ( T ) + 1 ] ) ; v7=float((1 - xx) * (1 - yy) * data[0] + xx * (1 - yy) * data[1] + (1 - xx) * yy * data[img.step / sizeof(T)] + xx * yy * data[img.step / sizeof(T) + 1]); v7=float((1xx)(1yy)data[0]+xx(1yy)data[1]+(1xx)yydata[img.step/sizeof(T)]+xxyydata[img.step/sizeof(T)+1]);

效果展示
在这里插入图片描述

3. 对二维激光点云中会对SLAM功能产生退化场景的检测

视频中高博讲到对于2DSLAM来说,可以对点云进行直线拟合,比较场景中直线方向是否“大体一致”。
具体思路:可以对场景中的点云进行聚类,然后对每个类簇中的点进行直线拟合,保存所有直线的系数。参考提示中的条件判断实现该功能。

在这里插入图片描述
实现代码如下:

bool Icp2d::IsDegeneration()
{if (target_cloud_->empty()) {LOG(ERROR) << "cannot load cloud...";return false;}int point_size = target_cloud_->size();if(point_size < 500) return true; // 点数太少,空旷退化场景LOG(INFO) << "traget_cloud size = " << point_size;PointCloudType::Ptr target_cloud(new PointCloudType());// 构建点云for (size_t i = 0; i < target_scan_->ranges.size(); ++i){if (target_scan_->ranges[i] < target_scan_->range_min || target_scan_->ranges[i] > target_scan_->range_max) {continue;}double real_angle = target_scan_->angle_min + i * target_scan_->angle_increment;PointType p;p.x = target_scan_->ranges[i] * std::cos(real_angle);p.y = target_scan_->ranges[i] * std::sin(real_angle);p.z = 1.0;target_cloud->points.push_back(p);}pcl::search::KdTree<PointType>::Ptr kdtree; // (new pcl::search::KdTree<PointType>)kdtree = boost::make_shared<pcl::search::KdTree<PointType>>();// 构建kdtreekdtree->setInputCloud(target_cloud);pcl::EuclideanClusterExtraction<PointType> clusterExtractor;// 创建一个向量来存储聚类的结果std::vector<pcl::PointIndices> cluster_indices;clusterExtractor.setClusterTolerance(0.02);        // 设置聚类的距离阈值clusterExtractor.setMinClusterSize(10);            // 设置聚类的最小点数clusterExtractor.setMaxClusterSize(1000);          // 设置聚类的最大点数 clusterExtractor.setSearchMethod(kdtree);         // 使用kdtree树进行加速clusterExtractor.setInputCloud(target_cloud);             // 设置点云聚类对象的输入点云数据clusterExtractor.extract(cluster_indices);         // 执行点云聚类LOG(INFO) << "cluster size: " << cluster_indices.size();// 创建可视化对象pcl::visualization::PCLVisualizer viewer("Cluster Viewer");viewer.setBackgroundColor(1.0, 1.0, 1.0);viewer.addPointCloud<PointType>(target_cloud, "cloud");int clusterNumber = 0;  // 输出聚类结果std::vector<Eigen::Vector3d> line_coeffs_vector;for (const auto& indices : cluster_indices) {LOG(INFO) << "Cluster " << clusterNumber << " has " << indices.indices.size() << " points.";pcl::PointCloud<PointType>::Ptr cluster(new pcl::PointCloud<PointType>);pcl::copyPointCloud(*target_cloud, indices, *cluster);// 拟合直线std::vector<Eigen::Vector2d> pts;pts.reserve(cluster->size());for (const PointType& pt : *cluster){pts.emplace_back(Eigen::Vector2d(pt.x, pt.y));}// 拟合直线,组装J、H和误差Eigen::Vector3d line_coeffs;// 利用当前点附近的几个有效近邻点,基于SVD奇异值分解,拟合出ax+by+c=0 中的最小直线系数 a,b,c,对应公式(6.11)if (math::FitLine2D(pts, line_coeffs)) {line_coeffs_vector.push_back(line_coeffs);}// 为聚类分配不同的颜色double r = static_cast<double>(rand()) / RAND_MAX;double g = static_cast<double>(rand()) / RAND_MAX;double b = static_cast<double>(rand()) / RAND_MAX;// 将聚类点云添加到可视化对象中std::string clusterId = "cluster_" + std::to_string(clusterNumber);viewer.addPointCloud<PointType>(cluster, clusterId);viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, r, g, b, clusterId);clusterNumber++;}int lines_size = line_coeffs_vector.size();LOG(INFO) << "clusterNumber = " << clusterNumber << " lines_size = " << lines_size;if(clusterNumber == lines_size){if(clusterNumber == 1) return true; // 如果仅有一条直线,就是退化场景float max_score = 0;for(int i = 0; i < lines_size - 1; ++i){for(int j = i+1; j < lines_size; ++j){float current_score = line_coeffs_vector[i].cross(line_coeffs_vector[j]).norm();LOG(INFO) << "current_score = " << current_score;max_score = current_score > max_score ? current_score : max_score;}}LOG(INFO) << "mas_score = " << max_score;if(max_score < 0.3) return true; // 叉积小于阈值,代表是退化场景,直线差不多都平行}if (!viewer.wasStopped()) {viewer.spinOnce(0.001);sleep(1);  //延时函数,不加的话刷新太快会看不到显示效果viewer.close();}return false;
}

在这里插入图片描述
上图为聚类结果,下面是程序运行时效果。
在这里插入图片描述
效果展示
在这里插入图片描述

4. 在诸如扫地机器人等这样基于2D激光雷达导航的机器人,如何处理悬空/低矮物体

在这里插入图片描述
在二维SLAM方案中,如果场景中存在其他高度的障碍物或物体形状随着高度变化的情况,可以采取一些方法来处理悬空物体和低矮物体,以避免机器人与它们发生碰撞。以下是几种思路:

①可以给机器人添加三维传感器,比如RGB-D相机,将相机构建的三维地图点投影到激光雷达构建的二维栅格地图中,使得二维地图中包含高度信息。具体做法如文献[1]孙健, 刘隆辉, 李智, 杨佳玉, & 申奥. (2022). 基于rgb-d相机和激光雷达的传感器数据融合算法. 湖南工程学院学报:自然科学版, 32(1), 7.中描述的“首先将RGB-D相机获取的深度图像转换为三维点云,剔除地面点云后进行投影得到模拟2D激光数据,然后与二维激光雷达数据进行点云配准,以获取两个传感器之间的位姿关系,最后通过扩展卡尔曼滤波(EKF)将两组激光数据进行融合.实验结果表明该方法能综合利用相机和激光雷达传感器优势,有效提高机器人环境感知的完整性和建图精度. ”
②由于2D SLAM生成的占据栅格地图,是基于像素的黑白灰三色地图,我们可以人工对此地图进行“加工”,对于特定场景中存在的要躲避的三维物体预先建模,在二维栅格地图中标注出他们的位置以及高度信息,来帮助机器人更好的躲避他们。
③尝试使用普通相机对环境中的物体进行语义识别,然后把需要躲避的三维物体投影到二维平面,“告诉”机器人前面有个“物体”阻挡,不具备通过的条件。

5. 也欢迎大家来我的读书号–过千帆,学习交流。

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

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

相关文章

Android wifi 框架以及Enable流程

Android P相比于Android O的变化 多了WifiStateMachinePrime&#xff08;状态机的前处理机制&#xff09;&#xff0c;wifiService的相关cmd 不再是直接send 给WifiStateMachine&#xff0c;而是被送到WifiStateMachinePrime先进行处理后&#xff0c;再送往WifiStateMachine也…

Java微信支付对帐,微信账单下载并读取到实体Bean,并保存至数据库

最近公司的项目需要微信对帐功能&#xff0c;这里展示了简单的微信账单下载并读取到数据库方法&#xff0c;有问题或者更好的想法的可以在评论区交流哟。 一、依赖 <!-- 微信支付 --> <dependency><groupId>com.github.wechatpay-apiv3</groupId><…

全网最新最全的自动化测试教程:python+pytest接口自动化-测试函数、测试类/测试方法的封装

前言 在pythonpytest 接口自动化系列中&#xff0c;我们之前的文章基本都没有将代码进行封装&#xff0c;但实际编写自动化测试脚本中&#xff0c;我们都需要将测试代码进行封装&#xff0c;才能被测试框架识别执行。 例如单个接口的请求代码如下&#xff1a; import reques…

深入理解JVM虚拟机第二十七篇:详解JVM当中InvokeDynamic字节码指令,Java是动态类型语言么?

😉😉 学习交流群: ✅✅1:这是孙哥suns给大家的福利! ✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 🥭🥭3:QQ群:583783824 📚📚 工作微信:BigTreeJava 拉你进微信群,免费领取! 🍎🍎4:本文章内容出自上述:Sp…

YOLO5Face算法解读

论文&#xff1a;YOLO5Face: Why Reinventing a Face Detector 链接&#xff1a;https://arxiv.org/abs/2105.12931v1 机构&#xff1a;深圳神目科技&LinkSprite Technologies&#xff08;美国&#xff09; 开源代码&#xff1a;https://github.com/deepcam-cn/yolov5-face…

如何定位当生产环境CPU飙升的时候的问题

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、排查思路 二、预防CPU飙升 三、总结 前言 在当今的信息化时代&#xff0c;计算机系统在各行各业都发挥着重要的作用。然而&a…

DeepStream--测试PCB-Defect-Detection

GitHub - clintonoduor/PCB-Defect-Detection-using-Deepstream: PCB defect detection using deepstream & YoloV5我参考了了这个代码&#xff0c;作者基于YoloV5&#xff0c;训练一个电路板检测的模型&#xff0c;训练数据集来自https://robotics.pkusz.edu.cn/resources…

BearPi Std 板从入门到放弃 - 后天篇(1)(I2C1 读取 光照强度)

简介 基于 BearPi Std 板从入门到放弃 - 引气入体篇&#xff08;5&#xff09;(printf打印到串口), 通过I2C接口&#xff0c;读取光照强度并打印到串口; 开发板 &#xff1a; Bearpi Std(小熊派标准板) 主芯片: STM32L431RCT6 LED : PC13 \ 推挽输出即可 \ 高电平点亮 串口: U…

SpringBoot整合RocketMQ

SpringBoot整合RocketMQ 文章目录 SpringBoot整合RocketMQ下载安装SpringBoot整合RocketMQ导坐标改配置实现消息生产与消费 下载安装 教程地址&#xff1a;https://www.bilibili.com/video/BV15b4y1a7yG/?p132&spm_id_from333.1007.top_right_bar_window_history.content.…

11. 哈希冲突

上一节提到&#xff0c;通常情况下哈希函数的输入空间远大于输出空间&#xff0c;因此理论上哈希冲突是不可避免的。比如&#xff0c;输入空间为全体整数&#xff0c;输出空间为数组容量大小&#xff0c;则必然有多个整数映射至同一桶索引。 哈希冲突会导致查询结果错误&#…

大数据技术学习笔记(四)—— HDFS

目录 1 HDFS 概述1.1 HDFS 背景与定义1.2 HDFS 优缺点1.3 HDFS 组成架构1.4 HDFS 文件块大小 2 HDFS的shell操作2.1 上传2.2 下载2.3 HDFS直接操作 3 HDFS的客户端操作3.1 Windows 环境准备3.2 获取 HDFS 的客户端连接对象3.3 HDFS文件上传3.4 HDFS文件下载3.5 HDFS删除文件和目…

最强AI之风袭来,你爱了吗?

2017年&#xff0c;柯洁同阿尔法狗人机大战&#xff0c;AlphaGo以3比0大获全胜&#xff0c;一代英才泪洒当场...... 2019年&#xff0c;换脸哥视频“杨幂换朱茵”轰动全网&#xff0c;时至今日AI换脸仍热度只增不减&#xff1b; 2022年&#xff0c;ChatGPT一经发布便轰动全球&a…

【hacker送书第8期】Java从入门到精通(第7版)

第8期图书推荐 内容简介编辑推荐作者简介图书目录参与方式 内容简介 《Java从入门到精通&#xff08;第7版&#xff09;》从初学者角度出发&#xff0c;通过通俗易懂的语言、丰富多彩的实例&#xff0c;详细讲解了使用Java语言进行程序开发需要掌握的知识。全书分为4篇共24章&a…

nginx对多个服务器的高可用,容易出现鉴权失败

高可用简单测试正常&#xff0c;但是出现高概率401鉴权错误 抓包发现&#xff0c;确实是401 &#xff0c; 而鉴权是两次交互&#xff1a; 抓包发现鉴权到不同服务器上了&#xff0c;导致鉴权没有完成。 此时就需要我们的ip_hash,把同一IP地址的请求,都分配给同一台后端服务器&…

【UE5】使用场系统炸毁一堵墙

效果 步骤 1. 新建一个空白项目 2. 新建一个Basic关卡&#xff0c;然后添加一个第三人称游戏和初学者内容包到内容浏览器 3. 在场景中添加一堵墙 4. 选项模式选择“破裂” 点击新建 新建一个文件夹用于存储几何体集 点击“统一” 最小和最大Voronoi点数都设置为100 点击“破…

cmd查看进程信息 终止进程

cmd查看进程信息 终止进程 1、cmd查看进程信息2、终止进程 1、cmd查看进程信息 tasklist命令 描述: 该工具显示在本地或远程机器上当前运行的进程列表。 tasklist /?查看本机所有进程列表 tasklist /V根据进程名 查看jmeter进程 tasklist /V |findstr /i jmeter2、终止进程…

1+x网络系统建设与运维(中级)-练习3

一.设备命名 AR1 [Huawei]sysn AR1 [AR1] 同理可得&#xff0c;所有设备的命名如上图所示 二.VLAN LSW1 [LSW1]vlan 10 [LSW1-vlan10]q [LSW1]int g0/0/1 [LSW1-GigabitEthernet0/0/1]port link-type access [LSW1-GigabitEthernet0/0/1]port default vlan 10 [LSW1-GigabitEt…

避免20种常见Selenium自动化测试异常,让你的测试更加稳定和可靠!

常见的Selenium异常 以下是所有Selenium WebDriver代码中可能发生的一些常见Selenium异常。 1、ElementClickInterceptedException 由于以某种方式隐藏了接收到click命令的元素&#xff0c;因此无法正确执行Element Click命令。 2、ElementNotInteractableException 即使目…

【Qt开发流程】之事件过滤器及sendEvent和postEvent

描述 事件过滤器(Event Filter)是Qt中一个强大的事件处理机制&#xff0c;它可以在对象接收到事件之前截获事件&#xff0c;并进行自定义处理。事件过滤器可以在不修改对象自身代码的前提下&#xff0c;对其进行事件处理和拦截。 事件过滤器的使用过程如下&#xff1a; 创建一…

C++ 文件操作之配置文件读取

C 文件操作之配置文件读取 在项目应用时常常会涉及一些调参工作&#xff0c;如果项目封装成了.exe或者.dll&#xff0c;那么频繁调参多次编译是一件十分低效的事情&#xff0c;如果代码算法或者逻辑是一定的&#xff0c;那么参数完全可以通过读入配置文件来获取之前在用C - op…