0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

从原理到c++代码实现 | 通过球面投影将点云转换为Range图像

3D视觉工坊 来源:DeepDriving 2023-06-21 10:30 次阅读

前言

将3维激光点云通过球面投影(Spherical Projection)转换为2维距离图像(Range Images),是自动驾驶应用场景中一种非常常见的点云处理方式。点云转换为距离图像后,通常会被输入给一个2维卷积神经网络去实现目标检测、语义分割等任务。目前采用这种点云处理方式的典型目标检测算法RangeDet,语义分割算法有SqueezeSegRangeNet++SalsaNext等。在这些文章中,都只给出一个通过球面投影转换到距离图像的最终公式,至于这个公式是怎么来的却没有详细的推导,初看论文的读者可能会比较困惑。本文将对这个投影公式做一定的推导,可能本人理解的也不是很对,欢迎大家批评指正。

52fc95ac-0fbf-11ee-962d-dac502259ad0.png

图片来源于RangeDet论文

球面投影推导过程

假设有一个m线的旋转扫描式激光雷达,它的垂直视场角FOV被分为上下两个部分:FOV_upFOV_down,通常以FOV_up的数值为正数而FOV_down数值为负数,所以FOV = FOV_up + abs(FOV_down)。激光雷达旋转扫描一周得到的点云相当于是以其自身为中心的空心圆柱体,如果把这个圆柱体展开的话,那么就可以把点云投影到一个图像平面中去,这个图像平面就是距离图像。

530cd84a-0fbf-11ee-962d-dac502259ad0.png

对于一个m线的激光雷达,在扫描的某一时刻会得到m个点,如果旋转一周扫描了n次,那么得到的点云就可以用一个的矩阵来表示。那么怎么把3维的点云投影到2维的距离图像平面呢?这就需要用到球面坐标。

531bcd0a-0fbf-11ee-962d-dac502259ad0.png

图片来源于RangeDet论文

球面坐标用3个参数来表示:距离,方位角(Azimuth),天顶角(Zenith)。通常使用的激光雷达点云中的每个由3维笛卡尔坐标表示的点实际上是从球面坐标系转换而来:

让我们再通过下图来理解一下3维笛卡尔坐标系和球面坐标系之间的关系。

5327b066-0fbf-11ee-962d-dac502259ad0.png

假设3维笛卡尔坐标系下的点坐标为,那么用球面坐标系可以这样表示该点:

如果以x轴方向为前视图的方向把激光雷达旋转扫描一周得到的圆柱体展开后,可以得到一副这样的图像:坐标原点在图像的中心,图像中像素的纵坐标由pitch角投影得到(范围为[FOV_down,FOV_up]),横坐标由yaw角投影得到(范围为)。

5335f04a-0fbf-11ee-962d-dac502259ad0.png

由于图像坐标系是以左上角作为坐标原点,所以上面得到的前视图还需要做一下坐标转换,把坐标原点移到左上角去:

把3维点云投影为2维图像,这种降维操作必然会带来信息损失。为了尽可能减少投影带来的信息损失,我们需要选择合适大小的投影图像。对于一个64线的激光雷达,一般会设置投影图像的高为64,那么图像的宽该如何设置呢?假设激光雷达的水平分辨率为0.35度,那么旋转一周一个激光器最多产生的点数为。在卷积神经网络中,一般会对输入特征图做多次2倍下采样,所以图像的宽度需要设置为2的次幂,这里可设置为1024

由于不同类型激光雷达的视场角、水平分辨率不同,投影图像的尺寸也会根据需要设置为不同的值,为了适应这些变化,yawpitch还需要进行规范化:

规范化后,再乘以投影图像的宽高,就得到了这个点投影到距离图像的坐标:

上式中的第二步是将代入得到的。

代码实现

理解了原理后,我们再用代码来把这个投影过程实现一遍。在RangeNet++中,点云被转换为5个通道的距离图像,这5个通道分别代表点云的这5个属性。下面的代码将展示如何通过球面投影将点云转换为需要的距离图像,使用的点云数据来源于SemanticKITTI数据集。

