主要内容

单目视觉同步定位与绘图

视觉同时定位和映射(VSLAM)是指在同时映射环境的同时计算相机的位置和方向的过程。该过程仅使用相机的可视输入。vslam的申请包括增强现实,机器人和自主驾驶。

此示例显示如何从单眼相机处理图像数据以构建室内环境的地图并估计相机的轨迹。该示例使用ORB-SLAM[1],是一种基于特征的vSLAM算法。

要加快计算速度,可以从计算机视觉工具箱首选项对话框。打开计算机Vision Toolbox™首选项首页选项卡,环境部分中,点击首选项.然后选择计算机视觉的工具箱。

术语表

在此示例中经常使用以下术语:

  • 关键帧:包含定位和跟踪线索的视频帧子集。两个连续的关键帧通常包含足够的视觉变化。

  • 地图上分:一个表示从关键帧重建的环境地图的三维点的列表。

  • Covisibility图:由关键帧作为节点组成的图形。如果它们共享通用地图点,则通过边沿连接两个关键帧。边缘的权重是共享地图点的数量。

  • 基本图:共可见图的一个子图,只包含高权值的边,即更多的共享映射点。

  • 识别数据库:用来识别一个地方过去是否被访问过的数据库。数据库存储了基于特征输入包的视觉词-图像映射。它用于搜索与查询图像在视觉上相似的图像。

概述ORB-SLAM

ORB-SLAM管道包括:

  • 地图初始化: ORB-SLAM首先从两个视频帧初始化三维点的地图。采用基于二维ORB特征对应的三角剖分方法计算三维点和相对相机位姿。

  • 跟踪一旦地图被初始化,对于每一个新的帧,相机的姿态是通过匹配当前帧的特征和最后一个关键帧的特征来估计的。通过跟踪局部地图,对估计的相机姿态进行优化。

  • 当地的地图:如果当前帧被识别为关键帧,则当前帧用于创建新的3-D地图点。在这个阶段,通过调整相机的姿态和3-D点来最小化重投影误差。

  • 循环关闭:通过使用特征包方法将每个关键帧与之前的所有关键帧进行比较,从而检测出每个关键帧的循环。一旦检测到一个闭环,姿态图将被优化,以细化所有关键帧的相机姿态。

下载并探索输入图像序列

本例中使用的数据来自TUM RGB-D基准[2].您可以通过web浏览器或运行以下代码将数据下载到临时目录:

baseDownloadURL =“https://vision.in.tum.de/rgbd/dataset/freiburg3/rgbd_dataset_freiburg3_long_office_household.tgz”;dataFolder = fullfile (tempdir,“tum_rgbd_dataset”, filesep);选择= weboptions (“超时”、正);tgzFileName = [dataFolder,“fr3_office.tgz”];folderExists =存在(dataFolder,“dir”);%在临时目录中创建文件夹以保存下载的文件如果~ folderExists mkdir (dataFolder);disp ('下载fr3_office.tgz(1.38 GB)。这个下载可能需要几分钟。)WebSave(TGZFileName,BrienceOdowLobleL,选项);%提取下载文件的内容disp (“提取fr3_office。tgz (1.38 GB)…)解压(tgzFileName dataFolder);结束

创建一个imageDatastore对象来检查RGB图像。

imageFolder = [dataFolder,“rgbd_dataset_freiburg3_long_office_household / rgb /”];imds = imageageataStore(imagefolder);%检查第一个图像currFrameIdx = 1;currI = readimage(imds, currFrameIdx);himage = imshow (currI);

地图初始化

ORB-SLAM管道首先初始化包含3-D世界点的地图。这一步至关重要,对最终SLAM结果的准确性有重要影响。使用以下方法可以找到初始ORB特性点对应matchfeatures.在一对图像之间。找到对应后,使用两个几何变换模型来建立地图初始化:

  • 单应性:如果场景是平面的,单应投影变换是描述特征点对应关系的更好选择。

  • 基本矩阵:如果场景是非平面的,则必须使用基本矩阵。

