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++
    +关注

    关注

    22

    文章

    2104

    浏览量

    73497
  • 代码
    +关注

    关注

    30

    文章

    4748

    浏览量

    68357
  • 编译
    +关注

    关注

    0

    文章

    654

    浏览量

    32809
  • 自动驾驶
    +关注

    关注

    783

    文章

    13685

    浏览量

    166150

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

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

收藏 人收藏

    评论

    相关推荐

    通过异步获取的图像与LiDAR对准的方案

    实现图像与其重建对象之间的精确2D-3D对应关系对于准确的图像定位至关重要,一种有前景的方法涉及在图像和激光雷达平面之间建立对应关系,激光雷达平面可以被视为来自激光雷达
    发表于 12-12 14:46 703次阅读

    如何二维数组转换为图像

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

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

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

    阿里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篇文章中,我解释如何ADC代码转换回相应的电压。
    发表于 07-23 04:45

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

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

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

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

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

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

    人脸识别C/C++代码

    人脸识别C/C++代码 生物特征识别应用于人脸,实际上是包含两个方面:第一,图像或视频帧
    发表于 02-09 16:05 184次下载

    代码转换为电压,如何可以实现

    许多初步了解模数转换器(ADC)的人想知道如何ADC代码转换为电压。或者,他们的问题是针对特定应用,例如:如何ADC
    的头像 发表于 10-12 08:30 1.1w次阅读
    <b class='flag-5'>将</b><b class='flag-5'>代码</b><b class='flag-5'>转换为</b>电压,如何可以<b class='flag-5'>实现</b>?

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

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

    C++在Linux内核开发中争议到成熟

    Linux 内核邮件列表中一篇已有六年历史的老帖近日再次引发激烈讨论 —— 主题是建议 Linux 内核的开发语言 C 转换为更现代的 C++
    的头像 发表于 01-31 14:11 589次阅读
    <b class='flag-5'>C++</b>在Linux内核开发中<b class='flag-5'>从</b>争议到成熟

    OpenCV图像识别C++代码

    的头文件 在您的C++代码中,包含以下必要的头文件: # include # include # include # include # include # include # include 读取图像
    的头像 发表于 07-16 10:42 1806次阅读