Ros_rosbag_提取点云数据pointclouds_pcd_图像数据img__jpg

Linux环境

Ubuntu 16 xenial   Ubuntu 18 bionic  Ubuntu 20 focal
Debian 10 Buster   Debian 11 bullseye	

  ROS中表示点云的数据结构有: 
    sensor_msgs::PointCloud 
    sensor_msgs::PointCloud2 
    pcl::PointCloud
  Ros中表示图像的数据结构 sensor_msgs/Image sensor_msgs/CompressedImage

 命令:
    pcl_ros 
         bag_to_pcd  pointcloud_to_pcd 
    image_view 
         extract_images  image_saver 

点云的命令行提取

  rosrun pcl_ros bag_to_pcd output.bag /com  /opt/pcd 	
  方法二:pointcloud_to_pcd
       一个终端通过ros发送messages,如:$ rosbag play XXX.bag
       另一个终端接收,             如:$ rosrun pcl_ros pointcloud_to_pcd input:=/velodyne_points	

图像的命令提取

 用于从sensor_msgs/Image流中将图片保存为jpg/png格式。	
     rosrun image_view extract_images _sec_per_frame:=0.01 image:=/fr/image_raw
     rosrun image_view image_saver  _encoding:='bgr8'  _filename_format:=%s image:=/fr/image_raw/compressed

 从a compressed image stream 提取 /fr/image_raw/compressed
    rosrun image_view image_view image:=/camera/image _image_transport:=compressed
	rosrun image_view image_saver filename_format:=left%04d.%s image:=/fr/image_raw _image_transport:=compressed
	 
	 
通过ros自带功能包 image_view 中的video来提取视频
     rosrun image_view video_recorder _fps:=30 image:=/velodyne/image _filename:=output.avi	

ROS1镜像运行

 ####1.拉取docker 环境-ros的环境
 docker pull osrf/ros:noetic-desktop-full	或者 docker pull osrf/ros:melodic-desktop-full
 
##运行docker环境
     docker run --name ros-pipe --restart always --add-host test.com:10.10.10.10 \
   -v /opt/code/workflow:/opt/code/workflow  \
   -it osrf/ros:noetic-desktop-full /bin/bash
   
 ###docker内运行
    ###docker环境内部执行
	确定是否有 roscore,可以搜索 whereis roscore
    如果没有的话,执行  source /ros_entrypoint.sh  
    roscore&
	
   1.查看bag包信息 
       rosbag info output.bag
   2.提取点云数据命令行--  bag文件转PCD文件
     http://wiki.ros.org/pcl_ro
       rosrun pcl_ros bag_to_pcd output.bag /com  /opt/pcd 
	   
	其他等价
       # Syntax is: /opt/ros/noetic/lib/pcl_ros/bag_to_pcd <file_in.bag> <topic> <output_directory> [<target_frame>]
        #Example: /opt/ros/noetic/lib/pcl_ros/bag_to_pcd  data.bag /laser_tilt_cloud ./pointclouds /base_link		
	 方法二:pointcloud_to_pcd
       一个终端通过ros发送messages,如:$ rosbag play XXX.bag
       另一个终端接收,             如:$ rosrun pcl_ros pointcloud_to_pcd input:=/velodyne_points	
   3.rostopic list -v	

   4.删除roscore进程
     killall -9 roscore
     killall -9 rosmaster
	 
 roscore&
   5.端口占用
    lsof -i:66
    lsof -i:77 
    fuser -v 66/udp 77/udp  
	  
###设置环境变量
source /ros_entrypoint.sh  
     /ros_entrypoint.sh
     #!/bin/bash
     set -e
     # setup ros environment
     source "/opt/ros/$ROS_DISTRO/setup.bash"
     exec "$@"
  其中   
    /opt/ros/noetic/setup.bash
    #!/usr/bin/env bash
    # generated from catkin/cmake/templates/setup.bash.in
    CATKIN_SHELL=bash
    # source setup.sh from same directory as this file
    _CATKIN_SETUP_DIR=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" > /dev/null && pwd)
    . "$_CATKIN_SETUP_DIR/setup.sh"

