目录
1.Livox ros driver 2内部主要参数配置说明
2.1 Livox pointcloud2(PointXYZRTLT)点云格式
2.3 PCL库中的标准pointcloud2(pcl :: PointXYZI)格式(仅ROS可以发布)
前言
Mid-360 是 4 线束非重复扫描固态激光雷达。Mid-360内部集成了IMU芯片(3轴加速度计和3轴陀螺仪),默认情况下,上电后即开始以200Hz频率推送IMU数据(可通过上位机开启或关闭)。数据内容包括3轴加速度以及3轴角速度,方向与点云坐标系相同,在点云坐标系下IMU芯片的位置为(x=11.0mm,y=23.29 mm, z=-44.12 mm)。
雷达的基本概念:
- 点云帧:雷达驱动每次向外发送的一组雷达数据集合称为一帧雷达数据。如果帧率是10HZ,那么每帧点云数据是100ms内雷达扫描的点云集合。每发布一次toptic就是一帧。注意:一帧激光点云数据并不一定是雷达旋转一周所扫描的点云数据。
- 点云帧发布频率:1s内雷达发送的帧数,比如10HZ表示1s内雷达发送10帧的点云数据,即每100ms发送一帧点云数据。
- 扫描频率:雷达的扫描频率一般是针对机械式旋转激光雷达而言的,指1s内雷达旋转的圈数。固态激光雷达的扫描方式和旋转式激光雷达不同,不同产品、厂商有不同的方式。
一、Livox-SDK2
地址:https://github.com/Livox-SDK/Livox-SDK2
ubuntu20.04下编译并安装Livox-SDK2
git clone https://github.com/Livox-SDK/Livox-SDK2.git
cd ./Livox-SDK2/
mkdir build
cd build
cmake .. && make -j
sudo make install
生成的共享库和静态库安装到“/usr/local/lib”目录下。头文件安装到“/usr/local/include”目录中。
提示:如果想要删除 Livox SDK2:
sudo rm -rf /usr/local/lib/liblivox_lidar_sdk_*
sudo rm -rf /usr/local/include/livox_lidar_*
Mid-360官方介绍:1.2. Mid-360 — Livox wiki 0.1 文档
二、livox_ros_driver2
地址:https://github.com/Livox-SDK/livox_ros_driver2
ubuntu20.04,ROS1 环境下编译并运行livox_ros_driver2
git clone https://github.com/Livox-SDK/livox_ros_driver2.git ws_livox/src/livox_ros_driver2
# ROS1 + ubuntu20.04
source /opt/ros/noetic/setup.sh
./build.sh ROS1
1.Livox ros driver 2内部主要参数配置说明
- publish_freq:设置点云发布频率。浮点数据类型,推荐值5.0、10.0、20.0、50.0等。最大发布频率为100.0 Hz。
- multi_topic:激光雷达设备是否有独立的主题发布点云数据。
0 -- 所有LiDAR设备使用相同的主题发布点云数据
1 -- 每个LiDAR设备都有自己的主题来发布点云数据
- xfer_format xfer_:设置点云格式。
0 -- Livox pointcloud2(PointXYZRTLT) 点云格式,即 sensor_msgs/PointCloud2
1 -- Livox定制点云格式,即 livox_ros_driver2/CustomMsg
2 -- PCL 库中的标准 pointcloud2 (pcl :: PointXYZI) 点云格式(仅适用于 ROS),即 sensor_msgs/PointCloud2
2.Livox_ros_driver2点云数据详细说明
2.1 Livox pointcloud2(PointXYZRTLT)点云格式
float32 x # 表示点的X坐标,单位为米(m),4 Byte
float32 y # 表示点的Y坐标,单位为米(m),4 Byte
float32 z # 表示点的Z坐标,单位为米(m),4 Byte
float32 intensity # 激光点的反射率, 0.0~255.0,4 Byte
uint8 tag # livox tag(激光点的标签,通常用于标识点的类型或来源),1 Byte
uint8 line # laser number in lidar(激光雷达中的激光线编号,通常用于指示点是来自哪个激光发射器),1 Byte
float64 timestamp # Timestamp of point,8 Byte
注意:一个字节(Byte)由 8 位(Bit)组成。该格式一个点占用 26 Byte。
注意:帧中的点数量可能不同,但每个点都提供时间戳。
注意:官方在这里将 intensity 注释为 reflectivity ,在其他激光雷达中,这两者可能不同。
- 反射率(reflectivity)是物体表面材料的固有属性,主要与物体材质有关,不随测量条件变化。
- 强度(intensity)是激光雷达接收到的实际信号强度,反映了回波信号的综合效果,除了反射率,还受到距离、环境和激光发射功率等的影响。
2.2 Livox定制点云格式
注意:这里 Mid-360 的时间戳格式为 64 位无符号整数,单位为 ns。
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(激光雷达设备的ID编号)
uint8[3] rsvd # Reserved use(保留字段,可能用于未来扩展或其他用途)
CustomPoint[] points # Pointcloud data
上述定制数据包中的定制点云(CustomPoint)格式:
uint32 offset_time # offset time relative to the base time(相对于timebase的偏移时间,通常用于时间同步)
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
2.3 PCL库中的标准pointcloud2(pcl :: PointXYZI)格式(仅ROS可以发布)
float32 x
float32 y
float32 z
float32 intensity
2.4 点云数据补充介绍
目标反射率:以0至255表示。其中0至150对应反射率介于0至100%的漫反射物体;而151至255对应全反射物体。当被测物体距离Mid-360小于2m时,目标反射率误差可能偏大,仅能用于区分目标为全反射物体还是漫反射物体。
坐标信息:Mid-360的坐标信息可表示为直角坐标系(x,y,z)或球坐标系(r,θ,φ),其直角坐标和球坐标的对应关系如下图所示。如果前方无被探测物体或者被探测物体超出量程范围(例如>100m),在直角坐标系下,点云输出为(0,0,0);在球坐标系下,点云输出为(0,θ,φ)。
标记(Tag):主要指示探测点的其它附加信息。点云标记为 8bit 无符号整数,按照bit划分为几个区域,每个区域表示此探测点的一种属性,其中包括雨雾灰尘、相近物体间的粘连点云等等;其中,置信度表示此探测点的可信程度,一般正常点为0(置信度优);置信度差表示该探测点受相应属性影响较大,探测结果可信度差;如需要,可据此信息对点云进行过滤。标记信息的格式如下:
一个字节(Byte)由 8 位(Bit)组成。
时间戳:Mid-360支持两种时间同步方式:PTP(IEEE 1588v2.0)和 gPTP(IEEE 802.1AS)网络协议同步和GPS同步(一般为秒脉冲PPS+GPRMC)。时间戳格式为 64 位无符号整数,单位为 ns。
3.激光雷达config文件配置
LiDAR 配置(例如 ip、端口、数据类型...等)可以通过 json 样式的配置文件进行设置。单个 HAP、Mid360 和混合 LiDAR 的配置文件位于“config”文件夹中。启动文件中名为“user_config_path”的参数表示该json文件路径。
连接多个激光雷达时,将不同激光雷达对应的对象添加到“lidar_configs”数组中。混合激光雷达配置文件内容示例如下:
{
"lidar_summary_info" : {
"lidar_type": 8 # protocol type index, please don't revise this value(协议类型索引,请不要修改此值)
},
"HAP": {
"lidar_net_info" : { # HAP ports, please don't revise these values(HAP端口,请不要修改这些值)
"cmd_data_port": 56000, # HAP command port(HAP命令端口)
"push_msg_port": 0,
"point_data_port": 57000,
"imu_data_port": 58000,
"log_data_port": 59000
},
"host_net_info" : {
"cmd_data_ip" : "192.168.1.5", # host ip
"cmd_data_port": 56000,
"push_msg_ip": "",
"push_msg_port": 0,
"point_data_ip": "192.168.1.5", # host ip
"point_data_port": 57000,
"imu_data_ip" : "192.168.1.5", # host ip
"imu_data_port": 58000,
"log_data_ip" : "",
"log_data_port": 59000
}
},
"MID360": {
"lidar_net_info" : { # Mid360 ports, please don't revise these values
"cmd_data_port": 56100, # Mid360 command port
"push_msg_port": 56200,
"point_data_port": 56300,
"imu_data_port": 56400,
"log_data_port": 56500
},
"host_net_info" : {
"cmd_data_ip" : "192.168.1.5", # host ip
"cmd_data_port": 56101,
"push_msg_ip": "192.168.1.5", # host ip
"push_msg_port": 56201,
"point_data_ip": "192.168.1.5", # host ip
"point_data_port": 56301,
"imu_data_ip" : "192.168.1.5", # host ip
"imu_data_port": 56401,
"log_data_ip" : "",
"log_data_port": 56501
}
},
"lidar_configs" : [
{
"ip" : "192.168.1.100", # ip of the HAP you want to config
"pcl_data_type" : 1,
"pattern_mode" : 0,
"blind_spot_set" : 50,
"extrinsic_parameter" : {
"roll": 0.0,
"pitch": 0.0,
"yaw": 0.0,
"x": 0,
"y": 0,
"z": 0
}
},
{
"ip" : "192.168.1.12", # ip of the Mid360 you want to config
"pcl_data_type" : 1,
"pattern_mode" : 0,
"extrinsic_parameter" : {
"roll": 0.0,
"pitch": 0.0,
"yaw": 0.0,
"x": 0,
"y": 0,
"z": 0
}
}
]
}
"lidar_configs"配置参数
- ip(string类型):激光雷达的IP
- pcl_data_type (int 类型):要发送的点云数据的分辨率
1 -- 笛卡尔坐标数据(32 位)
2 -- 笛卡尔坐标数据(16 位)
3 --球坐标数据
- pattern_mode(int 类型):空间扫描模式
0——非重复扫描模式
1——重复扫描模式
2——重复扫描模式(低扫描速率)
- blind_spot_set(仅适用于 HAP 激光雷达,int类型):范围从 50 厘米到 200 厘米
- extrinsic_parameter:外部参数,为雷达坐标系相对于某个父坐标系(如机器人基座
base_link
)的外参变换,即 T_base_lidar ,用于将雷达坐标系下的点云转到该父坐标系下。特别注意:改变该值会改变 lidar 和 imu 的外参,需要重新对imu和lidar进行标定,使用通常不建议修改该值!!!“roll”“picth”“yaw”的数据类型为float,单位为度。"x" "y" "z" 的数据类型为 int,单位为 mm。
关于 extrinsic_parameter,假设雷达前倾20°安装,此时将 pitch 也设置为20°,等价于将点云转移到了新的坐标系 base_link 下,如下图所示。此时,点云是处于 base_link 坐标系下的,livox 的 ROS 驱动会将该 base_link 重新命名为 livox_frame,这也就改变了 lidar坐标系 和 imu 坐标系的相对位置,改变了两者之间的外参。
当仅 发生变化时,俯视图如下:
当仅 发生变化时,正视图如下:
在雷达水平放置,仅 变为 90 的情况下,imu 的测量不变。即 imu 坐标系不随 extrinsic_parameter 的变化而变化。
rviz_MID360.launch 和 msg_MID360.launch 的不同之处在于 xfer_format 和 rviz_enable 参数的设置。
1. rviz_MID360.launch 中 xfer_format 的值为 0,发布的是 sensor_msgs/PointCloud2 类型的点云数据,可在 rviz 中进行可视化,故 rviz_enable 的值设置为 true。
2. msg_MID360.launch 中 xfer_format 的值为 0,发布的是 livox_ros_driver2/CustomMsg 类型的点云,不可在 rviz 中进行可视化,故 rviz_enable 的值设置为 false。
Livox HAP 通讯协议:https://github.com/Livox-SDK/Livox-SDK2/wiki/Livox-SDK-Communication-Protocol-HAP
Livox Mid-360 通讯协议:
1.2.1.1. 激光雷达通信协议–Mid360 — Livox wiki 0.1 文档
4.Mid-360激光雷达ip配置
Mid-360通过以太网进行数据通信(UDP),支持静态IP地址模式。所有Mid-360出厂默认IP地址为192.168.1.1XX(XX为Mid-360 SN码最后两位数字),子网掩码为255.255.255.0,默认网关为192.168.1.1。首次使用Mid-360时,无需通过路由器,即可直接与电脑连接。
-
IP地址(192.168.1.1XX):是设备在网络中的唯一标识符,用于区分网络中的其他设备。
-
子网掩码(255.255.255.0):子网掩码用于确定网络部分和主机部分。255.255.255.0表示前24位是网络部分,后8位是主机部分。在这种配置下,网络中可以有256个地址(0-255),其中254个可用地址(去掉网络地址和广播地址)。
-
网关(192.168.1.1):网关是连接不同网络的设备,通常是路由器。设备通过这个地址与外部网络进行通信。在 Mid-360 的使用中可以不用设置。
-
DNS(域名系统):是互联网中的一个重要服务,用于将域名(如www.example.com)转换为IP地址(如192.168.1.1),使设备能够通过友好的名称访问网站和其他网络资源。
5.实际传感器数据
1.雷达点云数据
- 当 xfer_format xfer_ = 0 时,发布 sensor_msgs/PointCloud2 类型点云数据,PointXYZITLT
使用 sensor_msgs.point_cloud2.read_points() 反序列化 PointField[] fields 中的点,脚本如下:
#!/usr/bin/env python
import rospy
import sensor_msgs.point_cloud2 as pc2
from sensor_msgs.msg import PointCloud2
import sys
def callback(pointcloud):
# 输出 Header 信息
header = pointcloud.header
# 将 header 的 stamp 转换为秒的浮点数格式
header_timestamp = header.stamp.secs + header.stamp.nsecs * 1e-9
rospy.loginfo(f"Header: seq: {header.seq}, stamp: {header_timestamp:.9f}, frame_id: {header.frame_id}")
count = 0 # 计数器
output_next_4 = False # 控制是否输出接下来的4个点
output_counter = 0 # 连续输出的点计数器
# 解析并输出每个点的 x, y, z, intensity
for point in pc2.read_points(pointcloud, field_names=("x", "y", "z", "intensity", "tag", "line", "timestamp"), skip_nans=True):
count += 1
x, y, z, intensity, tag, line, timestamp = point
timestamp = timestamp * 1e-9
# 将 tag 转换为 8 位二进制形式
tag_binary = format(int(tag), '08b')
# if count < 2:
# # 输出 x, y, z, intensity 的字节数
# rospy.loginfo(f"Size of x: {sys.getsizeof(x)} bytes")
# rospy.loginfo(f"Size of y: {sys.getsizeof(y)} bytes")
# rospy.loginfo(f"Size of z: {sys.getsizeof(z)} bytes")
# rospy.loginfo(f"Size of intensity: {sys.getsizeof(intensity)} bytes")
# rospy.loginfo(f"Size of tag: {sys.getsizeof(tag)} bytes")
# rospy.loginfo(f"Size of line: {sys.getsizeof(line)} bytes")
# rospy.loginfo(f"Size of timestamp: {sys.getsizeof(timestamp)} bytes")
# 每隔100个点,开启接下来4个点的输出
if count % 100 == 1:
output_next_4 = True
output_counter = 0 # 重置连续输出计数器
# 如果开启了输出,连续输出4个不同的点
if output_next_4:
rospy.loginfo(f"count: {count}, x: {x:.6f}, y: {y:.6f}, z: {z:.6f}, intensity: {intensity}, tag: {tag_binary}, line: {line}, timestamp: {timestamp:.9f}")
output_counter += 1
# 输出4个点后,停止
if output_counter >= 4:
output_next_4 = False
def listener():
# 初始化ROS节点
rospy.init_node('pointcloud_listener', anonymous=True)
# 订阅PointCloud2消息
rospy.Subscriber("/livox/lidar_192_168_1_177", PointCloud2, callback)
# 保持节点运行
rospy.spin()
if __name__ == '__main__':
listener()
输出结果如下:
wu@WP:~/Project/LivoxCloud_2_pcd_ws$ rosrun livox_2_pcd pointcloud_listener.py
[INFO] [1727342866.700987]: Header: seq: 124, stamp: 1727264145.735122204, frame_id: livox_frame
[INFO] [1727342866.701854]: count: 1, x: 0.000000, y: 0.000000, z: 0.240000, intensity: 0.0, tag: 00000000, line: 0, timestamp: 1727264145.735122204
[INFO] [1727342866.702393]: count: 2, x: 0.000000, y: 0.000000, z: 0.240000, intensity: 0.0, tag: 00000000, line: 1, timestamp: 1727264145.735127211
[INFO] [1727342866.702808]: count: 3, x: 0.000000, y: 0.000000, z: 0.240000, intensity: 0.0, tag: 00000000, line: 2, timestamp: 1727264145.735131979
[INFO] [1727342866.703189]: count: 4, x: 0.000000, y: 0.000000, z: 0.240000, intensity: 0.0, tag: 00000000, line: 3, timestamp: 1727264145.735137224
[INFO] [1727342866.736293]: count: 3101, x: 0.000000, y: 0.000000, z: 0.240000, intensity: 0.0, tag: 00000000, line: 0, timestamp: 1727264145.749756813
[INFO] [1727342866.736614]: count: 3102, x: 4.241266, y: -0.129206, z: 2.090489, intensity: 7.0, tag: 00100000, line: 1, timestamp: 1727264145.749761820
[INFO] [1727342866.736927]: count: 3103, x: 0.352700, y: 0.000460, z: 0.382363, intensity: 1.0, tag: 00010000, line: 2, timestamp: 1727264145.749766827
[INFO] [1727342866.737385]: count: 3104, x: 0.000000, y: 0.000000, z: 0.240000, intensity: 0.0, tag: 00000000, line: 3, timestamp: 1727264145.749771595
其中 tag: 00 01 00 00 代表 探测点的置信度为中,tag: 00 10 00 00 代表 探测点的置信度为差。
- 当 xfer_format xfer_ = 1 时,发布 livox_ros_driver2/CustomMsg 类型点云数据
编写代码输出消息内容
#include <ros/ros.h>
#include <livox_ros_driver2/CustomMsg.h>
#include <iomanip>
#include <bitset>
void callback(const livox_ros_driver2::CustomMsg::ConstPtr& msg) {
// 输出消息头信息
// 假设 msg->header 是 livox_ros_driver2 的消息头
// 如果 header 不同,请相应地调整代码
uint32_t seq = msg->header.seq; // 序列号
uint32_t secs = msg->header.stamp.sec; // 秒部分
uint32_t nsecs = msg->header.stamp.nsec; // 纳秒部分
double header_timestamp = secs + nsecs * 1e-9; // 将时间戳转换为浮点数
ROS_INFO("Header: seq: %u, stamp: %.9f, frame_id: %s", seq, header_timestamp, msg->header.frame_id.c_str());
int count = 0; // 计数器
bool output_next_4 = false; // 控制是否输出接下来的4个点
int output_counter = 0; // 连续输出的点计数器
// 输出 timebase, point_num, lidar_id, rsvd 信息
double timebase = msg->timebase * 1e-9;
uint32_t point_num = msg->point_num;
uint8_t lidar_id = msg->lidar_id;
const boost::array<uint8_t, 3> rsvd = msg->rsvd;
ROS_INFO("timebase: %.9f, point_num: %u, lidar_id: %u, rsvd: [%u, %u, %u]",
timebase, point_num, lidar_id, rsvd[0], rsvd[1], rsvd[2]);
// 遍历每个点
for (const auto& point : msg->points) {
count++;
float x = point.x;
float y = point.y;
float z = point.z;
float intensity = point.reflectivity; // 通常与 intensity 类似
uint8_t tag = point.tag;
uint8_t line = point.line;
double timestamp = point.offset_time * 1e-9; // 偏移时间
// 将 tag 转换为二进制
std::bitset<8> tag_binary(tag);
// 每隔100个点,开启接下来4个点的输出
if (count % 100 == 1){
output_next_4 = true;
output_counter = 0; // 重置连续输出计数器
}
// 如果开启了输出,连续输出4个不同的点
if (output_next_4){
// 输出点的信息
ROS_INFO("count: %u, x: %.6f, y: %.6f, z: %.6f, intensity: %.1f, tag: %s, line: %u, offset_time: %.9f",
count, x, y, z, intensity, tag_binary.to_string().c_str(), line, timestamp);
output_counter++;
// 输出4个点后,停止
if (output_counter >= 4){
output_next_4 = false;
}
}
}
}
int main(int argc, char** argv) {
// 初始化 ROS 节点
ros::init(argc, argv, "livox_listener");
ros::NodeHandle nh;
// 订阅 CustomMsg 类型的消息
ros::Subscriber sub = nh.subscribe("/livox/lidar_192_168_1_177", 10, callback); // 确保话题名称正确
// 保持节点运行
ros::spin();
return 0;
}
输出结果如下:
wu@WP:~/Project/LivoxCloud_2_pcd_ws$ rosrun livox_2_pcd livox_poindcloud_node
[ INFO] [1727350763.579644256]: Header: seq: 158, stamp: 1727264239.338434219, frame_id: livox_frame
[ INFO] [1727350763.580135924]: timebase: 1727264239.338434458, point_num: 19968, lidar_id: 192, rsvd: [0, 0, 0]
[ INFO] [1727350763.580151081]: count: 1, x: 0.000000, y: 0.000000, z: 0.240000, intensity: 0.0, tag: 00000000, line: 0, offset_time: 0.000000000
[ INFO] [1727350763.580156014]: count: 2, x: 0.000000, y: 0.000000, z: 0.240000, intensity: 0.0, tag: 00000000, line: 1, offset_time: 0.000004947
[ INFO] [1727350763.580160030]: count: 3, x: 0.000000, y: 0.000000, z: 0.240000, intensity: 0.0, tag: 00000000, line: 2, offset_time: 0.000009894
[ INFO] [1727350763.580164018]: count: 4, x: 0.000000, y: 0.000000, z: 0.240000, intensity: 0.0, tag: 00000000, line: 3, offset_time: 0.000014841
[ INFO] [1727350763.580186473]: count: 201, x: 0.568850, y: -0.504921, z: 0.364832, intensity: 4.0, tag: 00000000, line: 0, offset_time: 0.000484338
[ INFO] [1727350763.580190209]: count: 202, x: 0.694293, y: -0.631529, z: 0.368355, intensity: 5.0, tag: 00000000, line: 1, offset_time: 0.000489285
[ INFO] [1727350763.580194152]: count: 203, x: 0.524212, y: -0.486231, z: 0.320469, intensity: 11.0, tag: 00010000, line: 2, offset_time: 0.000494232
[ INFO] [1727350763.580302303]: count: 204, x: 0.515708, y: -0.490682, z: 0.301498, intensity: 11.0, tag: 00000000, line: 3, offset_time: 0.000499179
注意:timebase 是第一个点的时间戳,和 header 中的时间戳差了一点点。
- 当 xfer_format xfer_ = 2 时,发布 sensor_msgs/PointCloud2 类型点云数据, PointXYZI。