0 引言

刚刚入门学了近一个月的SLAM,但对理论推导一知半解,因此在matlab上捣鼓了个简单的2D LiDAR SLAM的demo来体会体会SLAM的完整流程。

(1)数据来源:德意志博物馆Deutsches Museum)的2D激光SLAM数据,链接如下:

Public Data - Cartographer ROS documentationgoogle-cartographer-ros.readthedocs.io

(2)SLAM过程展示(戳下面的小视频)

2D LiDAR SLAM

(3)demo得到的SLAM效果:

SLAM效果(包含路径、当前位姿、栅格地图)

1 数据准备与参数设置

1.1 2DLiDAR数据集准备

将提供的2DLiDAR数据集'b0-2014-07-11-10-58-16.bag',转为matlab的.mat数据文件,这其中包括有5522批次扫描数据,每次扫描得到1079个强度点。如下:

1.2 LiDAR的传感器参数设置

包括:扫描角度的范围、扫描角度间隔、扫描点数、扫描时间、一次扫描的线束数等。(详见源代码)

1.3 位姿与地图参数设置

包括:栅格地图单元尺寸对于的实际长度、机器人移动多少更新一次地图和位姿、初始位姿等。(详见源代码)

2 程序流程与思路

2.1 数据准备与参数设置。

2.2 遍历每一批次扫描数据(共5522批次),且对于某一批次scanIdx进行如下流程:

(1)求本批次扫描数据的局部(以移动机器人为原点)笛卡尔坐标(ReadAScan.m)。

(2)判断该批次是否为第一批?若是,则初始化(Initialize.m);若否,直接进入下一步。

(3)由本批次扫描数据(实际上这是一个含有1079个点的集合)的局部坐标,和当前位姿,求得当前扫描数据点的全局坐标(Transform.m与ExtractLocalMap.m)。

--------插入一段解释---------

局部坐标:以某次扫描时,机器人所在的空间(二维)位置为原点,得到的该次扫描数据点在空间中的二维坐标。

全局坐标:以机器人初始位置为原点,来描述任意批次的扫描数据的二维坐标。

-------------------------------------

(4)以这批数据的全局坐标,构建该次扫描得到的栅格地图(OccuGrid.m)。例如:

(5)预测下一位姿(位姿为x坐标、y坐标、旋转角度theta这个三维向量)。

*预测方法为:若当前位姿为初始位姿,预测的下一位姿=当前位姿;否则,预测的下一位姿 = 当前位姿 + ( 当前位姿 - 前一位姿 )。

(6)根据当前位姿的栅格地图来优化预测的下一位姿(FastMatch.m)。

*思路为:在预测的下一位姿上做一些细小的调整(对x、y、theta做细小调整);对于某一次调整后的预测下一位姿,利用下一位姿的扫描数据,构建下一位姿的栅格地图;以下一位姿的栅格地图与当前位姿的栅格地图的重合度作为目标函数,求该目标函数的最大值;此时得到的下一位姿即为优化后的下一位姿。(得到的结果并一定是全局最优解,因为仅在原始预测下一位姿基础上做了有限次的细微调整)

例如:预测位姿为[-16.5250; -0.7344; -3.9177];优化后为[-16.5250; -0.7156; -3.9057]。

(7)判断下一位姿与当前位姿间的差距是否达到设定的阈值?若是,进行更新(AddAKeyScan.m);否则,不进行更新。

更新步骤为:判断预测的下一位姿和当前位姿在x或y或theta上是否存在较大的差别?若是,则判断为预测有误,此时,令当前位姿=上一位姿(因此要求将上一位姿保存起来),从上一位姿开始重新开始遍历;否则,认为预测下一位姿正确,将下一位姿的数据集的全局坐标加入到全局地图中。

(8)把下一位姿并入路径中。

因此,路径为位姿[x;y;theta]的集合,如下:

(9)绘图(全局地图、路径、当前位姿)(PlotMap.m)

最终绘制的结果如下:

此外,得到的栅格地图如下:

3 MATLAB源代码(附有详细注释)

把下面这些函数放在路径下,直接运行main.m即可。

**补充一下:horizental_lidar.mat 这个数据 可戳下面的网盘链接下载,然后放在路径下:

