Karto slam源码阅读(三) 基于相关方法的scan-to-map匹配

[复制链接]
mahao 发表于 2023-9-3 12:18:46|来自:北京林业大学 | 显示全部楼层 |阅读模式


Karto slam源码阅读(三) 基于相关方法的scan-to-map匹配

scan-to-map匹配是激光雷达中一个基础的概念,scan是当前帧,map是当前帧时空维度上临近的帧组成的集合。map已经经过不断地优化,有一定精度了,而当前帧只有一个初始的估计位姿。至于怎么来的,可以是用前一帧位姿+运动计算,也可以是有外部传感器IMU、轮式里程计等得到的,总之它是不准确的。那么我们可以将当前帧的激光点,随意变换一下,再与map的激光点进行匹配。如果匹配的特别好,我们认为这个随意变换的pose就是我们需要的校正位姿。当然,随意变换是有个限制的,不能变换的特别特别大吧,所以定义了一个变化范围,称之为搜索空间。包括平移(x、y)、旋转(theta)维度上的搜索。
如何判断scan与map匹配的好不好呢?很简单,scan的激光点变换之后的位置,有map点接盘,不管是什么点,只要有个点就算匹配上了。统计一下匹配上的比例,用它来评判匹配好不好。为啥有效呢?可以想想。
另外要提一点,搜索空间中的角度问题。如果每平移一个(dx, dy)之后,再计算旋转dtheta角度对应的激光点位置,那计算DX*DY*DTheta次。能不能精简一下呢,可以的。我们知道旋转这个东西跟在哪没有关系,所以一开始就先计算好旋转一个dtheta角之后,激光点的偏移量,注意是偏移量。后面对于任何平移(dx, dy)之后的位置,加上这个偏移量就可以了。整个计算量就是DX*DY + DTheta次,少了很多。
scan-to-map匹配在很多地方都会用到。当前帧与局部map匹配,当前帧与闭环候选帧集合匹配,当前帧与广搜临近帧的chain匹配,都会调用这个方法。
相关函数包括:MatchScan、CorrelateScan、ComputeOffsets、GetResponse、
ComputePositionalCovariance、ComputeAngularCovariance。
主函数
  1. /**
  2. * 基于相关方法的scan-to-map匹配
  3. * 1、用前一帧优化前后的位姿变换,来校正当前帧优化前的位姿,得到一个初始位姿
  4. * 2、提取局部map中当前帧也可以看到的点,对应栅格设置为占用
  5. *    剔除相对于当前帧属于物体背面的点,也就是近邻帧与当前帧在物体两面
  6. * 3、scan-to-map匹配
  7. *    1) 创建旋转角度-激光点旋转后相对于当前帧位置的偏移量
  8. *       创建索引表,计算当前帧激光点,在各个旋转角度变换下,新的激光点位置相对于当前帧位置(机器人的位置)的坐标偏移量
  9. *       不管当前帧在什么位置,只要旋转角度一定,那么坐标偏移量也是确定的。
  10. *       目的是后面在搜索空间中对当前帧位姿施加不同的x平移、y平移之后再施加一个旋转的操作,不用重新计算每个搜索位置处旋转某个角度之后对应的点坐标,只需要加上对应旋转角度下算好的坐标偏移量即可。
  11. *    2) 遍历pose搜索空间计算响应值     
  12. *       a. 当前帧激光点集合在某个pose变换下得到新的位置,统计在局部map栅格中这些新位置处占用的数量,占用越多响应值越高,表示匹配越好
  13. *       b. 惩罚一下搜索偏移量,越远响应值折扣越多(当前帧搜索距离、角度偏移)
  14. *    3) 找到响应值最大的pose,如果有多个最佳响应pose,那么对pose求个均值,得到最终pose
  15. *    4) 计算位姿的协方差
  16. * 4、如果响应值是0,没匹配上,扩大角度搜索范围,增加20°、40°、60°,再执行一次scan-to-map匹配
  17. * 5、在scan-to-map优化之后的位姿基础上,再缩小搜索空间优化一次,搜索范围减半,角度区间减半,执行scan-to-map匹配
  18. * @param pScan         当前帧
  19. * @param rBaseScans    局部map(当前帧时空维度上相邻的帧集合)
  20. * @param rMean         输出优化后位姿
  21. * @param rCovariance   输出协方差矩阵,dx,dy,dtheta
  22. * @param doPenalize    是否对响应值做搜索距离上的惩罚
  23. * @param doRefineMatch 是否细化搜索空间,执行第二次优化
  24. * @return              返回响应值
  25. */
  26. kt_double ScanMatcher::MatchScan(LocalizedRangeScan* pScan, const LocalizedRangeScanVector& rBaseScans, Pose2& rMean,
  27.                                 Matrix3& rCovariance, kt_bool doPenalize, kt_bool doRefineMatch)
  28. {
  29. // 用前一帧优化前后的位姿变换,来校正当前帧优化前的位姿,得到一个初始位姿
  30. Pose2 scanPose = pScan->GetSensorPose();
  31. // 当前帧激光点数为零
  32. if (pScan->GetNumberOfRangeReadings() == 0)
  33. {
  34.     rMean = scanPose;
  35.     // 协方差越大,位姿的不确定度越大
  36.     rCovariance(0, 0) = MAX_VARIANCE;  // XX
  37.     rCovariance(1, 1) = MAX_VARIANCE;  // YY
  38.     rCovariance(2, 2) = 4 * math::Square(m_pMapper->m_pCoarseAngleResolution->GetValue());  // TH*TH
  39.     return 0.0;
  40. }
  41. // 2. get size of grid
  42. Rectangle2<kt_int32s> roi = m_pCorrelationGrid->GetROI();
  43. // 3. compute offset (in meters - lower left corner)
  44. Vector2<kt_double> offset;
  45. offset.SetX(scanPose.GetX() - (0.5 * (roi.GetWidth() - 1) * m_pCorrelationGrid->GetResolution()));
  46. offset.SetY(scanPose.GetY() - (0.5 * (roi.GETHeight() - 1) * m_pCorrelationGrid->GetResolution()));
  47. // 4. set offset
  48. m_pCorrelationGrid->GetCoordinateConverter()->SetOffset(offset);
  49. // 提取局部map中当前帧也可以看到的点,对应栅格设置为占用
  50. AddScans(rBaseScans, scanPose.GetPosition());
  51. // 搜索矩形框
  52. Vector2<kt_double> searchDimensions(m_pSearchSpaceProbs->GetWidth(), m_pSearchSpaceProbs->GetHeight());
  53. Vector2<kt_double> coarseSearchOffset(0.5 * (searchDimensions.GetX() - 1) * m_pCorrelationGrid->GetResolution(),
  54.                                         0.5 * (searchDimensions.GetY() - 1) * m_pCorrelationGrid->GetResolution());
  55. // 搜索步长,2个栅格
  56. Vector2<kt_double> coarseSearchResolution(2 * m_pCorrelationGrid->GetResolution(),
  57.                                             2 * m_pCorrelationGrid->GetResolution());
  58. // 基于相关方法的scan-to-map匹配
  59. // 1、创建旋转角度-激光点旋转后相对于当前帧位置的偏移量
  60. //    创建索引表,计算当前帧激光点,在各个旋转角度变换下,新的激光点位置相对于当前帧位置(机器人的位置)的坐标偏移量
  61. //    不管当前帧在什么位置,只要旋转角度一定,那么坐标偏移量也是确定的。
  62. //    目的是后面在搜索空间中对当前帧位姿施加不同的x平移、y平移之后再施加一个旋转的操作,不用重新计算每个搜索位置处旋转某个角度之后对应的点坐标,只需要加上对应旋转角度下算好的坐标偏移量即可。
  63. // 2、遍历pose搜索空间计算响应值
  64. //    1) 当前帧激光点集合在某个pose变换下得到新的位置,统计在局部map栅格中这些新位置处占用的数量,占用越多响应值越高,表示匹配越好
  65. //    2) 惩罚一下搜索偏移量,越远响应值折扣越多(当前帧搜索距离、角度偏移)
  66. // 3、找到响应值最大的pose,如果有多个最佳响应pose,那么对pose求个均值,得到最终pose
  67. // 4、计算位姿的协方差
  68. kt_double bestResponse = CorrelateScan(pScan, scanPose, coarseSearchOffset, coarseSearchResolution,
  69.                                         m_pMapper->m_pCoarseSearchAngleOffset->GetValue(),
  70.                                         m_pMapper->m_pCoarseAngleResolution->GetValue(),
  71.                                         doPenalize, rMean, rCovariance, false);
  72. if (m_pMapper->m_pUseResponseExpansion->GetValue() == true)
  73. {
  74.     // 如果响应值是0,没匹配上,扩大角度搜索范围
  75.     if (math::DoubleEqual(bestResponse, 0.0))
  76.     {
  77.     // 扩大搜索角度范围,增加20°、40°、60°
  78.     kt_double newSearchAngleOffset = m_pMapper->m_pCoarseSearchAngleOffset->GetValue();
  79.     for (kt_int32u i = 0; i < 3; i++)
  80.     {
  81.         newSearchAngleOffset += math::DegreesToRadians(20);
  82.         // 再执行一次scan-to-map匹配
  83.         bestResponse = CorrelateScan(pScan, scanPose, coarseSearchOffset, coarseSearchResolution,
  84.                                     newSearchAngleOffset, m_pMapper->m_pCoarseAngleResolution->GetValue(),
  85.                                     doPenalize, rMean, rCovariance, false);
  86.         // 响应值还是0,退出
  87.         if (math::DoubleEqual(bestResponse, 0.0) == false)
  88.         {
  89.         break;
  90.         }
  91.     }
  92.     }
  93. }
  94. // 在scan-to-map优化之后的位姿基础上,再缩小搜索空间优化一次
  95. if (doRefineMatch)
  96. {
  97.     // 搜索范围减半,角度区间减半
  98.     Vector2<kt_double> fineSearchOffset(coarseSearchResolution * 0.5);
  99.     Vector2<kt_double> fineSearchResolution(m_pCorrelationGrid->GetResolution(), m_pCorrelationGrid->GetResolution());
  100.     // 执行一次scan-to-map匹配
  101.     bestResponse = CorrelateScan(pScan, rMean, fineSearchOffset, fineSearchResolution,
  102.                                 0.5 * m_pMapper->m_pCoarseAngleResolution->GetValue(),
  103.                                 m_pMapper->m_pFineSearchAngleOffset->GetValue(),
  104.                                 doPenalize, rMean, rCovariance, true);
  105. }
  106. assert(math::InRange(rMean.GetHeading(), -KT_PI, KT_PI));
  107. return bestResponse;
  108. }