#include
#include

#include
#include
#include
#include
#include
#include

intmain(intargc,char**argv){
if(argc< 2){
std::cout<< "Usage:"<< argv[0]<< "
";
return-1;
}

conststd::stringpcd_file(argv[1]);
pcl::PointCloud::Ptrpoint_cloud(
newpcl::PointCloud);

if(pcl::loadPCDFile(pcd_file,*point_cloud)==-1){
std::cout<< "Couldn'treadpcdfile!
";
return-1;
}

constexprintwidth=2048;
constexprintheight=64;
constexprfloatfov_up=3*M_PI/180.0;
constexprfloatfov_down=-25*M_PI/180.0;
constexprfloatfov=std::abs(fov_up)+std::abs(fov_down);
conststd::vector<float>image_means{12.12,10.88,0.23,-1.04,0.21};
conststd::vector<float>image_stds{12.32,11.47,6.91,0.86,0.16};
float*range_images=newfloat[5*width*height]();

for(constauto&point:point_cloud->points){
constauto&x=point.x;
constauto&y=point.y;
constauto&z=point.z;
constauto&intensity=point.intensity;
constfloatrange=std::sqrt(x*x+y*y+z*z);
constfloatyaw=-std::atan2(y,x);
constfloatpitch=std::asin(z/range);

floatproj_x=0.5f*(yaw/M_PI+1.0f)*width;
floatproj_y=(1.0f-(pitch+std::abs(fov_down))/fov)*height;
proj_x=std::floor(proj_x);
proj_y=std::floor(proj_y);

constintu=std::clamp<int>(static_cast<int>(proj_x),0,width-1);
constintv=std::clamp<int>(static_cast<int>(proj_y),0,height-1);

range_images[0*width*height+v*width+u]=
(range-image_means.at(0))/image_stds.at(0);
range_images[1*width*height+v*width+u]=
(x-image_means.at(1))/image_stds.at(1);
range_images[2*width*height+v*width+u]=
(y-image_means.at(2))/image_stds.at(2);
range_images[3*width*height+v*width+u]=
(z-image_means.at(3))/image_stds.at(3);
range_images[4*width*height+v*width+u]=
(intensity-image_means.at(4))/image_stds.at(4);
}

//对range通道进行可视化
cv::Matrange=
cv::Mat(height,width,CV_32FC1,static_cast<void*>(range_images));
cv::Matnormalized_range,u8_range,color_map;
cv::normalize(range,normalized_range,255,0,cv::NORM_MINMAX);
normalized_range.convertTo(u8_range,CV_8UC1);
cv::applyColorMap(u8_range,color_map,cv::COLORMAP_JET);
cv::imwrite("range_color_map.jpg",color_map);
cv::imshow("RangeImage",color_map);
cv::waitKey(0);

delete[]range_images;

return0;
}

range通道可视化的结果如下图所示:

53403df2-0fbf-11ee-962d-dac502259ad0.jpg

上面的代码有几个需要说明的地方:

  • fov_upfov_downimage_meansimage_stds这几个参数来源于RangeNet++预训练模型中的arch_cfg.yaml文件。
  • std::clamp需要c++17支持,编译的时候请使用-std=c++17编译选项。
  • 实际使用中width * height的值只需要计算一次,没必要在循环里面反复计算,这里这么写只是为了方便理解。

参考资料

  • RangeDet: In Defense of Range View for LiDAR-based 3D Object Detection
  • https://towardsdatascience.com/spherical-projection-for-point-clouds-56a2fc258e6c

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • C++
    C++
    +关注

    关注

    21

    文章

    2066

    浏览量

    72900
  • 代码
    +关注

    关注

    30

    文章

    4555

    浏览量

    66771
  • 编译
    +关注

    关注

    0

    文章

    615

    浏览量

    32392
  • 自动驾驶
    +关注

    关注

    773

    文章

    13032

    浏览量

    163217

