3D激光SLAM:ALOAM中KITTI数据集处理--转换成ROS可用数据

本文涉及的产品
资源编排,不限时长
简介: KITTI数据集由德国卡尔斯鲁厄理工学院和丰田美国技术研究院联合创办,是目前国际上最大的自动驾驶场景下的计算机视觉算法评测数据集。该数据集用于评测立体图像(stereo),光流(optical flow),视觉测距(visual odometry),3D物体检测(object detection)和3D跟踪(tracking)等计算机视觉技术在车载环境下的性能。KITTI包含市区、乡村和高速公路等场景采集的真实图像数据,每张图像中最多达15辆车和30个行人,还有各种程度的遮挡与截断。

前言

KITTI数据集由德国卡尔斯鲁厄理工学院和丰田美国技术研究院联合创办,是目前国际上最大的自动驾驶场景下的计算机视觉算法评测数据集。该数据集用于评测立体图像(stereo),光流(optical flow),视觉测距(visual odometry),3D物体检测(object detection)和3D跟踪(tracking)等计算机视觉技术在车载环境下的性能。KITTI包含市区、乡村和高速公路等场景采集的真实图像数据,每张图像中最多达15辆车和30个行人,还有各种程度的遮挡与截断。整个数据集由389对立体图像和光流图,39.2 km视觉测距序列以及超过200k 3D标注物体的图像组成,以10Hz的频率采样及同步。总体上看,原始数据集被分类为’Road’, ’City’, ’Residential’, ’Campus’ 和 ’Person’。对于3D物体检测,label细分为car, van, truck, pedestrian, pedestrian(sitting), cyclist, tram以及misc组成。

通过该数据集可以验证自己的算法

但是获取数据后,需要将数据进行一个转换,转成ROS 的 Topic 或者 rosbag,否则没法使用.
例如图片是png格式的,lidar的数据是bin文件形式的.

本篇详解 如何通过 代码 来实现 kitti数据集的处理

KITTI数据集文件结构

kitti 数据集的文件结构,注意这个和程序里面的路径设置有关,不一致则读不到数据

---kitti_data
------sequence
---------00
------------image_0
------------------000000.png
------------------000001.png
------------------000002.png
------------image_1
------------------000000.png
------------------000001.png
------------------000002.png
------------calib.txt
------------times.txt
------velodye
---------sequence
------------00
---------------velodye
------------------000000.bin
------------------000001.bin
------------------000002.bin

Code

下面的代码实现了读取kitti数据集的 图像\雷达\时间戳 并发布topic和保存bag

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
image.png

