主要内容

使用SLAM从激光雷达数据构建地图

此示例显示了如何处理安装在车辆上的传感器的三维激光雷达数据,以逐步构建地图,并使用同步定位和测绘(SLAM)估计车辆的轨迹。除三维激光雷达数据外,还有惯性导航传感器(INS)也用于帮助构建地图。以这种方式构建的地图可以促进车辆导航的路径规划或用于定位。

概述

这个用激光雷达数据构建地图(自动驾驶工具箱)该示例使用三维激光雷达数据和IMU读数逐步构建车辆所经过环境的地图。虽然这种方法构建了一个局部一致的地图,但它只适用于绘制小区域的地图。在较长的序列中,漂移累积为显著误差。为了克服这一限制,本例识别以前访问过的位置,并尝试使用图SLAM方法纠正累积漂移。

加载和探索记录数据

本例中使用的数据是调速发电机大满贯数据集,表示近6分钟的记录数据。将数据下载到临时目录。

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

baseDownloadURL =“https://www.mrt.kit.edu/z/publ/download/velodyneslam/data/scenario1.zip”;dataFolder = fullfile (tempdir,“kit_velodyneslam_data_scenario1”, filesep);选择= weboptions (“超时”,Inf);zipFileName=dataFolder+“scenario1.zip”%获取scenario1文件夹中PNG文件的完整文件路径。pointCloudFilePattern = fullfile (dataFolder,“场景1”,“扫描* . png”);numExpectedFiles = 2513;folderExists =存在(dataFolder,“dir”);如果~ folderExists%在临时目录中创建一个文件夹来保存下载的zip文件%的文件。mkdir(数据文件夹);disp('正在下载scenario1.zip(153 MB)…') websave(zipFileName, baseDownloadURL,选项);解压下载的文件解压缩(zipFileName dataFolder);elseiffolderExists&&numel(dir(pointCloudFilePattern))%如果在临时目录中减少了数据,请重新下载。disp ('正在下载scenario1.zip(153 MB)…') websave(zipFileName, baseDownloadURL,选项);%解压缩下载的文件。解压缩(zipFileName dataFolder)结束
下载scenario .zip (153mb)…

使用helperReadDataset函数从创建的文件夹中读取数据时间表.激光雷达捕捉到的点云以PNG图像文件的形式存储。的点云文件名列表点云表变量。要从图像文件读取点云数据,请使用helperReadPointCloudFromFile函数。这个函数接受一个图像文件名并返回pointCloud对象。直接从配置文件读取INS读数并存储在insDataTable变量。

datasetTable = helperReadDataset(dataFolder, pointcloudfileppattern);pointCloudTable = datasetTable(:, 1);insDataTable = datasetTable(:, 2:end); / /数据表

读取第一个点云并在MATLAB®命令提示符中显示它

ptCloud = helperReadPointCloudFromFile (pointCloudTable.PointCloudFileName {1});disp (ptCloud)
具有属性的点云:位置:[64×870×3单个]计数:55680 XLimits:[-78.4980 77.7062]YLimits:[-76.8795 74.7502]ZLimits:[-2.4839 2.6836]颜色:[]正常:[]强度:[]

显示第一个INS读数。这个时间表持有标题,球场,,X,Y,Z移民局的信息。

: disp (insDataTable (1))
时间戳航向纵摇X Y Z________________________________________;13-2010年6月1日13-6-11-11-11-11-11-11-11-11-11:18:18:18:18-10-10-10-10-10-10-10-10-10-10-11-11-11

可视化点云使用pcplayer,流式点云显示。车辆通过由两个环路组成的路径。在第一个环路中,车辆进行一系列转弯并返回起点。在第二个环路中,车辆沿另一条路线进行一系列转弯并再次返回起点。

%指定玩家的限制Xlimits = [-45 45];%米ylimits=[-45];zlimits=[-1020];%创建流式点云显示对象lidarPlayer=pcplayer(xlimits、ylimits、zlimits);%自定义播放器轴标签xlabel(lidarPlayer.Axes,“X (m)”) ylabel (lidarPlayer。轴,“Y (m)”) zlabel (lidarPlayer。轴,“Z (m)”)标题(lidarPlayer。轴,“激光雷达传感器数据”)跳过每一帧,因为这是一个长序列skipFrames=2;numFrames=height(pointCloudTable);n = 1: skipFrames: numFrames读点云fileName=pointCloudTable.PointCloudFileName{n};ptCloud=helperReadPointCloudFromFile(fileName);%可视化点云视图(lidarPlayer ptCloud);暂停(0.01)结束

