You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 23:43:19