基於因子圖優化的鐳射IMU融合SLAM學習
因子圖優化鐳射&IMU融合SLAM
主要學習今年最新開源的LIOSAM.
LIOSAM 點雲預處理與特徵提取的環節的分析見這裡
legoloam系列演算法之點雲處理與特徵提取
這裡主要學習的是在提取了鐳射特徵後, 融合IMU, GPS 實現鐳射里程計的方法, 雖然LIOSAM也是具有迴環的, 但是用的方法基本和legoloam一樣, 所以重點就放在 鐳射里程計上.
梳理一下主要檔案:
關於IMU的融合主要在src/imuPreintegration.cpp中完成.
接收到去除畸變後的鐳射特徵以及GPS資料後, 對鐳射和GPS進行融合的過程在 src/mapOptmization.cpp 中完成.
本次學習的重點就是這兩個檔案了.
1. 鐳射特徵的處理
LIOSAM融合鐳射的方式是, 單獨求取兩幀鐳射的運動, 然後將其作為約束新增到因子圖中.
鐳射的特徵在 回撥函式 laserCloudInfoHandler() 中處理:
核心的幾個函式如下:
- 預測當前幀的位姿 updateInitialGuess()
- 提取區域性map的特徵 extractSurroundingKeyFrames()
- 降取樣本幀的特徵downsampleCurrentScan()
- 進行高斯牛頓優化求解運動 scan2MapOptimization()
updateInitialGuess()
extractSurroundingKeyFrames()
scan2MapOptimization()
通過前面的步驟獲取預測位姿以及提取出當前幀與Map幀的特徵, 然後進行高斯牛頓優化.
步驟如下:
-
首先對區域性Map的特徵構造kdtree, 用於當前幀的特徵進行搜尋關聯.
// 設定kdtree kdtreeCornerFromMap->setInputCloud(laserCloudCornerFromMapDS); kdtreeSurfFromMap->setInputCloud(laserCloudSurfFromMapDS);
-
進行迭代, 求解出位姿
// 30次迭代
for (int iterCount = 0; iterCount < 30; iterCount++)
{
laserCloudOri->clear();
coeffSel->clear();
// 角點殘差計算
cornerOptimization();
// 平面點殘差計算
surfOptimization();
// 整理出用於進行優化的點線,點面殘差
combineOptimizationCoeffs();
// 優化的主函式
if (LMOptimization(iterCount) == true)
break;
}
- 更新狀態 transformUpdate().
關鍵在於迭代求解的環節, 主要研究下面幾個函式
cornerOptimization()
作用: 對所有的線特徵, 計算點線殘差.
這個過程的幾個重點如下:
-
遍歷當前幀全部曲面特徵 , 將其變換到 Map座標系中, 並用kdtree搜尋Map中最近的5個曲面特徵
kdtreeCornerFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis);
其中kdtreeCornerFromMap的搜尋物件是 laserCloudCornerFromMapDS, 也就是map中全部 Corner
kdtreeCornerFromMap->setInputCloud(laserCloudCornerFromMapDS);
這個也是源自legoloam的操作, 這個做法的好處是,由於 特徵點只會和具有同樣標籤的特徵進行匹配, 這樣提高了資料關聯的準確性和效率.
-
求這5 個點構成的點雲的協方差矩陣
// 求這一團點雲的均值
float cx = 0, cy = 0, cz = 0;
for (int j = 0; j < 5; j++)
{
cx += laserCloudCornerFromMapDS->points[pointSearchInd[j]].x;
cy += laserCloudCornerFromMapDS->points[pointSearchInd[j]].y;
cz += laserCloudCornerFromMapDS->points[pointSearchInd[j]].z;
}
cx /= 5; cy /= 5; cz /= 5;
// 協方差矩陣
float a11 = 0, a12 = 0, a13 = 0, a22 = 0, a23 = 0, a33 = 0;
for (int j = 0; j < 5; j++)
{
float ax = laserCloudCornerFromMapDS->points[pointSearchInd[j]].x - cx;
float ay = laserCloudCornerFromMapDS->points[pointSearchInd[j]].y - cy;
float az = laserCloudCornerFromMapDS->points[pointSearchInd[j]].z - cz;
a11 += ax * ax; a12 += ax * ay; a13 += ax * az;
a22 += ay * ay; a23 += ay * az;
a33 += az * az;
}
a11 /= 5; a12 /= 5; a13 /= 5; a22 /= 5; a23 /= 5; a33 /= 5;
matA1.at<float>(0, 0) = a11; matA1.at<float>(0, 1) = a12; matA1.at<float>(0, 2) = a13;
matA1.at<float>(1, 0) = a12; matA1.at<float>(1, 1) = a22; matA1.at<float>(1, 2) = a23;
matA1.at<float>(2, 0) = a13; matA1.at<float>(2, 1) = a23; matA1.at<float>(2, 2) = a33;
- 進行特徵分解 , 並通過特徵值判斷是否是直線
// 進行特徵分解
cv::eigen(matA1, matD1, matV1);
// 如果 最大的特徵值比第二大特徵值大3倍以上 那麼認為是直線
if (matD1.at<float>(0, 0) > 3 * matD1.at<float>(0, 1))
{
......
}
接著取當前點與直線上兩個點
// 當前點
float x0 = pointSel.x;
float y0 = pointSel.y;
float z0 = pointSel.z;
// 取直線的兩個點 matV1 為直線方向向量
float x1 = cx + 0.1 * matV1.at<float>(0, 0);
float y1 = cy + 0.1 * matV1.at<float>(0, 1);
float z1 = cz + 0.1 * matV1.at<float>(0, 2);
float x2 = cx - 0.1 * matV1.at<float>(0, 0);
float y2 = cy - 0.1 * matV1.at<float>(0, 1);
float z2 = cz - 0.1 * matV1.at<float>(0, 2);
計算當前點到直線的距離 (根據叉乘得到三角形面積, 然後通過三角形面積求解高),
以及該距離的方向向量, 這個用於後面高斯牛頓優化的jacobian矩陣的求解, 實際上也是一個梯度向量
// | (p0 - p1) x (p0 - p2) |
//向量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)));
// |(p1 - p2)|
float l12 = sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2) + (z1 - z2)*(z1 - z2));
// 下面求d的方向向量 nd = {la, lb, lc}
// 這個也是當前距離函式的梯度向量
float la = ( (y1 - y2) * ( (x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1) ) +
(z1 - z2) * ( (x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1) ) ) / a012 / l12;
float lb = -((x1 - x2)*((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1))
- (z1 - z2)*((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1))) / a012 / l12;
float lc = -((x1 - x2)*((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1))
+ (y1 - y2)*((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1))) / a012 / l12;
// 點到直線的距離
float ld2 = a012 / l12;
參考下圖:
最後就是賦予一個權重, 然後將距離向量(梯度), 和距離殘差值放入 coeff
// 權重係數
float s = 1 - 0.9 * fabs(ld2);
// 點到直線的距離向量
coeff.x = s * la;
coeff.y = s * lb;
coeff.z = s * lc;
coeff.intensity = s * ld2; // 代價值為距離 x 權重
surfOptimization()
作用: 對所有的面特徵, 計算點面殘差.
同樣是遍歷全部平面特徵, 然後在Map的平面特徵集中尋找5個最近點,
然後是採用最小二乘, 把平面的方程擬合出來 這裡是構造 AX + 1 = 0
for (int j = 0; j < 5; j++)
{
matA0(j, 0) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].x;
matA0(j, 1) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].y;
matA0(j, 2) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].z;
}
// 求解 AX= B ( matA0*matX0 = -1 ) 對A進行QR分解 => QRX = B 令 RX = y => y = Q^t*B =>求解 X
matX0 = matA0.colPivHouseholderQr().solve(matB0);
// 平面係數
float pa = matX0(0, 0);
float pb = matX0(1, 0);
float pc = matX0(2, 0);
float pd = 1;
然後就是求點面距離的操作了
// 向量的模長
float ps = sqrt(pa * pa + pb * pb + pc * pc);
// 向量歸一化 這樣後面就可以直接求點到面的距離
pa /= ps;
pb /= ps;
pc /= ps;
pd /= ps; // 1 / ps 也需要同時縮小
...........
// 如果平面的精度足夠
if (planeValid)
{
// 點到面的距離 (有正負之分) 點面距離為什麼不取絕對值 ?
float pd2 = pa * pointSel.x + pb * pointSel.y + pc * pointSel.z + pd;
// 權重 怎麼理解 ????????????????????????????????
float s = 1 - 0.9 * 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)
{
laserCloudOriSurfVec[i] = pointOri;
coeffSelSurfVec[i] = coeff;
laserCloudOriSurfFlag[i] = true;
}
}
這裡思考一個問題, 為什麼 在構造點到直線的距離的時候, 使用的是距離的絕對值,
而構造點面的距離的時候用得卻是一個有正負的值呢?? 那麼可不可以直接把點到面的距離改成絕對值呢?
這樣是不可以的, 原因簡單來說, 是因為 , 構造點到線距離的時候, 其實不僅僅是求了一個點到直線的距離這麼簡單,它還求了一個距離函式的梯度,也就是一個朝向使距離函式值增大速度最快方向的向量, 而 構造點面距離殘差的部分 , 並沒有求一個梯度而是一個固定的平面法向量, 這時 我們直接將距離函式取絕對值, 那麼在高斯牛頓優化求解時, 系統並不能往正確的方向上去迭代, 如下圖, 點1和點2 距離平面距離都為h, 但是上面的點需要往下走, 而下面的點需要往上走, 而高斯牛頓求解時這兩種情況的更新相同, 那麼自然是錯誤的.
那麼如果要將點到平面的距離構造為絕對值的形式應該怎麼做呢? 參考下面程式碼
if (planeValid)
{
float pd2 = pa * pointSel.x + pb * pointSel.y + pc * pointSel.z + pd;
// 後面部分相除求的是[pa,pb,pc,pd]與pointSel的夾角餘弦值(兩個sqrt,其實並不是餘弦值)
// 這個夾角餘弦值越小越好,越小證明所求的[pa,pb,pc,pd]與平面越垂直
float s = 1 - 0.9 * fabs(pd2) / sqrt(sqrt(pointSel.x * pointSel.x
+ pointSel.y * pointSel.y + pointSel.z * pointSel.z));
if(pd2<0)
{
coeff.x = -s * pa;
coeff.y = -s * pb;
coeff.z = -s * pc;
}
else
{
coeff.x = s * pa;
coeff.y = s * pb;
coeff.z = s * pc;
}
// 改用距離的絕對值
coeff.intensity = s * fabs(pd2);
// 判斷是否是合格平面,是就加入laserCloudOri
if (s > 0.1)
{
laserCloudOri->push_back(pointOri);
coeffSel->push_back(coeff);
}
}
combineOptimizationCoeffs()
作用: 遍歷當前幀所有的特徵 將用於構成殘差的點放入 laserCloudOri, 殘差資訊放入 coeffSel
bool LMOptimization(int iterCount)
進行高斯牛頓優化, 這個函式是重點.
----------------------------------------------從下面開始 需要學習GTSAM的相關知識 ----------------------------------
2.IMU的處理
3.GPS的處理
相關文章
- 從零開始搭二維鐳射SLAM --- 基於PL-ICP的鐳射雷達里程計SLAM
- 一種視覺慣性+鐳射感測器的SLAM系統視覺SLAM
- 基於Barra多因子模型的組合權重優化模型優化
- 固態鐳射雷達優劣
- 基於CPU的深度學習推理部署優化實踐深度學習優化
- 百度無人車實現全球首個基於深度學習的鐳射點雲自定位技術深度學習
- 深入瞭解基於鐳射雷達的機器人定位感測器機器人
- 用於鐳射雷達的 APD,SPAD 和 SiPM 分析
- 基於飛槳復現FP-Net全程解析,補全缺失的鐳射點
- 由iphone12說說鐳射雷達 FMCW鐳射雷達iPhone
- 鐳射雷達線數 單線與多線鐳射雷達的區別
- 智慧駕駛-感知-融合定位IMU
- 基於機器人自主移動實現SLAM建圖機器人SLAM
- 鐳射雷達點雲語義分割學習筆記之RangeNet++筆記
- SLAM學習筆記(1)SLAM筆記
- 粒子群演算法中對於學習因子的改進演算法
- 基於Barra多因子模型的組合權重最佳化模型
- 基於策略搜尋的強化學習方法強化學習
- AutoTiKV:基於機器學習的資料庫調優機器學習資料庫
- 基於 Nebula Graph 構建圖學習能力
- 強化學習(十七) 基於模型的強化學習與Dyna演算法框架強化學習模型演算法框架
- 印表機噴墨和鐳射哪個好 噴墨印表機和鐳射印表機的區別
- bzoj1121: [POI2008]鐳射發射器SZK
- 基於 PageSpeed 的效能優化實踐優化
- 單目線鐳射三維重建
- CMOS鐳射感測器GV系列
- 基於Cucumber框架的學習框架
- mysql優化篇(基於索引)MySql優化索引
- 基於 StarRocks 進行湖倉融合的四種正規化
- 效能優化的過程學習優化
- 測量、基線和效能優化之三:基於測量、基線和變化的效能優化優化
- SLAM學習資料整理 殿堂級大牛SLAM
- 基於化學環境的多相催化吸附機器學習框架機器學習框架
- 基於XDanmuku的Android效能優化實戰Android優化
- Curve 基於 Raft 的寫時延優化Raft優化
- 洛谷 P2280 鐳射炸彈
- 如何用手機模擬鐳射筆
- qt鐳射加工軟體開發(一)QT