要包含的头文件 主要是 字节流的读入 消息 的处理等

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int main(int argc, char** argv)
{   
    //初始化节点
    ros::init(argc, argv, "kitti_helper");
    // ~ 发布的topic  会 以  节点名字+话题名字  发布  
    ros::NodeHandle n("~");

初始化节点

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // 声明字符串  相关的参数设置 在launch中设置的  数据的路径  数据的sequence 输出的bag路径
    std::string dataset_folder, sequence_number, output_bag_file;
    //从参数服务器获取 数据路径
    n.getParam("dataset_folder", dataset_folder);
    //从参数服务器获取 数据的sequence
    n.getParam("sequence_number", sequence_number);
    //打印 kitti数据 的信息
    std::cout << "Reading sequence " << sequence_number << " from " << dataset_folder << '\n';
    
    // 声明 变量 是否需要 生成bag
    bool to_bag;
    //从参数服务器获取 是否需要 生成bag
    n.getParam("to_bag", to_bag);
    //如果生成bag
    if (to_bag)
        n.getParam("output_bag_file", output_bag_file);//从参数服务器获取 
    //声明 变量 发布延时
    int publish_delay;
    //从参数服务器获取 发布延时
    n.getParam("publish_delay", publish_delay);
    //如果小于0 则 设置为1
    publish_delay = publish_delay <= 0 ? 1 : publish_delay;
 

从参数服务器中 读取 对应的 信息 注意 参数 服务器 的设置 在 launch 文件中
image.png

程序从参数服务器中读取的参数都是从 launch文件中设置的 需要设置的已注释

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    //lidar 的点云 发布句柄
    ros::Publisher pub_laser_cloud = n.advertise<sensor_msgs::PointCloud2>("/velodyne_points", 2);

    //图像 的 发布句柄
    image_transport::ImageTransport it(n);
    image_transport::Publisher pub_image_left = it.advertise("/image_left", 2);
    image_transport::Publisher pub_image_right = it.advertise("/image_right", 2);

    //里程计的发布句柄
    ros::Publisher pubOdomGT = n.advertise<nav_msgs::Odometry> ("/odometry_gt", 5);

    //声明一个 里程计
    nav_msgs::Odometry odomGT;
    odomGT.header.frame_id = "/camera_init";//赋值里程计的坐标系 父
    odomGT.child_frame_id = "/ground_truth";//赋值里程计的坐标系 子

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

声明设置 要通过 ros 发布的topic 句柄
有:点云的\图像的\里程计的\路径的

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

读取时间戳的文件
一个时间戳对应一组数据 ,后面的 点云\图像的消息的时间戳则赋值现在设置的 时间戳文件路径

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

设置 path的文件 的 路径

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

   //记录 rosbag
    rosbag::Bag bag_out;
    if (to_bag)
        bag_out.open(output_bag_file, rosbag::bagmode::Write);

根据参数设置 决定 是否 生成 rosbag 文件

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    Eigen::Matrix3d R_transform;//方向设置矩阵  本质上就是一个旋转矩阵
    R_transform << 0, 0, 1, -1, 0, 0, 0, -1, 0;//附初值
    Eigen::Quaterniond q_transform(R_transform);//旋转矩阵 转为 四元数

设置 方向设置矩阵 本质上就是一个旋转矩阵
位姿传感器的坐标系 方向 和 lidar 图像 的坐标系 方向 不一致

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    std::string line;//声明 读取行数后保存的字符串
    std::size_t line_num = 0;//声明读取的行数 控制读取的 图像  lidar 的 数据序号

    ros::Rate r(10.0 / publish_delay);//控制发布频率

声明 读取行数后保存的字符串
声明读取的行数 控制读取的 图像 lidar 的 数据序号
控制发布频率 publish_delay 这个值 从参数服务器中来 也就是 launch中
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    //循环读 时间戳 文件 的 每一行 
    while (std::getline(timestamp_file, line) && ros::ok())
    {
        float timestamp = stof(line);//将时间戳从 字符串 转 成 float

循环读 时间戳 文件 的 每一行
将时间戳从 字符串 转 成 float
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        std::stringstream left_image_path, right_image_path;//声明 左右图像的路径

        //设置 左图片的路径
        left_image_path << dataset_folder << "sequences/" + sequence_number + "/image_0/" << std::setfill('0') << std::setw(6) << line_num << ".png";
        //通过opencv的方法 读取图片
        cv::Mat left_image = cv::imread(left_image_path.str(), CV_LOAD_IMAGE_GRAYSCALE);
        //设置 右图片的路径
        right_image_path << dataset_folder << "sequences/" + sequence_number + "/image_1/" << std::setfill('0') << std::setw(6) << line_num << ".png";
        //通过opencv的方法 读取图片
        cv::Mat right_image = cv::imread(left_image_path.str(), CV_LOAD_IMAGE_GRAYSCALE);

通过opencv的方法 读取图片

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        //读取 ground_truth的一行
        std::getline(ground_truth_file, line);
        std::stringstream pose_stream(line);
        std::string s;
        Eigen::Matrix<double, 3, 4> gt_pose;//声明位姿 矩阵 3行4列
        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);//给位姿矩阵赋值
            }
        }

读取 ground_truth的一行
给位姿矩阵赋值
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        Eigen::Quaterniond q_w_i(gt_pose.topLeftCorner<3, 3>());//提取位姿矩阵的角度部分 并转成四元数
        Eigen::Quaterniond q = q_transform * q_w_i;//进行 方向的 旋转
        q.normalize();//归一化
        Eigen::Vector3d t = q_transform * gt_pose.topRightCorner<3, 1>();//提取位姿矩阵的位置部分 

提取位姿矩阵的角度部分 并转成四元数
提取位姿矩阵的位置部分
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        //赋值里程计的 内容
        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);

        //赋值路径的内容
        geometry_msgs::PoseStamped poseGT;
        poseGT.header = odomGT.header;
        poseGT.pose = odomGT.pose.pose;
        pathGT.header.stamp = odomGT.header.stamp;
        pathGT.poses.push_back(poseGT);
        //发布路径
        pubPathGT.publish(pathGT)

赋值里程计的 内容 发布里程计
赋值路径的内容 发布路径
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        // 读取 lidar 的点云 
        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 x y z i ....] 存储咋向量中
        std::cout << "totally " << lidar_data.size() / 4.0 << " points in this lidar frame \n";//4个元素是一个点 打印一共多少个点

