C語言lidar_align雷達(dá)里程計校準(zhǔn)功能詳解
功能介紹
功能包名稱:lidar_align git網(wǎng)址:鏈接
一種 校準(zhǔn) 3D 激光雷達(dá)和 6 自由度位姿傳感器 外參 的 方法
適配的ROS版本有 Indigo、Kinect、Melodic
準(zhǔn)確的結(jié)果需要 大量 非平面的運動,這使得該方法不適合 校準(zhǔn) 安裝在汽車上的傳感器 以為只有在那個方向上有數(shù)據(jù)的變化才能計算出最終的結(jié)果。
整個功能包大體上實現(xiàn)了下面的功能:
1、讀取lidar和位姿傳感器的數(shù)據(jù)
2、通過時間戳匹配lidar每幀里面的每個點和位姿傳感器的坐標(biāo)變換
3、通過上面的變換矩陣將 利用位姿信息將lidar的每幀拼接成點云
4、每個點和它最近鄰點的距離總和求出,在優(yōu)化中就是不斷的迭代找到坐標(biāo)變換使這個距離最小
功能包算法的整體思想:
1、為每幀lidar的每個點通過時間戳去匹配一個位姿數(shù)據(jù),并通過插值的方式得到更準(zhǔn)確的位姿值,每個點的時間偏移也是優(yōu)化因素之一
2、利用NLopt的庫的非線性優(yōu)化方法
3、目標(biāo)函數(shù):讓拼接后的點云的每個點的最近鄰點最小
4、優(yōu)化向量:x、y 、z、roll、pitch、yaw、time_offset 總體來說就是不斷的迭代,找到一個合適的優(yōu)化向量(也就是lidar到里程計的坐標(biāo)變換)使得拼在一起的點云每個點的最近鄰點距離最小。這個功能包的優(yōu)點就是可以離線計算,錄一個rosbag,然后跑一下就可以求的外參 它只會讀取一個bag ,所以 lidar和位姿都要在里面
最終的結(jié)果: 在運行的時候,功能包會輸出當(dāng)前的估計變化 優(yōu)化結(jié)束的時候 坐標(biāo)變換的參數(shù)會打印在終端上 也會在路徑下面保存一個txt文件和ply文件。
可以查看ply文件,就是拼接后的點云和場景是否一致
編譯
從git上下載后需要 下載 nlopt的庫
sudo apt-get install libnlopt-dev
然后編譯還是會報錯
Could not find a package configuration file provided by "NLOPT"
解決辦法 將 lidar_align 文件夾下的 NLOPTConfig.cmake 復(fù)制到 你ROS工作空間的src路徑下面
相關(guān)參數(shù)
這些都沒有在配置文件里面 想要修改的話需要改源碼然后再編譯
在每個類里面有個 Config的結(jié)構(gòu)體, 初始化了相關(guān)參數(shù)
和lidar相關(guān)的在 Scan類里面設(shè)置的
class Scan { public: struct Config { float min_point_distance = 80;//lidar數(shù)據(jù)里面點的距離的最小值 大于此值被使用 0.0; float max_point_distance = 200;// lidar數(shù)據(jù)里面點的距離的最大值 小于此值被使用 默認(rèn)100.0; float keep_points_ratio = 0.01;// 用于優(yōu)化點的比例 (此值越大會極大增加運行時間) 默認(rèn)0.01 float min_return_intensity = -1.0;//lidar數(shù)據(jù)里面點的強度的最小值 大于此值被使用 默認(rèn) -1.0 bool estimate_point_times = false;//是否估計lidar每個點的時間 用下面那倆變量 默認(rèn) false bool clockwise_lidar = false;//lidar旋轉(zhuǎn)的方向 順時針還是逆時針 默認(rèn) false(逆時針) bool motion_compensation = true;//是不是需要運行補償 默認(rèn) true float lidar_rpm = 600.0;// lidar的轉(zhuǎn)速 僅用于 estimate_point_times 默認(rèn) 600 };
輸入輸出的相關(guān)參數(shù)在launch里面設(shè)置的
<param name="input_bag_path" value="$(arg bag_file)" /> <param name="input_csv_path" value="$(arg csv_file)" /> <param name="output_pointcloud_path" value="$(find lidar_align)/results/$(anon lidar_points).ply" /> <param name="output_calibration_path" value="$(find lidar_align)/results/$(anon calibration).txt" /> <param name="transforms_from_csv" value="$(arg transforms_from_csv)"/>
use_n_scans 執(zhí)行優(yōu)化開始的 lidar的幀數(shù) 默認(rèn) 2147483647 input_bag_path ros bag 的路徑
transforms_from_csv 是否通過 csv文件 讀取位置 默認(rèn) false input_csv_path csv路徑 output_pointcloud_path ply文件輸出路徑 output_calibration_path 校準(zhǔn)結(jié)果輸出路徑
校正相關(guān)參數(shù) 在Aligner類里面設(shè)置的
class Aligner { public: struct Config { bool local = false;//執(zhí)行局部優(yōu)化還是全局優(yōu)化 初始化為false, 執(zhí)行全局的優(yōu)化,并且結(jié)果用于初始估計 然后執(zhí)行局部優(yōu)化 std::vector<double> inital_guess{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};//校準(zhǔn)的初始估計 僅用于運行 local 模式 double max_time_offset = 0.1;//最大的時間對準(zhǔn)偏差 x[6]的上下浮動范圍 double angular_range = 0.5; // 在初始估計位置的搜索范圍 (弧度) 角度偏移上下限 +-0.5弧度 double translation_range = 1.0;//在初始估計位置的搜索范圍 (位置) 位置偏移上下限 +-1米 double max_evals = 200;// 最大的評估次數(shù) double xtol = 0.0001;//x的容差 int knn_batch_size = 1000;//點的偏移數(shù)量 被用于 尋找最近鄰點時 int knn_k = 1;//尋找最近鄰點的個數(shù) , 程序了 自加1了 因為在一個點云里面找其中一個點的最近鄰一個是自己一個才是真的最近鄰 float local_knn_max_dist = 0.1;//局部優(yōu)化的時候 最近鄰點的最大距離 float global_knn_max_dist = 1.0;//全局優(yōu)化的時候 最近鄰點的最大距離 bool time_cal = true; //是否執(zhí)行時間補充計算 就是在找每個點的位姿時根據(jù)時間對里程計插值 std::string output_pointcloud_path = ""; //點云文件保存路徑 std::string output_calibration_path = ""; // 校準(zhǔn)信息文件保存路徑 };
代碼文件結(jié)構(gòu)
頭文件四個:
- aligner.h : Lidar 和 Odom 校正(外參計算)時用到的類
- **loader.h :**從ROS的Bag或CSV格式載入數(shù)據(jù)的相關(guān)函數(shù)
- **sensor.h :**主要包括Odom以及LIdar相關(guān)接口
- transform.h : 一些SO3變化的計算及轉(zhuǎn)換,在插值、優(yōu)化時使用
cpp文件四個:
- aligner.cpp : Lidar 和 Odom 校正(外參計算) 時 的實現(xiàn)函數(shù)
- lidar_align_node.cpp : main的啟動地方 實例各個類
- loader.cpp : 加載數(shù)據(jù)
- sensors.cpp : 處理lidar和里程計的數(shù)據(jù)
核心代碼
前面的數(shù)據(jù)讀取處理啥的就不整了,下面分析下主要的代碼部分
在面函數(shù)里面 調(diào)用了 下面的 函數(shù) 前面就是數(shù)據(jù)讀取和處理,從這個函數(shù)里面開啟了校準(zhǔn)的過程
aligner.lidarOdomTransform(&lidar, &odom);//執(zhí)行校準(zhǔn)
這個函數(shù)里面
/*根據(jù) 是否使用 時間補償 來確定 優(yōu)化參數(shù)的 個數(shù)*/ size_t num_params = 6;//初始化優(yōu)化參數(shù)的個數(shù) if (config_.time_cal) { ++num_params;//使用時間補償計算 優(yōu)化參數(shù)個數(shù)為7 }
根據(jù) 是否使用 時間補償 來確定 優(yōu)化參數(shù)的 個數(shù)
/*根據(jù)配置參數(shù)(是否執(zhí)行全局優(yōu)化) 執(zhí)行 初始角度的賦值 ,如果執(zhí)行全局優(yōu)化則先進(jìn)行一個 估計,得到角度 否則 直接附設(shè)置的初始值 */ if (!config_.local) { //默認(rèn) 執(zhí)行 全局優(yōu)化 并用于初始估計 //打印 正在 執(zhí)行 全局 優(yōu)化 ROS_INFO("Performing Global Optimization... "); std::vector<double> lb = {-M_PI, -M_PI, -M_PI};//設(shè)置下限 std::vector<double> ub = {M_PI, M_PI, M_PI};//設(shè)置上限 std::vector<double> global_x(3, 0.0);//設(shè)置 執(zhí)行全局優(yōu)化的 x參數(shù)向量 設(shè)置為3 個,初始值為0.0 optimize(lb, ub, &opt_data, &global_x);//執(zhí)行優(yōu)化(全局優(yōu)化) 只求 角度的 優(yōu)化結(jié)果 config_.local = true;//將全局優(yōu)化的flag關(guān)閉 下次則執(zhí)行 局部優(yōu)化 //賦值 角度 優(yōu)化 的結(jié)果 x[3] = global_x[0]; x[4] = global_x[1]; x[5] = global_x[2]; } else {//local 為 true的 話 則 為 默認(rèn)的 初始值 x = config_.inital_guess;//初始值 全為0 局部優(yōu)化則初始認(rèn)為 lidar和imu的坐標(biāo)軸一致對齊 其它情況最好按實際情況粗略配置 初始值 }
根據(jù)配置參數(shù)(是否執(zhí)行全局優(yōu)化) 執(zhí)行 初始角度的賦值 ,如果執(zhí)行全局優(yōu)化則先進(jìn)行一個 估計,得到角度 否則 直接附設(shè)置的初始值
//設(shè)置 參數(shù)向量 的下限 ,默認(rèn)參數(shù) -1,-1,-1,-0.5,-0.5,-0.5 std::vector<double> lb = { -config_.translation_range, -config_.translation_range, -config_.translation_range, -config_.angular_range, -config_.angular_range, -config_.angular_range}; //設(shè)置 參數(shù)向量 的上限 默認(rèn)參數(shù) 1,1,1,0.5,0.5,0.5 std::vector<double> ub = { config_.translation_range, config_.translation_range, config_.translation_range, config_.angular_range, config_.angular_range, config_.angular_range}; //將 全局優(yōu)化得到的 角度的 初始估計值 疊加到 上下限中 也就是說 初始估計 roll 為0.1弧度,則 優(yōu)化的范圍是 0.1 +- 0.5 弧度 for (size_t i = 0; i < 6; ++i) { lb[i] += x[i];//疊加下限 ub[i] += x[i];//疊加上限 } //將時間補償?shù)?優(yōu)化 范圍 加 到 上下限中 if (config_.time_cal) { ub.push_back(config_.max_time_offset); lb.push_back(-config_.max_time_offset); }
設(shè)置 優(yōu)化因素的 上限和下限
/*執(zhí)行局部優(yōu)化*/ optimize(lb, ub, &opt_data, &x);
執(zhí)行局部優(yōu)化
下面看下執(zhí)行局部優(yōu)化的主要內(nèi)容
也就是 optimize()函數(shù) 里面就是用的nlopt的方法進(jìn)行的非線性優(yōu)化
if (config_.local) {//看是執(zhí)行全局優(yōu)化還是局部優(yōu)化 用的算法不同 x的個數(shù)在前面配置的也不同, 全局優(yōu)化為3個,只求角度 局部優(yōu)化為7個 opt = nlopt::opt(nlopt::LN_BOBYQA, x->size()); } else { opt = nlopt::opt(nlopt::GN_DIRECT_L, x->size());//選用 全局優(yōu)化函數(shù) 不需要求導(dǎo)的 算法 DIRECT_L x的個數(shù)在前面配置的是三個 }
根據(jù)執(zhí)行全局優(yōu)化還是局部優(yōu)化 選擇不同的算法 并生成了 nlopt 對象實例
//設(shè)置下限 opt.set_lower_bounds(lb); //設(shè)置上限 opt.set_upper_bounds(ub); opt.set_maxeval(config_.max_evals);//設(shè)置最大估計次數(shù) opt.set_xtol_abs(config_.xtol);//設(shè)置x容差停止值 opt.set_min_objective(LidarOdomMinimizer, opt_data);//設(shè)置目標(biāo)函數(shù) LidarOdomMinimizer函數(shù)名 opt_data外部數(shù)據(jù)
nlopt的相關(guān)設(shè)置 其中 LidarOdomMinimizer 是目標(biāo)函數(shù)需要重點看的
目標(biāo)函數(shù)里面
//通過 kdtree 的 方法 求的 每個點的 最近鄰距離 總和 double error = d->aligner->lidarOdomKNNError(*(d->lidar));
在這里 通過 kdtree 的 方法 求的 每個點的 最近鄰距離 總和
然后不斷的迭代讓這個距離最小,得到的優(yōu)化向量就是 校準(zhǔn)信息了
主要的核心就是這些了,當(dāng)然這個里面的小細(xì)節(jié)太多了,有問題的童鞋可以私信我。
結(jié)果
最后上一個測試時的結(jié)果把
Active Transformation Vector (x,y,z,rx,ry,rz) from the Pose Sensor Frame to the Lidar Frame: [0.770924, -0.25834, 0.105557, 1.67974, -1.88141, -1.40085]
Active Transformation Matrix from the Pose Sensor Frame to the Lidar Frame: -0.300413 -0.623731 -0.721603 0.770924 -0.870125 -0.130671 0.475193 -0.25834 -0.390685 0.770639 -0.503468 0.105557 0 0 0 1
Active Translation Vector (x,y,z) from the Pose Sensor Frame to the Lidar Frame: [0.770924, -0.25834, 0.105557]
Active Hamiltonen Quaternion (w,x,y,z) the Pose Sensor Frame to the Lidar Frame: [-0.127913, -0.577435, 0.646763, 0.481564]
Time offset that must be added to lidar timestamps in seconds: -0.00263505
ROS Static TF Publisher:
在txt里面功能包輸出的結(jié)果就是這樣的。
以上就是C語言lidar_align雷達(dá)里程計校準(zhǔn)功能詳解的詳細(xì)內(nèi)容,更多關(guān)于C語言lidar_align雷達(dá)里程計校準(zhǔn)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++中關(guān)于多態(tài)實現(xiàn)和使用方法
這篇文章主要介紹了C++中關(guān)于多態(tài)實現(xiàn)和使用方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07C/C++ 動態(tài)數(shù)組的創(chuàng)建的實例詳解
這篇文章主要介紹了C/C++ 動態(tài)數(shù)組的創(chuàng)建的實例詳解的相關(guān)資料,希望通過本文能幫助到大家,讓大家掌握這樣的功能,需要的朋友可以參考下2017-10-10C語言內(nèi)存的動態(tài)分配比較malloc和realloc的區(qū)別
這篇文章主要介紹了C語言內(nèi)存的動態(tài)分配比較malloc和realloc的區(qū)別,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是本文的詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07