Open3D实现点云数据的序列化与网络传输
转载自个人博客:Open3D实现点云数据的序列化与网络传输

在处理点云数据的时候,有时候需要实现点云数据的远程传输。当然可以利用传输文件的方法直接把点云数据序列化成数据流进行传输,但Open3D源码在实现RPC功能时就提供了一套序列化及传输的方法及思路,那我们就可以搬过来化为己用。
其中利用Open3D方法序列化点云最为重要,总的流程为根据需要进行有损压缩、序列化、根据需要进行无损压缩、网络传输,即:根据需要使用2.1、1.1、根据需要使用2.2、3.1。
本文全文使用C++实现,另外,本文参考的Open3D源码是V0.18.0版本,若想查看完整Open3D源码,请自行Down\Clone:Open3D Github
1. 序列化(重点)
1.1 实现
Open3D在实现其RPC功能时提供了标准类型open3d::io::rpc::messages::SetMeshData,至于为什么要标准类型,是方便msgpack进行序列化。
那么流程即为:先将点云数据open3d::geometry::PointCloud转化为这个类型,再使用msgpack进行序列化。
将PointCloud转化为标准数据类型SetMeshData是需要逐步转化的,我在这里用一个函数封装,具体代码见下:
#include "open3d/geometry/PointCloud.h"
#include "open3d/io/rpc/Messages.h"/// 将需要序列化的目标点云数据pcd设定为:
/// std::shared_ptr<open3d::geometry::PointCloud> pcd;/// 定义的结构体,用于使用msgpack对其进行序列化
/// 可以添加其他需要一起发送的标准数据,一起序列化
struct PACK{/// Point cloud dataopen3d::io::rpc::messages::SetMeshData pcdMesh;/// Text datastd::vector<std::string> text;/// 用于msgpack序列化,只支持标准数据(std)MSGPACK_DEFINE_ARRAY(pcdMesh, text);
};PACK serialization(std::shared_ptr<open3d::geometry::PointCloud> pcd)
{PACK pack = {{}, {}};if(pcd == nullptr) return pack;pack.pcdMesh.path = "";pack.pcdMesh.time = 0;pack.pcdMesh.layer = "";/// pcd->points_pack.pcdMesh.data.vertices = open3d::io::rpc::messages::Array::FromPtr((double*)pcd->points_.data(), {int64_t(pcd->points_.size()), 3});/// pcd->normals_if(pcd->HasNormals()){pack.pcdMesh.data.vertex_attributes["normals"] =open3d::io::rpc::messages::Array::FromPtr((double*)pcd->normals_.data(), {int64_t(pcd->normals_.size()), 3});}/// pcd->colors_if(pcd->HasColors()){pack.pcdMesh.data.vertex_attributes["colors"] =open3d::io::rpc::messages::Array::FromPtr((double*)pcd->colors_.data(), {int64_t(pcd->colors_.size()), 3});}return pack;
}void main(){// std::shared_ptr<open3d::geometry::PointCloud> pcd;.../// 1.将PointCloud转化为标准数据类型SetMeshDataPACK pack = serialization(pcd);/// 2.将SetMeshData所在结构体用msgpack序列化msgpack::sbuffer sbuf;msgpack::pack(sbuf, pack); /// 得到序列化后的数据sbuf...
}
1.2 源码参考(可跳过)
Open3D源码中对处理点云数据的声明定义:
using namespace open3d::utility;namespace open3d {
namespace io {
namespace rpc {bool SetPointCloud(const geometry::PointCloud& pcd,const std::string& path,int time,const std::string& layer,std::shared_ptr<ConnectionBase> connection) {// TODO use SetMeshData here after switching to the new PointCloud class.if (!pcd.HasPoints()) {LogInfo("SetMeshData: point cloud is empty");return false;}messages::SetMeshData msg;msg.path = path;msg.time = time;msg.layer = layer;msg.data.vertices = messages::Array::FromPtr((double*)pcd.points_.data(), {int64_t(pcd.points_.size()), 3});if (pcd.HasNormals()) {msg.data.vertex_attributes["normals"] =messages::Array::FromPtr((double*)pcd.normals_.data(),{int64_t(pcd.normals_.size()), 3});}if (pcd.HasColors()) {msg.data.vertex_attributes["colors"] = messages::Array::FromPtr((double*)pcd.colors_.data(), {int64_t(pcd.colors_.size()), 3});}msgpack::sbuffer sbuf;messages::Request request{msg.MsgId()};msgpack::pack(sbuf, request);msgpack::pack(sbuf, msg);/// 得到序列化后的数据sbuf/// 之后是网络传输的部分zmq::message_t send_msg(sbuf.data(), sbuf.size());if (!connection) {connection = std::shared_ptr<Connection>(new Connection());}auto reply = connection->Send(send_msg);return ReplyIsOKStatus(*reply);
}}
那么使用即为:
/// 网络传输Connection的申明定义见3
auto connection = std::make_shared<Connection>("tcp://127.0.0.1:51454", 500, 500);
ASSERT_TRUE(SetPointCloud(pcd, "", 0, "", connection));
2. 压缩
一般,需要显示完全场景的点云数据都占用较大内存,如果像我这样需要实时传输点云数据以实时更新场景的情况,那对点云数据进行压缩处理就有利于应对更多的网络场景。
Open3D自身实现的下采样(有损压缩)和第三方实现的无损压缩可以同时使用。
2.1 下采样(有损压缩)
Open3D本身就提供了多种下采样方法,通过对原生点云数据再采样,减少点的个数,即减小点云数据的大小。
详细的各种下采样方法见我另一篇文章:点云下采样有损压缩
这里以体素下采样为例:
int voxelSize = 0.002; // 设置体素的尺寸大小
pcd = pcd->VoxelDownSample(voxelSize);
2.2 无损压缩
既然前面已经实现数据的序列化了,那就可以用其他常用的压缩库进行压缩,比如zlib。
要注意的是,这种压缩会消耗一定的CPU资源,占用一定时间。
zlib官方文档:zlib 1.3.1 Manual
其提供了几种压缩等级(level):
#define Z_NO_COMPRESSION 0 #define Z_BEST_SPEED 1 #define Z_BEST_COMPRESSION 9 #define Z_DEFAULT_COMPRESSION (-1)
完整代码如下:
#include "zlib.h"/// 序列化内容见上一节,这里略
/int compress(const std::string &input, std::string &output, int level)
{/// Estimate the maximum value of the compressed size and allocate spaceuLongf compressedMaxSize = compressBound(input.size());output.resize(compressedMaxSize);/// Compress and get the real sizeint result = compress2((Bytef *)output.data(), &compressedMaxSize,(const Bytef *)input.data(), input.size(),level);if (result != Z_OK) return result;/// Reallocate the real spaceoutput.resize(compressedMaxSize);return Z_OK;
}void main(){// std::shared_ptr<open3d::geometry::PointCloud> pcd;.../// 1.对原点云数据进行下采样(有损压缩)pcd = pcd->VoxelDownSample(0.002);/// 2.将PointCloud转化为标准数据类型SetMeshDataPACK pack = serialization(pcd);/// 3.将SetMeshData所在结构体用msgpack序列化msgpack::sbuffer sbuf;msgpack::pack(sbuf, pack); /// 得到序列化后的数据sbuf/// 4.对数据进行压缩QByteArray byteArray(sbuf.data(), static_cast<int>(sbuf.size()));std::string originalData(byteArray.data(), byteArray.size());std::string compressedData;if(compress(originalData, compressedData, 1) != Z_OK){std::cerr << "Compression failed." << std::endl;}/// 得到压缩后的数据compressedData
}
解压类似:
#include "zlib.h"int decompress(const std::string &input, std::string &output)
{/// Allocate an estimated spacestd::string data(input.size() * 10, '\0');uLongf size = data.size();int result = uncompress((Bytef *)data.data(), &size, (const Bytef *)input.data(), input.size());if (result != Z_OK) return result;output.assign(data.data(), size);return Z_OK;
}
3. 网络传输
我使用的是nanomsg,比源码中使用的ZeroMQ好用,当然,对数据流的传输可以使用其他的各种方法。
3.1 本人使用的传输方法
我使用的是nanomsg进行网络传输,nanomsg的使用参考我其他的文章。
这里的代码如下:
#include <nanomsg/nn.h>
#include <nanomsg/bus.h>/// 序列化内容和压缩内容见上两节,这里略
/
void main(){// std::shared_ptr<open3d::geometry::PointCloud> pcd;.../// 1.对原点云数据进行下采样(有损压缩)pcd = pcd->VoxelDownSample(0.002);/// 2.将PointCloud转化为标准数据类型SetMeshDataPACK pack = serialization(pcd);/// 3.将SetMeshData所在结构体用msgpack序列化msgpack::sbuffer sbuf;msgpack::pack(sbuf, pack); /// 得到序列化后的数据sbuf/// 4.对数据进行压缩QByteArray byteArray(sbuf.data(), static_cast<int>(sbuf.size()));std::string originalData(byteArray.data(), byteArray.size());std::string compressedData;if(compress(originalData, compressedData, 1) != Z_OK){std::cerr << "Compression failed." << std::endl;}/// 得到压缩后的数据compressedData/// 5.将压缩后的数据发送int socket = nn_socket(AF_SP, NN_BUS);nn_bind(socket, "ws://127.0.0.1:55555");if(nn_send(socket, compressedData.data(), compressedData.size(),0) < 0){std::cerr << "Send error" << std::endl;}/// 5.如果不需要压缩,直接发送序列化的数据if(nn_send(socket, sbuf.data(), sbuf.size(),0) < 0){std::cerr << "Send error" << std::endl;}
}
3.2 源码中的方法
Open3D使用ZeroMQ库进行网络传输
Open3D源码中对网络传输的声明定义:
using namespace open3d::utility;namespace open3d {
namespace io {
namespace rpc {Connection::Connection(): Connection(defaults.address, defaults.connect_timeout, defaults.timeout) {
}Connection::Connection(const std::string& address,int connect_timeout,int timeout): context_(GetZMQContext()), // GetZMQContext()在别处定义、实现socket_(new zmq::socket_t(*GetZMQContext(), ZMQ_REQ)),address_(address),connect_timeout_(connect_timeout),timeout_(timeout) {socket_->set(zmq::sockopt::linger, timeout_);socket_->set(zmq::sockopt::connect_timeout, connect_timeout_);socket_->set(zmq::sockopt::rcvtimeo, timeout_);socket_->set(zmq::sockopt::sndtimeo, timeout_);socket_->connect(address_.c_str());
}Connection::~Connection() { socket_->close(); }std::shared_ptr<zmq::message_t> Connection::Send(zmq::message_t& send_msg) {if (!socket_->send(send_msg, zmq::send_flags::none)) {zmq::error_t err;if (err.num()) {LogInfo("Connection::send() send failed with: {}", err.what());}}std::shared_ptr<zmq::message_t> msg(new zmq::message_t());if (socket_->recv(*msg)) {LogDebug("Connection::send() received answer with {} bytes",msg->size());} else {zmq::error_t err;if (err.num()) {LogInfo("Connection::send() recv failed with: {}", err.what());}}return msg;
}std::shared_ptr<zmq::message_t> Connection::Send(const void* data,size_t size) {zmq::message_t send_msg(data, size);return Send(send_msg);
}
}
那么使用即为:
auto connection = std::make_shared<Connection>("tcp://127.0.0.1:51454", 500, 500);/// 序列化内容见1
/// 得到序列化后的数据sbufzmq::message_t send_msg(sbuf.data(), sbuf.size());
auto reply = connection->Send(send_msg);
相关文章:
Open3D实现点云数据的序列化与网络传输
转载自个人博客:Open3D实现点云数据的序列化与网络传输 在处理点云数据的时候,有时候需要实现点云数据的远程传输。当然可以利用传输文件的方法直接把点云数据序列化成数据流进行传输,但Open3D源码在实现RPC功能时就提供了一套序列化及传输的…...
【C++11】右值引用
前言: 在C11中引入的右值引用(rvalue references)是现代C的一个重要特性,它允许开发者以更高效的方式处理临时对象(右值),避免不必要的拷贝,提升性能。右值引用通常与C11的**移动语义…...
CSS元素显示类型
display 属性是 CSS 中最重要的属性之一,主要用来控制元素的布局,通过 display 属性您可以设置元素是否显示以及如何显示。 根据元素类型的不同,每个元素都有一个默认的 display 属性值,例如<div>默认的 display 属性值为 …...
Flink 介绍(特性、概念、故障容错、运维部署、应用场景)
概述 特性 概念 数据流 状态 时间 savepoint 故障容错 运维部署 部署应用到任意地方 Flink能够更方便地升级、迁移、暂停、恢复应用服务 监控和控制应用服务 运行任意规模应用 应用场景 事件驱动型应用 什么是事件驱动型应用? 事件驱动型应用的优势 Flink如何…...
Python+Flask接口判断身份证省份、生日、性别、有效性验证+docker部署+Nginx代理运行
这里写目录标题 一、接口样式二、部署流程2.1 镜像打包2.1.1 准备工作2.1.2 build打包2.1.3 dokcer部署运行2.1.4 Nginx代理 三、代码及文件3.1 index.py3.2 areaCodes.json3.3 Dockerfile 一、接口样式 https://blog.henryplus.cn/idcardApi/idCard/query?idcard{idcard} 二、…...
门店收银营销活动打折特价-收银系统源码
1.功能描述 功能描述:连锁店总部/门店可以将商品设置第二件打折,如保温杯第一件10元,第二件5折; 2.适用场景 ☑新店开业、门店周年庆、节假日等特定时间促销; ☑会员拉新,设置会员专享套餐; …...
QTabWidget的每个tab居中显示图标和文本
使用QTabWidget,给每个tab添加了图标之后,文字和图标之间有间距,没有完美居中显示。 遇到此问题,尝试了多种办法,均不理想,最终自定义QTabBar,重绘tab,完美解决。 #include <QT…...
Ubuntu20.04如何安装Microsoft Edge浏览器?
Microsoft Edge是由微软开发的一款网页浏览器,首次发布于2015年,作为Windows 10操作系统的默认浏览器,取代了之前的Internet Explorer。 基于Chromium内核:自2019年起,Microsoft Edge转向了使用开源的Chromium内核,这使得它与Google Chrome在性能和兼容性方面有很多相似之…...
美团Java一面
美团Java一面 9.24一面,已经寄了 收到的第一个面试,表现很不好 spring bean生命周期 作用域(忘完了) 为什么用redis缓存 redis和数据库的缓存一致性问题 redis集群下缓存更新不一致问题 aop说一下 arraylist和linkedlist 数据库的…...
C#中ref关键字和out关键字
值传递和引用传递 值传递和引用传递是编程中涉及数据传递的两种方式。它们的主要区别在于数据是如何在函数或方法之间传递的。 值传递 值传递意味着当你把一个变量传递给一个函数时,实际上传递的是这个变量的值的一个拷贝。也就是说,函数内部对这个参数…...
贴吧软件怎么切换ip
在网络使用中,有时我们需要切换IP地址来满足特定的需求,比如需要切换贴吧软件IP以进行不同的操作。本文将介绍几种贴吧切换IP地址的方法,帮助用户更好地管理自己的网络身份和访问权限。 1、更换网络环境 通过连接到不同的Wi-Fi网络或使用移…...
图像分割恢复方法
传统的图像分割方法主要依赖于图像的灰度值、纹理、颜色等特征,通过不同的算法将图像分割成多个区域。这些方法通常可以分为以下几类: 1.基于阈值的方法 2.基于边缘的方法 3.基于区域的方法 4.基于聚类的方法 下面详细介绍这些方法及其示例代码。 1. 基…...
Ultralytics:YOLO11使用教程
Ultralytics:YOLO11使用教程 前言相关介绍前提条件实验环境安装环境项目地址LinuxWindows YOLO11使用教程进行目标检测进行实例分割进行姿势估计进行旋转框检测进行图像分类 参考文献 前言 由于本人水平有限,难免出现错漏,敬请批评改正。更多…...
前缀和算法——优选算法
个人主页:敲上瘾-CSDN博客 个人专栏:游戏、数据结构、c语言基础、c学习、算法 一、什么是前缀和? 前缀和是指从数组的起始位置到某一位置(或矩阵的某个区域)的所有元素的和。这种算法通过预处理数组或矩阵,…...
YOLO11改进|注意力机制篇|引入HAT超分辨率重建模块
目录 一、HAttention注意力机制1.1HAttention注意力介绍1.2HAT核心代码 二、添加HAT注意力机制2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、HAttention注意力机制 1.1HAttention注意力介绍 HAT模型 通过结合卷积特征提取与多尺度注意…...
老牛也想吃嫩草,思科为何巨资投入云初创CoreWeave?
【科技明说 | 科技热点关注】 当我看到前些天思科(Cisco)的新闻时笑了。业内朋友对我说,老牛也想吃嫩草,人之常情尔,都是为了好好活着。 作为全球著名的网络产品巨头,思科Cisco论是遭遇到何种市场与行业巨变ÿ…...
Spring Boot 事务管理入门
在 Spring Boot 应用中,事务管理是一个至关重要的方面,它确保了数据的一致性和完整性。本文将深入探讨 Spring Boot 中事务管理的机制、使用方法以及注意事项,并提供丰富的示例代码。 其它教程: mysql事务详解 一、事务基础概念…...
20年408数据结构
第一题: 解析:这种题可以先画个草图分析一下,一下就看出来了。 这里的m(7,2)对应的是这图里的m(2,7),第一列存1个元素,第二列存2个元素,第三列存3个元素,第四列存4个元素,第五列存5个元素&#…...
4反馈、LC、石英、RC振荡器
1什么是振荡器? 我们看看振荡器在无线通信中扮演什么角色? 1)无线通信的波是指电磁波。 2)电磁波的频率高于100KHz才能在空气中传播。 3)空气中的高频电磁波的相位和振幅可以排列组合包含信息。 4)无…...
go 的 timer reset
在 Go 语言 1.23 版本之前,与Timer(定时器)关联的通道是异步的(有缓冲,容量为 1)。这意味着即使在调用Timer.Stop(停止定时器)或Timer.Reset(重置定时器)并返…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
