featureAssociation.cpp解析二
loam源码地址:https://github.com/RobustFieldAutonomyLab/LeGO-LOAM.
论文学习:【论文阅读】LeGO-LOAM: Lightweight and Ground-Optimized Lidar Odometry and Mapping on Variable Terrain.
LeGO-LOAM源码解析汇总:
LeGO-LOAM源码解析1: 算法整体框架和utility.h.
LeGO-LOAM源码解析2: imageProjection.
LeGO-LOAM源码解析3: featureAssociation(一).
五、运行接口函数runFeatureAssociation
整体的运行流程如下:
判断时间同步:
void runFeatureAssociation()
{
if (newSegmentedCloud && newSegmentedCloudInfo && newOutlierCloud &&
std::abs(timeNewSegmentedCloudInfo - timeNewSegmentedCloud) < 0.05 &&
std::abs(timeNewOutlierCloud - timeNewSegmentedCloud) < 0.05){
newSegmentedCloud = false;
newSegmentedCloudInfo = false;
newOutlierCloud = false;
}else{
return;
}
特征提取部分,包括畸变去除、计算平滑度、去除不可靠点、提取、发布等:
/**
1. Feature Extraction
*/
adjustDistortion();
calculateSmoothness();
markOccludedPoints();
extractFeatures();
publishCloud(); // cloud for visualization
特征关联,包括预测位姿、更新变换、转换计算、发布信息等:
/**
2. Feature Association
*/
if (!systemInitedLM) {
checkSystemInitialization();
return;
}
updateInitialGuess();
updateTransformation();
integrateTransformation();
publishOdometry();
publishCloudsLast(); // cloud to mapOptimization
}
接下来我们将分成曲率计算与特征提取、特征关联两部分进行解读 。
六、曲率计算与特征提取
1. 畸变去除adjustDistortion
(1) 线性插值具体流程
将点云从xyz(右前上)坐标系转换到yzx坐标系:
void adjustDistortion()
{
bool halfPassed = false;
int cloudSize = segmentedCloud->points.size();
PointType point;
for (int i = 0; i < cloudSize; i++) {
point.x = segmentedCloud->points[i].y;
point.y = segmentedCloud->points[i].z;
point.z = segmentedCloud->points[i].x;
通过判断每个点云是否旋转过半,然后分情况和起始角、结束角比较,判断点云角度是否合法,然后进行补偿:
float ori = -atan2(point.x, point.z);
if (!halfPassed) {
if (ori < segInfo.startOrientation - M_PI / 2)
ori += 2 * M_PI;
else if (ori > segInfo.startOrientation + M_PI * 3 / 2)
ori -= 2 * M_PI;
if (ori - segInfo.startOrientation > M_PI)
halfPassed = true;
} else {
ori += 2 * M_PI;
if (ori < segInfo.endOrientation - M_PI * 3 / 2)
ori += 2 * M_PI;
else if (ori > segInfo.endOrientation + M_PI / 2)
ori -= 2 * M_PI;
}
计算当前点与起始时刻的点云的相对时间,并保存到点的强度信息中:
float relTime = (ori - segInfo.startOrientation) / segInfo.orientationDiff;
point.intensity = int(segmentedCloud->points[i].intensity) + scanPeriod * relTime;
寻找超越当前点云点时间的IMU时间,没有找到则imuPointerFront
为imuPointerLast
:
if (imuPointerLast >= 0) {
float pointTime = relTime * scanPeriod;
imuPointerFront = imuPointerLastIteration;
while (imuPointerFront != imuPointerLast) {
if (timeScanCur + pointTime < imuTime[imuPointerFront]) {
break;
}
imuPointerFront = (imuPointerFront + 1) % imuQueLength;
}
若没有找到超越点云时间的IMU时间,则保存最新IMU数据与当前点云的这个点对应:
if (timeScanCur + pointTime > imuTime[imuPointerFront]) {
imuRollCur = imuRoll[imuPointerFront];
imuPitchCur = imuPitch[imuPointerFront];
imuYawCur = imuYaw[imuPointerFront];
imuVeloXCur = imuVeloX[imuPointerFront];
imuVeloYCur = imuVeloY[imuPointerFront];
imuVeloZCur = imuVeloZ[imuPointerFront];
imuShiftXCur = imuShiftX[imuPointerFront];
imuShiftYCur = imuShiftY[imuPointerFront];
imuShiftZCur = imuShiftZ[imuPointerFront];
}
若找到超越点云时间的IMU时间,利用前后的IMU时间对该点云点的IMU数据进行插补:
else {
int imuPointerBack = (imuPointerFront + imuQueLength - 1) % imuQueLength;
float ratioFront = (timeScanCur + pointTime - imuTime[imuPointerBack])
/ (imuTime[imuPointerFront] - imuTime[imuPointerBack]);
float ratioBack = (imuTime[imuPointerFront] - timeScanCur - pointTime)
/ (imuTime[imuPointerFront] - imuTime[imuPointerBack]);
分别对旋转角、速度、位移进行线性插值,得到点云中每个点的IMU数据,需要注意的是,再进行旋转角的插值的时候,roll和pitch的角度变化一般都是比较小的,但是yaw变化可能比较大,所以要进行判断和补偿:
//旋转角
imuRollCur = imuRoll[imuPointerFront] * ratioFront + imuRoll[imuPointerBack] * ratioBack;
imuPitchCur = imuPitch[imuPointerFront] * ratioFront + imuPitch[imuPointerBack] * ratioBack;
if (imuYaw[imuPointerFront] - imuYaw[imuPointerBack] > M_PI) {
imuYawCur = imuYaw[imuPointerFront] * ratioFront + (imuYaw[imuPointerBack] + 2 * M_PI) * ratioBack;
} else if (imuYaw[imuPointerFront] - imuYaw[imuPointerBack] < -M_PI) {
imuYawCur = imuYaw[imuPointerFront] * ratioFront + (imuYaw[imuPointerBack] - 2 * M_PI) * ratioBack;
} else {
imuYawCur = imuYaw[imuPointerFront] * ratioFront + imuYaw[imuPointerBack] * ratioBack;
}
//速度
imuVeloXCur = imuVeloX[imuPointerFront] * ratioFront + imuVeloX[imuPointerBack] * ratioBack;
imuVeloYCur = imuVeloY[imuPointerFront] * ratioFront + imuVeloY[imuPointerBack] * ratioBack;
imuVeloZCur = imuVeloZ[imuPointerFront] * ratioFront + imuVeloZ[imuPointerBack] * ratioBack;
//位移
imuShiftXCur = imuShiftX[imuPointerFront] * ratioFront + imuShiftX[imuPointerBack] * ratioBack;
imuShiftYCur = imuShiftY[imuPointerFront] * ratioFront + imuShiftY[imuPointerBack] * ratioBack;
imuShiftZCur = imuShiftZ[imuPointerFront] * ratioFront + imuShiftZ[imuPointerBack] * ratioBack;
}
若为第一个点云,则记录为该帧点云的起始点,保存对应IMU信息:
if (i == 0) {
imuRollStart = imuRollCur;
imuPitchStart = imuPitchCur;
imuYawStart = imuYawCur;
imuVeloXStart = imuVeloXCur;
imuVeloYStart = imuVeloYCur;
imuVeloZStart = imuVeloZCur;
imuShiftXStart = imuShiftXCur;
imuShiftYStart = imuShiftYCur;
imuShiftZStart = imuShiftZCur;
若是第一个点,则对角度的累积量也进行更新(IMU时间>第一个点时间则插值):
if (timeScanCur + pointTime > imuTime[imuPointerFront]) {
imuAngularRotationXCur = imuAngularRotationX[imuPointerFront];
imuAngularRotationYCur = imuAngularRotationY[imuPointerFront];
imuAngularRotationZCur = imuAngularRotationZ[imuPointerFront];
}
else{
int imuPointerBack = (imuPointerFront + imuQueLength - 1) % imuQueLength;
float ratioFront = (timeScanCur + pointTime - imuTime[imuPointerBack])
/ (imuTime[imuPointerFront] - imuTime[imuPointerBack]);
float ratioBack = (imuTime[imuPointerFront] - timeScanCur - pointTime)
/ (imuTime[imuPointerFront] - imuTime[imuPointerBack]);
imuAngularRotationXCur = imuAngularRotationX[imuPointerFront] * ratioFront + imuAngularRotationX[imuPointerBack] * ratioBack;
imuAngularRotationYCur = imuAngularRotationY[imuPointerFront] * ratioFront + imuAngularRotationY[imuPointerBack] * ratioBack;
imuAngularRotationZCur = imuAngularRotationZ[imuPointerFront] * ratioFront + imuAngularRotationZ[imuPointerBack] * ratioBack;
}
计算与上一帧的角度变化:
imuAngularFromStartX = imuAngularRotationXCur - imuAngularRotationXLast;
imuAngularFromStartY = imuAngularRotationYCur - imuAngularRotationYLast;
imuAngularFromStartZ = imuAngularRotationZCur - imuAngularRotationZLast;
imuAngularRotationXLast = imuAngularRotationXCur;
imuAngularRotationYLast = imuAngularRotationYCur;
imuAngularRotationZLast = imuAngularRotationZCur;
该帧的第一个点的三角函数值:
updateImuRollPitchYawStartSinCos();
}
updateImuRollPitchYawStartSinCos
具体的函数实现 如下:
void updateImuRollPitchYawStartSinCos(){
cosImuRollStart = cos(imuRollStart);
cosImuPitchStart = cos(imuPitchStart);
cosImuYawStart = cos(imuYawStart);
sinImuRollStart = sin(imuRollStart);
sinImuPitchStart = sin(imuPitchStart);
sinImuYawStart = sin(imuYawStart);
}
若不是第一个点,则将其他点的速度和点云信息投影到第一个点下:
else {
VeloToStartIMU();
TransformToStartIMU(&point);
}
}
此时的点云信息均在第一个点的时刻:
segmentedCloud->points[i] = point;
}
记录IMU时间的位置,等待下一帧点云进来,IMU以这个时间为起点:
imuPointerLastIteration = imuPointerLast;
}
(2) 投影函数
a) 速度投影VeloToStartIMU
得到其他点云点与第一个点云点的相对速度(世界坐标系):
void VeloToStartIMU()
{
// imuVeloXStart,imuVeloYStart,imuVeloZStart是点云索引i=0时刻的速度
// 此处计算的是相对于初始时刻i=0时的相对速度
// 这个相对速度在世界坐标系下
imuVeloFromStartXCur = imuVeloXCur - imuVeloXStart;
imuVeloFromStartYCur = imuVeloYCur - imuVeloYStart;
imuVeloFromStartZCur = imuVeloZCur - imuVeloZStart;
转换公式: Δ V i = 0 = R z ( − r o l l ) ∗ R x ( − p i t c h ) ∗ R y ( − y a w ) Δ V W \Delta V_{i=0}= R_z(-roll) * R_x(-pitch) * R_y(-yaw) \Delta V_W ΔVi=0=Rz(−roll)∗Rx(−pitch)∗Ry(−yaw)ΔVW
// !!!下面从世界坐标系转换到start坐标系,roll,pitch,yaw要取负值
// 首先绕y轴进行旋转
// |cosry 0 sinry|
// Ry=|0 1 0|
// |-sinry 0 cosry|
float x1 = cosImuYawStart * imuVeloFromStartXCur - sinImuYawStart * imuVeloFromStartZCur;
float y1 = imuVeloFromStartYCur;
float z1 = sinImuYawStart * imuVeloFromStartXCur + cosImuYawStart * imuVeloFromStartZCur;
// 绕当前x轴旋转(-pitch)的角度
// |1 0 0|
// Rx=|0 cosrx -sinrx|
// |0 sinrx cosrx|
float x2 = x1;
float y2 = cosImuPitchStart * y1 + sinImuPitchStart * z1;
float z2 = -sinImuPitchStart * y1 + cosImuPitchStart * z1;
// 绕当前z轴旋转(-roll)的角度
// |cosrz -sinrz 0|
// Rz=|sinrz cosrz 0|
// |0 0 1|
imuVeloFromStartXCur = cosImuRollStart * x2 + sinImuRollStart * y2;
imuVeloFromStartYCur = -sinImuRollStart * x2 + cosImuRollStart * y2;
imuVeloFromStartZCur = z2;
}
b) 点云投影TransformToStartIMU
将所有点云点转换到世界坐标系下:
P
W
i
=
R
y
(
y
a
w
)
∗
R
x
(
p
i
t
c
h
)
∗
R
z
(
r
o
l
l
)
∗
P
c
u
r
i
P_{W_i}=R_y(yaw)*R_x(pitch)*R_z(roll)*P_{cur_i}
PWi=Ry(yaw)∗Rx(pitch)∗Rz(roll)∗Pcuri
// 该函数的功能是把点云坐标变换到初始imu时刻
void TransformToStartIMU(PointType *p)
{
// 因为在adjustDistortion函数中有对xyz的坐标进行交换的过程
// 交换的过程是x=原来的y,y=原来的z,z=原来的x
// 所以下面其实是绕Z轴(原先的x轴)旋转,对应的是roll角
//
// |cosrz -sinrz 0|
// Rz=|sinrz cosrz 0|
// |0 0 1|
// [x1,y1,z1]^T=Rz*[x,y,z]
//
// 因为在imuHandler中进行过坐标变换,
// 所以下面的roll其实已经对应于新坐标系中(X-Y-Z)的yaw
float x1 = cos(imuRollCur) * p->x - sin(imuRollCur) * p->y;
float y1 = sin(imuRollCur) * p->x + cos(imuRollCur) * p->y;
float z1 = p->z;
// 绕X轴(原先的y轴)旋转
//
// [x2,y2,z2]^T=Rx*[x1,y1,z1]
// |1 0 0|
// Rx=|0 cosrx -sinrx|
// |0 sinrx cosrx|
float x2 = x1;
float y2 = cos(imuPitchCur) * y1 - sin(imuPitchCur) * z1;
float z2 = sin(imuPitchCur) * y1 + cos(imuPitchCur) * z1;
// 最后再绕Y轴(原先的Z轴)旋转
// |cosry 0 sinry|
// Ry=|0 1 0|
// |-sinry 0 cosry|
float x3 = cos(imuYawCur) * x2 + sin(imuYawCur) * z2;
float y3 = y2;
float z3 = -sin(imuYawCur) * x2 + cos(imuYawCur) * z2;
将点云点从世界坐标系下投影到点云中第一个点下:
P
s
t
a
r
t
i
=
R
z
(
−
r
o
l
l
)
∗
R
x
(
−
p
i
t
c
h
)
∗
R
y
(
−
y
a
w
)
P
W
i
P_{{start}_i}= R_z(-roll) * R_x(-pitch) * R_y(-yaw) P_{W_i}
Pstarti=Rz(−roll)∗Rx(−pitch)∗Ry(−yaw)PWi
// 下面部分的代码功能是从imu坐标的原点变换到i=0时imu的初始时刻(从世界坐标系变换到start坐标系)
// 变换方式和函数VeloToStartIMU()中的类似
// 变换顺序:Cur-->世界坐标系-->Start,这两次变换中,
// 前一次是正变换,角度为正,后一次是逆变换,角度应该为负
// 可以参考:
// https://blog.csdn.net/wykxwyc/article/details/101712524
float x4 = cosImuYawStart * x3 - sinImuYawStart * z3;
float y4 = y3;
float z4 = sinImuYawStart * x3 + cosImuYawStart * z3;
float x5 = x4;
float y5 = cosImuPitchStart * y4 + sinImuPitchStart * z4;
float z5 = -sinImuPitchStart * y4 + cosImuPitchStart * z4;
// 绕z轴(原先的x轴)变换角度到初始imu时刻,另外需要加上imu的位移漂移
// 后面加上的 imuShiftFromStart.. 表示从start时刻到cur时刻的漂移,
// (imuShiftFromStart.. 在start坐标系下)
p->x = cosImuRollStart * x5 + sinImuRollStart * y5 + imuShiftFromStartXCur;
p->y = -sinImuRollStart * x5 + cosImuRollStart * y5 + imuShiftFromStartYCur;
p->z = z5 + imuShiftFromStartZCur;
}
2. 计算平滑度calculateSmoothness
这里与LOAM中的写法一致,并没有严格安装LOAM中的公式展开,而是直接用点到前后个五个点的距离的平方来描述平滑度,保存在cloudSmoothness
中。cloudNeighborPicked
中保存的是是否为有效点,0有效,1无效:
void calculateSmoothness()
{
int cloudSize = segmentedCloud->points.size();
for (int i = 5; i < cloudSize - 5; i++) {
float diffRange = segInfo.segmentedCloudRange[i-5] + segInfo.segmentedCloudRange[i-4]
+ segInfo.segmentedCloudRange[i-3] + segInfo.segmentedCloudRange[i-2]
+ segInfo.segmentedCloudRange[i-1] - segInfo.segmentedCloudRange[i] * 10
+ segInfo.segmentedCloudRange[i+1] + segInfo.segmentedCloudRange[i+2]
+ segInfo.segmentedCloudRange[i+3] + segInfo.segmentedCloudRange[i+4]
+ segInfo.segmentedCloudRange[i+5];
cloudCurvature[i] = diffRange*diffRange;
// 在markOccludedPoints()函数中对该参数进行重新修改
cloudNeighborPicked[i] = 0;
// 在extractFeatures()函数中会对标签进行修改,
// 初始化为0,surfPointsFlat标记为-1,surfPointsLessFlatScan为不大于0的标签
// cornerPointsSharp标记为2,cornerPointsLessSharp标记为1
cloudLabel[i] = 0;
cloudSmoothness[i].value = cloudCurvature[i];
cloudSmoothness[i].ind = i;
}
}
3. 去除不可靠点markOccludedPoints
这里去除不可靠点的方式与LOAM更加粗暴,为了排除可能的遮挡情况,直接判断两个点的距离若距离相近,直接去掉比较远的五个点:
void markOccludedPoints()
{
int cloudSize = segmentedCloud->points.size();
for (int i = 5; i < cloudSize - 6; ++i){
float depth1 = segInfo.segmentedCloudRange[i];
float depth2 = segInfo.segmentedCloudRange[i+1];
int columnDiff = std::abs(int(segInfo.segmentedCloudColInd[i+1] - segInfo.segmentedCloudColInd[i]));
if (columnDiff < 10){
// 选择距离较远的那些点,并将他们标记为1
if (depth1 - depth2 > 0.3){
cloudNeighborPicked[i - 5] = 1;
cloudNeighborPicked[i - 4] = 1;
cloudNeighborPicked[i - 3] = 1;
cloudNeighborPicked[i - 2] = 1;
cloudNeighborPicked[i - 1] = 1;
cloudNeighborPicked[i] = 1;
}else if (depth2 - depth1 > 0.3){
cloudNeighborPicked[i + 1] = 1;
cloudNeighborPicked[i + 2] = 1;
cloudNeighborPicked[i + 3] = 1;
cloudNeighborPicked[i + 4] = 1;
cloudNeighborPicked[i + 5] = 1;
cloudNeighborPicked[i + 6] = 1;
}
}
为了排除共面的情况,两两比较,舍弃距离变化过大的点:
float diff1 = std::abs(segInfo.segmentedCloudRange[i-1] - segInfo.segmentedCloudRange[i]);
float diff2 = std::abs(segInfo.segmentedCloudRange[i+1] - segInfo.segmentedCloudRange[i]);
// 选择距离变化较大的点,并将他们标记为1
if (diff1 > 0.02 * segInfo.segmentedCloudRange[i] && diff2 > 0.02 * segInfo.segmentedCloudRange[i])
cloudNeighborPicked[i] = 1;
}
}
4. 特征提取extractFeatures
在这里主要实现的是对已经计算了曲率的点进行特征提取。
首先清空上一帧的特征量:
void extractFeatures()
{
cornerPointsSharp->clear();
cornerPointsLessSharp->clear();
surfPointsFlat->clear();
surfPointsLessFlat->clear();
为了防止特征提取的过于密集,对当前点云帧按线束,依次分成6份提取特征,segInfo.startRingIndex
存储的是每条激光线束第一个点在点云中的索引,其中
s
p
sp
sp存储的是每份点云的起点,其计算为
s
p
j
=
s
t
a
r
t
+
(
e
n
d
−
s
t
a
r
t
)
∗
j
6
sp_j=start+\frac{(end-start)*j}{6}
spj=start+6(end−start)∗j;而
e
p
ep
ep存储的是每份点云的终点,其计算为
e
p
j
=
s
t
a
r
t
+
(
e
n
d
−
s
t
a
r
t
)
∗
(
j
+
1
)
6
ep_j=start+\frac{(end-start)*(j+1)}{6}
epj=start+6(end−start)∗(j+1):
for (int i = 0; i < N_SCAN; i++) {
surfPointsLessFlatScan->clear();
for (int j = 0; j < 6; j++) {
// sp和ep的含义是什么???startPointer,endPointer?
int sp = (segInfo.startRingIndex[i] * (6 - j) + segInfo.endRingIndex[i] * j) / 6;
int ep = (segInfo.startRingIndex[i] * (5 - j) + segInfo.endRingIndex[i] * (j + 1)) / 6 - 1;
if (sp >= ep)
continue;
将每一份中的点云的曲率按从小到大排列:
std::sort(cloudSmoothness.begin()+sp, cloudSmoothness.begin()+ep, by_value());
提取线特征的条件,不能是淘汰的点(这里指不可靠的点)、曲率必须大于一定的阈值、不能是地面点:
int largestPickedNum = 0;
for (int k = ep; k >= sp; k--) {
int ind = cloudSmoothness[k].ind;
if (cloudNeighborPicked[ind] == 0 &&
cloudCurvature[ind] > edgeThreshold &&
segInfo.segmentedCloudGroundFlag[ind] == false) {
提取曲率最大的两个点加入cornerPointsSharp
,而其他18个曲率较大的点加入cornerPointsLessSharp
集合:
largestPickedNum++;
if (largestPickedNum <= 2) {
cloudLabel[ind] = 2;
cornerPointsSharp->push_back(segmentedCloud->points[ind]);
cornerPointsLessSharp->push_back(segmentedCloud->points[ind]);
} else if (largestPickedNum <= 20) {
cloudLabel[ind] = 1;
cornerPointsLessSharp->push_back(segmentedCloud->points[ind]);
} else {
break;
}
将已经选取为线特征的点淘汰,同时检查这个点前后5个点,若距离较近,同样淘汰,淘汰是指cloudNeighborPicked
置1:
cloudNeighborPicked[ind] = 1;
for (int l = 1; l <= 5; l++) {
int columnDiff = std::abs(int(segInfo.segmentedCloudColInd[ind + l] - segInfo.segmentedCloudColInd[ind + l - 1]));
if (columnDiff > 10)
break;
cloudNeighborPicked[ind + l] = 1;
}
for (int l = -1; l >= -5; l--) {
int columnDiff = std::abs(int(segInfo.segmentedCloudColInd[ind + l] - segInfo.segmentedCloudColInd[ind + l + 1]));
if (columnDiff > 10)
break;
cloudNeighborPicked[ind + l] = 1;
}
}
}
提取线特征的条件,不能是淘汰的点(这里指不可靠的点以及线特征附近的点)、曲率必须小于一定的阈值、只能是地面点:
int smallestPickedNum = 0;
for (int k = sp; k <= ep; k++) {
int ind = cloudSmoothness[k].ind;
if (cloudNeighborPicked[ind] == 0 &&
cloudCurvature[ind] < surfThreshold &&
segInfo.segmentedCloudGroundFlag[ind] == true) {
cloudLabel[ind] = -1;
surfPointsFlat->push_back(segmentedCloud->points[ind]);
smallestPickedNum++;
if (smallestPickedNum >= 4) {
break;
}
将已经选取为面特征的点淘汰,同时检查这个点前后5个点,若距离较近,同样淘汰,淘汰是指cloudNeighborPicked
置1:
cloudNeighborPicked[ind] = 1;
for (int l = 1; l <= 5; l++) {
int columnDiff = std::abs(int(segInfo.segmentedCloudColInd[ind + l] - segInfo.segmentedCloudColInd[ind + l - 1]));
if (columnDiff > 10)
break;
cloudNeighborPicked[ind + l] = 1;
}
for (int l = -1; l >= -5; l--) {
int columnDiff = std::abs(int(segInfo.segmentedCloudColInd[ind + l] - segInfo.segmentedCloudColInd[ind + l + 1]));
if (columnDiff > 10)
break;
cloudNeighborPicked[ind + l] = 1;
}
}
}
将剩下所有的点设置为surfPointsLessFlatScan
for (int k = sp; k <= ep; k++) {
if (cloudLabel[k] <= 0) {
surfPointsLessFlatScan->push_back(segmentedCloud->points[k]);
}
}
}
surfPointsLessFlatScan中有过多的点云,如果点云太多,计算量太大,进行下采样,可以大大减少计算量:
surfPointsLessFlatScanDS->clear();
downSizeFilter.setInputCloud(surfPointsLessFlatScan);
downSizeFilter.filter(*surfPointsLessFlatScanDS);
*surfPointsLessFlat += *surfPointsLessFlatScanDS;
}
}
5. 点云发布publishCloud
分别发布曲率大的点云、曲率较大的点云、曲率小的点云、曲率较小的点云:
void publishCloud()
{
sensor_msgs::PointCloud2 laserCloudOutMsg;
if (pubCornerPointsSharp.getNumSubscribers() != 0){
pcl::toROSMsg(*cornerPointsSharp, laserCloudOutMsg);
laserCloudOutMsg.header.stamp = cloudHeader.stamp;
laserCloudOutMsg.header.frame_id = "/camera";
pubCornerPointsSharp.publish(laserCloudOutMsg);
}
if (pubCornerPointsLessSharp.getNumSubscribers() != 0){
pcl::toROSMsg(*cornerPointsLessSharp, laserCloudOutMsg);
laserCloudOutMsg.header.stamp = cloudHeader.stamp;
laserCloudOutMsg.header.frame_id = "/camera";
pubCornerPointsLessSharp.publish(laserCloudOutMsg);
}
if (pubSurfPointsFlat.getNumSubscribers() != 0){
pcl::toROSMsg(*surfPointsFlat, laserCloudOutMsg);
laserCloudOutMsg.header.stamp = cloudHeader.stamp;
laserCloudOutMsg.header.frame_id = "/camera";
pubSurfPointsFlat.publish(laserCloudOutMsg);
}
if (pubSurfPointsLessFlat.getNumSubscribers() != 0){
pcl::toROSMsg(*surfPointsLessFlat, laserCloudOutMsg);
laserCloudOutMsg.header.stamp = cloudHeader.stamp;
laserCloudOutMsg.header.frame_id = "/camera";
pubSurfPointsLessFlat.publish(laserCloudOutMsg);
}
}