[自动驾驶-传感器融合] 多激光雷达的外参标定
文章目录
- 引言
- 外参标定原理
- 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) R∈SO(3) 为旋转矩阵, t ∈ R 3 t \in \mathbb{R}^3 t∈R3 为平移向量。
一般来说,多激光雷达的主流的外参标定方法有手动标定调参法、自动标定法、基于车辆运动轨迹的标定法。
- 手动标定法:
使用已知几何形状的标定物(如立方体、棋盘格),通过人工测量或标定物特征点计算外参。优点:精度高,适合实验室环境。 缺点:依赖标定物,效率低。
基本步骤是:- 将标定物放置在雷达共同视场内;
- 提取标定物的角点或平面;
- 基于最小二乘法求解 R R R 和 t t t;
- 自动标定法:
基本方式是利用环境中的稳定特征(如地面、建筑物边缘)自动对齐点云,优点在于无需标定物、适应性较强,但易受动态物体干扰,且要求场景特征丰富。
多采用 ICP(Iterative Closest Point、NDT(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,tmini=1∑N∥(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);
}

参考文献
[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.
…
相关文章:
[自动驾驶-传感器融合] 多激光雷达的外参标定
文章目录 引言外参标定原理ICP匹配示例参考文献 引言 多激光雷达系统通常用于自动驾驶或机器人,每个雷达的位置和姿态不同,需要将它们的数据统一到同一个坐标系下。多激光雷达外参标定的核心目标是通过计算不同雷达坐标系之间的刚性变换关系(…...
C++ 学习(八)(模板,可变参数模板,模板专业化(完整模板专业化,部分模板专业化),类型 Traits,SFINAE(替换失败不是错误),)
C 模板 C 中的模板是一项强大的功能,允许您编写通用代码,这意味着您可以编写可以处理不同数据类型的单个函数或类。这意味着您无需为要使用的每种数据类型编写单独的函数或类。 模板函数 要创建模板函数,请使用 关键字,后跟类型…...
AI---DevOps常备工具(AI-Integrated DevOps Essential Tools)
AI---DevOps常备工具 技术领域正在迅速发展,随着我们步入 2025 年,有一点是明确的:人工智能(AI)不再只是一个流行词,它是每个 DevOps 工程师都需要掌握的工具。随着云环境的复杂性增加、对更快部署的需求以…...
题目梳理2025[长期更新]
题目梳理 组合类题目(2025年3月5日) 组合总数1,组合总数2,组合总数3 -> 递归回溯的思想 组合总数4 -> 爬楼的思想,动态规划,确定递归边界,确定递归入口,最后一步怎么走的思想...
Maven 中 SNAPSHOT 版本与 RELEASE 版本的区别
Maven 仓库分为 Snapshot 快照仓库和 Release 发行仓库两种类型的仓库。Snapshot 快照仓库用于保存 SNAPSHOT 版本,Release 发行仓库用于保存 RELEASE 版本。 SNAPSHOT 是一种特殊的版本标识,主要用于表示项目的不稳定、正在开发中的版本,而…...
JavaScript 知识点整理
1. 什么是AST?它在前端有哪些应用场景? AST Abstract Syntax Tree抽象语法树,用于表达源码的树形结构 应用: Babel:一个广泛使用的 JS 编译器,将ES6 或 JSX 等现代语法转换为兼容性较好的 ES5 代码。Esl…...
迷你世界脚本出生点接口:Spawnport
出生点接口:Spawnport 彼得兔 更新时间: 2023-04-26 10:19:56 具体函数名及描述如下: 序号 函数名 函数描述 1 getSpawnPoint(...) 获取默认出生点 2 setSpawnPoint(...) 设置出生点位置 3 getChunkValidSpawnPos(...) 获取区块有效刷新点…...
鸿蒙与DeepSeek深度整合:构建下一代智能操作系统生态
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 https://www.captainbed.cn/north 目录 技术融合背景与价值鸿蒙分布式架构解析DeepSeek技术体系剖析核心整合架构设计智能调度系统实现…...
利用行波展开法测量横观各向同性生物组织的生物力学特性|文献速递-医学影像人工智能进展
Title 题目 Measurement of biomechanical properties of transversely isotropic biological tissue using traveling wave expansion 利用行波展开法测量横观各向同性生物组织的生物力学特性 01 文献速递介绍 纤维嵌入结构在自然界中普遍存在。从脑白质(罗曼…...
AR配置静态IP双链路负载分担示例
AR配置静态IP双链路负载分担示例 适用于大部分企业网络出口 业务需求: 运营商1分配的接口IP为100.100.1.2,子网掩码为255.255.255.252,网关IP为100.100.1.1。 运营商2分配的接口IP为200.200.1.2,子网掩码为255.255.255.248&am…...
文件操作(详细讲解)(1/2)
你好这里是我说风俗,希望各位客官点点赞,收收藏,关关注,各位对我的支持是我持续更新的动力!!!!第二期会马上更的关注我获得最新消息哦!!!…...
[AI]从零开始的so-vits-svc歌声推理及混音教程
一、前言 在之前的教程中已经为大家讲解了如何安装so-vits-svc以及使用现有的模型进行文本转语音。可能有的小伙伴就要问了,那么我们应该怎么使用so-vits-svc来进行角色歌曲的创作呢?其实歌曲的创作会相对麻烦一些,会使用到好几个软件&#x…...
华为OD机试-停车场最大距离(Java 2024 E卷 100分)
题目描述 停车场有一排车位,用 0 表示空位,1 表示已停车。至少有一辆车停在车位上,也至少有一个空位。为了防剐蹭,需要找到一个空位,使得该空位与最近的车辆之间的距离最大。返回这个最大距离。 输入描述 一个用半角逗号分隔的停车标识字符串,停车标识为 0 或 1,0 表示…...
SpringMVC控制器定义:@Controller注解详解
文章目录 引言一、Controller注解基础二、RequestMapping与请求映射三、参数绑定与数据校验四、RestController与RESTful API五、控制器建议与全局处理六、控制器测试策略总结 引言 在SpringMVC框架中,控制器(Controller)是整个Web应用的核心组件,负责处…...
免费分享一个软件SKUA-GOCAD-2022版本
若有需要,可以下载。 下载地址 通过网盘分享的文件:Paradigm SKUA-GOCAD 22 build 2022.06.20 (x64).rar 链接: https://pan.baidu.com/s/10plenNcMDftzq3V-ClWpBg 提取码: tm3b 安装教程 Paradigm SKUA-GOCAD 2022版本v2022.06.20安装和破解教程-CS…...
学习threejs,使用LineBasicMaterial基础线材质
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.LineBasicMaterial1.…...
python保留字及作用
在 Python 中,保留字(Reserved Keywords)是具有特殊意义和用途的单词,不能用作变量名、函数名或标识符。以下是 Python 的保留字及其作用: Python 保留字列表 保留字作用False布尔值,表示假。None表示空值…...
java面试题(一)基础部分
1.【String】StringBuffer和StringBuilder区别? String对象是final修饰的不可变的。对String对象的任何操作只会生成新对象,不会对原有对象进行操作。 StringBuilder和StringBuffer是可变的。 其中StringBuilder线程不安全,但开销小。 St…...
Mac mini M4安装nvm 和node
先要安装Homebrew(如果尚未安装)。在终端中输入以下命令: /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 根据提示操作完成Homebrew的安装。 安装nvm。在终端中输入以下命令…...
Ubuntu20.04双系统安装及软件安装(四):国内版火狐浏览器
Ubuntu20.04双系统安装及软件安装(四):国内版火狐浏览器 Ubuntu系统会自带火狐浏览器,但该浏览器不是国内版的,如果平常有记录书签、浏览记录、并且经常使用浏览器插件的习惯,建议重装火狐浏览器为国内版的…...
数据库的乐观锁和悲观锁
乐观锁(Optimistic Locking) 乐观锁是一种假设数据库操作不会发生冲突的锁定机制。在执行数据更新操作时,它并不会立刻加锁,而是先允许所有事务继续执行,并在提交时检查数据是否发生了变化。如果数据在读取后被其他事…...
react中如何使用使用react-redux进行数据管理
以上就是react-redux的使用过程,下面我们开始优化部分:当一个组件只有一个render生命周期,那么我们可以改写成一个无状态组件(UI组件到无状态组件,性能提升更好)...
Dify 开源大语言模型应用开发平台使用(二)
文章目录 说明Dify 使用报告1. 应用创建——专业的锂电池相关知识解答1.1 平台简介1.2 创建应用 2. 知识库、工作流、变量、节点与编排节点详解2.1 知识库管理2.2 工作流配置2.3 变量管理2.4 节点与编排节点 3. 测试和调试3.1 单元测试3.2 日志与监控3.3 实时调试3.4 性能测试 …...
DeepSeek使用手册分享-附PDF下载连接
本次主要分享DeepSeek从技术原理到使用技巧内容,这里展示一些基本内容,后面附上详细PDF下载链接。 DeepSeek基本介绍 DeepSeek公司和模型的基本简介,以及DeepSeek高性能低成本获得业界的高度认可的原因。 DeepSeek技术路线解析 DeepSeek V3…...
5.训练策略:优化深度学习训练过程的实践指南——大模型开发深度学习理论基础
在实际开发中,训练策略对神经网络的表现起着至关重要的作用。通过合理的训练策略,我们可以有效避免过拟合和欠拟合,加速模型收敛,并提升最终性能。本文将从实际开发角度详细介绍几种关键的训练策略,包括 Early Stoppin…...
新品速递 | 多通道可编程衰减器+矩阵系统,如何破解复杂通信测试难题?
在无线通信技术快速迭代的今天,多通道可编程数字射频衰减器和衰减矩阵已成为测试领域不可或缺的核心工具。它们凭借高精度、灵活配置和强大的多通道协同能力,为5G、物联网、卫星通信等前沿技术的研发与验证提供了关键支持。从基站性能测试到终端设备校准…...
Data truncation: Out of range value for column ‘allow_invite‘ at row 1
由于前端传递的数值超过了mysql数据库中tinyint类型的取值范围,所以就会报错。 Caused by: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Out of range value for column allow_invite at row 1at com.mysql.cj.jdbc.exceptions.SQLExcept…...
HCIA—IP路由静态
一、概念及作用 1、概念:IP路由是指在IP网络中,数据从源节点到目的节点所经过的路径选择和数据转发的过程。 2、作用 ①实现网络互联:使不同网段的设备能够相互通信,构建大规模的互联网络 ②优化网络拓扑:根据网络…...
Hz的DP总结
前言: 鉴于本人是一个DP低手,以后每写一道DP都会在本篇博客下进行更新,包括解题思路,方法,尽量做到分类明确,其中的题目来自包括但并不限于牛客,洛谷,CodeForces,AtCode…...
GB/T 25000.51-2016 标准中维护性如何测试,关注哪些内容
以下是 GB/T 25000.51-2016 标准中维护性下条款各方面的测试方法及关注内容: 模块化 测试方法 组件停止与替换测试:在系统运行时,尝试停止或替换某个组件,观察其他组件能否正常独立运行及处理任务1。接口调用测试:检…...