单应性和基本矩阵可以用estimateGeometricTransform2DestimateFundamentalMatrix,分别。采用产生较小重投影误差的模型估计两帧间的相对旋转和平移relativeCameraPose.由于RGB图像是由单目相机拍摄的,不提供深度信息,相对平移只能恢复到特定的比例因子。

在给定相机相对位姿和两幅图像中匹配的特征点的情况下,使用由三角形组成的函数。当一个三角形地图点位于两个相机的前面,当它的重投影误差很低,并且当这个点的两个视图的视差足够大时,它是有效的。

%为重现性设置随机种子rng (0);%创建一个cameraIntrinsics对象来存储相机的内部参数。数据集的本质可以在下面的页面找到:% https://vision.in.tum.de/data/datasets/rgbd-dataset/file_formats%注意,数据集中的图像已经是未变形的,因此存在%不需要指定失真系数。focalLength = [535.4, 539.2];%,单位为像素principalPoint = [320.1, 247.6];%,单位为像素imageSize = size(currI,[1 2]);%,单位为像素intrinsics = cameraIntrinsics(focalLength, principalPoint, imageSize);%检测和提取ORB特征scaleFactor = 1.2;numLevels = 8;[preFeatures, prePoints] = helperDetectAndExtractFeatures(currI, scaleFactor, numLevels);currFrameIdx = currFrameIdx + 1;firstI = currI;%保留第一帧isMapInitialized = false;%映射初始化循环〜IsmapInitialized && curframeidx 找到假定的特征匹配indexPairs = matchFeatures(preFeatures, currFeatures,“独特的”,真的,...“MaxRatio”, 0.9,“MatchThreshold”, 40);preMatchedPoints = prePoints (indexPairs (: 1):);currMatchedPoints = currPoints (indexPairs (:, 2):);%如果没有找到足够的匹配,检查下一帧minMatches = 100;如果< minMatches . size(indexPairs, 1) < minMatches . size(indexPairs, 1继续结束preMatchedPoints = prePoints (indexPairs (: 1):);currMatchedPoints = currPoints (indexPairs (:, 2):);%计算同众法和评估重构[tformH, scoreH, inliersIdxH] = helperComputeHomography(preMatchedPoints, currMatchedPoints);计算基本矩阵和评估重建[tformf,scoundf,inliersidxf] = empercomputefundamentalmatrix(prematchedpoints,currmatchedpoints);%基于启发式选择模型ratio = scoreH/(scoreH + scoreF);ratioThreshold = 0.45;如果比率> ratiothreshold inliertformidx = Inliersidxh;tform = tformh;其他的inlierTformIdx = inliersIdxF;tform = tformF;结束%按比例计算摄像机位置。用一半%点数减少计算inlierPrePoints = preMatchedPoints (inlierTformIdx);inlierCurrPoints = currMatchedPoints (inlierTformIdx);[relOrient, relLoc, validFraction] = relativeCameraPose(tform, intrinsics, validFraction),...inlierPrePoints(1:2)结束,inlierCurrPoints(1:2:结束));%如果没有找到足够的嵌线,移动到下一帧如果validFraction < 0.9 || numel(size(relOrient))==3继续结束对两个视图进行三角测量以获得三维地图点有关= rigid3d(Relorient,Relloc);minparallax = 1;%的度[isValid, xyzWorldPoints, inlierTriangulationIdx] = helperTriangulateTwoFrames(...rigid3d, relPose, inlierPrePoints, inlierCurrPoints, intrinsics, minParallax);如果~ isValid继续结束%获得两个关键帧中的原始功能索引indexPairs = indexPairs (inlierTformIdx (inlierTriangulationIdx):);isMapInitialized = true;disp (['Map被帧1和帧初始化'num2str (currFrameIdx-1)])结束%映射初始化循环结束
用帧1和帧26初始化的映射
如果isMapInitialized关闭(himage.Parent.Parent);%关闭前面的数字显示匹配的特征hfeature = showMatchedFeatures(firstI, currI, prePoints(indexPairs(:,1)),...currPoints (indexPairs (:, 2)),“蒙太奇”);其他的错误('无法初始化映射。')结束

