主要内容

用激光雷达数据构建地图

该示例示出了如何从安装在车辆上的传感器处理3-D LIDAR数据,以惯性测量单元(IMU)读数的帮助逐步构建地图。这样的地图可以促进车辆导航的路径规划或可用于本地化。为了评估所生成的地图,该示例还示出了如何将车辆的轨迹与全球定位系统(GPS)记录进行比较。

概述

高清晰度(HD)地图是映射服务,可在准确率上提供高达几厘米的道路的精确几何。这种精度水平使高清地图适用于自动化驾驶工作流程,例如本地化和导航。这种高清地图是通过从3-D LIDAR扫描的地图构建地图而产生的,与高精度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,“驾驶室段11、18、16”,filesep);选项= weboptions(“超时”,Inf);lidarFileName=dataFolder+“lidarpointclouds.mat”;imuFileName = dataFolder +“iMuoOrientations.mat”;gpsFileName = dataFolder +“gpssequence.mat”;folderxists =存在(datafolder,'dir');Matfilesexist =存在(Lidarfilename,'文件')&&存在(imufilename,'文件'...&&存在(gpsfilename,'文件');如果〜Folderexists Mkdir(DataFolder);结束如果〜Matfilesexist Disp('下载lidarpointclouds.mat(613 MB)......')Websave(Lidarfilename,BribyOrdLoadURL +“lidarpointclouds.mat”, 选项);DISP('下载imuorientation.mat(1.2 MB)......') websave(imuFileName, baseDownloadURL +“iMuoOrientations.mat”, 选项);DISP('下载GPSSequence.mat(3 KB)......') websave(gpsFileName, baseDownloadURL +“gpssequence.mat”, 选项);结束

首先,加载从Velodyne®HDL32ELIDAR保存的点云数据。使用的LIDAR数据的每次扫描都被存储为3-D点云使用pointcloud.目的。此对象使用K-D树数据结构进行内部组织数据以更快搜索。与每个激光扫描扫描相关联的时间戳被记录在其中时间时间表的变量。

从MAT-file加载激光雷达数据data = load(lidarfilename);lidarpointclouds = data.lidarpointclouds;%显示前几行的LIDAR数据头(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设备记录的地理坐标。

%加载来自Mat文件的GPS序列data = load(gpsfilename);gpssequence = data.gpssequence;%显示前几行GPS数据头(GPSSequence)
4.4.4-4.4 -4.4 4-4.4-4.4-4.4-11-11.11-4 4.5 23:46:12.5 5 4.4 4-4 4-4 4 4.11 11:46:12.4 4 4:12.5 10 10 10:12.4 4 4 4:12.4 4 4 4 4.4 4.4 4 4.4 4-4 4 4 4 4-4-12.4 4 4-12.4 4-12.4 4 4 4 4 4 4 4 4-12.11 11 11-11 11 11 11-11 11 11 11 11-4 4 4.5 5 5 5 5 5 5 5 5 5 5 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11-5 5 5 5 5 5 5 23:13:10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 16.456737.4-122.11-42.523:46:17.457337.4-122.11-42.523:46:18.465637.4-122.11 -42.5

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

来自Mat文件的%加载IMU录制数据=加载(imuFileName);imuOrientations=数据。imuOrientations;%将IMU录制转换为四元数类型imuOrientations = convertvars (imuOrientations,'方向''四元碘');%显示前几行的IMU数据头(imuOrientations)
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.format =';imuframeduration.format ='%计算帧速率Lidarrate = 1 /秒(Lidarframeduration);gpsrate = 1 /秒(gpsframedration);模仿= 1 /秒(IMUFRAMERSURION);%显示框架持续时间和速率fprintf('LIDAR:%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),刺激);
LIDAR:0.10008秒,10.0 Hz GPS:1.0001秒,1.0 Hz IMU:0.002493秒,401.1 Hz

GPS传感器是最慢的,以靠近1 Hz的速率运行。LIDAR是下一个最慢的,以靠近10 Hz的速度运行,其次是近400 Hz的速率。

数据可视化驾驶

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

%创建地质图层以可视化流式地理坐标拉脱中心= gpssequence.latitude(1);Loncenter = GPSSequence.Longitude(1);zoomlevel = 17;gpsplayer = geoplayer(拉脱Center,LonCenter,Zoomlevel);%绘制完整的路线plotRoute (gpsPlayer gpsSequence。纬度,gpsSequence.Longitude);确定玩家的极限xlimits = [-45 45];%仪表ylimits = [-45 45];zlimits = [-10 20];%创建一个pcplayer以可视化来自激光雷达传感器的流点云lidarPlayer=pcplayer(xlimits、ylimits、zlimits);%自定义播放器轴标签xlabel(lidarplayer.axes,'x(m)') ylabel (lidarPlayer。轴,'y(m)')zlabel(lidarplayer.axes,‘Z(m)’)标题(LidarPlayer.axes,'LIDAR传感器数据'%在屏幕上对齐玩家HelperalignPlayers({GPSPlayer,LidarPlayer});通过GPS读数外回路(信号变慢)为了g = 1:高度(gpsSequence%从时间表提取地理坐标纬度=gpsSequence.纬度(g);经度=gpsSequence.经度(g);%更新GPS显示中的当前位置绘图(GPSPlayer,纬度,经度);%计算当前和下一个GPS读取的时间跨度时间跨度=时间范围(gpsSequence.Time(g), gpsSequence.Time(g+1));%提取在此时间跨度期间记录的LIDAR帧lidarFrames=lidarPointClouds(时间跨度:);%内环激光雷达读数(更快的信号)为了l = 1:高度(Lidarframes)%提取点云ptcloud = lidarframes.pointCloud(L);%更新激光雷达显示器视图(lidarPlayer、ptCloud);%暂停以减慢显示屏暂停(0.01)结束结束

使用录制的LIDAR数据来构建地图

Lidars是强大的传感器,可用于在挑战环境中的感知,其中其他传感器没有用。它们提供了一种详细的,完整的360度的车辆环境视图。

%隐藏玩家隐藏(gpsPlayer)隐藏(lidarPlayer)%选择LIDAR数据帧以演示注册工作流程Framenum = 600;ptcloud = lidarpointclouds.pointcloud(framenum);%显示和旋转自我视图以显示LIDAR数据Helpervisualizeegoview(Ptcloud);

Lidars可用于构建厘米准确的高清地图,包括整个城市的高清地图。这些地图稍后可以用于车载本地化。构建这种地图的典型方法是将从移动车辆获得的连续激光乐队扫描对准,并将它们组合成单个大点云。此示例的其余部分探讨了构建地图的这种方法。

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

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

首先,获取与附近激光雷达扫描相对应的两点云。要加快处理速度,并在扫描之间积累足够的运动,请使用每十次扫描。

skipframes = 10;Framenum = 100;固定= lidarpointclouds.pointCloud(Framenum);移动= lidarpointclouds.pointcloud(framenum + skipframes);

在注册之前,处理点云,以便在点云中保留与众不同的结构。这些预处理步骤包括以下步骤:*检测并移除接地层*检测并移除车辆

这些步骤将更详细地描述用激光雷达探测地面和障碍物例子。在这个例子中,HelperProcessPointCloud.帮助函数完成这些步骤。

固定处理= HelperProcessPointCloud(固定);移动处理= HelperProcessPointCloud(移动);

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

hFigFixed=图;pcshowpair(固定,固定处理)视图(2);%调整视图以显示顶视图HelpermakeFigurepublishfriendly(HFigfixed);在配准前对点云进行下采样。将采样提高%配准精度和算法速度。downsamplePercent = 0.1;fixedDownsampled = pcdownsample (fixedProcessed,'随机的',下扫描);移动= pcdownsample(移动处理,'随机的',下扫描);

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

regGridStep=5;tform=pcregisterndt(移动下采样、固定下采样、regGridStep);movingReg=pctransform(movingProcessed,tform);在注册之前和之后的尾视图中的%可视化对齐hFigAlign=图;子批次(121)pcshowpair(移动处理、固定处理)标题('注册前')查看(2)子平台(122)PCShowpair(voldeReg,固定处理)标题('注册后')查看(2)HelpermakeFigurepublishFriendly(Hfigalign);

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

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

mergeGridStep = 0.5;ptCloudAccum = pcmerge(fixedProcessed, movingReg, mergeGridStep); / /将数据合并hFigAccum =图;pcshow (ptCloudAccum)标题('累积点云')视图(2)helperMakeFigurePublishFriendly(hFigAccum);

现在,对于单个点云的处理管道得到很好的理解,在整个记录数据序列上将其放入循环中。这helperLidarMapBuilderClass将所有这些放在一起。这UpdateMap.类的方法采用一个新的点云,并执行前面详细介绍的步骤:

  • 通过使用删除地面平面和自我车辆来处理点云ProcessPointCloud.方法。

  • 下采样点云。

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

  • 将点云转换回第一帧。

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

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

提供用于注册初始估计的常用方法是使用恒定的速度假设。使用从前一个迭代的转换为初始估计。

更新方法另外创建和更新2-D顶视图流点云显示。

%创建Map Builder对象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. 将GPS坐标转换为当地笛卡尔东北方坐标使用Latlon2local.功能。从轨迹起点开始的GPS位置用作参考点,并定义局部x、y、z坐标系的原点。

  2. 旋转笛卡尔坐标,使局部坐标系与第一个激光雷达传感器坐标对齐。由于不知道车辆上激光雷达和GPS的确切安装配置,因此会对其进行估计。

%选择参考点作为第一个GPS读数原点=[gpsSequence.纬度(1)、gpsSequence.经度(1)、gpsSequence.海拔(1)];%将GPS读数转换为当地的东-北-北坐标系统[xEast, yNorth, zUp] = latlon2local(gpsSequence。纬度,gpsSequence。经度,...GPSSequence.altitud,Origin);在轨迹开始时估计粗方向以对齐局部ENULIDAR坐标系的%系统Theta =中位数(atan2d(ynorth(1:15),Xeast(1:15)));r = [cosd(90-theta)sind(90-θ)0;-sind(90-θ)cosd(90-θ)0;0 0 1];%旋转ENU坐标以与LIDAR坐标系对齐Troundtruthtrajectory = xeast,ynorth,zup] * r;

在内置地图上叠加地面真相轨迹。

hold(mapbuilder.axes,'在')分散(MapBuilder.Axes,TountTreTrajectory(:,1),地面Truthtrajectory(:,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读数。previme = lidarpointclouds.time(n  -  skipframes);curtime = lidarpointclouds.time(n);TimeIncescan = Timerange(previmity,curtime);Imureadings = Imuorientations(TimeIncescan,'方向');%使用IMU读数形成初始估计initTform = helperComputeInitialEstimateFromIMU(imureads, tform);结束使用点云更新映射tform = updateMap(mapBuilder, ptCloud, initTform);%更新映射显示updateDisplay (mapBuilder closeDisplay);结束%在新地图上叠加地面真实轨迹hold(mapbuilder.axes,'在')分散(MapBuilder.Axes,TountTreTrajectory(:,1),地面Truthtrajectory(:,2),...“绿色”'填充');Helperaddlegend(MapBuilder.axes,...{'地图积分''估计轨迹'“地面真相轨道”});%捕获用于发布的快照snapnow;%近距离关闭([Hfigfixed,Hfigalign,Hfigaccum]);updateDisplay(mapBuilder,true);

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

金宝app支持功能

HelperalignPlayers.对齐电池阵列的流媒体播放器,以便在屏幕上从左到右排列。

功能helperAlignPlayers(球员)validateattributes(球员,{“细胞”}, {'向量'});Hasaxes = Cellfun(@(p)是isprop(p,“斧头”),球员,'统一输出', 真的);如果〜所有(Hasaxes)错误('预计所有观众都有一个轴属性');结束screensize = get(groot,'屏幕尺寸'); 屏幕边距=[50100];PlayerSize=cellfun(@getPlayerSize,玩家,'统一输出', 错误的);Playersize = Cell2mat(Playersizes);maxheightinset = max(Playersize(1:3:结束));%垂直安排玩家,以便最高的球员是100像素% 顶端。位置=圆形([屏幕边距(1),屏幕尺寸(4)-屏幕边距(2)-最大高度插入]);为了n=1:numel(players)player=players{n};hFig=祖先(player.Axes,'数字');hFig.OuterPosition(1:2) =位置;右转,%设置下一个位置location = location + [50+hFig.OuterPosition(3), 0]; / /当前位置结束功能sz=getPlayerSize(查看器)%获取父数据容器h =祖先(查看者.Axes,'数字');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);%关掉轴线轴(Player.Axes,'离开');%设置相机以显示自我视图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 0 0 -sind(θ)cosd(θ)0 0 0 0 1 0 0 0 0 1];rotatebytheta = rigid3d(r);为了n = 0:THETA:359%旋转点云θptcloud = pctransform(ptcloud,rotatebytheta);%显示点云查看(播放器、云端);暂停(0.05)结束结束

HelperProcessPointCloud.通过删除属于地平面或车辆的点来处理点云。

功能ptcloudprocessed = helperprocesspointcloud(ptcloud)%检查点云是否被组织IsOrganized =〜Ismatrix(Ptcloud.Location);%如果点云已组织,请使用基于范围的泛洪填充算法%(SegentgegroundFromlidardata)。否则,使用平面拟合。groundSegmentationMethods = [“平面图”“RangeFloodfill”];方法= GroundSegationMethods(IsOrganized + 1);如果方法==.“平面图”%线段地面作为主导平面,参考法向量%指向正Z方向,使用PCFitplane。组织%点云,考虑使用SexMeStudio Li DARDATA代替。maxdistance = 0.4;%仪表maxangdistance = 5;%程度refVector = [0,0,1];%z方向[〜,groundindices] = pcfitplane(ptcloud,maxdistance,refvector,maxangdistance);埃尔塞夫方法==.“RangeFloodfill”使用基于范围的洪水填充分割地面。Groundindices = SegmentgroundFromLIDARDATA(PTCLOUD);其他的错误(“预期的方法是‘planefit’或‘rangefloodfill’”结束%段EGO车辆作为给定的传感器半径内的点SensorLocation = [0,0,0];半径= 3.5;egoindices = findneighborsinradius(ptcloud,sensorlocation,半径);%移除属于地面或车辆的点ptstokeep = true(ptcloud.count,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 *结合(Q2);%转换为欧拉角yawpitchroll = euler(q,'Zyx'“点”);%丢弃音调和滚角估计。仅使用标题角度估计从IMU方向百分比。yawpectroll(2:3)= 0;转换回旋转矩阵q =四元数(yawPitchRoll,“欧拉”'Zyx'“点”);r = rotmat(q,“点”);使用计算旋转tform。T(1:3, 1:3) = r ';结束

杠杆制服将图例添加到坐标轴上。

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

HelpermakeFigurepublish友谊调整数字,以便发布捕获的屏幕截图是正确的。

功能HelpermakeFigurepublishFriendly(HFIG)如果~isempty(hFig)和&isvalid(hFig)hFig.Handleviability='打回来'结束结束

也可以看看

职能

对象

相关话题

外部网站