转换逻辑

 01.使用cv_bridge将ROS图像转换为OpenCV图像,反之亦然,从而实现ROS和OpenCV的交互
    CvBridge是一个ROS库,提供ROS和OpenCV之间的接口	
       要将ROS图像消息转换为cv :: Mat,模块cv_bridge.CvBridge提供以下功能
         cv_image = bridge.imgmsg_to_cv2(image_message, desired_encoding="bgr8")	
       将OpenCV图像转换为ROS图像消息
         image_message = cv2_to_imgmsg(cv_image, encoding="passthrough")		   
 02.  rosrun pcl_ros bag_to_pcd output.bag /com  /opt/pcd 
     "/opt/ros/noetic/lib/pcl_ros/bag_to_pcd {} {} {}".format(bag_path_nm, pc_topic, pcd_out_path) )

    介绍几个ROS节点运行的几种工具。他们的作用是ROS格式点云或包与点云数据(PCD)文件格式之间的相互转换。
    (1)bag_to_pcd
    用法:rosrun pcl_ros bag_to_pcd <input_file.bag> <topic> <output_directory>
    读取一个包文件,保存所有ROS点云消息在指定的PCD文件中。
    
    (2)convert_pcd_to_image
    用法:rosrun pcl_ros convert_pcd_to_image <cloud.pcd>
    加载一个PCD文件,将其作为ROS图像消息每秒中发布五次。
    
    (3) convert_pointcloud_to_image
    用法:rosrun pcl_ros convert_pointcloud_to_image input:=/my_cloud output:=/my_imag
     查看图像:rosrun image_view image_view image:=/my_image
    订阅一个ROS的点云的话题并以图像的信息发布出去。
	
    (4)pcd_to_pointcloud
    用法:rosrun pcl_ros pcd_to_pointcloud <file.pcd> [ <interval> ]
    <file.pcd> is the (required) file name to read.
    <interval> is the (optional) number of seconds to sleep between messages. If <interval> is zero or not specified the message is published once.
    加载一个PCD文件,发布一次或多次作为ROS点云消息
	
    (5)pointcloud_to_pcd
    例如: rosrun pcl_ros pointcloud_to_pcd input:=/velodyne/pointcloud2
    订阅一个ROS的话题和保存为点云PCD文件。每个消息被保存到一个单独的文件,名称是由一个可自定义的前缀参数,ROS时间的消息,和以PCD扩展的文件

代码示例

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# ---------------------------
"""" 以def 开头 和以return 结尾的中间所有行"""
def get_between_def(file_nm_path):
    with open(file_nm_path,mode='r',encoding='utf8') as fobj:
        start_flag = False
        for num, line in enumerate(fobj):
            line = line.rstrip()
            if 'def' in line.rstrip() or start_flag:
                start_flag = True
                print(line)
                if  "return" in line.strip():
                    start_flag = False
                    print("hello world ##############",num)
    return start_flag


if __name__ == "__main__":
    file_nm= r"D:\Imag\flow\testFile.txt"
    get_between_def(file_nm) 
	
###说明
 引入第三方标识--通过改变标识来改变逻辑
 通过【subprocess.getstatusoutput】获得shell返回结
   
o3d.t.io.write_point_cloud( save_file, pcd, write_ascii=False)

使用python提取

 	rosbags-image  cv_bridge 

  CvBridge.compressed_imgmsg_to_cv2
   CvBridge.imgmsg_to_cv2

python 包说明

pip install rosbags-image
from rosbags.image import message_to_cvimage

# msg is rosbags Image or CompressedImage message instance
msg = ...
# get opencv image without any conversions
img = message_to_cvimage(msg)
# get opencv image and convert to bgr8 color space
img = message_to_cvimage(msg, 'bgr8')
###参考 https://pypi.org/project/rosbags-image/    

代码示例

# -*- coding: utf-8 -*-  
import os
import argparse
import cv2
import open3d as o3d
import numpy as np
import rosbag
import sensor_msgs.point_cloud2 as point_cloud2
from rosbags.image import message_to_cvimage
from tqdm import tqdm


