自动驾驶系统每秒钟会产生海量的传感器数据。以一辆装备了6个摄像头、3个激光雷达和5个毫米波雷达的自动驾驶汽车为例,在行驶过程中每秒会产生超过2GB的原始数据。这些数据不仅量大,而且来源多样、格式各异。摄像头产生的是图像帧,激光雷达输出点云,毫米波雷达生成目标列表,再加上GPS、IMU等定位数据,传统的数据存储方式很快就会变得难以管理。
我在实际项目中就遇到过这样的困扰:早期我们使用ROS bag格式存储数据,但随着数据量增加,出现了文件过大难以分割、读取效率低下等问题。特别是在回放数据时,经常需要等待很长时间才能定位到特定时间点的数据。后来我们尝试了MCAP格式,这些问题都得到了很好的解决。
MCAP(Modular Container Format)是专为机器人应用设计的标准化数据容器格式。它最大的特点是支持混合数据类型存储,可以把Protobuf序列化的结构化数据、二进制点云、图像帧等非结构化数据都打包到同一个文件中。这就像是一个智能收纳箱,能把各种形状的物品整齐归类存放。
Protobuf(Protocol Buffers)是Google开发的高效序列化工具。相比JSON和XML,它有三大优势特别适合自动驾驶场景:
举个例子,我们定义车辆状态消息时,用Protobuf可以这样写:
protobuf复制syntax = "proto3";
message VehicleState {
uint64 timestamp = 1; // 纳秒时间戳
float speed = 2; // 车速(m/s)
float acceleration = 3;
float steering_angle = 4;
repeated float position = 5; // [x,y,z]坐标
}
这个结构体在内存中只占约32字节,序列化后更小。而如果用JSON表示,光字段名就会占用不少空间。
MCAP文件内部采用分块存储结构,主要由以下几部分组成:
这种结构设计带来了几个实用特性:
建议使用以下工具链:
在Ubuntu系统上可以这样安装依赖:
bash复制sudo apt install build-essential cmake protobuf-compiler
git clone https://github.com/foxglove/mcap.git
cd mcap/cpp && mkdir build && cd build
cmake .. && make && sudo make install
首先需要为各类传感器数据创建.proto文件。这里给出一个综合示例:
protobuf复制syntax = "proto3";
message PointCloud {
uint64 timestamp = 1;
uint32 num_points = 2;
bytes data = 3; // 点云二进制数据
}
message CameraImage {
uint64 timestamp = 1;
uint32 width = 2;
uint32 height = 3;
enum Format {
JPEG = 0;
PNG = 1;
RAW = 2;
}
Format format = 4;
bytes image_data = 5;
}
message VehicleCAN {
uint64 timestamp = 1;
float speed = 2;
float throttle = 3;
float brake = 4;
}
使用protoc编译器生成C++代码:
bash复制protoc --cpp_out=. sensors.proto
下面是核心的数据写入代码片段:
cpp复制#include <mcap/writer.hpp>
#include "sensors.pb.h"
void writeToMcap(const std::string& filename) {
mcap::McapWriter writer;
auto options = mcap::McapWriterOptions("ros1");
writer.open(filename, options);
// 注册Schema
mcap::Schema can_schema("VehicleCAN", "protobuf",
foxglove::BuildFileDescriptorSet(VehicleCAN::descriptor()).SerializeAsString());
writer.addSchema(can_schema);
// 创建数据通道
mcap::Channel can_channel("/vehicle/can", "protobuf", can_schema.id);
writer.addChannel(can_channel);
// 模拟写入CAN数据
VehicleCAN can_msg;
for (int i = 0; i < 1000; ++i) {
can_msg.set_timestamp(std::chrono::nanoseconds(
std::chrono::system_clock::now().time_since_epoch()).count());
can_msg.set_speed(i % 100);
mcap::Message msg;
msg.channelId = can_channel.id;
msg.sequence = i;
msg.publishTime = can_msg.timestamp();
msg.data = reinterpret_cast<const std::byte*>(can_msg.SerializeAsString().data());
msg.dataSize = can_msg.ByteSizeLong();
writer.write(msg);
}
writer.close();
}
Foxglove Studio是专门为机器人数据设计的可视化工具,支持MCAP格式的直接播放。安装后只需:
实测发现几个实用功能:
在长期数据采集中,我们总结出几个优化点:
分块大小:MCAP默认1GB分块,对于SSD可以调整为512MB
cpp复制options.chunkSize = 512 * 1024 * 1024;
压缩选择:Zstd压缩比高但耗CPU,LZ4更轻量
cpp复制options.compression = mcap::Compression::Zstd;
索引间隔:降低索引密度可以节省空间
cpp复制options.noChunkCRC = true; // 关闭CRC校验提升写入速度
多线程写入:对于高频率传感器(如激光雷达)建议单独线程写入
在部署这套方案时,我们踩过几个坑值得注意:
时间同步问题:不同传感器的时间戳可能来自不同时钟源。我们的解决方案是使用PTP协议同步所有设备时钟,并在消息头中添加时钟基准标记。
磁盘IO瓶颈:在NVMe SSD上实测,单个写入线程可以稳定处理200MB/s的数据流。如果遇到卡顿,可以:
数据校验:曾遇到过数据损坏的情况,后来我们添加了定期校验机制:
cpp复制writer.close();
mcap::McapReader reader;
auto status = reader.open(filename);
if (!status.ok()) {
// 文件损坏处理
}
这套系统目前已经稳定运行超过1年,累计存储了超过500TB的自动驾驶数据。MCAP格式的可靠性得到了充分验证,特别是在数据回放和分析阶段,相比之前方案效率提升了3倍以上。