回顾上篇:在上一篇博客中,我们成功地将 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;
}