[自動駕駛-傳感器融合] 多激光雷達的外參標定

文章目錄

  • 引言
  • 外參標定原理
  • ICP匹配示例
  • 參考文獻

引言

多激光雷達系統通常用于自動駕駛或機器人,每個雷達的位置和姿態不同,需要將它們的數據統一到同一個坐標系下。多激光雷達外參標定的核心目標是通過計算不同雷達坐標系之間的剛性變換關系(旋轉矩陣 R R R 和平移向量 t t t),將多個雷達的點云數據統一到同一坐標系下。具體需求包括:

  • 數據融合:消除多雷達間的位姿差異,生成全局一致的點云。
  • 減少累積誤差:避免多傳感器數據因坐標系不統一導致的定位與建圖誤差。
  • 提升感知精度:為自動駕駛或機器人提供更可靠的環境感知能力。

外參標定原理

外參標定本質是求解兩個坐標系之間的最優變換參數,設雷達A的坐標系為源坐標系,雷達B為目標坐標系。對于同一物理點 P P P,其在兩個坐標系下的坐標分別為 P A P_A PA? P B P_B PB?,滿足:
P B = R ? P A + t P_B = R \cdot P_A + t PB?=R?PA?+t
其中 R ∈ S O ( 3 ) R \in SO(3) RSO(3) 為旋轉矩陣, t ∈ R 3 t \in \mathbb{R}^3 tR3 為平移向量。

