Ponca  aa50bfdf187919869239c5b44b748842569114c1
Point Cloud Analysis library
Loading...
Searching...
No Matches
curvatureEstimation.hpp
1template < class DataPoint, class _WFunctor, int DiffType, typename T>
4{
5 if (Base::finalize() == STABLE) {
6 if (Base::curvatureEstimatorBase().isValid()) Base::m_eCurrentState = CONFLICT_ERROR_FOUND;
7 Base::m_eCurrentState = computeCurvature(false);
8 }
9
10 return Base::m_eCurrentState;
11}
12
13template < class DataPoint, class _WFunctor, int DiffType, typename T>
15{
16 PONCA_MULTIARCH_STD_MATH(abs);
17
18 // Get the object space Weingarten map dN
19 MatrixType dN = Base::dNormal().template middleCols<DataPoint::Dim>(Base::isScaleDer() ? 1: 0);
20
21 // Compute tangent-space basis
22 Mat32 B = tangentPlane(useNormal);
23
24 // Compute the 2x2 matrix representing the shape operator by transforming dN to the basis B.
25 // Recall that dN is a bilinear form, it thus transforms as follows:
26 Mat22 S = B.transpose() * dN * B;
27
28 // Recall that at this stage, the shape operator represented by S describes the normal curvature K_n(u) in the direction u \in R^2 as follows:
29 // K_n(u) = u^T S u
30 // The principal curvatures are fully defined by the values and the directions of the extrema of K_n.
31 //
32 // If the normal field N(x) comes from the gradient of a scalar field, then N(x) is curl-free, and dN and S are symmetric matrices.
33 // In this case, the extrema of the previous quadratic form are directly obtained by the the eigenvalue decomposition of S.
34 // However, if N(x) is only an approximation of the normal field of a surface, then N(x) is not necessarily curl-free, and in this case S is not symmetric.
35 // In this case, we first have to find an equivalent symmetric matrix S' such that:
36 // K_n(u) = u^T S' u,
37 // for any u \in R^2.
38 // It is easy to see that such a S' is simply obtained as:
39 // S' = (S + S^T)/2
40
41 S(0,1) = S(1,0) = (S(0,1) + S(1,0))/Scalar(2);
42 Eigen::SelfAdjointEigenSolver<Mat22> eig2;
43 eig2.computeDirect(S);
44
45 if (eig2.info() != Eigen::Success) return UNDEFINED;
46
47 Base::setCurvatureValues(eig2.eigenvalues()(0), eig2.eigenvalues()(1),
48 B * eig2.eigenvectors().col(0), B * eig2.eigenvectors().col(1));
49
50 return Base::m_eCurrentState;
51}
52
53template < class DataPoint, class _WFunctor, int DiffType, typename T>
54typename NormalDerivativesCurvatureEstimator<DataPoint, _WFunctor, DiffType, T>::Mat32
56{
57 typedef typename VectorType::Index Index;
58
59 PONCA_MULTIARCH_STD_MATH(sqrt);
60 PONCA_MULTIARCH_STD_MATH(abs);
61
62 Mat32 B;
63 Index i0=Index(-1), i1=Index(-1), i2=Index(-1);
64
65 // Two choices to compute a basis of the tangent plane
66 if(useNormal)
67 {
68 // Use a vector orthogonal to the surface (the gradient) and compute an
69 // orthonormal basis from it
70 VectorType n = Base::primitiveGradient();
71 n.array().abs().minCoeff(&i0); // i0: dimension where n extends the least
72 i1 = (i0+1)%3;
73 i2 = (i0+2)%3;
74
75 B.col(0)[i0] = 0;
76 B.col(0)[i1] = n[i2];
77 B.col(0)[i2] = -n[i1];
78
79 B.col(0).normalize();
80 B.col(1) = B.col(0).cross(n);
81 }
82 else
83 {
84 // Use the spatial derivative of the normal. This option leads to NaN
85 // values if dN is null (like in the case of a perfect plane)
86
87 // Get the object space Weingarten map dN
88 MatrixType dN = Base::dNormal().template middleCols<DataPoint::Dim>(Base::isScaleDer() ? 1: 0);
89
90 // Compute tangent-space basis from dN
91 // 1 - pick the column with maximal norm as the first tangent vector,
92 Scalar sqNorm = dN.colwise().squaredNorm().maxCoeff(&i0);
93 B.col(0) = dN.col(i0) / sqrt(sqNorm);
94 // 2 - orthogonalize the other column vectors, and pick the most reliable one
95 i1 = (i0+1)%3;
96 i2 = (i0+2)%3;
97 VectorType v1 = dN.col(i1) - B.col(0).dot(dN.col(i1)) * B.col(0);
98 VectorType v2 = dN.col(i2) - B.col(0).dot(dN.col(i2)) * B.col(0);
99 Scalar v1norm2 = v1.squaredNorm();
100 Scalar v2norm2 = v2.squaredNorm();
101 if(v1norm2 > v2norm2) B.col(1) = v1 / sqrt(v1norm2);
102 else B.col(1) = v2 / sqrt(v2norm2);
103 }
104
105 return B;
106}
107
108template < class DataPoint, class _WFunctor, int DiffType, typename T>
109void
111{
112 Base::init(_evalPos);
113 m_cov = MatrixType::Zero();
114 m_cog = VectorType::Zero();
115}
116
117template < class DataPoint, class _WFunctor, int DiffType, typename T>
118bool
120 (Scalar w, const VectorType &localQ, const DataPoint &attributes, ScalarArray &dw)
121{
122 if( Base::addLocalNeighbor(w, localQ, attributes, dw) )
123 {
124 m_cov += w * attributes.normal() * attributes.normal().transpose();
125 m_cog += attributes.normal();
126 return true;
127 }
128 return false;
129}
130
131template < class DataPoint, class _WFunctor, int DiffType, typename T>
134{
135 typedef typename VectorType::Index Index;
136
137 PONCA_MULTIARCH_STD_MATH(abs);
138
139 FIT_RESULT res = Base::finalize();
140
141 if(this->isReady())
142 {
143 // center of gravity (mean)
144 m_cog /= Base::getWeightSum();
145
146 // Center the covariance on the centroid
147 m_cov = m_cov/Base::getWeightSum() - m_cog * m_cog.transpose();
148
149 m_solver.computeDirect(m_cov);
150
151 Scalar kmin = m_solver.eigenvalues()(1);
152 Scalar kmax = m_solver.eigenvalues()(2);
153
154 VectorType vmin = m_solver.eigenvectors().col(1);
155 VectorType vmax = m_solver.eigenvectors().col(2);
156
157 // fallback
158 // TODO(thib) which epsilon value should be chosen ?
159 Scalar epsilon = Scalar(1e-3);
160 if(kmin<epsilon && kmax<epsilon)
161 {
162 kmin = Scalar(0);
163 kmax = Scalar(0);
164
165 // set principal directions from normals center of gravity
166 VectorType n = m_cog.normalized();
167 Index i0 = -1, i1 = -1, i2 = -1;
168 n.rowwise().squaredNorm().minCoeff(&i0);
169 i1 = (i0+1)%3;
170 i2 = (i0+2)%3;
171 vmin[i0] = 0;
172 vmin[i1] = n[i2];
173 vmin[i2] = -n[i1];
174 vmax[i0] = n[i1]*n[i1] + n[i2]*n[i2];
175 vmax[i1] = -n[i1]*n[i0];
176 vmax[i2] = -n[i2]*n[i0];
177 }
178
179 Base::setCurvatureValues(kmin, kmax, vmin, vmax);
180 }
181 return res;
182}
183
184template < class DataPoint, class _WFunctor, int DiffType, typename T>
185void
187{
188 Base::init(_evalPos);
189
190 m_cog = Vector2::Zero();
191 m_cov = Mat22::Zero();
192 m_pass = FIRST_PASS;
193 m_tframe = Mat32::Zero();
194}
195
196template < class DataPoint, class _WFunctor, int DiffType, typename T>
197bool
199 (Scalar w, const VectorType &localQ, const DataPoint &attributes, ScalarArray &dw)
200{
201 if(m_pass == FIRST_PASS)
202 {
203 return Base::addLocalNeighbor(w, localQ, attributes, dw);
204 }
205 else if(m_pass == SECOND_PASS)
206 {
207 // project normal on plane
208 VectorType n = attributes.normal();
209 Vector2 proj = m_tframe.transpose() * n;
210
211 m_cov += proj * proj.transpose();
212 m_cog += w * proj;
213 return true;
214 }
215 return false;
216}
217
218template < class DataPoint, class _WFunctor, int DiffType, typename T>
221{
222 if(m_pass == FIRST_PASS)
223 {
224 FIT_RESULT res = Base::finalize();
225
226 if(res != UNDEFINED)
227 {
228 // get normal of fitted plane
229 VectorType n = this->primitiveGradient(VectorType());
230
231 // compute orthonormal frame of the tangent plane
232 Index i0 = -1, i1 = -1, i2 = -1;
233 n.rowwise().squaredNorm().minCoeff(&i0);
234 i1 = (i0+1)%3;
235 i2 = (i0+2)%3;
236 m_tframe.col(0)[i0] = 0;
237 m_tframe.col(0)[i1] = n[i2];
238 m_tframe.col(0)[i2] = -n[i1];
239 m_tframe.col(1)[i0] = n[i1]*n[i1] + n[i2]*n[i2];
240 m_tframe.col(1)[i1] = -n[i1]*n[i0];
241 m_tframe.col(1)[i2] = -n[i2]*n[i0];
242
243 // go to second pass
244 m_pass = SECOND_PASS;
245 return NEED_OTHER_PASS;
246 }
247 else
248 {
249 return UNDEFINED;
250 }
251 }
252 else if(m_pass == SECOND_PASS)
253 {
254 // center of gravity (mean)
255 m_cog /= Base::getWeightSum();
256
257 // Center the covariance on the centroid
258 m_cov = m_cov/Base::getWeightSum() - m_cog * m_cog.transpose();
259
260 m_solver.computeDirect(m_cov);
261
262 Base::m_kmin = m_solver.eigenvalues()(0);
263 Base::m_kmax = m_solver.eigenvalues()(1);
264
265 // transform from local plane coordinates to world coordinates
266 Base::m_v1 = m_tframe * m_solver.eigenvectors().col(0);
267 Base::m_v2 = m_tframe * m_solver.eigenvectors().col(1);
268
269 //TODO(thib) which epsilon value should be chosen ?
270// Scalar epsilon = Eigen::NumTraits<Scalar>::dummy_precision();
271 Scalar epsilon = Scalar(1e-3);
272 if(Base::m_kmin<epsilon && Base::m_kmax<epsilon)
273 {
274 Base::m_kmin = Scalar(0);
275 Base::m_kmax = Scalar(0);
276
277 // set principal directions from fitted plane
278 Base::m_v2 = m_tframe.col(0);
279 Base::m_v1 = m_tframe.col(1);
280 }
281
282 return STABLE;
283 }
284 return UNDEFINED;
285}
Extension to compute curvature values based on a covariance analysis of normal vectors of neighbors.
typename DataPoint::Scalar Scalar
Alias to scalar type.
typename Base::ScalarArray ScalarArray
Alias to scalar derivatives array.
typename Base::VectorType VectorType
Alias to vector type.
Extension to compute curvature values from the Weingarten map .
typename DataPoint::MatrixType MatrixType
Alias to matrix type.
typename Base::VectorType VectorType
Alias to vector type.
typename DataPoint::Scalar Scalar
Alias to scalar type.
Extension to compute curvature values based on a covariance analysis of normal vectors of neighbors p...
typename Base::VectorType VectorType
Alias to vector type.
typename DataPoint::Scalar Scalar
Alias to scalar type.
typename Base::ScalarArray ScalarArray
Alias to scalar derivatives array.
FIT_RESULT
Enum corresponding to the state of a fitting method (and what the finalize function returns)
Definition: enums.h:15
@ UNDEFINED
The fitting is undefined, you can't use it for valid results.
Definition: enums.h:22
@ NEED_OTHER_PASS
The fitting procedure needs to analyse the neighborhood another time.
Definition: enums.h:25
@ CONFLICT_ERROR_FOUND
Multiple classes of the fitting procedure initialize the primitive.
Definition: enums.h:27
@ STABLE
The fitting is stable and ready to use.
Definition: enums.h:17