主要内容

从激光雷达数据构建地图

此示例显示了如何在惯性测量单元(IMU)的帮助下,处理安装在车辆上的传感器的三维激光雷达数据,以逐步构建地图读数。此类地图可促进车辆导航的路径规划或用于定位。为了评估生成的地图,本示例还显示了如何将车辆轨迹与全球定位系统(GPS)记录进行比较。

概述

高清晰度(HD)地图是一种提供精确道路几何图形的地图服务,精确度可达几厘米。这种精度水平使高清地图适用于自动驾驶流程,如定位和导航。这种高清地图是由3-D激光雷达扫描生成的,结合高精度GPS和或IMU传感器,可以用于定位车辆在几厘米内。这个示例实现了构建这样一个系统所需的特性子集。

在本例中,您将学习如何:

  • 加载、浏览并可视化记录的驾驶数据

  • 使用激光雷达扫描构建地图

  • 使用IMU读数改进地图

加载和探索记录的驾驶数据

本例中使用的数据来自这GitHub®库,表示大约100秒的激光雷达、GPS和IMU数据。数据以MAT文件的形式保存,每个文件包含时间表。从存储库下载MAT文件并将其加载到MATLAB®工作区。

注意:此下载可能需要几分钟时间。

baseDownloadURL ='https://github.com/mathworks/udacity-self-driving-data-subset/raw/master/drive_segment_11_18_16/';dataFolder = fullfile (tempdir,“drive_segment_11_18_16”, filesep);选择= weboptions (“超时”,Inf);lidarFileName=dataFolder+“lidarPointClouds.mat”;imuFileName=dataFolder+“imuOrientations.mat”;gpsFileName=dataFolder+“gpsSequence.mat”;folderExists =存在(dataFolder,“dir”);matfilesExist =存在(lidarFileName,“文件”)&&exist(imuFileName,“文件”...&&存在(gpsFileName,“文件”);如果~ folderExists mkdir (dataFolder);终止如果~ matfilesExist disp ('正在下载lidarPointClouds.mat(613 MB)…') websave(lidarFileName, baseDownloadURL +“lidarPointClouds.mat”、选择);disp (“下载imuOrientations。垫(1.2 MB)……”)websave(imuFileName,baseDownloadURL+“imuOrientations.mat”、选择);disp (“下载gpsSequence。垫(3 KB)……”)websave(gpsFileName,baseDownloadURL+“gpsSequence.mat”、选择);终止
下载lidarPointClouds。垫(613 MB)…下载imuOrientations。垫(1.2 MB)…下载gpsSequence。垫(3 KB)……

首先,加载从Velodyne®HDL32E激光雷达保存的点云数据。激光雷达数据的每次扫描使用点云对象。该对象使用K-d树数据结构在内部组织数据,以加快搜索速度。与每次激光雷达扫描相关的时间戳记录在时间时间表的可变部分。

%从MAT文件加载激光雷达数据data =负载(lidarFileName);lidarPointClouds = data.lidarPointClouds;%显示前几行激光雷达数据头(lidarPointClouds)
ans = 8×1 PointCloud时间表的时间  _____________ ______________ 23:46:10.5115 1×1 pointCloud 23:46:10.6115 1×1 pointCloud 23:46:10.7116 1×1 pointCloud 23:46:10.8117 1×1 pointCloud 23:46:10.9118 1×1 pointCloud 23:46:11.0119 1×1 pointCloud 23:46:11.1120 1×1 pointCloud 23:46:11.2120 1×1 pointCloud

从MAT文件加载GPS数据纬度经度,高度变量的时间表用于存储由车载GPS设备记录的地理坐标。

载入GPS序列从MAT-filedata =负载(gpsFileName);gpsSequence = data.gpsSequence;%显示前几行GPS数据头(gpsSequence)
ans = 8×3时间表时间纬度经度海拔_____________ ________ _________ ________ 23:46:11.4563 37.4 -122.11 -42.5 23:46:12.4563 37.4 -122.11 -42.5 23:46:14.4455 37.4 -122.11 -42.5 23:46:15.4455 37.4 -122.11 -42.5 23:46:16.4567 37.4 -122.11 -42.5 23:46:16.4567 37.4 -122.11 -42.5 23:46:17.4573 37.4 -122.11 -42.5 23:46:17.4573-122.11 - -42.5

从MAT文件加载IMU数据。IMU通常由报告车辆运动信息的单个传感器组成。它们结合了多种传感器,包括加速度计、陀螺仪和磁强计。这个取向变量存储IMU传感器的报告方向。这些读数以四元数形式报告。每个读数指定为包含四个四元数部分的1×4矢量。将1×4矢量转换为四元数对象。

从mat文件加载IMU录音data =负载(imuFileName);imuOrientations = data.imuOrientations;%将IMU录制转换为四元数类型imuOrientations=转换器变量(imuOrientations,“定位”“四元数”);%显示IMU数据的前几行头部(定向)
ans = 8×1时间表时间取向  _____________ ______________ 23:46:11.4570 1×1四元数23:46:11.4605 1×1四元数23:46:11.4620 1×1四元数23:46:11.4655 1×1四元数23:46:11.4670 1×1四元数23:46:11.4705 1×1四元数23:46:11.4720 1×1四元数23:46:11.4755 1×1四元数

为了了解传感器读数是如何得出的,请计算每个传感器的近似帧持续时间

lidarFrameDuration =值(diff (lidarPointClouds.Time));gpsFrameDuration =值(diff (gpsSequence.Time));imuFrameDuration =值(diff (imuOrientations.Time));%调整显示格式为秒lidarFrameDuration.Format=“年代”;gpsFrameDuration。格式=“年代”; imuFrameDuration.Format=“年代”%计算帧速率lidarRate = 1 /秒(lidarFrameDuration);gpsRate = 1 /秒(gpsFrameDuration);imuRate = 1 /秒(imuFrameDuration);%显示帧持续时间和速率流('激光雷达:%s, %3.1f Hz\n'char (lidarFrameDuration), lidarRate);流('全球定位系统:%s,%3.1f赫兹\n',char(gpsFrameDuration),gpsRate;fprintf('IMU:%s,%3.1f Hz\n'char (imuFrameDuration), imuRate);
激光雷达:0.10008秒,10.0赫兹GPS:1.0001秒,1.0赫兹IMU:0.002493秒,401.1赫兹

GPS传感器速度最慢,运行频率接近1 Hz。其次是激光雷达,运行频率接近10 Hz,其次是IMU,运行频率接近400 Hz。

可视化驾驶数据

为了理解场景包含什么,使用流媒体播放器可视化记录的数据。要使GPS读数形象化,请使用地质层.使用可视化激光雷达读数pcplayer

%创建地质图层以可视化流式地理坐标latCenter = gpsSequence.Latitude (1);lonCenter = gpsSequence.Longitude (1);zoomLevel = 17;gpsPlayer = geoplayer(latCenter, lonCenter, zoomLevel);绘制完整的路线绘图路线(gpsPlayer、gpsSequence.纬度、gpsSequence.经度);%确定玩家的限制Xlimits = [-45 45];%米Ylimits = [-45 45];Zlimits = [-10 20];%创建一个pc播放器从激光雷达传感器可视化流点云lidarPlayer = pcplayer(xlimits, ylimits, zlimits);%自定义播放器轴标签包含(lidarPlayer。轴,“X (m)”)ylabel(lidarPlayer.Axes,‘Y(m)’) zlabel (lidarPlayer。轴,“Z (m)”)标题(lidarPlayer。轴,“激光雷达传感器数据”%在屏幕上对齐玩家HelperAlignPlayer({gpsPlayer,lidarPlayer});%外环超过GPS读数(较慢信号)g=1:高度(GPS顺序)-1%从时间表中提取地理坐标纬度= gpsSequence.Latitude (g);经度= gpsSequence.Longitude (g);%更新当前位置在GPS显示plotPosition (gpsPlayer,经度和纬度);%计算当前和下一次GPS读取之间的时间跨度timeSpan=时间范围(gpsSequence.Time(g),gpsSequence.Time(g+1));%提取这段时间内记录的激光雷达帧lidarFrames = lidarPointClouds(timeSpan,:);激光雷达读数(更快的信号)l = 1:高度(lidarFrames)提取点云ptCloud = lidarFrames.PointCloud (l);更新激光雷达显示视图(lidarPlayer ptCloud);%暂停以减慢显示速度暂停(0.01)终止终止

使用记录的激光雷达数据绘制地图

激光雷达是一种功能强大的传感器,可以在其他传感器不起作用的环境中进行感知。它们提供了车辆环境的详细、完整的360度视图。

%隐藏球员隐藏(gpsPlayer)隐藏(lidarPlayer)%选择一帧激光雷达数据来演示注册流程frameNum = 600;ptCloud = lidarPointClouds.PointCloud (frameNum);%显示并旋转ego视图以显示激光雷达数据helperVisualizeEgoView (ptCloud);

激光雷达可以用来制作厘米精度的高清地图,包括整个城市的高清地图。这些地图稍后可用于车内定位。构建这样一张地图的典型方法是对齐从移动车辆获得的连续激光雷达扫描,并将它们组合成单个大型点云。本示例的其余部分将探讨这种构建地图的方法。

  1. 结合激光雷达扫描:使用点云配准技术(如迭代最近点(ICP)算法或正态分布变换(NDT)算法)对齐连续激光雷达扫描。看见pcregistericppcregisterndt有关每种算法的更多细节。本例使用NDT,因为它通常更准确,特别是在考虑旋转时。的pcregisterndt函数返回将移动点云相对于参考点云对齐的刚性转换。通过依次组合这些变换,每个点云被转换回第一个点云的参考系。

  2. 组合对齐扫描:注册新的点云扫描并将其转换回第一个点云的参考坐标系后,可以使用pcmerge

首先取两个点云对应附近的激光雷达扫描。为了加快处理速度,并在两次扫描之间积累足够的移动,请每10次扫描一次。

skipFrames = 10;frameNum = 100;固定= lidarPointClouds.PointCloud (frameNum);= lidarPointClouds移动。PointCloud (frameNum + skipFrames);

在配准之前,对点云进行处理,以保留点云中独特的结构。这些预处理步骤包括:

  • 检测并拆除接地面

  • 侦测并移除自我载具

这些步骤在中有更详细的描述用激光雷达探测地面和障碍物的例子。在这个例子中helperProcessPointCloudHelper函数完成这些步骤。

fixedProcessed = helperProcessPointCloud(固定);movingProcessed = helperProcessPointCloud(移动);

在俯视图中显示原始和处理过的点云。在加工过程中去除了品红点。这些点对应于地平面和车辆。

hFigFixed =图;pcshowpair(固定,fixedProcessed)视图(2);%调整视图以显示顶部视图helperMakeFigurePublishFriendly (hFigFixed);%在注册之前对点云进行下采样。下采样可提高%配准精度和算法速度。下降采样率=0.1;固定下降采样率=pcdownsample(固定已处理,“随机”, downsamplePercent);movingDownsampled = pcdownsample (movingProcessed,“随机”, downsamplePercent);

对点云进行预处理后,使用无损检测进行注册。在注册之前和之后可视化对齐。

regGridStep = 5;tform = pcregisterndt(movingdownsampling, fixeddownsampling, regGridStep); / /指定采样时间movingReg = pctransform(movingProcessed, tform);%在注册前和注册后的顶部视图中可视化对齐hFigAlign=图;子批次(121)pcshowpair(移动处理、固定处理)标题(“注册前”) view(2) subplot(122) pcshowpair(movingReg, fixedProcessed) title(注册后的)视图(2)helperMakeFigurePublishFriendly (hFigAlign);

注意点云在配准后对齐良好。即使点云是紧密对齐的,对齐仍然不是完美的。

接下来,使用合并点云pcmerge

mergeGridStep=0.5;ptCloudAccum=pcmerge(fixedProcessed,movingReg,mergeGridStep);hFigAccum=figure;pcshow(ptCloudAccum)标题(“累积点云”)视图(2)helperMakeFigurePublishFriendly (hFigAccum);

现在,对单个点云的处理管道已经很好地理解了,将它们放在一个循环中,覆盖整个记录的数据序列。的helperLidarMapBuilder这门课把所有这些放在一起updateMap类的方法接收一个新的点云,并完成前面详述的步骤:

  • 对点云进行处理,去除地平面和ego vehicleprocessPointCloud方法

  • 对点云下采样。

  • 估计合并上一个点云和当前点云所需的刚性变换。

  • 将点云变换回第一帧。

  • 将点云与累积的点云图合并。

此外,updateMap方法还接受一个初始转换估计,该估计用于初始化注册。良好的初始化可以显著改善注册的结果。相反,糟糕的初始化会对注册产生不利影响。提供良好的初始化还可以提高算法的执行时间。

为注册提供初始估计的一种常见方法是使用恒定速度假设。使用来自先前迭代的转换作为初始估计。

updateDisplay方法还创建和更新二维俯视图流点云显示。

创建一个地图构建器对象mapBuilder = helperLidarMapBuilder (“降采样率”, downsamplePercent);%设置随机数种子rng (0);closeDisplay = false;numFrames =身高(lidarPointClouds);tform = rigid3d;n = 1: skipFrames: numFrames - skipFrames%获取第n个点云ptCloud = lidarPointClouds.PointCloud (n);%使用上一次迭代的转换作为初始估计%点云配准的当前迭代(等速)initTform = tform;%使用点云更新地图tform=updateMap(mapBuilder、ptCloud、initTform);%更新地图显示更新显示(mapBuilder、closeDisplay);终止

仅点云注册就可以构建车辆所经过环境的地图。虽然地图可能在局部上看起来是一致的,但它可能在整个序列上产生了显著的漂移。

将记录的GPS读数用作地面真实轨迹,以直观地评估构建地图的质量。首先转换GPS读数(纬度、经度、高度)到局部坐标系。选择与序列中第一个点云原点重合的局部坐标系。此转换使用两种转换计算:

  1. 将GPS坐标转换为本地笛卡尔东-北-北坐标latlon2local函数。用从轨迹开始的GPS位置作为参考点,定义了局部x、y、z坐标系的原点。

  2. 旋转笛卡尔坐标,使局部坐标系统与第一个激光雷达传感器坐标对齐。由于激光雷达和GPS在车辆上的确切安装配置还不知道,它们是估计的。

%选择参考点作为第一个GPS读数原点=[gpsSequence.纬度(1)、gpsSequence.经度(1)、gpsSequence.海拔(1)];%将GPS读数转换为本地东北向上坐标系[xEast,yNorth,zUp]=latlon2local(gpsSequence.纬度,gpsSequence.经度,...gpsSequence。高度,origin);%估计轨迹开始时的大致方向,以对齐局部轨迹%激光雷达坐标系theta =中值(atan2d(yNorth(1:15), xEast(1:15)));R = [cosd(90-theta) sind(90-theta) 0;信德(90 -θ)cosd(90 -θ)0;0 0 1);旋转ENU坐标与激光雷达坐标系统对齐地面轨迹=[xEast,yNorth,zUp]*R;

将地面真相轨迹叠加到构建的地图上。

(mapBuilder。轴,“上”)散射(mapBuilder。轴,groundTruthTrajectory(:,1), groundTruthTrajectory(:,2),...“绿色”“填充”);helperAddLegend (mapBuilder。轴,...“地图点”“估计轨迹”“地面实况轨迹”});

初始转弯后,估计轨迹明显偏离地面真实轨迹。仅使用点云注册估计的轨迹可能会漂移,原因有很多:

  • 传感器的噪声扫描没有足够的重叠

  • 缺乏足够强大的特征,例如,在较长的道路附近

  • 不准确的初始变换,尤其是当旋转显著时。

%关闭地图显示updateDisplay (mapBuilder,真实);

使用IMU方向改进已建地图

IMU是安装在平台上的电子设备。imu包含多个传感器,可以报告有关车辆运动的各种信息。典型的imu包括加速度计、陀螺仪和磁力计。IMU可以提供可靠的方位测量。

使用IMU读数为注册提供一个更好的初始估计。本例中使用的imu报告的传感器读数已经在设备上进行了过滤。

重置地图生成器以清除先前生成的地图重置(mapBuilder);%设置随机数种子rng (0);initTform = rigid3d;n = 1: skipFrames: numFrames - skipFrames%获取第n个点云ptCloud = lidarPointClouds.PointCloud (n);如果n > 1由于IMU传感器以更快的速度报告读数,请收集%自上次激光雷达扫描以来报告的IMU读数。prevTime = lidarPointClouds。时间(n - skipFrames);currTime = lidarPointClouds.Time (n);timeSinceScan = timerange(prevTime, currTime);imuReadings = imuOrientations (timeSinceScan,“定位”);%使用IMU读数形成初步估计initTform=helpercomputeinitialestimatefromu(imuReadings,tform);终止%使用点云更新地图tform=updateMap(mapBuilder、ptCloud、initTform);%更新地图显示更新显示(mapBuilder、closeDisplay);终止在新地图上叠加地面真实轨迹(mapBuilder。轴,“上”)散射(mapBuilder。轴,groundTruthTrajectory(:,1), groundTruthTrajectory(:,2),...“绿色”“填充”);helperAddLegend (mapBuilder。轴,...“地图点”“估计轨迹”“地面实况轨迹”});%捕获用于发布的快照snapnow;%关闭打开数字关闭([hFigFixed,hFigAlign,hFigAccum]);更新显示(mapBuilder,true);

使用来自IMU的方向估计显著改善了配准,导致更近的轨迹和更小的漂移。

金宝app支持功能

helperAlignPlayers对齐流播放器的单元阵列,使它们在屏幕上从左到右排列。

函数HelperAlignPlayer(玩家)验证属性(玩家、{“细胞”}, {“向量”})hasAxes=cellfun(p)isprop(p,“轴”),玩家,“UniformOutput”,真正的);如果~ (hasAxes)错误(“希望所有观众都能拥有Axes属性”);终止拉=得到(大的,“拉”);screenMargin = [50,100];cellfun(@getPlayerSize, players,“UniformOutput”、假);playerSizes = cell2mat (playerSizes);maxHeightInSet = max (playerSizes(1:3:结束));垂直排列球员,使最高的球员是100像素%。location = round([screenMargin(1), screenSize(4)-screenMargin(2)- maxheight set]);N = 1: numel(players) player = players{N};hFig =祖先(球员。轴,“数字”)hFig.外部位置(1:2)=位置;向右设置下一个位置位置=位置+[50+hFig.外部位置(3),0];终止函数sz=getPlayerSize(查看器)%获取父图形容器h=祖先(viewer.Axes,“数字”);深圳= h.OuterPosition (3:4);终止终止