一般來說,多激光雷達的主流的外參標定方法有手動標定調參法、自動標定法、基于車輛運動軌跡的標定法。

  • 手動標定法:
    使用已知幾何形狀的標定物(如立方體、棋盤格),通過人工測量或標定物特征點計算外參。優點:精度高,適合實驗室環境。 缺點:依賴標定物,效率低。
    基本步驟是:
    1. 將標定物放置在雷達共同視場內;
    2. 提取標定物的角點或平面;
    3. 基于最小二乘法求解 R R R t t t
  • 自動標定法:
    基本方式是利用環境中的穩定特征(如地面、建筑物邊緣)自動對齊點云,優點在于無需標定物、適應性較強,但易受動態物體干擾,且要求場景特征豐富。
    多采用 ICP(Iterative Closest PointNDT(Normal Distributions Transform) 求解 R R R t t t
    ICP 通過迭代最近點匹配,最小化點對距離:
    min ? R , t ∑ i = 1 N ∥ ( R ? P A , i + t ) ? P B , i ∥ 2 \min_{R,t} \sum_{i=1}^N \| (R \cdot P_{A,i} + t) - P_{B,i} \|^2 R,tmin?i=1N?(R?PA,i?+t)?PB,i?2
    NDT(Normal Distributions Transform) 則將點云轉換為概率密度函數,通過優化概率分布相似性求解變換。

激光雷達外參標定

  • 基于運動軌跡的標定:
    利用雷達在運動過程中采集的數據,通過里程計或SLAM生成軌跡約束。一般是聯合優化多個雷達的外參和運動軌跡,再使用因子圖優化(Factor Graph Optimization)等方式進行優化,求解雷達之間的運動軌跡所匹配的 R R R t t t
    這種方式適合動態環境卻可在線標定,但計算復雜度高,需高精度里程計支持。

注:實際工程落地時,可不用追求高難度的優化算法,可根據具體場景選擇合適的方式來進行標定

ICP匹配示例

在這篇文章中,我們可結合手動標定法與自動標定法聯調方式來確保標定精度,簡單操作是假設我們通過手動標定方式初步得到基準激光雷達與附屬激光雷達的相對坐標變換參數 R 0 R_0 R0? t 0 t_0 t0? ,在此基礎上采用 G I C P GICP GICP I C P ICP ICP等基礎算法進行多次配準提高標定精度,得到最終的 R R R t t t

這里展示一個 I C P ICP ICP的使用示例:
按" 空格 "即可觀察迭代匹配的效果。

#include <pcl/console/time.h>  
#include <pcl/io/ply_io.h>
#include <pcl/point_types.h>
#include <pcl/registration/icp.h>
#include <pcl/visualization/pcl_visualizer.h>#include <string>typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloudT;bool next_iteration = false;void print4x4Matrix(const Eigen::Matrix4d& matrix) {printf("Rotation matrix :\n");printf("    | %6.3f %6.3f %6.3f | \n", matrix(0, 0), matrix(0, 1),matrix(0, 2));printf("R = | %6.3f %6.3f %6.3f | \n", matrix(1, 0), matrix(1, 1),matrix(1, 2));printf("    | %6.3f %6.3f %6.3f | \n", matrix(2, 0), matrix(2, 1),matrix(2, 2));printf("Translation vector :\n");printf("t = < %6.3f, %6.3f, %6.3f >\n\n", matrix(0, 3), matrix(1, 3),matrix(2, 3));
}
/*** 此函數是查看器的回調。 當查看器窗口位于頂部時,只要按任意鍵,就會調用此函數。* 如果碰到“空格”; 將布爾值設置為true。* @param event* @param nothing*/
void keyboardEventOccurred(const pcl::visualization::KeyboardEvent& event,void* nothing) {if (event.getKeySym() == "space" && event.keyDown()) next_iteration = true;
}int main(int argc, char* argv[]) {// The point clouds we will be usingPointCloudT::Ptr cloud_in(new PointCloudT);   // Original point cloudPointCloudT::Ptr cloud_tr(new PointCloudT);   // Transformed point cloudPointCloudT::Ptr cloud_icp(new PointCloudT);  // ICP output point cloud//    我們檢查程序的參數,設置初始ICP迭代的次數,然后嘗試加載PLY文件。// Checking program argumentsif (argc < 2) {printf("Usage :\n");printf("\t\t%s file.ply number_of_ICP_iterations\n", argv[0]);PCL_ERROR("Provide one ply file.\n");return (-1);}int iterations = 1;  // Default number of ICP iterationsif (argc > 2) {// If the user passed the number of iteration as an argumentiterations = atoi(argv[2]);if (iterations < 1) {PCL_ERROR("Number of initial iterations must be >= 1\n");return (-1);}}pcl::console::TicToc time;time.tic();if (pcl::io::loadPLYFile(argv[1], *cloud_in) < 0) {PCL_ERROR("Error loading cloud %s.\n", argv[1]);return (-1);}std::cout << "\nLoaded file " << argv[1] << " (" << cloud_in->size()<< " points) in " << time.toc() << " ms\n"<< std::endl;// 我們使用剛性矩陣變換來變換原始點云。// cloud_in包含原始點云。// cloud_tr和cloud_icp包含平移/旋轉的點云。// cloud_tr是我們將用于顯示的備份(綠點云)。// Defining a rotation matrix and translation vectorEigen::Matrix4d transformation_matrix = Eigen::Matrix4d::Identity();// A rotation matrix (see https://en.wikipedia.org/wiki/Rotation_matrix)double theta = M_PI / 8;  // The angle of rotation in radianstransformation_matrix(0, 0) = std::cos(theta);transformation_matrix(0, 1) = -sin(theta);transformation_matrix(1, 0) = sin(theta);transformation_matrix(1, 1) = std::cos(theta);// A translation on Z axis (0.4 meters)transformation_matrix(2, 3) = 0.4;// Display in terminal the transformation matrixstd::cout << "Applying this rigid transformation to: cloud_in -> cloud_icp"<< std::endl;print4x4Matrix(transformation_matrix);// Executing the transformationpcl::transformPointCloud(*cloud_in, *cloud_icp, transformation_matrix);*cloud_tr = *cloud_icp;  // We backup cloud_icp into cloud_tr for later use// 這是ICP對象的創建。 我們設置ICP算法的參數。// setMaximumIterations(iterations)設置要執行的初始迭代次數(默認值為1)。// 然后,我們將點云轉換為cloud_icp。// 第一次對齊后,我們將在下一次使用該ICP對象時(當用戶按下“空格”時)將ICP最大迭代次數設置為1。// The Iterative Closest Point algorithmtime.tic();pcl::IterativeClosestPoint<PointT, PointT> icp;icp.setMaximumIterations(iterations);icp.setInputSource(cloud_icp);icp.setInputTarget(cloud_in);icp.align(*cloud_icp);icp.setMaximumIterations(1);  // We set this variable to 1 for the next time// we will call .align () functionstd::cout << "Applied " << iterations << " ICP iteration(s) in " << time.toc()<< " ms" << std::endl;// 檢查ICP算法是否收斂; 否則退出程序。// 如果返回true,我們將轉換矩陣存儲在4x4矩陣中,然后打印剛性矩陣轉換。if (icp.hasConverged()) {// std::cout << "\nICP has converged, score is " << icp.getFitnessScore()//           << std::endl;// std::cout << "\nICP transformation " << iterations//           << " : cloud_icp -> cloud_in" << std::endl;transformation_matrix = icp.getFinalTransformation().cast<double>();print4x4Matrix(transformation_matrix);} else {PCL_ERROR("\nICP has not converged.\n");return (-1);}// Visualizationpcl::visualization::PCLVisualizer viewer("ICP demo");// Create two vertically separated viewportsint v1(0);int v2(1);viewer.createViewPort(0.0, 0.0, 0.5, 1.0, v1);viewer.createViewPort(0.5, 0.0, 1.0, 1.0, v2);// The color we will be usingfloat bckgr_gray_level = 0.0;  // Blackfloat txt_gray_lvl = 1.0 - bckgr_gray_level;// Original point cloud is whitepcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_in_color_h(cloud_in, (int)255 * txt_gray_lvl, (int)255 * txt_gray_lvl,(int)255 * txt_gray_lvl);viewer.addPointCloud(cloud_in, cloud_in_color_h, "cloud_in_v1", v1);viewer.addPointCloud(cloud_in, cloud_in_color_h, "cloud_in_v2", v2);// Transformed point cloud is greenpcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_tr_color_h(cloud_tr, 20, 180, 20);viewer.addPointCloud(cloud_tr, cloud_tr_color_h, "cloud_tr_v1", v1);// ICP aligned point cloud is redpcl::visualization::PointCloudColorHandlerCustom<PointT> cloud_icp_color_h(cloud_icp, 180, 20, 20);viewer.addPointCloud(cloud_icp, cloud_icp_color_h, "cloud_icp_v2", v2);// Adding text descriptions in each viewportviewer.addText("White: Original point cloud\nGreen: Matrix transformed point cloud", 10,15, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl, "icp_info_1", v1);viewer.addText("White: Original point cloud\nRed: ICP aligned point cloud",10, 15, 16, txt_gray_lvl, txt_gray_lvl, txt_gray_lvl,"icp_info_2", v2);std::stringstream ss;ss << iterations;std::string iterations_cnt = "ICP iterations = " + ss.str();viewer.addText(iterations_cnt, 10, 60, 16, txt_gray_lvl, txt_gray_lvl,txt_gray_lvl, "iterations_cnt", v2);// Set background colorviewer.setBackgroundColor(bckgr_gray_level, bckgr_gray_level,bckgr_gray_level, v1);viewer.setBackgroundColor(bckgr_gray_level, bckgr_gray_level,bckgr_gray_level, v2);// Set camera position and orientationviewer.setCameraPosition(-3.68332, 2.94092, 5.71266, 0.289847, 0.921947,-0.256907, 0);viewer.setSize(1280, 1024);  // Visualiser window size// Register keyboard callback :viewer.registerKeyboardCallback(&keyboardEventOccurred, (void*)NULL);// Display the visualiserwhile (!viewer.wasStopped()) {viewer.spinOnce();// The user pressed "space" :if (next_iteration) {// The Iterative Closest Point algorithmtime.tic();// 如果用戶按下鍵盤上的任意鍵,則會調用keyboardEventOccurred函數。// 此功能檢查鍵是否為“空格”。// 如果是,則全局布爾值next_iteration設置為true,從而允許查看器循環輸入代碼的下一部分:調用ICP對象以進行對齊。// 記住,我們已經配置了該對象輸入/輸出云,并且之前通過setMaximumIterations將最大迭代次數設置為1。icp.align(*cloud_icp);std::cout << "Applied 1 ICP iteration in " << time.toc() << " ms"<< std::endl;// 和以前一樣,我們檢查ICP是否收斂,如果不收斂,則退出程序。if (icp.hasConverged()) {// printf(“ 033 [11A”);// 在終端增加11行以覆蓋顯示的最后一個矩陣是一個小技巧。// 簡而言之,它允許替換文本而不是編寫新行; 使輸出更具可讀性。// 我們增加迭代次數以更新可視化器中的文本值。printf("\033[11A");  // Go up 11 lines in terminal output.printf("\nICP has converged, score is %+.0e\n", icp.getFitnessScore());// 這意味著,如果您已經完成了10次迭代,則此函數返回矩陣以將點云從迭代10轉換為11。std::cout << "\nICP transformation " << ++iterations<< " : cloud_icp -> cloud_in" << std::endl;// 函數getFinalTransformation()返回在迭代過程中完成的剛性矩陣轉換(此處為1次迭代)。transformation_matrix *=icp.getFinalTransformation().cast<double>();  // WARNING /!\ This is not accurate! For// "educational" purpose only!print4x4Matrix(transformation_matrix);  // Print the transformation between// original pose and current posess.str("");ss << iterations;std::string iterations_cnt = "ICP iterations = " + ss.str();viewer.updateText(iterations_cnt, 10, 60, 16, txt_gray_lvl,txt_gray_lvl, txt_gray_lvl, "iterations_cnt");viewer.updatePointCloud(cloud_icp, cloud_icp_color_h, "cloud_icp_v2");} else {PCL_ERROR("\nICP has not converged.\n");return (-1);}//這不是我們想要的。//如果我們將最后一個矩陣與新矩陣相乘,那么結果就是從開始到當前迭代的轉換矩陣。}next_iteration = false;}return (0);
}

