如何将LiDAR坐标系下的3D点投影到相机2D图像上

将激光雷达点云投影到相机图像上做数据层的前融合,或者把激光雷达坐标系下标注的物体点云的3d bbox投影到相机图像上画出来,都需要做点云3D点坐标到图像像素坐标的转换计算,也就是LiDAR 3D坐标转像素坐标。

看了网上一些文章都存在有错误或者把公式推导说的含混不清有误导人的地方(如果你完全按那些列出来的公式去计算,发现投影结果在图像上怎么都不对!),在此结合我经过验证是正确的代码详细解释一下,也供备忘,插图是对网上的原图做了正确修改的。

对于普通无畸变平面相机,坐标转换涉及到的主要是两个参数矩阵:用于激光雷达坐标系到相机坐标系转换的外参矩阵和用于相机坐标系到像素坐标系转换的相机本身的内参矩阵。

针孔相机模型下,相机坐标系下的三维空间中点P(X,Y,Z),对应在相机成像平面的图像坐标系(注意不是像素坐标系!)中的坐标点是p(x,y),焦距f是焦点到成像平面之间的距离,Z是P点到焦点的距离。

 根据相似三角形原理,有:

 由此可得,根据相机坐标系下的坐标(X,Y,Z)计算相机图像坐标系下的坐标(x,y)的计算公式:

注意,此处的x、y、f都是实际空间尺寸,单位一般是mm,如果将等式两边都除以图像每像素对应的实际尺寸,等式仍然成立,此时的x、y、和f就都是像素了,所以我们平时看到的数据集里camera的焦距值都是像素值,正是我们计算像素为单位的x和y所需的,然后,因为把图像坐标坐标系下的像素为单位的坐标(x,y)转像素坐标系下的坐标(u,v),只需加上图像横纵方向各自的偏移量cx和cy即可(因为图像坐标系的原点是在图像的中央,而像素坐标系的原点是在图像的左上顶点),另外相机的纵横方向的焦距稍有差异,不是同一个f值,所以还区分为fx和fy,至此,稍做推导可以得出相机坐标系下的坐标P(X,Y,Z)转换为图像上的像素坐标的计算公式为:

整理成矩阵运算形式就是:

其中相机内参矩阵就是:

将激光雷达坐标系下的3D点坐标转换到相机图像上的像素坐标的过程就是先将激光雷达坐标系下的3D坐标(Xw,Yw,Zw)(此处假设雷达不动或者我们只关注激光雷达坐标系下的坐标转换到像素坐标,(Xw,Yw,Zw)表示激光雷达坐标系下的坐标,此处的(Xw,Yw,Zw)不是激光雷达坐标而是导航用的世界坐标系下的坐标的话,下面的表示是错的,需要先用global2ego之类参数矩阵将坐标转换到雷达坐标系下),左乘以lidar2camera相机外参矩阵转换到相机坐标系下的3D坐标(Xc,Yc,Zc):

然后如上面所述使用相机本身的内参矩阵将相机坐标系下的3D坐标点转换到像素坐标系下的像素坐标,于是整个计算可以合并表示为(此处的fx、fy、Cx、Cy都是像素为单位的值!):

很多文章里列出连乘公式都漏了这个重要的 ,导致计算出来的像素坐标根本不对(把像素坐标点在图像上画出来看图像上的点投影效果比较直观),包括一些BEV模型的实现代码都犯了这个错误,效果能好才怪!这个转换关系(K是相机内参矩阵)要记住:

这里的Z就是相机坐标系下的3D坐标(Xc,Yc,Zc)的Zc,上面倒数第三个公式里也说明了。那么我们代码实现时,对于普通无畸变平面相机,非常简单,就是 (1/Z) X 相机的内参矩阵 X 相机的外参矩阵(lidar2camera),即可由激光雷达下的3D坐标计算出像素坐标系下的坐标,用我写的经过验证是正确的C++代码作为示例,借助Eigen库实现非常简单:

    Eigen::Vector4f pointVec;pointVec << point3D, 1.0;Eigen::Vector4f cam_point3D =  K * extrinK * pointVec;Eigen::Vector3f point = cam_point3D.head<3>();float x = point.x();float y = point.y();float z = point.z();if (z < 1e-6) {return;}int u = static_cast<int>(x / z);int v = static_cast<int>(y / z);

上面K是4x4奇次相机内参矩阵,extrinK是4x4奇次外参矩阵,算出来的部分u,v值有的可能超出了相机图像的范围,用图像的cols和rows最大值最小值过滤一下就可以了,对于BEV模型,投影到6个相机的图像上使用各个相机各自的内外参矩阵依次做上述计算即可。

