transformMaintenance.cpp解析
loam源码地址: https://github.com/cuitaixiang/LOAM_NOTED.
论文学习: LOAM: Lidar Odometry and Mapping in Real-time 论文阅读.
loam源码解析汇总:
loam源码解析1 : scanRegistration(一).
loam源码解析2 : scanRegistration(二).
loam源码解析3 : laserOdometry(一).
loam源码解析4 : laserOdometry(二).
loam源码解析5 : laserOdometry(三).
loam源码解析6 : laserMapping(一).
loam源码解析7 : laserMapping(二).
loam源码解析8 : transformMaintenance.
七、点云配准
当边缘点大于10,平面点数量大于100时才进行迭代优化
if (laserCloudCornerLastNum > 10 && laserCloudSurfLastNum > 100) {
std::vector<int> indices;
pcl::removeNaNFromPointCloud(*cornerPointsSharp,*cornerPointsSharp, indices);
int cornerPointsSharpNum = cornerPointsSharp->points.size();
int surfPointsFlatNum = surfPointsFlat->points.size();
迭代次数25次
for (int iterCount = 0; iterCount < 25; iterCount++) {
laserCloudOri->clear();
coeffSel->clear();
1.边缘点(线特征)处理
需要说明的是,基本思路是将提取到的曲率大的点,先在上一帧曲率较大的点中寻找最近邻点,再在最近邻点所在scan的相邻scan中找到另一个距离最近的点,具体分析如下:
首先将每个线特征点(边缘点)都投影到该帧起始点的坐标系下
for (int i = 0; i < cornerPointsSharpNum; i++) {
TransformToStart(&cornerPointsSharp->points[i], &pointSel);
每迭代五次都寻找一次最近点,先在KD树中找到最近邻点:
//每迭代五次,重新查找最近点
if (iterCount % 5 == 0) {
std::vector<int> indices;
pcl::removeNaNFromPointCloud(*laserCloudCornerLast,*laserCloudCornerLast, indices);
//kd-tree查找一个最近距离点,边沿点未经过体素栅格滤波,一般边沿点本来就比较少,不做滤波
kdtreeCornerLast->nearestKSearch(pointSel, 1, pointSearchInd, pointSearchSqDis);
int closestPointInd = -1, minPointInd2 = -1;
寻找相邻线距离目标点距离最小的点(迷惑行为,不是知道了最近邻的scanID麻~,还整这么麻烦)
//再次提醒:velodyne是2度一线,scanID相邻并不代表线号相邻,相邻线度数相差2度,也即线号scanID相差2
if (pointSearchSqDis[0] < 25) {//找到的最近点距离的确很近的话
closestPointInd = pointSearchInd[0];
//提取最近点线号
int closestPointScan = int(laserCloudCornerLast->points[closestPointInd].intensity);
float pointSqDis, minPointSqDis2 = 25;//初始门槛值5米,可大致过滤掉scanID相邻,但实际线不相邻的值
//寻找距离目标点最近距离的平方和最小的点
for (int j = closestPointInd + 1; j < cornerPointsSharpNum; j++) {//向scanID增大的方向查找
if (int(laserCloudCornerLast->points[j].intensity) > closestPointScan + 2.5) {//非相邻线
break;
}
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 (int(laserCloudCornerLast->points[j].intensity) > closestPointScan) {//确保两个点不在同一条scan上(相邻线查找应该可以用scanID == closestPointScan +/- 1 来做)
if (pointSqDis < minPointSqDis2) {//距离更近,要小于初始值5米
//更新最小距离与点序
minPointSqDis2 = pointSqDis;
minPointInd2 = j;
}
}
}
//同理
for (int j = closestPointInd - 1; j >= 0; j--) {//向scanID减小的方向查找
if (int(laserCloudCornerLast->points[j].intensity) < closestPointScan - 2.5) {
break;
}
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 (int(laserCloudCornerLast->points[j].intensity) < closestPointScan) {
if (pointSqDis < minPointSqDis2) {
minPointSqDis2 = pointSqDis;
minPointInd2 = j;
}
}
}
}
//记住组成线的点序
pointSearchCornerInd1[i] = closestPointInd;//kd-tree最近距离点,-1表示未找到满足的点
pointSearchCornerInd2[i] = minPointInd2;//另一个最近的,-1表示未找到满足的点
}
接下来的工作就比较简单了,距离的计算与论文中一致(不熟悉的可以看看LOAM: Lidar Odometry and Mapping in Real-time 论文阅读.)公式如下:
值得说明的是,代码中在五次迭代后多了一个权重计算,距离越大权重越小,距离越小权重越大,得到的权重范围小于等于1。
还有一个值得注意的点,代码中不仅计算了点到直线的距离,还得到了点到直线的方向向量,以及该方向向量在各个轴的分解量,这将被用在雅可比求解中。
if (pointSearchCornerInd2[i] >= 0) {//大于等于0,不等于-1,说明两个点都找到了
tripod1 = laserCloudCornerLast->points[pointSearchCornerInd1[i]];
tripod2 = laserCloudCornerLast->points[pointSearchCornerInd2[i]];
//选择的特征点记为O,kd-tree最近距离点记为A,另一个最近距离点记为B
float x0 = pointSel.x;
float y0 = pointSel.y;
float z0 = pointSel.z;
float x1 = tripod1.x;
float y1 = tripod1.y;
float z1 = tripod1.z;
float x2 = tripod2.x;
float y2 = tripod2.y;
float z2 = tripod2.z;
//向量OA = (x0 - x1, y0 - y1, z0 - z1), 向量OB = (x0 - x2, y0 - y2, z0 - z2),向量AB = (x1 - x2, y1 - y2, z1 - z2)
//向量OA OB的向量积(即叉乘)为:
//| i j k |
//|x0-x1 y0-y1 z0-z1|
//|x0-x2 y0-y2 z0-z2|
//模为:
float a012 = sqrt(((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1))
* ((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1))
+ ((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1))
* ((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1))
+ ((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1))
* ((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1)));
//两个最近距离点之间的距离,即向量AB的模
float l12 = sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2) + (z1 - z2)*(z1 - z2));
//AB方向的单位向量与OAB平面的单位法向量的向量积在各轴上的分量(d的方向)
//x轴分量i
float la = ((y1 - y2)*((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1))
+ (z1 - z2)*((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1))) / a012 / l12;
//y轴分量j
float lb = -((x1 - x2)*((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1))
- (z1 - z2)*((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1))) / a012 / l12;
//z轴分量k
float lc = -((x1 - x2)*((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1))
+ (y1 - y2)*((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1))) / a012 / l12;
//点到线的距离,d = |向量OA 叉乘 向量OB|/|AB|
float ld2 = a012 / l12;
//unused
pointProj = pointSel;
pointProj.x -= la * ld2;
pointProj.y -= lb * ld2;
pointProj.z -= lc * ld2;
//权重计算,距离越大权重越小,距离越小权重越大,得到的权重范围<=1
float s = 1;
if (iterCount >= 5) {//5次迭代之后开始增加权重因素
s = 1 - 1.8 * fabs(ld2);
}
//考虑权重
coeff.x = s * la;
coeff.y = s * lb;
coeff.z = s * lc;
coeff.intensity = s * ld2;
if (s > 0.1 && ld2 != 0) {//只保留权重大的,也即距离比较小的点,同时也舍弃距离为零的
laserCloudOri->push_back(cornerPointsSharp->points[i]);
coeffSel->push_back(coeff);
}
}
}
2.平面点(面特征)处理
同样的,先说明的是,基本思路是将提取到的曲率小的点,先在上一帧曲率较小的点中寻找最近邻点,再在最近邻点所在scan的相邻scan中找到另一个距离最近的点,额外的还需要在最近邻点的同一帧中找到另一个距离最小的点(或者说第二小的点),三个点才能形成一个平面。具体分析如下:
首先将每个面特征点(平面点)都投影到该帧起始点的坐标系下
//对本次接收到的曲率最小的点,从上次接收到的点云曲率比较小的点中找三点组成平面,一个使用kd-tree查找,另外一个在同一线上查找满足要求的,第三个在不同线上查找满足要求的
for (int i = 0; i < surfPointsFlatNum; i++) {
TransformToStart(&surfPointsFlat->points[i], &pointSel);
在kd树中每五次迭代就寻找一次最近邻点
if (iterCount % 5 == 0) {
//kd-tree最近点查找,在经过体素栅格滤波之后的平面点中查找,一般平面点太多,滤波后最近点查找数据量小
kdtreeSurfLast->nearestKSearch(pointSel, 1, pointSearchInd, pointSearchSqDis);
int closestPointInd = -1, minPointInd2 = -1, minPointInd3 = -1;
if (pointSearchSqDis[0] < 25) {
closestPointInd = pointSearchInd[0];
int closestPointScan = int(laserCloudSurfLast->points[closestPointInd].intensity);
在scanID增大的方向寻找除了最近邻点以外的剩余两个点
float pointSqDis, minPointSqDis2 = 25, minPointSqDis3 = 25;
for (int j = closestPointInd + 1; j < surfPointsFlatNum; j++) {
if (int(laserCloudSurfLast->points[j].intensity) > closestPointScan + 2.5) {
break;
}
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 (int(laserCloudSurfLast->points[j].intensity) <= closestPointScan) {//如果点的线号小于等于最近点的线号(应该最多取等,也即同一线上的点)
if (pointSqDis < minPointSqDis2) {
minPointSqDis2 = pointSqDis;
minPointInd2 = j;
}
} else {//如果点处在大于该线上
if (pointSqDis < minPointSqDis3) {
minPointSqDis3 = pointSqDis;
minPointInd3 = j;
}
}
}
在scanID减小的方向寻找除了最近邻点以外的剩余两个点
//同理
for (int j = closestPointInd - 1; j >= 0; j--) {
if (int(laserCloudSurfLast->points[j].intensity) < closestPointScan - 2.5) {
break;
}
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 (int(laserCloudSurfLast->points[j].intensity) >= closestPointScan) {
if (pointSqDis < minPointSqDis2) {
minPointSqDis2 = pointSqDis;
minPointInd2 = j;
}
} else {
if (pointSqDis < minPointSqDis3) {
minPointSqDis3 = pointSqDis;
minPointInd3 = j;
}
}
}
}
找到特征点的对应点以后的工作就是计算距离了,点面距离公式与论文一致:
与线特征相比,同样也多了一个权重计算和平面法向量(就是点到平面的直线的方向向量)在各个轴的方向分解(基本一致),同样也被用到雅可比的计算中。
pointSearchSurfInd1[i] = closestPointInd;//kd-tree最近距离点,-1表示未找到满足要求的点
pointSearchSurfInd2[i] = minPointInd2;//同一线号上的距离最近的点,-1表示未找到满足要求的点
pointSearchSurfInd3[i] = minPointInd3;//不同线号上的距离最近的点,-1表示未找到满足要求的点
}
if (pointSearchSurfInd2[i] >= 0 && pointSearchSurfInd3[i] >= 0) {//找到了三个点
tripod1 = laserCloudSurfLast->points[pointSearchSurfInd1[i]];//A点
tripod2 = laserCloudSurfLast->points[pointSearchSurfInd2[i]];//B点
tripod3 = laserCloudSurfLast->points[pointSearchSurfInd3[i]];//C点
//向量AB = (tripod2.x - tripod1.x, tripod2.y - tripod1.y, tripod2.z - tripod1.z)
//向量AC = (tripod3.x - tripod1.x, tripod3.y - tripod1.y, tripod3.z - tripod1.z)
//向量AB AC的向量积(即叉乘),得到的是法向量
//x轴方向分向量i
float pa = (tripod2.y - tripod1.y) * (tripod3.z - tripod1.z)
- (tripod3.y - tripod1.y) * (tripod2.z - tripod1.z);
//y轴方向分向量j
float pb = (tripod2.z - tripod1.z) * (tripod3.x - tripod1.x)
- (tripod3.z - tripod1.z) * (tripod2.x - tripod1.x);
//z轴方向分向量k
float pc = (tripod2.x - tripod1.x) * (tripod3.y - tripod1.y)
- (tripod3.x - tripod1.x) * (tripod2.y - tripod1.y);
float pd = -(pa * tripod1.x + pb * tripod1.y + pc * tripod1.z);
//法向量的模
float ps = sqrt(pa * pa + pb * pb + pc * pc);
//pa pb pc为法向量各方向上的单位向量
pa /= ps;
pb /= ps;
pc /= ps;
pd /= ps;
//点到面的距离:向量OA与与法向量的点积除以法向量的模
float pd2 = pa * pointSel.x + pb * pointSel.y + pc * pointSel.z + pd;
//unused
pointProj = pointSel;
pointProj.x -= pa * pd2;
pointProj.y -= pb * pd2;
pointProj.z -= pc * pd2;
//同理计算权重
float s = 1;
if (iterCount >= 5) {
s = 1 - 1.8 * fabs(pd2) / sqrt(sqrt(pointSel.x * pointSel.x
+ pointSel.y * pointSel.y + pointSel.z * pointSel.z));
}
//考虑权重
coeff.x = s * pa;
coeff.y = s * pb;
coeff.z = s * pc;
coeff.intensity = s * pd2;
if (s > 0.1 && pd2 != 0) {
//保存原始点与相应的系数
laserCloudOri->push_back(surfPointsFlat->points[i]);
coeffSel->push_back(coeff);
}
}
}
满足要求的特征点至少10个,特征匹配数量太少弃用此帧数据
int pointSelNum = laserCloudOri->points.size();
if (pointSelNum < 10) {
continue;
}