ICP效果示例

參考文獻

[1] 姜聿于——自動駕駛感知【激光雷達】:一、標定

[2] Segal A , Hhnel D , Thrun S .Generalized-ICP[J]. 2009.DOI:10.15607/RSS.2009.V.021.

[3] Kulmer D , Tahiraj I , Chumak A ,et al.Multi-LiCa: A Motion and Targetless Multi LiDAR-to-LiDAR Calibration Framework[J].IEEE, 2025.DOI:10.1109/MFI62651.2024.10705773.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71387.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71387.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71387.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Blazor-路由模板(下)

路由約束 類型約束 我們這里使用{id:int}限制路由&#xff0c;id為int類型&#xff0c;并且路由參數 id 對應的 Id 屬性也必須是 int 類型。我們試試能否正常訪問 page "/demoPage/{id:int}" <h3>demoPage</h3> <h2>路由參數Id&#xff1a;Id&l…

多線程-JUC源碼

簡介 JUC的核心是AQS&#xff0c;大部分鎖都是基于AQS擴展出來的&#xff0c;這里先結合可重入鎖和AQS&#xff0c;做一個講解&#xff0c;其它的鎖的實現方式也幾乎類似 ReentrantLock和AQS AQS的基本結構 AQS&#xff0c;AbstractQueuedSynchronizer&#xff0c;抽象隊列…