使用里程计绘制地图

首先,使用中解释的方法用激光雷达数据构建地图(自动驾驶工具箱)示例来构建一个地图。该方法包括以下步骤:

  • 对齐激光雷达扫描:使用点云注册技术对齐连续激光雷达扫描。本例使用pcregisterndt注册扫描。通过依次组合这些变换,每个点云被转换回第一个点云的参考系。

  • 结合对齐扫描:通过合并所有转换的点云生成一张地图。

这种增量构建地图和估计车辆轨迹的方法被称为里程计

使用pcviewset对象来存储和管理跨多个视图的数据。视图集由一组连接的视图组成。

  • 每个视图存储与单个视图相关的信息。此信息包括视图的绝对姿势、在该视图中捕获的点云传感器数据以及视图的唯一标识符。使用将视图添加到视图集中addView

  • 建立视图之间的连接添加连接.连接存储诸如连接视图之间的相对转换、计算此度量(表示为信息矩阵)和相关视图标识符所涉及的不确定性等信息。

  • 使用情节方法可视化视图集建立的连接。这些连接可以用来可视化车辆所经过的路径。

隐藏(lidarPlayer)%设置随机种子以确保重复性rng (0);创建一个空视图集vSet = pcviewset;创建一个图形用于视图集显示hFigBefore =图(“姓名”,“视图设置显示”);hAxBefore =轴(hFigBefore);%初始化点云处理参数downsamplePercent = 0.1;regGridSize = 3;%初始化转换absTform=rigid3d;%对参考系的绝对变换relTform = rigid3d;%连续扫描之间的相对转换viewId = 1;skipFrames = 5;numFrames =身高(pointCloudTable);displayRate = 100;%每100帧更新一次n = 1: skipFrames: numFrames读点云文件名= pointCloudTable.PointCloudFileName {n};ptCloudOrig = helperReadPointCloudFromFile(文件名);%过程点云% -切割并拆除接地面%-分割并移除车辆ptCloud = helperProcessPointCloud (ptCloudOrig);%向下采样处理的点云ptCloud = pcdownsample (ptCloud,“随机”, downsamplePercent);firstFrame = (n = = 1);如果firstFrame%将第一次点云扫描作为视图添加到视图集中vSet=添加视图(vSet、viewId、absTform、,“点云”, ptCloudOrig);viewId = viewId + 1;ptCloudPrev = ptCloud;继续结束%使用INS估计注册的初始转换initTform=helperComputeInitialEstimateFromINS(relTform,...insDataTable (n-skipFrames: n));%计算注册当前点云的刚性转换%前点云relTform=pcregisterndt(ptCloud、ptCloudPrev、regGridSize、,...“初始化转换”, initTform);%更新绝对变换到参考帧(第一个点云)absTform = rigid3d(relTform。T * absTform。T);%将当前点云扫描作为视图添加到视图集vSet=添加视图(vSet、viewId、absTform、,“点云”, ptCloudOrig);%添加从上一个视图到当前视图的连接,表示%它们之间的相对变换vSet = addConnection(vSet, viewId-1, viewId, relTform);viewId=viewId+1;ptCloudPrev=ptCloud;initTform=relTform;如果n> 1&&mod(n,显示速率)==1个绘图(vSet,“父”,hAxBefore);刷新屏幕使现代化结束结束

视图集对象vSet,现在保存视图和连接AbsolutePose变量指定每个视图相对于第一个视图的绝对姿态。在连接vSet这个相对相位变量指定连接视图之间的相对约束信息矩阵变量为每个边指定与连接关联的不确定性。

%显示前几个视图和连接头(vSet.Views)头(vSet.Connections)
ans=8×3表格ViewId绝对姿态点云1×1刚性点云2 1×1刚性点云1×1刚性点云1×1刚性点云4 1×1刚性点云1×1刚性点云5 1×1刚性点云6 1×1刚性点云1×1刚性点云1×1刚性点云1×1刚性点云1×1刚性点云1×1刚性点云1×1刚性点云1点云4 1刚性点云1×1刚性点云1×1刚性点云1×1刚性点云1×1刚性点云1×1刚性点云6刚性点云1×1刚性点云1刚性点云1.WiWiWiWiWiWi1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3}671×1rigid3d{6×6double}781×1rigid3d{6×6double}891×1rigid3d{6×6double}

