Open3D源码阅读与学习

发表信息: by

Open3D 简介

Open3D是由IntelVCL小组开发的一款轻量级的三维数据处理库。其采用C++11开发,同时与Python完美适配。作者本着usefulness and ease of use 的原则,对数据结构,和算法进行了精细的选择。没有依赖大型库如boost,PCL等,是一个小巧独立的三维点云库。笔者最喜欢的是其visualizer的设计,相比而言其可视化模块易用性比PCL的要高,同时还支持动画。

Open3D的安装

Open3D的所有源码在github上,使用者只需要按照其提供的安装教程,进行编译即可。这里需要注意的是,Ubuntu系统上对Python的选择默认的是Python3,那么还是使用Python2.7的用户需要手动将这两个变量修改一下,再进行编译即可。如果还有疑惑可以参阅Document

PYTHON_EXECUTABLE                /usr/bin/python
PYTHON_LIBRARY                   /usr/lib/x86_64-linux-gnu/libpython2.7.so 

Open3D的Python使用

由于整个项目还在持续的开发,所以作者希望大家使用其Python借口,而不是使用CPP接口。 编译完成之后,我们可以在build文件夹下面,找到lib文件夹,在下面的Tutorial文件夹有关于Python接口的全部教程。结合其官方的英文文档可以较轻松的学习整个项目的功能。笔者就不一一赘述了。下面主要介绍CPP接口的使用。

C++接口的使用

准备工作

不幸的是我们并没有一个CPP接口的官方教程,然后整个项目也没有提供make install的功能(不久以后,应该会有)。我们就需要没有条件创造条件也要上了。想要使用其CPP接口,首先需要头文件,然后我们需要库文件,.a或者.so。

头文件

我们首先提取头文件。万幸的是,Open3D的cmake文件写的很好,很有条理,我们可以发现在Core,IO,Visualization三个文件夹下面有分别关于这个模块的CMakeLists.txt文件,我们修改这些CMakeLists.txt文件。

