当前位置: 首页 > article >正文

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自身预留的字段。

后记

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

相关文章:

Matlab写入点云数据到Rosbag

最近有需要读取一个点云并做处理后,重新写回rosbag。网上有很多读取的教程,但没有写入。自己写入时也遇到了很多麻烦,踩了一堆坑进行记录。 1. rosbag中一个lidar的msg有哪些信息? 通过如下代码,先读取一个rosbag的l…...

数据分析--数据清洗

一、数据清洗的重要性:数据质量决定分析成败 1.1 真实案例警示 电商平台事故:2019年某电商大促期间,因价格数据未清洗导致错误标价,产生3000万元损失医疗数据分析:未清洗的异常血压值(如300mmHg&#xff…...

iNeuOS工业互联网操作系统,民爆远程运维平台案例

iNeuOS工业互联网操作系统,民爆远程运维平台案例 目 录 1. 概述... 2 2. iNeuOS在民爆生产厂区和北京运维中心配置... 3 1.1 生产厂区配置... 3 1.2 运维中心配置... 7 1. 概述 针对本项目进行初步调研,项目的总体需求为满足新建…...

用命令模式设计一个JSBridge用于JavaScript与Android交互通信

用命令模式设计一个JSBridge用于JavaScript与Android交互通信 在开发APP的过程中,通常会遇到Android需要与H5页面互相传递数据的情况,而Android与H5交互的容器就是WebView。 因此要想设计一个高可用的 J S B r i d g e JSBridge JSBridge,不…...

Vue 3最新组件解析与实践指南:提升开发效率的利器

目录 引言 一、Vue 3核心组件特性解析 1. Composition API与组件逻辑复用 2. 内置组件与生命周期优化 3. 新一代UI组件库推荐 二、高级组件开发技巧 1. 插件化架构设计 2. 跨层级组件通信 三、性能优化实战 1. 惰性计算与缓存策略 2. 虚拟滚动与列表优化 3. Tree S…...

计算机网络(涵盖OSI,TCP/IP,交换机,路由器,局域网)

一、网络通信基础 (一)网络通信的概念 网络通信是指终端设备之间通过计算机网络进行的信息传递与交流。它类似于现实生活中的物品传递过程:数据(物品)被封装成报文(包裹),通过网络…...

JVM-Java程序的运行环境

Java Virtual Machine Java程序的运行环境 JVM组成 程序计数器 线程私有的,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。 Java堆 线程共享的区域: 主要用来保存对象实例, 数组等, 当堆中没有内存空间可分配给实例也无法再扩展时, 则抛出OutOfMe…...

什么是网关,网关的作用是什么?网络安全零基础入门到精通实战教程!

1. 什么是网关 网关又称网间连接器、协议转换器,也就是网段(局域网、广域网)关卡,不同网段中的主机不能直接通信,需要通过关卡才能进行互访,比如IP地址为192.168.31.9(子网掩码:255.255.255.0)和192.168.7.13(子网掩码…...

makefile+LSF

LSF LSF(Load Sharing Facility)是一种常用的集群作业调度系统,bsub 命令用于提交作业到 LSF 集群,而若要关闭(终止)一个正在运行的作业,需要使用 bkill 命令,下面为你详细介绍相关…...

《千恋万花》无广版手游安卓苹果免费下载直装版

自取https://pan.xunlei.com/s/VOJS77k8NDrVawqcOerQln2lA1?pwdn6k8 《千恋万花》:柚子社的和风恋爱杰作 《千恋万花》(Senren * Banka)是由日本知名美少女游戏品牌柚子社(Yuzusoft)于2016年推出的一款和风恋爱题材…...

javaEE-14.spring MVC练习

目录 1.加法计算器 需求分析: 前端页面代码: 后端代码实现功能: 调整前端页面代码: 进行测试: 2.用户登录 需求分析: 定义接口: 1.登录数据校验接口: 2.查询登录用户接口: 前端代码: 后端代码: 调整前端代码: 测试/查错因 后端: 前端: lombok工具 1.引入依赖…...

rabbitmq五种模式的实现——springboot

rabbitmq五种模式的实现——springboot 基础知识和javase的实现形式可以看我之前的博客 代码地址:https://github.com/9lucifer/rabbitmq4j-learning 一、进行集成 (一)Spring Boot 集成 RabbitMQ 概述 Spring Boot 提供了对 RabbitMQ 的自…...

23. AI-大语言模型-DeepSeek赋能开发-Spring AI集成

文章目录 前言一、Spring AI 集成 DeepSeek1. 开发AI程序2. DeepSeek 大模型3. 集成 DeepSeek 大模型1. 接入前准备2. 引入依赖3. 工程配置4. 调用示例5. 小结 4. 集成第三方平台(已集成 DeepSeek 大模型)1. 接入前准备2. POM依赖3. 工程配置4. 调用示例…...

Educational Codeforces Round 174 (Rated for Div. 2)(ABCD)

A. Was there an Array? 翻译: 对于整数数组 ​,我们将其相等特征定义为数组 ,其中,如果数组 a 的第 i 个元素等于其两个相邻元素,则 ;如果数组 a 的第 i 个元素不等于其至少一个相邻元素,则 …...

如何在本机上模拟IP地址

如何在本机上模拟IP地址 前言 在某些开发或测试场景中,我们可能需要在本机上模拟一个指定的 IP 地址,并让局域网内的其他设备能够通过该 IP 访问本机提供的服务(如 Web 服务)。 本文将详细介绍如何在 Windows 和 macOS 系统上实…...

C++二叉树:数据的“家族树”与高效检索的奥秘

C二叉树:数据的“家族树”与高效检索的奥秘 开篇小故事:图书馆的“智能目录” 想象一座古老的图书馆,藏书百万,却能在几秒内找到任意一本书。 秘密在于它的“智能目录系统”——一本巨大的家族树状手册: 每本书按主题…...

深入解析 Vue 项目中的缓存刷新机制:原理与实战

目录 前言1. Demo2. 知识拓展 前言 在 Vue 项目中,缓存通常用于存储用户信息、角色权限、系统设置等,以提高页面加载速度并减少 API 请求 这里使用 web-storage-cache 作为封装的本地存储工具,支持 localStorage 和 sessionStorage 方式存储…...

【嵌入式Linux应用开发基础】进程间通信(1):管道

目录 一、管道的基本概念 二、管道的工作原理 三、管道的类型 3.1. 匿名管道(Anonymous Pipe) 3.2. 命名管道(Named Pipe,FIFO) 四、管道的读写规则 4.1. 匿名管道的读写规则 4.2. 命名管道的读写规则 五、管…...

【DeepSeek】Mac m1电脑部署DeepSeek

一、电脑配置 个人电脑配置 二、安装ollama 简介:Ollama 是一个强大的开源框架,是一个为本地运行大型语言模型而设计的工具,它帮助用户快速在本地运行大模型,通过简单的安装指令,可以让用户执行一条命令就在本地运…...

DHCP详解,网络安全零基础入门到精通实战教程!

一、DHCP简介 DHCP(Dynamic Host Configuration Protocol),动态主机配置协议,是一个应用层协议。当我们将客户主机ip地址设置为动态获取方式时,DHCP服务器就会根据DHCP协议给客户端分配IP,使得客户机能够利用这个IP上网。 DHCP前身是BOOTP&am…...

蓝桥杯篇---IAP15F2K61S2中断

文章目录 前言简介中断源1.外部中断2.定时器中断3.串口中断4.ADC中断5.PCA中断6.SPI中断7.PWM中断 中断优先级中断相关寄存器1.IE2.IP3.TCON4.SCON 中断使用步骤1.配置中断源2.使能中断3.设置优先级4.编写中断服务程序5.清除中断标志 示例代码:外部中断使用示例代码…...

【Prometheus】prometheus结合pushgateway实现脚本运行状态监控

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…...

立创实战派ESP32-S3烧录小智AI指南

小智 AI 聊天机器人-开源项目介绍 本项目是一个开源项目,主要用于教学目的。我们希望通过这个项目,能够帮助更多人入门 AI 硬件开发,了解如何将当下飞速发展的大语言模型应用到实际的硬件设备中。无论你是对 AI 感兴趣的学生,还是…...

深度学习的集装箱箱号OCR识别技术,识别率99.9%

集装箱箱号OCR识别技术是一项结合计算机视觉和规则校验的复杂任务,以下是其关键要点及实现思路的总结: 1、集装箱号结构:11位字符,格式为公司代码(3字母)和序列号(6数字)以及校验码(1数字)和尺寸/类型代码(可选),例如…...

使用 PyTorch 实现标准卷积神经网络(CNN)

卷积神经网络(CNN)是深度学习中的重要组成部分,广泛应用于图像处理、语音识别、视频分析等任务。在这篇博客中,我们将使用 PyTorch 实现一个标准的卷积神经网络(CNN),并介绍各个部分的作用。 什…...

Casbin 权限管理介绍及在 Go 语言中的使用入门

引言 在现代软件开发过程中,权限管理是一个至关重要的环节,它关系到系统的安全性和用户体验。Casbin 是一个强大的访问控制库,支持多种访问控制模型,如 ACL(访问控制列表)、RBAC(基于角色的访问…...

如何在Windows下使用Ollama本地部署DeepSeek R1

参考链接: 通过Ollama本地部署DeepSeek R1以及简单使用的教程(超详细) 【DeepSeek应用】DeepSeek R1 本地部署(OllamaDockerOpenWebUI) 如何将 Chatbox 连接到远程 Ollama 服务:逐步指南 首先需要安装oll…...

【分布式理论12】事务协调者高可用:分布式选举算法

文章目录 一、分布式系统中事务协调的问题二、分布式选举算法1. Bully算法2. Raft算法3. ZAB算法 三、小结与比较 一、分布式系统中事务协调的问题 在分布式系统中,常常有多个节点(应用)共同处理不同的事务和资源。前文 【分布式理论9】分布式…...

详细介绍Tess4J的使用:从PDF到图像的OCR技术实现

在当今的数字化时代,OCR(光学字符识别)技术被广泛应用于文档扫描、图片文字识别以及其他自动化数据提取任务。Tesseract作为一款强大的开源OCR引擎,在处理图像和PDF中的文本提取方面具有非常高的准确度和效率。本文将详细介绍如何…...

postgres源码学习之简单sql查询

postgres源码学习之sql查询 sql查询的主流程读取sql解析sql重写sql获得执行计划执行查询操作结果返回 sql查询的主流程 参考postgres的处理流程 由上一节,我们可以看到,当有新的连接通过权限认证之后,将进入等待接收sql语句,并执…...