diff --git a/CHANGELOG.md b/CHANGELOG.md index 2542b81812f..ad1f4f43b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ - Split pybind declarations/definitions to avoid C++ types in Python docs (PR #6869) - Fix minimal oriented bounding box of MeshBase derived classes and add new unit tests (PR #6898) - Fix projection of point cloud to Depth/RGBD image if no position attribute is provided (PR #6880) +- Add choice of voxel pooling mode when creating VoxelGrid from PointCloud (PR #6937) - Support lowercase types when reading PCD files (PR #6930) - Fix visualization/draw ICP example and add warnings (PR #6933) - Unified cloud initializer pipeline for ICP (fixes segfault colored ICP) (PR #6942) @@ -55,6 +56,7 @@ - Fix infinite loop in segment_plane if num_points < ransac_n (PR #7032) - Add select_by_index method to Feature class (PR #7039) + ## 0.13 - CUDA support 10.1 -> 11.0. Tensorflow 2.3.1 -> 2.4.1. PyTorch 1.6.0 -> 1.7.1 (PR #3049). This requires a custom PyTorch wheel from due to PyTorch issue #52663 diff --git a/cpp/open3d/geometry/VoxelGrid.h b/cpp/open3d/geometry/VoxelGrid.h index 1c15a5073bb..258e975a12d 100644 --- a/cpp/open3d/geometry/VoxelGrid.h +++ b/cpp/open3d/geometry/VoxelGrid.h @@ -189,18 +189,28 @@ class VoxelGrid : public Geometry3D { double height, double depth); + /// \enum VoxelPoolingMode + /// + /// \brief Possible ways of determining voxel color from PointCloud. + enum class VoxelPoolingMode { AVG, MIN, MAX, SUM }; + /// Creates a VoxelGrid from a given PointCloud. The color value of a given - /// voxel is the average color value of the points that fall into it (if the + /// voxel is determined by the VoxelPoolingMode, e.g. by default the average + /// color value of the points that fall into it (if the /// PointCloud has colors). /// The bounds of the created VoxelGrid are computed from the PointCloud. /// /// \param input The input PointCloud. /// \param voxel_size Voxel size of of the VoxelGrid construction. + /// \param color_mode Mode of determining color for each voxel. static std::shared_ptr CreateFromPointCloud( - const PointCloud &input, double voxel_size); + const PointCloud &input, + double voxel_size, + VoxelPoolingMode color_mode = VoxelPoolingMode::AVG); /// Creates a VoxelGrid from a given PointCloud. The color value of a given - /// voxel is the average color value of the points that fall into it (if the + /// voxel is determined by the VoxelPoolingMode, e.g. by default the average + /// color value of the points that fall into it (if the /// PointCloud has colors). /// The bounds of the created VoxelGrid are defined by the given parameters. /// @@ -208,11 +218,13 @@ class VoxelGrid : public Geometry3D { /// \param voxel_size Voxel size of of the VoxelGrid construction. /// \param min_bound Minimum boundary point for the VoxelGrid to create. /// \param max_bound Maximum boundary point for the VoxelGrid to create. + /// \param color_mode Mode of determining color for each voxel. static std::shared_ptr CreateFromPointCloudWithinBounds( const PointCloud &input, double voxel_size, const Eigen::Vector3d &min_bound, - const Eigen::Vector3d &max_bound); + const Eigen::Vector3d &max_bound, + VoxelPoolingMode color_mode = VoxelPoolingMode::AVG); /// Creates a VoxelGrid from a given TriangleMesh. No color information is /// converted. The bounds of the created VoxelGrid are computed from the @@ -294,5 +306,63 @@ class AvgColorVoxel { Eigen::Vector3d color_; }; +/// \class AggColorVoxel +/// +/// \brief Class to aggregate color values from different votes in one voxel +/// Can be used to compute min, max, average and sum voxel color. +class AggColorVoxel { +public: + AggColorVoxel() + : num_of_points_(0), + color_(0.0, 0.0, 0.0), + min_color_(Eigen::Vector3d::Constant( + std::numeric_limits::max())), + max_color_(Eigen::Vector3d::Constant( + std::numeric_limits::lowest())) {} + +public: + void Add(const Eigen::Vector3i &voxel_index) { + if (num_of_points_ > 0 && voxel_index != voxel_index_) { + utility::LogWarning( + "Tried to aggregate ColorVoxel with different " + "voxel_index"); + } + voxel_index_ = voxel_index; + } + + void Add(const Eigen::Vector3i &voxel_index, const Eigen::Vector3d &color) { + Add(voxel_index); + color_ += color; + num_of_points_++; + min_color_ = min_color_.cwiseMin(color); + max_color_ = max_color_.cwiseMax(color); + } + + Eigen::Vector3i GetVoxelIndex() const { return voxel_index_; } + + Eigen::Vector3d GetAverageColor() const { + if (num_of_points_ > 0) { + return color_ / double(num_of_points_); + } else { + return color_; + } + } + + Eigen::Vector3d GetMinColor() const { return min_color_; } + + Eigen::Vector3d GetMaxColor() const { return max_color_; } + + Eigen::Vector3d GetSumColor() const { return color_; } + +public: + int num_of_points_; + Eigen::Vector3i voxel_index_; + Eigen::Vector3d color_; + +private: + Eigen::Vector3d min_color_; + Eigen::Vector3d max_color_; +}; + } // namespace geometry } // namespace open3d diff --git a/cpp/open3d/geometry/VoxelGridFactory.cpp b/cpp/open3d/geometry/VoxelGridFactory.cpp index bbe5df52c3f..39fe03668ed 100644 --- a/cpp/open3d/geometry/VoxelGridFactory.cpp +++ b/cpp/open3d/geometry/VoxelGridFactory.cpp @@ -45,7 +45,8 @@ std::shared_ptr VoxelGrid::CreateFromPointCloudWithinBounds( const PointCloud &input, double voxel_size, const Eigen::Vector3d &min_bound, - const Eigen::Vector3d &max_bound) { + const Eigen::Vector3d &max_bound, + VoxelGrid::VoxelPoolingMode pooling_mode) { auto output = std::make_shared(); if (voxel_size <= 0.0) { utility::LogError("voxel_size <= 0."); @@ -57,7 +58,7 @@ std::shared_ptr VoxelGrid::CreateFromPointCloudWithinBounds( } output->voxel_size_ = voxel_size; output->origin_ = min_bound; - std::unordered_map> voxelindex_to_accpoint; Eigen::Vector3d ref_coord; @@ -76,9 +77,15 @@ std::shared_ptr VoxelGrid::CreateFromPointCloudWithinBounds( } for (auto accpoint : voxelindex_to_accpoint) { const Eigen::Vector3i &grid_index = accpoint.second.GetVoxelIndex(); - const Eigen::Vector3d &color = - has_colors ? accpoint.second.GetAverageColor() - : Eigen::Vector3d(0, 0, 0); + // clang-format off + const Eigen::Vector3d &color = has_colors ? + (pooling_mode == VoxelPoolingMode::AVG ? accpoint.second.GetAverageColor() + : pooling_mode == VoxelPoolingMode::MIN ? accpoint.second.GetMinColor() + : pooling_mode == VoxelPoolingMode::MAX ? accpoint.second.GetMaxColor() + : pooling_mode == VoxelPoolingMode::SUM ? accpoint.second.GetSumColor() + : Eigen::Vector3d::Zero()) + : Eigen::Vector3d::Zero(); + // clang-format on output->AddVoxel(geometry::Voxel(grid_index, color)); } utility::LogDebug( @@ -88,12 +95,14 @@ std::shared_ptr VoxelGrid::CreateFromPointCloudWithinBounds( } std::shared_ptr VoxelGrid::CreateFromPointCloud( - const PointCloud &input, double voxel_size) { + const PointCloud &input, + double voxel_size, + VoxelGrid::VoxelPoolingMode pooling_mode) { Eigen::Vector3d voxel_size3(voxel_size, voxel_size, voxel_size); Eigen::Vector3d min_bound = input.GetMinBound() - voxel_size3 * 0.5; Eigen::Vector3d max_bound = input.GetMaxBound() + voxel_size3 * 0.5; return CreateFromPointCloudWithinBounds(input, voxel_size, min_bound, - max_bound); + max_bound, pooling_mode); } std::shared_ptr VoxelGrid::CreateFromTriangleMeshWithinBounds( diff --git a/cpp/pybind/geometry/voxelgrid.cpp b/cpp/pybind/geometry/voxelgrid.cpp index 8cf66ce8a95..92edd621fa6 100644 --- a/cpp/pybind/geometry/voxelgrid.cpp +++ b/cpp/pybind/geometry/voxelgrid.cpp @@ -63,6 +63,15 @@ void pybind_voxelgrid_definitions(py::module &m) { static_cast, std::shared_ptr, Geometry3D>>( m.attr("VoxelGrid")); + + py::enum_ pooling_mode( + voxelgrid, "VoxelPoolingMode", + "Mode of determining color for each voxel."); + pooling_mode.value("AVG", VoxelGrid::VoxelPoolingMode::AVG) + .value("MIN", VoxelGrid::VoxelPoolingMode::MIN) + .value("MAX", VoxelGrid::VoxelPoolingMode::MAX) + .value("SUM", VoxelGrid::VoxelPoolingMode::SUM); + py::detail::bind_default_constructor(voxelgrid); py::detail::bind_copy_functions(voxelgrid); voxelgrid @@ -129,19 +138,23 @@ void pybind_voxelgrid_definitions(py::module &m) { .def_static("create_from_point_cloud", &VoxelGrid::CreateFromPointCloud, "Creates a VoxelGrid from a given PointCloud. The " - "color value of a given voxel is the average color " + "color value of a given voxel is determined by the " + "VoxelPoolingMode, e.g. by default the average color " "value of the points that fall into it (if the " "PointCloud has colors). The bounds of the created " "VoxelGrid are computed from the PointCloud.", - "input"_a, "voxel_size"_a) + "input"_a, "voxel_size"_a, + "pooling_mode"_a = VoxelGrid::VoxelPoolingMode::AVG) .def_static("create_from_point_cloud_within_bounds", &VoxelGrid::CreateFromPointCloudWithinBounds, "Creates a VoxelGrid from a given PointCloud. The " - "color value of a given voxel is the average color " + "color value of a given voxel is determined by the " + "VoxelPoolingMode, e.g. by default the average color " "value of the points that fall into it (if the " "PointCloud has colors). The bounds of the created " "VoxelGrid are defined by the given parameters.", - "input"_a, "voxel_size"_a, "min_bound"_a, "max_bound"_a) + "input"_a, "voxel_size"_a, "min_bound"_a, "max_bound"_a, + "pooling_mode"_a = VoxelGrid::VoxelPoolingMode::AVG) .def_static("create_from_triangle_mesh", &VoxelGrid::CreateFromTriangleMesh, "Creates a VoxelGrid from a given TriangleMesh. No " @@ -162,6 +175,7 @@ void pybind_voxelgrid_definitions(py::module &m) { "origin point.") .def_readwrite("voxel_size", &VoxelGrid::voxel_size_, "``float64`` Size of the voxel."); + docstring::ClassMethodDocInject(m, "VoxelGrid", "has_colors"); docstring::ClassMethodDocInject(m, "VoxelGrid", "has_voxels"); docstring::ClassMethodDocInject(m, "VoxelGrid", "get_voxel", @@ -214,7 +228,9 @@ void pybind_voxelgrid_definitions(py::module &m) { docstring::ClassMethodDocInject( m, "VoxelGrid", "create_from_point_cloud", {{"input", "The input PointCloud"}, - {"voxel_size", "Voxel size of of the VoxelGrid construction."}}); + {"voxel_size", "Voxel size of of the VoxelGrid construction."}, + {"pooling_mode", + "VoxelPoolingMode for determining voxel color."}}); docstring::ClassMethodDocInject( m, "VoxelGrid", "create_from_point_cloud_within_bounds", {{"input", "The input PointCloud"}, @@ -222,7 +238,10 @@ void pybind_voxelgrid_definitions(py::module &m) { {"min_bound", "Minimum boundary point for the VoxelGrid to create."}, {"max_bound", - "Maximum boundary point for the VoxelGrid to create."}}); + "Maximum boundary point for the VoxelGrid to create."}, + {"pooling_mode", + "VoxelPoolingMode that determines how to compute the voxel " + "color."}}); docstring::ClassMethodDocInject( m, "VoxelGrid", "create_from_triangle_mesh", {{"input", "The input TriangleMesh"}, diff --git a/docs/jupyter/geometry/voxelization.ipynb b/docs/jupyter/geometry/voxelization.ipynb index 934e7b7d419..e7b98a64f7c 100644 --- a/docs/jupyter/geometry/voxelization.ipynb +++ b/docs/jupyter/geometry/voxelization.ipynb @@ -64,7 +64,7 @@ "metadata": {}, "source": [ "## From point cloud\n", - "The voxel grid can also be created from a point cloud using the method `create_from_point_cloud`. A voxel is occupied if at least one point of the point cloud is within the voxel. The color of the voxel is the average of all the points within the voxel. The argument `voxel_size` defines the resolution of the voxel grid." + "The voxel grid can also be created from a point cloud using the method `create_from_point_cloud`. A voxel is occupied if at least one point of the point cloud is within the voxel. The argument `voxel_size` defines the resolution of the voxel grid. By default, the color of the voxel is the average of all the points within the voxel. The argument `pooling_mode` can be changed to determine the color by average, min, max or sum value of the points, e.g. with `o3d.geometry.VoxelGrid.VoxelPoolingMode.MIN`." ] }, {