复制代码
scan-to-map匹配(ScanMatcher::CorrelateScan)
  1. /**
  2.    * 基于相关方法的scan-to-map匹配
  3.    * 1、创建旋转角度-激光点旋转后相对于当前帧位置的偏移量
  4.    *    创建索引表,计算当前帧激光点,在各个旋转角度变换下,新的激光点位置相对于当前帧位置(机器人的位置)的坐标偏移量
  5.    *    不管当前帧在什么位置,只要旋转角度一定,那么坐标偏移量也是确定的。
  6.    *    目的是后面在搜索空间中对当前帧位姿施加不同的x平移、y平移之后再施加一个旋转的操作,不用重新计算每个搜索位置处旋转某个角度之后对应的点坐标,只需要加上对应旋转角度下算好的坐标偏移量即可。
  7.    * 2、遍历pose搜索空间计算响应值
  8.    *    1) 当前帧激光点集合在某个pose变换下得到新的位置,统计在局部map栅格中这些新位置处占用的数量,占用越多响应值越高,表示匹配越好
  9.    *    2) 惩罚一下搜索偏移量,越远响应值折扣越多(当前帧搜索距离、角度偏移)
  10.    * 3、找到响应值最大的pose,如果有多个最佳响应pose,那么对pose求个均值,得到最终pose
  11.    * 4、计算位姿的协方差
  12.    * @param pScan                   当前帧
  13.    * @param rSearchCenter           当前帧位姿(搜索空间的中心)
  14.    * @param rSearchSpaceOffset      搜索范围,矩形半径
  15.    * @param rSearchSpaceResolution  搜索步长
  16.    * @param searchAngleOffset       搜索角度范围
  17.    * @param searchAngleResolution   角度步长
  18.    * @param doPenalize              惩罚搜索距离较远的位姿,降低响应值
  19.    * @param rMean                   输出匹配结果位姿
  20.    * @param rCovariance             输出匹配结果方差
  21.    * @param doingFineMatch          是否执行二次优化
  22.    * @return                        返回响应值
  23.    */
  24.   kt_double ScanMatcher::CorrelateScan(LocalizedRangeScan* pScan, const Pose2& rSearchCenter,
  25.                                        const Vector2<kt_double>& rSearchSpaceOffset,
  26.                                        const Vector2<kt_double>& rSearchSpaceResolution,
  27.                                        kt_double searchAngleOffset, kt_double searchAngleResolution,
  28.                                        kt_bool doPenalize, Pose2& rMean, Matrix3& rCovariance, kt_bool doingFineMatch)
  29.   {
  30.     assert(searchAngleResolution != 0.0);
  31.     // 创建索引表,计算当前帧激光点,在各个旋转角度变换下,新的激光点位置相对于当前帧位置(机器人的位置)的坐标偏移量
  32.     // 不管当前帧在什么位置,只要旋转角度一定,那么坐标偏移量也是确定的。
  33.     // 目的是后面在搜索空间中对当前帧位姿施加不同的x平移、y平移之后再施加一个旋转的操作,不用重新计算每个搜索位置处旋转某个角度之后对应的点坐标,只需要加上对应旋转角度下算好的坐标偏移量即可。
  34.     m_pGridLookup->ComputeOffsets(pScan, rSearchCenter.GetHeading(), searchAngleOffset, searchAngleResolution);
  35.     // only initialize probability grid if computing positional covariance (during coarse match)
  36.     if (!doingFineMatch)
  37.     {
  38.       m_pSearchSpaceProbs->Clear();
  39.       Vector2<kt_double> offset(rSearchCenter.GetPosition() - rSearchSpaceOffset);
  40.       m_pSearchSpaceProbs->GetCoordinateConverter()->SetOffset(offset);
  41.     }
  42.     std::vector<kt_double> xPoses;
  43.     // x轴搜索步数
  44.     kt_int32u nX = static_cast<kt_int32u>(math::Round(rSearchSpaceOffset.GetX() *
  45.                                           2.0 / rSearchSpaceResolution.GetX()) + 1);
  46.     // x轴搜索起始偏移
  47.     kt_double startX = -rSearchSpaceOffset.GetX();
  48.     // x轴搜索偏移集合
  49.     for (kt_int32u xIndex = 0; xIndex < nX; xIndex++)
  50.     {
  51.       xPoses.push_back(startX + xIndex * rSearchSpaceResolution.GetX());
  52.     }
  53.     assert(math::DoubleEqual(xPoses.back(), -startX));
  54.     std::vector<kt_double> yPoses;
  55.     // y轴搜索步数
  56.     kt_int32u nY = static_cast<kt_int32u>(math::Round(rSearchSpaceOffset.GetY() *
  57.                                           2.0 / rSearchSpaceResolution.GetY()) + 1);
  58.     // y轴搜索起始偏移
  59.     kt_double startY = -rSearchSpaceOffset.GetY();
  60.     // y轴搜索偏移集合
  61.     for (kt_int32u yIndex = 0; yIndex < nY; yIndex++)
  62.     {
  63.       yPoses.push_back(startY + yIndex * rSearchSpaceResolution.GetY());
  64.     }
  65.     assert(math::DoubleEqual(yPoses.back(), -startY));
  66.     // calculate pose response array size
  67.     // 搜索角度数量
  68.     kt_int32u nAngles = static_cast<kt_int32u>(math::Round(searchAngleOffset * 2.0 / searchAngleResolution) + 1);
  69.     // 搜索空间数
  70.     kt_int32u poseResponseSize = static_cast<kt_int32u>(xPoses.size() * yPoses.size() * nAngles);
  71.     // allocate array
  72.     // 搜索空间
  73.     std::pair<kt_double, Pose2>* pPoseResponse = new std::pair<kt_double, Pose2>[poseResponseSize];
  74.     // 搜索起点
  75.     Vector2<kt_int32s> startGridPoint = m_pCorrelationGrid->WorldToGrid(Vector2<kt_double>(rSearchCenter.GetX()
  76.                                                                         + startX, rSearchCenter.GetY() + startY));
  77.     // 按照y方向、x方向、角度,遍历搜索空间
  78.     kt_int32u poseResponseCounter = 0;
  79.     forEachAs(std::vector<kt_double>, &yPoses, yIter)
  80.     {
  81.       // y偏移量
  82.       kt_double y = *yIter;
  83.       // 当前帧偏移y的位置
  84.       kt_double newPositionY = rSearchCenter.GetY() + y;
  85.       // 搜索距离平方
  86.       kt_double squareY = math::Square(y);
  87.       forEachAs(std::vector<kt_double>, &xPoses, xIter)
  88.       {
  89.         // x偏移量
  90.         kt_double x = *xIter;
  91.         // 当前帧偏移x的位置
  92.         kt_double newPositionX = rSearchCenter.GetX() + x;
  93.         // 搜索距离平方
  94.         kt_double squareX = math::Square(x);
  95.         // 当前帧偏移(x,y)之后所在的网格点坐标,网格索引
  96.         Vector2<kt_int32s> gridPoint = m_pCorrelationGrid->WorldToGrid(Vector2<kt_double>(newPositionX, newPositionY));
  97.         kt_int32s gridIndex = m_pCorrelationGrid->GridIndex(gridPoint);
  98.         assert(gridIndex >= 0);
  99.         kt_double angle = 0.0;
  100.         // 起始角度,heading是0°朝向,searchAngleOffset是一半搜索角度偏移量
  101.         kt_double startAngle = rSearchCenter.GetHeading() - searchAngleOffset;
  102.         for (kt_int32u angleIndex = 0; angleIndex < nAngles; angleIndex++)
  103.         {
  104.           // 角度
  105.           angle = startAngle + angleIndex * searchAngleResolution;
  106.           // 计算搜索pose的响应值
  107.           // 当前帧激光点集合在某个pose变换下得到新的位置,统计在局部map栅格中这些新位置处占用的数量,占用越多响应值越高,表示匹配越好
  108.           kt_double response = GetResponse(angleIndex, gridIndex);
  109.           // 惩罚一下搜索偏移量,越远响应值折扣越多(当前帧搜索距离、角度偏移)
  110.           if (doPenalize && (math::DoubleEqual(response, 0.0) == false))
  111.           {
  112.             // simple model (approximate Gaussian) to take odometry into account
  113.             // 距离平方
  114.             kt_double squaredDistance = squareX + squareY;
  115.             // 距离惩罚项
  116.             kt_double distancePenalty = 1.0 - (DISTANCE_PENALTY_GAIN *
  117.                                                squaredDistance / m_pMapper->m_pDistanceVariancePenalty->GetValue());
  118.             distancePenalty = math::Maximum(distancePenalty, m_pMapper->m_pMinimumDistancePenalty->GetValue());
  119.             // 角度惩罚项
  120.             kt_double squaredAngleDistance = math::Square(angle - rSearchCenter.GetHeading());
  121.             kt_double anglePenalty = 1.0 - (ANGLE_PENALTY_GAIN *
  122.                                             squaredAngleDistance / m_pMapper->m_pAngleVariancePenalty->GetValue());
  123.             anglePenalty = math::Maximum(anglePenalty, m_pMapper->m_pMinimumAnglePenalty->GetValue());
  124.             // 响应打个折扣,距离、角度偏移越大,响应越小
  125.             // 意思是理论上纠正的位姿与当前的位姿不会变化很大,如果特别大就不太靠谱
  126.             response *= (distancePenalty * anglePenalty);
  127.           }
  128.           // 存响应值、新pose
  129.           pPoseResponse[poseResponseCounter] = std::pair<kt_double, Pose2>(response, Pose2(newPositionX, newPositionY,
  130.                                                                            math::NormalizeAngle(angle)));
  131.           poseResponseCounter++;
  132.         }
  133.         assert(math::DoubleEqual(angle, rSearchCenter.GetHeading() + searchAngleOffset));
  134.       }
  135.     }
  136.     assert(poseResponseSize == poseResponseCounter);
  137.     // find value of best response (in [0; 1])
  138.     // 找到响应值最大的pose
  139.     kt_double bestResponse = -1;
  140.     for (kt_int32u i = 0; i < poseResponseSize; i++)
  141.     {
  142.       bestResponse = math::Maximum(bestResponse, pPoseResponse[i].first);
  143.       {
  144.         const Pose2& rPose = pPoseResponse[i].second;
  145.         Vector2<kt_int32s> grid = m_pSearchSpaceProbs->WorldToGrid(rPose.GetPosition());
  146.         // Changed (kt_double*) to the reinterpret_cast - Luc
  147.         kt_double* ptr = reinterpret_cast<kt_double*>(m_pSearchSpaceProbs->GetDataPointer(grid));
  148.         if (ptr == NULL)
  149.         {
  150.           throw std::runtime_error(&#34;Mapper FATAL ERROR - Index out of range in probability search!&#34;);
  151.         }
  152.         *ptr = math::Maximum(pPoseResponse[i].first, *ptr);
  153.       }
  154.     }
  155.     // 有多个最佳响应pose,那么对pose求个均值,得到最终pose
  156.     Vector2<kt_double> averagePosition;
  157.     kt_double thetaX = 0.0;
  158.     kt_double thetaY = 0.0;
  159.     kt_int32s averagePoseCount = 0;
  160.     for (kt_int32u i = 0; i < poseResponseSize; i++)
  161.     {
  162.       if (math::DoubleEqual(pPoseResponse[i].first, bestResponse))
  163.       {
  164.         averagePosition += pPoseResponse[i].second.GetPosition();
  165.         kt_double heading = pPoseResponse[i].second.GetHeading();
  166.         thetaX += cos(heading);
  167.         thetaY += sin(heading);
  168.         averagePoseCount++;
  169.       }
  170.     }
  171.     Pose2 averagePose;
  172.     if (averagePoseCount > 0)
  173.     {
  174.       averagePosition /= averagePoseCount;
  175.       thetaX /= averagePoseCount;
  176.       thetaY /= averagePoseCount;
  177.       averagePose = Pose2(averagePosition, atan2(thetaY, thetaX));
  178.     }
  179.     else
  180.     {
  181.       throw std::runtime_error(&#34;Mapper FATAL ERROR - Unable to find best position&#34;);
  182.     }
  183.     delete [] pPoseResponse;
  184.     if (!doingFineMatch)
  185.     {
  186.       // 计算新的位姿的协方差,位置
  187.       ComputePositionalCovariance(averagePose, bestResponse, rSearchCenter, rSearchSpaceOffset,
  188.                                   rSearchSpaceResolution, searchAngleResolution, rCovariance);
  189.     }
  190.     else
  191.     {
  192.       // 计算新的位姿的协方差,角度
  193.       ComputeAngularCovariance(averagePose, bestResponse, rSearchCenter,
  194.                               searchAngleOffset, searchAngleResolution, rCovariance);
  195.     }
  196.     // 最终pose
  197.     rMean = averagePose;
  198.     if (bestResponse > 1.0)
  199.     {
  200.       bestResponse = 1.0;
  201.     }
  202.     assert(math::InRange(bestResponse, 0.0, 1.0));
  203.     assert(math::InRange(rMean.GetHeading(), -KT_PI, KT_PI));
  204.     // 最佳响应
  205.     return bestResponse;
  206.   }
复制代码
生成查找表
查找表存在的意义就是相比于暴力匹配,不要每次都重新计算每个激光数据信息,相同角度不同位置的激光数据信息只需要被索引一次。



Karto slam源码阅读(三) 基于相关方法的scan-to-map匹配

计算响应(ScanMatcher::GetResponse)
  1. /**
  2. * 计算搜索pose的响应值
  3. * 当前帧激光点集合在某个pose变换下得到新的位置,统计在局部map栅格中这些新位置处占用的数量,占用越多响应值越高,表示匹配越好
  4. * @param angleIndex        旋转角度索引
  5. * @param gridPositionIndex 新的激光帧网格位置索引
  6. * @return                  响应值
  7. */
  8. kt_double ScanMatcher::GetResponse(kt_int32u angleIndex, kt_int32s gridPositionIndex) const
  9. {
  10. kt_double response = 0.0;
  11. // 局部map的栅格数据,存的是100占用,默认值0非占用,加上gridPositionIndex之后,pByte指向当前新的激光帧网格位置处
  12. kt_int8u* pByte = m_pCorrelationGrid->GetDataPointer() + gridPositionIndex;
  13. // 索引表内容为旋转角-旋转后激光点相对帧位置的坐标偏移量(转网格索引)之间的对应关系,取出当前旋转角下对应的偏移数据
  14. const LookupArray* pOffsets = m_pGridLookup->GetLookupArray(angleIndex);
  15. assert(pOffsets != NULL);
  16. // 激光点数量
  17. kt_int32u nPoints = pOffsets->GetSize();
  18. if (nPoints == 0)
  19. {
  20.     return response;
  21. }
  22. // calculate response
  23. kt_int32s* pAngleIndexPointer = pOffsets->GetArrayPointer();
  24. // 遍历激光点
  25. for (kt_int32u i = 0; i < nPoints; i++)
  26. {
  27.     // ignore points that fall off the grid
  28.     kt_int32s pointGridIndex = gridPositionIndex + pAngleIndexPointer[i];
  29.     if (!math::IsUpTo(pointGridIndex, m_pCorrelationGrid->GetDataSize()) || pAngleIndexPointer[i] == INVALID_SCAN)
  30.     {
  31.     continue;
  32.     }
  33.     // pByte指向局部map栅格中新(经过平移)的激光帧位置,再加上pAngleIndexPointer[i]是当前旋转之后新的激光点相对于激光帧位置的偏移量,
  34.     // 就等于新的激光点的位置,取它的栅格值。
  35.     // 也就是当前帧pose施加一个平移、旋转,激光点落在的栅格对应值相加,即为当前帧pose的响应值
  36.     // 100占用,0非占用。注:虽然定义了0未知、100占用、255free,但这三个是用于更新地图的,这里计算响应只用到100、0
  37.     response += pByte[pAngleIndexPointer[i]];
  38. }
  39. // 归一化[0,1],响应值越大越好,表示scan-to-map匹配越好
  40. // 仅通过占用的数量就能判定匹配的好不好,这个在激光里面是合理的,因为激光打到物体表面上,占用点就是薄薄的一层,甚至1个像素,
  41. // free点就特别多了,能匹配到占用点,说明很接近了。
  42. // 如果点云是平面,那么匹配可以很好的纠正旋转,平移就不好说了;如果是墙角这种形状,旋转和平移都能纠正。
  43. // 类似于VO里面的直接法,两帧图像直接用像素灰度值配准,当然它有很强的灰度不变假设。
  44. // 上面仅是个人理解。
  45. response /= (nPoints * GridStates_Occupied);
  46. assert(fabs(response) <= 1.0);
  47. return response;
  48. }
复制代码
全部回复0 显示全部楼层
暂无回复,精彩从你开始!

快速回帖

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则