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是需要逐步转化的,我在这里用一个函数封装,具体代码见下:

c++ 复制代码
#include "open3d/geometry/PointCloud.h"
#include "open3d/io/rpc/Messages.h"

/// 将需要序列化的目标点云数据pcd设定为:
/// std::shared_ptr<open3d::geometry::PointCloud> pcd;

/// 定义的结构体,用于使用msgpack对其进行序列化
/// 可以添加其他需要一起发送的标准数据,一起序列化
struct PACK{
	/// Point cloud data
	open3d::io::rpc::messages::SetMeshData pcdMesh;

	/// Text data
	std::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转化为标准数据类型SetMeshData
    PACK pack = serialization(pcd);
    /// 2.将SetMeshData所在结构体用msgpack序列化
    msgpack::sbuffer sbuf;
    msgpack::pack(sbuf, pack); 
    /// 得到序列化后的数据sbuf
    ...
}

1.2 源码参考(可跳过)

Open3D源码中对处理点云数据的声明定义:

c++ 复制代码
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);
}
    
}

那么使用即为:

c++ 复制代码
/// 网络传输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本身就提供了多种下采样方法,通过对原生点云数据再采样,减少点的个数,即减小点云数据的大小。

详细的各种下采样方法见我另一篇文章:点云下采样有损压缩

这里以体素下采样为例:

c++ 复制代码
int voxelSize = 0.002; // 设置体素的尺寸大小
pcd = pcd->VoxelDownSample(voxelSize);

2.2 无损压缩

既然前面已经实现数据的序列化了,那就可以用其他常用的压缩库进行压缩,比如zlib。

要注意的是,这种压缩会消耗一定的CPU资源,占用一定时间。

zlib官方文档:zlib 1.3.1 Manual

其提供了几种压缩等级(level):

c++ 复制代码
#define Z_NO_COMPRESSION         0
#define Z_BEST_SPEED             1
#define Z_BEST_COMPRESSION       9
#define Z_DEFAULT_COMPRESSION  (-1)

完整代码如下:

c++ 复制代码
#include "zlib.h"

/// 序列化内容见上一节,这里略
/

int compress(const std::string &input, std::string &output, int level)
{
    /// Estimate the maximum value of the compressed size and allocate space
    uLongf compressedMaxSize = compressBound(input.size());
    output.resize(compressedMaxSize);

    /// Compress and get the real size
    int result = compress2((Bytef *)output.data(), &compressedMaxSize,
                           (const Bytef *)input.data(), input.size(),
                           level);
    if (result != Z_OK) return result;

    /// Reallocate the real space
    output.resize(compressedMaxSize);
    return Z_OK;
}

void main(){
    // std::shared_ptr<open3d::geometry::PointCloud> pcd;
    ...
    /// 1.对原点云数据进行下采样(有损压缩)
	pcd = pcd->VoxelDownSample(0.002);
    
    /// 2.将PointCloud转化为标准数据类型SetMeshData
    PACK 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
}

解压类似:

c++ 复制代码
#include "zlib.h"

int decompress(const std::string &input, std::string &output)
{
    /// Allocate an estimated space
    std::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的使用参考我其他的文章。

这里的代码如下:

c++ 复制代码
#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转化为标准数据类型SetMeshData
    PACK 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源码中对网络传输的声明定义:

c++ 复制代码
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);
}
}

那么使用即为:

c++ 复制代码
auto connection = std::make_shared<Connection>("tcp://127.0.0.1:51454", 500, 500);

/// 序列化内容见1
/// 得到序列化后的数据sbuf

zmq::message_t send_msg(sbuf.data(), sbuf.size());
auto reply = connection->Send(send_msg);
相关推荐
捕鲸叉4 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer4 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq4 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
青花瓷6 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
幺零九零零7 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
捕鲸叉7 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
Dola_Pan8 小时前
C++算法和竞赛:哈希算法、动态规划DP算法、贪心算法、博弈算法
c++·算法·哈希算法
yanlou2338 小时前
KMP算法,next数组详解(c++)
开发语言·c++·kmp算法
小林熬夜学编程8 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
阿洵Rain9 小时前
【C++】哈希
数据结构·c++·算法·list·哈希算法