https://pan.baidu.com/s/1KDb9zO1dDlvJq-ut9Xj0gg

----------------------

引用自:

https://github.com/meyiao/LaserSLAM?files=1

----------------------

函数列表:

(1)main.m

(2)SetLidarParameters.m

(3)ReadAScan.m

(4)Initializ.m

(5)Transform.m

(6)ExtractLocalMap.m

(7)OccuGrid.m

(8)FastMatch.m

(9)AddAKeyScan.m

(10)PlotMap.m

(11)DiffPose.m

----------------------

(1)main.m

  1. %主函数
  2. clear; close all; clc;
  3. cfig = figure(1);
  4. %cfig = figure('Position', [10,10,1280,1080]);
  5. % 激光雷达的传感器参数
  6. lidar = SetLidarParameters();
  7. % 地图参数
  8. borderSize = 1; % 边界尺寸
  9. pixelSize = 0.2; % 栅格地图的一个单元的边长 对应 实际距离pixelSize米(这里设置为0.2米)
  10. miniUpdated = false; %
  11. miniUpdateDT = 0.1; % 单位m 若机器人在x方向或y方向移动超过miniUpdateDT 则更新位姿
  12. miniUpdateDR = deg2rad(5); % 单位rad 若机器人旋转超过miniUpdateDR 则更新位姿
  13. % 如果机器人从最后一次键扫描移动了0.1米或旋转了5度,我们将添加一个新的键扫描并更新地图
  14. % 扫描匹配参数
  15. fastResolution = [0.05; 0.05; deg2rad(0.5)]; % [m; m; rad]的分辨率
  16. bruteResolution = [0.01; 0.01; deg2rad(0.1)]; % not used
  17. % 读取激光雷达数据
  18. lidar_data = load('horizental_lidar.mat');
  19. N = size(lidar_data.timestamps, 1);%扫描次数(控制下面的循环次数)
  20. % 构造一个空全局地图
  21. map.points = [];%地图点集
  22. map.connections = [];
  23. map.keyscans = [];%keyscans保存当前正确位姿的扫描数据 如果预测得到的下一位姿出现错误 则返回到距其最近的前一位姿重新计算
  24. pose = [0; 0; 0];%初始位姿为(x=0,y=0,theta=0)
  25. path = pose;%位姿并置构成路径
  26. %是否将绘制过程保存成视频
  27. saveFrame=0;
  28. if saveFrame==1
  29. % 视频保存文件定义与打开
  30. writerObj=VideoWriter('SLAMprocess.avi'); % 定义一个视频文件用来存动画
  31. open(writerObj); % 打开该视频文件
  32. end
  33. % Here we go!!!!!!!!!!!!!!!!!!!!
  34. for scanIdx = 1 : 1 : N
  35. disp(['scan ', num2str(scanIdx)]);
  36. % 得到当前的扫描 [x1,y1; x2,y2; ...]
  37. %time = lidar_data.timestamps(scanIdx) * 1e-9;%时间设置成每1e-9扫描一次
  38. scan = ReadAScan(lidar_data, scanIdx, lidar, 24);%得到该次扫描数据的局部笛卡尔坐标
  39. % 如果是第一次扫描 则初始化
  40. if scanIdx == 1
  41. map = Initialize(map, pose, scan);%把扫描数据scan坐标 通过位姿pose 转换为全局地图map坐标
  42. miniUpdated = true;
  43. continue;
  44. end
  45. % 1. 如果我们在最后一步执行了 mini更新,我们将更新 局部点集图 局部栅格地图(粗略)
  46. % 1. If we executed a mini update in last step, we shall update the local points map and local grid map (coarse)
  47. if miniUpdated
  48. localMap = ExtractLocalMap(map.points, pose, scan, borderSize);%得到当前扫描的全局坐标
  49. gridMap1 = OccuGrid(localMap, pixelSize);%从点集localMap 栅格单元尺寸对应实际长度以pixelSize 创建占用栅格地图
  50. gridMap2 = OccuGrid(localMap, pixelSize/2);%从点集localMap 栅格单元尺寸对应实际长度以pixelSize/2 创建占用栅格地图
  51. end
  52. % 2. 使用恒定速度运动模型预测当前位姿(即用前一状态到本状态的过程 作为本状态到下一状态的过程 从而由本状态预测下一状态)
  53. if scanIdx > 2
  54. pose_guess = pose + DiffPose(path(:,end-1), pose);%预测下一位姿=当前位姿+(当前位姿与上一位姿的差) pose是一个全局坐标
  55. else
  56. pose_guess = pose;
  57. end
  58. % 3. 快速匹配
  59. if miniUpdated
  60. [pose, ~] = FastMatch(gridMap1, scan, pose_guess, fastResolution);%根据当前栅格地图 优化 预测的下一位姿
  61. else
  62. [pose, ~] = FastMatch(gridMap2, scan, pose_guess, fastResolution);
  63. end
  64. % 4. 使用较高的分辨率再细化 预测下一位姿
  65. % gridMap = OccuGrid(localMap, pixelSize/2);
  66. [pose, hits] = FastMatch(gridMap2, scan, pose, fastResolution/2);%返回进一步更新的下一位姿pose
  67. % 如果机器人移动了一定距离,则执行mini更新
  68. dp = abs(DiffPose(map.keyscans(end).pose, pose));%两次位姿的差
  69. if dp(1)>miniUpdateDT || dp(2)>miniUpdateDT || dp(3)>miniUpdateDR
  70. miniUpdated = true;
  71. [map, pose] = AddAKeyScan(map, gridMap2, scan, pose, hits,...
  72. pixelSize, bruteResolution, 0.1, deg2rad(3));
  73. else
  74. miniUpdated = false;
  75. end
  76. path = [path, pose]; %把当前位姿pose 并入路径path
  77. % ===== Loop Closing =========================================
  78. % if miniUpdated
  79. % if TryLoopOrNot(map)
  80. % map.keyscans(end).loopTried = true;
  81. % map = DetectLoopClosure(map, scan, hits, 4, pi/6, pixelSize);
  82. % end
  83. % end
  84. %----------------------------------------------------------------------
  85. % 绘图
  86. if mod(scanIdx, 30) == 0%每30步画一次图
  87. PlotMap(cfig, map, path, scan, scanIdx);
  88. %获取视频帧并保存成视频
  89. if saveFrame==1
  90. frame = getframe(cfig);
  91. writeVideo(writerObj, frame);
  92. end
  93. end
  94. end
  95. if saveFrame==1
  96. close(writerObj); %关闭视频文件句柄
  97. end

