转载自个人博客: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);