如何修改ROS+OpenCV的PCA代码以适配2D激光雷达/scan话题?
改造ROS PCA代码适配激光雷达/scan话题
关键改造步骤
1. 替换话题订阅逻辑
把原代码中订阅图像话题的部分,改成订阅sensor_msgs/LaserScan类型的/scan话题:
ros::Subscriber scan_sub = nh.subscribe("/scan", 10, scanCallback);
2. 激光扫描数据转笛卡尔坐标
激光雷达返回的是角度+距离的极坐标数据,需要转换成机器人坐标系下的笛卡尔坐标(x,y),同时过滤无效数据(超出量程、无穷大、NaN值):
void scanCallback(const sensor_msgs::LaserScan::ConstPtr& scan_msg) { std::vector<cv::Point2f> points; for (int i = 0; i < scan_msg->ranges.size(); ++i) { float range = scan_msg->ranges[i]; // 过滤无效点 if (range < scan_msg->range_min || range > scan_msg->range_max || std::isinf(range) || std::isnan(range)) { continue; } // 计算当前点对应的角度 float angle = scan_msg->angle_min + i * scan_msg->angle_increment; // 极坐标转笛卡尔坐标 float x = range * cos(angle); float y = range * sin(angle); points.push_back(cv::Point2f(x, y)); } // 后续PCA处理逻辑... }
3. 构造PCA输入矩阵
将过滤后的点集转换成OpenCV的cv::Mat格式(每行存储一个点的x、y坐标),匹配原图像PCA代码的输入要求:
cv::Mat points_mat(points.size(), 2, CV_32F); for (int i = 0; i < points.size(); ++i) { points_mat.at<float>(i, 0) = points[i].x; points_mat.at<float>(i, 1) = points[i].y; }
4. 复用PCA计算逻辑
这部分和原OpenCV PCA代码基本一致,直接计算主成分、均值点和主方向:
// 执行PCA分析 cv::PCA pca(points_mat, cv::Mat(), cv::PCA::DATA_AS_ROW); // 提取均值点(对应走廊中心区域) cv::Point2f mean_point(pca.mean.at<float>(0), pca.mean.at<float>(1)); // 提取主成分方向(对应走廊墙壁的垂直方向,即机器人前进参考方向) cv::Point2f main_dir(pca.eigenvectors.at<float>(0, 0), pca.eigenvectors.at<float>(0, 1));
5. 结果可视化(可选)
可以通过ROS的visualization_msgs/Marker发布主方向标记,在RVIZ中直观查看分析结果:
visualization_msgs::Marker marker; marker.header.frame_id = scan_msg->header.frame_id; marker.header.stamp = ros::Time::now(); marker.ns = "pca_corridor"; marker.id = 0; marker.type = visualization_msgs::Marker::ARROW; marker.action = visualization_msgs::Marker::ADD; // 设置箭头起点为均值点 marker.pose.position.x = mean_point.x; marker.pose.position.y = mean_point.y; marker.pose.position.z = 0; // 设置箭头朝向主方向 tf2::Quaternion q; q.setRPY(0, 0, atan2(main_dir.y, main_dir.x)); marker.pose.orientation.x = q.x(); marker.pose.orientation.y = q.y(); marker.pose.orientation.z = q.z(); marker.pose.orientation.w = q.w(); // 设置箭头尺寸和颜色 marker.scale.x = 1.5; marker.scale.y = 0.2; marker.scale.z = 0.2; marker.color.r = 1.0f; marker.color.g = 0.0f; marker.color.b = 0.0f; marker.color.a = 1.0; marker_pub.publish(marker);
完整示例代码框架
#include <ros/ros.h> #include <sensor_msgs/LaserScan.h> #include <opencv2/opencv.hpp> #include <visualization_msgs/Marker> #include <tf2/LinearMath/Quaternion.h> ros::Publisher marker_pub; void scanCallback(const sensor_msgs::LaserScan::ConstPtr& scan_msg) { std::vector<cv::Point2f> points; // 1. 激光数据转笛卡尔坐标并过滤无效点 for (int i = 0; i < scan_msg->ranges.size(); ++i) { float range = scan_msg->ranges[i]; if (range < scan_msg->range_min || range > scan_msg->range_max || std::isinf(range) || std::isnan(range)) { continue; } float angle = scan_msg->angle_min + i * scan_msg->angle_increment; float x = range * cos(angle); float y = range * sin(angle); points.push_back(cv::Point2f(x, y)); } if (points.size() < 2) { // 点数量不足无法计算PCA ROS_WARN("Not enough valid laser points for PCA"); return; } // 2. 构造PCA输入矩阵 cv::Mat points_mat(points.size(), 2, CV_32F); for (int i = 0; i < points.size(); ++i) { points_mat.at<float>(i, 0) = points[i].x; points_mat.at<float>(i, 1) = points[i].y; } // 3. 执行PCA分析 cv::PCA pca(points_mat, cv::Mat(), cv::PCA::DATA_AS_ROW); // 4. 提取分析结果 cv::Point2f mean_point(pca.mean.at<float>(0), pca.mean.at<float>(1)); cv::Point2f main_dir(pca.eigenvectors.at<float>(0, 0), pca.eigenvectors.at<float>(0, 1)); ROS_INFO("Corridor center: (%.2f, %.2f)", mean_point.x, mean_point.y); ROS_INFO("Main direction: (%.2f, %.2f)", main_dir.x, main_dir.y); // 5. 发布可视化标记 visualization_msgs::Marker marker; marker.header.frame_id = scan_msg->header.frame_id; marker.header.stamp = ros::Time::now(); marker.ns = "pca_corridor"; marker.id = 0; marker.type = visualization_msgs::Marker::ARROW; marker.action = visualization_msgs::Marker::ADD; marker.pose.position.x = mean_point.x; marker.pose.position.y = mean_point.y; marker.pose.position.z = 0; tf2::Quaternion q; q.setRPY(0, 0, atan2(main_dir.y, main_dir.x)); marker.pose.orientation.x = q.x(); marker.pose.orientation.y = q.y(); marker.pose.orientation.z = q.z(); marker.pose.orientation.w = q.w(); marker.scale.x = 1.5; marker.scale.y = 0.2; marker.scale.z = 0.2; marker.color.r = 1.0f; marker.color.g = 0.0f; marker.color.b = 0.0f; marker.color.a = 1.0; marker_pub.publish(marker); } int main(int argc, char** argv) { ros::init(argc, argv, "laser_pca_corridor_detector"); ros::NodeHandle nh; marker_pub = nh.advertise<visualization_msgs::Marker>("pca_corridor_marker", 10); ros::Subscriber scan_sub = nh.subscribe("/scan", 10, scanCallback); ros::spin(); return 0; }
编译配置(CMakeLists.txt片段)
确保添加必要的依赖项:
find_package(catkin REQUIRED COMPONENTS roscpp sensor_msgs visualization_msgs tf2 ) find_package(OpenCV REQUIRED) include_directories( ${catkin_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ) add_executable(laser_pca_corridor_detector src/laser_pca_corridor_detector.cpp) target_link_libraries(laser_pca_corridor_detector ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} )
内容的提问来源于stack exchange,提问作者Bob9710
相关产品推荐
相关产品推荐