通過多線程獲取RV1126的AAC碼流

目錄 一RV1126多線程獲取音頻編碼AAC碼流的流程 1.1AI模塊的初始化并使能 1.2AENC模塊的初始化 ???????1.3綁定AI模塊和AENC模塊 ???????1.4多線程獲取每一幀AAC碼流 ???????1.5每個AAC碼流添加ADTSHeader頭部 ???????1.6寫入具體每一幀AAC的…

JVM常用概念之對象初始化的成本

在JVM常用概念之新對象實例化博客中我講到了對象的實例化&#xff0c;主要包含分配&#xff08;TLAB&#xff09;、系統初始化、用戶初始化&#xff0c;而我在JVM常用概念之線程本地分配緩沖區&#xff08;ThreadLocal Allocation Buffer&#xff0c;TLAB&#xff09;博客中也講…

java后端開發day27--常用API(二)正則表達式爬蟲

&#xff08;以下內容全部來自上述課程&#xff09; 1.正則表達式&#xff08;regex&#xff09; 可以校驗字符串是否滿足一定的規則&#xff0c;并用來校驗數據格式的合法性。 1.作用 校驗字符串是否滿足規則在一段文本中查找滿足要求的內容 2.內容定義 ps&#xff1a;一…

AI---DevOps常備工具(?AI-Integrated DevOps Essential Tools)

AI---DevOps常備工具 技術領域正在迅速發展&#xff0c;隨著我們步入 2025 年&#xff0c;有一點是明確的&#xff1a;人工智能&#xff08;AI&#xff09;不再只是一個流行詞&#xff0c;它是每個 DevOps 工程師都需要掌握的工具。隨著云環境的復雜性增加、對更快部署的需求以…

Pytorch中的主要函數

目錄 一、torch.manual_seed(seed)二、torch.cuda.manual_seed(seed)三、torch.rand(*size, outNone, dtypeNone, layouttorch.strided, deviceNone, requires_gradFalse)四、給大家寫一個常用的自動選擇電腦cuda 或者cpu 的小技巧五、torch.version.cuda&#xff1b;torch.bac…

Spring Boot中對接Twilio以實現發送驗證碼和驗證短信碼

Twilio介紹 Twilio是一家提供云通信服務的公司&#xff0c;旨在幫助開發者和企業通過簡單的API實現各種通信功能。以下是Twilio的一些主要特點和服務介紹&#xff1a; 核心功能 短信服務&#xff08;SMS&#xff09;&#xff1a;允許用戶通過API發送和接收短信&#xff0c;支…

VSCode詳細安裝步驟,適用于 Windows/macOS/Linux 系統

