slam定位练习笔记(一)

  • Post author:
  • Post category:其他


实现上一篇

学习笔记

里面的想法。

首先总结lida_localization里面test_frame_work的整体工作。

1.订阅了三个传感器数据。其中cloud_data提供当前帧的点云,gnss提供t向量,imu提供R矩阵,然后由t和R的数据整合生成transform的4×4矩阵。

2.将订阅到的点云转换为PCL格式,然后使用PCL库函数处理,其中主要是使用transform矩阵进行位置变换,然后在将点云转换为ROS格式,然后再发布出去。

3.利用前面得到的transform矩阵,发布odometry。

主要是这个流程,现在有之前跟这李太白大佬学习二维slam建图的数据lesson1.bag。看了一下包的数据格式:

发布的话题有/laser_scan和/odom所有想着实现和上面一样的效果,每一帧点云都在转换在里程计上面。

具体流程:

1.订阅/laser_scan,并将这个转换为PCL格式。

2.订阅/odom,并获得transform。

3.利用PCL库函数将PCL格式的点云带上transform变换,然后转换回ROS格式并发布,再发布odom。

首先要创建工作空间,确定相关的依赖。

mkdir -p node1/src
cd node1/src
catkin_init_workspace

再确定相关的依赖,直接看lida_localization里面package.xml文件内容:

  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>roscpp</build_depend>
  <build_depend>rospy</build_depend>
  <build_export_depend>roscpp</build_export_depend>
  <build_export_depend>rospy</build_export_depend>
  <exec_depend>roscpp</exec_depend>
  <exec_depend>rospy</exec_depend>

  <depend>sensor_msgs</depend>
  <depend>pcl_ros</depend>
  <depend>std_msgs</depend>
  <depend>geometry_msgs</depend>
  <depend>tf</depend>
  <depend>eigen_conversions</depend>

依赖包有roscpp、rospy、sensor_msgs、pcl_ros、std_msgs、geometry_msgs、tf、eigen_conversions、nav_msgs。直接照抄就可以了。

一、设置PCL点云数据格式

这里直接参考lidar_localization里面cloud的数据格式。

cloud_data.hpp

#ifndef FRAME_EXERCISE_SENSOR_DATA_CLOUD_DATA_HPP_
#define FRAME_EXERCISE_SENSOR_DATA_CLOUD_DATA_HPP_

#include <pcl/point_types.h>
#include <pcl/point_cloud.h>

namespace frame_exercise
{
    class CloudData 
    {
    public:
        using POINT = pcl::PointXYZ;
        using CLOUD = pcl::PointCloud<POINT>;
        using CLOUD_PTR = CLOUD::Ptr;

    public:
        CloudData()
        :cloud_ptr(new CLOUD()) {
        }

    public:
        double time = 0.0;
        CLOUD_PTR cloud_ptr;
    };    
}

注意这里同时也要设置好cmake文件里面关于pcl的设置

find_package(PCL 1.7 REQUIRED)
list(REMOVE_ITEM PCL_LIBRARIES "vtkproj4")

include_directories(${PCL_INCLUDE_DIRS})
list(APPEND ALL_TARGET_LIBRARIES ${PCL_LIBRARIES})

二、订阅bag包发送的/laser_scan

和lidar_localization里面的处理方式一样,写单独的类,并用构造函数来订阅topic。

这里有一点点需要变换,在lidar_localization里面

它订阅的是

sensor_msgs::PointCloud2::ConstPtr

数据类型,

而这里订阅的是

sensor_msgs/LaserScan

数据类型。所以需要修改。

cloud_subscriber.hpp

#ifndef FRAME_EXERCISE_SUBSCRIBER_CLOUD_SUBSCRIBER_HPP_
#define FRAME_EXERCISE_SUBSCRIBER_CLOUD_SUBSCRIBER_HPP_

#include <deque>

#include <ros/ros.h>
#include <sensor_msgs/PointCloud2.h>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl_conversions/pcl_conversions.h>

#include "frame_exercise/sensor_data/cloud_data.hpp"

namespace frame_exercise
{
    class CloudSubscriber {
    public:
        CloudSubscriber(ros::NodeHandle& nh, std::string topic_name, size_t buff_size);
        CloudSubscriber() = default;
        void ParseData(std::deque<CloudData>& deque_cloud_data);

    private:
        void msg_callback(const sensor_msgs::LaserScan::ConstPtr& cloud_msg_ptr);

    private:
        ros::NodeHandle nh_;
        ros::Subscriber subscriber_;

        std::deque<CloudData> new_cloud_data_;
    };    
} // namespace frame_exercise