存储初始关键帧和映射点

使用两个帧初始化映射后,可以使用imageviewsetworldpointsethelperViewDirectionAndDepth存储两个关键帧和对应的映射点:

  • imageviewset存储关键帧及其属性,如ORB描述符、特征点和相机姿态,以及关键帧之间的连接,如特征点匹配和相对相机姿态。它还构建和更新一个姿势图。里程计边缘的绝对相机位姿和相对相机位姿存储为rigid3d对象。闭合环边的相对相机位姿存储为affine3d对象。

  • worldpointset将地图点的3-D位置和3-D映射为2-D映射对应:哪些地图点在关键帧中被观察,哪些关键帧在观察地图点。

  • helperViewDirectionAndDepth存储映射点的其他属性,例如平均视图方向、代表性ORB描述符以及可以观察到映射点的距离范围。

%创建一个空的imageviewset对象来存储关键帧vSetKeyFrames = imageviewset;%创建一个空的worldpointset对象来存储3d地图点mapPointSet = worldpointset;创建一个helperViewDirectionAndDepth对象来存储视图的方向和深度directionAndDepth = helperViewDirectionAndDepth(size(xyzWorldPoints, 1));添加第一个关键帧。放置与第一个相关联的相机%关键帧在原点,沿z轴方向preViewId = 1;vSetKeyFrames = addView(vSetKeyFrames, preViewId, rigid3d,“点”,prepoints,...“特性”, preFeatures.Features);添加第二个关键帧currViewId = 2;vSetKeyFrames = addView(vSetKeyFrames, currViewId, relPose,“点”currPoints,...“特性”, currFeatures.Features);%在第一和第二键帧之间添加连接vSetKeyFrames = addConnection(vSetKeyFrames, preViewId, currViewId, relPose,“匹配”,indexpairs);%添加3-D映射点[mapPointSet, newPointIdx] = addWorldPoints(mapPointSet, xyzWorldPoints);增加地图点的观察值preLocations = prePoints.Location;currLocations = currPoints.Location;预分频= prePoints.Scale;currScales = currPoints.Scale;在第一个关键帧中添加与地图点对应的图像点mapPointSet = addements (mapPointSet, preViewId, newPointIdx, indexPairs(:,1));%在第二个关键帧中添加与地图点对应的图像点mappointset = AddCorreskents(Mappointset,CurrViewID,NewpointIdx,IndexPairs(:,2));

改进和可视化初始重建

优化使用初始重建bundleAdjustment,它优化了相机姿态和世界点,以最小化整体重投影误差。细化后,更新地图点的属性,包括三维位置、视图方向和深度范围。您可以使用helperVisualizeMotionAndStructure可视化地图点和摄像机位置。

