
上篇我们聊了激光雷达的三种测距原理——ToF、三角测距和相位法。但拿到原始数据之后怎么处理很多候选人能说出原理一上手就懵了。之前面试一家做AGV的公司面试官给了我一组2D激光雷达的原始扫描数据让我现场写个滤波和障碍物提取的代码。我当时的处理逻辑有问题虽然最后勉强写出来了但过程很狼狈。今天就把2D激光雷达数据处理这件事从头到尾讲清楚。从数据结构、坐标转换、滤波去噪到障碍物提取和特征识别一次讲透。2D激光雷达的数据结构2D激光雷达比如经典的SICK LMS系列、Hokayo URG系列、思岚RPLIDAR系列输出的数据本质上就是一个极坐标序列。每一帧扫描包含一组测量值每个测量值有三个关键信息角度θ、距离r、反射强度intensity。用ROS2的话题来描述就是sensor_msgs/LaserScan消息# LaserScan消息的核心字段 angle_min # 起始角度弧度 angle_max # 终止角度弧度 angle_increment # 角度分辨率相邻两个点的角度间隔 time_increment # 两个点之间的时间间隔 scan_time # 一帧扫描的时间 range_min # 最小有效距离 range_max # 最大有效距离 ranges[] # 距离数组每个元素对应一个角度 intensities[] # 反射强度数组一个典型的2D雷达比如SICK LMS511扫描范围270度角度分辨率0.25度一帧就有1081个测距点。扫描频率25Hz每秒25帧。不同型号的参数差异很大。思岚RPLIDAR A1360度扫描角度分辨率约0.9度一帧大约400个点扫描频率5-10Hz。Hokuyo URG-04LX扫描范围240度角度分辨率0.25度一帧961个点扫描频率10Hz。选型的时候这几个参数直接决定了你后续算法的计算量和实时性要求。面试时候有个坑要注意ranges数组里的值可能是inf或nan。inf表示距离超出量程nan表示这次测量无效比如反射太弱。处理数据之前必须先过滤掉这些无效值不然后面计算会炸。还有一个容易忽略的细节angle_increment是固定的但实际每个点的角度可能有微小偏差。这是因为电机的转速不是绝对均匀的。对于精度要求不高的应用比如避障可以忽略这个偏差直接用理论角度。但做SLAM的时候这个偏差会影响建图质量需要用时间戳做插值补偿。import numpy as np def filter_valid_ranges(scan): 过滤无效的测距值 ranges np.array(scan.ranges) valid_mask (ranges scan.range_min) \ (ranges scan.range_max) \ ~np.isnan(ranges) \ ~np.isinf(ranges) return ranges[valid_mask]讲真很多初学者拿到LaserScan数据直接就用从来不检查有效值。结果程序跑着跑着突然崩溃一查发现是某个nan值传进了距离计算。养成先过滤再处理的习惯能省很多debug的时间。极坐标到笛卡尔坐标的转换2D雷达的数据是极坐标形式的但实际处理做SLAM、做避障都需要笛卡尔坐标。这个转换是基础中的基础。def scan_to_cartesian(scan): 将LaserScan转换为笛卡尔坐标点集 angles np.arange(scan.angle_min, scan.angle_max scan.angle_increment, scan.angle_increment) ranges np.array(scan.ranges) # 过滤无效值 valid (ranges scan.range_min) \ (ranges scan.range_max) \ ~np.isnan(ranges) angles angles[valid] ranges ranges[valid] # 极坐标转笛卡尔 x ranges * np.cos(angles) y ranges * np.sin(angles) return np.column_stack([x, y])这里有个容易忽略的点上面算出来的坐标是雷达坐标系下的。如果你的雷达安装在机器人上雷达坐标系和机器人基坐标系base_link之间有个固定的TF变换。你需要把这个变换也考虑进去。# 雷达安装在机器人上的位置和朝向 lidar_x 0.1 # 相对base_link向前10cm lidar_y 0.0 # 居中 lidar_yaw 0.0 # 没有旋转 # 坐标变换 cos_yaw np.cos(lidar_yaw) sin_yaw np.sin(lidar_yaw) x_robot x * cos_yaw - y * sin_yaw lidar_x y_robot x * sin_yaw y * cos_yaw lidar_y实际项目中用TF2库来做这个变换就行不需要手动算。但面试时候问你原理你得知道底层就是这样一个旋转加平移。有个坑我踩过ROS2里的TF变换是有时间戳的。如果你用lookup_transform的时候传了当前时间但雷达数据的时间戳是上一帧的可能会查不到变换因为TF缓存还没更新。解决方案是用tf2_ros::Buffer的get_latest_common_time方法或者直接用Time(0)表示最新的可用变换。另外如果你的机器人有运动比如差速底盘在转弯雷达扫描一帧需要时间比如40毫秒在这40毫秒内机器人已经移动了一小段距离。对于高速运动的机器人这个运动畸变会影响建图质量。解决方案是用里程计数据做运动补偿——把每个点根据它的时间戳变换到同一时刻的坐标系下。这个在SLAM部分会详细讲。面试中怎么聊面试官问2D激光雷达数据处理你可以按这个顺序回答先说数据结构LaserScan消息的字段含义再说坐标转换极坐标到笛卡尔再到机器人坐标系然后讲滤波去噪飞点检测、中值滤波接着讲障碍物提取距离聚类、特征计算最后结合你做过的项目说应用场景。如果面试官继续深入可能会问你聚类算法的复杂度是多少怎么优化动态障碍物怎么跟踪多雷达数据怎么融合多雷达融合是个高频问题。如果你的机器人前后各装了一个2D雷达两个雷达的扫描数据需要统一到同一个坐标系下。这里有两个难点一是时间同步两个雷达的扫描频率可能不一样比如前面10Hz后面25Hz需要用时间戳做插值对齐二是空间标定两个雷达之间的相对位姿必须精确测量否则融合出来的点云会出现重影。标定通常用标定板配合优化算法来做这个在传感器标定专题里会展开讲。再补充一个实际项目中遇到的corner case。当时我们做仓储AGV用的思岚RPLIDAR A1在仓库里运行时发现一个问题货架是金属的激光打上去反射很强经常出现一个货架被检测成多个分离的簇。原因是金属表面的镜面反射导致激光在不同位置产生了多个回波。解决办法是结合反射强度intensity来辅助判断——镜面反射的intensity通常很高而正常漫反射的intensity适中。我们设了一个intensity阈值超过的直接标记为可疑点在聚类时降低它们的权重。另外仓库里还有一种情况是激光穿过货架缝隙打到后面的墙上导致两个货架之间出现虚假的障碍物。这种点的距离值通常比货架面更远可以用距离跳变检测来过滤——如果相邻两个点的距离差超过0.5米中间的过渡区域大概率是无效数据。这些实战中的细节书本上不会教你但面试时候讲出来会让面试官觉得你真的做过项目。这些问题留到后面的文章详细讲。下一篇我们聊3D激光雷达的数据处理。从2D到3D数据量暴增处理方式也有很多不同。如果这篇文章对你有帮助欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。「机器人软件开发面试·从入门到精通」连载系列上一篇第146篇 激光雷达测距原理——ToF/三角测距/相位法的区别 下一篇预告第148篇 3D激光雷达数据处理——点云结构、格式和处理流程有任何问题欢迎评论区留言我会尽量回复。