问题背景


在进行图像和点云融合的过程中,我们通常首先在图像中进行目标检测,以确定感兴趣的对象。这一步通常涉及使用深度学习模型,如卷积神经网络(CNN),来识别和定位图像中的目标。完成目标检测后,下一步是在点云数据中定位相应的目标,这是实现三维目标检测的关键步骤。

点云数据,通常由激光雷达(LiDAR)等传感器提供,包含大量的三维空间点。这些点代表了环境中物体的位置和形状。然而,直接在这些庞大的点云数据中寻找特定目标是非常具有挑战性的,尤其是在动态和复杂的环境中。这就是视锥滤波技术发挥作用的地方。

视锥滤波(Frustum Filtering)是一种有效的技术,用于从点云中提取与图像目标检测结果相对应的三维信息。这种方法的基本思想是利用从图像中获得的目标位置来创建一个“视锥”(frustum)。这个视锥是一个三维空间区域,其形状和位置由图像中检测到的目标决定。然后,算法会筛选出位于这个视锥内的点云数据,从而有效地定位和提取与二维图像目标相对应的三维对象。

图片来自:charlesq34/frustum-pointnets: Frustum PointNets for 3D Object Detection from RGB-D Data (github.com)

视锥滤波的关键在于如何准确地从二维图像检测结果转换到三维空间。这涉及到将图像坐标系与点云坐标系对齐,以及处理图像和点云数据之间的潜在不一致性。例如,由于不同传感器的视角和分辨率差异,相同的物体在图像和点云中可能会有不同的表现。

这篇博客主要讲解 PCL 点云库提供的视锥滤波功能处理 Livox Avia 固态激光雷达的点云数据,如要进行三维目标检测应用,请提前标定相机的内参、相机雷达的外参并对齐相机和雷达的 FOV。

准备工作


  • 相机内参标定
  • 相机雷达外参标定
  • FOV对齐

前两个问题可参考我的另一篇博客:【图像与点云融合教程(二)】相机雷达联合标定 - 古月居 (guyuehome.com)

由于相机和雷达的 FOV 不同,为了保证点云的视野与相机一致,需要对相机的 FOV 进行裁剪,Livox 官方建议固定相机和雷达后,根据实时显示的图像来手动调整相机的 FOV。

该问题的讨论链接:想知道怎麼從FOV算出image dimension · Issue #12 · Livox-SDK/livox_camera_lidar_calibration (github.com)

视锥

首先,构建视锥所需的六个平面包括:一个远平面和一个近平面,上下两个平面,以及左右两个平面。这些平面共同形成一个类似于梯形体的结构。
接下来,确定视锥的九个关键顶点:其中四个位于近平面,四个位于远平面,再加上一个特殊的点,即相机的位置。
然后,通过分析相机的位置,我们可以计算出相机在水平和垂直方向上的视场角(FOV),以及相机与近平面和远平面的距离。这些计算涉及到一些背景知识中的参数转换。
最后,基于第三步的计算结果,我们还需要计算近平面和远平面的高度和宽度。这可以通过以下公式实现:

  • 近平面的宽度(Wnear)= 2 _ tan(水平FOV / 2) _ 近平面距离(neardist_)
  • 近平面的高度(Hnear)= 2 _ tan(垂直FOV / 2) _ 近平面距离(neardist_)

  • 远平面的宽度(Wfar)= 2 _ tan(水平FOV / 2) _ 远平面距离(fardist_)

  • 远平面的高度(Hfar)= 2 _ tan(垂直FOV / 2) _ 远平面距离(fardist_)

这些步骤共同帮助我们精确地定义视锥的空间结构,从而在点云数据中正确地定位和提取目标。

PCL 视锥滤波

PCL 官方文档:Point Cloud Library (PCL): pcl::FrustumCulling< PointT > Class Template Reference (pointclouds.org)

核心代码如下:

pcl::FrustumCulling<pcl::PointXYZ> fc;
fc.setInputCloud(cloud);
fc.setVerticalFOV(70.4);
fc.setHorizontalFOV(77.2);
fc.setRegionOfInterest(0.5, 0.5, 0.8, 0.2);
fc.setNearPlaneDistance(0);
fc.setFarPlaneDistance(500);
Eigen::Matrix4f camera_pose; // 相机位姿在原点
camera_pose << 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1; fc.setCameraPose(camera_pose);
fc.filter(*cloud_filtered);