原文标题:从原理到c++代码实现 | 通过球面投影将点云转换为Range图像

文章出处:【微信号:3D视觉工坊,微信公众号:3D视觉工坊】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何二维数组转换为图像

    如何二维数组转换为图像
    发表于 03-10 11:15

    【创龙TMS320C665x申请】图像中数字及字母的识别

    彩色信息,进行灰度化;2.图像灰度修正,通过点算法使输入图像转换为每一灰度上都有项目糊涂的像素的输出
    发表于 03-04 09:38

    【创龙TMS320C665x开发板试用】任务篇之图像灰度

    信息,进行灰度化;2.图像灰度修正,通过点算法使输入图像转换为每一灰度上都有项目糊涂的像素的输出图像
    发表于 04-03 23:03

    如何LabVIEW的VI转换为C语言代码

    `写软件登记的时候,考虑LABVIEW的VI转化成C代码,就搜集了一些资料跟大家分享。 1、安装labview的C代码转换
    发表于 03-02 11:06

    阿里SDK再升级,宣布支持C++语言

    C++ 语言开发者更加便捷地使用SDK调用产品API来操作产品,包括二次开发、自动化运维的实现等。此查看原文:http://click.aliyun.com/m/41955/日前,阿里
    发表于 02-08 13:48

    基于FPGA水平垂直投影(字符分割)法的实现

    列向x轴方向投影垂直投影是指二维图象按行向y轴方向投影投影的结果可以看成是一维图像.2 matlab实现
    发表于 08-07 10:15

    基于Verilog的垂直投影实现

    `基于Verilog的垂直投影实现微信公众号:FPGA自习室一、概述投影,在立体几何中我们学到过,是空间直线在某个方向上的投影,那么图像处理
    发表于 03-03 17:51

    ADC代码转换为电压的方法

    在本系列的第1篇文章中,我解释了如何通过使用公式1ADC的输出代码乘以最低有效位(LSB)大小来计算模数转换器(ADC)的输入电压: 为计算ADC的LSB大小,我们使用公式2: 现
    发表于 03-27 06:45

    如何ADC代码转换为电压

    讨论如何为各种应用执行这一数学转换。在第1篇文章中,我解释如何ADC代码转换回相应的电压。
    发表于 07-23 04:45

    如何利用codermatlab中的程序转换C/C++

    利用codermatlab中的程序转换C/C++众所周知,matlab的功能是非常强大的,简便易于操作的工具包更是非常的方便。为机器学习,深度学习,
    发表于 08-17 06:56

    如何实现90%的C++代码自动迁移?

    如何代码迁出x86架构?如何实现90%的C++代码自动迁移?
    发表于 10-25 09:21

    如何在新版本中将C项目转换为C++呢?

    我正在尝试 C 项目转换为 C++。在以前的版本中,属性中有一个“转换为 C++”选项。我在
    发表于 01-06 08:13

    对卷积层的C++实现详细介绍

    就不会出现图像缩小现象。如果内核大小为 3,则带填充的输入图像大小在宽度和高度上均为 +2,因为 +1 像素添加到屏幕外部且值为零。在我们的模型中,我们在所有卷积层中使用零填充。C
    发表于 02-24 15:41

    ONNX模型转换为中间表示(IR)后,精度下降了怎么解决?

    ONNX 模型转换为 IR。 与使用 PyTorch 运行 ONNX 模型相比,Ran IR 采用 基准 C++ 工具,其性能准确率降低了 20%。 无法确定如何对图像进行预处理
    发表于 08-15 08:28

    关于彩色图像高斯反向投影基于OpenCV的C++代码

    图像反向投影的最终目的是获取ROI然后实现对ROI区域的标注、识别、测量等图像处理与分析,是计算机视觉与人工智能的常见方法之一。图像反向
    的头像 发表于 05-31 10:31 761次阅读