主要内容

用激光雷达数据构建地图

这个例子展示了如何处理安装在车辆上的传感器的3-D激光雷达数据,并借助惯性测量单元(IMU)读数逐步构建地图。这样的地图可以方便的路径规划车辆导航或可用于定位。为了评估生成的地图,本示例还演示了如何将车辆的轨迹与全球定位系统(GPS)记录进行比较。

概述

高清晰度(HD)地图是映射服务,可准确地提供高达几厘米的道路的精确几何。这种精度水平使高清地图适用于自动化驾驶工作流程,例如本地化和导航。这种高清图是通过从3-D LIDAR扫描的地图构建地图而生成的,与高精度GPS和或IMU传感器一起使用,并且可用于在几厘米内定位车辆。此示例实现构建此类系统所需的功能子集。

在这个例子中,您将学习如何:

  • 加载,探索和可视化记录的驾驶数据

  • 用激光雷达扫描建立一张地图

  • 使用IMU读数改进地图

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

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

笔记:这个下载可能需要几分钟。

BriencedLoadDurl =.“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);options=weboptions('暂停',INF);lidarfilename = datafolder +“lidarPointClouds.mat”;imuFileName = dataFolder +“imuorientation.mat”;gpsFileName = dataFolder +“gpsSequence.mat”;folderExists=exist(dataFolder,“dir”);matfilesExist=exist(lidarFileName,'文件') & &存在(imuFileName'文件')......& &存在(gpsFileName'文件');如果~folderExists mkdir(dataFolder);结束如果~matfiles disp(“下载lidarPointClouds。垫(613 MB)……”)websave(lidarFileName,baseDownloadURL)+“lidarPointClouds.mat”,选项);显示('正在下载imuoritations.mat(1.2MB)…') websave(imuFileName, baseDownloadURL +“imuorientation.mat”,选项);显示('正在下载gpsSequence.mat(3 KB)…') websave(gpsFileName, baseDownloadURL +“gpsSequence.mat”、选择);结束
下载lidarPointClouds.mat(613 MB)…下载imuoritations.mat(1.2 MB)…下载gpsSequence.mat(3 KB)。。。

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

从MAT-file加载激光雷达数据data = load(lidarfilename);lidarpointclouds = data.lidarpointclouds;%显示激光雷达数据的前几行头(Lidarpointclouds)
ans = 8×1时间表时间pointcloud _____________ ________________1×1 pointcloud 23:46:10.6115 1×1 pointcloud 23:46:107116 1×1 pointcloud 23:46:10.8117 1×1 pointcloud 23:46:10.9118 1×1 POINTCLOUD 23:46:11.0119 1×1 PINTCLOUD 23:46:11.1120 1×1 PINTCLOUD 23:46:11.2120 1×1 PINTCLOUD

从MAT-file加载GPS数据。这个纬度,经度, 和高度变量的时间表用于存储由车辆上的GPS设备记录的地理坐标。

来自Mat文件的%加载GPS序列数据=加载(gpsFileName);gpsSequence=data.gpsSequence;%显示前几行GPS数据头部(GPS序列)
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:13.4565 37.4 -122.11 -42.5 33: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:17.4573 37.4 -122.11-42.5 23:46:18.4656 37.4 -122.11 -42.5

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