在这段代码中,我们使用了PCL(Point Cloud Library)库来执行视锥滤波(Frustum Culling),这是一种用于从三维点云中筛选特定区域的技术。让我们逐步解释这段代码的功能和目的。

  1. 初始化视锥滤波器

    pcl::FrustumCulling<pcl::PointXYZ> fc;
    

    这行代码创建了一个FrustumCulling对象,用于处理点云数据。这里的pcl::PointXYZ指明了点云数据中每个点的数据类型,包含X、Y、Z三个坐标。

  2. 设置输入点云

    fc.setInputCloud(cloud);
    

    此处将cloud(一个包含点云数据的对象)设为输入。这意味着fc将对cloud中的点进行处理。

  3. 设置视场角(FOV)

    fc.setVerticalFOV(70.4);
    fc.setHorizontalFOV(77.2);
    

    这两行代码分别设置了视锥的垂直和水平视场角。视场角决定了相机可以“看到”的区域范围,这里分别为70.4度和77.2度,是 Livox Avia 的 FOV 参数,你需要修改为自己雷达的参数。
    注意:由于坐标系的定义存在差异,PCL 中的竖直 FOV 对应 Livox 的水平 FOV,PCL 中的水平 FOV 对应 Livox 的竖直 FOV。

  4. 设置感兴趣区域(ROI)

    fc.setRegionOfInterest(0.5, 0.5, 0.8, 0.2);
    

    这行代码的作用是设置视锥滤波器的“感兴趣区域”(Region of Interest, ROI)。感兴趣区域是指在图像或点云数据中特别关注的部分。在这个例子中,虽然这行代码被注释掉了,但如果启用,它将如下影响滤波过程:

  • 第一个参数(0.5)和第二个参数(0.5)分别定义了ROI的中心点在相机视场中的水平和垂直位置,以归一化的坐标表示。这里的0.5, 0.5意味着ROI的中心位于视场的中心。
  • 第三个参数(0.8)和第四个参数(0.2)定义了ROI的宽度和高度,同样以归一化的坐标表示。这意味着ROI的宽度占相机水平视场的80%,高度占垂直视场的20%。

    通过设置ROI,FrustumCulling滤波器将只关注并处理这个特定区域内的点云数据。这对于专注于图像或三维场景中的特定部分非常有用,比如当我们只对场景中的某个特定对象感兴趣时。在一些应用场景中,如监控或目标跟踪,这可以显著减少处理的数据量,提高系统的效率和响应速度。

  1. 设置近平面和远平面的距离

    fc.setNearPlaneDistance(20);
    fc.setFarPlaneDistance(200);
    

    这里设置了视锥的近平面和远平面距离。所有位于近平面之内或远平面之外的点将被滤除。在这个示例中,近平面设为0,远平面设为500单位距离。

  2. 设置相机位姿

    Eigen::Matrix4f camera_pose;
    camera_pose << 1, 0, 0, 0,
                   0, 1, 0, 0,
                   0, 0, 1, 0,
                   0, 0, 0, 1;
    fc.setCameraPose(camera_pose);
    

    这部分代码定义了传感器的位姿,即其在空间中的位置和朝向。这里使用了一个4x4矩阵表示传感器位姿,其中传感器位于原点,朝向保持默认。

  3. 应用滤波并获取结果

    fc.filter(*cloud_filtered);
    

    最后,filter函数应用之前设置的所有参数,执行视锥滤波,并将结果存储在cloud_filtered中。这意味着cloud_filtered将只包含位于定义的视锥内的点云数据。

相机雷达融合时的视野裁剪对齐

Livox Avia雷达有一个独特特性,即其不同的水平和竖直视场(FOV)。这种不对称的视场使得雷达的探测区域更类似于一个椭圆锥形,而不是传统的圆锥或矩形锥形。

Livox Avia 的技术参数:Specs - Avia 傲览激光雷达 - Livox (livoxtech.com)

椭圆锥形视场的重要性
当考虑将雷达数据与相机数据融合时,理解这个椭圆锥形视场的概念至关重要。相机通常提供一个矩形视场,而雷达则提供一个椭圆形的视场。这种形状差异意味着,当我们试图将雷达数据(椭圆锥形视场)与相机数据(矩形视场)对齐时,将面临额外的挑战。

处理视场差异的策略
要有效地处理这些差异,我们需要采取一些策略来调整视场,以便更好地对齐数据。一种可能的方法是将雷达的椭圆形远平面视场调整为一个与相机矩形视场匹配的最大可能矩形。这种方法可以最大限度地保留雷达数据,同时确保与相机视场的最大可能对齐。
另一种方法是将相机的视场调整为与雷达的椭圆锥形视场相匹配的内接矩形,尽管这可能导致相机视场的一部分被剪裁,从而减少可用数据。

考量数据融合时的权衡
选择哪种方法取决于具体的应用需求。在某些场景中,保留尽可能多的雷达数据可能更为重要,而在其他情况下,确保数据在空间上的准确对齐可能是首要考虑。在做出决策时,必须权衡数据完整性和对齐精度之间的关系。

源码

#include <Eigen/Dense>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/frustum_culling.h>
#include <pcl/visualization/pcl_visualizer.h>

int main()
{
    // 读取点云
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::io::loadPCDFile<pcl::PointXYZ>("/home/zth/DATA/铁塔监控/Gazebo仿真/雷达倾斜/移除原点/f30_o0.pcd", *cloud);
    // pcl::io::loadPCDFile<pcl::PointXYZ>("/home/zth/DATA/铁塔监控/南瑞训练基地/_4_rotated_cloud.pcd", *cloud);

    // 滤波后点云
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);

    // 创建滤波器对象
    pcl::FrustumCulling<pcl::PointXYZ> fc;
    fc.setInputCloud(cloud);
    fc.setVerticalFOV(70.4);
    fc.setHorizontalFOV(77.2);
    // fc.setRegionOfInterest(0.5, 0.5, 0.8, 0.2);
    fc.setNearPlaneDistance(0);
    fc.setFarPlaneDistance(500);
    Eigen::Matrix4f camera_pose;
    // 相机位姿在原点
    camera_pose << 1, 0, 0, 0,
                   0, 1, 0, 0,
                   0, 0, 1, 0,
                   0, 0, 0, 1;
    fc.setCameraPose(camera_pose);
    fc.filter(*cloud_filtered);

    // 显示滤波前后的点云
    pcl::visualization::PCLVisualizer viewer("PCL Viewer");
    viewer.addPointCloud(cloud, "cloud");
    viewer.addPointCloud(cloud_filtered, "cloud_filtered");
    viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud");
    viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud_filtered");
    viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0, 1, 0, "cloud_filtered");
    while (!viewer.wasStopped()) {
        viewer.spinOnce();
    }

    return 0;
}