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())
运行与验证
- 将上述代码保存为
lidar_converter.py,在终端执行python3 lidar_converter.py - 程序生成
output.pcd文件,并打印转换点数和前两个点坐标 - 预期输出(由代码逻辑唯一确定):
转换完成,共 2 个点,已写入 output.pcd
10.0 0.0 -2.5882(通道0:10m,-15°)
6.0 0.0 0.1047(通道1:6m,1°) - 验证通过条件:
output.pcd文件头部包含FIELDS x y z,点数与打印一致,坐标值符合上述预期
飞控系统接入说明
生成的PCD文件可直接被PCL库(C++)或Open3D(Python)加载。在飞控系统中,建议将本转换函数封装为ROS节点或MAVLink扩展服务,实时接收UDP数据包并发布sensor_msgs/PointCloud2消息。实际传感器接入时,需从UDP套接字接收1248字节完整数据包,并将代码中的offset调整为0(若已剥离以太网头)。