以下是 Visual Studio Code (VSCode) 的詳細安裝步驟&#xff0c;適用于 Windows/macOS/Linux 系統&#xff1a; VSCode 的詳細安裝步驟 一、Windows 系統安裝1. 下載安裝包2. 運行安裝程序3. 驗證安裝 二、macOS 系統安裝1. 方法一&#xff1a;官網下載安裝包2. 方法二&#x…

基于PyTorch的深度學習3——基于autograd的反向傳播

反向傳播&#xff0c;可以理解為函數關系的反向傳播。

設備管理系統功能與.NET+VUE(IVIEW)技術實現

在現代工業和商業環境中&#xff0c;設備管理系統&#xff08;Equipment Management System&#xff0c;簡稱EMS&#xff09;是確保設備高效運行和維護的關鍵工具。本文采用多租戶設計的設備管理系統&#xff0c;基于.NET后端和VUE前端&#xff08;使用IVIEW UI框架&#xff09…

PHP之特性

在你有別的編程語言的基礎下&#xff0c;你想學習PHP&#xff0c;可能要了解的PHP特有的東西。 定界符 使用<<<TT(可以是任意字符&#xff0c;但是不可以在別的地方使用過)和TT&#xff0c;會解析html格式和變量&#xff0c;如果在<<<后面加上單引號就會不…

9-Agent大模型中工作流的使用方法分析

目錄 關鍵詞 摘要 速覽 配置插件進行新聞內容查找的工作流設置 自動化調用用戶輸入變量的插件配置教程 配置大模型以整理并簡要輸出新聞內容 新聞內容總結功能調試與優化 搭建與發布工作流優化布局的流程詳解 創建和配置智能體工作流程 調試頁面與工作流配置演示 思…

記一次:泛微OA集成Mybatis后 insert/update執行成功,但未真正插入或修改數據

背景&#xff1a;通過Mybatis插入數據或更新數據&#xff0c;顯示插入/更新成功&#xff0c;查詢數據庫&#xff0c;發現并未插入成功、數據也沒更新成功。下面是Mapper文件 public interface TestOrmMapper {int insertByTest(Param("requestId") Integer requestI…

使用 Spring Boot 實現前后端分離的海康威視 SDK 視頻監控

使用 Spring Boot 實現前后端分離的海康威視 SDK 視頻監控系統&#xff0c;可以分為以下幾個步驟&#xff1a; 1. 系統架構設計 前端&#xff1a;使用 Vue.js、React 或 Angular 等前端框架實現用戶界面。后端&#xff1a;使用 Spring Boot 提供 RESTful API&#xff0c;負責與…

【大模型系列篇】國產開源大模型DeepSeek-V3技術報告解析

DeepSeek-V3技術報告 目錄 DeepSeek-V3技術報告 1. 摘要 2. 引言 3. DeepSeek V3 架構 3.1 基礎架構 3.1.1. 多頭潛在注意力 3.1.2. DeepSeekMoE和無輔助損失的負載均衡 3.2 多令牌預測 4. 基礎設施 4.1 計算集群 4.2 訓練框架 4.2.1. DualPipe算法與計算通信協同優…

負載均衡 - 一致性hash算法

構建場景 假如我們有三臺緩存服務器編號node0、node1、node2&#xff0c;現在有3000萬個key&#xff0c;希望可以將這些個key均勻的緩存到三臺機器上&#xff0c;你會想到什么方案呢&#xff1f; 我們可能首先想到的方案&#xff0c;是取模算法hash&#xff08;key&#xff0…

pdfplumber 解析 PDF 表格的原理

&#x1f4cc; pdfplumber 解析 PDF 表格的原理 pdfplumber 處理表格的原理是基于幾何分析&#xff08;geometric analysis&#xff09;&#xff0c;它通過分析 PDF 頁面中的線條、單元格間距和文本分布&#xff0c;提取表格數據。它主要利用 垂直線&#xff08;vertical line…

洛谷P1334

題目如下 思路&#xff1a; 每次選擇最短的兩塊木板進行合并&#xff0c;直到只剩下一塊木板。使用最小堆&#xff08;優先隊列&#xff09;來實現這一過程。使用最小堆&#xff1a; 將所有木板的長度放入最小堆&#xff08;優先隊列&#xff09; 每次從堆中取出兩塊最短的木…

JVM(Java Virtual Machine,Java 虛擬機)的作用

JVM&#xff08;Java Virtual Machine&#xff0c;Java 虛擬機&#xff09;的作用至關重要&#xff0c;它是 Java 語言“一次編寫&#xff0c;到處運行”&#xff08;Write Once, Run Anywhere&#xff0c;WORA&#xff09;特性的基石&#xff0c;也是 Java 平臺的核心組成部分…