一、对应源文件
add_executable(alaserOdometry src/laserOdometry.cpp)
target_link_libraries(alaserOdometry ${catkin_LIBRARIES} ${PCL_LIBRARIES} ${CERES_LIBRARIES})
二、原理
1. 点云符号表示
时间段内接收到的点云: :
投影到终止时刻 Lidar 坐标系:
投影到初始时刻 Lidar 坐标系:(用当前估计的 pose)
边缘点云:
平面点云:
2. 特征关联
2.1 线特征
输入:
点云集合 ,从中取一个点
匹配点:点云集合
步骤:
step1:从 中找到 的最近邻点
step2:从 的相邻扫描束中找 的最近邻点 (因为直线在一个扫描束上只可能有一个点云)
step3:检查 的平滑度
输出:
2.2 平面特征
输入:
点云集合 ,从中取一个点
匹配点:点云集合
步骤:
step1:从 中找到 的最近邻点
step2:从 的同一个扫描束中找 的最近邻点 , 从 的相邻扫描束中找 的最近邻点
step3:检查 的平滑度
输出:
2.3 图示
3. 误差项构造
3.1 线特征
点到直线距离
3.2 平面特征
点到平面距离
4. 运动补偿
4.1 问题引入
(1)在一个 sweep 期间,不同时刻 Lidar 坐标系不一样,需要线性插值,来估计不同时刻的位姿
(2)待求解 delta pose 是一个 sweep 周期 的 ,而每组观测来源于不同时刻,所以需要引入运动补偿,将观测统一变换至扫描起始时刻 Lidar 坐标系下
4.2 公式推导
时间周期:, delta pose:
这儿用旋转矢量对旋转部分进行建模
中间时刻 pose:
,
时刻点的坐标 变换至 sweep 起始时刻
5. 结合运动补偿的误差构造
Levenberg-Marquardt method
6. 算法流程
输入:
上一个周期的点云:
当前周期实时累加的点云:
上次迭代求解的 之间 delta pose:
输出:
最新计算的 delta pose :
返回:
If the algorithm reaches the end of a sweep, is reprojected to time stamp using the estimated motion during the sweep. Otherwise, only the transform is returned for the next round of recursion
三、ROS interface
1.1 subscribe topic
// sharp point clouds, 更新 cornerSharpBuf
ros::Subscriber subCornerPointsSharp = nh.subscribe<sensor_msgs::PointCloud2>("/laser_cloud_sharp", 100, laserCloudSharpHandler);
// less_sharp point clouds,更新 cornerLessSharpBuf
ros::Subscriber subCornerPointsLessSharp = nh.subscribe<sensor_msgs::PointCloud2>("/laser_cloud_less_sharp", 100, laserCloudLessSharpHandler);
// flat surface,更新 surfFlatBuf
ros::Subscriber subSurfPointsFlat = nh.subscribe<sensor_msgs::PointCloud2>("/laser_cloud_flat", 100, laserCloudFlatHandler);
// less flat surface,更新 surfLessFlatBuf
ros::Subscriber subSurfPointsLessFlat = nh.subscribe<sensor_msgs::PointCloud2>("/laser_cloud_less_flat", 100, laserCloudLessFlatHandler);
// all point cloud,更新 fullPointsBuf
ros::Subscriber subLaserCloudFullRes = nh.subscribe<sensor_msgs::PointCloud2>("/velodyne_cloud_2", 100, laserCloudFullResHandler);
1.2. publish topic
// last corner pts
ros::Publisher pubLaserCloudCornerLast = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_corner_last", 100);
// last flat pts
ros::Publisher pubLaserCloudSurfLast = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_surf_last", 100);
// all pts
ros::Publisher pubLaserCloudFullRes = nh.advertise<sensor_msgs::PointCloud2>("/velodyne_cloud_3", 100);
// odom
ros::Publisher pubLaserOdometry = nh.advertise<nav_msgs::Odometry>("/laser_odom_to_init", 100);
// path
ros::Publisher pubLaserPath = nh.advertise<nav_msgs::Path>("/laser_odom_path", 100);
1.3. 缓存队列
// sharp corners
cornerSharpBuf.push(cornerPointsSharp2);
// less sharp corners
cornerLessSharpBuf.push(cornerPointsLessSharp2);
// flat surface
surfFlatBuf.push(surfPointsFlat2);
// less flat surface
surfLessFlatBuf.push(surfPointsLessFlat2);
// all pointclouds
fullPointsBuf.push(laserCloudFullRes2);
四、算法实现
1. 点云预处理
// 算法输入:
pcl::PointCloud<PointType>::Ptr cornerPointsSharp(new pcl::PointCloud<PointType>());
pcl::PointCloud<PointType>::Ptr cornerPointsLessSharp(new pcl::PointCloud<PointType>());
pcl::PointCloud<PointType>::Ptr surfPointsFlat(new pcl::PointCloud<PointType>());
pcl::PointCloud<PointType>::Ptr surfPointsLessFlat(new pcl::PointCloud<PointType>());
pcl::PointCloud<PointType>::Ptr laserCloudFullRes(new pcl::PointCloud<PointType>());
// 1. 时间戳同步检查
......
// 2. 转换为 pcl 点云
......
2. factor 构造
src/lidarFactor.hpp
2.1 LidarEdgeFactor
todo
2.2 LidarPlaneFactor
todo
3. 两次优化迭代
3.1 构造 problem
ceres::LossFunction *loss_function = new ceres::HuberLoss(0.1);
ceres::LocalParameterization *q_parameterization = new
ceres::EigenQuaternionParameterization();
ceres::Problem::Options problem_options;
ceres::Problem problem(problem_options);
// 状态变量:
// double para_q[4] = {0, 0, 0, 1};
// double para_t[3] = {0, 0, 0};
problem.AddParameterBlock(para_q, 4, q_parameterization);
problem.AddParameterBlock(para_t, 3);
3.2 运动补偿
变换至起始帧下
TransformToStart(&(cornerPointsSharp->points[i]), &pointSel);
void TransformToStart(PointType const *const pi, PointType *const po)
{
// 获取相对时间
double s;
if (DISTORTION) {
s = (pi->intensity - int(pi->intensity)) / SCAN_PERIOD;
} else {
s = 1.0;
}
// R_start_i
Eigen::Quaterniond q_point_last = Eigen::Quaterniond::Identity().slerp(s, q_last_curr);
// t_start_i
Eigen::Vector3d t_point_last = s * t_last_curr;
Eigen::Vector3d point(pi->x, pi->y, pi->z);
Eigen::Vector3d un_point = q_point_last * point + t_point_last;
po->x = un_point.x();
po->y = un_point.y();
po->z = un_point.z();
po->intensity = pi->intensity;
}
关于如何从 intensity 中获取相对时间:
LOAM 系列(2): ascanRegistration node 分析_scofield_ly的博客-CSDN博客
3.3 线特征
(1). 输入点 ,运动补偿去畸变变换至起始帧下
TransformToStart(&(cornerPointsSharp->points[i]), &pointSel);
(2). kdtree 搜索找到最近点
kdtreeCornerLast->nearestKSearch(pointSel, 1, pointSearchInd, pointSearchSqDis);
closestPointInd = pointSearchInd[0];
// 并获取对应扫描线束号
int closestPointScanID = int(laserCloudCornerLast->points[closestPointInd].intensity);
(3). 查找 扫描线束相邻线束的最近邻点
(closestPointScanID, closestPointScanID + NEARBY_SCAN)
// search in the direction of increasing scan line
for (int j = closestPointInd + 1; j < (int)laserCloudCornerLast->points.size(); ++j) {
// if in the same scan line, continue
if (int(laserCloudCornerLast->points[j].intensity) <= closestPointScanID) {
continue;
}
// if not in nearby scans, end the loop
if (int(laserCloudCornerLast->points[j].intensity) > (closestPointScanID + NEARBY_SCAN)) {
break;
}
// 点云距离
double pointSqDis = (laserCloudCornerLast->points[j].x - pointSel.x) *
(laserCloudCornerLast->points[j].x - pointSel.x) +
(laserCloudCornerLast->points[j].y - pointSel.y) *
(laserCloudCornerLast->points[j].y - pointSel.y) +
(laserCloudCornerLast->points[j].z - pointSel.z) *
(laserCloudCornerLast->points[j].z - pointSel.z);
if (pointSqDis < minPointSqDis2) {
// find nearer point
minPointSqDis2 = pointSqDis;
minPointInd2 = j;
}
}
(closestPointScanID, closestPointScanID - NEARBY_SCAN)
// search in the direction of decreasing scan line
for (int j = closestPointInd - 1; j >= 0; --j) {
// if in the same scan line, continue
if (int(laserCloudCornerLast->points[j].intensity) >= closestPointScanID) {
continue;
}
// if not in nearby scans, end the loop
if (int(laserCloudCornerLast->points[j].intensity) < (closestPointScanID - NEARBY_SCAN)) {
break;
}
double pointSqDis = (laserCloudCornerLast->points[j].x - pointSel.x) *
(laserCloudCornerLast->points[j].x - pointSel.x) +
(laserCloudCornerLast->points[j].y - pointSel.y) *
(laserCloudCornerLast->points[j].y - pointSel.y) +
(laserCloudCornerLast->points[j].z - pointSel.z) *
(laserCloudCornerLast->points[j].z - pointSel.z);
if (pointSqDis < minPointSqDis2) {
// find nearer point
minPointSqDis2 = pointSqDis;
minPointInd2 = j;
}
}
(4). 添加残差
if (minPointInd2 >= 0) { // both closestPointInd and minPointInd2 is valid
Eigen::Vector3d curr_point(cornerPointsSharp->points[i].x,
cornerPointsSharp->points[i].y,
cornerPointsSharp->points[i].z);
Eigen::Vector3d last_point_a(laserCloudCornerLast->points[closestPointInd].x,
laserCloudCornerLast->points[closestPointInd].y,
laserCloudCornerLast->points[closestPointInd].z);
Eigen::Vector3d last_point_b(laserCloudCornerLast->points[minPointInd2].x,
laserCloudCornerLast->points[minPointInd2].y,
laserCloudCornerLast->points[minPointInd2].z);
double s;
if (DISTORTION) {
s = (cornerPointsSharp->points[i].intensity - int(cornerPointsSharp->points[i].intensity)) / SCAN_PERIOD;
} else {
s = 1.0;
}
ceres::CostFunction *cost_function = LidarEdgeFactor::Create(curr_point, last_point_a, last_point_b, s);
problem.AddResidualBlock(cost_function, loss_function, para_q, para_t);
corner_correspondence++;
}
3.4. 面特征
(1). 输入点 ,运动补偿去畸变变换至起始帧下
同线特征
(2). kdtree 搜索找到最近点
同线特征,只不过 kd tree 用的是 kdtreeSurfLast
(3). 查找 相同线束的最近邻点 ,以及相邻线束的最近邻点
(closestPointScanID, closestPointScanID + NEARBY_SCAN)
// search in the direction of increasing scan line
for (int j = closestPointInd + 1; j < (int)laserCloudSurfLast->points.size(); ++j) {
// if not in nearby scans, end the loop
if (int(laserCloudSurfLast->points[j].intensity) > (closestPointScanID + NEARBY_SCAN))
break;
double pointSqDis = (laserCloudSurfLast->points[j].x - pointSel.x) *
(laserCloudSurfLast->points[j].x - pointSel.x) +
(laserCloudSurfLast->points[j].y - pointSel.y) *
(laserCloudSurfLast->points[j].y - pointSel.y) +
(laserCloudSurfLast->points[j].z - pointSel.z) *
(laserCloudSurfLast->points[j].z - pointSel.z);
// if in the same or lower scan line
if (int(laserCloudSurfLast->points[j].intensity) <= closestPointScanID && pointSqDis < minPointSqDis2) {
minPointSqDis2 = pointSqDis;
minPointInd2 = j;
}
// if in the higher scan line
else if (int(laserCloudSurfLast->points[j].intensity) > closestPointScanID && pointSqDis < minPointSqDis3) {
minPointSqDis3 = pointSqDis;
minPointInd3 = j;
}
}
(closestPointScanID, closestPointScanID - NEARBY_SCAN)
// search in the direction of decreasing scan line
for (int j = closestPointInd - 1; j >= 0; --j) {
// if not in nearby scans, end the loop
if (int(laserCloudSurfLast->points[j].intensity) < (closestPointScanID - NEARBY_SCAN))
break;
double pointSqDis = (laserCloudSurfLast->points[j].x - pointSel.x) *
(laserCloudSurfLast->points[j].x - pointSel.x) +
(laserCloudSurfLast->points[j].y - pointSel.y) *
(laserCloudSurfLast->points[j].y - pointSel.y) +
(laserCloudSurfLast->points[j].z - pointSel.z) *
(laserCloudSurfLast->points[j].z - pointSel.z);
// if in the same or higher scan line
if (int(laserCloudSurfLast->points[j].intensity) >= closestPointScanID && pointSqDis < minPointSqDis2) {
minPointSqDis2 = pointSqDis;
minPointInd2 = j;
} else if (int(laserCloudSurfLast->points[j].intensity) < closestPointScanID && pointSqDis < minPointSqDis3) {
// find nearer point
minPointSqDis3 = pointSqDis;
minPointInd3 = j;
}
}
(4). 添加残差
if (minPointInd2 >= 0 && minPointInd3 >= 0) {
Eigen::Vector3d curr_point(surfPointsFlat->points[i].x,
surfPointsFlat->points[i].y,
surfPointsFlat->points[i].z);
Eigen::Vector3d last_point_a(laserCloudSurfLast->points[closestPointInd].x,
laserCloudSurfLast->points[closestPointInd].y,
laserCloudSurfLast->points[closestPointInd].z);
Eigen::Vector3d last_point_b(laserCloudSurfLast->points[minPointInd2].x,
laserCloudSurfLast->points[minPointInd2].y,
laserCloudSurfLast->points[minPointInd2].z);
Eigen::Vector3d last_point_c(laserCloudSurfLast->points[minPointInd3].x,
laserCloudSurfLast->points[minPointInd3].y,
laserCloudSurfLast->points[minPointInd3].z);
double s;
if (DISTORTION) {
s = (surfPointsFlat->points[i].intensity - int(surfPointsFlat->points[i].intensity)) / SCAN_PERIOD;
} else {
s = 1.0;
}
ceres::CostFunction *cost_function = LidarPlaneFactor::Create(curr_point, last_point_a, last_point_b, last_point_c, s);
problem.AddResidualBlock(cost_function, loss_function, para_q, para_t);
plane_correspondence++;
}
3.5. ceres 求解
ceres::Solver::Options options;
options.linear_solver_type = ceres::DENSE_QR;
options.max_num_iterations = 4;
options.minimizer_progress_to_stdout = false;
ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);
4. 依据帧间位姿更新当前帧位姿
t_w_curr = t_w_curr + q_w_curr * t_last_curr;
q_w_curr = q_w_curr * q_last_curr;
5. publish odometry && path
nav_msgs::Odometry laserOdometry;
laserOdometry.header.frame_id = "/camera_init";
laserOdometry.child_frame_id = "/laser_odom";
laserOdometry.header.stamp = ros::Time().fromSec(timeSurfPointsLessFlat);
laserOdometry.pose.pose.orientation.x = q_w_curr.x();
laserOdometry.pose.pose.orientation.y = q_w_curr.y();
laserOdometry.pose.pose.orientation.z = q_w_curr.z();
laserOdometry.pose.pose.orientation.w = q_w_curr.w();
laserOdometry.pose.pose.position.x = t_w_curr.x();
laserOdometry.pose.pose.position.y = t_w_curr.y();
laserOdometry.pose.pose.position.z = t_w_curr.z();
pubLaserOdometry.publish(laserOdometry);
geometry_msgs::PoseStamped laserPose;
laserPose.header = laserOdometry.header;
laserPose.pose = laserOdometry.pose.pose;
laserPath.header.stamp = laserOdometry.header.stamp;
laserPath.poses.push_back(laserPose);
laserPath.header.frame_id = "/camera_init";
pubLaserPath.publish(laserPath);
6. 重新初始化 kdtree
kdtreeCornerLast->setInputCloud(laserCloudCornerLast);
kdtreeSurfLast->setInputCloud(laserCloudSurfLast);