helperVisualizeEgoView通过绕中心旋转,在自我视角中可视化点云数据。

函数球员= helperVisualizeEgoView (ptCloud)%创建pcplayer对象xlimits=ptCloud.xlimits;ylimits=ptCloud.ylimits;zlimits=ptCloud.zlimits;player=pcplayer(xlimits,ylimits,zlimits);%关闭轴线轴(player.Axes,“关”);%设置摄像头以显示自我视图camproj(球员。轴,“透视图”);camva(球员。轴,90);坎波斯(球员。轴,[0 0 0]); camtarget(player.Axes, [-1 0 0]);%将变换设置为旋转5度θ= 5;R = [cosd(theta) sind(theta) 0 0 -sind(theta) cosd(theta) 0 0 0 1 0 0 0 1];rotateByTheta = rigid3d (R);N = 0: theta: 359%将点云旋转为ptCloud=pctransform(ptCloud,rotateByTheta);显示点云视图(球员,ptCloud);暂停(0.05)终止终止

helperProcessPointCloud通过去除属于地平面或自我载体的点来处理点云。

函数ptCloudProcessed = helperProcessPointCloud (ptCloud)%检查点云是否有组织isOrganized=~ismatrix(ptCloud.Location);%如果点云是有组织的,使用基于范围的洪水填充算法% (segmentGroundFromLidarData)。否则,使用平面配件。地面分段方法=[“planefit”“rangefloodfill”];方法= groundSegmentationMethods (isOrganized + 1);如果方法= =“planefit”%以参考法向量分段地面作为主平面%指向正z方向,使用pcfitplane。为组织%点云,考虑使用segmentGroundFromLidarData代替。maxDistance = 0.4;%米maxAngDistance = 5;%度refVector=[0,0,1];% z方向[~, grounddinices] = pcfitplane(ptCloud, maxDistance, refVector, maxAngDistance);elseif方法= =“rangefloodfill”%使用基于范围的泛洪填充分割地面。groundIndices = segmentGroundFromLidarData (ptCloud);其他的错误(“预期方法为‘planefit’或‘rangefloodfill’”终止在给定的传感器半径内,以点的形式分割自我车辆传感器位置=[0,0,0];半径=3.5;EgoIndex=findNeighborsInRadius(ptCloud,传感器位置,半径);移除属于地面或自我车辆的点数ptsToKeep=true(ptCloud.Count,1);ptsToKeep(groundindex)=false;ptsToKeep(egoindex)=false;%如果点云是有组织的,保持有组织的结构如果isOrganized ptCloudProcessed=选择(ptCloud,查找(ptstokep),“OutputSize”“全部”);其他的ptCloudProcessed=select(ptCloud,find(ptstokep));终止终止

helperComputeInitialEstimateFromIMU使用IMU方向读数和先前估计的转换来估算NDT的初始转换。

函数tform = helperComputeInitialEstimateFromIMU(imureads, prevTform)%使用先前估计的转换初始化转换tform=prevTform;%如果没有IMU读数,返回如果高度(imuReadings) < = 1返回终止% IMU方向读数报告为四元数表示%到主体框架的旋转偏移。计算方向变化%在间隔期间第一个和最后一个报告的IMU方向之间%激光雷达扫描的结果。q1 = imuReadings.Orientation (1);q2 = imuReadings.Orientation(结束);计算第一次和最后一次IMU读取之间的旋转偏移% -从q2帧旋转到体帧% -从身体框架旋转到q1框架q=q1*conj(q2);%转换为欧拉角yawPitchRoll =欧拉(q,“ZYX股票”“点”);放弃俯仰和滚转角度估计。只使用方向角估计%从IMU方向。yawPitchRoll (2:3) = 0;%转换回旋转矩阵q=四元数(yawPitchRoll,“欧拉”“ZYX股票”“点”);R=rotmat(q,“点”);%使用计算旋转T形式T(1:3,1:3)=R';终止

helperAddLegend向轴添加图例。

函数helperAddLegend (hAx、标签)给坐标轴添加一个图例hLegend=图例(hAx,标签{:});设置文本颜色和字体权重hLegend.TextColor=[1];hLegend.FontWeight=“大胆”终止

helperMakeFigurePublishFriendly调整数据,使截图发布是正确的。

函数helperMakeFigurePublishFriendly(hFig)如果~是空的(hFig) &&是有效的(hFig)HandleVisibility =“回调”终止终止

另请参阅

功能

对象

相关的话题

外部网站