1 // ----------------------------------------------------------------------------
2 // -                        Open3D: www.open3d.org                            -
3 // ----------------------------------------------------------------------------
4 // The MIT License (MIT)
5 //
6 // Copyright (c) 2018 www.open3d.org
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
14 //
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 // IN THE SOFTWARE.
25 // ----------------------------------------------------------------------------
26 
27 #include "PointCloud.h"
28 
29 #include <Eigen/Eigenvalues>
30 #include <Core/Utility/Console.h>
31 #include <Core/Geometry/KDTreeFlann.h>
32 
33 namespace three{
34 
35 namespace {
36 
sqr(double x)37 double sqr(double x) { return x * x; }
38 
FastEigen3x3(const Eigen::Matrix3d & A)39 Eigen::Vector3d FastEigen3x3(const Eigen::Matrix3d &A)
40 {
41     // Based on:
42     // https://en.wikipedia.org/wiki/Eigenvalue_algorithm#3.C3.973_matrices
43     double p1 = sqr(A(0, 1)) + sqr(A(0, 2)) + sqr(A(1, 2));
44     Eigen::Vector3d eigenvalues;
45     if (p1 == 0.0) {
46         eigenvalues(2) = std::min(A(0, 0), std::min(A(1, 1), A(2, 2)));
47         eigenvalues(0) = std::max(A(0, 0), std::max(A(1, 1), A(2, 2)));
48         eigenvalues(1) = A.trace() - eigenvalues(0) - eigenvalues(2);
49     } else {
50         double q = A.trace() / 3.0;
51         double p2 = sqr((A(0, 0) - q)) + sqr(A(1, 1) - q) + sqr(A(2, 2) - q) +
52                 2 * p1;
53         double p = sqrt(p2 / 6.0);
54         Eigen::Matrix3d B = (1.0 / p) * (A - q * Eigen::Matrix3d::Identity());
55         double r = B.determinant() / 2.0;
56         double phi;
57         if (r <= -1) {
58             phi = M_PI / 3.0;
59         } else if (r >= 1) {
60             phi = 0.0;
61         } else {
62             phi = std::acos(r) / 3.0;
63         }
64         eigenvalues(0) = q + 2.0 * p * std::cos(phi);
65         eigenvalues(2) = q + 2.0 * p * std::cos(phi + 2.0 * M_PI / 3.0);
66         eigenvalues(1) = q * 3.0 - eigenvalues(0) - eigenvalues(2);
67     }
68 
69     Eigen::Vector3d eigenvector =
70             (A - Eigen::Matrix3d::Identity() * eigenvalues(0)) *
71             (A.col(0) - Eigen::Vector3d(eigenvalues(1), 0.0, 0.0));
72     double len = eigenvector.norm();
73     if (len == 0.0) {
74         return Eigen::Vector3d::Zero();
75     } else {
76         return eigenvector.normalized();
77     }
78 }
79 
ComputeNormal(const PointCloud & cloud,const std::vector<int> & indices)80 Eigen::Vector3d ComputeNormal(const PointCloud &cloud,
81         const std::vector<int> &indices)
82 {
83     if (indices.size() == 0) {
84         return Eigen::Vector3d::Zero();
85     }
86     Eigen::Matrix3d covariance;
87     Eigen::Matrix<double, 9, 1> cumulants;
88     cumulants.setZero();
89     for (size_t i = 0; i < indices.size(); i++) {
90         const Eigen::Vector3d &point = cloud.points_[indices[i]];
91         cumulants(0) += point(0);
92         cumulants(1) += point(1);
93         cumulants(2) += point(2);
94         cumulants(3) += point(0) * point(0);
95         cumulants(4) += point(0) * point(1);
96         cumulants(5) += point(0) * point(2);
97         cumulants(6) += point(1) * point(1);
98         cumulants(7) += point(1) * point(2);
99         cumulants(8) += point(2) * point(2);
100     }
101     cumulants /= (double)indices.size();
102     covariance(0, 0) = cumulants(3) - cumulants(0) * cumulants(0);
103     covariance(1, 1) = cumulants(6) - cumulants(1) * cumulants(1);
104     covariance(2, 2) = cumulants(8) - cumulants(2) * cumulants(2);
105     covariance(0, 1) = cumulants(4) - cumulants(0) * cumulants(1);
106     covariance(1, 0) = covariance(0, 1);
107     covariance(0, 2) = cumulants(5) - cumulants(0) * cumulants(2);
108     covariance(2, 0) = covariance(0, 2);
109     covariance(1, 2) = cumulants(7) - cumulants(1) * cumulants(2);
110     covariance(2, 1) = covariance(1, 2);
111 
112     return FastEigen3x3(covariance);
113     //Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> solver;
114     //solver.compute(covariance, Eigen::ComputeEigenvectors);
115     //return solver.eigenvectors().col(0);
116 }
117 
118 }    // unnamed namespace
119 
EstimateNormals(PointCloud & cloud,const KDTreeSearchParam & search_param)120 bool EstimateNormals(PointCloud &cloud,
121         const KDTreeSearchParam &search_param/* = KDTreeSearchParamKNN()*/)
122 {
123     bool has_normal = cloud.HasNormals();
124     if (cloud.HasNormals() == false) {
125         cloud.normals_.resize(cloud.points_.size());
126     }
127     KDTreeFlann kdtree;
128     kdtree.SetGeometry(cloud);
129 #ifdef _OPENMP
130 #pragma omp parallel for schedule(static)
131 #endif
132     for (int i = 0; i < (int)cloud.points_.size(); i++) {
133         std::vector<int> indices;
134         std::vector<double> distance2;
135         Eigen::Vector3d normal;
136         if (kdtree.Search(cloud.points_[i], search_param, indices,
137                 distance2) >= 3) {
138             normal = ComputeNormal(cloud, indices);
139             if (normal.norm() == 0.0) {
140                 if (has_normal) {
141                     normal = cloud.normals_[i];
142                 } else {
143                     normal = Eigen::Vector3d(0.0, 0.0, 1.0);
144                 }
145             }
146             if (has_normal && normal.dot(cloud.normals_[i]) < 0.0) {
147                 normal *= -1.0;
148             }
149             cloud.normals_[i] = normal;
150         } else {
151             cloud.normals_[i] = Eigen::Vector3d(0.0, 0.0, 1.0);
152         }
153     }
154 
155     return true;
156 }
157 
OrientNormalsToAlignWithDirection(PointCloud & cloud,const Eigen::Vector3d & orientation_reference)158 bool OrientNormalsToAlignWithDirection(PointCloud &cloud,
159         const Eigen::Vector3d &orientation_reference
160         /* = Eigen::Vector3d(0.0, 0.0, 1.0)*/)
161 {
162     if (cloud.HasNormals() == false) {
163         PrintDebug("[OrientNormalsToAlignWithDirection] No normals in the PointCloud. Call EstimateNormals() first.\n");
164     }
165 #ifdef _OPENMP
166 #pragma omp parallel for schedule(static)
167 #endif
168     for (int i = 0; i < (int)cloud.points_.size(); i++) {
169         auto &normal = cloud.normals_[i];
170         if (normal.norm() == 0.0) {
171             normal = orientation_reference;
172         } else if (normal.dot(orientation_reference) < 0.0) {
173             normal *= -1.0;
174         }
175     }
176     return true;
177 }
178 
OrientNormalsTowardsCameraLocation(PointCloud & cloud,const Eigen::Vector3d & camera_location)179 bool OrientNormalsTowardsCameraLocation(PointCloud &cloud,
180         const Eigen::Vector3d &camera_location/* = Eigen::Vector3d::Zero()*/)
181 {
182     if (cloud.HasNormals() == false) {
183         PrintDebug("[OrientNormalsTowardsCameraLocation] No normals in the PointCloud. Call EstimateNormals() first.\n");
184     }
185 #ifdef _OPENMP
186 #pragma omp parallel for schedule(static)
187 #endif
188     for (int i = 0; i < (int)cloud.points_.size(); i++) {
189         Eigen::Vector3d orientation_reference = camera_location -
190                 cloud.points_[i];
191         auto &normal = cloud.normals_[i];
192         if (normal.norm() == 0.0) {
193             normal = orientation_reference;
194             if (normal.norm() == 0.0) {
195                 normal = Eigen::Vector3d(0.0, 0.0, 1.0);
196             } else {
197                 normal.normalize();
198             }
199         } else if (normal.dot(orientation_reference) < 0.0) {
200             normal *= -1.0;
201         }
202     }
203     return true;
204 }
205 
206 }    // namespace three
207