(2)SetLidarParameters.m

  1. %激光雷达传感器参数
  2. %Laser sensor's parameters
  3. function lidar = SetLidarParameters()
  4. lidar.angle_min = -2.351831;%最小扫描角
  5. lidar.angle_max = 2.351831;%最大扫描角
  6. lidar.angle_increment = 0.004363;%角度增量 即lidar相邻线束之间的夹角
  7. lidar.npoints = 1079;
  8. lidar.range_min = 0.023;
  9. lidar.range_max = 60;
  10. lidar.scan_time = 0.025;%扫描时间
  11. lidar.time_increment = 1.736112e-05;%时间增量
  12. lidar.angles = (lidar.angle_min : lidar.angle_increment : lidar.angle_max)';%一次扫描各线束的角度

(3)ReadAScan.m

  1. %将LiDARdidx次扫描数据从极坐标转化为笛卡尔坐标(相对于小车的局部坐标)
  2. % Read a laser scan
  3. function scan = ReadAScan(lidar_data, idx, lidar, usableRange)
  4. %--------------------------------------------------------------------------
  5. % 输入:
  6. %lidar_data为读取的LiDAR扫描数据
  7. %idx为扫描次数的索引值
  8. %lidar为由SetLidarParameters()设置的LiDAR参数
  9. %usableRange为可使用的范围
  10. %--------------------------------------------------------------------------
  11. angles = lidar.angles;%
  12. ranges = lidar_data.ranges(idx, :)';%选取LiDAR数据的ranges中idx索引对应的这次扫描的数据
  13. % 删除范围不太可靠的点
  14. % Remove points whose range is not so trustworthy
  15. maxRange = min(lidar.range_max, usableRange);
  16. isBad = ranges < lidar.range_min | ranges > maxRange;%ranges中小于最小角度或大于最大角度的 数据的 索引下标
  17. angles(isBad) = [];
  18. ranges(isBad) = [];
  19. % 从极坐标转换为笛卡尔坐标
  20. % Convert from polar coordinates to cartesian coordinates
  21. [xs, ys] = pol2cart(angles, ranges);%(angles, ranges)为极坐标中的(theta,rho)
  22. scan = [xs, ys];
  23. end