来自MAT文件的%加载IMU录制data =负载(imuFileName);imuOrientations = data.imuOrientations;%将IMU录音转换为四元数类型imuOrientations = convertvars (imuOrientations,'方向',“四元数”);%显示IMU数据的前几行头(imuOrientations)
ans = 8×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。格式='s';gpsFrameDuration.Format='s';imuFrameDuration。格式='s'计算帧速率Lidarrate = 1 /秒(Lidarframeduration);GPSRate = 1 /秒(GPSFRAMERTION);模仿= 1 /秒(IMUFREMARDURION);%显示帧持续时间和速率fprintf('激光雷达:%s,%3.1f Hz\n',char(lidarframeduration),lidarrate);fprintf('GPS: %s, %3.1f Hz\n'char (gpsFrameDuration), gpsRate);fprintf('IMU: %s, %3.1f Hz\n',char(imuFrameDuration),imuRate;
激光雷达:0.10008秒,10.0 Hz GPS: 1.0001秒,1.0 Hz IMU: 0.002493秒,401.1 Hz

GPS传感器是最慢的,运行速度接近1hz。激光雷达是下一个最慢的,以接近10hz的速率运行,其次是IMU以接近400hz的速率运行。

数据可视化驾驶

要了解场景包含的内容,请使用流播放器可视化录制的数据。可视化GPS读数,使用geoplayer。使用PCPlayer.

%创建一个Geoplayer以可视化流地理坐标拉脱中心= gpssequence.latitude(1);Loncenter = GPSSequence.Longitude(1);zoomlevel = 17;gpsplayer = geoplayer(拉脱Center,LonCenter,Zoomlevel);%画出完整的路线plotRoute (gpsPlayer gpsSequence。纬度,gpsSequence.Longitude);确定玩家的极限xlimits=[-45];%仪表ylimits=[-45];zlimits=[-1020];%创建一个PCPlayer,以从LIDAR传感器可视化流点云lidarplayer = pcplayer(xlimits,ylimits,zlimits);%自定义播放器轴标签xlabel(lidarPlayer.Axes,'x(m)') ylabel (lidarPlayer。轴,“Y (m)”)zlabel(lidarPlayer.Axes,'z(m)')标题(Lidarplayer.axes,“激光雷达传感器数据”)%在屏幕上对齐玩家helperAlignPlayers ({gpsPlayer, lidarPlayer});通过GPS读数外回路(信号变慢)对于g = 1:高度(gpsSequence从时间表中提取地理坐标纬度= gpssequence.latitude(g);经度= GPSSequence.Longitude(g);%更新GPS显示中的当前位置绘图位置(gpsPlayer、纬度、经度);%计算当前和下一个GPS读数之间的时间跨度时间跨度=时间范围(gpsSequence.Time(g), gpsSequence.Time(g+1));%提取在此时间跨度期间记录的LIDAR帧lidarframes = lidarpointclouds(timespan,:);LIDAR读数%内循环(更快的信号)对于l=1:高度(激光雷达框架)%提取点云ptcloud = lidarframes.pointCloud(L);%更新LIDAR显示查看(Lidarplayer,Ptcloud);%暂停以降低显示速度暂停(0.01)结束结束

使用记录的激光雷达数据建立地图

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

%隐藏玩家隐藏(gpsplayer)hide(lidarplayer)%选择一帧激光雷达数据以演示注册工作流frameNum=600;ptCloud=lidarPointClouds.PointCloud(frameNum);%显示和旋转自我视图以显示激光雷达数据Helpervisualizeegoview(Ptcloud);

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

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

  2. 结合对齐扫描:一旦一个新的点云扫描被注册并转换回第一个点云的参考框架,点云可以与第一个点云合并使用PCMerge.

首先取两个云对应于附近的LIDAR扫描。加快处理,并在扫描之间累积足够的运动,使用每第十次扫描。

skipFrames=10;frameNum=100;fixed=lidarPointClouds.PointClouds(frameNum);moving=lidarPointClouds.PointClouds(frameNum+skipFrames);

在注册之前,处理点云以便在具有独特的点云中保留结构。这些预处理步骤包括以下内容:

  • 检测并移除地面平面

  • 侦测并移除自我载具

对这些步骤进行了更详细的描述使用激光雷达接地平面和障碍物检测在这个例子中helperProcessPointCloud帮助函数完成这些步骤。

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

在顶视图中显示原始和处理的点云。在处理期间删除洋红色点。这些点对应于地面平面和自动载体。

hfigfixed =图;PCShowpair(固定,固定处理)视图(2);%调整视图以显示俯视图Helpermakefigurepublishfriency(HFigfixed);在配准前对点云进行下采样。将采样提高%登记准确性和算法速度。downsamplePercent = 0.1;fixedDownsampled = pcdownsample (fixedProcessed,“随机”,沮丧地区);移动= pcdownsample(移动处理,“随机”,沮丧地区);

在预处理点云后,使用NDT注册它们。在注册之前和之后可视化对齐。

ReggridStep = 5;tform = pcregisterndt(移动的,固定下拉采样,reggridstep);vishtreg = pctransform(移动处理,tform);在注册之前和之后,%可视化对齐hFigAlign =图;subplot(121) pcshowpair(movingProcessed, fixedProcessed) title(之前注册的)视图(2)子地块(122)pcshowpair(movingReg,fixedProcessed)标题(“注册后”)查看(2)HelperMakeFigurepublishFriendly(Hfigalign);

请注意,点云在注册后对齐良好。即使点云紧密对齐,对齐仍然不完美。

接下来,合并点云使用PCMerge.

mergeGridStep = 0.5;ptCloudAccum = pcmerge(fixedProcessed, movingReg, mergeGridStep); / /将数据合并hFigAccum =图;pcshow (ptCloudAccum)标题(“累积点云”)查看(2)HelpermakeFigurepublishFriendly(HFigAccum);

现在已经很好地理解了单对点云的处理管道,将其放在整个记录数据序列的循环中HelperlidarmapBuilder.Class将所有这些放在一起。这个更新映射类的方法采用一个新的点云,并执行前面详细介绍的步骤:

  • 使用移除地平面和ego车辆来处理点云processPointCloud方法。

  • 下采样点云。

  • 估计合并先前点云和当前点云所需的刚性转换。

  • 将点云转换回第一帧。

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

此外更新映射方法还接受初始转换估计,用于初始化注册。良好的初始化可以显着提高注册结果。相反,较差的初始化可能会对注册产生不利影响。提供良好的初始化也可以改善算法的执行时间。

为配准提供初始估计的常用方法是使用恒定速度假设。使用上一次迭代的变换作为初始估计。

这个更新方法另外创建和更新一个二维俯视图流点云显示。

%创建地图生成器对象MapBuilder = HelperlidarmapBuilder(“DownsamplePercent”,沮丧地区);%设置随机数种子rng (0);cliftisplay = false;numframes = height(lidarpointclouds);tform = rigid3d;对于n = 1:skipframes:numframes  -  skipframes得到第n个点云ptCloud = lidarPointClouds.PointCloud (n);使用来自先前迭代的转换作为初始估计点云配准的当前迭代。(恒定速度)initTform=tform;%使用点云更新地图tform = updateMap(mapBuilder, ptCloud, initTform);%更新地图显示updateDisplay (mapBuilder closeDisplay);结束

点云注册单独构建车辆遍历的环境的地图。虽然地图可能出现在本地一致的虽然,它可能在整个序列上显影了漂移。

使用记录的GPS读数作为地面真实轨迹,以视觉评估已建成地图的质量。首先将GPS读数(纬度、经度、高度)转换为当地坐标系统。选择一个与序列中第一个点云的原点重合的局部坐标系。这个转换是通过两个转换计算的:

  1. 使用Latlon2local.功能。从轨迹开始的GPS位置用作参考点,并定义本地x,y,z坐标系的原点。

  2. 旋转笛卡尔坐标,使局部坐标系与第一LIDAR传感器坐标对齐。由于车辆上的LIDAR和GP的确切安装配置是不知道的,因此估计它们。

%选择参考点作为首次GPS读数origin = [gpsSequence.Latitude(1), gpsSequence.Longitude(1), gpsSequence.Altitude(1)];%将GPS读数转换为当地的东-北-北坐标系统[xEast, yNorth, zUp] = latlon2local(gpsSequence。纬度,gpsSequence。经度,......GPSSequence.altitude,Origin);在轨迹开始时估计粗方向以对齐局部ENU%系统与激光雷达坐标系统Theta =中位数(atan2d(ynorth(1:15),Xeast(1:15)));r = [cosd(90-θ)sind(90-θ)0;-sind(90-θ)cosd(90-θ)0;0 0 1];%旋转ENU坐标与LIDAR坐标系对齐groundTruthTrajectory = [xEast, yNorth, zUp] * R;

在建立的地图上叠加地面真相轨迹。

hold(mapbuilder.axes,“开”)散布(mapBuilder.Axes、地面轨迹(:,1)、地面轨迹(:,2),......“绿色”,“填充”);Helperaddlegend(MapBuilder.axes,......{“地图点”,“预计轨迹”,'地面真理轨迹'});

初始转弯后,估计轨迹明显偏离地面真轨迹。仅使用点云配准估计的轨迹可能会漂移,原因有以下几个:

  • 嘈杂的扫描来自传感器而没有足够的重叠

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

  • 不准确的初始转换,特别是当旋转是重要的。

%关闭地图显示updatedisplay(mapbuilder,true);

使用IMU方向来改进内置地图

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

使用IMU读数来提供更好的注册初始估计。在该示例中使用的IMU报告的传感器读数已经过滤在设备上。

%重置Map Builder以清除以前构建的地图重置(mapBuilder);%设置随机数种子rng (0);initTform = rigid3d;对于n = 1:skipframes:numframes  -  skipframes得到第n个点云ptCloud = lidarPointClouds.PointCloud (n);如果n>1%由于IMU传感器以更快的速度报告读数,因此收集上次激光雷达扫描后的IMU读数。prevTime=lidarPointClouds.Time(n-skipFrames);currTime=lidarPointClouds.Time(n);TimesRunScan=timerange(prevTime,currTime);imuReadings=imuOrientations(TimesRunScan,'方向');%使用IMU读数形成初始估计initTform = helperComputeInitialEstimateFromIMU(imureads, tform);结束%使用点云更新地图tform = updateMap(mapBuilder, ptCloud, initTform);%更新地图显示updateDisplay (mapBuilder closeDisplay);结束在新地图上叠加地面真实轨迹hold(mapbuilder.axes,“开”)散布(mapBuilder.Axes、地面轨迹(:,1)、地面轨迹(:,2),......“绿色”,“填充”);Helperaddlegend(MapBuilder.axes,......{“地图点”,“预计轨迹”,'地面真理轨迹'});%捕获用于发布的快照snapnow;%未结数关闭([hFigFixed、hFigAlign hFigAccum]);updatedisplay(mapbuilder,true);

使用IMU的定向估计显着改善了注册,导致更靠近较小漂移的轨迹。

金宝app支持功能

帮手对齐流媒体播放器的单元阵列,以便在屏幕上从左到右排列。

功能helperAlignPlayers(球员)validateattributes(球员,{“细胞”}, {'向量'});hasAxes = cellfun (@ (p) isprop (p,“轴”),球员,'统一输出', 真的);如果〜所有(Lasaxes)错误('预计所有观众都有一个轴属性');结束screensize = get(groot,“屏幕大小”);screenmargin = [50,100];Playersize = Cellfun(@getPlayerSize,玩家,'统一输出'、假);playerSizes = cell2mat (playerSizes);maxHeightInSet = max (playerSizes(1:3:结束));%垂直安排玩家,以便最高的球员是100像素%顶部。location = round([screenMargin(1), screenSize(4)-screenMargin(2)- maxheight set]);对于n = 1:numel(玩家)播放器=玩家{n};hfig =祖先(player.axes,“图”);hFig.OuterPosition(1:2) =位置;%右转设置下一个位置location = location + [50+hFig.OuterPosition(3), 0]; / /当前位置结束功能深圳= getPlayerSize(观众)%获取父图形容器h =祖先(查看器。轴,“图”);sz = h.outerposition(3:4);结束结束

Helpervisualizeegoview.通过旋转围绕中心,可视化EGO视角下的点云数据。

功能Player = HelpervisualizeeGoview(PtCloud)%创建一个pcplayer对象xlimits = ptCloud.XLimits;ylimits = ptCloud.YLimits;zlimits = ptCloud.ZLimits;Player = pcplayer(xlimits, ylimits, zlimits);关闭轴线轴(球员。轴,“关闭”);设置摄像机显示自我视图camproj(player.Axes,“视角”);Camva(Player.Axes,90);Campos(Player.axes,[0 0 0]);Camtarget(Player.axes,[-1 0 0]);%设置一个变换,旋转5度θ=5;R=[cosd(θ)sind(θ)0-sind(θ)cosd(θ)0 0 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)。否则,使用平面配件。groundSegmentationMethods = [“planefit”,“洪水填充”];方法= groundSegmentationMethods (isOrganized + 1);如果方法==“planefit”%线段地面作为主导平面,参考法向量%指向正Z方向,使用PCFitplane。组织%点云,考虑使用segmentGroundFromLidarData代替。maxdistance = 0.4;%仪表maxangdistance = 5;%学位refVector = [0,0,1];%z方向[〜,groundindices] = pcfitplane(ptcloud,maxdistance,refvector,maxangdistance);elsef.方法==“洪水填充”使用基于范围的洪水填充分割地面。groundindices = segmentgroundfromlidardata(ptcloud);其他的错误(“预期的方法是‘planefit’或‘rangefloodfill’”)结束%将车辆分段为传感器给定半径内的点传感器位置= [0,0,0];半径= 3.5;egoIndices = findNeighborsInRadius(ptCloud, sensorLocation, radius);%删除属于地面或自助车辆的点ptsToKeep = true (ptCloud。统计,1);ptsToKeep (groundIndices) = false;ptsToKeep (egoIndices) = false;%如果有组织点云,则保留有组织结构如果isOrganized ptCloudProcessed = select(ptCloud, find(ptsToKeep)),“输出大小”,“全部”);其他的ptCloudProcessed = select(ptCloud, find(ptsToKeep));结束结束

HelpercomputeInitialEstimateFromimu.使用IMU方向读数和先前估计的转换估计NDT的初始转换。

功能tform = helpercomputeinitialestimatefromimu(imureadings,prevtform)%使用先前估计的转换初始化转换tform = prevTform;%如果没有IMU读数,返回如果高度(Imureadings)<= 1返回结束%IMU定向读数被报告为代表的四季度到车身框架的旋转偏移%。计算方向变化在间隔期间第一和上次报告的IMU方向之间%%的激光雷达扫描。Q1 = Imureadings.Oorientation(1);Q2 = Imureadings.Oorientation(END);计算第一次和最后一次IMU读取之间的旋转偏移% - 从Q2帧旋转到车身框架% - 从身体框架旋转到Q1框架Q = q1 * conj(q2);%转换为欧拉角yawPitchRoll=欧拉(q,“ZYX”,“点”);%放弃俯仰角和横滚角估计值。仅使用航向角估计值IMU方向的%。yawpectroll(2:3)= 0;转换回旋转矩阵q =四元数(yawPitchRoll,'euler',“ZYX”,“点”);R = rotmat (q,“点”);使用计算旋转tform。T(1:3, 1:3) = r ';结束

助手将图例添加到坐标轴上。

功能helperAddLegend (hAx、标签)%向轴添加图例hLegend = legend(hAx, labels{:});%设置文本颜色和字体重量hLegend。TextColor = [1 1 1];hLegend。FontWeight ='胆大'结束

helperMakeFigurePublishFriendly调整数字,以便通过发布捕获的屏幕截图是正确的。

功能helperMakeFigurePublishFriendly (hFig)如果〜isempty(hfig)&& iSvalid(HFIG)HFIG.HandLevisibility =“回调”结束结束

另见

功能

物体

相关话题

外部网站