对于有畸变的平面相机,则不能使用内参和外参矩阵连乘,而是需要先左乘以外参矩阵把点云3D坐标转到相机坐标系下的坐标(Xc,Yc,Zc)后,把Zc小于等于0的坐标过滤掉,然后需要把Xc和Yc除以Zc,得到未校正的相机坐标系下的2D坐标(Xc/Zc,Yc/Zc),就是Z=1归一化平面里的坐标,然后根据相机厂家给出的计算公式使用相机的径向和切向畸变系数对此2D坐标做畸变计算得出畸变后的坐标(X,Y),再将此坐标扩展为奇次坐标后转置再左乘以相机的3x3原始内参矩阵,得出畸变后像素坐标(u,v),关于畸变和去畸变的计算,可以参考相机标定之畸变矫正与反畸变计算 - 达达MFZ - 博客园这篇文章给出的畸变算法的实现,代码如果借助Eigen矩阵运算,可以写得更简洁点,不过他这样写的好处就是比较容易看清楚。针孔相机畸变成像详解 这篇文章对畸变/去畸变原理做了比较清楚的解释。

车载森云相机的畸变/去畸变就是这样的算法,我们借助Eigen库自己实现的代码涉及到商业秘密就不列出来了。

多说一点就是,根据上面的计算公式:

假设我们知道每个像素的深度值Zc的话,可以由下面的公式计算出图像里每个像素对应的相机坐标系下的3D点坐标(Xc,Yc,Zc) :

这就是LSS模型预测每像素的深度后获得对应的3D点坐标的计算原理,从而实现2D到3D的对应转换,进而Splat成BEV。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/17399.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【Pikachu】XML外部实体注入实战

若天下不定&#xff0c;吾往&#xff1b;若世道不平&#xff0c;不回&#xff01; 1.XXE漏洞实战 首先写入一个合法的xml文档 <?xml version "1.0"?> <!DOCTYPE gfzq [<!ENTITY gfzq "gfzq"> ]> <name>&gfzq;</name&…

游戏引擎学习第13天

视频参考:https://www.bilibili.com/video/BV1QQUaYMEEz/ 改代码的地方尽量一张图说清楚吧,懒得浪费时间 game.h #pragma once #include <cmath> #include <cstdint> #include <malloc.h>#define internal static // 用于定义内翻译单元内部函数 #…

(一)Ubuntu20.04服务器端部署Stable-Diffusion-webui AI绘画环境

一、说明 cup型号&#xff1a; Intel(R) Celeron(R) CPU G1610 2.60GHz 内存大小&#xff1a; 7.5Gi 356Mi 4.6Gi 1.0Mi 2.6Gi 6.8Gi Swap: 4.0Gi 0B 4.0Gi 显卡型号&#xff1a;NVIDIA P104-100 注意&#xff1a…

Python Tornado框架教程:高性能Web框架的全面解析

Python Tornado框架教程&#xff1a;高性能Web框架的全面解析 引言 在现代Web开发中&#xff0c;选择合适的框架至关重要。Python的Tornado框架因其高性能和非阻塞I/O特性而备受青睐。它特别适合处理大量并发连接的应用&#xff0c;比如聊天应用、实时数据处理和WebSocket服务…

ubuntu20.04安装anaconda

在anaconda的官网&#xff08;Anaconda | The Operating System for AI&#xff09;或者清华镜像源网站&#xff08;Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror&#xff09;中下载对应的anaconda版本 可以在网页直接下载或者通过命…

平衡二叉搜索树之 红黑 树的模拟实现【C++】

文章目录 红黑树的简单介绍定义红黑树的特性红黑树的应用 全部的实现代码放在了文章末尾准备工作包含头文件类的成员变量和红黑树节点的定义 构造函数和拷贝构造swap和赋值运算符重载析构函数findinsert【重要】第一步&#xff1a;按照二叉搜索树的方式插入新节点第二步&#x…

【设计模式】行为型模式(四):备忘录模式、中介者模式

行为型模式&#xff08;四&#xff09;&#xff1a;备忘录模式、中介者模式 7.备忘录模式&#xff08;Memento&#xff09;7.1 通俗易懂的解释7.2 具体步骤7.3 代码示例7.3.1 定义发起人7.3.2 定义备忘录7.3.3 定义管理者7.3.4 客户端7.3.5 输出 7.4 总结 8.中介者模式&#xf…

Thinkpad E15 在linux下升级 bios

安装xubuntu 24.04后&#xff0c;发现键盘的Fn按键全都无法使用&#xff0c;在Windows环境下是正常的&#xff0c;按说是驱动的问题&#xff0c;网上也有说可以通过升级BIOS解决&#xff0c;所以打算升级看看&#xff0c;升级有风险。 参考&#xff1a; https://blog.stigok.c…

