使用三维激光雷达点云执行SLAM
加载数据并设置可调参数
outputFolder=完整文件(tempdir, '停车场' ); 数据URL=[ ' https://ssd.mathworks.com/supportfiles/lidar/data/ ' ... “NatickParkingLotLidarData.tar” ]; pClouds=helperDownloadData(输出文件夹,数据URL);
点云配准算法的参数
最大激光雷达范围=20;
引用向量 -垂直于地平面。 最大距离 -删除地面和天花板平面时内层的最大距离。 最大角度距离 -拟合地面和天花板平面时与参考矢量的最大角度偏差。
referenceVector=[0 0 1]; 最大距离=0.5; 最大角度距离=15;
randomSampleRatio=0.25;
gridStep=2.5; distanceMovedThreshold=0.3;
环路闭合估计算法的参数
loopClosure搜索半径=3;
nScansPerSubmap=3; subMapThresh=50;
环形区域界限=【-0.75 0.75】;
rmseThreshold=0.26;
环路闭合阈值=150; optimizationInterval=2;
初始化变量
%用于存储估计相对姿势的3D Posegraph对象 pGraph=poseGraph3D; %6×6信息矩阵的默认序列化右上三角 信息材料=[1,0,0,0:0,0,1,0,0-0,01,0,00,1,0-0,1,0,0,1,1,0,1]; %自上次姿势图优化和贴图优化以来添加的循环闭合边数 numLoopClosuresSinceLastOptimization=0; %姿势图优化后直到下次扫描时为True mapUpdated=false; %如果扫描被接受,则等于1 扫描接受=0; %用于创建和可视化3D地图的3D占用栅格对象 mapResolution=8; %每米电池数 omap=占用Map3D(mapResolution);
pcProcessed=细胞(1,长度(pClouds)); lidarScans2d=细胞(1,长度(pClouds)); submaps=单元格(1,长度(pClouds)/nScansPerSubmap); pcsToView=单元格(1,长度(pClouds));
%设置为1以在构建过程中可视化创建的地图和posegraph viewMap=1; %设置为1可在生成过程中可视化处理的点云 viewPC=0;
rng(0);
%如果要在按顺序处理点云的同时查看它们 如果 viewPC==1 pplayer=pcplayer([-50 50],[-50 50.],[-10 10], '标记大小' ,10); 结束 %如果要在构建过程中查看创建的地图和posegraph 如果 viewMap==1 ax=新图; %图轴手柄 视图(20,50); 网格 在 ; 结束
基于位姿图优化的轨迹估计与优化
点云过滤 点云下采样 点云注册 循环结束查询 姿势图优化
计数=0; %用于跟踪添加扫描数的计数器 显示( '估计机器人轨迹…' );
正在估计机器人轨迹。。。
对于 i=1:长度(pClouds) %按顺序读取点云 pc=pClouds{i};
点云过滤
ind=(-maxLidarRange<pc(:,1)&pc(: ... &-最大激光雷达范围<pc(:,2)和pc(: ... &(abs(pc(:,2))>abs(0.5*pc(:; pcl=点云(pc(ind,:));
[~,~,离群值]= ... pcfitplane(pcl,maxDistance,referenceVector,maxAngularDistance); pcl_wogrd=选择(pcl,离群值, '输出大小' , “已满” );
[~,~,离群值]= ... pcfitplane(pcl_wogrd,0.2,referenceVector,maxAngularDistance); pcl_wogrd=选择(pcl_worgrd,离群值, '输出大小' , “已满” );
ind=(pcl_wogrd.Location(:,3)<annularRegionLimits(2))&(pcl_0ogrd.Location; pcl_wogrd=选择(pcl_worgrd,ind, '输出大小' , “已满” );
点云下采样
pcl_wogrd_sampled=pc下采样(pcl_wgrd, “随机” ,randomSampleRatio); 如果 viewPC==1 %可视化向下采样的点云 视图(pplayer,pcl_wogrd_sampled); 暂停(0.001) 结束
点云注册
如果 计数==0 %第一个罐子 t形式=[]; 扫描验收=1; 其他的 如果 计数==1 tform=pcregisterndt(pcl_wogrd_sampled,prevPc,gridStep); 其他的 tform=pcregisterndt(pcl_wogrd_sampled,prevPc,gridStep, ... '初始转换' ,prevTform); 结束 relPose=[tform2rvec(tform.T')tform2quat(tform.T')]; 如果 sqrt(norm(relPose(1:3)))>距离移动阈值 添加相对位置(pGraph,relPose); 扫描验收=1; 其他的 扫描接受=0; 结束 结束
循环结束查询
从创建子映射 nScansPerSubmap(每个子映射扫描) 连续扫描。 将当前扫描与 循环闭合搜索半径 . 如果比赛分数大于 循环关闭阈值 。表示接受子映射的所有扫描都被视为可能的循环候选。 估计可能的回路候选和当前扫描之间的相对姿势。 只有当RMSE小于 rmse阈值 .
如果 扫描合格==1 计数=计数+1; pcProcessed{count}=pcl_wogrd_sampled; lidarScans2d{count}=exampleHelperCreate2DScan(pcl_wogrd_sampled); %创建子映射是为了更快地进行循环结束查询。 如果 rem(计数,nScansPerSubmap)==0 submps{count/nScansPerSubmap}=exampleHelperCreateSubmap(lidarScans2d, ... pGraph、count、nScansPerSubmap、maxLidarRange); 结束 %loopSubmapId包含匹配的子映射id(如果有的话),否则为空。 如果 (楼层(计数/nScansPerSubmap)>subMapThresh) [loopSubmapIds,~]=exampleHelperEstimateLoopCandidates(pGraph, ... count,submap,lidarScans2d{count},nScansPerSubmap, ... loopClosure SearchRadius,loopCloseThreshold,subMapThresh); 如果 ~isempty(loopSubmapIds) rmseMin=inf; %估计与当前扫描的最佳匹配 对于 k=1:长度(loopSubmapIds) %对于子映射中的每个扫描 对于 j=1:nScansPerSubmap 可能的循环候选= ... loopSubmapIds(k)*nScansPerSubmap-j+1; [loopT形式,~,rmse]=pcregisterndt(pcl_wogrd_sampled, ... pcProcessed{probableLoopCandidate},gridStep); %更新最佳循环结束候选 如果 rmse<rmse最小值 loopCandidate=probableLoopCandiate; rmseMin=rmse; 结束 如果 rmse最小值<rmse阈值 打破 ; 结束 结束 结束 %检查循环候选是否有效 如果 rmse最小值<rmse阈值 %回路闭合约束 relPose=[tform2rvec(loopTform.T')tform2quat(loopT form.T`)]; 添加相对位置(pGraph、relPose、infoMat、, ... loopCandidate,计数); numLoopClosuresSineLastOptimization=numLoop ClosuresIneLast最优化+1; 结束 结束 结束
姿势图优化
如果 (numLoopClosuresSinceLastOptimization==优化间隔)|| ... ((numLoopClosuresSinceLastOptimization>0)&&(i==长度(pClouds)) 如果 loopClosure搜索半径~=1 显示( “进行姿势图优化以减少漂移。” ); 结束 %位姿图优化 pGraph=优化PoseGraph(pGraph); loopClosure搜索半径=1; 如果 viewMap==1 位置=p图形节点; %姿势图优化后重建贴图 omap=占用Map3D(mapResolution); 对于 n=1:(pGraph.NumNodes-1) insertPointCloud(omap,位置(n,:),pcsToView{n}.removeInvalidPoints,maxLidarRange); 结束 mapUpdated=true; ax=新图; 网格 在 ; 结束 numLoopClosuresSinceLastOptimization=0; %优化后降低优化频率 %轨迹 optimizationInterval=优化间隔*7; 结束
pcToView=pcdownsample(pcl_wogrd_sampled, “随机” , 0.5); pcsToView{count}=pcToView; 如果 viewMap==1 %将点云插入到占用地图的正确位置 位置=pGraph.nodes(计数); 插入点云(omap,位置,pcToView.removeInvalidPoints,maxLidarRange); 如果 (rem(count-1,15)==0)||map已更新 示例HelperVisualizeMapAndPoseGraph(omap、pGraph、ax); 结束 mapUpdated=false; 其他的 %提供反馈以了解示例正在运行 如果 (雷姆(计数-1,15)==0) fprintf(打印)( '.' ); 结束 结束
prevPc=pcl_wogrd_sampled; prevTform=tform; 结束 结束
进行姿势图优化以减少漂移。
构建并可视化三维入住率地图
如果 (viewMap~=1)||(numLoopClosuresSinceLastOptimization>0) nodesPositions=节点(pGraph); %创建三维占用栅格 omapToView=占用Map3D(mapResolution); 对于 i=1:(大小(节点位置,1)-1) pc=pcsToView{i}; 位置=节点位置(i,:); %将点云插入到占用地图的正确位置 insertPointCloud(omapToView,位置,pc.removeInvalidPoints,maxLidarRange); 结束 图形; axisFinal=新图; 示例HelperVisualizeMapAndPoseGraph(omapToView、pGraph、axisFinal); 结束
实用程序功能
功能 pClouds=helperDownloadData(输出文件夹,lidarURL) %将数据集从给定的URL下载到输出文件夹。 lidarDataTarFile=完整文件(outputFolder, “NatickParkingLotLidarData.tar” ); 如果 ~存在(lidarDataTarFile, '文件' ) mkdir(输出文件夹); 显示( '正在下载激光雷达数据(472 MB)…' ); 网络保存(lidarDataTarFile,lidarURL); untar(lidarDataTarFile,outputFolder); 结束 %解压缩文件。 如果 (~exist(完整文件(outputFolder, “NatickParkingLotLidarData” ), “目录” )) untar(lidarDataTarFile,outputFolder); 结束 pClouds=加载(完整文件(outputFolder, “NatickParkingLotLidarData” , “NatickParkingLotLidarData.mat” )).pClouds(1:5:结束); 结束
另请参阅
相关主题
使用SLAM从二维激光雷达扫描构建地图 (激光雷达工具箱) 使用SLAM从三维激光雷达数据构建占用地图 (激光雷达工具箱)