def extract_bag_pcd(bag_path):
    bag = rosbag.Bag(bag_path)
    pcd_path = os.path.join(os.path.splittext(bag_path)[0],"pcd")
    if not os.path.exists(pcd_path):
        os.makedirs(pcd_path)
    topic = ['/points']  
    for topic, msg, t in tqdm(bag.read_messages(topics=topic)):
        msg_stamp = str(msg.header.stamp)
        pcd_file = os.path.join(pcd_path, '{}.{}.pcd'.format(msg_stamp[0:10], msg_stamp[10:-3]))
        cloud  = np.asarray(list(point_cloud2.read_points(msg, field_names=['x', 'y', 'z', "intensity"])))          
        pcd_obj = o3d.t.geometry.PointCloud()
        pcd_obj.point['positions'] = o3d.core.Tensor(cloud[:, :3],dtype=o3d.core.Dtype.Float32)
        pcd_obj.point['intensity'] = o3d.core.Tensor(cloud[:, 3].reshape(-1,1),dtype=o3d.core.Dtype.Float32)
        o3d.t.io.write_point_cloud( pcd_file, pcd_obj, write_ascii=False)
    bag.close()

def extract_bag_image(bag_path):
    bag = rosbag.Bag(bag_path)
    image_path = os.path.join(os.path.splittext(bag_path)[0],"image")
    if not os.path.exists(image_path):
        os.makedirs(image_path)
    topics = ['/tt/image_raw','/tt/image_raw']
    for topic, msg, t in tqdm(bag.read_messages(topics=topics)):
        msg_stamp = str(msg.header.stamp)
        image_dir = os.path.join(image_path, topic.split('/')[1])
        if not os.path.exists(image_dir):
            os.makedirs(image_dir)
        image_file = os.path.join(image_dir, '{}.{}.jpg'.format(msg_stamp[0:10], msg_stamp[10:-3]))
        cv_image = message_to_cvimage(msg,color_space="bgr8")
        cv2.imwrite(image_file, cv_image)
    bag.close()

if __name__ == "__main__":
    parser = argparse.ArgumentParser( description='bag')
    parser.add_argument('--bag_path', type=str, default='/data/1.bag', help='bag path')                                    
    args = parser.parse_args()
    exp_bag = args.bag_path
    extract_pcd(exp_bag)
    extract_image(exp_bag)

排查

1.版本
python3 -c '''
import open3d
print(open3d.__version__)
'''	
2.open3d Warning  pointcloud contains intensity attribute which is not supported by PCD IO Only
 open3d warning write pcd:skipping attribute        
   open3d.cpu.pybind.t.geometry.PointCloud  
   open3d.cpu.pybind.t.geometry.TensorMap
 结论:reshape(-1,1)是将一维数据在行上变化,而reshape(1,-1)是将一维数据在列上变化
 [1,2,3]都是 (3,)如果想要得到 (3,1) 或者 (1,3) 的 shape,需要使用 reshape 方法
  如果 想得到 (3,1) ,调用 a.reshape(-1, 1) 
  第一个形状为(3,)的数组是一维数组。它的结构应该与此类似:a=[1,2,3]
  第二个形状为(3,1)。 类似于b=[[1],[2],[3]]

代码参考

### version 16.0
import open3d as o3d

device = o3d.core.Device("CPU:0")
dtype = o3d.core.float32
pcd = o3d.t.geometry.PointCloud(device)
pcd.point.positions = o3d.core.Tensor([[0, 0, 0],
                                          [1, 1, 1],
                                          [2, 2, 2]], dtype, device)
pcd.point.colors = o3d.core.Tensor([[0.0, 0.0, 0.0],
                                        [0.1, 0.1, 0.1],
                                        [0.2, 0.2, 0.2]], dtype, device)
# User-defined attributes.
pcd.point.intensities = o3d.core.Tensor([0.3, 0.1, 0.4], dtype, device)
pcd.point.intensity = o3d.core.Tensor([[0.3], [0.1], [0.4]], dtype, device)
pcd_file="/data/cloud/test.pcd"
o3d.t.io.write_point_cloud( pcd_file, pcd, write_ascii=False)

###version 15.0
import open3d as o3d

device = o3d.core.Device("CPU:0")
dtype = o3d.core.float32
pcd = o3d.t.geometry.PointCloud(device)
pcd.point["positions"] = o3d.core.Tensor([[0, 0, 0],
                                          [1, 1, 1],
                                          [2, 2, 2]], dtype, device)
