讲解该函数void saveKeyFramesAndFactor()
// 如果是第一帧关键帧
if (cloudKeyPoses3D->points.empty())
{
// 置信度就设置差一点,尤其是不可观的平移和yaw角
noiseModel::Diagonal::shared_ptr priorNoise = noiseModel::Diagonal::Variances((Vector(6) << 1e-2, 1e-2, M_PI*M_PI, 1e8, 1e8, 1e8).finished()); // rad*rad, meter*meter
// 增加先验约束,对第0个节点增加约束
gtSAMgraph.add(PriorFactor<Pose3>(0, trans2gtsamPose(transformTobeMapped), priorNoise));
// 加入节点信息
initialEstimate.insert(0, trans2gtsamPose(transformTobeMapped));
else{
// 如果不是第一帧,就增加帧间约束
// 这时帧间约束置信度就设置高一些
noiseModel::Diagonal::shared_ptr odometryNoise = noiseModel::Diagonal::Variances((Vector(6) << 1e-6, 1e-6, 1e-6, 1e-4, 1e-4, 1e-4).finished());
// 转成gtsam的格式
gtsam::Pose3 poseFrom = pclPointTogtsamPose3(cloudKeyPoses6D->points.back());
gtsam::Pose3 poseTo = trans2gtsamPose(transformTobeMapped);
// 这是一个帧间约束,分别输入两个节点的id,帧间约束大小以及置信度
gtSAMgraph.add(BetweenFactor<Pose3>(cloudKeyPoses3D->size()-1, cloudKeyPoses3D->size(), poseFrom.between(poseTo), odometryNoise));
// 加入节点信息
initialEstimate.insert(cloudKeyPoses3D->size(), poseTo);
}
GPS的使用比较谨慎,所以有很多判断退出的条件
void addGPSFactor()
{
// 如果没有gps信息就算了
if (gpsQueue.empty())
return;
// wait for system initialized and settles down
// 如果有gps但是没有关键帧信息也算了
if (cloudKeyPoses3D->points.empty())
return;
else
{
// 第一个关键帧和最后一个关键帧相差很近也算了,要么刚起步,要么会触发回环
if (pointDistance(cloudKeyPoses3D->front(), cloudKeyPoses3D->back()) < 5.0)
return;
}
// pose covariance small, no need to correct
// gtsam反馈的当前x,y的置信度,如果置信度比较高也不需要gps来打扰
if (poseCovariance(3,3) < poseCovThreshold && poseCovariance(4,4) < poseCovThreshold)
return;
// last gps position
static PointType lastGPSPoint;
while (!gpsQueue.empty())
{
// 把距离当前帧比较早的帧都抛弃
if (gpsQueue.front().header.stamp.toSec() < timeLaserInfoCur - 0.2)
{
// message too old
gpsQueue.pop_front();
}
// 比较晚就索性再等等lidar计算
else if (gpsQueue.front().header.stamp.toSec() > timeLaserInfoCur + 0.2)
{
// message too new
break;
}
else
{
// 说明这个gps时间上距离当前帧已经比较近了,那就把这个数据取出来
nav_msgs::Odometry thisGPS = gpsQueue.front();
gpsQueue.pop_front();
// GPS too noisy, skip
float noise_x = thisGPS.pose.covariance[0];
float noise_y = thisGPS.pose.covariance[7];
float noise_z = thisGPS.pose.covariance[14];
// 如果gps的置信度不高,也没有必要使用了
if (noise_x > gpsCovThreshold || noise_y > gpsCovThreshold)
continue;
// 取出gps的位置
float gps_x = thisGPS.pose.pose.position.x;
float gps_y = thisGPS.pose.pose.position.y;
float gps_z = thisGPS.pose.pose.position.z;
// 通常gps的z没有x y准,因此这里可以不使用z值
if (!useGpsElevation)
{
gps_z = transformTobeMapped[5];
noise_z = 0.01;
}
// GPS not properly initialized (0,0,0)
// gps的x或者y太小说明还没有初始化好
if (abs(gps_x) < 1e-6 && abs(gps_y) < 1e-6)
continue;
// Add GPS every a few meters
PointType curGPSPoint;
curGPSPoint.x = gps_x;
curGPSPoint.y = gps_y;
curGPSPoint.z = gps_z;
// 加入gps观测不宜太频繁,相邻不超过5m
if (pointDistance(curGPSPoint, lastGPSPoint) < 5.0)
continue;
else
lastGPSPoint = curGPSPoint;
gtsam::Vector Vector3(3);
// gps的置信度,标准差设置成最小1m,也就是不会特别信任gps信号
Vector3 << max(noise_x, 1.0f), max(noise_y, 1.0f), max(noise_z, 1.0f);
noiseModel::Diagonal::shared_ptr gps_noise = noiseModel::Diagonal::Variances(Vector3);
// 调用gtsam中集成的gps的约束
gtsam::GPSFactor gps_factor(cloudKeyPoses3D->size(), gtsam::Point3(gps_x, gps_y, gps_z), gps_noise);
gtSAMgraph.add(gps_factor);
// 加入gps之后等同于回环,需要触发较多的isam update
aLoopIsClosed = true;
break;
}
}
}
回环信息队列里面的数据由回环检测线程塞进来,具体处理见LIO-SAM后端中的回环检测及位姿计算
void addLoopFactor()
{
// 有一个专门的回环检测线程会检测回环,检测到就会给这个队列塞入回环结果
if (loopIndexQueue.empty())
return;
// 把队列里所有的回环约束都添加进来
for (int i = 0; i < (int)loopIndexQueue.size(); ++i)
{
int indexFrom = loopIndexQueue[i].first;
int indexTo = loopIndexQueue[i].second;
// 这是一个帧间约束
gtsam::Pose3 poseBetween = loopPoseQueue[i];
// 回环的置信度就是icp的得分
gtsam::noiseModel::Diagonal::shared_ptr noiseBetween = loopNoiseQueue[i];
// 加入约束
gtSAMgraph.add(BetweenFactor<Pose3>(indexFrom, indexTo, poseBetween, noiseBetween));
}
// 清空回环相关队列
loopIndexQueue.clear();
loopPoseQueue.clear();
loopNoiseQueue.clear();
// 标志位置true
aLoopIsClosed = true;
}
// 所有因子加完了,就调用isam接口更新图模型
// update iSAM
isam->update(gtSAMgraph, initialEstimate);
isam->update();
// 如果加入了gps的约束或者回环约束,isam需要进行更多次的优化
if (aLoopIsClosed == true)
{
isam->update();
isam->update();
isam->update();
isam->update();
isam->update();
}
// 将约束和节点信息清空,他们已经被加入到isam中去了,因此这里清空不会影响整个优化
gtSAMgraph.resize(0);
initialEstimate.clear();
// 下面保存关键帧信息
//save key poses
PointType thisPose3D;
PointTypePose thisPose6D;
Pose3 latestEstimate;
isamCurrentEstimate = isam->calculateEstimate();
// 取出优化后的最新关键帧位姿
latestEstimate = isamCurrentEstimate.at<Pose3>(isamCurrentEstimate.size()-1);
// cout << "****************************************************" << endl;
// isamCurrentEstimate.print("Current estimate: ");
// 平移信息取出来保存进cloudKeyPoses3D这个结构中,其中索引作为intensity值
thisPose3D.x = latestEstimate.translation().x();
thisPose3D.y = latestEstimate.translation().y();
thisPose3D.z = latestEstimate.translation().z();
thisPose3D.intensity = cloudKeyPoses3D->size(); // this can be used as index
cloudKeyPoses3D->push_back(thisPose3D);
// 6D姿态同样保留下来
thisPose6D.x = thisPose3D.x;
thisPose6D.y = thisPose3D.y;
thisPose6D.z = thisPose3D.z;
thisPose6D.intensity = thisPose3D.intensity ; // this can be used as index
thisPose6D.roll = latestEstimate.rotation().roll();
thisPose6D.pitch = latestEstimate.rotation().pitch();
thisPose6D.yaw = latestEstimate.rotation().yaw();
thisPose6D.time = timeLaserInfoCur;
cloudKeyPoses6D->push_back(thisPose6D);
// 保存当前位姿的置信度
poseCovariance = isam->marginalCovariance(isamCurrentEstimate.size()-1);
// save updated transform
// 将优化后的位姿更新到transformTobeMapped数组中,作为当前最佳估计值
transformTobeMapped[0] = latestEstimate.rotation().roll();
transformTobeMapped[1] = latestEstimate.rotation().pitch();
transformTobeMapped[2] = latestEstimate.rotation().yaw();
transformTobeMapped[3] = latestEstimate.translation().x();
transformTobeMapped[4] = latestEstimate.translation().y();
transformTobeMapped[5] = latestEstimate.translation().z();
// save all the received edge and surf points
pcl::PointCloud<PointType>::Ptr thisCornerKeyFrame(new pcl::PointCloud<PointType>());
pcl::PointCloud<PointType>::Ptr thisSurfKeyFrame(new pcl::PointCloud<PointType>());
// 当前帧的点云的角点和面点分别拷贝一下
pcl::copyPointCloud(*laserCloudCornerLastDS, *thisCornerKeyFrame);
pcl::copyPointCloud(*laserCloudSurfLastDS, *thisSurfKeyFrame);
// save key frame cloud
// 关键帧的点云保存下来
cornerCloudKeyFrames.push_back(thisCornerKeyFrame);
surfCloudKeyFrames.push_back(thisSurfKeyFrame);
// save path for visualization
// 根据当前最新位姿更新rviz可视化
updatePath(thisPose6D);