Core模块CMakeLists.txt末尾添加
install(FILES ${headers} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Core)
install(FILES ${CORE_UTILITY_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Core/Utility)
install(FILES ${CORE_GEOMETRY_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Core/Geometry)
install(FILES ${CORE_CAMERA_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Core/Camera)
install(FILES ${CORE_REGISTRATION_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Core/Registration)
install(FILES ${CORE_ODOMETRY_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Core/Odometry)
install(FILES ${CORE_INTEGRATION_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Core/Integration)
IO模块CMakeLists.txt末尾添加
install(FILES ${headers} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/IO)
install(FILES ${IO_CLASSIO_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/IO/ClassIO)
install(FILES ${IO_FILEFORMAT_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/IO/FileFormat)
Visualization模块CMakeLists.txt末尾添加
install(FILES ${headers} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Visualization)
install(FILES ${VISUALIZATION_UTILITY_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Visualization/Utility)
install(FILES ${VISUALIZATION_SHADER_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Visualization/Shader)
install(FILES ${VISUALIZATION_VISUALIZER_HEADER_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/Visualization/Visualizer)

这样通过make install可以把所有的头文件放好。

库文件

然后关于库文件,这里我就偷了个懒,直接复制的,当然也可以通过修改CMakeLists.txt文件来实现。

构建.cmake文件

我们知道当使用一个用cmake来管理的CPP库的时候,可以使用find_package()来寻找这个库的头文件,库文件,然后生成对应的变量,用于接下来的项目编译。同样的我们希望使用find_package()来管理Open3D,这样我们就需要写一个.cmake文件。

FindOpen3d.cmake
include_directories(/usr/include/eigen3/)
include_directories(/usr/include/GL)

set(OPEN3D_INCLUDE_DIRS /home/sun/src/Open3D/install/include)
link_directories(/home/sun/src/Open3D/install/lib)


find_package(OpenMP)
if (OPENMP_FOUND)
    set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
    set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
    set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
endif()

set(OPEN3D_LIBRARIES
        Visualization
        IO
        Core
        IO
        pthread
        tinyfiledialogs
        jpeg
        png
        jsoncpp
        zlib
        /usr/lib/x86_64-linux-gnu/libglfw.so
        /usr/lib/x86_64-linux-gnu/libGLEW.so
        /usr/lib/x86_64-linux-gnu/libdl.so
        /usr/lib/x86_64-linux-gnu/librt.so
        /usr/lib/x86_64-linux-gnu/libm.so
        /usr/lib/x86_64-linux-gnu/libX11.so
        /usr/lib/x86_64-linux-gnu/libXrandr.so
        /usr/lib/x86_64-linux-gnu/libXinerama.so
        /usr/lib/x86_64-linux-gnu/libXi.so
        /usr/lib/x86_64-linux-gnu/libXxf86vm.so
        /usr/lib/x86_64-linux-gnu/libXcursor.so
        /usr/lib/x86_64-linux-gnu/libGL.so
        )

然后我们在写自己项目的CMakeLists.txt的时候将FindOpen3d.cmake所在的目录添加到CMAKE_MODULE_PATH环境变量中去。就可以使用find_package来配置Open3d库了。

Open3D之PointCloud

Open3D的CPP接口文档

点云是三维数据处理中的基本的数据结构。在经典的点云库(PCL)中,点云是一种比较复杂的数据结构,而在Open3D中点云较为简单。

点云继承关系

由图中可以看到,点云继承自Geometry3D而Geometry3D继承自Geometry。Geometry是一个纯粹的抽象类,也就是所有的几何数据类型都是以其为模板,它有一个枚举,包含了Unspecified,PointCloud,Lineset,TriangleMesh,Image。而Geometry3D则会有一些bound,和Transform的属性,更为具体。 PointCloud由数据点,法线,和颜色构成。结构较为简单。

// ----------------------------------------------------------------------------
// -                        Open3D: www.open3d.org                            -
// ----------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2018 www.open3d.org
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// ----------------------------------------------------------------------------

#include <iostream>
#include <memory>
#include <Eigen/Dense>

#include <Core/Core.h>
#include <IO/IO.h>
#include <Visualization/Visualization.h>

void PrintPointCloud(const three::PointCloud &pointcloud)
{
    using namespace three;

    bool pointcloud_has_normal = pointcloud.HasNormals();
    PrintInfo("Pointcloud has %d points.\n",
              (int)pointcloud.points_.size());

    Eigen::Vector3d min_bound = pointcloud.GetMinBound();
    Eigen::Vector3d max_bound = pointcloud.GetMaxBound();
    PrintInfo("Bounding box is: (%.4f, %.4f, %.4f) - (%.4f, %.4f, %.4f)\n",
              min_bound(0), min_bound(1), min_bound(2),
              max_bound(0), max_bound(1), max_bound(2));

    for (size_t i = 0; i < pointcloud.points_.size(); i++) {
        if (pointcloud_has_normal) {
            const Eigen::Vector3d &point = pointcloud.points_[i];
            const Eigen::Vector3d &normal = pointcloud.normals_[i];
            PrintDebug("%.6f %.6f %.6f %.6f %.6f %.6f\n",
                       point(0), point(1), point(2),
                       normal(0), normal(1), normal(2));
        } else {
            const Eigen::Vector3d &point = pointcloud.points_[i];
            PrintDebug("%.6f %.6f %.6f\n", point(0), point(1), point(2));
        }
    }
    PrintDebug("End of the list.\n\n");
}

int main(int argc, char *argv[])
{

    using namespace three;

    SetVerbosityLevel(VerbosityLevel::VerboseAlways);

    if(argc < 2)
    {
        PrintError("请给定点云文件!");
    }

    auto pcd = CreatePointCloudFromFile(argv[1]); //! 工厂函数
    {
        ScopeTimer timer("FPFH estimation with Radius 0.25"); //! 块计时器,在析构的时候打印耗时
        //for (int i = 0; i < 20; i++) {
        ComputeFPFHFeature(*pcd,
                           three::KDTreeSearchParamRadius(0.25));
        //}
    }

    {
        ScopeTimer timer("Normal estimation with KNN20");
        for (int i = 0; i < 20; i++) {
            EstimateNormals(*pcd,
                            three::KDTreeSearchParamKNN(20)); //! 已20个点来估算一个点的normal
        }
    }
    std::cout << pcd->normals_[0] << std::endl;
    std::cout << pcd->normals_[10] << std::endl;

    {
        ScopeTimer timer("Normal estimation with Radius 0.01666");
        for (int i = 0; i < 20; i++) {
            EstimateNormals(*pcd,
                            three::KDTreeSearchParamRadius(0.01666)); //! 半径来估算
        }
    }
    std::cout << pcd->normals_[0] << std::endl;
    std::cout << pcd->normals_[10] << std::endl;

    {
        ScopeTimer timer("Normal estimation with Hybrid 0.01666, 60");
        for (int i = 0; i < 20; i++) {
            EstimateNormals(*pcd,
                            three::KDTreeSearchParamHybrid(0.01666, 60)); //! 混合估算,第一个参数为半径,后面一个参数为数量
        }
    }
    std::cout << pcd->normals_[0] << std::endl;
    std::cout << pcd->normals_[10] << std::endl;

    auto downpcd = VoxelDownSample(*pcd, 0.05); //! 体素降采样,0.05代表最小的一个box长度为0.05

    // 1. test basic pointcloud functions.

    PointCloud pointcloud;
    PrintPointCloud(pointcloud);
    //! https://blog.csdn.net/xiaolewennofollow/article/details/52559364
    //! https://blog.csdn.net/yockie/article/details/52674366
    //! https://blog.csdn.net/kaitiren/article/details/22302579
    //! emplace_back只调用构造函数,不调用拷贝构造,和转移构造函数,速度很快
    pointcloud.points_.emplace_back(0.0, 0.0, 0.0);
    pointcloud.points_.emplace_back(1.0, 0.0, 0.0);
    pointcloud.points_.emplace_back(0.0, 1.0, 0.0);
    pointcloud.points_.emplace_back(0.0, 0.0, 1.0);
    PrintPointCloud(pointcloud);

    // 2. test pointcloud IO.

    const std::string filename_xyz("test.xyz");
    const std::string filename_ply("test.ply");

    if (ReadPointCloud(argv[1], pointcloud)) {
        PrintWarning("Successfully read %s\n", argv[1]);

        /*
        PointCloud pointcloud_copy;
        pointcloud_copy.CloneFrom(pointcloud);

        if (WritePointCloud(filename_xyz, pointcloud)) {
            PrintWarning("Successfully wrote %s\n\n", filename_xyz.c_str());
        } else {
            PrintError("Failed to write %s\n\n", filename_xyz.c_str());
        }

        if (WritePointCloud(filename_ply, pointcloud_copy)) {
            PrintWarning("Successfully wrote %s\n\n", filename_ply.c_str());
        } else {
            PrintError("Failed to write %s\n\n", filename_ply.c_str());
        }
         */
    } else {
        PrintError("Failed to read %s\n\n", argv[1]);
    }

    // 3. test pointcloud visualization

    Visualizer visualizer;
    std::shared_ptr<PointCloud> pointcloud_ptr(new PointCloud);
    *pointcloud_ptr = pointcloud;
    pointcloud_ptr->NormalizeNormals();
    BoundingBox bounding_box;
    bounding_box.FitInGeometry(*pointcloud_ptr);

    std::shared_ptr<PointCloud> pointcloud_transformed_ptr(new PointCloud);
    *pointcloud_transformed_ptr = *pointcloud_ptr;
    Eigen::Matrix4d trans_to_origin = Eigen::Matrix4d::Identity();
    trans_to_origin.block<3, 1>(0, 3) = bounding_box.GetCenter() * -1.0;
    Eigen::Matrix4d transformation = Eigen::Matrix4d::Identity();
    transformation.block<3, 3>(0, 0) = static_cast<Eigen::Matrix3d>(
            Eigen::AngleAxisd(M_PI / 4.0, Eigen::Vector3d::UnitX()));
    pointcloud_transformed_ptr->Transform(
            trans_to_origin.inverse() * transformation * trans_to_origin);
            //! 先把所有的点云归到原点,然后,绕着x轴旋转45度,然后再平移回去,总的来说就是绕着点云的中心点绕着x轴旋转45度

    visualizer.CreateWindow("Open3D", 1600, 900);
    visualizer.AddGeometry(pointcloud_ptr);
    visualizer.AddGeometry(pointcloud_transformed_ptr);
    visualizer.Run();
    visualizer.DestroyWindow();

    auto coord = CreateMeshCoordinateFrame(0.2);

    // 4. test operations
    *pointcloud_transformed_ptr += *pointcloud_ptr;
    DrawGeometries({pointcloud_transformed_ptr,coord}, "Combined Pointcloud");

    // 5. test downsample
    auto downsampled = VoxelDownSample(*pointcloud_ptr, 0.05);
    DrawGeometries({downsampled,coord}, "Down Sampled Pointcloud");

    // 6. test normal estimation
    //! https://segmentfault.com/a/1190000006212012
    //! [&] 表达式的意思是所有的局部变量将按照引用的方式进行传递
    DrawGeometriesWithKeyCallbacks({pointcloud_ptr},
                                   },
                                   "Press Space to Estimate Normal", 1600, 900);

    // n. test end

    PrintAlways("End of the test.\n");
}

Open3d之Lineset

Lineset顾名思义就是线段的集合,我们在显示的时候经常要连接匹配点对,以表示他们的关系。在open3d中我们可以方便的使用lineset来构建这些,无需复杂的调用绘制函数。

// ----------------------------------------------------------------------------
// -                        Open3D: www.open3d.org                            -
// ----------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2018 www.open3d.org
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// ----------------------------------------------------------------------------

#include <cstdio>
#include <vector>
#include <Eigen/Dense>

#include <Core/Core.h>
#include <IO/IO.h>
#include <Visualization/Visualization.h>

int main(int argc, char **argv)
{
    using namespace three;
    using namespace flann;

    SetVerbosityLevel(VerbosityLevel::VerboseAlways);

    if (argc < 2) {
        PrintInfo("Usage:\n");
        PrintInfo("    > TestFlann [filename]\n");
        PrintInfo("    The program will :\n");
        PrintInfo("    1. load the pointcloud in [filename].\n");
        PrintInfo("    2. use KDTreeFlann to compute 50 nearest neighbors of point0.\n");
        PrintInfo("    3. convert the correspondences to LineSet and render it.\n");
        PrintInfo("    4. rotate the point cloud slightly to get another point cloud.\n");
        PrintInfo("    5. find closest point of the original point cloud on the new point cloud, mark as correspondences.\n");
        PrintInfo("    6. convert to LineSet and render it.\n");
        PrintInfo("    6. distance below 0.05 are rendered as red, others as black.\n");
        return 0;
    }

    auto cloud_ptr = CreatePointCloudFromFile(argv[1]);
    std::vector<std::pair<int, int>> correspondences;

    const int nn = 50;
    KDTreeFlann kdtree; //! 构建kdtree
    kdtree.SetGeometry(*cloud_ptr);
    std::vector<int> indices_vec(nn);
    std::vector<double> dists_vec(nn);
    kdtree.SearchKNN(cloud_ptr->points_[0], nn, indices_vec, dists_vec);
    for (int i = 0; i < nn; i++) {
        correspondences.emplace_back(0, indices_vec[i]);
    }
    //! 构建线集合,两个点云之间的对应点连线
    auto lineset_ptr = CreateLineSetFromPointCloudCorrespondences(
            *cloud_ptr, *cloud_ptr, correspondences);
    DrawGeometries({cloud_ptr, lineset_ptr});

    //! http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared
    //! 用make_shared要比使用new来得快
    //! https://www.cnblogs.com/egmkang/archive/2013/04/28/3049102.html
    //! http://tech-foo.blogspot.com/2012/04/experimenting-with-c-stdmakeshared.html
    auto new_cloud_ptr = std::make_shared<PointCloud>();
    *new_cloud_ptr = *cloud_ptr;
    BoundingBox bounding_box;
    bounding_box.FitInGeometry(*new_cloud_ptr); //! 计算bounding box
    //! 作者用了一个比较有趣的构建方法
    Eigen::Matrix4d trans_to_origin = Eigen::Matrix4d::Identity();
    trans_to_origin.block<3, 1>(0, 3) = bounding_box.GetCenter() * -1.0;
    Eigen::Matrix4d transformation = Eigen::Matrix4d::Identity();
    transformation.block<3, 3>(0, 0) = static_cast<Eigen::Matrix3d>(
            Eigen::AngleAxisd(M_PI / 6.0, Eigen::Vector3d::UnitX()));
    new_cloud_ptr->Transform(
            trans_to_origin.inverse() * transformation * trans_to_origin);
    correspondences.clear();
    //! 对原始点云的所有的点都找一下,原来点云旋转过后的最近的点
    for (size_t i = 0; i < new_cloud_ptr->points_.size(); i++) {
        kdtree.SearchKNN(new_cloud_ptr->points_[i], 1, indices_vec, dists_vec);
        correspondences.emplace_back(indices_vec[0], (int)i);
    }
    auto new_lineset_ptr = CreateLineSetFromPointCloudCorrespondences(
            *cloud_ptr, *new_cloud_ptr, correspondences);
    new_lineset_ptr->colors_.resize(new_lineset_ptr->lines_.size());
    //! 上色,点点间距小于0.05×包围盒尺寸的上红色,其他的上黑色。
    for (size_t i = 0; i < new_lineset_ptr->lines_.size(); i++) {
        auto point_pair = new_lineset_ptr->GetLineCoordinate(i);
        if ((point_pair.first - point_pair.second).norm() < 0.05 *
                                                            bounding_box.GetSize()) {
            new_lineset_ptr->colors_[i] = Eigen::Vector3d(1.0, 0.0, 0.0);
        } else {
            new_lineset_ptr->colors_[i] = Eigen::Vector3d(0.0, 0.0, 0.0);
        }
    }
    //! 由于对3维空间的元素很好的抽象了,所以点云与线集都可以直接放到visualizer中显示。
    DrawGeometries({cloud_ptr, new_cloud_ptr, new_lineset_ptr});

    return 0;
}

Open3D之Image

在Open3D中,图像是以std::vector<uint8_t> data_;存储的,作者使用了一些指针的方法可以将其存储float也可以将其存储uint_8等等。我们来看测试代码

// ----------------------------------------------------------------------------
// -                        Open3D: www.open3d.org                            -
// ----------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2018 www.open3d.org
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// ----------------------------------------------------------------------------

#include <cstdio>

#include <Core/Core.h>
#include <IO/IO.h>

int main(int argc, char **argv)
{
    using namespace three;

    SetVerbosityLevel(VerbosityLevel::VerboseAlways);

    if (argc != 3) {
        PrintInfo("Usage:\n");
        PrintInfo("    > TestImage [image filename] [depth filename]\n");
        PrintInfo("    The program will :\n");
        PrintInfo("    1) Read 8bit RGB and 16bit depth image\n");
        PrintInfo("    2) Convert RGB image to single channel float image\n");
        PrintInfo("    3) 3x3, 5x5, 7x7 Gaussian filters are applied\n");
        PrintInfo("    4) 3x3 Sobel filter for x-and-y-directions are applied\n");
        PrintInfo("    5) Make image pyramid that includes Gaussian blur and downsampling\n");
        PrintInfo("    6) Will save all the layers in the image pyramid\n");
        return 0;
    }

    const std::string filename_rgb(argv[1]);
    const std::string filename_depth(argv[2]);
    //! 彩色图像都是8位的
    Image color_image_8bit;
    if (ReadImage(filename_rgb, color_image_8bit)) {

        PrintDebug("RGB image size : %d x %d\n",
                   color_image_8bit.width_, color_image_8bit.height_);
        //! 灰度化 通过灰度化算法讲8位的彩色图像变成float图像
        auto gray_image = CreateFloatImageFromImage(color_image_8bit);
        WriteImage("gray.png",//! 存储的时候再次恢复位图
                   *CreateImageFromFloatImage<uint8_t>(*gray_image));
        //! https://www.cnblogs.com/qiqibaby/p/5289977.html
        //! 高斯滤波,3,5,7代表的是模板的大小,模板越大那么图像就越模糊
        //! 需要对灰度图像进行操作
        PrintDebug("Gaussian Filtering\n");
        auto gray_image_b3 = FilterImage(*gray_image,
                                         Image::FilterType::Gaussian3);
        WriteImage("gray_blur3.png",
                   *CreateImageFromFloatImage<uint8_t>(*gray_image_b3));
        auto gray_image_b5 = FilterImage(*gray_image,
                                         Image::FilterType::Gaussian5);
        WriteImage("gray_blur5.png",
                   *CreateImageFromFloatImage<uint8_t>(*gray_image_b5));
        auto gray_image_b7 = FilterImage(*gray_image,
                                         Image::FilterType::Gaussian7);
        WriteImage("gray_blur7.png",
                   *CreateImageFromFloatImage<uint8_t>(*gray_image_b7));
        //! https://en.wikipedia.org/wiki/Sobel_operator
        //! Sobel算子用来近似计算一阶梯度。
        PrintDebug("Sobel Filtering\n");
        auto gray_image_dx = FilterImage(*gray_image,
                                         Image::FilterType::Sobel3Dx);
        // make [-1,1] to [0,1].
        LinearTransformImage(*gray_image_dx, 0.5, 0.5);
        ClipIntensityImage(*gray_image_dx);
        WriteImage("gray_sobel_dx.png",
                   *CreateImageFromFloatImage<uint8_t>(*gray_image_dx));
        auto gray_image_dy = FilterImage(*gray_image,
                                         Image::FilterType::Sobel3Dy);
        LinearTransformImage(*gray_image_dy, 0.5, 0.5);
        ClipIntensityImage(*gray_image_dy);
        WriteImage("gray_sobel_dy.png",
                   *CreateImageFromFloatImage<uint8_t>(*gray_image_dy));
        //! https://en.wikipedia.org/wiki/Pyramid_(image_processing)
        //! 创建了一个四层的图像金字塔
        PrintDebug("Build Pyramid\n");
        auto pyramid = CreateImagePyramid(*gray_image, 4);
        for (int i = 0; i < 4; i++) {
            auto level = pyramid[i];
            auto level_8bit = CreateImageFromFloatImage<uint8_t>(*level);
            std::string outputname =
                    "gray_pyramid_level" + std::to_string(i) + ".png";
            WriteImage(outputname, *level_8bit);
        }
    } else {
        PrintError("Failed to read %s\n\n", filename_rgb.c_str());
    }
    //! 深度图像是16位的
    Image depth_image_16bit;
    if (ReadImage(filename_depth, depth_image_16bit)) {

        PrintDebug("Depth image size : %d x %d\n",
                   depth_image_16bit.width_, depth_image_16bit.height_);
        auto depth_image = CreateFloatImageFromImage(depth_image_16bit);
        WriteImage("depth.png",
                   *CreateImageFromFloatImage<uint16_t>(*depth_image));

        PrintDebug("Gaussian Filtering\n");
        auto depth_image_b3 = FilterImage(*depth_image,
                                          Image::FilterType::Gaussian3);
        WriteImage("depth_blur3.png",
                   *CreateImageFromFloatImage<uint16_t>(*depth_image_b3));
        auto depth_image_b5 = FilterImage(*depth_image,
                                          Image::FilterType::Gaussian5);
        WriteImage("depth_blur5.png",
                   *CreateImageFromFloatImage<uint16_t>(*depth_image_b5));
        auto depth_image_b7 = FilterImage(*depth_image,
                                          Image::FilterType::Gaussian7);
        WriteImage("depth_blur7.png",
                   *CreateImageFromFloatImage<uint16_t>(*depth_image_b7));

        PrintDebug("Sobel Filtering\n");
        auto depth_image_dx = FilterImage(*depth_image,
                                          Image::FilterType::Sobel3Dx);
        // make [-65536,65536] to [0,13107.2]. // todo: need to test this
        LinearTransformImage(*depth_image_dx, 0.1, 6553.6);
        ClipIntensityImage(*depth_image_dx, 0.0, 13107.2);
        WriteImage("depth_sobel_dx.png",
                   *CreateImageFromFloatImage<uint16_t>(*depth_image_dx));
        auto depth_image_dy = FilterImage(*depth_image,
                                          Image::FilterType::Sobel3Dy);
        LinearTransformImage(*depth_image_dy, 0.1, 6553.6);
        ClipIntensityImage(*depth_image_dx, 0.0, 13107.2);
        WriteImage("depth_sobel_dy.png",
                   *CreateImageFromFloatImage<uint16_t>(*depth_image_dy));

        PrintDebug("Build Pyramid\n");
        auto pyramid = CreateImagePyramid(*depth_image, 4);
        for (int i = 0; i < 4; i++) {
            auto level = pyramid[i];
            auto level_16bit = CreateImageFromFloatImage<uint16_t>(*level);
            std::string outputname =
                    "depth_pyramid_level" + std::to_string(i) + ".png";
            WriteImage(outputname, *level_16bit);
        }
    }
    else {
        PrintError("Failed to read %s\n\n", filename_depth.c_str());
    }

    return 0;
}

Open3d之TriangleMesh

mesh就是将点云中的点三角化连接起来了。

// ----------------------------------------------------------------------------
// -                        Open3D: www.open3d.org                            -
// ----------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2018 www.open3d.org
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// ----------------------------------------------------------------------------

#include <iostream>

#include <Core/Core.h>
#include <IO/IO.h>
#include <Visualization/Visualization.h>

void PrintHelp()
{
    using namespace three;
    PrintInfo("Usage :\n");
    PrintInfo("    > TestTriangleMesh sphere\n");
    PrintInfo("    > TestTriangleMesh merge <file1> <file2>\n");
    PrintInfo("    > TestTriangleMesh normal <file1> <file2>\n");
}

void PaintMesh(three::TriangleMesh &mesh, const Eigen::Vector3d &color)
{
    mesh.vertex_colors_.resize(mesh.vertices_.size());
    for (size_t i = 0; i < mesh.vertices_.size(); i++) {
        mesh.vertex_colors_[i] = color;
    }
}

int main(int argc, char *argv[])
{
    using namespace three;

    SetVerbosityLevel(VerbosityLevel::VerboseAlways);

    if (argc < 2) {
        PrintHelp();
        return 0;
    }
    //! 这边我们学习mesh
    //! 作者为我们内置了一些简单的几何体,如球,圆柱,圆锥,箭头,坐标系
    std::string option(argv[1]);
    if (option == "sphere") {
        auto mesh = CreateMeshSphere(0.05);
        mesh->ComputeVertexNormals();
        DrawGeometries({mesh});
        WriteTriangleMesh("sphere.ply", *mesh, true, true);
    } else if (option == "cylinder") {
        auto mesh = CreateMeshCylinder(0.5, 2.0);
        mesh->ComputeVertexNormals();
        DrawGeometries({mesh});
        WriteTriangleMesh("cylinder.ply", *mesh, true, true);
    } else if (option == "cone") {
        auto mesh = CreateMeshCone(0.5, 2.0, 20, 3);
        mesh->ComputeVertexNormals();
        DrawGeometries({mesh});
        WriteTriangleMesh("cone.ply", *mesh, true, true);
    } else if (option == "arrow") {
        auto mesh = CreateMeshArrow();
        mesh->ComputeVertexNormals();
        DrawGeometries({mesh});
        WriteTriangleMesh("arrow.ply", *mesh, true, true);
    } else if (option == "frame") {
        if (argc < 3) {
            auto mesh = CreateMeshCoordinateFrame();
            DrawGeometries({mesh});
            WriteTriangleMesh("frame.ply", *mesh, true, true);
        } else {
            //! 导入mesh,并为mesh计算normal
            auto mesh = CreateMeshFromFile(argv[2]);
            mesh->ComputeVertexNormals();
            BoundingBox boundingbox(*mesh);
            auto mesh_frame = CreateMeshCoordinateFrame(
                    boundingbox.GetSize() * 0.2, boundingbox.min_bound_);
            //! 绘制mesh加上坐标系
            DrawGeometries({mesh, mesh_frame});
        }
    } else if (option == "merge") {
        auto mesh1 = CreateMeshFromFile(argv[2]);
        auto mesh2 = CreateMeshFromFile(argv[3]);
        PrintInfo("Mesh1 has %d vertices, %d triangles.\n",
                  mesh1->vertices_.size(), mesh1->triangles_.size());
        PrintInfo("Mesh2 has %d vertices, %d triangles.\n",
                  mesh2->vertices_.size(), mesh2->triangles_.size());
        //! 重载操作符+=后,可以合并mesh
        *mesh1 += *mesh2;
        PrintInfo("After merge, Mesh1 has %d vertices, %d triangles.\n",
                  mesh1->vertices_.size(), mesh1->triangles_.size());
        /// Function to remove duplicated and non-manifold vertices/triangles
        mesh1->Purge();
        PrintInfo("After purge vertices, Mesh1 has %d vertices, %d triangles.\n",
                  mesh1->vertices_.size(), mesh1->triangles_.size());
        DrawGeometries({mesh1});
        WriteTriangleMesh("temp.ply", *mesh1, true, true);
    } else if (option == "normal") {
        auto mesh = CreateMeshFromFile(argv[2]);
        mesh->ComputeVertexNormals();
        WriteTriangleMesh(argv[3], *mesh, true, true);
        /// 对mesh放大
    } else if (option == "scale") {
        auto mesh = CreateMeshFromFile(argv[2]);
        double scale = std::stod(argv[4]);
        Eigen::Matrix4d trans = Eigen::Matrix4d::Identity();
        trans(0, 0) = trans(1, 1) = trans(2, 2) = scale;
        mesh->Transform(trans);
        WriteTriangleMesh(argv[3], *mesh);
        /// 将mesh归一到一个统一的boundingbox,同时把中心也移动过去
    } else if (option == "unify") {
        /// unify into (0, 0, 0) - (scale, scale, scale) box
        auto mesh = CreateMeshFromFile(argv[2]);
        BoundingBox bbox;
        bbox.FitInGeometry(*mesh);
        double scale1 = std::stod(argv[4]);
        double scale2 = std::stod(argv[5]);
        Eigen::Matrix4d trans = Eigen::Matrix4d::Identity();
        trans(0, 0) = trans(1, 1) = trans(2, 2) = scale1 / bbox.GetSize();
        mesh->Transform(trans);
        trans.setIdentity();
        trans.block<3, 1>(0, 3) = Eigen::Vector3d(scale2 / 2.0, scale2 / 2.0,
                                                  scale2 / 2.0) - bbox.GetCenter() * scale1 / bbox.GetSize();
        mesh->Transform(trans);
        WriteTriangleMesh(argv[3], *mesh);
    } else if (option == "distance") {
        auto mesh1 = CreateMeshFromFile(argv[2]);
        auto mesh2 = CreateMeshFromFile(argv[3]);
        double scale = std::stod(argv[4]);
        mesh1->vertex_colors_.resize(mesh1->vertices_.size());
        KDTreeFlann kdtree;
        kdtree.SetGeometry(*mesh2);
        std::vector<int> indices(1);
        std::vector<double> dists(1);
        double r = 0.0;
        /// 距离越大颜色越深
        for (size_t i = 0; i < mesh1->vertices_.size(); i++) {
            kdtree.SearchKNN(mesh1->vertices_[i], 1, indices, dists);
            double color = std::min(sqrt(dists[0]) / scale, 1.0);
            mesh1->vertex_colors_[i] = Eigen::Vector3d(color, color, color);
            r += sqrt(dists[0]);
        }
        PrintInfo("Average distance is %.6f.\n",
                  r / (double)mesh1->vertices_.size());
        if (argc > 5) {
            WriteTriangleMesh(argv[5], *mesh1);
        }
        DrawGeometries({mesh1});
    } else if (option == "showboth") {
        auto mesh1 = CreateMeshFromFile(argv[2]);
        PaintMesh(*mesh1, Eigen::Vector3d(1.0, 0.75, 0.0));
        auto mesh2 = CreateMeshFromFile(argv[3]);
        PaintMesh(*mesh2, Eigen::Vector3d(0.25, 0.25, 1.0));
        std::vector<std::shared_ptr<const Geometry>> meshes;
        meshes.push_back(mesh1);
        meshes.push_back(mesh2);
        DrawGeometries(meshes);
    } else if (option == "colormapping") {
        auto mesh = CreateMeshFromFile(argv[2]);
        mesh->ComputeVertexNormals();
        /// 相机轨迹,外参序列
        PinholeCameraTrajectory trajectory;
        ReadIJsonConvertible(argv[3], trajectory);
        if (filesystem::DirectoryExists("image") == false) {
            PrintWarning("No image!\n");
            return 0;
        }
        int idx = 3000;
        std::vector<std::shared_ptr<const Geometry>> ptrs;
        ptrs.push_back(mesh);
        auto mesh_sphere = CreateMeshSphere(0.05);
        Eigen::Matrix4d trans;
        trans.setIdentity();
        trans.block<3, 1>(0, 3) = mesh->vertices_[idx];
        mesh_sphere->Transform(trans);
        mesh_sphere->ComputeVertexNormals();
        ptrs.push_back(mesh_sphere);
        DrawGeometries(ptrs);

        for (size_t i = 0; i < trajectory.extrinsic_.size(); i += 10) {
            char buffer[1024];
            sprintf(buffer, "image/image_%06d.png", (int)i + 1);
            auto image = CreateImageFromFile(buffer);
            auto fimage = CreateFloatImageFromImage(*image);
            /// 将球上面的顶点变换到相机坐标系下面
            Eigen::Vector4d pt_in_camera = trajectory.extrinsic_[i] *
                                           Eigen::Vector4d(mesh->vertices_[idx](0),
                                                           mesh->vertices_[idx](1), mesh->vertices_[idx](2), 1.0);
            /// 用相机内参投影
            Eigen::Vector3d pt_in_plane =
                    trajectory.intrinsic_.intrinsic_matrix_ *
                    pt_in_camera.block<3, 1>(0, 0);
            Eigen::Vector3d uv = pt_in_plane / pt_in_plane(2);
            std::cout << pt_in_camera << std::endl;
            std::cout << pt_in_plane << std::endl;
            std::cout << pt_in_plane / pt_in_plane(2) << std::endl;
            /// 获取图像中的点
            auto result = fimage->FloatValueAt(uv(0), uv(1));
            if (result.first) {
                PrintWarning("%.6f\n", result.second);
            }
            DrawGeometries({fimage}, "Test", 1920, 1080);
        }
    }
}

Open3D之Flann

啥也不说了上代码,比较简单,自己对flann也没有什么研究。总共测试了原生的flann方法和Open3D实现的Kdtree

// ----------------------------------------------------------------------------
// -                        Open3D: www.open3d.org                            -
// ----------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2018 www.open3d.org
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// ----------------------------------------------------------------------------

#include <cstdio>
#include <vector>

#include <flann/flann.hpp>
#include <Core/Core.h>
#include <IO/IO.h>
#include <Visualization/Visualization.h>

int main(int argc, char **argv)
{
    using namespace three;
    using namespace flann;

    if (argc < 2) {
        PrintInfo("Usage:\n");
        PrintInfo("    > TestFlann [filename]\n");
        return 0;
    }

    auto cloud_ptr = std::make_shared<PointCloud>();
    if (ReadPointCloud(argv[1], *cloud_ptr)) {
        PrintWarning("Successfully read %s\n", argv[1]);
    } else {
        PrintError("Failed to read %s\n\n", argv[1]);
        return 0;
    }

    if ((int)cloud_ptr->points_.size() < 100) {
        PrintError("Boring point cloud.\n");
        return 0;
    }

    if (!cloud_ptr->HasColors()) {
        cloud_ptr->colors_.resize(cloud_ptr->points_.size());
        for (size_t i = 0; i < cloud_ptr->points_.size(); i++) {
            cloud_ptr->colors_[i].setZero(); /// 涂黑
        }
    }

    int nn = std::min(20, (int)cloud_ptr->points_.size() - 1);
    /// https://eigen.tuxfamily.org/dox/group__QuickRefPage.html
    Matrix<double> dataset((double *)cloud_ptr->points_.data(),
                           cloud_ptr->points_.size(), 3 );
    Matrix<double> query((double *)cloud_ptr->points_.data(), 1, 3);
    std::vector<int> indices_vec(nn);
    std::vector<double> dists_vec(nn);
    Matrix<int> indices(indices_vec.data(), query.rows, nn);
    Matrix<double> dists(dists_vec.data(), query.rows, nn);
    /// 使用flann原生的nn搜索
    Index<L2<double>> index(dataset, KDTreeSingleIndexParams(10));
    index.buildIndex();
    index.knnSearch(query, indices, dists, nn, SearchParams(-1, 0.0));

    for (size_t i = 0; i < indices_vec.size(); i++) {
        PrintInfo("%d, %f\n", (int)indices_vec[i], sqrt(dists_vec[i]));
        cloud_ptr->colors_[indices_vec[i]] = Eigen::Vector3d(1.0, 0.0, 0.0);
    }

    cloud_ptr->colors_[0] = Eigen::Vector3d(0.0, 1.0, 0.0);

    float r = float(sqrt(dists_vec[nn - 1]) * 2.0);
    Matrix<double> query1((double *)cloud_ptr->points_.data() + 3 * 99, 1, 3);
    /// 使用flann的半径搜索
    int k = index.radiusSearch(query1, indices, dists, r * r,
                               SearchParams(-1, 0.0));

    PrintInfo("======== %d, %f ========\n", k, r);
    for (int i = 0; i < k; i++) {
        PrintInfo("%d, %f\n", (int)indices_vec[i], sqrt(dists_vec[i]));
        cloud_ptr->colors_[indices_vec[i]] = Eigen::Vector3d(0.0, 0.0, 1.0);
    }
    cloud_ptr->colors_[99] = Eigen::Vector3d(0.0, 1.0, 1.0);

    DrawGeometries({cloud_ptr}, "TestFlann", 1600, 900);
    /// 使用Open3d的kdtree接口
    auto new_cloud_ptr = std::make_shared<PointCloud>();
    if (ReadPointCloud(argv[1], *new_cloud_ptr)) {
        PrintWarning("Successfully read %s\n", argv[1]);
    } else {
        PrintError("Failed to read %s\n\n", argv[1]);
        return 0;
    }

    if ((int)new_cloud_ptr->points_.size() < 100) {
        PrintError("Boring point cloud.\n");
        return 0;
    }

    if (new_cloud_ptr->HasColors() == false) {
        new_cloud_ptr->colors_.resize(new_cloud_ptr->points_.size());
        for (size_t i = 0; i < new_cloud_ptr->points_.size(); i++) {
            new_cloud_ptr->colors_[i].setZero();
        }
    }
    /// 构建kdtree
    KDTreeFlann kdtree;
    kdtree.SetGeometry(*new_cloud_ptr);
    std::vector<int> new_indices_vec(nn);
    std::vector<double> new_dists_vec(nn);
    kdtree.SearchKNN(new_cloud_ptr->points_[0], nn,
                     new_indices_vec, new_dists_vec);

    for (size_t i = 0; i < new_indices_vec.size(); i++) {
        PrintInfo("%d, %f\n", (int)new_indices_vec[i], sqrt(new_dists_vec[i]));
        new_cloud_ptr->colors_[new_indices_vec[i]] =
                Eigen::Vector3d(1.0, 0.0, 0.0);
    }

    new_cloud_ptr->colors_[0] = Eigen::Vector3d(0.0, 1.0, 0.0);

    k = kdtree.SearchRadius(new_cloud_ptr->points_[99], r, new_indices_vec,
                            new_dists_vec);

    PrintInfo("======== %d, %f ========\n", k, r);
    for (int i = 0; i < k; i++) {
        PrintInfo("%d, %f\n", (int)new_indices_vec[i], sqrt(new_dists_vec[i]));
        new_cloud_ptr->colors_[new_indices_vec[i]] =
                Eigen::Vector3d(0.0, 0.0, 1.0);
    }
    new_cloud_ptr->colors_[99] = Eigen::Vector3d(0.0, 1.0, 1.0);

    k = kdtree.Search(new_cloud_ptr->points_[199], KDTreeSearchParamRadius(r),
                      new_indices_vec, new_dists_vec);

    PrintInfo("======== %d, %f ========\n", k, r);
    for (int i = 0; i < k; i++) {
        PrintInfo("%d, %f\n", (int)new_indices_vec[i], sqrt(new_dists_vec[i]));
        new_cloud_ptr->colors_[new_indices_vec[i]] =
                Eigen::Vector3d(0.0, 0.0, 1.0);
    }
    new_cloud_ptr->colors_[199] = Eigen::Vector3d(0.0, 1.0, 1.0);

    DrawGeometries({new_cloud_ptr}, "TestKDTree", 1600, 900);
    return 0;
}