A-LOAM代码注释学习(二)kittiHelper.cpp


一、代码注释

kittiHelper.cpp主要用来把文件格式的KITTI数据集打包成ros格式并发布topic,需要与kitti_helper.launch配合使用。

1.1 kittiHelper.cpp

注意:
此处有一个矩阵运算的bug,已进行标注,可以分别对修改前后生成的bag的信息进行查看。主要查看 /path_gt 和 /odometry_gt 的输出。

#include <iostream>                         //标准的输入输出流头文件
#include <fstream>                          //fstream也就是文件流file stream
#include <iterator>                         //STL标准中的一些迭代器模板类
#include <string>
#include <vector>
#include <opencv2/opencv.hpp>
#include <image_transport/image_transport.h>
#include <opencv2/highgui/highgui.hpp>      //高层GUI图像交互模块
#include <nav_msgs/Odometry.h>              //ROS导航功能包里的里程计
#include <nav_msgs/Path.h>                  //用于显示轨迹
#include <ros/ros.h>
#include <rosbag/bag.h>
#include <geometry_msgs/PoseStamped.h>      //时间、位置和姿态
#include <cv_bridge/cv_bridge.h>            //在ROS图像消息和OpenCV图像之间进行转换的一个功能包
#include <sensor_msgs/image_encodings.h>    //关于图像编码模式的源文件,其中规定了RGB的图像以及深度图的编码模式
#include <eigen3/Eigen/Dense>               //Eigen库
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>   //一些数据类型转换的函数
#include <sensor_msgs/PointCloud2.h>

//定义一个float类型的容器read_lidar_data读取雷达数据
std::vector<float> read_lidar_data(const std::string lidar_data_path)    
{
    //c++中文件读取和写入以流的形式进行
    // 函数原型 ifstream(const char *filename, std::ifstream::openmode mode);
    //ifstream 打开文件,mode模式std::ifstream::in | std::ifstream::binary:以只读方式打开 | 以二进制而非文本格式进行操作
    std::ifstream lidar_data_file(lidar_data_path, std::ifstream::in | std::ifstream::binary);

    //seekg()用于设置在输入流中的位置,seek + get
    //新位置将从由 place 给出的起始位置开始,偏移 offset 个字节。offset 形参是一个 long 类型的整数,而 place 可以是以下 ios 类中的之一
    //起始位置可能是文件的开头、文件的当前位置或文件的末尾,这些地方分别由常量 ios::beg、ios::cur 和 ios::end 表示。
    lidar_data_file.seekg(0, std::ios::end);

    //size_t 一个与机器相关的unsigned类型。统计一下文件有多少float数据
    //tellg() 用于在输入流中获取位置
    const size_t num_elements = lidar_data_file.tellg() / sizeof(float);

    // 文件指针指向文件开始
    lidar_data_file.seekg(0, std::ios::beg);

    std::vector<float> lidar_data_buffer(num_elements);  //定义float类型的容器,雷达数据缓冲(数据量)
    // 读取所有的数据
    //.read()文件读取,有两个参数,从  lidar_data_file读取num_elements*sizeof(float)数量的数据到&lidar_data_buffer[0]里面
    lidar_data_file.read(reinterpret_cast<char*>(&lidar_data_buffer[0]), num_elements*sizeof(float));
    return lidar_data_buffer;  //返回 lidar_data_buffer
}