%在前两个关键帧上运行完整的bundle调整曲目= findtracks(vsetkeyframs);cameraposes = pose(vsetkeyframes);[RefiningPoints,Refiningabsposs] = BundleAdjustment(XYZWorldPoints,轨道,...Cameraposes,内在,“FixedViewIDs”, 1...“PointsUndistorted”,真的,“AbsoluteTolerance”,1e-7,...“RelativeTolerance”1 e15汽油,“MaxIteration”, 50);%使用地图点的中值深度缩放地图和相机姿态medianDepth =值(vecnorm (refinedPoints。');refinedPoints = refinedPoints / medianDepth;refinedAbsPoses.AbsolutePose (currViewId)。翻译=...Refiningabsposs.absolutepose(currviewid)。翻译/中美长期;resime.Translation = resimed.ranslation /中介;%更新带有精细姿势的关键帧vSetKeyFrames = updateView(vSetKeyFrames, refinedAbsPoses);vSetKeyFrames = updateConnection(vSetKeyFrames, preViewId, currViewId, relPose);%更新地图点与精炼的位置mapPointSet = updateWorldPoints(mapPointSet, newPointIdx, refinedPoints);%更新视图方向和深度directionAndDepth = update(directionAndDepth, mapPointSet, vSetKeyFrames.)视图、newPointIdx真实);%在当前帧中可视化匹配的特征关闭(hfeature.Parent.Parent);featurePlot = helperVisualizeMatchedFeatures(currI, currPoints(indexPairs(:,2)));

可视化初始地图点和摄像机轨迹mapPlot = helperVisualizeMotionAndStructure(vSetKeyFrames, mapPointSet);%显示传奇showLegend (mapPlot);

跟踪

跟踪过程使用每一帧执行,并决定何时插入新的关键帧。为了简化这个示例,我们将在找到循环闭包后终止跟踪过程。

当前关键帧的% ViewIdcurrKeyFrameId = currViewId;上一个关键帧的% ViewIdlastKeyFrameId = currViewId;具有最多共可见的参考关键帧的% ViewId%映射当前关键帧的点refKeyFrameId = currViewId;%输入图像序列中最后一个关键帧的索引lastKeyFrameIdx = currFrameIdx - 1;%输入图像序列中所有关键帧的索引addedFramesIdx = [1;lastKeyFrameIdx];isLoopClosed = false;

每帧的处理如下:

  1. 为每个新帧提取ORB特性,然后进行匹配(使用matchfeatures.),最后一个关键帧的特征已经知道相应的3-D地图点。

  2. 使用Perspective-n-Point算法估计相机的姿态estimateWorldCameraPose

  3. 给定相机姿态,将最后一帧观察到的地图点投影到当前帧中,并使用matchFeaturesInRadius

  4. 在当前帧中使用3-D到2-D对应,通过执行仅运动束调整来优化相机的姿态bundleAdjustmentMotion

  5. 将局部地图点投影到当前框架中,以搜索更多的特征对应matchFeaturesInRadius再次调整相机的姿势bundleAdjustmentMotion

  6. 跟踪的最后一步是判断当前帧是否是新的关键帧。如果当前帧是关键帧,则继续当地的地图的过程。否则,开始跟踪下一帧。

%主循环~isLoopClosed && currFrameIdx < numel(imds. files) currI = readimage(imds, currFrameIdx);[currFeatures, currPoints] = helperDetectAndExtractFeatures(currI, scaleFactor, numLevels);%跟踪最后一个关键帧% mapPointsIdx:当前帧中观察到的映射点的索引% featureIdx:文件中相应特征点的索引%当前帧[currPose, mapPointsIdx, featureIdx] = helperTrackLastKeyFrame(mapPointSet,...vSetKeyFrames。的观点,currFeatures, currPoints, lastKeyFrameId, intrinsics, scaleFactor);%跟踪本地地图% refKeyFrameId:引用关键帧的ViewId与当前帧共可见的地图点% localKeyFrameIds:当前帧连接的关键帧的ViewId[refKeyFrameId, localkeyframeid, currPose, mapPointsIdx, featureIdx] = .使用实例...helperTrackLocalMap(mapPointSet, directionAndDepth, vSetKeyFrames, mapPointsIdx,...featureIdx, currPose, currFeatures, currPoints, intrinsics, scaleFactor, numLevels);%检查当前帧是否是关键帧。%满足以下两个条件的帧为关键帧:%% 1。从上一个关键帧到现在已经过去了至少20帧%当前帧跟踪少于100个地图点%2.当前帧跟踪的地图点少于90%由参考关键帧跟踪的%点isKeyFrame = helperIsKeyFrame(mapPointSet, refKeyFrameId, lastKeyFrameIdx,...currFrameIdx mapPointsIdx);可视化匹配特征updatePlot (featurePlot、currI currPoints (featureIdx));如果〜ISKeyFrame CurrFrameIDX = CurrFrameIDX + 1;继续结束%更新当前密钥帧IDcurrKeyFrameId = currKeyFrameId + 1;

当地的地图

对每个关键帧进行局部映射。当确定一个新的关键帧时,将其添加到关键帧中,更新新关键帧观察到的地图点的属性。以确保mapPointSet包含尽可能少的异常值,一个有效的地图点必须在至少3个关键帧中观察到。

通过对当前关键帧及其连接的关键帧中的ORB特征点进行三角化来创建新的映射点。对于当前关键帧中的每一个未匹配的特征点,利用该方法在已连接的关键帧中与其他未匹配的特征点进行匹配matchfeatures..局部束调整优化当前关键帧的位姿、连接关键帧的位姿以及在这些关键帧中观察到的所有地图点。

%添加新的关键帧[mappointet,vsetkeyframes] = helperaddnewkeyframe(mappointset,vsetkeyframes,...currform, currFeatures, currPoints, mapPointsIdx, featureIdx, localKeyFrameIds);%删除在关键帧内观察到的离群点[mapPointSet, directionAndDepth, mapPointsIdx] = helperCullRecentMapPoints(mapPointSet,...directionAndDepth、mapPointsIdx newPointIdx);通过三角测量创建新的地图点minNumMatches = 20;minParallax = 3;[mapPointSet, vSetKeyFrames, newPointIdx] = helperCreateNewMapPoints(mapPointSet, vSetKeyFrames, newPointIdx)...currKeyFrameId, intrinsics, scaleFactor, minNumMatches, minParallax);%更新视图方向和深度directionAndDepth = update(directionAndDepth, mapPointSet, vSetKeyFrames.)的观点,...[mapPointsIdx;newPointIdx),真正的);局部束平差[mapPointSet, directionAndDepth, vSetKeyFrames, newPointIdx] = helperLocalBundleAdjustment(...mapPointSet、directionAndDepth vSetKeyFrames,...currKeyFrameId, intrinsic newPointIdx);可视化3D世界点和摄像机轨迹updateplot(mapplot,vsetkeyframes,mappointset);