pcd.point["colors"] = o3d.core.Tensor([[0.0, 0.0, 0.0],
                                        [0.1, 0.1, 0.1],
                                        [0.2, 0.2, 0.2]], dtype, device)
# User-defined attributes.
pcd.point["intensities"] = o3d.core.Tensor([0.3, 0.1, 0.4], dtype, device)
pcd.point["intensity"] = o3d.core.Tensor([[0.3], [0.1], [0.4]], dtype, device)
pcd_file="/data/cloud/test.pcd"
o3d.t.io.write_point_cloud( pcd_file, pcd, write_ascii=False)

#  [Open3d Warning] Write PCD: Skipping intensities attribute.
  pointcloud contains intensity attribute which is not supported by PCD IO 
  Only points,normal,colors 
     and attributes with shape(num_points,1) are supported
  Expected shape:{3,1}but got{3}

cv_bridge用于ROS图像和OpenCV图像的转换

ROS以自己的sensor\msgs/Image消息格式传递图像,
希望将图像与OpenCV结合使用。在OpenCV中,图像以Mat矩阵的形式存储,
与ROS定义的图像消息的格式有一定的区别,CvBridge是一个ROS库,提供ROS和OpenCV之间的接口

# msg is rosbags Image or CompressedImage message instance
msg = ...

# get opencv image without any conversions
img = message_to_cvimage(msg)

# get opencv image and convert to bgr8 color space
img = message_to_cvimage(msg, 'bgr8')

https://gitlab.com/ternaris/rosbags-image
CvBridge识别以下Bayer pattern encodings:
    bayer_rggb8
    bayer_bggr8
    bayer_gbrg8
    bayer_grbg8	
	
CvBridge可以根据需要进行color或像素深度的转换。

要想使用此功能,需要将编码指定为以下格式之一:

mono8:cv8uc1,灰度图像
mono16:cv16uc1,16位灰度图像
bgr8:cv8uc3,蓝绿红顺序彩色图像
rgb8:cv8uc3,红-绿-蓝颜色顺序的彩色图像
bgra8:cv8uc4,带alpha通道的BGR彩色图像
rgba8:cv8uc4,带alpha通道的RGB彩色图像
注意:mono8和bgr8(1和3)是大多数OpenCV函数所期望的两种图像编码格式。

PIL、OpenCV和numpy

opencv读取图片的通道数默认为b,g,r。 opencv用的都是BGR格式
OpenCV读取图像文件后的存储格式就是np.array
  import cv2
  
  img = cv2.imread('1.png')
  if len(img.shape) == 2 or img.shape[-1] == 1:
  	 img= cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
  img= img[:, :, ::-1]
  cv.imwirte('2.png',img)
  
 
  img=cv2.imread('1.jpg') #打开图像,opencv默认读取图片的数据为: (高,宽,通道(B,G,R))。

# PLT默认读取图片数据格式:(高,宽,通道(R,G,B))
 from PIL import Image
 image = Image.open("img.jpg")
 image.save('XXX.jpg')  # 保存为XXX.jpg
PIL的Image.open()读取图片
def to_cvimage(self,img_pil):
    return cv2.cvtColor(np.asarray(img_pil), cv2.COLOR_RGB2BGR)	
# numpy 转 image类
mask = Image.fromarray(np.uint8(mask)) 	
将PIL image或者一个numpy.ndarray变成tensor	
img_tensor = transforms.ToTensor()
img_PIL_tensor_GPU = img_tensor(img_PIL).cuda()

图像发布到ros的bag中

#!/usr/bin/python3
# -*- coding: utf-8 -*- 

import  os
import roslib
import rospy
import numpy as np
roslib.load_manifest('sensor_msgs')
from ros import rosbag
from cv_bridge import CvBridge
from PIL import Image as ImagePIL


'''sort image name'''
def CompSortFileNamesNr(f):
    g = os.path.splitext(os.path.split(f)[1])[0]
    numbertext = ''.join(c for c in g if c.isdigit())
    return int(numbertext)