现在,使用创建的视图集构建点云地图。使用将视图绝对姿势与视图集中的点云对齐pcalign.指定网格大小以控制地图的分辨率。映射作为pointCloud对象。

ptClouds=vSet.Views.PointCloud;absPoses=vSet.Views.AbsolutePose;mapGridSize=0.2;ptCloudMap=pcalign(ptClouds,absPoses,mapGridSize);

请注意,使用此方法穿过的路径会随时间而漂移。虽然沿第一个回路返回起点的路径似乎合理,但第二个回路明显偏离起点。累积漂移导致第二个回路在距离起点几米处终止。

仅用里程计绘制的地图是不准确的。显示构建的点云地图与遍历路径。注意,第二个循环的映射和遍历路径与第一个循环不一致。

持有(hAxBefore“开”); pcshow(ptCloudMap);保持(hAxBefore,“关闭”);关上(hAxBefore.Parent)

使用姿态图优化校正漂移

图形大满贯在里程计中是一种广泛应用的解决漂移的方法。图SLAM方法以增量的方式创建一个图,其中节点对应于车辆的姿态,边缘代表约束连接姿态的传感器测量值。这样的图称为a构成图。由于测量中的噪音或不准确,姿势图包含编码矛盾信息的边。然后对构造图中的节点进行优化,以找到能够最佳解释测量的车辆姿势集。此技术称为构成图优化

要从视图集创建姿势图,可以使用createPoseGraph作用此函数用于为每个视图创建一个节点,并为视图集中的每个连接创建一条边。要优化姿势图,可以使用优化组合图(导航工具箱)函数。

图SLAM在纠正漂移方面的有效性的一个关键方面是精确检测环路,即之前访问过的地方。这就是所谓的环路闭合检测位置识别.在闭合环对应的位姿图中添加边,为被连接节点的位姿提供了一个矛盾测量方法,可以在位姿图优化过程中解决这个问题。

环路闭合可以使用描述符检测,描述符描述了激光雷达传感器可见的本地环境。这个扫描上下文描述符[1]就是这样一个描述符,它可以使用扫描上下文描述符函数。这个例子使用了scanContextLoopDetector对象来管理与每个视图对应的扫描上下文描述符。它使用detectLoop用两阶段描述符搜索算法检测循环闭包的目标函数。在第一阶段,它计算环密钥子描述符来寻找潜在的循环候选。在第二阶段,它通过设置扫描上下文距离的阈值将视图分类为循环闭包。

%设置随机种子以确保重复性rng (0);创建一个空视图集vSet = pcviewset;%创建一个环路闭合检测器loopDetector = scanContextLoopDetector;创建一个图形用于视图集显示hFigBefore =图(“姓名”,“视图设置显示”);hAxBefore =轴(hFigBefore);%初始化转换absTform=rigid3d;%对参考系的绝对变换relTform = rigid3d;%连续扫描之间的相对转换maxTolerableRMSE = 3;允许接受一个循环闭包候选的最大RMSEviewId = 1;n = 1: skipFrames: numFrames读点云文件名= pointCloudTable.PointCloudFileName {n};ptCloudOrig = helperReadPointCloudFromFile(文件名);%过程点云% -切割并拆除接地面%-分割并移除车辆ptCloud = helperProcessPointCloud (ptCloudOrig);%向下采样处理的点云ptCloud = pcdownsample (ptCloud,“随机”, downsamplePercent);firstFrame = (n = = 1);如果firstFrame%将第一次点云扫描作为视图添加到视图集中vSet=添加视图(vSet、viewId、absTform、,“点云”, ptCloudOrig);%从第一个点云提取扫描上下文描述符描述符= scanContextDescriptor (ptCloudOrig);%将第一个描述符添加到循环闭包检测器addDescriptor(loopDetector、viewId、descriptor)viewId=viewId+1;ptCloudPrev=ptCloud;继续结束%使用INS估计注册的初始转换initTform=helperComputeInitialEstimateFromINS(relTform,...insDataTable (n-skipFrames: n));%计算注册当前点云的刚性转换%前点云relTform=pcregisterndt(ptCloud、ptCloudPrev、regGridSize、,...“初始化转换”, initTform);%更新绝对变换到参考帧(第一个点云)absTform = rigid3d(relTform。T * absTform。T);%将当前点云扫描作为视图添加到视图集vSet=添加视图(vSet、viewId、absTform、,“点云”, ptCloudOrig);%添加一个从前一个视图到当前视图的连接%它们之间的相对变换vSet = addConnection(vSet, viewId-1, viewId, relTform);%从点云中提取扫描上下文描述符描述符= scanContextDescriptor (ptCloudOrig);将描述符添加到循环闭包检测器中addDescriptor (loopDetector viewId描述符)%检测候选循环闭包loopViewId = detectLoop (loopDetector);%找到了一个循环候选项如果~isempty(loopViewId)loopViewId=loopViewId(1);从视图集检索点云loopView = findView(vSet, loopViewId);ptCloudOrig = loopView.PointCloud;%过程点云ptCloudOld = helperProcessPointCloud (ptCloudOrig);下采样点云ptCloudOld = pcdownsample (ptCloudOld,“随机”, downsamplePercent);%使用注册来估计相对姿势[relTform, ~, rmse] = pcregisterndt(ptCloud, ptCloudOld,...regGridSize,“最大迭代次数”, 50);acceptLoopClosure = rmse <= maxTolerableRMSE;如果接受环闭合为简便起见,使用一个常数,小的信息矩阵%环闭合边infoMat = 0.01 * eye(6);添加一个与循环闭包相对应的连接vSet=addConnection(vSet、loopViewId、viewId、relTform、infoMat);结束结束viewId=viewId+1;ptCloudPrev=ptCloud;initTform=relTform;如果n>1 && mod(n, displayRate) == 1 hG = plot(vSet,“父”,hAxBefore);刷新屏幕使现代化结束结束