循环关闭

循环闭合步骤采用本地映射过程处理的当前密钥帧,并尝试检测和关闭循环。使用袋式方法执行循环检测。视觉词汇代表为abagOfFeatures对象使用SURF描述符离线创建,该描述符从数据集中的大量图像中提取,调用:

包= bagfeatures (imds,'CustomExtractor', @helperSURFFeatureExtractorFunction);

在哪里洛桑国际管理发展学院是一个imageDatastore对象存储训练图像和helperSURFFeatureExtractorFunction是SURF特征提取函数。看到图像检索与袋视觉词想要查询更多的信息。

循环关闭过程增量地构建一个数据库,表示为invertedImageIndex对象,存储基于SURF功能包的可视文字到图像映射。循环候选通过查询数据库中与当前使用的关键帧在视觉上相似的图像来识别evaluateMageretrieval..如果一个候选关键帧没有连接到最后一个关键帧,并且它的三个邻居关键帧是循环候选帧,那么这个候选关键帧是有效的。

当找到一个有效的循环候选时,使用estimateGeometricTransform3D计算循环候选帧与当前关键帧之间的相对位姿。相对位姿表示存储在affine3d对象。然后添加与相对姿态和更新的循环连接mapPointSetvSetKeyFrames

初始化循环闭包数据库如果currKeyFrameId = = 3%加载脱机创建的功能数据bofData =负载(“bagOfFeaturesData.mat”);初始化位置识别数据库loopCandidates = [1;2);loopDatabase = indexImages(子集(imds, loopCandidates), bofData.bof);%在一些关键帧被创建后检查循环关闭elseifcurrKeyFrameId > 20%循环边的最小特征匹配数loopEdgeNumMatches = 50;%检测可能的循环关闭关键帧候选[isDetected, validLoopCandidates] = helperCheckLoopClosure(vSetKeyFrames, currKeyFrameId,...loopDatabase, currI, loopCandidates, loopEdgeNumMatches);如果isdetected.%添加循环关闭连接[isLoopClosed, mapPointSet, vSetKeyFrames] = helperAddLoopConnections(...mapPointSet, vSetKeyFrames, validLoopCandidates, currKeyFrameId,...Currfeatures,CurPoints,LoopedGenummatches);结束结束%如果没有检测到循环闭包,将映像添加到数据库中如果~isLoopClosed addImages(loopDatabase,子集(imds, currFrameIdx)),'verbose'、假);loopCandidates = [loopCandidates;currKeyFrameId];% #好< AGROW >结束%更新id和索引LastKeyFrameID = CurkeyFrameID;LastKeyFrameIdX = CurrFrameIdx;AddatedFramesIDX = [已添加FramesIdx;CurrframeIdx];% #好< AGROW >CurrframeIDX = CurrFrameIDX + 1;结束%主循环结束
使用功能袋创建反转图像索引。---------------------------------------------------------------------------------------使用功能袋编码图像。-------------------------------- *编码2张图片......完成。完成创建图像索引。

