激光雷达工作时会先在当前位置发出激光并接收反射光束,解析得到距离信息,而后激光发射器会转过一个角度分辨率对应的角度再次重复这个过程。限于物理及机械方面的限制,激光雷达通常会有一部分“盲区”。使用激光雷达返回的数据通常可以描绘出一幅极坐标图,极点位于雷达扫描中心,0-360°整周圆由扫描区域及盲区组成。在扫描区域中激光雷达在每个角度分辨率对应位置解析出的距离值会被依次连接起来,这样,通过极坐标表示就能非常直观地看到周围物体的轮廓,激光雷达扫描范围示意图可以参见下图。
激光雷达通常有四个性能衡量指标:测距分辨率、扫描频率(有时也用扫描周期)、角度分辨率及可视范围。测距分辨率衡量在一个给定的距离下测距的精确程度,通常与距离真实值相差在5-20mm;扫描频率衡量激光雷达完成一次完整扫描的快慢,通常在10Hz及以上;角度分辨率直接决定激光雷达一次完整扫描能返回多少个样本点;可视范围指激光雷达完整扫描的广角,可视范围之外即为盲区。
目前,移动机器人的研究中已经大量使用激光雷达辅助机器人的避障导航。通常激光雷达会提供ROS驱动,如果没有的话我们也可以自己采集激光数据后按照ROS中定义的消息格式将信息发布出去。首先,输入下面的指令查看LaserScan消息结构:
rosmsg show sensor_msgs/LaserScan
LaserScan消息结构如下:
std_msgs/Header header
uint32 seq
time stamp
string frame_id # in frame frame_id, angles are measured around the positive Z axis (counterclockwise, if Z is up)
# with zero angle being forward along the x axis
float32 angle_min # start angle of the scan [rad]
float32 angle_max # end angle of the scan [rad]
float32 angle_increment # angular distance between measurements [rad]
float32 time_increment # time between measurements [seconds] - if your scanner
# is moving, this will be used in interpolating position
# of 3d points
float32 scan_time # time between scans [seconds]
float32 range_min # minimum range value [m]
float32 range_max # maximum range value [m]
float32[] ranges # range data [m] (Note: values < range_min or > range_max should be discarded)
float32[] intensities # intensity data [device-specific units]. If your device does not provide intensities, please leave the array empty.
以下图为例,该激光雷达扫描范围为270°,角度分辨率为0.25°,扫描距离为0~20m,每扫描一圈会得到1081个点:
那么该激光雷达发布的LaserScan消息内容如下:
angle_min= -135 * (pi/180); //angle correspond to FIRST beam in scan ( in rad) angle_max= 135 * (pi/180); //angle correspond to LAST beam in scan ( in rad) angle_increment =0.25 * (pi/180); // Angular resolution i.e angle between 2 beams // lets assume sensor gives 50 scans per second. i.e every 20 milli seconds 1 scan with 1081 beams. // Each beam is measured in (20 ms/ 1081 ) ~ = 0.0185 ms time_increment = (1 / 50) / (1081); scan_time = ; // scan is collected at which time range_min =0 ; // in meters range_max = 20; // scan can measure upto this range
// ranges is array of 1081 floats for each laser beam ranges[0] = //distance measure corresponds to angle -135 deg ranges[1] = //distance measure corresponds to angle -134.75 deg . . . ranges[1080] = //distance measure corresponds to angle +135 deg // To understand Intensities // if a laser beam hits reflective surface like glass it will have intensity 1. // And if beam hit some surface which absorbs laser , then intensity is zero. // Middle values are different surfaces in between.
下面的代码模拟了激光雷达的数据,并将sensor_msgs/LaserScan消息发布到/scan话题上:
#include <ros/ros.h> #include <sensor_msgs/LaserScan.h> int main(int argc, char** argv) { ros::init(argc, argv, "laser_scan_publisher");
ros::NodeHandle n; ros::Publisher scan_pub = n.advertise<sensor_msgs::LaserScan>("scan", 50); unsigned int num_readings = 100; double laser_frequency = 40; double ranges[num_readings]; double intensities[num_readings];
int count = 0; ros::Rate r(1.0); while(n.ok()) { //generate some fake data for our laser scan for(unsigned int i = 0; i < num_readings; ++i) { ranges[i] = count; intensities[i] = 100 + count; } ros::Time scan_time = ros::Time::now(); //populate the LaserScan message sensor_msgs::LaserScan scan; scan.header.stamp = scan_time; scan.header.frame_id = "base_link"; scan.angle_min = -1.57; scan.angle_max = 1.57; scan.angle_increment = 3.14 / num_readings; scan.time_increment = (1 / laser_frequency) / (num_readings); scan.range_min = 0.0; scan.range_max = 100.0; scan.ranges.resize(num_readings); scan.intensities.resize(num_readings); for(unsigned int i = 0; i < num_readings; ++i) { scan.ranges[i] = ranges[i]; scan.intensities[i] = intensities[i]; } scan_pub.publish(scan); ++count;
r.sleep(); } }
可以在rviz中将激光数据点显示出来:Fixed Frame修改为base_link,添加LaserScan并将Topic设为/scan
如果Fixed Frame为map,为了能正确显示出激光扫描点来,需要发布map和base_link之间的坐标变换关系(因为我们的激光数据是相对于base_link坐标系描述的)。如果这两个参考系不发生相对位置变化,那么可以用static_transform_publisher工具发布两个参考系之间的静态坐标变换。
命令的格式如下:
- static_transform_publisher x y z yaw pitch roll frame_id child_frame_id period_in_ms
- static_transform_publisher x y z qx qy qz qw frame_id child_frame_id period_in_ms
以上两种命令格式,需要设置坐标的偏移和旋转参数,偏移参数都使用相对于x、y、z三轴的坐标位移。旋转参数第一种命令格式使用以弧度为单位的 yaw/pitch/roll三个角度,第二种命令格式使用四元数表达旋转角度。发布频率以ms为单位,一般100ms比较合适。
static_transform_publisher is designed both as a command-line tool for manual use, as well as for use within roslaunch files for setting static transforms. For example:
<launch>
<node pkg="tf" type="static_transform_publisher" name="link1_broadcaster" args="1 0 0 0 0 0 1 link1_parent link1 100" />
</launch>
ROS中使用激光雷达(镭神智能)
LS01C是深圳市镭神智能系统有限公司研发的激光三角测距系统 。在机械旋转模块的带动下,LS01C 的高频核心测距模块将进行顺时钟旋转,从而实现对周围环境进行360°扫描测距。LS01C通过uart 串口信号与外部系统通讯,默认每秒采样3600点、扫描频率10hz,最大扫描距离6m,角度分辨率为1度。
将LS01C的ROS驱动文件(官网上没有下载连接,直接打电话给客服要的)解压重命名为talker后复制到catkin_ws/src下面,然后使用catkin_make进行编译
插入USB后在终端中输入以下命令查看USB转串口设备:
在发现ttyUSB0后在终端中输入下面命令给USB 转串口设置权限:
sudo chmod 666 /dev/ttyUSB0
修改launch文件中的串口名,改为我们插入的ttyUSB0
<launch>
<node name="talker" pkg="talker" type="talker">
<param name="scan_topic" value="scan"/> <!-- 设置激光数据topic名称 -->
<param name="laser_link" value="laser_link"/> <!-- 激光坐标 -->
<param name="serial_port" value="/dev/ttyUSB0"/> <!-- 雷达连接的串口 -->
</node>
</launch>
然后执行launch文件
roslaunch talker talker.launch
在终端中可以看到talker节点已经开启:
打开rviz,添加LaserScan并设置topic和参考坐标系,可以动态的显示激光扫描点:
还可以通过 rostopic hz命令查看激光数据发布频率,可以看出其频率为10Hz
参考:
explanation on sensor_msgs/LaserScan
Learning ROS for Robotics Programming - Second Edition