将格式改变后,突然发现不能借助pcl库的函数进行转换了,得自己来写。。。

这里参靠李太白大佬的

文章

,来写将laserscan转化为pointcloud2格式。

比较简单,将参数格式一改,查询一下相关的rosmsg show就好了

将LaserScan的格式转换为PointCloud2的格式。

cloud_subscriber.cpp:

#include "frame_exercise/subscriber/cloud_subscriber.hpp"


namespace frame_exercise
{
CloudSubscriber::CloudSubscriber(ros::NodeHandle& nh, std::string topic_name, size_t buff_size)
    :nh_(nh) {
    subscriber_ = nh_.subscribe(topic_name, buff_size, &CloudSubscriber::msg_callback, this);
}

void CloudSubscriber::msg_callback(const sensor_msgs::LaserScan::ConstPtr& scan_msg) {
    CloudData cloud_data;
    cloud_data->cloud_ptr->points.resize(scan_msg->ranges.size());

    cloud_data.time = scan_msg->header.stamp.toSec();
    // pcl::fromROSMsg(*cloud_msg_ptr, *(cloud_data.cloud_ptr));
    // 这里不能使用这个函数,得自己手写数据转换方式
    for(unsigned int i = 0; i < scan_msg->ranges.size(); ++i )
    {
        pcl::PointXYZ & point_tmp = cloud_data->cloud_ptr->points[i];
        float range = scan_msg->ranges[i]; 

        if (!std::isfinite(range))
        {
            // std::cout << " " << i << " " << scan_msg->ranges[i];
            point_tmp = invalid_point_;
            continue;
        }
        if (range > scan_msg->range_min && range < scan_msg->range_max)
        {
            // 获取第i个点对应的角度
            float angle = scan_msg->angle_min + i * scan_msg->angle_increment;
            // 获取第i个点在笛卡尔坐标系下的坐标
            point_tmp.x = range * cos(angle);
            point_tmp.y = range * sin(angle);
            point_tmp.z = 0.0;
        }
        else
        {
            // 无效点
            point_tmp = invalid_point_;
        }
        cloud_data->cloud_ptr->width = scan_msg->ranges.size();
        cloud_data->cloud_ptr->height = 1;
        cloud_data->cloud_ptr->is_dense = false;    // contains nans

        cloud_data->cloud_ptr->header.seq = scan_msg->header->seq;
        cloud_data->cloud_ptr->header.frame_id = scan_msg->header->frame_id;

    }

    new_cloud_data_.push_back(cloud_data);
}

void CloudSubscriber::ParseData(std::deque<CloudData>& cloud_data_buff) {
    if (new_cloud_data_.size() > 0) {
        cloud_data_buff.insert(cloud_data_buff.end(), new_cloud_data_.begin(), new_cloud_data_.end());
        new_cloud_data_.clear();
    }
}
} // namespace frame_exercise

三、订阅bag包发送的/odom

查看Odometry的数据格式

思考:在订阅odometry的时候要不要也像cloud_subscriber一样添加一个专门的clouddata的格式,应该不用,我只需要提取每一帧的odom中的transform就行了,不需要对他进行专门的数据处理。

主要用到了四元数与旋转矩阵之间的转换,这篇

文章

写的很好,参考这篇文章提到的函数。

odom_subscriber.hpp

#ifndef FRAME_EXERCISE_SUBSCRIBER_CLOUD_SUBSCRIBER_HPP_
#define FRAME_EXERCISE_SUBSCRIBER_CLOUD_SUBSCRIBER_HPP_

// 订阅到odom得到transform矩阵。
#include <deque>

#include <ros/ros.h>
#include <nav_msgs/Odometry.h>
#include <Eigen/Dense>

namespace frame_exercise
{
class OdomSubscriber{
    public:
        OdomSubscriber(ros::NodeHandle& nh_, std::string topic_name, std::size_t buff_size);
        std::deque<Eigen::Matrix4f> odom_transform;
    private:
        void msg_callback(const nav_msgs::Odometry::ConstPtr& odom_msg);
    private:
        ros::NodeHandle nh_;
        ros::Subscriber subscriber_;

};
} // namespace frame_exercise


特地写了一个用于存放odomtransform的队列。

OdomSubscriber.cpp

#include "frame_exercise/subscriber/odom_subscriber.hpp"

