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);
相关推荐
Ajiang28247353041 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
‘’林花谢了春红‘’6 小时前
C++ list (链表)容器
c++·链表·list
机器视觉知识推荐、就业指导7 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Yang.999 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王9 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_9 小时前
C++自己写类 和 运算符重载函数
c++
六月的翅膀10 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++
liujjjiyun10 小时前
小R的随机播放顺序
数据结构·c++·算法
¥ 多多¥10 小时前
c++中mystring运算符重载
开发语言·c++·算法