读取 lidar 的点云
以 [x y z i x y z i ....] 存储咋向量中
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        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)//遍历上面读取的那个 lidar数据的向量
        {
            lidar_points.emplace_back(lidar_data[i], lidar_data[i+1], lidar_data[i+2]);//存入lidar位置
            lidar_intensities.push_back(lidar_data[i+3]);//存入lidar 强度

            pcl::PointXYZI point; //声明一个PCL的点
            //赋值点的 x y z i
            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);//将点存入点云
        }

将lidar 的信息 存入 PCL形式的点云中

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        sensor_msgs::PointCloud2 laser_cloud_msg;//声明一个ros的点云
        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的点云

发布ros的点云

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        //将opecv的图像 转成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);

发布ROS的左右图像

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        if (to_bag)//如果发布rosbag 的话
        {   
            //写入 rosbag
            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);
        }

写入 rosbag
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        line_num ++;//更新行数  也相当于 更新 读取的 文件 序号
        r.sleep();//进行延时 控制发布频率

更新行数 也相当于 更新 读取的 文件 序号
进行延时 控制发布频率
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    bag_out.close(); // 关闭rosbag
    std::cout << "Done \n";
    return 0;
}

关闭rosbag
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Result

成功的话可以在 rviz里面 播放出 图片和 lidar的点云 信息
在这里插入图片描述

相关实践学习
使用ROS创建VPC和VSwitch
本场景主要介绍如何利用阿里云资源编排服务,定义资源编排模板,实现自动化创建阿里云专有网络和交换机。
阿里云资源编排ROS使用教程
资源编排(Resource Orchestration)是一种简单易用的云计算资源管理和自动化运维服务。用户通过模板描述多个云计算资源的依赖关系、配置等,并自动完成所有资源的创建和配置,以达到自动化部署、运维等目的。编排模板同时也是一种标准化的资源和应用交付方式,并且可以随时编辑修改,使基础设施即代码(Infrastructure as Code)成为可能。 产品详情:https://www.aliyun.com/product/ros/
相关文章
|
机器学习/深度学习 机器人 中间件
ubuntu16.04下ROS操作系统学习笔记(五)gazebo物理仿真环境搭建、加载服务端模型数据减少报错
ubuntu16.04下ROS操作系统学习笔记(五)gazebo物理仿真环境搭建、加载服务端模型数据减少报错
199 0
|
存储
ROS学习-记录和回放数据
ROS学习-记录和回放数据
440 0
ROS学习-记录和回放数据
|
传感器 C++
ROS仿真中 读取 turtlebot3 扫描雷达传感器数据 求 前方距离和最短距离(对应方向)
ROS仿真中 读取 turtlebot3 扫描雷达传感器数据 求 前方距离和最短距离(对应方向)
ROS仿真中  读取  turtlebot3  扫描雷达传感器数据  求 前方距离和最短距离(对应方向)
|
C++ Python
【古月21讲】ROS入门系列(3)——客户端Client、服务器Server的编程实现+自定义服务数据编程实现
【古月21讲】ROS入门系列(3)——客户端Client、服务器Server的编程实现+自定义服务数据编程实现
271 0
【古月21讲】ROS入门系列(3)——客户端Client、服务器Server的编程实现+自定义服务数据编程实现
|
Ubuntu C++ 计算机视觉
在ROS中实现多Realscene D455数据的读取并发布
在ROS中实现多Realscene D455数据的读取并发布
在ROS中实现多Realscene D455数据的读取并发布
|
关系型数据库 数据库 对象存储
资源编排配合实例自定义数据,实现RDS自动创建与恢复
背景不少客户需要批量部署系统他们希望ROS不仅能帮助他们自动部署底层PAAS与IAAS资源还能够省去安装软件链接数据库导入数据库文件的动作。而ROS能完美结合ECS的自定义数据自定义镜像RDS Web API最大化减少人肉工作。
2191 0
|
2月前
|
Ubuntu 机器人 Linux
|
16天前
|
XML 算法 自动驾驶
ROS进阶:使用URDF和Xacro构建差速轮式机器人模型
【11月更文挑战第7天】本篇文章介绍的是ROS高效进阶内容,使用URDF 语言(xml格式)做一个差速轮式机器人模型,并使用URDF的增强版xacro,对机器人模型文件进行二次优化。
|
21天前
|
自动驾驶 安全 机器人
ROS2:从初识到深入,探索机器人操作系统的进化之路
【11月更文挑战第4天】ROS2的学习过程和应用,介绍DDS系统的框架和知识。

推荐镜像

更多