Skip to content

Commit bc5aa8e

Browse files
quentin-leboutetgithub-actions[bot]Copilotssheorey
authored
Minimum volume oriented bounding ellipsoid (#7377)
This PR adds support for Oriented Bounding Ellipsoids (OBE) to Open3D, complementing the existing bounding volume primitives (AABB and OBB). Oriented bounding ellipsoids provide a tighter fit for certain geometries compared to boxes, which can be beneficial for: More accurate spatial representations of point clouds and meshes Improved collision detection and computational geometry applications Better geometric analysis and understanding of 3D shapes The implementation uses Khachiyan's algorithm to compute the minimum volume enclosing ellipsoid, providing an efficient approximation of the optimal bounding ellipsoid. MVOBE support for C++ and Python for point clouds and triangle meshes (both legacy and tensor). All computation is done on CPU. Also add ellipsoid primitive for rendering as either mesh or lineset. Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Sameer Sheorey <41028320+ssheorey@users.noreply.github.com>
1 parent adf2d0f commit bc5aa8e

43 files changed

Lines changed: 2128 additions & 21 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cpp/open3d/geometry/BoundingVolume.cpp

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,107 @@
1515
#include "open3d/geometry/Qhull.h"
1616
#include "open3d/geometry/TriangleMesh.h"
1717
#include "open3d/t/geometry/kernel/MinimumOBB.h"
18+
#include "open3d/t/geometry/kernel/MinimumOBE.h"
1819
#include "open3d/utility/Logging.h"
1920

2021
namespace open3d {
2122
namespace geometry {
2223

24+
OrientedBoundingEllipsoid& OrientedBoundingEllipsoid::Clear() {
25+
center_.setZero();
26+
radii_.setZero();
27+
R_ = Eigen::Matrix3d::Identity();
28+
color_.setOnes();
29+
return *this;
30+
}
31+
32+
bool OrientedBoundingEllipsoid::IsEmpty() const { return Volume() <= 0; }
33+
34+
Eigen::Vector3d OrientedBoundingEllipsoid::GetMinBound() const {
35+
auto points = GetEllipsoidPoints();
36+
return ComputeMinBound(points);
37+
}
38+
39+
Eigen::Vector3d OrientedBoundingEllipsoid::GetMaxBound() const {
40+
auto points = GetEllipsoidPoints();
41+
return ComputeMaxBound(points);
42+
}
43+
44+
Eigen::Vector3d OrientedBoundingEllipsoid::GetCenter() const { return center_; }
45+
46+
AxisAlignedBoundingBox OrientedBoundingEllipsoid::GetAxisAlignedBoundingBox()
47+
const {
48+
return AxisAlignedBoundingBox::CreateFromPoints(GetEllipsoidPoints());
49+
}
50+
51+
OrientedBoundingBox OrientedBoundingEllipsoid::GetOrientedBoundingBox(
52+
bool) const {
53+
return OrientedBoundingBox::CreateFromPoints(GetEllipsoidPoints());
54+
}
55+
56+
OrientedBoundingBox OrientedBoundingEllipsoid::GetMinimalOrientedBoundingBox(
57+
bool robust) const {
58+
return OrientedBoundingBox::CreateFromPoints(GetEllipsoidPoints());
59+
}
60+
61+
OrientedBoundingEllipsoid& OrientedBoundingEllipsoid::Transform(
62+
const Eigen::Matrix4d& transformation) {
63+
utility::LogError(
64+
"A general transform of an OrientedBoundingEllipsoid is not "
65+
"implemented. "
66+
"Call Translate, Scale, and Rotate.");
67+
return *this;
68+
}
69+
70+
OrientedBoundingEllipsoid& OrientedBoundingEllipsoid::Translate(
71+
const Eigen::Vector3d& translation, bool relative) {
72+
if (relative) {
73+
center_ += translation;
74+
} else {
75+
center_ = translation;
76+
}
77+
return *this;
78+
}
79+
80+
OrientedBoundingEllipsoid& OrientedBoundingEllipsoid::Scale(
81+
const double scale, const Eigen::Vector3d& center) {
82+
radii_ *= scale;
83+
center_ = scale * (center_ - center) + center;
84+
return *this;
85+
}
86+
87+
OrientedBoundingEllipsoid& OrientedBoundingEllipsoid::Rotate(
88+
const Eigen::Matrix3d& R, const Eigen::Vector3d& center) {
89+
R_ = R * R_;
90+
center_ = R * (center_ - center) + center;
91+
return *this;
92+
}
93+
94+
double OrientedBoundingEllipsoid::Volume() const {
95+
return 4 * M_PI * radii_(0) * radii_(1) * radii_(2) / 3.0;
96+
}
97+
98+
std::vector<Eigen::Vector3d> OrientedBoundingEllipsoid::GetEllipsoidPoints()
99+
const {
100+
Eigen::Vector3d x_axis = R_ * Eigen::Vector3d(radii_(0), 0, 0);
101+
Eigen::Vector3d y_axis = R_ * Eigen::Vector3d(0, radii_(1), 0);
102+
Eigen::Vector3d z_axis = R_ * Eigen::Vector3d(0, 0, radii_(2));
103+
std::vector<Eigen::Vector3d> points(6);
104+
points[0] = center_ + x_axis;
105+
points[1] = center_ - x_axis;
106+
points[2] = center_ + y_axis;
107+
points[3] = center_ - y_axis;
108+
points[4] = center_ + z_axis;
109+
points[5] = center_ - z_axis;
110+
return points;
111+
}
112+
113+
OrientedBoundingEllipsoid OrientedBoundingEllipsoid::CreateFromPoints(
114+
const std::vector<Eigen::Vector3d>& points, bool robust) {
115+
return t::geometry::kernel::minimum_obe::ComputeMinimumOBEKhachiyan(points,
116+
robust);
117+
}
118+
23119
OrientedBoundingBox& OrientedBoundingBox::Clear() {
24120
center_.setZero();
25121
extent_.setZero();
@@ -363,4 +459,4 @@ std::vector<size_t> AxisAlignedBoundingBox::GetPointIndicesWithinBoundingBox(
363459
}
364460

365461
} // namespace geometry
366-
} // namespace open3d
462+
} // namespace open3d

cpp/open3d/geometry/BoundingVolume.h

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,119 @@ namespace open3d {
1515
namespace geometry {
1616

1717
class AxisAlignedBoundingBox;
18+
class OrientedBoundingBox;
19+
20+
class OrientedBoundingEllipsoid : public Geometry3D {
21+
public:
22+
/// \brief Default constructor.
23+
///
24+
/// Creates an empty Oriented Bounding Ellipsoid.
25+
OrientedBoundingEllipsoid()
26+
: Geometry3D(Geometry::GeometryType::OrientedBoundingEllipsoid),
27+
center_(0, 0, 0),
28+
R_(Eigen::Matrix3d::Identity()),
29+
radii_(0, 0, 0),
30+
color_(1, 1, 1) {}
31+
32+
/// \brief Parameterized constructor.
33+
///
34+
/// \param center Specifies the center position of the bounding ellipsoid.
35+
/// \param R The rotation matrix specifying the orientation of the
36+
/// bounding ellipsoid with the original frame of reference.
37+
/// \param radii The radii of the bounding ellipsoid.
38+
OrientedBoundingEllipsoid(const Eigen::Vector3d& center,
39+
const Eigen::Matrix3d& R,
40+
const Eigen::Vector3d& radii)
41+
: Geometry3D(Geometry::GeometryType::OrientedBoundingEllipsoid),
42+
center_(center),
43+
R_(R),
44+
radii_(radii),
45+
color_(1, 1, 1) {}
46+
~OrientedBoundingEllipsoid() override {}
47+
48+
public:
49+
OrientedBoundingEllipsoid& Clear() override;
50+
bool IsEmpty() const override;
51+
virtual Eigen::Vector3d GetMinBound() const override;
52+
virtual Eigen::Vector3d GetMaxBound() const override;
53+
virtual Eigen::Vector3d GetCenter() const override;
54+
55+
/// Creates an axis-aligned bounding box around the object.
56+
virtual AxisAlignedBoundingBox GetAxisAlignedBoundingBox() const override;
57+
58+
/// Returns an oriented bounding box around the ellipsoid.
59+
virtual OrientedBoundingBox GetOrientedBoundingBox(
60+
bool robust) const override;
61+
62+
/// Returns an oriented bounding box around the ellipsoid.
63+
virtual OrientedBoundingBox GetMinimalOrientedBoundingBox(
64+
bool robust) const override;
65+
66+
virtual OrientedBoundingEllipsoid& Transform(
67+
const Eigen::Matrix4d& transformation) override;
68+
virtual OrientedBoundingEllipsoid& Translate(
69+
const Eigen::Vector3d& translation, bool relative = true) override;
70+
virtual OrientedBoundingEllipsoid& Scale(
71+
const double scale, const Eigen::Vector3d& center) override;
72+
virtual OrientedBoundingEllipsoid& Rotate(
73+
const Eigen::Matrix3d& R, const Eigen::Vector3d& center) override;
74+
75+
/// Returns the volume of the bounding box.
76+
double Volume() const;
77+
78+
/** Returns the six critical points of the bounding ellipsoid.
79+
* \verbatim
80+
* ------- x
81+
* /|
82+
* / |
83+
* / | z
84+
* y
85+
* 2
86+
* .--|---.
87+
* .--' | '--.
88+
* .--' | '--.
89+
* .' | 4 '.
90+
* / | / \
91+
* / | / \
92+
* 0 |------------------|-------------------| 1
93+
* \ / | /
94+
* \ / | /
95+
* '. 5 | .'
96+
* '--. | .--'
97+
* '--. | .--'
98+
* '--|---'
99+
* 3
100+
* \endverbatim
101+
*/
102+
std::vector<Eigen::Vector3d> GetEllipsoidPoints() const;
103+
104+
/// Return indices to points that are within the bounding ellipsoid.
105+
std::vector<size_t> GetPointIndicesWithinBoundingEllipsoid(
106+
const std::vector<Eigen::Vector3d>& points) const;
107+
108+
/// Creates an oriented bounding ellipsoid using a PCA.
109+
/// Note, that this is only an approximation to the minimum oriented
110+
/// bounding box that could be computed for example with O'Rourke's
111+
/// algorithm (cf. http://cs.smith.edu/~jorourke/Papers/MinVolBox.pdf,
112+
/// https://www.geometrictools.com/Documentation/MinimumVolumeBox.pdf)
113+
/// \param points The input points
114+
/// \param robust If set to true uses a more robust method which works
115+
/// in degenerate cases but introduces noise to the points
116+
/// coordinates.
117+
static OrientedBoundingEllipsoid CreateFromPoints(
118+
const std::vector<Eigen::Vector3d>& points, bool robust = false);
119+
120+
public:
121+
/// The center point of the bounding ellipsoid.
122+
Eigen::Vector3d center_;
123+
/// The rotation matrix of the bounding ellipsoid to transform the original
124+
/// frame of reference to the frame of this ellipsoid.
125+
Eigen::Matrix3d R_;
126+
/// The radii of the bounding ellipsoid in its frame of reference.
127+
Eigen::Vector3d radii_;
128+
/// The color of the bounding ellipsoid in RGB.
129+
Eigen::Vector3d color_;
130+
};
18131

19132
/// \class OrientedBoundingBox
20133
///
@@ -206,6 +319,7 @@ class AxisAlignedBoundingBox : public Geometry3D {
206319
/// and orientation as the object
207320
virtual OrientedBoundingBox GetMinimalOrientedBoundingBox(
208321
bool robust = false) const override;
322+
209323
virtual AxisAlignedBoundingBox& Transform(
210324
const Eigen::Matrix4d& transformation) override;
211325
virtual AxisAlignedBoundingBox& Translate(
@@ -288,4 +402,4 @@ class AxisAlignedBoundingBox : public Geometry3D {
288402
};
289403

290404
} // namespace geometry
291-
} // namespace open3d
405+
} // namespace open3d

cpp/open3d/geometry/Geometry.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class Geometry {
4747
OrientedBoundingBox = 11,
4848
/// AxisAlignedBoundingBox
4949
AxisAlignedBoundingBox = 12,
50+
/// OrientedBoundingEllipsoid
51+
OrientedBoundingEllipsoid = 13,
5052
};
5153

5254
public:

cpp/open3d/geometry/Geometry3D.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace geometry {
1919

2020
class AxisAlignedBoundingBox;
2121
class OrientedBoundingBox;
22+
class OrientedBoundingEllipsoid;
2223

2324
/// \class Geometry3D
2425
///

cpp/open3d/geometry/LineSet.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace open3d {
1717
namespace geometry {
1818

1919
class PointCloud;
20+
class OrientedBoundingEllipsoid;
2021
class OrientedBoundingBox;
2122
class AxisAlignedBoundingBox;
2223
class TriangleMesh;
@@ -133,6 +134,13 @@ class LineSet : public Geometry3D {
133134
static std::shared_ptr<LineSet> CreateFromAxisAlignedBoundingBox(
134135
const AxisAlignedBoundingBox &box);
135136

137+
/// \brief Factory function to create a LineSet from an
138+
/// OrientedBoundingEllipsoid.
139+
///
140+
/// \param ellipsoid The input bounding ellipsoid.
141+
static std::shared_ptr<LineSet> CreateFromOrientedBoundingEllipsoid(
142+
const OrientedBoundingEllipsoid &ellipsoid);
143+
136144
/// Factory function to create a LineSet from edges of a triangle mesh.
137145
///
138146
/// \param mesh The input triangle mesh.

cpp/open3d/geometry/LineSetFactory.cpp

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ namespace open3d {
1717
namespace geometry {
1818

1919
std::shared_ptr<LineSet> LineSet::CreateFromPointCloudCorrespondences(
20-
const PointCloud &cloud0,
21-
const PointCloud &cloud1,
22-
const std::vector<std::pair<int, int>> &correspondences) {
20+
const PointCloud& cloud0,
21+
const PointCloud& cloud1,
22+
const std::vector<std::pair<int, int>>& correspondences) {
2323
auto lineset_ptr = std::make_shared<LineSet>();
2424
size_t point0_size = cloud0.points_.size();
2525
size_t point1_size = cloud1.points_.size();
@@ -39,7 +39,7 @@ std::shared_ptr<LineSet> LineSet::CreateFromPointCloudCorrespondences(
3939
}
4040

4141
std::shared_ptr<LineSet> LineSet::CreateFromTriangleMesh(
42-
const TriangleMesh &mesh) {
42+
const TriangleMesh& mesh) {
4343
auto line_set = std::make_shared<LineSet>();
4444
line_set->points_ = mesh.vertices_;
4545

@@ -52,7 +52,7 @@ std::shared_ptr<LineSet> LineSet::CreateFromTriangleMesh(
5252
line_set->lines_.push_back(Eigen::Vector2i(vidx0, vidx1));
5353
}
5454
};
55-
for (const auto &triangle : mesh.triangles_) {
55+
for (const auto& triangle : mesh.triangles_) {
5656
InsertEdge(triangle(0), triangle(1));
5757
InsertEdge(triangle(1), triangle(2));
5858
InsertEdge(triangle(2), triangle(0));
@@ -61,8 +61,21 @@ std::shared_ptr<LineSet> LineSet::CreateFromTriangleMesh(
6161
return line_set;
6262
}
6363

64+
std::shared_ptr<LineSet> LineSet::CreateFromOrientedBoundingEllipsoid(
65+
const OrientedBoundingEllipsoid& ellipsoid) {
66+
std::shared_ptr<TriangleMesh> obel =
67+
geometry::TriangleMesh::CreateEllipsoid(ellipsoid.radii_(0),
68+
ellipsoid.radii_(1),
69+
ellipsoid.radii_(2));
70+
obel->Rotate(ellipsoid.R_, Eigen::Vector3d::Zero());
71+
obel->Translate(ellipsoid.center_);
72+
auto line_set = CreateFromTriangleMesh(*obel);
73+
line_set->PaintUniformColor(ellipsoid.color_);
74+
return line_set;
75+
}
76+
6477
std::shared_ptr<LineSet> LineSet::CreateFromOrientedBoundingBox(
65-
const OrientedBoundingBox &box) {
78+
const OrientedBoundingBox& box) {
6679
auto line_set = std::make_shared<LineSet>();
6780
line_set->points_ = box.GetBoxPoints();
6881
line_set->lines_.push_back(Eigen::Vector2i(0, 1));
@@ -82,7 +95,7 @@ std::shared_ptr<LineSet> LineSet::CreateFromOrientedBoundingBox(
8295
}
8396

8497
std::shared_ptr<LineSet> LineSet::CreateFromAxisAlignedBoundingBox(
85-
const AxisAlignedBoundingBox &box) {
98+
const AxisAlignedBoundingBox& box) {
8699
auto line_set = std::make_shared<LineSet>();
87100
line_set->points_ = box.GetBoxPoints();
88101
line_set->lines_.push_back(Eigen::Vector2i(0, 1));
@@ -101,7 +114,7 @@ std::shared_ptr<LineSet> LineSet::CreateFromAxisAlignedBoundingBox(
101114
return line_set;
102115
}
103116

104-
std::shared_ptr<LineSet> LineSet::CreateFromTetraMesh(const TetraMesh &mesh) {
117+
std::shared_ptr<LineSet> LineSet::CreateFromTetraMesh(const TetraMesh& mesh) {
105118
auto line_set = std::make_shared<LineSet>();
106119
line_set->points_ = mesh.vertices_;
107120

@@ -114,7 +127,7 @@ std::shared_ptr<LineSet> LineSet::CreateFromTetraMesh(const TetraMesh &mesh) {
114127
line_set->lines_.push_back(Eigen::Vector2i(vidx0, vidx1));
115128
}
116129
};
117-
for (const auto &tetra : mesh.tetras_) {
130+
for (const auto& tetra : mesh.tetras_) {
118131
InsertEdge(tetra(0), tetra(1));
119132
InsertEdge(tetra(1), tetra(2));
120133
InsertEdge(tetra(2), tetra(0));
@@ -129,8 +142,8 @@ std::shared_ptr<LineSet> LineSet::CreateFromTetraMesh(const TetraMesh &mesh) {
129142
std::shared_ptr<LineSet> LineSet::CreateCameraVisualization(
130143
int view_width_px,
131144
int view_height_px,
132-
const Eigen::Matrix3d &intrinsic,
133-
const Eigen::Matrix4d &extrinsic,
145+
const Eigen::Matrix3d& intrinsic,
146+
const Eigen::Matrix4d& extrinsic,
134147
double scale) {
135148
Eigen::Matrix4d intrinsic4;
136149
intrinsic4 << intrinsic(0, 0), intrinsic(0, 1), intrinsic(0, 2), 0.0,
@@ -140,8 +153,8 @@ std::shared_ptr<LineSet> LineSet::CreateCameraVisualization(
140153
Eigen::Matrix4d m = (intrinsic4 * extrinsic).inverse();
141154
auto lines = std::make_shared<geometry::LineSet>();
142155

143-
auto mult = [](const Eigen::Matrix4d &m,
144-
const Eigen::Vector3d &v) -> Eigen::Vector3d {
156+
auto mult = [](const Eigen::Matrix4d& m,
157+
const Eigen::Vector3d& v) -> Eigen::Vector3d {
145158
Eigen::Vector4d v4(v.x(), v.y(), v.z(), 1.0);
146159
auto result = m * v4;
147160
return Eigen::Vector3d{result.x() / result.w(), result.y() / result.w(),

0 commit comments

Comments
 (0)