循环边缘添加在关键帧:6和204之间

最后,对中基本图进行相似位姿图优化vSetKeyFrames来纠正相机姿势的漂移。本质图是通过去除小于的连接而在内部创建的minNumMatches在共可见图中匹配。在相似位姿图优化后,利用优化后的位姿和相关比例尺更新地图点的三维位置。

优化姿势minNumMatches = 30;[vSetKeyFramesOptim, poseScales] = optimizePoses(vSetKeyFrames, minNumMatches,“宽容”,1E-16);%在优化姿态后更新地图点mappointset = helperupdateglobalmap(mappointset,directionanddepth,...vSetKeyFrames、vSetKeyFramesOptim poseScales);updateplot(mapplot,vsetkeyframes,mappointset);%绘制优化的摄像机轨迹optimizedPoses =姿势(vSetKeyFramesOptim);plotOptimizedTrajectory (mapPlot optimizedPoses)%更新传奇showLegend (mapPlot);

与地面真理比较

可以将优化后的相机轨迹与地面真实值进行比较,以评估ORB-SLAM的精度。下载的数据包含groundtruth.txt存储每帧相机姿态的地面真实的文件。数据已以mat文件的形式保存。您还可以计算轨迹估计的均方根误差(RMSE)。

%负荷地面真相gTruthData =负载(“orbslamGroundTruth.mat”);gTruth = gTruthData.gTruth;绘制实际的摄像机轨迹plotActualTrajectory (mapPlot gTruth (addedFramesIdx) optimizedPoses);%显示传奇showLegend (mapPlot);

评估跟踪精度HelperestimateTrajectoryError(GTRUTH(AddanedFramesIDX),优化);
关键帧轨迹的绝对RMSE (m): 0.25809

本文概述了如何利用ORB-SLAM来构建室内环境的地图和估计相机的轨迹。

金宝app支持功能

下面包括一些简短的辅助函数。较大的函数包含在单独的文件中。

helperAddLoopConnections在当前关键帧和有效循环候选帧之间添加连接。

HelperaddnewKeyFrame.添加关键帧到关键帧集。

helperCheckLoopClosure通过从数据库中检索视觉上类似的图像来检测循环候选密钥帧。

HelpercreateNewmappoints.通过三角测量创建新的地图点。

helperLocalBundleAdjustment优化当前关键帧的姿态和周围场景的地图。

helperSURFFeatureExtractorFunction实现bagfeatures中使用的SURF特征提取。

helperTrackLastKeyFrame通过跟踪最后一个关键框架来估计当前的相机姿势。

helperTrackLocalMap通过跟踪本地地图优化当前的相机姿态。

helperViewDirectionAndDepth存储平均视图方向和预测的地图点深度

helperVisualizeMatchedFeatures在一个框架中显示匹配的特征。

helperVisualizeMotionAndStructure显示地图点和摄像机轨迹。

helperDetectAndExtractFeatures从图像中检测和提取ORB特征。