'''get image from dir'''
def ReadImages(img_dir):
    '''Generates a list of files from the directory'''
    print( "Searching directory %s" % img_dir )
    all = []
    left_files = []
    img_timestamps=[]
    if os.path.exists(img_dir):
        for path, names, files in os.walk(img_dir ):
            for f in sorted(files, key=CompSortFileNamesNr):
                if os.path.splitext(f)[1] in ['.bmp', '.png', '.jpg']:
                    if 'left' in f or 'image_0' in path  :
                        file_nm =os.path.join( path, f )
                        left_files.append( file_nm )
                        time_img =os.path.splitext(f)[0]
                        img_timestamps.append(time_img)
                    all.append( os.path.join( path, f ) )
    return left_files ,img_timestamps


def create_Ros_bag(img_dir,out_bag_nm):
    out_file_dir = ReadImages(img_dir)
    '''read image'''
    left_imgs = out_file_dir[0]
    '''read time stamps'''
    imagetimestamps=out_file_dir[1]
    '''Creates a bag file containing  image '''
    bag = rosbag.Bag(out_bag_nm, 'w')
    try:
        br = CvBridge()
        topic_nm_r = '/cam1/image_raw'
        for i in range(len(left_imgs)):
            print("Adding {} {} {}".format(i,imagetimestamps[i],left_imgs[i]) )
            rgb_image =  ImagePIL.open(left_imgs[i])
            data = np.asarray(rgb_image)
            # cv2_to_compressed_imgmsg
            rgb_image_msg = br.cv2_to_imgmsg(data)  # Convert the color image to a message
            # Stamp = rospy.rostime.Time.from_sec(float( (float(imagetimestamps[i]))/1e6 ))
            Stamp = rospy.rostime.Time.from_sec(float(imagetimestamps[i]))
            rgb_image_msg.header.stamp = Stamp
            rgb_image_msg.header.frame_id = "camera/left"
            rgb_image_msg.encoding = "rgb8"
            bag.write(topic_nm_r, rgb_image_msg, Stamp)
    finally:
        bag.close()

if __name__ == "__main__":
    img_dir_exp = r"/test/"
    bag_nm_exp = r"/data/test.bag"
    create_Ros_bag(img_dir_exp,bag_nm_exp)    

C++解析bag包

// catkin_create_pkg beginner_tutorials std_msgs rospy roscpp rosbag 

#include <rosbag/bag.h>
#include <rosbag/view.h>
#include <std_msgs/Int32.h>
#include <std_msgs/String.h>
#include <sensor_msgs/PointCloud2.h>
#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH

int main(int argc, char *argv[])
{
      if(argc<2){
        return -1;
    }else{
       // 输入参数
        std::string path=argv[1];
        std::string SavePath=argv[2];
       //输出文件
        FILE *fp;
        fp=fopen(SavePath.c_str(),"w");
        //打开一个bag文件
        rosbag::Bag bag;
        std::cout<<"rosbag path:"<<path<<std::endl;
        bag.open(path, rosbag::bagmode::Read);
        //设置需要遍历的topic
        std::vector<std::string> topics;
        topics.push_back(std::string("/right/rslidar_packets"));        
        topics.push_back(std::string("/usb_cam/image_raw0/compressed"));
        // 读指定的topic
        rosbag::View view(bag, rosbag::TopicQuery(topics));
        //读取全部topic: rosbag::View view_all(view); 
       //用foreach遍历所有帧数据,每个messageInstance就是一帧数据
        foreach(rosbag::MessageInstance const m, view)
        {
            std_msgs::String::ConstPtr s = m.instantiate<std_msgs::String>();
          // novatel_msgs::INSPVAX::ConstPtr s = m.instantiate<novatel_msgs::INSPVAX>();
            if (s != NULL) 
             std::cout << s->latitude << std::endl;
        }
        bag.close();
        return 0;
    }
}  

参考

https://gitlab.com/ternaris/rosbags-image/-/blob/master/src/rosbags/image/image.py	
http://www.open3d.org/docs/0.15.1/   Open3D: A Modern Library for 3D Data Processing
posted @ 2022-04-01 16:36  辰令  阅读(2399)  评论(0编辑  收藏  举报