相信大家使用到传感器都会用到相应的驱动,该驱动主要都是该设备的厂家提供的,这里的驱动主要是指软件驱动。为啥这里提前将驱动,主要是如果你作为一个算法工程师,如果对所使用的传感器的特性不了解,包括硬件、软件参数和特性都没有了解,那你也无法正确使用它的各种性能,不可能充分发挥出该传感器的优良特性。
我们主要从传感器的硬件物理特性、通讯文件等入手来充分了解传感器的驱动特性。想主要以livox mid360以及雷神128线激光雷达为例结合物理特性和驱动软件来透彻分析驱动软件为啥那样写。
“驱动不就是厂家给个库,调个API就完事了吗?”如果你这么想,那大概率会在某个深夜被一个诡异的点云畸变问题折磨到怀疑人生。作为算法工程师,我们习惯把传感器当作一个“黑盒”:给电、给网、给数据。但激光雷达不是普通的传感器——它每秒产生数几万到数十万乃至数百万个三维点,每帧数据都带着微秒级的时间戳,不同型号的扫描方式、数据组织逻辑、同步机制天差地别。如果不理解这些物理特性和通讯协议,你连驱动里那些看起来“莫名其妙”的代码为什么要那样写都搞不清楚,更别说调优了。
话不多说,切入正题。
首先我们先从激光雷达驱动入手,举两个例子说明一下学习的方向。现在的激光雷达可以分为纯固态激光雷达、混合固态激光雷达和机械激光雷达。首先介绍一下当前机器人行业用的最多的混合固态激光雷达,livox mid360,所有livox激光雷达共用一套官方驱动,专门针对这种非重复式花瓣型扫描方式,从ros节点入口、SDK接口,到数据缓存、多线程分发,再到ros消息发布。着重理解一下其多个雷达并行处理,怎么实现高并发、低延迟的。在启动多个雷达时是怎么创建与发布线程的?可以着重看这三个函数:在 Lddc 里:
OnLidarActivated(uint8_t index)
DistributePointCloudData(uint32_t lidar_id)
DistributeImuData(uint32_t lidar_id)
彻底读懂per-lidar 懒创建原理和好处。
从用点云的角度来分析,我们先要搞清楚需要什么信息格式的点云,一般定位导航采用livox自定义的消息多一些,即充分理解CustomPoint.msg:
Livox costom pointcloud format.
uint32 offset_time # offset time relative to the base time
float32 x # X axis, unit:m
float32 y # Y axis, unit:m
float32 z # Z axis, unit:m
uint8 reflectivity # reflectivity, 0~255
uint8 tag # livox tag
uint8 line # laser number in lidar
和CustomMsg.msg
Livox publish pointcloud msg format.
std_msgs/Header header # ROS standard message header
uint64 timebase # The time of first point
uint32 point_num # Total number of pointclouds
uint8 lidar_id # Lidar device id number
uint8[3] rsvd # Reserved use
CustomPoint[] points # Pointcloud data
在此只是提示学习方法,offset_time、timebase和header的关系,tag是干什么用的?不要它是否可以?num_scans_是什么意思?有没有发现很多激光雷达都有这个参数,想想不同的激光雷达怎么将这个参数应用在slam的建图定位中? sensor_msgs::PointCloud2和livox_ros_driver2::CustomMsg的转换关系?是否两者可以相互转换?为什么?
然后用的时候要着重理解一下次段:
std::for_each(std::execution::par_unseq, index.begin(), index.end(), [&](const uint &i) {
if ((msg->points[i].line < num_scans_) &&
((msg->points[i].tag & 0x30) == 0x10 || (msg->points[i].tag & 0x30) == 0x00)) {
if (i % point_filter_num_ == 0) {
cloud_full_[i].x = msg->points[i].x;
cloud_full_[i].y = msg->points[i].y;
cloud_full_[i].z = msg->points[i].z;
cloud_full_[i].intensity = msg->points[i].reflectivity;
cloud_full_[i].time = msg->points[i].offset_time / float(1000000);
if ((abs(cloud_full_[i].x - cloud_full_[i - 1].x) > 1e-7) ||
(abs(cloud_full_[i].y - cloud_full_[i - 1].y) > 1e-7) ||
(abs(cloud_full_[i].z - cloud_full_[i - 1].z) > 1e-7)) {
is_valid_pt[i] = true;
}
}
}
});
在此你就能看出tag的作用了,0x30和0x10、0x00分别代表什么?
这里说一下答案:作用
目的是:只保留第一次回波(单回波或双回波的第一回波),丢弃后续回波的点,避免多回波在点云中造成重叠、拖影或距离歧义。
具体分析:在 Livox SDK 中,LivoxPoint 的 tag 定义:
bit 0-1: 回波序号,0 表示第一次回波,1 表示第二次,2 表示第三次。
bit 2: 扫描方向,0 表示正向,1 表示反向。
bit 3: 点类型,0 表示正常点,1 表示噪声点。
bit 4-5: 保留。
bit 6-7: 置信度,0 表示低,1 表示中,2 表示高。
Livox SDK 的 livox_def.h 中,有:
typedef enum {
kLivoxPointTagSingle = 0x00,
kLivoxPointTagDual = 0x10,
kLivoxPointTagTriple = 0x20,
} LivoxPointTag;
再举个例子,以雷神的128线固态激光雷达,如果要用这个替代livox激光雷达,应该怎么样接入建图定位程序中呢?比如说fast_lio2、faster_lio或者pointlio?首先还是要熟读雷神128线的用户手册,
如果这种激光雷达接入fast_lio2该怎么进行接入接口信息修改?且听下回分解。