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);
相关推荐
Code out the future11 分钟前
【C++——临时对象,const T&】
开发语言·c++
sam-zy31 分钟前
MFC用List Control 和Picture控件实现界面切换效果
c++·mfc
aaasssdddd961 小时前
C++的封装(十四):《设计模式》这本书
数据结构·c++·设计模式
发呆小天才O.oᯅ1 小时前
YOLOv8目标检测——详细记录使用OpenCV的DNN模块进行推理部署C++实现
c++·图像处理·人工智能·opencv·yolo·目标检测·dnn
qincjun2 小时前
文件I/O操作:C++
开发语言·c++
星语心愿.2 小时前
D4——贪心练习
c++·算法·贪心算法
汉克老师2 小时前
2023年厦门市第30届小学生C++信息学竞赛复赛上机操作题(三、2023C. 太空旅行(travel))
开发语言·c++
single5942 小时前
【c++笔试强训】(第四十一篇)
java·c++·算法·深度优先·图论·牛客
yuanbenshidiaos2 小时前
C++-----函数与库
开发语言·c++·算法
Lenyiin3 小时前
3354. 使数组元素等于零
c++·算法·leetcode·周赛