函数[功能,validPoints] = helperDetectAndExtractFeatures(Irgb,...scaleFactor, numLevels, varargin)%在这个例子中,图像已经是未变形的。在一般%工作流,取消注释以下代码,以消除扭曲的图像。%% if nargin > 3% intrinsics = varargin{1};%结束%Irgb = untostortimage(IRGB,内在);%检测ORB功能Igray = im2gray (Irgb);点= detectORBFeatures (Igray,“ScaleFactor”scaleFactor,'numlevels', numLevels);%选择一个特征子集,均匀分布在整个图像中点数= selectUniform(点数,numPoints,大小(Igray, 1:2));%提取特征[features, validPoints] = extractFeatures(Igray, points);结束

HelperhomographyScore.计算单应性并评估重建。

函数[H,Score,InliersIndex] = HelperComputeHomography(MatchedPoints1,MatchedPoints2)[H,InlierslogicalIndex] = eStimateGeometricTransform2D(...matchedPoints1 matchedPoints2,“射影”...“MaxNumTrials”1 e3,“MaxDistance”,4,“信心”, 90);inlierPoints1 = matchedPoints1 (inliersLogicalIndex);inlierPoints2 = matchedPoints2 (inliersLogicalIndex);inliersIndex =找到(inliersLogicalIndex);locations1 = inlierPoints1.Location;locations2 = inlierPoints2.Location;xy1In2 = transformPointsForward(H, locations1); / /调整位置xy2In1 = transformPointsInverse(H, locations2);error1in2 = sum((locations2 - xy1In2))。2) ^ 2;error2in1 = sum((locations1 - xy2In1))。2) ^ 2; outlierThreshold = 6; score = sum(max(outlierThreshold-error1in2, 0)) +...sum (max (outlierThreshold-error2in1, 0));结束

helperFundamentalMatrixScore计算基本矩阵和评估重建。

函数[F, score, inlierindex] = helperComputeFundamentalMatrix(matchedpoint1, matchedpoint2) [F, inliersLogicalIndex] = estimateFundamentalMatrix(...matchedPoints1 matchedPoints2,“方法”“RANSAC”...“NumTrials”1 e3,“DistanceThreshold”, 0.01);inlierPoints1 = matchedPoints1 (inliersLogicalIndex);inlierPoints2 = matchedPoints2 (inliersLogicalIndex);inliersIndex =找到(inliersLogicalIndex);locations1 = inlierPoints1.Location;locations2 = inlierPoints2.Location;点到极线的距离linein1 = epipolarline(f',位置2);ERROR2IN1 =(SUM([LOCATIONS1,SUNITY(LOCIONS1,1),1)]。* LINEIN1,2))。^ 2...。/笔(lineIn1(:, 1:2)。2) ^ 2;lineIn2 = epipolarLine(F, locations1);Error1in2 = (sum([locations2, ones(size(locations2, 1),1)])。* lineIn2, 2)) ^ 2...。/笔(lineIn2(:, 1:2)。2) ^ 2;outlierThreshold = 4;score = sum(max(outlierthreshold - error1in2,0)) +...sum (max (outlierThreshold-error2in1, 0));结束

helperTriangulateTwoFrames三角形化两个框架以初始化地图。

函数[isValid, xyzPoints, inlierIdx] = helperTriangulateTwoFrames(...pose1、pose2 matchedPoints1、matchedPoints2 intrinsic minParallax) (R1, t1) = cameraPoseToExtrinsics (pose1。旋转,pose1.Translation);camMatrix1 = cameraMatrix(intrinsics, R1, t1);[R2, t2] = cameraPoseToExtrinsics(pose2. p);旋转,pose2.Translation);camMatrix2 = cameraMatrix(intrinsics, R2, t2);[xyzPoints, reprojectionErrors, isInFront] = triangulate(matchedPoints1,...matchedPoints2、camMatrix1 camMatrix2);%通过视图方向和重投影错误过滤点minReprojError = 1;inlierIdx = isInFront & reprojectionErrors < minReprojError;xyzPoints = xyzPoints(inlierIdx,:);%具有显著视差的良好双视点ray1 = xyzPoints - pose1.Translation;ray2 = xyzPoints - pose2.Translation;cosAngle = sum(ray1 .* ray2) ./ (vecnorm(ray1, 2,2) .* vecnorm(ray2, 2,2));%检查视差isValid = all(cosAngle < cosd(minParallax) & cosAngle>0);结束

