Matlab写入点云数据到Rosbag

最近有需要读取一个点云并做处理后,重新写回rosbag。网上有很多读取的教程,但没有写入。自己写入时也遇到了很多麻烦,踩了一堆坑进行记录。

1. rosbag中一个lidar的msg有哪些信息?

通过如下代码,先读取一个rosbag的lidar的msg:

bag = rosbag('E:\04_Data\Share\test_data\double-water.bag');
lidarTopic = '/velodyne_points';
lidarMsgs = readMessages(select(bag, 'Topic', lidarTopic));
N_lidar = size(lidarMsgs, 1);

% 输出topic看一下:
lidarMsgs{1}

可以看到,输出的结果如下:
在这里插入图片描述
其中:
Header对应rosbag中的header
Fields对应rosbag中每个点有多少个“字段”,
Heightwidth中,height永远是1,width的数量和点云点数相同
IsBigendian大端模式,根据系统选择。我的系统是0。
PointStep,一个数据点的“step”,可以认为,一个数据点有多长。多长取决于有多少个Fields,以及每个Fields的数据类型
RowStep,数值等于 PointStep*Width,这个是计算出来的
Data,注意是84400*1的uint8类型。这个很重要。84400和RowStep一致,uint8是ros在传输数据时,将message中的所有数据都编码成uint8类型。
IsDense表示是否是稠密的?这个暂不清楚有什么用。

这里重点解释Data的长度、类型以及Fields是什么

我们进一步看Fields有哪些内容。这里有5个,我们先列出来第一个:
在这里插入图片描述
最上方的INT8~FLOAT64这几行,是说明,并不是实际的数据;这表示,在ROS中数据类型的编号(后面还会涉及在matlab中的数据类型,所以强调区分;例如,ROS中FLOAT32占4个bytes,对应matlab中应该用占4bytes的single类型转化)是什么。

1.2 Fileds字段详解

Fileds(1)的Name是’x’,表示“这个字段的含义是x”;
Offset是0,表示“这个字段在一个(长度为PointStep中的)数据点中的“起始位置”是0,也就是说从第1位开始读某个长度的字节Bytes,就是’x’的数值。读多长呢?往下看
Datatype是7,表示这个字段的数据类型是7,7是什么?上面给出了 ‘FLOAT32: 7’,即是一个FLOAT32类型。
Count是1,表示只有一个数据(lidar字段中好像所有的count都是1)。

那么,这表示,“每一个数据的第一个字段是,从第1个byte开始,读取类型FLOAT32(实际上是4个bytes)长度的数据,读取1个,这个字段的名称是x”。

同样,y和z也是如此,如下(第二个字段是y):
在这里插入图片描述
由于刚才讲到,Datatype=7表示一个FLOAT32,长度需要4个bytes,所以x的下一个字段y就要从Offset=4开始。

接下来看不同其他Fileds。这里我的LiDAR数据包括: x, y, z, intensity, ring信息,所以共有5个字段。需要注意的是,每个字段用什么类型,是和rosbag中读取和转化有关的,这又涉及到ros中PCL的转化,这里不做展开介绍,可以参考之前的博客:【学习记录】Ouster雷达运行fastlio提示 Failed to find match for field ‘ring‘ 的解决办法。我们这里只讲,数据是这个格式,在matlab里面是什么样子的。为什么用FLOAT32,或者uint16,这些是雷达驱动底层、代码接口定义的,这里不做探究。

在我的数据中,intensity也是FLOAT32类型,ring是uint16类型,所以具体的intensity如下,从第12个bytes开始读取7类型的数据:
在这里插入图片描述
ring的格式如下,从16tytes开始读取Datatype=4的数据:
在这里插入图片描述

至此,我们搞清楚了rosbag读取一个message后,有哪些数据了。

1.3 Data字段详解

可以看到,msg中的Data字段是一个: 84400*1的uint8的数据。
84400=4220*20,其中4220是总的lidar点数,20是一个点所包含的上述5个Fields的长度。