(4)Initializ.m

  1. %针对第一次扫描的初始化
  2. function map = Initialize(map, pose, scan)
  3. %--------------------------------------------------------------------------
  4. %输入
  5. % map为地图(全局)
  6. % pose
  7. % scan
  8. %--------------------------------------------------------------------------
  9. % 把对于小车的局部坐标数据 转化为 全局地图坐标
  10. % Points in world frame
  11. map.points = Transform(scan, pose);%将转化为全局坐标后的扫描数据scan放入全局地图点集
  12. %
  13. % Key scans' information
  14. k = length(map.keyscans);
  15. map.keyscans(k+1).pose = pose;
  16. map.keyscans(k+1).iBegin = 1;
  17. map.keyscans(k+1).iEnd = size(scan, 1);
  18. map.keyscans(k+1).loopClosed = true;
  19. map.keyscans(k+1).loopTried = false;

(5)Transform.m

  1. %把局部坐标转化为全局坐标
  2. function tscan = Transform(scan, pose)
  3. %--------------------------------------------------------------------------
  4. %输入
  5. % pose为当前位姿(x坐标tx y坐标ty 旋转角theta)
  6. % scan为某次扫描数据的局部笛卡尔坐标
  7. %输出
  8. % tscan 通过当前位姿pose 将当前扫描数据的局部笛卡尔坐标scan 转换为的全局笛卡尔坐标
  9. %--------------------------------------------------------------------------
  10. tx = pose(1);
  11. ty = pose(2);
  12. theta = pose(3);
  13. ct = cos(theta);
  14. st = sin(theta);
  15. R = [ct, -st; st, ct];
  16. tscan = scan * (R');
  17. tscan(:,1) = tscan(:,1) + tx;
  18. tscan(:,2) = tscan(:,2) + ty;

(6)ExtractLocalMap.m

  1. % 从全局地图中 提取当前扫描周围的局部地图 的全局坐标
  2. % Extract a local map around current scan
  3. function localMap = ExtractLocalMap(points, pose, scan, borderSize)
  4. %--------------------------------------------------------------------------
  5. %输入
  6. % points为全局地图点集
  7. % pose为当前位姿
  8. % scan为当前扫描数据的局部坐标
  9. % borderSize
  10. %--------------------------------------------------------------------------
  11. % 将当前扫描数据坐标scan 转化为全局坐标scan_w
  12. % Transform current scan into world frame
  13. scan_w = Transform(scan, pose);
  14. % 设置 左上角 右下角
  15. % Set top-left & bottom-right corner
  16. minX = min(scan_w(:,1) - borderSize);
  17. minY = min(scan_w(:,2) - borderSize);
  18. maxX = max(scan_w(:,1) + borderSize);
  19. maxY = max(scan_w(:,2) + borderSize);
  20. % 提取位于范围内的全局地图中的点
  21. % Extract
  22. isAround = points(:,1) > minX...
  23. & points(:,1) < maxX...
  24. & points(:,2) > minY...
  25. & points(:,2) < maxY;
  26. %从全局地图中提取到的当前扫描点
  27. localMap = points(isAround, :);

(7)OccuGrid.m

  1. % 从点集创建占用栅格地图
  2. % Create an occupancy grid map from points
  3. function gridmap = OccuGrid(pts, pixelSize)
  4. %--------------------------------------------------------------------------
  5. %输入
  6. % pts为当前扫描得到点集的全局坐标
  7. % pixelSize表示 栅格地图一个单元的边长 对应 实际距离pixelSize
  8. %--------------------------------------------------------------------------
  9. % 网格尺寸
  10. % Grid size
  11. minXY = min(pts) - 3 * pixelSize;%min(pts)返回x的最小值和y的最小值构成的向量(这并不一定是对应左下角,因为可能图里面没有左下角)
  12. maxXY = max(pts) + 3 * pixelSize;% +3*pixelSize意思是 构成的栅格地图中 占用栅格最边界离地图边界留有3个栅格单元的余量
  13. Sgrid = round((maxXY - minXY) / pixelSize) + 1;%Sgrid(1)为x轴向栅格数量,Sgrid(2)为y轴向栅格数量
  14. N = size(pts, 1);%点集 里面 点的个数
  15. %hits为被占用的栅格的二维坐标 (第hits(1)块,第hits(2)块)
  16. hits = round( (pts-repmat(minXY, N, 1)) / pixelSize ) + 1;%点集里每个点的坐标 都减去它们的左下角坐标 再除单个栅格尺寸 再取整 再+1
  17. %上面这一步使得 得到的栅格地图会较原始地图出现一个翻转(当点集里不存在左下角时会出现翻转)
  18. idx = (hits(:,1)-1)*Sgrid(2) + hits(:,2);%把被占用的栅格的二维坐标转化为一维坐标
  19. %构造一个空的栅格地图
  20. grid = false(Sgrid(2), Sgrid(1));
  21. %将被占用的栅格幅值为正逻辑
  22. grid(idx) = true;
  23. gridmap.occGrid = grid;%栅格地图
  24. gridmap.metricMap = min(bwdist(grid),10);%bwdist(grid)表示grid0元素所在的位置靠近非零元素位置的最短距离构成的矩阵
  25. gridmap.pixelSize = pixelSize;%栅格单元边长对应的实际长度
  26. gridmap.topLeftCorner = minXY;%栅格地图的x最小值和y最小值构成的向量的全局坐标

(8)FastMatch.m

  1. %根据当前位姿的栅格地图 优化预测的下一位姿 使下一位姿的栅格地图与当前位姿的栅格地图达到最大的重合度
  2. %快速扫描匹配(请注意这可能会陷入局部最小值)
  3. function [pose, bestHits] = FastMatch(gridmap, scan, pose, searchResolution)
  4. %--------------------------------------------------------------------------
  5. %输入
  6. % gridmap为局部栅格地图
  7. % scan为构成gridmap的当前扫描点集的局部笛卡尔坐标
  8. % pose为预测的下一位姿(预测得到的pose_guess)
  9. % searchResolution为搜索的分辨率(为主函数中预设的扫描匹配参数 [0.05; 0.05; deg2rad(0.5)] )
  10. %输出
  11. % pose为优化过后的 预测下一位姿 优化目标函数是使下一位姿的栅格地图与当前位姿的栅格地图达到最大的重合度
  12. % bestHits pose对应的 最佳重合度score对应的 当前位姿栅格地图的原始距离矩阵
  13. %--------------------------------------------------------------------------
  14. %局部栅格地图信息
  15. % Grid map information
  16. metricMap = gridmap.metricMap;%栅格地图中0元素所在的位置靠近非零元素位置的最短栅格距离构成的矩阵
  17. ipixel = 1 / gridmap.pixelSize;%实际距离1m对应几个栅格单元边长 (栅格单元尺寸对应的实际距离的倒数)
  18. minX = gridmap.topLeftCorner(1);%栅格地图中的最左端的横坐标(全局)
  19. minY = gridmap.topLeftCorner(2);%栅格地图中的最下端的纵坐标(全局)
  20. nCols = size(metricMap, 2);
  21. nRows = size(metricMap, 1);
  22. %最大后验占用栅格算法(爬山算法) ?
  23. % Go down the hill
  24. maxIter = 50;%最大循环次数
  25. maxDepth = 3;%提高分辨率的次数的最大值
  26. iter = 0;%循环变量
  27. depth = 0;%分辨率提高次数
  28. pixelScan = scan * ipixel;%将 扫描数据 实际坐标 转化为 栅格地图中的栅格坐标
  29. bestPose = pose;
  30. bestScore = Inf;
  31. t = searchResolution(1);%xy坐标的搜索分辨率
  32. r = searchResolution(3);%theta的搜索分辨率
  33. while iter < maxIter
  34. noChange = true;
  35. % 旋转
  36. % Rotation
  37. for theta = pose(3) + [-r, 0, r]%遍历这个三个旋转角 [旋转角-r 旋转角 旋转角+r]
  38. ct = cos(theta);
  39. st = sin(theta);
  40. S = pixelScan * [ct, st; -st, ct];%把 扫描数据(单位:栅格) 逆时针旋转theta 得到S
  41. % 转换
  42. % Translation
  43. for tx = pose(1) + [-t, 0, t]%遍历这三个横坐标 [预测位姿横坐标-t 预测位姿横坐标 预测位姿横坐标+t]
  44. Sx = round(S(:,1)+(tx-minX)*ipixel) + 1;%以栅格为单位的横坐标 (下一位姿的预测 叠加 当前位姿的扫描数据)
  45. for ty = pose(2) + [-t, 0, t]
  46. Sy = round(S(:,2)+(ty-minY)*ipixel) + 1;
  47. isIn = Sx>1 & Sy>1 & Sx<nCols & Sy<nRows;%筛选出 下一位姿得到的扫描栅格 落在 当前扫描得到的栅格中 的坐标
  48. ix = Sx(isIn);%提取出下一位姿扫描栅格 落在当前栅格地图区域的部分 的横坐标(单位:栅格)
  49. iy = Sy(isIn);%提取出下一位姿扫描栅格 落在当前栅格地图区域的部分 的纵坐标(单位:栅格)
  50. % metric socre
  51. idx = iy + (ix-1)*nRows;%把下一位姿扫描栅格的二维坐标转换为一维坐标idx
  52. %metricMap为当前位姿栅格地图中 非占用点距离占用点的距离矩阵
  53. %score理解为 下一位姿扫描栅格与当前位姿扫描栅格的重合度(score约小 表示重合度越高)
  54. hits = metricMap(idx);
  55. score = sum(hits);
  56. % update
  57. if score < bestScore %目的是找到最低的score(即预测栅格与当前栅格达到最高重合度)
  58. noChange = false;
  59. bestPose = [tx; ty; theta];%将这个最高重合度的 预测位姿 作为最佳预测位姿
  60. bestScore = score;
  61. bestHits = hits;
  62. end
  63. end
  64. end
  65. end
  66. % 找不到更好的匹配,提高分辨率
  67. if noChange
  68. r = r / 2;
  69. t = t / 2;
  70. depth = depth + 1;
  71. if depth > maxDepth %分辨率提高次数不能超过maxDepth
  72. break;
  73. end
  74. end
  75. pose = bestPose;%最佳位姿作为预测的下一位姿
  76. iter = iter + 1;
  77. end

(9)AddAKeyScan.m

  1. %将预测下一位姿的地图添加到全局地图中
  2. %或者如果判断下一位姿出现了错误,回到的距其最近的正确位姿,重新往后进行
  3. function [map, pose] = AddAKeyScan(map,...
  4. gridMap,...
  5. scan,...
  6. pose,...
  7. hits,...
  8. pixelSize,...
  9. bruteResolution,...
  10. tmax,...
  11. rmax)
  12. %--------------------------------------------------------------------------
  13. %输入
  14. % map为全局地图
  15. % gridMap
  16. % scan为当前扫描数据的局部笛卡尔坐标
  17. % pose为优化后的预测的下一位姿
  18. % hits为占用栅格地图(一维形式)
  19. % pixelSize
  20. % bruteResolution
  21. % tmax
  22. % rmax
  23. %输出
  24. % map为在当前全局地图基础上 添加了下一位姿测量数据的地图
  25. % pose 如果预测的下一步位姿出现错误 返回到的距其最近的正确位姿 再重新往后进行
  26. %--------------------------------------------------------------------------
  27. % 首先,评估posehits,确保没有大的错误
  28. % 如果出现大的错误,则返回无错误最近的一步的位姿
  29. lastKeyPose = map.keyscans(end).pose;
  30. dp = DiffPose(lastKeyPose, pose);%若下一位姿与当前位姿出现了较大的差别则判断下一位姿有错
  31. if abs(dp(1)) > 0.5 || abs(dp(2)) > 0.5 || abs(dp(3)) > pi
  32. disp('Oh no no no nobody but you : So Large Error!');
  33. pose = lastKeyPose;
  34. end
  35. % 细化相对位姿,估计位姿协方差. 并将它们放入map.connections,当我们关闭一个循环时(姿势图优化)将需要它。
  36. scan_w = Transform(scan, pose);%将当前扫描数据利用下一位姿转化为全局坐标(理解为估计的下一位姿的扫描数据)
  37. newPoints = scan_w(hits>1.1, :);%把预测的下一位姿的扫描数据中,和当前栅格地图的距离大于1.1的数据 筛选出来
  38. %
  39. if isempty(newPoints)%意思是 预测的下一位姿的扫描数据 完全落在当前位姿构成的栅格地图中
  40. return;
  41. end
  42. % keyscans
  43. k = length(map.keyscans);
  44. map.keyscans(k+1).pose = pose;
  45. map.keyscans(k+1).iBegin = size(map.points, 1) + 1;
  46. map.keyscans(k+1).iEnd = size(map.points, 1) + size(newPoints, 1);
  47. map.keyscans(k+1).loopTried = false;
  48. map.keyscans(k+1).loopClosed = false;
  49. %把下一位姿的扫描数据添加到全局地图中
  50. map.points = [map.points; newPoints];
  51. % connections
  52. % 估计相对姿势和协方差,当我们关闭循环时它们将是有用的(姿势图优化)
  53. c = length(map.connections);
  54. map.connections(c+1).keyIdPair = [k, k+1];
  55. map.connections(c+1).relativePose = zeros(3,1);
  56. map.connections(c+1).covariance = zeros(3);

(10)PlotMap.m

  1. %绘图(点集地图、路径、当前位姿、当前LiDAR扫描线)
  2. function PlotMap(cfig, map, path, scan, scanIdx)
  3. %--------------------------------------------------------------------------
  4. %输入
  5. % cfigplot绘制位置(将所有时刻的图叠加在一张图上)
  6. % map为全局地图
  7. % path为路径
  8. % scan为当前位置的局部笛卡尔坐标
  9. % scanIdx为当前扫描索引
  10. %--------------------------------------------------------------------------
  11. world = map.points;%全局地图点集赋给world
  12. scan = Transform(scan, path(:,end));%将当前位置的局部笛卡尔坐标 利用路径 转化为全局笛卡尔坐标
  13. worldColor = [0 0 0];%地图的颜色(黑色)
  14. scanColor = [148/255 0 211/255];%当前位置颜色(深紫色)
  15. pathColor = [0 0 1];%路径颜色(蓝色)
  16. lidarColor=[205/255 38/255 38/255];%LiDAR扫描线颜色(砖红色)
  17. % Plot
  18. cfig(1); clf;
  19. set(0,'defaultfigurecolor','w')
  20. set(gca,'box','on')
  21. set(gca, 'color', [1,1,1]);%设置背景颜色(白色)
  22. hold on; axis equal;
  23. plot(world(:,1), world(:,2), '+', 'MarkerSize', 1, 'color', worldColor);%画全局地图点集
  24. plot(scan(:,1), scan(:,2), '.', 'MarkerSize', 2, 'color', scanColor);%画当前的扫描点图
  25. plot(path(1,:), path(2,:), '-.', 'LineWidth', 1, 'color', pathColor);%画路径
  26. for i = 1:20:length(scan)
  27. line([path(1,end), scan(i,1)], [path(2,end), scan(i,2)], 'color', lidarColor);%画出当前位置的LiDAR扫描线
  28. end
  29. title(['Scan: ', num2str(scanIdx)]);%标题
  30. drawnow

(11)DiffPose.m

  1. %求位姿差
  2. function dp = DiffPose(pose1, pose2)
  3. dp = pose2 - pose1;
  4. %dp(3) = angdiff(pose1(3), pose2(3));
  5. dp(3) = pose2(3)-pose2(3);
  6. end

我用MATLAB撸了一个2D LiDAR SLAM的更多相关文章

  1. 【转载】matlab如何判断一个点是否在多面体内

    转载自:http://www.52souji.net/point-within-a-polyhedron/ 我遇到的一个实际问题是:要在空位区域随机放置一定数量的原子,这些原子在空位区域任何一处存在的 ...

  2. ROS与Matlab系列:一个简单的运动控制

    ROS与Matlab系列:一个简单的运动控制 转自:http://blog.exbot.net/archives/2594 Matlab拥有强大的数据处理.可视化绘图能力以及众多成熟的算法函数,非常适 ...

  3. 【跟我一起学Unity3D】做一个2D的90坦克大战之AI系统

    对于AI,我的初始想法非常easy,首先他要能动,而且是在地图里面动. 懂得撞墙后转弯,然后懂得射击,其它的没有了,基于这个想法,我首先创建了一个MyTank类,用于管理玩家的坦克的活动,然后创建AI ...

  4. 撸了一个微信小程序项目

    学会一项开发技能最快的步骤就是:准备,开火,瞄准.最慢的就是:准备,瞄准,瞄准,瞄准-- 因为微信小程序比较简单,直接开撸就行,千万别瞄准. 于是乎,趁着今天上午空气质量不错,撸了一个小程序,放在了男 ...

  5. Unity引擎入门——制作第一个2D游戏(1)

    Unity作为当今最流行的游戏引擎之一,受到各大厂商的喜爱. 像是炉石传说,以及最近的逃离塔克夫,都是由unity引擎开发制作. 作为初学者的我们,虽然无法直接做出完成度那么高的作品,但每一个伟大的目 ...

  6. 撸了一个 Feign 增强包 V2.0 升级版

    前言 大概在两年前我写过一篇 撸了一个 Feign 增强包,当时准备是利用 SpringBoot + K8s 构建应用,这个库可以类似于 SpringCloud 那样结合 SpringBoot 使用声 ...

  7. 3D惯导Lidar SLAM

    3D惯导Lidar SLAM LIPS: LiDAR-Inertial 3D Plane SLAM 摘要 本文提出了最*点*面表示的形式化方法,并分析了其在三维室内同步定位与映射中的应用.提出了一个利 ...

  8. [转载]高效使用matlab之四:一个加速matlab程序的例子

    原文地址:http://www.bfcat.com/index.php/2012/11/speed-up-app/ 这篇文章原文是matlab网站上的,我把它翻译过来同时自己也学习一下.原文见这里 这 ...

  9. 如何在HoloLens中创建一个2D的Hello World程序

    注:本文提及到的代码示例下载地址 > How to build an "Hello World" 2D app in HololLens. HoloLens 是微软的一款MR ...

随机推荐

  1. 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)

    一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...

  2. Library弱依赖打包

    为减少强依赖,运行时动态监测依赖是否存在. 例如:内置的 HTTP client 可以是 OkHttpClient 或者是 HttpURLConnection.前者拥有更高的性能,但需要引入 OkHt ...

  3. My google script which based on Google Sheet and Form

    My google script which based on Google Sheet and Form // get sheet data function getSpreadsheetData( ...

  4. ASP.NET Core MVC+EF Core从开发到部署

    笔记本电脑装了双系统(Windows 10和Ubuntu16.04)快半年了,平时有时间就喜欢切换到Ubuntu系统下耍耍Linux,熟悉熟悉Linux命令.Shell脚本以及Linux下的各种应用的 ...

  5. 数据共享Manager

    将数据设置成共享数据,一个进程修改了数据,另外一个进程就能就接受的被修改的数据. 起50个进程让他们都去操作一个数据: from multiprocessing import Process, Man ...

  6. hdu 4486 Pen Counts

    Pen Counts Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  7. react 使用 lazyload 懒加载图片

    文档地址 index.html <script> (function(w, d) { var b = d.getElementsByTagName("body")[0] ...

  8. IOS系统下虚拟键盘遮挡文本框问题的解决

    最近在项目中发现同样的代码在Android端微信网页中点击文本框唤出的虚拟键盘不会遮挡文本框,但是在IOS端的微信网页中点击文本框唤出的键盘却在大部分情况下会遮挡文本框 经过高人指点,这个问题终于解决 ...

  9. OOD之问题空间到解空间—附FP的建模

    通常会被问到,什么事OOD,然后大部分人期待的答案比较死板,继承.封装.多态!懂这个的人多的去了,有什么好问?回答出来的人是否拿着Java又去做一些面向过程的勾当? 计算机革命起源于机器,因此编程语言 ...

  10. 主席树 || 可持久化线段树 || LCA || BZOJ 2588: Spoj 10628. Count on a tree || Luogu P2633 Count on a tree

    题面: Count on a tree 题解: 主席树维护每个节点到根节点的权值出现次数,大体和主席树典型做法差不多,对于询问(X,Y),答案要计算ans(X)+ans(Y)-ans(LCA(X,Y) ...