主要内容

单目视觉同步定位与作图

视觉同步定位和映射(vSLAM),是指计算相机相对于周围环境的位置和方向,同时映射环境的过程。这个过程只使用来自相机的视觉输入。vSLAM的应用asscociated包括增强现实、机器人和自动驾驶。

本例展示了如何处理来自单目摄像机的图像数据,以构建室内环境地图并估计摄像机的轨迹。本例使用ORB-SLAM[1],是一种基于特征的vSLAM算法。

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

术语表

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

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

  • 地图上分:表示由关键帧重建的环境地图的3-D点列表。

  • Covisibility图:以关键帧为节点的图。如果两个关键帧共享公共映射点,则由一条边连接。一条边的权值是共享映射点的数量。

  • 基本图:共视图的一个子图,只包含高权重的边,即更多的共享地图点。

  • 地点识别资料库:用来识别某地过去是否有人去过的数据库。数据库存储基于特征输入包的可视字到图像映射。它用于搜索与查询图像在视觉上相似的图像。

ORB-SLAM概述

ORB-SLAM管道包括:

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

  • 跟踪: map初始化后,对于每一帧新帧,通过匹配当前帧中的特征和上一关键帧中的特征来估计相机姿态。估计的相机姿态是通过跟踪当地地图来改进的。

  • 当地的地图:如果当前帧被识别为关键帧,则用于创建新的三维地图点。在这个阶段,通过调整相机姿势和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 =[数据文件夹,“fr3_office.tgz”];folderExists = exist(数据文件夹“dir”);在临时目录中创建一个文件夹来保存下载的文件如果~ folderExists mkdir (dataFolder);disp (“下载fr3_office。tgz (1.38 GB)。下载过程可能需要几分钟。”) websave(tgzFileName, baseDownloadURL,选项);提取下载文件的内容disp (“提取fr3_office。tgz (1.38 GB)…') untar(tgzFileName, dataFolder);结束

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

imageFolder =[数据文件夹,“rgbd_dataset_freiburg3_long_office_household / rgb /”];imds = imageDatastore(imageFolder);检查第一个图像currFrameIdx = 1;currI = readimage(imds, currFrameIdx);himage = imshow(currI);

地图初始化

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

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

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

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

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

为重现性设置随机种子rng (0);创建一个cameraIntrinsics对象来存储相机的内在参数。数据集的intrinsic可以在下面的页面中找到:% 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]);%,以像素为单位intrinsic = cameraIntrinsics(focalLength, principalPoint, imageSize);检测和提取ORB特征scaleFactor = 1.2;numLevels = 8;numPoints = 1000;[preFeatures, prePoints] = helperDetectAndExtractFeatures(currI, scaleFactor, numLevels, numPoints);currFrameIdx = currFrameIdx + 1;firstI = currI;保留第一帧isMapInitialized = false;%映射初始化循环~isMapInitialized && currFrameIdx < numel(imds. files) currI = readimage(imds, currFrameIdx);[currFeatures, currPoints] = helperDetectAndExtractFeatures(currI, scaleFactor, numLevels, numPoints);currFrameIdx = currFrameIdx + 1;查找假定的特征匹配indexPairs = matchFeatures(preFeatures, currFeatures,“独特的”,真的,...“MaxRatio”, 0.9,“MatchThreshold”, 40);preMatchedPoints = prePoints(indexPairs(:,1),:);currMatchedPoints = currPoints(indexPairs(:,2),:);如果没有找到足够的匹配,检查下一帧minMatches = 100;如果size(indexPairs, 1) < minMatches . size(indexPairs, 1继续结束preMatchedPoints = prePoints(indexPairs(:,1),:);currMatchedPoints = currPoints(indexPairs(:,2),:);计算单应性和评估重建[tformH, score, inliersIdxH] = helperComputeHomography(preMatchedPoints, currMatchedPoints);计算基本矩阵并评估重建[tformF, scoreF, inliersIdxF] = helperComputeFundamentalMatrix(preMatchedPoints, currMatchedPoints);根据启发式选择模型ratio = score /(score + scoreF);ratioThreshold = 0.45;如果ratio > ratioThreshold inliertlikox = inliersIdxH;tform = tformH;其他的inliersIdxF;tform = tformF;结束按比例计算相机位置。使用一半的%以减少计算量inlierPrePoints =预匹配点(inliertlikox);inlierCurrPoints = currMatchedPoints(inliertlikox);[relOrient, relLoc, validFraction] = relativeCameraPose(tform, intrinsic,...inlierPrePoints(1:2)结束,inlierCurrPoints(1:2:结束));%如果没有找到足够的内线,移动到下一帧如果validFraction < 0.9 || number (size(relOrient))==3继续结束三角化两个视图以获得3-D地图点relPose = rigid3d(relOrient, relLoc);minParallax = 1;%(度)[isValid, xyzWorldPoints, inlierTriangulationIdx] = helperTriangulateTwoFrames(...rigid3d, relPose, inlierPrePoints, inlierCurrPoints, intrinsic, minParallax);如果~ isValid继续结束获取两个关键帧中特征的原始索引indexPairs = indexPairs(inliertx (inlierTriangulationIdx),:);isMapInitialized = true;disp (['用帧1和帧初始化的映射'num2str (currFrameIdx-1)])结束映射初始化循环结束
用帧1和帧26初始化的映射
如果isMapInitialized关闭(himage.Parent.Parent);关闭上一个图形显示匹配的特征hfeature = showMatchedFeatures(firstI, currI, prePoints(indexPairs(:,1)),...currPoints (indexPairs (:, 2)),“蒙太奇”);其他的错误(“无法初始化地图。”结束

存储初始关键帧和映射点

在使用两个帧初始化映射后,您可以使用imageviewsetworldpointset而且helperViewDirectionAndDepth存储两个关键帧和对应的映射点:

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

  • worldpointset存储地图点的三维位置和三维到二维的投影对应关系:哪些地图点在一个关键帧中观察,哪些关键帧观察一个地图点。

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

创建一个空的imageviewset对象来存储关键帧vSetKeyFrames =图像视图;创建一个空的worldpointset对象来存储三维地图点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地图点[mappointment set, newPointIdx] = addWorldPoints(mappointment set, xyzWorldPoints);添加地图点的观测值preLocations = prePoints.Location;currLocations = currPoints.Location;presscales = prePoints.Scale;currScales = currPoints.Scale;在第一个关键帧中添加对应地图点的图像点mappointment set = addcorresponences (mappointment set, preViewId, newPointIdx, indexPairs(:,1));在第二个关键帧中添加对应地图点的图像点mappointment set = addcorresponences (mappointment set, currViewId, newPointIdx, indexPairs(:,2));

初始化位置识别数据库

循环检测是使用单词袋方法执行的。表示为的可视化词汇表bagOfFeatures对象离线创建,使用从数据集中大量图像中提取的ORB描述符,通过调用:

bag = bagfeatures (imds,'CustomExtractor', @helperORBFeatureExtractorFunction, 'TreeProperties', [2,20], 'StrongestFeatures', 1);

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

循环关闭过程以增量方式构建一个数据库,表示为invertedImageIndex对象,该对象存储基于ORB特性包的可视单词到图像映射。

加载离线创建的特性数据包bofData = load(“bagOfFeaturesData.mat”);初始化位置识别数据库loopDatabase = invertedImageIndex(bofData.bof,“SaveFeatureLocations”、假);将前两个关键帧的特征添加到数据库中addmagefeatures (loopDatabase, preFeatures, preViewId);addmagefeatures (loopDatabase, currFeatures, currViewId);

细化和可视化初始重建

使用优化初始重建bundleAdjustment,它优化了相机姿势和世界点,以最大限度地减少整体重投影误差。细化后,地图点的三维位置、视角方向、深度范围等属性得到更新。你可以使用helperVisualizeMotionAndStructure以可视化的地图点和摄像机位置。

在前两个关键帧上运行完整的bundle调整跟踪= findTracks(vSetKeyFrames);cameraPoses = pose (vSetKeyFrames);[refinedPoints, refinedAbsPoses] = bundleadadjustment (xyzWorldPoints, tracks,...cameraPoses intrinsic,“FixedViewIDs”, 1...“PointsUndistorted”,真的,“AbsoluteTolerance”1 e -...“RelativeTolerance”1 e15汽油,“MaxIteration”, 20岁,...“规划求解”“preconditioned-conjugate-gradient”);使用地图点的中值深度缩放地图和相机姿势medianDepth = median(vecnorm(refinedPoints.'));refinedPoints = refine points / medianDepth;refinedAbsPoses.AbsolutePose (currViewId)。翻译=...refinedAbsPoses.AbsolutePose (currViewId)。翻译/ medianDepth;relPose。翻译= relPose.Translation/medianDepth;用精致的姿势更新关键帧vSetKeyFrames =升级视图(vSetKeyFrames, refinedAbsPoses);vSetKeyFrames = updateConnection(vSetKeyFrames, preViewId, currViewId, relPose);%更新地图点与细化的位置mapotset = updateWorldPoints(mapotset, newPointIdx, refinedPoints);更新视图方向和深度directionAndDepth = update(directionAndDepth, mappointment set, vSetKeyFrames。Views, newPointIdx, true);在当前帧中可视化匹配的特征关闭(hfeature.Parent.Parent);featurePlot = helperVisualizeMatchedFeatures(currI, currPoints(indexPairs(:,2)));

可视化初始地图点和相机轨迹mapPlot = helperVisualizeMotionAndStructure(vSetKeyFrames, mapotset);%显示图例showLegend (mapPlot);

跟踪

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

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

每帧处理如下:

  1. 为每个新帧提取ORB特征,然后进行匹配(使用matchFeatures),最后一个关键帧的特征已经知道对应的三维地图点。

  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, numPoints);跟踪最后一个关键帧% mappointment sidx:当前帧中观察到的映射点的索引% featureIdx:表中对应特征点的索引%电流帧[currPose, mappointment sidx, featureIdx] = helperTrackLastKeyFrame(mappointment set,...vSetKeyFrames。的观点,currFeatures, currPoints, lastKeyFrameId, intrinsics, scaleFactor);跟踪本地地图refKeyFrameId:包含最多的参考关键帧的ViewId%与当前帧共可见的地图点localKeyFrameIds:当前帧中连接的关键帧的ViewId[refKeyFrameId, localKeyFrameIds, currPose, mapPointsIdx, featureIdx] =...helptracklocalmap (mappointment set, directionAndDepth, vSetKeyFrames, mappointment sidx,...featureIdx, currPose, currFeatures, currPoints, intrinsic, scaleFactor, numLevels);检查当前帧是否是关键帧。如果满足以下两个条件,一个帧就是关键帧:% 1。自最后一个关键帧或%当前帧跟踪少于100个地图点% 2。当前帧跟踪的地图点小于90%由参考关键帧跟踪的%点numSkipFrames = 20;isKeyFrame = helperIsKeyFrame(mapotset, refKeyFrameId, lastKeyFrameIdx,...currFrameIdx, mappointment sidx, numSkipFrames);%可视化匹配的特征updatePlot(featurePlot, currI, currPoints(featureIdx));如果~isKeyFrame currFrameIdx = currFrameIdx + 1;继续结束更新当前关键帧IDcurrKeyFrameId = currKeyFrameId + 1

当地的地图

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

通过对当前关键帧及其连接的关键帧中的ORB特征点进行三角划分来创建新的映射点。对于当前关键帧中的每个不匹配的特征点,使用搜索与连接的关键帧中的其他不匹配的特征点匹配matchFeatures.局部束调整细化当前关键帧的姿态,连接关键帧的姿态,以及在这些关键帧中观察到的所有地图点。

添加新的关键帧[mapetset, vSetKeyFrames] = helperAddNewKeyFrame(mapetset, vSetKeyFrames,...currPose, currFeatures, currPoints, mapPointsIdx, featureIdx, localKeyFrameIds);移除在少于3个关键帧内观察到的离群地图点[mappointment set, directionAndDepth, mappointment sidx] = helperCullRecentMapPoints(mappointment set, directionAndDepth, mappointment sidx)...directionAndDepth, mappointment sidx, newPointIdx);通过三角测量创建新的地图点minNumMatches = 20;minParallax = 3;[mapetset, vSetKeyFrames, newPointIdx] = helperCreateNewMapPoints(mapetset, vSetKeyFrames,...currKeyFrameId, intrinsic, scaleFactor, minNumMatches, minParallax);更新视图方向和深度directionAndDepth = update(directionAndDepth, mappointment set, vSetKeyFrames。的观点,...[mapPointsIdx;newPointIdx),真正的);%本地bundle调整[mapPointSet, directionAndDepth, vSetKeyFrames, newPointIdx] = helperLocalBundleAdjustment(...mappointment, directionAndDepth, vSetKeyFrames...currKeyFrameId, intrinsic, newPointIdx);可视化3D世界点和相机轨迹updatePlot(mapPlot, vSetKeyFrames, mapotset);

循环关闭

环路闭合检测步骤取本地映射进程处理过的当前关键帧,尝试检测并闭合环路。通过在数据库中查询与当前关键帧在视觉上相似的图像来识别循环候选evaluateImageRetrieval.如果一个候选关键帧没有连接到最后一个关键帧,并且它的三个邻居关键帧是循环候选关键帧,那么这个候选关键帧是有效的。

当找到一个有效的循环候选时,使用estimateGeometricTransform3D计算循环候选帧和当前关键帧之间的相对姿态。相对位姿表示存储在对象中的三维相似度转换affine3d对象。然后添加相对姿态和更新的循环连接mapPointSet而且vSetKeyFrames

在一些关键帧创建后检查循环是否关闭如果currKeyFrameId > 20循环边特征匹配的最小个数loopEdgeNumMatches = 50;检测可能的闭环关键帧候选[isDetected, validLoopCandidates] = helperCheckLoopClosure(vSetKeyFrames, currKeyFrameId,...loopDatabase, currI, loopEdgeNumMatches);如果isDetected添加循环关闭连接[isLoopClosed, mapPointSet, vSetKeyFrames] = helperAddLoopConnections(...mapPointSet, vSetKeyFrames, validloopcandidate, currKeyFrameId,...currFeatures, currPoints, loopEdgeNumMatches);结束结束%如果未检测到循环关闭,则将当前特性添加到数据库中如果~isLoopClosed addImageFeatures(loopDatabase, currFeatures, currKeyFrameId);结束更新id和索引lastKeyFrameId = currKeyFrameId;lastKeyFrameIdx = currFrameIdx;addedFramesIdx = [addedFramesIdx;currFrameIdx];% #好< AGROW >currFrameIdx = currFrameIdx + 1;结束主循环结束

循环边缘添加在关键帧:4和187之间

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

%优化姿势minNumMatches = 30;[vSetKeyFramesOptim, poseScales] = optimizepositions (vSetKeyFrames, minNumMatches,“宽容”1 e-16);优化姿势后更新地图点mappointment set = helperUpdateGlobalMap(mappointment set, directionAndDepth,...vSetKeyFrames, vSetKeyFramesOptim, poseScales;updatePlot(mapPlot, vSetKeyFrames, mapotset);绘制优化后的相机轨迹optimizedposed = pose (vSetKeyFramesOptim);plotOptimizedTrajectory (mapPlot optimizedPoses)%更新图例showLegend (mapPlot);

与地面真相比较

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

%负载地面真实值gTruthData = load(“orbslamGroundTruth.mat”);gTruth = gTruthData.gTruth;绘制实际相机轨迹plotActualTrajectory(mapPlot, gTruth(addedFramesIdx), optimizedpositions);%显示图例showLegend (mapPlot);

评估跟踪精度helperEstimateTrajectoryError (gTruth (addedFramesIdx) optimizedPoses);
关键帧轨迹的绝对RMSE (m): 0.054353

本文总结了如何使用ORB-SLAM构建室内环境地图并估计摄像机的轨迹。通过调整以下参数,您可以使用不同的数据集测试可视化SLAM管道:

  • numPoints:图像分辨率为480x640像素设置numPoints等于1000。如果分辨率较高,如720 金宝搏官方网站× 1280,请设置为2000。较大的值需要更多的时间进行特征提取。

  • numSkipFrames:当帧率为30fps时,设置numSkipFrames20岁。对于较慢的帧速率,请将其设置为较小的值。增加numSkipFrames提高跟踪速度,但可能会导致跟踪丢失时,相机运动快。

金宝app支持功能

下面包含简短的帮助函数。较大的函数包含在单独的文件中。

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

helperAddNewKeyFrame将关键帧添加到关键帧集。

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

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

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

helperORBFeatureExtractorFunction实现bagfeatures中使用的ORB特征提取。

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

helperTrackLocalMap通过跟踪本地地图来细化当前的相机姿势。

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

helperVisualizeMatchedFeatures在一帧中显示匹配的特征。

helperVisualizeMotionAndStructure显示地图点和相机轨迹。

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

函数[features, validPoints] = helperDetectAndExtractFeatures(Irgb,...scaleFactor, numLevels, numPoints, varargin)在本例中,图像已经是未扭曲的。总的来说%工作流,取消以下代码的注释以恢复图像的扭曲。% if nargin >% intrinsic = varargin{1};%结束% Irgb = undistortion timage (Irgb, intrinsic);检测ORB特征Igray = im2gray(Irgb);点= detectorb特征(灰色,“ScaleFactor”scaleFactor,“NumLevels”, numLevels);选择特征的子集,均匀分布在整个图像中points = selectUniform(points, numPoints, size(灰色,1:2));提取特征[features, validPoints] = extractFeatures(灰色,点);结束

helperHomographyScore计算单应性和评估重建。

函数[H, score, inliersLogicalIndex] = helpcomputehomography (matchedPoints1, matchedPoints2) [H, inliersLogicalIndex] = estimateGeometricTransform2D(...matchedPoints1 matchedPoints2,“射影”...“MaxNumTrials”1 e3,“MaxDistance”4“信心”, 90);inliersLogicalIndex = matchedPoints1;inliersLogicalIndex = matchedPoints2;inliersLogicalIndex = find(inliersLogicalIndex);location1 = inlierPoints1.Location;locations2 = inlierPoints2.Location;xy1In2 = transformPointsForward(H, location1);xy2In1 = transformPointsInverse(H, locations2);error1in2 = sum((locations2 - xy1In2)。2) ^ 2;error2in1 = sum((location1 - xy2In1)。2) ^ 2; outlierThreshold = 6; score = sum(max(outlierThreshold-error1in2, 0)) +...sum (max (outlierThreshold-error2in1, 0));结束

helperFundamentalMatrixScore计算基本矩阵并评估重构。

函数[F, score, inliersIndex] = helperComputeFundamentalMatrix(matchedPoints1, matchedPoints2) [F, inliersLogicalIndex] = estimatfundamental matrix (...matchedPoints1 matchedPoints2,“方法”“RANSAC”...“NumTrials”1 e3,“DistanceThreshold”, 0.01);inliersLogicalIndex = matchedPoints1;inliersLogicalIndex = matchedPoints2;inliersLogicalIndex = find(inliersLogicalIndex);location1 = inlierPoints1.Location;locations2 = inlierPoints2.Location;%点到极线的距离lineIn1 = epipolarLine(F', locations2);Error2in1 = (sum([locations1, ones(size(locations1, 1),1)]。* lineIn1, 2)).^2...。/笔(lineIn1(:, 1:2)。2) ^ 2;lineIn2 = epipolarLine(F, location1);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(intrinsic, R1, t1);[R2, t2] = cameraPoseToExtrinsics(pose2.)旋转,pose2.Translation);camMatrix2 = cameraMatrix(intrinsic, R2, t2);[xyzPoints, reprojectionErrors, isInFront] = triangulate(matchedPoints1,...matchedPoints2, camMatrix1, camMatrix2);通过视图方向和重投影误差筛选点minrerjerror = 1;inlierIdx = isInFront & reprojectionErrors < minrerjerror;xyzPoints = xyzPoints(inlierIdx,:);一个具有显著视差的良好的双视图。ray1 = xyzPoints - pose1.翻译;ray2 = xyzPoints - pose2.翻译;cosAngle = sum(ray1 .* ray2, 2) ./ (vecnorm(ray1, 2,2) .* vecnorm(ray2, 2,2));检查视差isValid = all(cosAngle < cosd(minParallax) & cosAngle>0);结束

helperIsKeyFrame检查一个帧是否是关键帧。

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

helperCullRecentMapPointsCull最近增加了地图点。

函数[mappointment set, directionAndDepth, mappointment sidx] = helperCullRecentMapPoints(mappointment set, directionAndDepth, mappointment sidx, newPointIdx) outlierIdx = setdiff(newPointIdx, mappointment sidx);如果~isempty(outlierIdx) mappointment set = removeWorldPoints(mappointment set, 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 = median(vecnorm(gLocations, 2,2))/ median(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;posesNew = vSetKeyFramesOptim.Views.AbsolutePose;positionsOld = mappointment . worldpoints;positionsNew = positionsOld;指数= 1:mappointment . count;根据的新绝对姿态更新每个地图点的世界位置%对应的主要视图i = indexes majorViewIds = directionAndDepth.MajorViewId(i);posennew = posesNew(majorViewIds).T;poseNew(1:3, 1:3) = poseNew(1:3, 1:3) * poseScales(majorViewIds);tform = posesOld(majorViewIds)。T \ posennew;positionsOld(i,:) * tform(1:3,1:3) + tform(4,1:3);结束mappointment set = updateWorldPoints(mappointment set, indexes, posisnew);结束

参考

穆尔-阿塔尔,劳尔,何塞·玛丽亚·马丁内斯·蒙蒂埃尔,胡安·d·塔尔多斯。“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智能机器人与系统国际会议论文集, pp. 573-580, 2012。

相关的话题