Java学习Day61:薄纱王灵官!(Nginx review)

1.Nginx是什么 Nginx是一款轻量级、高性能&#xff0c;并发性好的HTTP和反向代理服务器 2.功能 2.1反向代理 正向代理是指客户端向代理服务器发送请求&#xff0c;代理服务器代表客户端去访问目标服务器。简单来说&#xff0c;正向代理是客户端的代理&#xff0c;客户端通过…

MATLAB用到的矩阵基础知识(矩阵的乘和矩阵的逆)

1. 矩阵乘法 方法: 设第一个矩阵为 A A A,第二个矩阵为 B B B,则 A A A的第一行乘 B B B的第一列,先想乘再相加,作为目标矩阵的一个元素。 前提条件: 所以我们可以看到矩阵相乘的前提条件:第一个矩阵的列数等于第二个矩阵的行数。否则,我们就无法进行行和列的相乘。 最…

Oracle OCP认证考试考点详解082系列22

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 105. 第105题&#xff1a; 题目 解析及答案&#xff1a; 题目翻译&#xff1a; 关于Oracle数据库中的事务请选择两个正确的陈述&#xf…

【ict基础软件赛道】真题-50%openGauss

题目取自赛前测试链接 OpenGauss安装前使用哪个工具检查环境是否符合安装哪个功能不是gs_guc工具提供的opengauss数据库逻辑复制的特点描述正确的是opengauss的全密态数据库等值查询能力描述正确的是哪个不属于ssh客户端工具opengauss三权分立说法正确的是opengauss wdr snapsh…

MDK5(Keil5)工具设置及技巧

设置&#xff1a; 1点击扳手&#xff08;设置&#xff09; 2文字设置为GB2312简体 3勾选显示空格 4按一下TAB键移动4个空格 修改keil中数字大小及颜色&#xff08;增加对比&#xff09; 勾选全部 1提示结构体 2提示函数参数 3打上几个英文符号后开始提示 4TAB作为填充字符 5打开…

4G模组Air780E对json数据处理的基本方法~

4G模组Air780E在数据传输和处理中扮演着越来越重要的角色。在实际应用中&#xff0c;JSON作为一种轻量级的数据交换格式&#xff0c;被广泛应用于网络数据传输和存储。本文将详细介绍4G模组Air780E对JSON数据处理的基本方法&#xff0c;以帮助开发者更好地利用这一模组进行数据…

学习使用LVGL,依赖官方网址

LVGL Basics — LVGL documentation LVGL基础知识 LVGL是一个开源的图形库&#xff0c;提供创建嵌入式GUI的一切 LVGL数据流 您为每个物理显示面板 创建一个显示器 (lv_display) &#xff0c;在其上创建屏幕小部件&#xff0c;将小部件添加到这些屏幕上。要处理触摸、鼠标、…

【数据库】组合索引生效规则及索引失效

文章目录 索引演示示例组合索引索引失效 索引演示示例 # 创建表结构 CREATE TABLE Employees (EmployeeID INT PRIMARY KEY,FirstName VARCHAR(50),LastName VARCHAR(50),DepartmentID INT,Salary DECIMAL(10, 2),HireDate DATE );# 插入示例数据 INSERT INTO Employees (Empl…

力扣 LeetCode 145. 二叉树的后序遍历(Day6:二叉树)

解题思路&#xff1a; 方法一&#xff1a;递归&#xff08;左右中&#xff09; class Solution {List<Integer> res new ArrayList<>();public List<Integer> postorderTraversal(TreeNode root) {recur(root);return res;}public void recur(TreeNode ro…

用指针遍历数组

#include<stdio.h> int main() {//定义一个二维数组int arr[3][4] {{1,2,3,4},{2,3,4,5},{3,4,5,6},};//获取二维数组的指针int (*p)[4] arr;//二维数组里存的是一维数组int[4]for (int i 0; i < 3; i){//遍历一维数组for (int j 0; j <4; j){printf("%d &…

[HCTF 2018]Warmup 详细题解

知识点: 目录穿越_文件包含 static静态方法 参数传递引用 mb_strpos函数 mb_substr函数 正文: 页面有一张 滑稽 的表情包,查看一下页面源代码,发现提示 那就访问/source.php 得到源码 <?phphighlight_file(__FILE__);class emmm{public static function checkFil…

从0-1训练自己的数据集实现火焰检测

随着工业、建筑、交通等领域的快速发展,火灾作为一种常见的灾难性事件,对生命财产安全造成了严重威胁。为了提高火灾的预警能力,减少火灾损失,火焰检测技术应运而生,成为火灾监控和预防的有效手段之一。 传统的火灾检测方法,如烟雾探测器、温度传感器等,存在响应时间慢…