MeshLib C++ Docs
Loading...
Searching...
No Matches
MRConeApproximator.h
Go to the documentation of this file.
1#pragma once
2
3#include "MRMeshFwd.h"
4#include "MRCone3.h"
5#include "MRConstants.h"
6#include "MRParallelFor.h"
7#include "MRToFromEigen.h"
8#include "MRPch/MREigenCore.h"
9#include "MRPch/MRSuppressWarning.h"
10
11#include <Eigen/SVD>
12
13#ifdef _MSC_VER
14#pragma warning(push)
15#pragma warning(disable: 4244)
16#pragma warning(disable: 4464)
17#endif
18#include <unsupported/Eigen/NonLinearOptimization>
19#include <unsupported/Eigen/NumericalDiff>
20#ifdef _MSC_VER
21#pragma warning(pop)
22#endif
23
24#include <algorithm>
25
29
30namespace MR
31{
34
35
39template <typename T>
41{
42 using Scalar = T;
43 using InputType = Eigen::Matrix<T, Eigen::Dynamic, 1>;
44 using ValueType = Eigen::Matrix<T, Eigen::Dynamic, 1>;
45 using JacobianType = Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>;
46
47 std::vector <Eigen::Vector3<T>> points;
48
49 void setPoints( const std::vector<MR::Vector3<T>>& pointsMR )
50 {
51 points.reserve( pointsMR.size() );
52 for ( auto i = 0; i < pointsMR.size(); ++i )
53 {
54 points.push_back( toEigen( pointsMR[i] ) );
55 }
56 }
57
58 int inputs() const
59 {
60 return 6;
61 }
62 int values() const
63 {
64 return static_cast< int > ( points.size() );
65 }
66
70 int operator()( const InputType& x, ValueType& F ) const
71 {
72 Eigen::Vector3<T> V;
73 V( 0 ) = x( 0 );
74 V( 1 ) = x( 1 );
75 V( 2 ) = x( 2 );
76
77 Eigen::Vector3<T> W;
78 W( 0 ) = x( 3 );
79 W( 1 ) = x( 4 );
80 W( 2 ) = x( 5 );
81
82 for ( int i = 0; i < points.size(); ++i )
83 {
84 Eigen::Vector3<T> delta = V - points[i];
85 T deltaDotW = delta.dot( W );
86 F[i] = delta.squaredNorm() - deltaDotW * deltaDotW;
87 }
88 return 0;
89 }
90
93 int df( const InputType& x, JacobianType& J ) const
94 {
95
96 Eigen::Vector3<T> V;
97 V( 0 ) = x( 0 );
98 V( 1 ) = x( 1 );
99 V( 2 ) = x( 2 );
100
101 Eigen::Vector3<T> W;
102 W( 0 ) = x( 3 );
103 W( 1 ) = x( 4 );
104 W( 2 ) = x( 5 );
105
106 for ( int i = 0; i < points.size(); ++i )
107 {
108 const Eigen::Vector3<T>& P = points[i];
109 Eigen::Vector3<T> D = ( V - P );
110 T PW = D.dot( W );
111 Eigen::Vector3<T> PVW = V - P - PW * W;
112 Eigen::Vector3<T> PWD = PW * D;
113
115 J( i, 0 ) = 2 * PVW.x();
116 J( i, 1 ) = 2 * PVW.y();
117 J( i, 2 ) = 2 * PVW.z();
118
120 J( i, 3 ) = -2 * PWD.x();
121 J( i, 4 ) = -2 * PWD.y();
122 J( i, 5 ) = -2 * PWD.z();
123 }
124 return 0;
125 }
126
127};
128
129
136
143
148template <typename T>
150{
151public:
152
154
156 T solve( const std::vector<MR::Vector3<T>>& points,
157 Cone3<T>& cone, const Cone3ApproximationParams& params = {} )
158
159 {
160 params_ = params;
161
162 switch ( params_.coneFitterType )
163 {
165 return solveSpecificAxisFit_( points, cone );
166 break;
168 return solveHemisphereSearchFit_( points, cone );
169 break;
171 return solveApproximationPCM_( points, cone );
172 break;
173 default:
174 return std::numeric_limits<T>::max();
175 break;
176 };
177 }
178
179
180private:
181
183 Cone3ApproximationParams params_;
184
186 T solveFixedAxis_( const std::vector<MR::Vector3<T>>& points,
187 Cone3<T>& cone, bool useConeInputAsInitialGuess = false )
188
189 {
190 ConeFittingFunctor<T> coneFittingFunctor;
191 coneFittingFunctor.setPoints( points );
192 Eigen::LevenbergMarquardt<ConeFittingFunctor<T>, T> lm( coneFittingFunctor );
193 lm.parameters.maxfev = params_.levenbergMarquardtMaxIteration;
194
195 MR::Vector3<T> center, U;
196 computeCenterAndNormal_( points, center, U );
197
198 MR::Vector3<T>& coneAxis = cone.direction();
199 if ( useConeInputAsInitialGuess )
200 {
201 coneAxis = coneAxis.normalized();
202 }
203 else
204 {
205 cone = computeInitialCone_( points, center, U );
206 }
207
208 Eigen::VectorX<T> fittedParams( 6 );
209 coneToFitParams_( cone, fittedParams );
210 [[maybe_unused]] Eigen::LevenbergMarquardtSpace::Status result = lm.minimize( fittedParams );
211
214
215 fitParamsToCone_( fittedParams, cone );
216
217 T const one_v = static_cast< T >( 1 );
218 auto cosAngle = std::clamp( one_v / coneAxis.length(), static_cast< T >( 0 ), one_v );
219 cone.angle = std::acos( cosAngle );
220 cone.direction() = cone.direction().normalized();
221 cone.height = calculateConeHeight_( points, cone );
222
223 return getApproximationRMS_( points, cone );
224 }
225
226 T solveApproximationPCM_( const std::vector<MR::Vector3<T>>& points, Cone3<T>& cone )
227 {
228 return solveFixedAxis_( points, cone, false );
229 }
230
231 T solveSpecificAxisFit_( const std::vector<MR::Vector3<T>>& points, Cone3<T>& cone )
232 {
233 return solveFixedAxis_( points, cone, true );
234 }
235
237 T solveHemisphereSearchFit_( const std::vector<MR::Vector3<T>>& points, Cone3<T>& cone )
238 {
239 Vector3<T> center = computeCenter_( points );
240 ConeFittingFunctor<T> coneFittingFunctor;
241 coneFittingFunctor.setPoints( points );
242
243 constexpr T pi2 = static_cast< T >( PI2 );
244 T const theraStep = static_cast< T >( 2 * PI ) / params_.hemisphereSearchPhiResolution;
245 T const phiStep = pi2 / params_.hemisphereSearchPhiResolution;
246
247 struct BestCone {
248 Cone3<T> bestCone;
249 T minError = std::numeric_limits<T> ::max();
250 };
251 std::vector<BestCone> bestCones;
252 bestCones.resize( params_.hemisphereSearchPhiResolution + 1 );
253
254 ParallelFor( 0, params_.hemisphereSearchPhiResolution + 1, [&] ( int j )
255 {
256 T phi = phiStep * j;
257 T cosPhi = std::cos( phi );
258 T sinPhi = std::sin( phi );
259 for ( size_t i = 0; i < params_.hemisphereSearchThetaResolution; ++i )
260 {
261 T theta = theraStep * i;
262 T cosTheta = std::cos( theta );
263 T sinTheta = std::sin( theta );
264
266 Vector3<T> U( cosTheta * sinPhi, sinTheta * sinPhi, cosPhi );
267
268 auto tmpCone = computeInitialCone_( points, center, U );
269
270 Eigen::VectorX<T> fittedParams( 6 );
271 coneToFitParams_( tmpCone, fittedParams );
272
274 Eigen::LevenbergMarquardt<ConeFittingFunctor<T>, T> lm( coneFittingFunctor );
275 lm.parameters.maxfev = params_.levenbergMarquardtMaxIteration;
276 [[maybe_unused]] Eigen::LevenbergMarquardtSpace::Status result = lm.minimize( fittedParams );
277
280
281 fitParamsToCone_( fittedParams, tmpCone );
282
283 T const one_v = static_cast< T >( 1 );
284 auto cosAngle = std::clamp( one_v / tmpCone.direction().length(), static_cast< T >( 0 ), one_v );
285 tmpCone.angle = std::acos( cosAngle );
286 tmpCone.direction() = tmpCone.direction().normalized();
287
289 T error = getApproximationRMS_( points, tmpCone );
290 if ( error < bestCones[j].minError )
291 {
292 bestCones[j].minError = error;
293 bestCones[j].bestCone = tmpCone;
294 }
295 }
296 } );
297
299 auto bestAppox = std::min_element( bestCones.begin(), bestCones.end(), [] ( const BestCone& a, const BestCone& b )
300 {
301 return a.minError < b.minError;
302 } );
303
304 cone = bestAppox->bestCone;
305
307 cone.height = calculateConeHeight_( points, cone );
308
309 return bestAppox->minError;
310 }
311
313 T calculateConeHeight_( const std::vector<MR::Vector3<T>>& points, Cone3<T>& cone )
314 {
315 T length = static_cast< T > ( 0 );
316 for ( auto i = 0; i < points.size(); ++i )
317 {
318 length = std::max( length, std::abs( MR::dot( points[i] - cone.apex(), cone.direction() ) ) );
319 }
320 return length;
321 }
322
323 T getApproximationRMS_( const std::vector<MR::Vector3<T>>& points, const Cone3<T>& cone )
324 {
325 if ( points.size() == 0 )
326 return std::numeric_limits<T>::max();
327
328 T error = 0;
329 for ( auto p : points )
330 error = error + distanceSq( cone.projectPoint( p ), p );
331
332 return error / points.size();
333 }
334
335 MR::Vector3<T> computeCenter_( const std::vector<MR::Vector3<T>>& points )
336 {
338 MR::Vector3<T> center;
339 for ( auto i = 0; i < points.size(); ++i )
340 {
341 center += points[i];
342 }
343 center = center / static_cast< T >( points.size() );
344 return center;
345 }
346
347
348 void computeCenterAndNormal_( const std::vector<MR::Vector3<T>>& points, MR::Vector3<T>& center, MR::Vector3<T>& U )
349 {
350 center = computeCenter_( points );
351
353 U = Vector3f();
354 for ( auto i = 0; i < points.size(); ++i )
355 {
356 Vector3<T> Z = points[i] - center;
357 U += Z * MR::dot( Z, Z );
358 }
359 U = U.normalized();
360 }
361
362
364 Cone3<T> computeInitialCone_( const std::vector<MR::Vector3<T>>& points, const MR::Vector3<T>& center, const MR::Vector3<T>& axis )
365 {
366 Cone3<T> result;
367 MR::Vector3<T>& coneApex = result.apex();
368 result.direction() = axis;
369 MR::Vector3<T>& U = result.direction();
370 T& coneAngle = result.angle;
371
376
377 std::vector<Vector2<T>> hrPairs( points.size() );
378 T hMin = std::numeric_limits<T>::max(), hMax = -hMin;
379 for ( auto i = 0; i < points.size(); ++i )
380 {
381 MR::Vector3<T> delta = points[i] - center;
382 T h = MR::dot( U, delta );
383 hMin = std::min( hMin, h );
384 hMax = std::max( hMax, h );
385 Vector3<T> projection = delta - MR::dot( U, delta ) * U;
386 T r = projection.length();
387 hrPairs[i] = { h, r };
388 }
389
392
393 MR::Vector2<T> avgPoint;
394 T a, b;
395 findBestFitLine_( hrPairs, a, b, &avgPoint );
396 T hAverage = avgPoint.x;
397 T rAverage = avgPoint.y;
398 T hrSlope = a;
399
403 if ( hrSlope < 0 )
404 {
405 U = -U;
406 hrSlope = -hrSlope;
407 std::swap( hMin, hMax );
408 hMin = -hMin;
409 hMax = -hMax;
410 }
411
413 T rMin = rAverage + hrSlope * ( hMin - hAverage );
414 T rMax = rAverage + hrSlope * ( hMax - hAverage );
415 T hRange = hMax - hMin;
416 T rRange = rMax - rMin;
417
419 T tanAngle = rRange / hRange;
420 coneAngle = std::atan2( rRange, hRange );
421
423 T offset = rMax / tanAngle - hMax;
424 coneApex = center - offset * U;
425 return result;
426 }
427
429 void findBestFitLine_( const std::vector<MR::Vector2<T>>& xyPairs, T& lineA, T& lineB, MR::Vector2<T>* avg = nullptr )
430 {
431 auto numPoints = xyPairs.size();
432 Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> A( numPoints, 2 );
433 Eigen::Vector<T, Eigen::Dynamic> b( numPoints );
434
435 for ( auto i = 0; i < numPoints; ++i )
436 {
437 A( i, 0 ) = xyPairs[i].x;
438 A( i, 1 ) = 1.0;
439 b( i ) = xyPairs[i].y;
440 if ( avg )
441 *avg = *avg + xyPairs[i];
442 }
443 if ( avg )
444 {
445 *avg = *avg / static_cast < T > ( xyPairs.size() );
446 }
448 MR_SUPPRESS_WARNING_PUSH
449 MR_SUPPRESS_WARNING( "-Wdeprecated-declarations", 4996 )
450 Eigen::Matrix<T, Eigen::Dynamic, 1> x = A.bdcSvd( Eigen::ComputeThinU | Eigen::ComputeThinV ).solve( b );
451 MR_SUPPRESS_WARNING_POP
452
453 lineA = x( 0 );
454 lineB = x( 1 );
455
456 if ( avg )
457 {
458 *avg = *avg / static_cast < T > ( xyPairs.size() );
459 avg->y = lineA * avg->x + lineB;
460 }
461 }
462
464 void fitParamsToCone_( Eigen::Vector<T, Eigen::Dynamic>& fittedParams, Cone3<T>& cone )
465 {
466 cone.apex().x = fittedParams[0];
467 cone.apex().y = fittedParams[1];
468 cone.apex().z = fittedParams[2];
469
470 cone.direction().x = fittedParams[3];
471 cone.direction().y = fittedParams[4];
472 cone.direction().z = fittedParams[5];
473 }
474
476 void coneToFitParams_( Cone3<T>& cone, Eigen::Vector<T, Eigen::Dynamic>& fittedParams )
477 {
479 fittedParams[0] = cone.apex().x;
480 fittedParams[1] = cone.apex().y;
481 fittedParams[2] = cone.apex().z;
482
484 T coneCosAngle = std::cos( cone.angle );
485 fittedParams[3] = cone.direction().x / coneCosAngle;
486 fittedParams[4] = cone.direction().y / coneCosAngle;
487 fittedParams[5] = cone.direction().z / coneCosAngle;
488 }
489
490
491};
492
493}
Definition MRConeApproximator.h:150
Base class for cone parameterization.
Definition MRCone3.h:14
auto ParallelFor(I begin, I end, F &&f, Cb &&... cb)
Definition MRParallelFor.h:28
int hemisphereSearchThetaResolution
Definition MRConeApproximator.h:141
ConeFitterType
Definition MRConeApproximator.h:131
int values() const
Definition MRConeApproximator.h:62
Eigen::Matrix< T, Eigen::Dynamic, 1 > InputType
Definition MRConeApproximator.h:43
int hemisphereSearchPhiResolution
Definition MRConeApproximator.h:140
int inputs() const
Definition MRConeApproximator.h:58
int levenbergMarquardtMaxIteration
Definition MRConeApproximator.h:138
int operator()(const InputType &x, ValueType &F) const
Definition MRConeApproximator.h:70
ConeFitterType coneFitterType
Definition MRConeApproximator.h:139
T solve(const std::vector< MR::Vector3< T > > &points, Cone3< T > &cone, const Cone3ApproximationParams &params={})
returns RMS for original points
Definition MRConeApproximator.h:156
void setPoints(const std::vector< MR::Vector3< T > > &pointsMR)
Definition MRConeApproximator.h:49
auto length() const
Definition MRVector3.h:69
Cone3Approximation()=default
std::vector< Eigen::Vector3< T > > points
Definition MRConeApproximator.h:47
int df(const InputType &x, JacobianType &J) const
Definition MRConeApproximator.h:93
Vector3 normalized() const MR_REQUIRES_IF_SUPPORTED(std
Definition MRVector3.h:77
Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > JacobianType
Definition MRConeApproximator.h:45
T angle(const Vector3< T > &a, const Vector3< T > &b)
Definition MRVector3.h:222
Eigen::Matrix< T, Eigen::Dynamic, 1 > ValueType
Definition MRConeApproximator.h:44
T Scalar
Definition MRConeApproximator.h:42
length
Definition MRObjectDimensionsEnum.h:17
@ HemisphereSearchFit
approximation of cone axis by principal component method
std::optional< T > distanceSq(const Plane3< T > &plane1, const Plane3< T > &plane2, T errorLimit=std::numeric_limits< T >::epsilon() *T(20))
Definition MRIntersection.h:90
Eigen::Matrix< T, 2, 1 > toEigen(const Vector2< T > &v)
Definition MRToFromEigen.h:31
only for bindings generation
Definition MRCameraOrientationPlugin.h:8
Definition MRConeApproximator.h:137
Definition MRConeApproximator.h:41
Definition MRVector2.h:29
T x
Definition MRVector2.h:35
T y
Definition MRVector2.h:35
Definition MRVector3.h:33