回顾上篇:在上一篇博客中,我们成功地将 Livox 的 lvx 点云格式转换为了 pcd 格式。今天,我们将基于这一成果,探索如何将 pcd 格式进一步转换为 las 点云格式。
探索 LAS 点云格式
LAS:这一格式是轻侦测和测距(LiDAR)数据的黄金标准,广泛应用于 GIS 和测绘领域。其不仅仅是三维坐标的载体,还能承载如强度、分类代码和颜色等丰富的属性信息。
格式的关键特点:
- 数据多样性:支持包括强度、时间戳等多种数据属性。
- 版本多样:不同版本间兼容性和功能各异。
- 高效存储:压缩版本 LAZ 为文件尺寸的精简提供了可能。
- 广泛应用:在地形分析、城市规划和林业管理等领域发挥着关键作用。
安装步骤:
源自:探索和安装 libLAS 的更多信息,可参考其 GitHub 页面。
简洁明了,只需几步:
mkdir build && cd build/
cmake ..
make
sudo make install
实际应用:使用 libLAS
环境配置
- 操作系统:Ubuntu 20.04
CMakeLists.txt 的配置
根据您的项目需求,您可以轻松修改以下编译目标:
# Import Liblas
find_package(libLAS 1.8.1 REQUIRED)
if(LibLAS_FOUND)
message( STATUS "libLAS_FOUND: " ${libLAS_FOUND})
message( STATUS "libLAS_INCLUDE_DIRS: " ${libLAS_INCLUDE_DIRS})
message( STATUS "libLAS_LIBRARY_DIRS: " ${libLAS_LIBRARY_DIRS})
include_directories(${libLAS_INCLUDE_DIRS})
else()
message(err: libLAS not found)
endif()
# ......
add_executable(main main.cpp ${SOURCE_FILES}) # 添加编译目标
target_link_libraries(main
${libLAS_LIBRARIES}
)
源码:LVX 转 LAS
可以设置累积的帧数,下面的代码会读取 lvx 文件中的每帧点云,每累积 5 帧后保存为一个 LAS 点云。
#include <iostream>
#include <chrono>
#include <thread>
#include <experimental/filesystem>
#include <fstream>
#include <string>
#include <fmt/format.h>
#include <liblas/liblas.hpp>
#ifdef _DEBUG
#pragma comment(lib,"fmtd.lib")
#else
#pragma comment(lib,"fmt.lib")
#endif
#pragma pack(push,1)
struct PcdPoint
{
float x;
float y;
float z;
float intensity;
};
// Public Header结构体
struct PublicHeader {
char file_signature[16];
uint8_t versionA;
uint8_t versionB;
uint8_t versionC;
uint8_t versionD;
char magic_code[4];
// filesignature前10个字节为livox_tech,后6个字节填充为0
//versionA versionB为1,versionC versionD为0,表示此格式的版本
//magiccode为0xAC0EA767
};
// Private Header结构体
struct PrivateHeader {
unsigned int frame_duration;//一帧的持续时间,单位ms
unsigned char device_count;//DeviceInfo的数量
};
// Device Info结构体
struct DeviceInfo {
char lidar_sn[16];
char hub_sn[16];
unsigned char device_index; //Index in device info list
unsigned char device_type; //设备类型 0:lidar hub 1:Mid-40.100 2:Tele-15 4:Horizon
unsigned char extrinsic_enable;//0:禁用外部参数,应在没有外部参数的情况下计算点云; 1:启用外部参数,应使用外部参数计算云点;
float roll;
float pitch;
float yaw;
float x;
float y;
float z;
};
// Frame Header结构体
struct FrameHeader {
long long cur_offset;
long long next_offset;
long long frame_index;
};
// Package结构体
struct Package {
unsigned char device_index;//refer to deviceInfo
uint8_t version;
uint8_t slot_id;
uint8_t lidar_id;
uint8_t reserved;
uint32_t status_code;
uint8_t timestamp_type;
uint8_t data_type;//点云坐标格式
uint64_t timestamp; //ns
};
struct Datatype_0 {
int32_t x;
int32_t y;
int32_t z;
int32_t reflectivity;
};
struct Datatype_1 {
int32_t depth;
int32_t theta;
int32_t phi;
int32_t reflectivity;
};
struct Datatype_2 {
int32_t x;
int32_t y;
int32_t z;
uint8_t reflectivity;
uint8_t tag;
};
struct Datatype_3 {
int32_t depth;
int32_t theta;
int32_t phi;
int32_t reflectivity;
uint8_t tag;
};
struct Datatype_4 {
int32_t x1;
int32_t y1;
int32_t z1;
uint8_t reflectivity1;
uint8_t tag1;
int32_t x2;
int32_t y2;
int32_t z2;
uint8_t reflectivity2;
uint8_t tag2;
};
struct Datatype_5 {
int32_t theta;
int32_t phi;
int32_t depth1;
int32_t reflectivity1;
uint8_t tag1;
int32_t depth2;
int32_t reflectivity2;
uint8_t tag2;
};
struct Datatype_6 {
float gyro_x;
float gyro_y;
float gyro_z;
float acc_x;
float acc_y;
float acc_z;
};
struct Datatype_7 {
int32_t x1; /**< X axis, Unit:mm */
int32_t y1; /**< Y axis, Unit:mm */
int32_t z1; /**< Z axis, Unit:mm */
uint8_t reflectivity1; /**< Reflectivity */
uint8_t tag1; /**< Tag */
int32_t x2; /**< X axis, Unit:mm */
int32_t y2; /**< Y axis, Unit:mm */
int32_t z2; /**< Z axis, Unit:mm */
uint8_t reflectivity2; /**< Reflectivity */
uint8_t tag2; /**< Tag */
int32_t x3; /**< X axis, Unit:mm */
int32_t y3; /**< Y axis, Unit:mm */
int32_t z3; /**< Z axis, Unit:mm */
uint8_t reflectivity3; /**< Reflectivity */
uint8_t tag3; /**< Tag */
};
/** Triple extend spherical coordinate format. */
struct Datatype_8 {
uint16_t theta; /**< Zenith angle[0, 18000], Unit: 0.01 degree */
uint16_t phi; /**< Azimuth[0, 36000], Unit: 0.01 degree */
uint32_t depth1; /**< Depth, Unit: mm */
uint8_t reflectivity1; /**< Reflectivity */
uint8_t tag1; /**< Tag */
uint32_t depth2; /**< Depth, Unit: mm */
uint8_t reflectivity2; /**< Reflectivity */
uint8_t tag2; /**< Tag */
uint32_t depth3; /**< Depth, Unit: mm */
uint8_t reflectivity3; /**< Reflectivity */
uint8_t tag3; /**< Tag */
};
#pragma pack(pop) // 恢复默认的字节对齐方式
void Convert(std::experimental::filesystem::path filepath)
{
std::cout << filepath.string() << std::endl;
auto root = filepath.parent_path();
auto name = filepath.filename().string();
auto dir = root / (name + "_output");
std::experimental::filesystem::create_directories(dir);
int frame_index = 0;
int file_index = 0;
std::vector<PcdPoint> cloud_points;
std::ifstream file(filepath, std::ios::binary); // 打开二进制文件
if (!file) {
std::cout << "无法打开文件." << std::endl;
return;
}
// 读取Public Header
PublicHeader publicHeader;
file.read(reinterpret_cast<char*>(&publicHeader), sizeof(PublicHeader));
// 读取Private Header
PrivateHeader privateHeader;
file.read(reinterpret_cast<char*>(&privateHeader), sizeof(PrivateHeader));
// 读取DeviceInfo
DeviceInfo deviceInfo;
file.read(reinterpret_cast<char*>(&deviceInfo), sizeof(DeviceInfo));
while (file.peek() != EOF) {
long long bytes = 24;//frameheader字节24
// 读取Frame Header
FrameHeader frame_header;
file.read(reinterpret_cast<char*>(&frame_header), sizeof(FrameHeader));
// 计算每个Frame的字节数
uint32_t frame_size = frame_header.next_offset - frame_header.cur_offset;
frame_index++;
while (bytes < frame_size) {
Package package;
file.read(reinterpret_cast<char*>(&package), sizeof(Package));
bytes += 19;
if (package.data_type == 7)
{
for (int i = 0; i < 30; i++)
{
Datatype_7 pointdata;
file.read(reinterpret_cast<char*>(&pointdata), sizeof(Datatype_7));
bytes += sizeof(Datatype_7);
PcdPoint pt;
pt.x = double((pointdata.x1) / 1000.f);
pt.y = double((pointdata.y1) / 1000.f);
pt.z = double((pointdata.z1) / 1000.f);
pt.intensity = pointdata.reflectivity1;
cloud_points.push_back(pt);
}
}
else if (package.data_type == 6)
{
Datatype_6 pointdata;
file.read(reinterpret_cast<char*>(&pointdata), sizeof(Datatype_6));
bytes += sizeof(Datatype_6);
}
else if (package.data_type == 0)
{
for (int i = 0; i < 100; i++)
{
Datatype_0 pointdata;
file.read(reinterpret_cast<char*>(&pointdata), sizeof(Datatype_0));
bytes += sizeof(Datatype_0);
PcdPoint pt;
pt.x = double((pointdata.x) / 1000.f);
pt.y = double((pointdata.y) / 1000.f);
pt.z = double((pointdata.z) / 1000.f);
pt.intensity = pointdata.reflectivity;
cloud_points.push_back(pt);
}
}
else if (package.data_type == 2)
{
for (int i = 0; i < 96; i++)
{
Datatype_2 pointdata;
file.read(reinterpret_cast<char*>(&pointdata), sizeof(Datatype_2));
bytes += sizeof(Datatype_2);
PcdPoint pt;
pt.x = double((pointdata.x) / 1000.f);
pt.y = double((pointdata.y) / 1000.f);
pt.z = double((pointdata.z) / 1000.f);
pt.intensity = pointdata.reflectivity;
cloud_points.push_back(pt);
}
}
}
std::cout << frame_index << std::endl;
if (frame_index == 5)
{
auto filename = fmt::format("{}.las", ++file_index);
auto filepath = dir / filename;
liblas::Header header;
header.SetDataFormatId(liblas::ePointFormat1);
header.SetPointRecordsCount(cloud_points.size());
header.SetScale(0.01, 0.01, 0.01); // 设置点云坐标缩放比例
header.SetOffset(0, 0, 0); // 设置点云坐标偏移量
std::ofstream ofs(filepath, std::ios::out | std::ios::binary);
liblas::Writer writer(ofs, header);
for (const auto &point : cloud_points) {
liblas::Point lasPoint(&header);
lasPoint.SetCoordinates(point.x, point.y, point.z);
writer.WritePoint(lasPoint);
}
writer.WriteHeader(); // 写入文件头
ofs.close();
frame_index = 0;
cloud_points.clear();
}
}
file.close();
}
int main(int argc, char** argv) {
Convert(argv[1]);
return 0;
}
源码:PCD 转 LAS
以下代码片段将展示如何读取 PCD 文件并保存为 LAS 格式:
#include <iostream>
#include <fstream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <liblas/liblas.hpp>
int main() {
// 替换为您的PCD文件路径
std::string pcd_file = "your_pcd_file.pcd";
// 替换为您希望保存的LAS文件路径
std::string las_file = "output.las";
// 读取PCD文件
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>(pcd_file, *cloud) == -1) {
std::cout << "读取PCD文件失败" << std::endl;
return -1;
}
// 准备LAS文件的写入
liblas::Header header;
header.SetDataFormatId(liblas::ePointFormat1);
header.SetPointRecordsCount(cloud->points.size());
header.SetScale(0.01, 0.01, 0.01); // 设置点云坐标缩放比例
header.SetOffset(0, 0, 0); // 设置点云坐标偏移量
std::ofstream ofs(las_file, std::ios::out | std::ios::binary);
if (!ofs.is_open()) {
std::cout << "LAS文件保存失败" << std::endl;
return -2;
}
liblas::Writer writer(ofs, header);
for (const auto &point : cloud->points) {
liblas::Point lasPoint(&header);
lasPoint.SetCoordinates(point.x, point.y, point.z);
writer.WritePoint(lasPoint);
}
writer.WriteHeader(); // 写入文件头
ofs.close();
std::cout << "LAS文件保存成功" << std::endl;
return 0;
}
评论(0)
您还未登录,请登录后发表或查看评论