namespace frame_exercise
{
    OdomSubscriber::OdomSubscriber(ros::NodeHandle& nh, std::string topic_name, size_t buff_size):
        nh_(nh){
            subscriber_ = nh_.subscriber(topic_name, buff_size, &OdomSubscriber::msg_callback, this);
        }
    void OdomSubscriber::msg_callback(const nav_msgs::Odometry::ConstPtr& odom_msg)
    {
        Eigen::Maxtrix4f transform;
        // t向量
        transform(0, 3) = odom_msg.pose.pose.position.x;
        transform(1, 3) = odom_msg.pose.pose.position.y;
        transform(2, 3) = odom_msg.pose.pose.position.z;
        // R旋转矩阵
        Eigen::Quaternionf q;
        q.x() = odom_msg.pose.pose.orientation.x;
        q.y() = odom_msg.pose.pose.orientation.y;
        q.z() = odom_msg.pose.pose.orientation.z;
        transform.block<3,3>(0,0) = q.matrix();
        odom_transfrom.push_back(transform);
    }
} // namespace frame_exercise

感觉应该没一个函数都要测试一下能不能正常使用,达到我想要的效果,不能这样一股闹的全部写完再测试。

四、发布环节

主要应用到函数:

pcl::transformPointCloud(*cloud_data.cloud_ptr, *cloud_data.cloud_ptr, odometry_matrix);

现在的问题是odometry_matrix这个已经存在了,但不知道怎么提取出来。

还是采用了lidar_localization的框架,在里面单独使用一个队列,然后在callback函数里面处理这个队列。

里面遇到了很多坑,都是我对于pcl没有特别理解的后果。。。

a.回调函数里面关于引用的处理要带上*。

b.关于头文件相互包含的问题。(这个错误卡了我一个下午,最后发现是宏定义出了问题,我当时直接复制了cloud_subscriber.hpp的宏定义!!!)

直接将发布环节写在frame_exercise.cpp里面了:

#include "ros/ros.h"
#include "pcl/common/transforms.h"
#include <pcl_ros/point_cloud.h>
#include <sensor_msgs/PointCloud2.h>
#include <nav_msgs/Odometry.h>

#include "frame_exercise/subscriber/cloud_subscriber.hpp"
#include "frame_exercise/subscriber/odom_subscriber.hpp"

using namespace frame_exercise;

int main(int argc, char** argv)
{
    ros::init(argc, argv, "test_frame");
    ros::NodeHandle nh;
    ros::Publisher pub_odom;
    ros::Publisher pub_cloud;

    // pub_odom = nh.advertise<nav_msgs::Odometry>("/odom1", 10);
    pub_cloud = nh.advertise<sensor_msgs::PointCloud2>("/cloud1", 10);

    std::shared_ptr<CloudSubscriber> cloud_sub_ptr = std::make_shared<CloudSubscriber>(nh, "/laser_scan",  1000);
    std::shared_ptr<OdomSubscriber> odom_sub_ptr = std::make_shared<OdomSubscriber>(nh, "/odom", 1000);
    

    std::deque<CloudData> cloud_data_buff;
    std::deque<Eigen::Matrix4f> transform_buff;

    ros::Rate rate(100);

    while(ros::ok())
    {
        ros::spinOnce();
        cloud_sub_ptr->ParseData(cloud_data_buff);
        odom_sub_ptr->ParseData(transform_buff);

        while(cloud_data_buff.size() > 0 && transform_buff.size())
        {
            CloudData cloud_data = cloud_data_buff.front();
            Eigen::Matrix4f odometry_matrix = transform_buff.front();

            cloud_data_buff.pop_front();
            transform_buff.pop_front();

            pcl::transformPointCloud(*cloud_data.cloud_ptr, *cloud_data.cloud_ptr, odometry_matrix);
            // 如果想要发布odom的话,还需要定义一个nav_msgs::Odometry对象,太复杂了,先不写了。
            pub_cloud.publish(cloud_data.cloud_ptr);

        }
        rate.sleep();
    }
    return 0;
}

最开始编译的时候 会报这个错误,想了很久也不知道是哪里的问题,最后是看了这篇

文章

解决。

然后就可以成功编译。

但显示明明生成了可执行文件,但还是rosrun的时候报错说没有。

我在devel的lib中没有发现,但在build里面的frame_exercise文件里面找到了。导致rosrun的时候找不到可执行文件,解决这个问题主要看了这篇

文章

然后发现了很多bug,暂时解决不了。先列举一下发现的bug:

1.四元数是有w的。

2.发布需要变成rosmsg格式的,要改进。

3.pcl::transformPointCloud(*cloud_data.cloud_ptr, *cloud_data.cloud_ptr, odometry_matrix);运行到这里就会报错,不知道是哪里的问题。

先挖个坑,以后在解决这些问题。


实践出真知



版权声明:本文为weixin_45572737原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。