int main(int argc, char** argv)
{
    ros::init(argc, argv, "kitti_helper");  //node name为kitti_helper
    ros::NodeHandle n("~");                 //命名空间为/node_namespace/node_name ,即 kitti_helper/kitti_helper
    std::string dataset_folder, sequence_number, output_bag_file;
    n.getParam("dataset_folder", dataset_folder);
    n.getParam("sequence_number", sequence_number);
    std::cout << "Reading sequence " << sequence_number << " from " << dataset_folder << '\n';
    
    // 是否将数据集转换为rosbag文件格式,由launch文件设置
    //从参数服务器获取 是否需要生成bag
    bool to_bag;
    n.getParam("to_bag", to_bag);
    if (to_bag)                                                 //如果生成bag
        n.getParam("output_bag_file", output_bag_file);         //获取bag输出文件夹
    int publish_delay;
    n.getParam("publish_delay", publish_delay);                 //定义 一个整型 发布延迟,由launch文件设置
    publish_delay = publish_delay <= 0 ? 1 : publish_delay;     //三目运算符,延迟时间<=0 则设为1

    //创建一个publisher,发布名为/velodyne_points的topic,消息类型为sensor_msgs::PointCloud2,队列长度为2
    ros::Publisher pub_laser_cloud = n.advertise<sensor_msgs::PointCloud2>("/velodyne_points", 2);

    //image_transport发布图像
    image_transport::ImageTransport it(n);         //Image_transport 提供用于为低带宽压缩格式(compressed formats)image传输提供透明支持
    image_transport::Publisher pub_image_left = it.advertise("/image_left", 2);
    image_transport::Publisher pub_image_right = it.advertise("/image_right", 2);
    //常规发布节点 ros::Publisher pub = n.advertise<sensor_msgs::Image>("/image_left", 2);

    //里程计发布,odomGT,包括旋转的四元数和位置坐标向量
    ros::Publisher pubOdomGT = n.advertise<nav_msgs::Odometry> ("/odometry_gt", 5);
    nav_msgs::Odometry odomGT;                      //声明一个里程计信息类型
    // 赋值里程计的坐标系
    odomGT.header.frame_id = "camera_init";         //真值pose的坐标系实际是左相机的坐标系 
    odomGT.child_frame_id = "/ground_truth";        

    //发布轨迹 pathGT
    ros::Publisher pubPathGT = n.advertise<nav_msgs::Path> ("/path_gt", 5);
    nav_msgs::Path pathGT;                          //声明一个路径变量
    pathGT.header.frame_id = "camera_init";         //赋值坐标系

    //读取时间戳文件
    std::string timestamp_path = "sequences/" + sequence_number + "/times.txt";
    std::ifstream timestamp_file(dataset_folder + timestamp_path, std::ifstream::in);

    // 读取ground_truth_path
    std::string ground_truth_path = "results/" + sequence_number + ".txt";
    std::ifstream ground_truth_file(dataset_folder + ground_truth_path, std::ifstream::in);

    //函数原型,void rosbag::Bag::open(const std::__cxx11::string &filename, uint32_t mode = 2U)
    rosbag::Bag bag_out;
    if (to_bag)
        bag_out.open(output_bag_file, rosbag::bagmode::Write);
    
    //camera 相对于lidar的旋转矩阵
    Eigen::Matrix3d R_transform;
    R_transform << 0, 0, 1, -1, 0, 0, 0, -1, 0;                //其实更准确的应该使用KITTI提供的左相机到Lidar的标定参数进行变换
    Eigen::Quaterniond q_transform(R_transform);

    std::string line;                   //声明字符串line
    std::size_t line_num = 0;           //声明读取的行数

    ros::Rate r(10.0 / publish_delay);  //发布频率
    while (std::getline(timestamp_file, line) && ros::ok())     ///读 时间戳 文件 成为一个字符串
    {
        float timestamp = stof(line);                           //stof()将字符串转换为float型

        std::stringstream left_image_path, right_image_path;
        //自动填充'0'使得'0'+line_num凑成6个字符
        left_image_path << dataset_folder << "sequences/" + sequence_number + "/image_0/" << std::setfill('0') << std::setw(6) << line_num << ".png";
        
        //通过opencv读取图片
        //cv::Mat是OpenCV2和OpenCV3中基本的数据类型,函数cv2.imread(filepath,flags)读入一副图片,CV_LOAD_IMAGE_GRAYSCALE,其为将原图转为灰度图片
        cv::Mat left_image = cv::imread(left_image_path.str(), cv::IMREAD_GRAYSCALE);
        right_image_path << dataset_folder << "sequences/" + sequence_number + "/image_1/" << std::setfill('0') << std::setw(6) << line_num << ".png";
        cv::Mat right_image = cv::imread(left_image_path.str(), cv::IMREAD_GRAYSCALE);

        // 得到ground truth的文件,这里没用
        std::getline(ground_truth_file, line);  //读取 ground_truth的一行,一行12个数,塞到矩阵 gt_pose 里
        std::stringstream pose_stream(line);
        std::string s;
        Eigen::Matrix<double, 3, 4> gt_pose;
        for (std::size_t i = 0; i < 3; ++i)
        {
            for (std::size_t j = 0; j < 4; ++j)
            {
                std::getline(pose_stream, s, ' ');
                gt_pose(i, j) = stof(s);
            }
        }

        Eigen::Quaterniond q_w_i(gt_pose.topLeftCorner<3, 3>());            //提取位姿矩阵的旋转矩阵部分 并转成四元数
        //Eigen::Quaterniond q = q_transform * q_w_i;                       //此处有bug,修改成下一行
        Eigen::Quaterniond q = q_transform * q_w_i * q_transform.inverse(); //坐标系进行变换的四元数 q
        q.normalize();                                                      //对四元数 q 归一化

        //q 来自于 q_w_i, q_w_i来自于 gt_pose, gt_pose来自于ground_truth_file,也就是真值文件.txt
        //t 来自于 gt_pose,
        Eigen::Vector3d t = q_transform * gt_pose.topRightCorner<3, 1>(); //q_transform是四元数,和3*1向量相乘,有运算法则
       

        //odomGT 又来自于 q,t
        odomGT.header.stamp = ros::Time().fromSec(timestamp);               //把浮点型变成时间戳
        odomGT.pose.pose.orientation.x = q.x();                             
        odomGT.pose.pose.orientation.y = q.y();
        odomGT.pose.pose.orientation.z = q.z();
        odomGT.pose.pose.orientation.w = q.w();
        odomGT.pose.pose.position.x = t(0);
        odomGT.pose.pose.position.y = t(1);
        odomGT.pose.pose.position.z = t(2);
        pubOdomGT.publish(odomGT);                                  //发布里程计

        //又把odomGT 放到了 poseGT
        geometry_msgs::PoseStamped poseGT;                          //定义几何信息
        poseGT.header = odomGT.header;
        poseGT.pose = odomGT.pose.pose;

        //nav_msgs::Path pathGT; 用于ROS中画轨迹
        //又把odomGT 放到了 pathGT 
        // nav_msgs::Path = std_msgs/Header + geometry_msgs::PoseStamped
        pathGT.header.stamp = odomGT.header.stamp;
        pathGT.poses.push_back(poseGT);                             //函数将一个新的元素加到最后面

        pubPathGT.publish(pathGT);                                  //发布pathGT

        // 读取雷达点云
        std::stringstream lidar_data_path;
        lidar_data_path << dataset_folder << "velodyne/sequences/" + sequence_number + "/velodyne/" 
                        << std::setfill('0') << std::setw(6) << line_num << ".bin";
        std::vector<float> lidar_data = read_lidar_data(lidar_data_path.str());  //读取lidar的数据,以[x,y,z,i]存储在向量中
        std::cout << "totally " << lidar_data.size() / 4.0 << " points in this lidar frame \n";  //以四个元素(xyzi)为一个点,总共打印lidar_data.size() / 4.0个点


        std::vector<Eigen::Vector3d> lidar_points;   // 声明雷达点的位置向量
        std::vector<float> lidar_intensities;        // 声明雷达点的强度向量
        pcl::PointCloud<pcl::PointXYZI> laser_cloud; // 声明一个pcl的雷达点云
        for (std::size_t i = 0; i < lidar_data.size(); i += 4)
        {
            //emplace_back是就地构造,不用构造后再次复制到容器中。因此效率更高
            lidar_points.emplace_back(lidar_data[i], lidar_data[i+1], lidar_data[i+2]);  //位置XYZ塞到3维向量lidar_points
            lidar_intensities.push_back(lidar_data[i+3]);                                //点云强度塞到浮点一维向量lidar_intensities

            pcl::PointXYZI point;
            point.x = lidar_data[i];
            point.y = lidar_data[i + 1];
            point.z = lidar_data[i + 2];
            point.intensity = lidar_data[i + 3];
            laser_cloud.push_back(point);           //将点存入laser_cloud
        }

        sensor_msgs::PointCloud2 laser_cloud_msg;
        pcl::toROSMsg(laser_cloud, laser_cloud_msg);                    //pcl格式转换为ros的
        laser_cloud_msg.header.stamp = ros::Time().fromSec(timestamp);  //把浮点型变成时间戳
        laser_cloud_msg.header.frame_id = "camera_init";                //声明坐标系
        pub_laser_cloud.publish(laser_cloud_msg);                       //发布ros的点云

        //将opencv的图像转换成ros的图像
        sensor_msgs::ImagePtr image_left_msg = cv_bridge::CvImage(laser_cloud_msg.header, "mono8", left_image).toImageMsg();
        sensor_msgs::ImagePtr image_right_msg = cv_bridge::CvImage(laser_cloud_msg.header, "mono8", right_image).toImageMsg();
        pub_image_left.publish(image_left_msg);
        pub_image_right.publish(image_right_msg);

        if (to_bag)
        {
            bag_out.write("/image_left", ros::Time::now(), image_left_msg);
            bag_out.write("/image_right", ros::Time::now(), image_right_msg);
            bag_out.write("/velodyne_points", ros::Time::now(), laser_cloud_msg);
            bag_out.write("/path_gt", ros::Time::now(), pathGT);
            bag_out.write("/odometry_gt", ros::Time::now(), odomGT);
        }

        line_num ++;        //更新行数,相当于更新读取的文件序号
        r.sleep();          //按照循环频率延时  
    }
    bag_out.close();        //关闭输出的rosbag
    std::cout << "Done \n"; //打印 done


    return 0;
}