使用从视图集中创建姿势图createPoseGraph方法。姿势图是一个有向图对象具有:

  • 节点包含每个视图的绝对姿态

  • 包含每个连接的相对姿势约束的边

G = createPoseGraph (vSet);disp (G)
具有属性的有向图:Edges: [539×3 table] Nodes: [503×2 table]

除了连续视图之间的里程连接外,视图集现在还包括环路闭合连接。例如,注意第二个循环遍历和第一个循环遍历之间的新连接。这些是闭环连接。这些可以被识别为图中的边,其结束节点不是连续的。

%更新轴限制将重点放在循环闭包连接上xlim(hAxBefore,[-5050]);ylim(hAxBefore,[-10050]);找到并突出显示循环闭包连接loopEdgeIds =找到(abs (diff (G.Edges。EndNodes, 1, 2)) > 1);突出(hG,“边缘”loopEdgeIds,“EdgeColor”,“红色”,“线宽”, 3)

优化姿势图形使用优化组合图

optimG = optimizePoseGraph (G,“g2o-levenberg-marquardt”); vSetOptim=updateView(vSet,optimG.Nodes);

以优化的姿态显示视图集。注意,探测到的回路现在被合并,结果是一个更精确的轨迹。

绘图(vSetOptim,“父”,hAxBefore)

在优化的视图集中的绝对姿态现在可以用来建立一个更精确的地图。使用pcalign函数将视图集点云与优化的视图集绝对姿态对齐到一个单点云地图。指定网格大小以控制创建的点云地图的分辨率。

mapGridSize = 0.2;ptClouds = vSetOptim.Views.PointCloud;absPoses = vSetOptim.Views.AbsolutePose;ptCloudMap = pcalign(ptClouds, abspses, mapGridSize);hFigAfter =图(“姓名”,“视图设置显示(优化后)”);hAxAfter =轴(hFigAfter);pcshow (ptCloudMap“父”, hAxAfter);%覆盖视图集显示持有绘图(vSetOptim,“父”, hAxAfter);helperMakeFigurePublishFriendly (hFigAfter);

虽然精度仍然可以提高,但这个点云图明显更准确。

参考文献

  1. G.Kim和A.Kim,“扫描上下文:三维点云地图中用于位置识别的以自我为中心的空间描述符,”2018 IEEE/RSJ智能机器人与系统国际会议(IROS),马德里,2018年,第4802-4809页。

金宝app支持函数和类

helperReadDataset从指定的文件夹中读取数据到时间表中。