具体的,我们看一下msg.Data的具体内容,如下:

在这里插入图片描述
可以看出,Data字段的前20个数据,是一个完整的lidar点。注释如下:
在这里插入图片描述
不信的话,我们可以将x转化回FLOAT32类型,看一下是不是真实的x坐标,如下图。确实如此。typecast将一个数据转成指定的类型,这里用single是因为,MATLAB中的single类型对应ros中的FLOAT32,字节都是4bytes。
在这里插入图片描述
y、z、intensity和ring的验证这里不表。

还注意到,在长度为20的一个数据中,ring后面有2个0,这是为什么?因为ros要求4字节对齐,必须是4的整数倍。目前到ring总共有18个bytes,因此需要再补2个凑到20。这两个0是不影响读取的,因为Field(5)字段已经给出了从16开始读取uint16长度数据,因此会忽略后面的。

2. 创建msg

创建msg需要把所有字段都创建,重点是Data部分怎么处理。我们首先假设点云数据是这样的,10个随机数:

% 假设数据
K = 10;
xyz = rand(K,3);          % 100个点的XYZ坐标
intensity = rand(K);     % 随机强度值
ring = randi(16,K)-1;   % 环号(0~15)

% 数据格式转化为对应的类型。matlab->ros对应关系:single->float32, uint16->uint16
xyz = single(xyz);          % 强制转换为FLOAT32
intensity = single(intensity); % 强制转换为FLOAT32
ring = uint16(ring);        % 强制转换为UINT16

2.1 msg各个字段创建

对Header, Field等字段创建。这里我们先不管Data怎么搞。

% 创建PointCloud2消息对象
lidarMsg = rosmessage('sensor_msgs/PointCloud2');

% 设置消息头信息
lidarMsg.Header.Stamp = rostime('now');
lidarMsg.Header.FrameId = 'lidar_frame';

% 获取点云数量
numPoints = size(xyz, 1);

% 设置点云基本信息
lidarMsg.Height = 1;
lidarMsg.Width = numPoints;
lidarMsg.IsDense = true;

% 定义字段(x, y, z, intensity, ring)
fields = {'x','y','z','intensity','ring'};
offsets = uint32([0, 4, 8, 12, 16]); % 各字段的字节偏移量
datatypes = [7, 7, 7, 7, 4];        % 7=FLOAT32, 4=UINT16
counts = [1, 1, 1, 1, 1];

% 添加字段到消息
lidarMsg.Fields = [];
for i = 1:length(fields)
    field = rosmessage('sensor_msgs/PointField');
    field.Name = fields{i};
    field.Offset = offsets(i);
    field.Datatype = datatypes(i);
    field.Count = counts(i);
    lidarMsg.Fields = [lidarMsg.Fields; field];
end

% 设置点云数据步长(每个点20字节)
lidarMsg.PointStep = 20;
lidarMsg.RowStep = lidarMsg.PointStep * lidarMsg.Width;
lidarMsg.IsBigendian = false; % 小端字节序

搞清楚上面数据的分析,具体的创建就简单多了。
需要注意的是,需要对应准确相应字段的数据格式。

2.2 重点关注Data字段

重点关注Data字段怎么创建。
Data是把所有点按顺序拉成一列,然后每个点有Fields里面的字段。所以首先需要把所有点先组成一个Data,再拉成列。需要注意:

  • Data数据是,先一个点(x,y,z,intensity, ring…),再下一个点,拼接的;而不是所有的x再所有的y这样拼接;
  • 拼接时,注意首先要把每个点的所有数据格式都转成uint8,而不是先拼一起再转uint8,因为拼接时会有自动转化导致格式错误;
  • 不足4字节的,补0

代码如下:

