vgl_fit_sphere_3d.hxx
Go to the documentation of this file.
1 // This is core/vgl/algo/vgl_fit_sphere_3d.hxx
2 #ifndef vgl_fit_sphere_3d_hxx_
3 #define vgl_fit_sphere_3d_hxx_
4 //:
5 // \file
6 
7 #include <iostream>
8 #include "vgl_fit_sphere_3d.h"
10 #include <vnl/algo/vnl_svd.h>
11 #include <vnl/vnl_matrix.h>
12 #ifdef _MSC_VER
13 # include <vcl_msvc_warnings.h>
14 #endif
15 #include <vgl/vgl_distance.h>
16 #include <vnl/algo/vnl_levenberg_marquardt.h>
17 #include <vnl/vnl_least_squares_function.h>
18 class sphere_residual_function : public vnl_least_squares_function{
19  public:
21  vnl_least_squares_function(4, static_cast<unsigned>(pts.size()), vnl_least_squares_function::use_gradient), pts_(pts){}
22  //
23  // x = [x0, y0, z0, r]^t
24  // f = [(r0-r), (r1-r), ... , (ri-r), ... , (rn-1-r)]^t
25  // where ri = sqrt( (xi-x0)^2 + (yi-y0)^2 + (zi-z0)^2 )
26  //
27  void f(vnl_vector<double> const& x, vnl_vector<double>& fx) override{
28  double x0 = x[0], y0 = x[1], z0 = x[2], r = x[3];
29  unsigned n = get_number_of_residuals();
30  for(unsigned i = 0; i<n; ++i){
32  double xi = (p.x()-x0), yi = (p.y()-y0), zi = (p.z()-z0);
33  double ri = std::sqrt(xi*xi + yi*yi + zi*zi);
34  fx[i] = (ri-r);
35  }
36  //std::cout << "f\n" << fx << '\n';
37  }
38 //
39 // x = [x0, y0, z0, r]^t
40 // ri = sqrt( (xi-x0)^2 + (yi-y0)^2 + (zi-z0)^2 )
41 // J[i] = [-(xi-x0)/ri, -(yi-y0)/ri, -(zi-z0)/ri, -1]
42 //
43  void gradf(vnl_vector<double> const& x, vnl_matrix<double>& J) override{
44  double x0 = x[0], y0 = x[1], z0 = x[2];
45  unsigned n = get_number_of_residuals();
46  for (unsigned i = 0; i<n; ++i)
47  {
49  double xi = (p.x()-x0), yi = (p.y()-y0), zi = (p.z()-z0);
50  double ri = std::sqrt(xi*xi + yi*yi + zi*zi);
51  J[i][0]= -(xi-x0)/ri; J[i][1]= -(yi-y0)/ri; J[i][2]= -(zi-z0)/ri; J[i][3]= -1.0;
52  }
53  //std::cout << "J\n" << J << '\n';
54  }
55  private:
56  std::vector<vgl_homg_point_3d<double> > pts_;
57 };
58 
59 template <class T>
61 
62 {
63  for(typename std::vector<vgl_point_3d<T> >::iterator pit = points.begin();
64  pit != points.end(); ++pit)
65  points_.push_back(vgl_homg_point_3d<T>(*pit));
66 }
67 
68 template <class T>
70 {
71  points_.push_back(vgl_homg_point_3d<T>(p));
72 }
73 
74 template <class T>
75 void vgl_fit_sphere_3d<T>::add_point(const T x, const T y, const T z)
76 {
77  points_.push_back(vgl_homg_point_3d<T>(x, y, z));
78 }
79 
80  template <class T>
82 {
83  points_.clear();
84 }
85 
86 template <class T>
87 T vgl_fit_sphere_3d<T>::fit_linear(std::ostream* errstream)
88 {
89  const unsigned n = static_cast<unsigned>(points_.size());
90  if(!n){
91  if (errstream)
92  *errstream << "No points to fit sphere\n";
93  return T(-1);
94  }
95  // normalize the points
97  if (!norm.compute_from_points(points_) && errstream) {
98  *errstream << "there is a problem with norm transform\n";
99  return T(-1);
100  }
101  vnl_matrix<T> A(n,4), B(n,1);
102  for (unsigned i=0; i<n; i++) {
103  vgl_homg_point_3d<T> hp = norm(points_[i]);//normalize
104  const T x = hp.x()/hp.w();
105  const T y = hp.y()/hp.w();
106  const T z = hp.z()/hp.w();
107  A[i][0]= -T(2)*x; A[i][1]= -T(2)*y; A[i][2]= -T(2)*z; A[i][3]=T(1);
108  B[i][0] = -(x*x + y*y + z*z);
109  }
110  vnl_svd<T> svd(A);
111  vnl_matrix<T> P = svd.solve(B);
112  T x0 = P[0][0], y0 = P[1][0], z0 = P[2][0];
113  T rho = P[3][0];
114  T r2 = x0*x0 + y0*y0 + z0*z0 - rho;
115  if(r2<T(0)){
116  if(errstream)
117  *errstream << "Negative squared radius - impossible result \n";
118  return T(-1);
119  }
120  T r = std::sqrt(r2);
121 
122  vnl_matrix_fixed<T,4,4> H = norm.get_matrix();
123  T scale = H[0][0];
124  T tx = H[0][3]; T ty= H[1][3]; T tz= H[2][3];
125 
126  // scale back to original coordinates
127  T x0p = (x0-tx)/scale, y0p = (y0-ty)/scale, z0p = (z0-tz)/scale, rp = r/scale;
128 
129  // construct the sphere
130  sphere_lin_.set_radius(rp);
131  sphere_lin_.set_centre(vgl_point_3d<T>(x0p, y0p, z0p));
132  // compute average distance error
133  double dsum = 0.0;
134  for (unsigned i=0; i<n; i++) {
135  vgl_point_3d<T> p(points_[i]);
136  double d = vgl_distance(p, sphere_lin_);
137  dsum += d;
138  }
139  return static_cast<T>(dsum/n);
140 }
141 template <class T>
142 T vgl_fit_sphere_3d<T>::fit(std::ostream* outstream, bool verbose){
143 
144  T error = this->fit_linear(outstream);
145  T lin_radius = sphere_lin_.radius();
146 
147  if(error == T(-1) || error> T(0.1)*lin_radius){
148  if(outstream)
149  *outstream << " Linear fit failed - non-linear fit abandoned\n";
150  return T(-1);
151  }
152  unsigned n = static_cast<unsigned>(points_.size());
153  // normalize the points
155  if (!norm.compute_from_points(points_) && outstream) {
156  *outstream << "there is a problem with norm transform\n";
157  return T(-1);
158  }
159  vnl_matrix_fixed<T,4,4> H = norm.get_matrix();
160  T scale = H[0][0];
161  T tx = H[0][3]; T ty= H[1][3]; T tz= H[2][3];
162  // normalize linear fit sphere
163  lin_radius *= scale;
164  // scale center
165  vgl_point_3d<T> c = sphere_lin_.centre();
166  T lin_x0 = scale*c.x()+tx, lin_y0 = scale*c.y()+ty, lin_z0 = scale*c.z()+tz;
167 
168  std::vector<vgl_homg_point_3d<double> > pts;
169  for(unsigned i = 0; i<n; ++i){
170  vgl_homg_point_3d<T> hp = norm(points_[i]);//normalize
171  vgl_homg_point_3d<double> hpd(static_cast<double>(hp.x()),
172  static_cast<double>(hp.y()),
173  static_cast<double>(hp.z()),
174  static_cast<double>(hp.w()));
175  pts.push_back(hpd);
176  }
177  sphere_residual_function srf(pts);
178  vnl_levenberg_marquardt lm(srf);
179 
180  vnl_vector<double> x_init(4);
181  x_init[0]=static_cast<double>(lin_x0);x_init[1]=static_cast<double>(lin_y0);
182  x_init[2]=static_cast<double>(lin_z0);x_init[3]=static_cast<double>(lin_radius);
183 
184  lm.minimize(x_init);
185  if(outstream && verbose)
186  lm.diagnose_outcome(*outstream);
187 
188  vnl_nonlinear_minimizer::ReturnCodes code = lm.get_failure_code();
189  if((code==vnl_nonlinear_minimizer::CONVERGED_FTOL||
190  code==vnl_nonlinear_minimizer::CONVERGED_XTOL||
191  code==vnl_nonlinear_minimizer::CONVERGED_XFTOL||
192  code==vnl_nonlinear_minimizer::CONVERGED_GTOL)){
193  T x0 = static_cast<T>(x_init[0]), y0 = static_cast<T>(x_init[1]), z0 = static_cast<T>(x_init[2]);
194  T nr = static_cast<T>(x_init[3]);
195  // scale back to original coordinates
196  T x0p = (x0-tx)/scale, y0p = (y0-ty)/scale, z0p = (z0-tz)/scale, rp = nr/scale;
197 
198  vgl_point_3d<T> cf(x0p, y0p, z0p);
199  sphere_non_lin_.set_centre(cf);
200  sphere_non_lin_.set_radius(rp);
201  }else{//non linear optimize failed -- use linear fit result
202  sphere_non_lin_ = sphere_lin_;
203  }
204  // compute average distance error
205  double dsum = 0.0;
206  for (unsigned i=0; i<n; i++) {
207  vgl_point_3d<T> p(points_[i]);
208  double d = vgl_distance(p, sphere_non_lin_);
209  dsum += d;
210  }
211  return static_cast<T>(dsum/n);
212 }
213 template <class T>
214 std::vector<vgl_point_3d<T> > vgl_fit_sphere_3d<T>::get_points() const{
215  std::vector<vgl_point_3d<T> > ret;
216  const unsigned n = static_cast<unsigned>(points_.size());
217  for (unsigned i=0; i<n; i++){
218  vgl_point_3d<T> p(points_[i]);
219  ret.push_back(p);
220  }
221  return ret;
222 }
223 template <class T>
224 bool vgl_fit_sphere_3d<T>::fit_linear(const T error_marg, std::ostream* outstream){
225  T error = fit_linear(outstream);
226  return (error<error_marg);
227 }
228 
229 template <class T>
230 bool vgl_fit_sphere_3d<T>::fit(const T error_marg, std::ostream* outstream, bool verbose){
231  T error = fit(outstream, verbose);
232  return (error<error_marg);
233 }
234 //--------------------------------------------------------------------------
235 #undef VGL_FIT_SPHERE_3D_INSTANTIATE
236 #define VGL_FIT_SPHERE_3D_INSTANTIATE(T) \
237 template class vgl_fit_sphere_3d<T >
238 
239 #endif // vgl_fit_sphere_3d_hxx_
std::vector< vgl_homg_point_3d< double > > pts_
T fit_linear(std::ostream *outstream=nullptr)
fit a sphere to the stored points using a linear method.
sphere_residual_function(std::vector< vgl_homg_point_3d< double > > const &pts)
Represents a cartesian 3D point.
Definition: vgl_fwd.h:11
Represents a homogeneous 3D point.
Definition: vgl_fwd.h:9
Type & z()
Definition: vgl_point_3d.h:73
std::vector< vgl_point_3d< T > > get_points() const
vgl_fit_sphere_3d()=default
void add_point(vgl_point_3d< T > const &p)
add a point to point set.
bool compute_from_points(std::vector< vgl_homg_point_3d< T > > const &points)
compute the normalizing transform.
void gradf(vnl_vector< double > const &x, vnl_matrix< double > &J) override
Type & x()
Definition: vgl_point_3d.h:71
Fits a sphere to a set of 3D points.
vnl_matrix_fixed< T, 4, 4 > const & get_matrix() const
Return the 4x4 homography matrix.
T fit(std::ostream *outstream=nullptr, bool verbose=false)
fits a sphere nonlinearly to the stored points using Levenberg Marquardt.
double vgl_distance(vgl_point_2d< T >const &p1, vgl_point_2d< T >const &p2)
return the distance between two points.
Definition: vgl_distance.h:104
void f(vnl_vector< double > const &x, vnl_vector< double > &fx) override
Set of distance functions.
The similarity transform that normalizes a 3-d point set.
void clear()
clear internal data.
Type & y()
Definition: vgl_point_3d.h:72
vgl_point_3d< Type > centre(vgl_point_3d< Type > const &p1, vgl_point_3d< Type > const &p2)
Return the point at the centre of gravity of two given points.
Definition: vgl_point_3d.h:224