函数datasetTable = helperReadDataset(dataFolder, pointcloudfileppattern)%helperReadDataset将Velodyne SLAM数据集读取到时间表中%datasetTable=helperReadDataset(dataFolder)从将dataFolder中指定的%文件夹放入时间表中。这个函数%期望来自Velodyne SLAM数据集的数据。%%另请参见文件数据存储,helperReadINSConfigFile。%创建文件数据存储以按正确的顺序读取文件fileDS=fileDatastore(pointCloudFilePattern,“ReadFcn”,...@helperReadPointCloudFromFile);%从数据存储中提取文件列表pointCloudFiles = fileDS.Files;imuConfigFile = fullfile (dataFolder,“场景1”,“imu.cfg”);insDataTable = helperReadINSConfigFile (imuConfigFile);%从INS配置文件中删除坏行indatatable (1447,:) = [];%删除不使用的列datasetTable=removevars(insDataTable,...{“Num_Satellites”,“纬度”,“经度”,“高度”,“欧米茄”标题,...“欧米茄沥青”,“欧米茄卷”,“V_X”,“V_Y”,“V_ZDown”});datasetTable = addvars(datasetTable, pointCloudFiles,“之前”1....“NewVariableNames”,“PointCloudFileName”);结束

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

函数ptCloud=helperProcessPointCloud(ptCloudIn,方法)%helperProcessPointCloud处理pointCloud移除地面和自我车辆%ptCloud=helperProcessPointCloud(ptCloudIn,方法)进程% ptCloudIn通过移除地平面和自我飞行器。%方法可以是“planefit”或“rangefloodfill”。%%参见pcfitplane, pointCloud/findNeighborsInRadius。参数ptCloudIn(1,1)点云方法字符串{mustBeMember(方法,[“planefit”,“rangefloodfill”])}=“洪水填充”结束isOrganized = ~ ismatrix (ptCloudIn.Location);如果(方法)==“洪水填充”& & isOrganized)%分割地面使用洪水填充的范围图像groundFixedIdx=分段groundfromLidardata(ptCloudIn,...“ElevationAngleDelta”11);其他的以参考法线为主导平面的线段地面指向正z方向的%矢量maxDistance = 0.4;maxAngularDistance = 5;referenceVector = [0 0 1];[~, groundFixedIdx] = pcfitplane(ptCloudIn, maxDistance,...参考向量,最大角度距离);结束如果isOrganized groundFixed = false(size(ptCloudIn.Location,1),size(ptCloudIn.Location,2));其他的(ptCloudIn groundFixed = false。统计,1);结束groundFixed(groundFixedIdx)=真;%将车辆分段为传感器给定半径内的点传感器位置= [0 0 0];半径= 3.5;egoFixedIdx = findNeighborsInRadius(ptCloudIn, sensorLocation, radius);如果isOrganized egoFixed = false(size(ptCloudIn.Location,1),size(ptCloudIn.Location,2));其他的(ptCloudIn egoFixed = false。统计,1);结束egoFixed (egoFixedIdx) = true;%保留无地面和自我车辆的点云子集如果isOrganized indices = ~groundFixed & ~egoFixed;其他的索引= find(~groundFixed & ~egoFixed);结束ptCloud = select(ptCloudIn, indices);结束

helperComputeInitialEstimateFromINS根据INS读数估算初始转换以进行注册。

函数initTform=helpercomputeinitialestimatefromfromins(initTform,insData)%如果没有可用的INS读数,返回如果isempty (insData)返回结束%INS读数的X指向前方,Y指向左侧%和Z向上。下面的翻译说明了转换为激光雷达%框架。insToLidarOffset=[0-0.79-1.73];%请参阅DATAFORMAT.txtTnow=[-insData.Y(结束)、insData.X(结束)、insData.Z(结束)].+INSTOLIDARDOFFSET';Tbef=[-insData.Y(1)、insData.X(1)、insData.Z(1)].“+insToLidarOffset”;%由于车辆预计将沿地面移动,因此侧倾的变化%和音高是最小的。忽略横摇和俯仰的变化,只使用航向。Rnow = rotmat(quaternion([insData.Heading(end) 0 0]),“欧拉”,“ZYX股票”,“点”),“点”);Rbef=rotmat(四元数([insData.标题(1)0],“欧拉”,“ZYX股票”,“点”),“点”);T = [Rbef Tbef;0 0 0 1] \ [Rnow Tnow;0 0 0 1];initTform = rigid3d (T。');结束

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

函数helperMakeFigurePublishFriendly (hFig)如果~isempty(hFig)和&isvalid(hFig)hFig.Handleviability=“回调”结束结束

另见

功能

物体

相关的话题

外部网站