HelperisKeyFrame.检查帧是否是关键帧。

函数isKeyFrame = helperIsKeyFrame (mappoint,...numPointsRefKeyFrame = nummel (findWorldPointsInView(mapPoints, refKeyFrameId));从上次键帧插入已超过20个超过20帧tooManyNonKeyFrames = currFrameIndex > lastKeyFrameIndex + 20;%追踪少于100个地图点tooFewMapPoints = nummel (mapPointsIndices) < 100;%被跟踪的地图点少于90%被跟踪的点%参考关键帧toofewtrackkedpoints = nummel (mapPointsIndices) < 0.9 * numPointsRefKeyFrame;isKeyFrame = (tooManyNonKeyFrames || tooFewMapPoints) && toofewtrackkedpoints;结束

Helpercullrecentmappoints.Cull最近增加了地图点。

函数[mapPointSet, directionAndDepth, mapPointsIdx] = helperCullRecentMapPoints(mapPointSet, directionAndDepth, mapPointsIdx, newPointIdx) outlierIdx = setdiff(newPointIdx, mapPointsIdx);如果~isempty(outlierIdx) mapPointSet = removeWorldPoints(mapPointSet, outlierIdx);directionAndDepth = remove(directionAndDepth, outlierIdx);mappointment sidx = mappointment sidx - arrayfun(@(x) nnz(x>outlierIdx), mappointment sidx);结束结束

HelperestimateTrajectoryError.计算跟踪误差。

函数rmse = helperEstimateTrajectoryError(gTruth, cameraPoses) locations = vertcat(cameraPoses. absolutepose . translation);gLocations = vertcat (gTruth.Translation);scale =中位数(vecnorm(gLocations, 2,2))/中位数(vecnorm(locations, 2,2));scaledLocations =位置*比例;rmse = sqrt(mean(sum((scaledLocations - gLocations))。^ 2, 2)));disp ([关键帧轨迹的绝对RMSE (m):num2str (rmse)]);结束

helperUpdateGlobalMap在姿态图优化后更新地图点的三维位置

函数[mapPointSet, directionAndDepth] = helperUpdateGlobalMap(...mapPointSet, directionAndDepth, vSetKeyFrames, vSetKeyFramesOptim, poseScales)%HelperupDateGlobalMap更新姿势图优化后的地图点posesOld = vSetKeyFrames.Views.AbsolutePose;一直陷于= vSetKeyFramesOptim.Views.AbsolutePose;positionsOld = mapPointSet.WorldPoints;positionsNew = positionsOld;指数= 1:mapPointSet.Count;%更新每个地图点的世界位置基于新的绝对姿态%相应的主要视图i = 1: mapPointSet。Count majorviewid = directionAndDepth.MajorViewId(i); / /显示主视图poseNew =一直陷于(majorViewIds) .T;posennew (1:3, 1:3) = posennew (1:3, 1:3) * poseScales(majorViewIds);tform = posesOld (majorViewIds)。T \ poseNew;position new (i,:) = positionsOld(i,:) * tform(1:3,1:3) + tform(4, 1:3);结束mapPointSet = updateWorldPoints(mapPointSet, indices, positionsNew);结束

参考

murr - artal, Raul, Jose Maria Martinez Montiel和Juan D. Tardos。ORB-SLAM:一种多功能、精确的单目SLAM系统。IEEE机器人学报31日。5, pp 1147- 116,2015。

[2] Sturm, Jürgen, Nikolas Engelhard, Felix Endres, Wolfram Burgard和Daniel Cremers。“评估RGB-D SLAM系统的基准”。在IEEE/RSJ智能机器人与系统国际会议论文集,页573- 580,2012。

相关的话题