赞
踩
2021SC@SDUSC
Open3D是一个非常棒的点云处理库,包含一系列常用的点云处理函数,而且代码非常干净易读。
本篇解读PointCloud和PointCloudIO两个与点云有关的基类。
点云类。点云由点坐标以及可选的点颜色和点法线组成。
注意点:
由于需要解析的代码较多,为使代码解读更加清晰,我将代码分析的详细过程写在代码段的注释中。
中文部分是源码解读,包含代码分析和遇到的问题。
类中部分函数(由于PointCloud类中函数较多,因此展示部分关键函数):
Clear函数必须要有,因为基类Geometry中定义了纯虚函数。
VoxelDownSample,向下采样输入点云到输出点云与体素的函数。
VoxelDownSampleAndTrace,函数使用geometry.PointCloud.VoxelDownSample进行降低取样(缩小取样)。并且记录下采样前的点云指数。
UniformDownSample,将输入点云统一下采样到输出点云的函数。采样按照点的顺序执行,总是选择第0个点,而不是随机的。
RandomDownSample,将输入点云随机下采样到输出点云的函数。
ComputeMahalanobisDistance,计算输入点云中点的马氏距离的函数。
ComputeNearestNeighborDistance,计算从一个点到输入点云中它最近的邻居的距离的函数。
ComputeConvexHull,利用qhull算法计算点云的凸包的函数。
ClusterDBSCAN,点云集群使用DBSCAN算法。
SegmentPlane,使用RANSAC算法分割点云平面。
#pragma once #include <Eigen/Core> #include <memory> #include <tuple> #include <vector> #include "open3d/geometry/Geometry3D.h" #include "open3d/geometry/KDTreeSearchParam.h" namespace open3d { namespace camera { class PinholeCameraIntrinsic; } namespace geometry { class Image; class RGBDImage; class TriangleMesh; class VoxelGrid; /// \class PointCloud /// 点云由点坐标、点颜色和点法线组成。 class PointCloud : public Geometry3D { public: /// 默认构造函数。 PointCloud() : Geometry3D(Geometry::GeometryType::PointCloud) {} /// 参数化构造函数。 /// \param points 点坐标。 PointCloud(const std::vector<Eigen::Vector3d> &points) : Geometry3D(Geometry::GeometryType::PointCloud), points_(points) {} ~PointCloud() override {} public: /// Clear函数必须要有,因为基类Geometry中定义了纯虚函数。 /// 参考资料:https://blog.csdn.net/hou09tian/article/details/108862875 /// 当成员函数返回的为*this时,就写Geometry3D&,表示返回值是调用该成员函数的变量的引用 PointCloud &Clear() override; bool IsEmpty() const override; Eigen::Vector3d GetMinBound() const override; Eigen::Vector3d GetMaxBound() const override; Eigen::Vector3d GetCenter() const override; AxisAlignedBoundingBox GetAxisAlignedBoundingBox() const override; OrientedBoundingBox GetOrientedBoundingBox() const override; PointCloud &Transform(const Eigen::Matrix4d &transformation) override; PointCloud &Translate(const Eigen::Vector3d &translation, bool relative = true) override; PointCloud &Scale(const double scale, const Eigen::Vector3d ¢er) override; PointCloud &Rotate(const Eigen::Matrix3d &R, const Eigen::Vector3d ¢er) override; PointCloud &operator+=(const PointCloud &cloud); PointCloud operator+(const PointCloud &cloud) const; /// 如果点云包含点,返回true。 bool HasPoints() const { return points_.size() > 0; } /// 如果点云包含点法线,返回true。 bool HasNormals() const { return points_.size() > 0 && normals_.size() == points_.size(); } /// 如果点云包含点颜色,返回true。 bool HasColors() const { return points_.size() > 0 && colors_.size() == points_.size(); } /// 如果点云包含每个点的协方差矩阵,则返回true。 bool HasCovariances() const { return !points_.empty() && covariances_.size() == points_.size(); } /// 归一化点法线长度。 PointCloud &NormalizeNormals() { for (size_t i = 0; i < normals_.size(); i++) { normals_[i].normalize(); } return *this; } /// 为PointCloud中的每个点分配相同的颜色。 /// /// \param color RGB颜色的点。 PointCloud &PaintUniformColor(const Eigen::Vector3d &color) { ResizeAndPaintUniformColor(colors_, points_.size(), color); return *this; } ///删除点云中所有具有nan entry或infinite entries的点。 ///并且删除相应的法线和颜色条目。 /// /// \param remove_nan 从PointCloud中删除NaN值。 /// \param remove_infinite 从PointCloud中删除infinite值。 PointCloud &RemoveNonFinitePoints(bool remove_nan = true, bool remove_infinite = true); /// 选择点从输入点云到输出点云的函数。 /// 有被选中的指标的点。 /// /// \param indices 将要选择的点的指标。 /// \param invert 设置为True时,反转索引的选择。 std::shared_ptr<PointCloud> SelectByIndex( const std::vector<size_t> &indices, bool invert = false) const; /// 向下采样输入点云到输出点云与体素的函数。 /// normal和colors如果存在,则取平均值。 /// /// \param voxel_size 定义体素网格的分辨率,值越小输出点云越密集。 std::shared_ptr<PointCloud> VoxelDownSample(double voxel_size) const; /// 函数使用geometry.PointCloud.VoxelDownSample进行降低取样(缩小取样)。 /// 并且记录下采样前的点云指数。 /// /// \param voxel_size 下采样到的体素大小。 /// \param min_bound 体素边界的最小坐标 /// \param max_bound 体素边界的最大坐标 /// \param approximate_class 是否近似。 std::tuple<std::shared_ptr<PointCloud>, Eigen::MatrixXi, std::vector<std::vector<int>>> VoxelDownSampleAndTrace(double voxel_size, const Eigen::Vector3d &min_bound, const Eigen::Vector3d &max_bound, bool approximate_class = false) const; /// 将输入点云统一下采样到输出点云的函数。 /// 采样按照点的顺序执行,总是选择第0个点,而不是随机的。 /// /// \param every_k_points 采样率,选择的点指标是[0,k,2k,…]。 std::shared_ptr<PointCloud> UniformDownSample(size_t every_k_points) const; /// 将输入点云随机下采样到输出点云的函数。 /// 采样是通过随机选择点云中点的索引来执行的。 /// /// \param sampling_ratio 采样是通过随机选择点云中点的索引来执行的。 std::shared_ptr<PointCloud> RandomDownSample(double sampling_ratio) const; /// ???剪裁点云到输出点云的函数。 /// 所有坐标在边界框bbox之外的点都会被剪切。 /// /// \param bbox AxisAlignedBoundingBox to crop points. std::shared_ptr<PointCloud> Crop(const AxisAlignedBoundingBox &bbox) const; /// ???剪裁点云到输出点云的函数。 /// 所有坐标在边界框bbox之外的点都会被剪切。 /// /// \param bbox OrientedBoundingBox to crop points. std::shared_ptr<PointCloud> Crop(const OrientedBoundingBox &bbox) const; /// 移除给定半径范围内小于nb_points的点的函数。 /// /// \param nb_points 半径内的点数。 /// \param search_radius 球面的半径。 std::tuple<std::shared_ptr<PointCloud>, std::vector<size_t>> RemoveRadiusOutliers(size_t nb_points, double search_radius) const; /// 移除给定半径范围内小于nb_points的点的函数。 /// /// \param nb_neighbors 半径内的点数。 /// \param std_ratio 球体的半径。 std::tuple<std::shared_ptr<PointCloud>, std::vector<size_t>> RemoveStatisticalOutliers(size_t nb_neighbors, double std_ratio) const; /// 计算点云的法线的函数。 /// 如果存在法线,则法线是针对输入点云的。 /// /// \param search_param KDTree的邻居搜索参数。 /// \param fast_normal_computation 如果为真,则正态估计使用非迭代方法从协方差矩阵中提取特征向量。这更快,但在数值上不那么稳定。 void EstimateNormals( const KDTreeSearchParam &search_param = KDTreeSearchParamKNN(), bool fast_normal_computation = true); /// 定向点云的法线的函数。 /// /// \param orientation_reference 法线是相对于orientation_reference定向的。 void OrientNormalsToAlignWithDirection( const Eigen::Vector3d &orientation_reference = Eigen::Vector3d(0.0, 0.0, 1.0)); /// 通过另一种方法定向点云的法线的函数。 /// \param camera_location 法线指向camera_location。 void OrientNormalsTowardsCameraLocation( const Eigen::Vector3d &camera_location = Eigen::Vector3d::Zero()); /// 按照Hoppe等人在1992年“从无组织点进行曲面重构”中描述的,基于统一的切平面的统一定向估计法线的函数。 /// /// \param k k最近邻,用于正常传播的图重构。 void OrientNormalsConsistentTangentPlane(size_t k); /// 计算点云之间的点对点距离的函数。 /// 对于源点云中的每个点,计算到目标点云的距离。 /// /// \param target 目标点云。 std::vector<double> ComputePointCloudDistance(const PointCloud &target); /// 计算点云中每一点的协方差矩阵的静态函数。该函数不会改变PointCloud的输入,只是输出协方差矩阵。 /// /// /// \param input 用于协方差计算的PointCloud。 /// \param search_param 用于邻域搜索的KDTree搜索参数。 static std::vector<Eigen::Matrix3d> EstimatePerPointCovariances( const PointCloud &input, const KDTreeSearchParam &search_param = KDTreeSearchParamKNN()); /// 计算点云中每个点的协方差矩阵的函数。 /// /// \param search_param 用于邻域搜索的KDTree搜索参数。 void EstimateCovariances( const KDTreeSearchParam &search_param = KDTreeSearchParamKNN()); /// 计算点云的均值和协方差矩阵的函数。 std::tuple<Eigen::Vector3d, Eigen::Matrix3d> ComputeMeanAndCovariance() const; /// 计算输入点云中点的马氏距离的函数。 /// /// See: https://en.wikipedia.org/wiki/Mahalanobis_distance std::vector<double> ComputeMahalanobisDistance() const; /// 计算从一个点到输入点云中它最近的邻居的距离的函数。 std::vector<double> ComputeNearestNeighborDistance() const; /// 利用qhull算法计算点云的凸包的函数。 std::tuple<std::shared_ptr<TriangleMesh>, std::vector<size_t>> ComputeConvexHull() const; /// \brief This is an implementation of the Hidden Point Removal operator /// described in Katz et. al. 'Direct Visibility of Point Sets', 2007. /// /// Additional information about the choice of radius /// for noisy point clouds can be found in Mehra et. al. 'Visibility of /// Noisy Point Cloud Data', 2010. /// /// \param camera_location 从那个位置看不见的所有点都将被移除。 /// \param radius 球面投影的半径。 std::tuple<std::shared_ptr<TriangleMesh>, std::vector<size_t>> HiddenPointRemoval(const Eigen::Vector3d &camera_location, const double radius) const; /// 点云集群使用DBSCAN算法。 /// Ester et al., "A Density-Based Algorithm for Discovering Clusters /// in Large Spatial Databases with Noise", 1996 /// /// 返回一个点标签列表,-1根据算法表示噪声。 /// /// \param eps 密度参数,用于寻找相邻点。 /// \param min_points 形成一个集群的最小点数。 /// \param print_progress 如果为true,过程将在控制台中可视化。 std::vector<int> ClusterDBSCAN(double eps, size_t min_points, bool print_progress = false) const; /// 使用RANSAC算法分割点云平面。 /// /// \param distance_threshold 一个点到平面模型的最大距离,并且被认为是一个内链。 /// \param ransac_n 在每次迭代中考虑内嵌的初始点数。 /// \param num_iterations \迭代次数。 /// \return \返回平面模型ax + by + cz + d = 0和平面内层的索引。 std::tuple<Eigen::Vector4d, std::vector<size_t>> SegmentPlane( const double distance_threshold = 0.01, const int ransac_n = 3, const int num_iterations = 100) const; /// 工厂函数,用于从深度图像和相机模型创建点云。 /// /// 在(u, v)图像坐标处给定深度值d,对应的3d点为:z = d /depth_scale\n x = (u - cx) * z / fx\n y = (v - cy) * z /// fy\n /// /// \param depth 输入的深度图像可以是浮动图像,也可以是uint16_t图像。 /// \param intrinsic 相机的固有参数。 /// \param extrinsic 相机的外部参数。 /// \param depth_scale 深度由1 /depth_scale缩放。 /// \param depth_trunc 在depth_trunc距离截断。 /// \param stride 支持粗点云提取的采样因子。 /// /// \return 如果转换失败,则为空点云。 /// /// 如果project_valid_depth_only为true,返回点云,它没有nan点。如果值为false,返回点云,它为每个像素有一个点,而无效的深度将导致NaN点。 static std::shared_ptr<PointCloud> CreateFromDepthImage( const Image &depth, const camera::PinholeCameraIntrinsic &intrinsic, const Eigen::Matrix4d &extrinsic = Eigen::Matrix4d::Identity(), double depth_scale = 1000.0, double depth_trunc = 1000.0, int stride = 1, bool project_valid_depth_only = true); /// 工厂函数,用于从RGB-D图像和相机模型创建点云。 /// /// 在(u, v)图像坐标处给定深度值d,对应的3d点为:z=d/depth_scale\nx=(u - cx) * z / fx\n y = (v - cy)*z\fy\n。 /// /// \param image 输入图像。 /// \param intrinsic 相机的固有参数。 /// \param extrinsic 相机的外部参数。 /// \return 如果转换失败,则为空点云。 /// /// 如果\param project_valid_depth_only为true,返回点云,它没有nan点。如果值为false,返回点云,该点云为每个像素有一 /// 个点,而无效的深度将导致NaN点。 static std::shared_ptr<PointCloud> CreateFromRGBDImage( const RGBDImage &image, const camera::PinholeCameraIntrinsic &intrinsic, const Eigen::Matrix4d &extrinsic = Eigen::Matrix4d::Identity(), bool project_valid_depth_only = true); /// 从VoxelGrid创建PointCloud的函数。 /// 它使用原始点云坐标(相对于体素网格的中心)将体素中心转换为3D点。 /// /// \param voxel_grid 输入VoxelGrid。 std::shared_ptr<PointCloud> CreateFromVoxelGrid( const VoxelGrid &voxel_grid); public: /// 点坐标 std::vector<Eigen::Vector3d> points_; /// 点法线 std::vector<Eigen::Vector3d> normals_; /// RGB颜色的点 std::vector<Eigen::Vector3d> colors_; /// 每个点的协方差矩阵 std::vector<Eigen::Matrix3d> covariances_; }; } // namespace geometry } // namespace open3d
注:注释中文部分是源码解读,包含分析和遇到的问题
#pragma once #include <string> #include "open3d/geometry/PointCloud.h" namespace open3d { namespace io { ///工厂函数,从一个文件创建一个点云。 ///如果读取失败,返回一个空的pointcloud。 std::shared_ptr<geometry::PointCloud> CreatePointCloudFromFile( const std::string &filename, const std::string &format = "auto", bool print_progress = false); /// \struct ReadPointCloudOption /// ReadPointCloud的可选参数 struct ReadPointCloudOption { ReadPointCloudOption( //注意:当你更新默认值时,更新pybind/io/class_io.cpp中的文档字符串 std::string format = "auto", bool remove_nan_points = false, bool remove_infinite_points = false, bool print_progress = false, std::function<bool(double)> update_progress = {}) : format(format), remove_nan_points(remove_nan_points), remove_infinite_points(remove_infinite_points), print_progress(print_progress), update_progress(update_progress){}; ReadPointCloudOption(std::function<bool(double)> up) : ReadPointCloudOption() { update_progress = up; }; ///指定文件内容的格式(以及使用什么加载器),默认的auto意味着关闭文件扩展名。 std::string format; ///是否删除所有具有nan的点。 bool remove_nan_points; ///是否删除所有带正负inf的点。 bool remove_infinite_points; ///将加载进度打印到stdout。 ///如果你想要有自己的进度指示器或者取消加载,也可以查看update_progress。 bool print_progress; ///读取正在进行时调用的回调,参数是完成的百分比。 ///返回true表示继续加载,false表示试图停止加载和清理。 std::function<bool(double)> update_progress; }; ///从文件中读取PointCloud的常规入口。 ///该函数基于扩展名filename调用read函数。 ///有关可以传递的其他选项,请参阅ReadPointCloudOption。 ///如果read函数成功则返回true,否则返回false。 bool ReadPointCloud(const std::string &filename, geometry::PointCloud &pointcloud, const ReadPointCloudOption ¶ms = {}); /// \struct WritePointCloudOption /// WritePointCloud的可选参数 struct WritePointCloudOption { enum class IsAscii : bool { Binary = false, Ascii = true }; enum class Compressed : bool { Uncompressed = false, Compressed = true }; WritePointCloudOption( //注意:当你更新默认值时,更新pybind/io/class_io.cpp中的文档字符串。 IsAscii write_ascii = IsAscii::Binary, Compressed compressed = Compressed::Uncompressed, bool print_progress = false, std::function<bool(double)> update_progress = {}) : write_ascii(write_ascii), compressed(compressed), print_progress(print_progress), update_progress(update_progress){}; // 体现出WritePointCloud的兼容性 WritePointCloudOption(bool write_ascii, bool compressed = false, bool print_progress = false, std::function<bool(double)> update_progress = {}) : write_ascii(IsAscii(write_ascii)), compressed(Compressed(compressed)), print_progress(print_progress), update_progress(update_progress){}; WritePointCloudOption(std::function<bool(double)> up) : WritePointCloudOption() { update_progress = up; }; ///是否以Ascii或二进制保存。 ///一些存储程序/装置能够做到这两点,而另一些则忽略了这一点。 IsAscii write_ascii; ///保存压缩文件还是未压缩文件。目前,只有PCD能够压缩,并且只有在使用IsAscii::Binary时可以,其他所有格式都忽略了这一点。 Compressed compressed; /// 将加载进度打印到标准输出。如果想要有自己的进度指示器或者取消加载,可以查看update_progress。 bool print_progress; /// 回调调用作为读取正在进行,参数是完成的百分比。 /// 返回true表示继续加载,false表示尝试停止加载和清理。 std::function<bool(double)> update_progress; }; ///将PointCloud写入文件的常规入口。 ///该函数调用基于扩展名filename的write函数。 ///有关可以传递的其他选项,请参阅WritePointCloudOption。 ///如果写函数成功则返回true,否则返回false。 bool WritePointCloud(const std::string &filename, const geometry::PointCloud &pointcloud, const WritePointCloudOption ¶ms = {}); bool ReadPointCloudFromXYZ(const std::string &filename, geometry::PointCloud &pointcloud, const ReadPointCloudOption ¶ms); bool WritePointCloudToXYZ(const std::string &filename, const geometry::PointCloud &pointcloud, const WritePointCloudOption ¶ms); bool ReadPointCloudFromXYZN(const std::string &filename, geometry::PointCloud &pointcloud, const ReadPointCloudOption ¶ms); bool WritePointCloudToXYZN(const std::string &filename, const geometry::PointCloud &pointcloud, const WritePointCloudOption ¶ms); bool ReadPointCloudFromXYZRGB(const std::string &filename, geometry::PointCloud &pointcloud, const ReadPointCloudOption ¶ms); bool WritePointCloudToXYZRGB(const std::string &filename, const geometry::PointCloud &pointcloud, const WritePointCloudOption ¶ms); bool ReadPointCloudFromPLY(const std::string &filename, geometry::PointCloud &pointcloud, const ReadPointCloudOption ¶ms); bool WritePointCloudToPLY(const std::string &filename, const geometry::PointCloud &pointcloud, const WritePointCloudOption ¶ms); bool ReadPointCloudFromPCD(const std::string &filename, geometry::PointCloud &pointcloud, const ReadPointCloudOption ¶ms); bool WritePointCloudToPCD(const std::string &filename, const geometry::PointCloud &pointcloud, const WritePointCloudOption ¶ms); bool ReadPointCloudFromPTS(const std::string &filename, geometry::PointCloud &pointcloud, const ReadPointCloudOption ¶ms); bool WritePointCloudToPTS(const std::string &filename, const geometry::PointCloud &pointcloud, const WritePointCloudOption ¶ms); } // namespace io } // namespace open3d
本次源码分析,我对点云的基类进行了分析,接下来将对体素的相关类进行分析,以及对点云展开应用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。