1.2 kitti_helper.launch

根据KITTI数据集存放位置和kittiHelper.cpp中的文件路径对kitti_helper.launch文件进行修改。
注意:
kittiHelper.cpp中的dataset_folder和sequence_number 是数据集路径设置,取决于kitti_helper.launch文件的设置。
to_bag和output_bag_file设置是否将数据转换为bag文件及文件保存路径,取决于kitti_helper.launch文件的设置。
“10 / publish_delay” 设置ROS的发布频率,但是,取1,按照10Hz发布。

  • kittiHelper.cpp中图片和激光雷达点云文件路径:
//图片的路径 
left_image_path << dataset_folder << "sequences/" + sequence_number + "/image_0/" << std::setfill('0') << std::setw(6) << line_num << ".png";
right_image_path << dataset_folder << "sequences/" + sequence_number + "/image_1/" << std::setfill('0') << std::setw(6) << line_num << ".png";
//
//激光雷达点云数据路径
lidar_data_path << dataset_folder << "velodyne/sequences/" + sequence_number + "/velodyne/" << std::setfill('0') << std::setw(6) << line_num << ".bin";

主要是文件路径修改对,建议按照KITTI数据集的结构保存数据。

<launch>
    	<node name="kittiHelper" pkg="aloam_velodyne" type="kittiHelper" output="screen"> 
        <param name="dataset_folder" type="string" value="/mnt/hgfs/H/KITTI/" />
        <param name="sequence_number" type="string" value="00" />
        <param name="to_bag" type="bool" value="true" />
        <param name="output_bag_file" type="string" value="/mnt/hgfs/H/KITTI/kitti01.bag" /> <!-- replace with your output folder -->
        <param name="publish_delay" type="int" value="1" />
    </node>
</launch>

  • 数据集结构如下:
//文件夹结构如下
KITTI
     --sequences
    		--01
    				--image_0
    				--image_1
    				--cablib.txt
    				--times.txt
     --velodyne
    		--sequences
    				--01
    					--velodyne
    						--*.bin文件
     --results
     		--01.txt

二、使用方法

2.1 用法一:KITTI数据集转rosbag

把launch文件中设置为<param name=“to_bag” type=“bool” value=“true” >,用于将KITTI数据集分散的图片、点云和路径信息打包成一个rosbag文件,用于算法测试。转换成的bag信息如下图所示。
在这里插入图片描述

2.2 用法二:在线转换并运行A-LOAM算法

先执行算法,然后新开一个终端在线转换并播放数据。

source devel/setup.bash
roslaunch aloam_velodyne aloam_velodyne_VLP_16.launch
roslaunch aloam_velodyne kitti_helper.launch

在这里插入图片描述

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可见一班

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值