% 预分配内存并逐个点填充字节
dataBytes = zeros(numPoints, 20, 'uint8');
for i = 1:numPoints
    % 转换x, y, z, intensity为小端字节序
    xyzIntensityBytes = typecast([xyz(i,:), intensity(i)], 'uint8');
    % 转换ring为小端字节序
    ringBytes = typecast(ring(i), 'uint8');
    % 合并数据(16字节xyzIntensity + 2字节ring + 2字节填充)
    dataBytes(i,:) = [xyzIntensityBytes, ringBytes, 0, 0];
end
% 将数据转换为列向量并赋值
lidarMsg.Data = reshape(dataBytes', [], 1);		% 注意这里的转置,否则reshape时按列拼接不对。

3. 其他

Data字段并不要求每个Field必须是连续的。我某个雷达录制的数据,'z’和’intensity’的起始位置分别是8和16,显然12-16这4个Bytes是空的,但不影响。可能是LiDAR自身预留的字段。

后记

折腾了一个晚上加一个白天,才把这些问题搞清楚。 真是不容易啊。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/972505.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

c++标准io与线程,互斥锁

封装一个 File 类, 用有私有成员 File* fp 实现以下功能 File f "文件名" 要求打开该文件 f.write(string str) 要求将str数据写入文件中 string str f.read(int size) 从文件中读取最多size个字节, 并将读取到的数据返回 析构函数 #…

springboot-ffmpeg-m3u8-convertor nplayer视频播放弹幕效果

学习链接 ffmpeg-cli-wrapper - 内部封装了操作ffmpeg命令的java类库,它提供了一些类和方法,可以方便地构建和执行 ffmpeg 命令,而不需要直接操作字符串或进程。并且支持异步执行和进度监听 springboot-ffmpeg-m3u8-convertor - gitee代码 …

在做题中学习(90):螺旋矩阵II

解法:模拟 思路:创建相同大小的一个二维数组(矩阵),用变量标记原矩阵的行数和列数,每次遍历完一行或一列,相应行/列数--,进行对应位置的赋值即可。此题是正方形矩阵,因此…

FreeRTOS任务调度介绍

FreeRTOS 操作系统支持三种调度方式:抢占式调度,时间片调度和合作式调度。 实际应用主要是抢占式调度和时间片调度,合作式调度用到的很少 (1)抢占式调度 每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数,比如vTaskDelay,执…

微信小程序image组件mode属性详解

今天学习微信小程序开发的image组件,mode属性的属性值不少,一开始有点整不明白。后来从网上下载了一张图片,把每个属性都试验了一番,总算明白了。现总结归纳如下: 1.使用scaleToFill。这是mode的默认值,sc…

关于Unity的一些基础知识点汇总

1.Prefab实例化后,哪些资源是共用的?哪些资源是拷贝的? 共用资源 脚本组件:实例化后的 Prefab 共享脚本组件的代码。若脚本中无状态数据,多个实例对脚本方法的调用会有相同逻辑。比如一个控制物体移动的脚本&#xff0…

React之旅-02 创建项目

创建React项目,常用的方式有两种: 官方提供的脚手架,官网:https://create-react-app.dev/。如需创建名为 my-app 的项目,请运行如下命令: npx create-react-app my-app 使用Vite包,官网&…

Cursor实战:Web版背单词应用开发演示

Cursor实战:Web版背单词应用开发演示 需求分析自行编写需求文档借助Cursor生成需求文档 前端UI设计后端开发项目结构环境参数数据库设计安装Python依赖运行应用 前端代码修改测试前端界面 测试数据生成功能测试Bug修复 总结 在上一篇《Cursor AI编程助手不完全指南》…

【JavaEE进阶】Spring MVC(3)

欢迎关注个人主页:逸狼 创造不易,可以点点赞吗 如有错误,欢迎指出~ 返回响应 返回静态页面 //RestController Controller RequestMapping("/response") public class ResponseController {RequestMapping("/returnHtmlPage&…

文本操作基础知识:正则表达式

目录 摘要: 一、语法 二、匹配模式pattern 1、普通字符[ ] 2、限定字符 3、定位字符 4、运算字符( ) 三、修饰符flags 四、各语言的正则使用 1、Python的re 参考资料: 摘要: 常用匹配:[A-C]、[^A-C]、\w、\d、\n、\r、…

ROS-相机话题-获取图像-颜色目标识别与定位-目标跟随-人脸检测

文章目录 相机话题获取图像颜色目标识别与定位目标跟随人脸检测 相机话题 启动仿真 roslaunch wpr_simulation wpb_stage_robocup.launch rostopic hz /kinect2/qhd/image_color_rect/camera/image_raw:原始的、未经处理的图像数据。 /camera/image_rect&#xff…

Ubuntu USB耳机找不到设备解决

​ 一. 确定硬件连接 lsusb -t 插拔USB耳机,确定是否有USB识别到 二. 查看输出设备 sudo apt-get install pavucontrol pavucontrol 点击想要使用的输出设备后面的绿色选项 三. 输出设备没有USB耳机时调试 3.1 确认ALSA是否识别设备 列出ALSA播放设备&#…

深入解析「卡顿帧堆栈」 | UWA GPM 2.0 技术细节与常见问题

在游戏开发过程中,卡顿问题一直是影响玩家体验的关键因素。UWA GPM 2.0全新推出的「卡顿帧堆栈」功能,专为研发团队提供精准、高效的卡顿分析方案,能够直观呈现游戏运行时的堆栈信息,助力团队迅速找到性能瓶颈。该功能一经上线&am…

Web3.py 入门笔记

Web3.py 学习笔记 📚 1. Web3.py 简介 🌟 Web3.py 是一个 Python 库,用于与以太坊区块链进行交互。它就像是连接 Python 程序和以太坊网络的桥梁。 官方文档 1.1 主要功能 查询区块链数据(余额、交易等)发送交易与…

点击unity资源文件自动展开左侧的文件路径

背景: 最近从cocos那边转过来的unity同事总是吐糟我们unity选中一个资源后都无法清晰的看到他的文件路径,这给他的工作带来了很多的烦恼,于是我想到昨天刚看到一个unity编辑器下的简易协程实现,通过2个接口Selection.activeObjec…

几种查询本机公网IP的方式

英文网站 bgp.he.net 链接地址:https://bgp.he.net/ bgp.he.net是一个在线工具平台,主要用于查询IP的路由信息,特别是与BGP(边界网关协议)相关的信息。 以下是对bgp.he.net的详细介绍: 一、平台功能 BGP查询:用户可以通过输入IP地址,查询该IP的BGP路由信息,包括AS号…

每日一题——编辑距离

编辑距离 参考资料题目描述示例 解题思路动态规划(DP)方法 代码实现复杂度分析示例详解示例1:"nowcoder" → "new"示例2:"intention" → "execution" 总结与心得 参考资料 建议先参考下…

ChatGPT行业热门应用提示词案例-AI绘画类

AI 绘画指令是一段用于指导 AI 绘画工具(如 DALLE、Midjourney 等)生成特定图像的文本描述。它通常包含场景、主体、风格、色彩、氛围等关键信息,帮助 AI 理解创作者的意图,从而生成符合要求的绘画作品。 ChatGPT 拥有海量的知识…

LearnOpenGL——高级OpenGL(下)

教程地址:简介 - LearnOpenGL CN 高级数据 原文链接:高级数据 - LearnOpenGL CN 在OpenGL中,我们长期以来一直依赖缓冲来存储数据。本节将深入探讨一些操作缓冲的高级方法。 OpenGL中的缓冲本质上是一个管理特定内存块的对象,它…

VScode插件EIDE - 嵌入式开发工具

Embedded IDE - 可以选开源GCC编译器,直接替代Keil;或者用Keil内置的编译器, - 可导入keil的工程,与Keil Assistant插件相比,优势在于可以不用打开Keil改文件架构(增删等) 再吐槽一下富文本编辑…