1. 点云数据读取与格式转换

点云数据读取与格式转换 学习卡片

任务目标

本案例实现从激光雷达传感器读取原始点云数据,并转换为飞控系统可用的标准格式(PCD或PLY)。以Velodyne VLP-16的二进制数据包为例,完成数据解析、坐标计算与PCD文件输出,形成可供后续飞控避障/建图模块直接加载的标准点云文件。

环境依赖

  • Python 3.8+,仅使用标准库(struct, math, os),无需第三方库
  • 输入数据:VLP-16原始数据包(1206字节/包,含42字节以太网头 + 1206字节UDP载荷),本案例提供模拟数据生成函数用于验证

核心实现

VLP-16每个数据包包含12个数据块,每块含32个距离点(16通道×2次回波),距离单位为2mm。水平角由块内首字节给出(分辨率0.01°),垂直角由通道号查表确定。转换公式:x = d·cos(v)·sin(h),y = d·cos(v)·cos(h),z = d·sin(v),其中d为距离(米),v为垂直角,h为水平角。

import struct, math, os

# VLP-16 垂直角查找表(度)
VERT_ANGLES = [-15, 1, -13, 3, -11, 5, -9, 7, -7, 9, -5, 11, -3, 13, -1, 15]

def parse_vlp16_packet(packet: bytes):
    """解析单个VLP-16数据包,返回[(x,y,z), ...]"""
    points = []
    # 跳过42字节以太网头,从UDP载荷开始(实际使用时按需截取)
    offset = 42
    for block in range(12):
        # 每块:2字节flag + 2字节azimuth + 32×2字节距离
        base = offset + block * 100
        flag, azimuth_raw = struct.unpack_from('<HH', packet, base)
        if flag != 0xEEFF:
            continue
        azimuth = azimuth_raw * 0.01  # 度
        for chan in range(32):
            dist_raw = struct.unpack_from('<H', packet, base + 4 + chan*2)[0]
            if dist_raw == 0:
                continue
            dist_m = dist_raw * 0.002  # 2mm单位转米
            v_rad = math.radians(VERT_ANGLES[chan % 16])
            # 同一通道两次回波共享同一水平角(简化处理)
            h_rad = math.radians(azimuth)
            x = dist_m * math.cos(v_rad) * math.sin(h_rad)
            y = dist_m * math.cos(v_rad) * math.cos(h_rad)
            z = dist_m * math.sin(v_rad)
            points.append((round(x, 4), round(y, 4), round(z, 4)))
    return points

def write_pcd(points, filename):
    """将点列表写入ASCII格式PCD文件"""
    with open(filename, 'w') as f:
        f.write('# .PCD v0.7 - Point Cloud Data file format\n')
        f.write('VERSION 0.7\nFIELDS x y z\nSIZE 4 4 4\n')
        f.write('TYPE F F F\nCOUNT 1 1 1\n')
        f.write(f'WIDTH {len(points)}\nHEIGHT 1\nVIEWPOINT 0 0 0 1 0 0 0\n')
        f.write(f'POINTS {len(points)}\nDATA ascii\n')
        for p in points:
            f.write(f'{p[0]} {p[1]} {p[2]}\n')

def generate_test_packet():
    """生成含2个有效点的模拟数据包(用于验证)"""
    packet = bytearray(1248)  # 42 + 1206
    # 构造第一个数据块
    offset = 42
    struct.pack_into('<H', packet, offset, 0xEEFF)      # flag
    struct.pack_into('<H', packet, offset+2, 9000)      # 水平角 90.00°
    # 通道0:距离 5000×2mm = 10m,垂直角 -15°
    struct.pack_into('<H', packet, offset+4, 5000)
    # 通道1:距离 3000×2mm = 6m,垂直角 1°
    struct.pack_into('<H', packet, offset+6, 3000)
    return bytes(packet)

if __name__ == '__main__':
    # 生成模拟数据并转换
    test_packet = generate_test_packet()
    pts = parse_vlp16_packet(test_packet)
    write_pcd(pts, 'output.pcd')
    print(f'转换完成,共 {len(pts)} 个点,已写入 output.pcd')
    # 验证:读取并显示前2个点
    with open('output.pcd') as f:
        lines = f.readlines()
    for line in lines[-2:]:
        print(line.strip())

运行与验证

  1. 将上述代码保存为 lidar_converter.py,在终端执行 python3 lidar_converter.py
  2. 程序生成 output.pcd 文件,并打印转换点数和前两个点坐标
  3. 预期输出(由代码逻辑唯一确定):
    转换完成,共 2 个点,已写入 output.pcd
    10.0 0.0 -2.5882(通道0:10m,-15°)
    6.0 0.0 0.1047(通道1:6m,1°)
  4. 验证通过条件:output.pcd 文件头部包含 FIELDS x y z,点数与打印一致,坐标值符合上述预期

飞控系统接入说明

生成的PCD文件可直接被PCL库(C++)或Open3D(Python)加载。在飞控系统中,建议将本转换函数封装为ROS节点或MAVLink扩展服务,实时接收UDP数据包并发布sensor_msgs/PointCloud2消息。实际传感器接入时,需从UDP套接字接收1248字节完整数据包,并将代码中的offset调整为0(若已剥离以太网头)。