ellipse_refinement.cpp
Go to the documentation of this file.
1 
9 #include <eigen3/Eigen/Dense>
10 #include <iostream>
11 //#include <opencv2/highgui/highgui.hpp>
12 
13 
14 namespace tuw
15 {
16 
17 template<typename T>
18 inline bool isZero(const T d)
19 {
20  return fabs(d) <= std::numeric_limits<T>::epsilon();
21 }
22 
26 inline double scaleAngle_0_2pi(double a)
27 {
28  while(a >= 2.*M_PI) a -= 2.*M_PI;
29  while(a < 0.) a += 2.*M_PI;
30  return a;
31 }
32 
33 using namespace std;
34 
39  : param(_param)
40 {
41 
42 }
43 
45 {
46 
47 }
48 
52 bool EllipseRefinement::refine(const cv::Mat_<short> &im_dx, const cv::Mat_<short> &im_dy,
53  const std::vector<cv::Point2f> &points, Ellipse &ellipse)
54 {
55  // get region around the ellipse edgels where to compute the dual ellipse
56  // that means, pixels where the gradient is higher than the given threshold and the distance
57  // to the ellipse borders along the line joining the pixel and the ellipse center
58  // is less than a certain radius (3px for example)
59 
60  double minx=10000, maxx=0, miny=10000, maxy=0;
61  double minGradPoints=10000,maxGradPoints=0;
62 
63  for(unsigned i=0; i<points.size(); i++)
64  {
65  const cv::Point2f &pt = points[i];
66 
67  if(pt.x < minx) minx=pt.x;
68  if(pt.y < miny) miny=pt.y;
69  if(pt.x > maxx) maxx=pt.x;
70  if(pt.y > maxy) maxy=pt.y;
71  float a = im_dx(pt.y,pt.x);
72  float b = im_dy(pt.y,pt.x);
73  float grad = sqrt(a*a + b*b);
74  if(minGradPoints > grad) minGradPoints = grad;
75  if(grad > maxGradPoints) maxGradPoints = grad;
76  }
77 
78  double gradThreshold = (minGradPoints + (maxGradPoints - minGradPoints)/2)/2;
79  gradThreshold = param.min_gradient; // minimum gradient to use a pixel
80  // it does not make sense to use different values for ellipseRand and rand
81  int ellipseRand=param.border; // maximum distance to given hypothesis
82  int rand=param.border; // window border size to search for border points (hypothesis parameter A+rand X B+rand)
83  pointsToUse.clear();
84  double sumx=0,sumy=0;
85  // searching within the window for points to use
86  short min_y = (short)(miny-rand);
87  short max_y = (short)(maxy+rand);
88  short min_x = (short)(minx-rand);
89  short max_x = (short)(maxx+rand);
90  if (min_y < 0) min_y = 0;
91  if (max_y >= im_dx.rows) max_y = (short)im_dx.rows-1;
92  if (min_x < 0) min_x = 0;
93  if (max_x >= im_dx.cols) max_x = (short)im_dx.cols-1;
94 
95  for(short j=min_y; j < max_y; j++)
96  {
97  for(short i=min_x; i < max_x; i++)
98  {
99  float a = im_dx(j,i);
100  float b = im_dy(j,i);
101  float grad = sqrt(a*a + b*b);
102  if(gradThreshold < grad &&
103  ellipse.insideEllipse(ellipse.a+ellipseRand,ellipse.b+ellipseRand,ellipse.x,ellipse.y, ellipse.phi,i,j) &&
104  !ellipse.insideEllipse(ellipse.a-ellipseRand,ellipse.b-ellipseRand,ellipse.x,ellipse.y, ellipse.phi,i,j))
105  {
106  pointsToUse.push_back(cv::Point2d(i,j));
107  sumx+=i;
108  sumy+=j;
109  }
110  }
111  }
112 
113  // now the pointsToUse array exists (points with a distance smaller than ellipseRand from the hypothesis)
114  // normalisation (to ensure numeric stability)
115  double mx = sumx/pointsToUse.size();
116  double my = sumy/pointsToUse.size();
117 
118  // compute scaling factors
119  double lengths=0;
120  for(unsigned i=0; i<pointsToUse.size(); i++)
121  {
122  double a = pointsToUse[i].x-mx;
123  double b = pointsToUse[i].y-my;
124 
125  lengths+= sqrt(a*a + b*b);
126  }
127 
128  double ss = sqrt(2.) / (lengths/pointsToUse.size());
129  Eigen::Matrix3d H;
130  H << ss, 0, -ss*mx, 0, ss, -ss*my, 0, 0, 1;
131  Eigen::Matrix3d Hinv = H.inverse();
132  Eigen::Matrix3d HinvT = Hinv.transpose();
133 
134 
135  // transformation finished
136  /*
137  Establish the least-square-system.
138 
139  In the following, [] means the definition of a vector or matrix and [x]^T its
140  the transpose of [x]
141 
142  A point xi = [ui,vi,1]^T (in homogenous coordinates) lies on the conic C iff
143  it satisfies xi^T*C*xi=0
144  C is:
145  A B/2 D/2
146  B/2 C E/2
147  D/2 E/2 F
148  The ConicParameters = [A,B,C,D,E,F] of the conic C can be obtained by least square techniques
149  by minimizing f(ConicParameters of C):
150 
151  f(ConicParameters of C) = sum_{iterate points}(w_i*(x_i^T*C*x_i)^2)
152 
153  Following part is based in the duality relationship of projective geometry where the role of
154  homogenous points and lines can be interchanged.
155 
156  In the dual space, a line l_i = [a_i,b_i,c_i]^T is tangent to the dual of the conic C*
157  iff it satisfies l_i^T*(C*)*l_i=0 where C* = inv(C)
158  As in the point space, the DualConicParameters = [A*,B*,C*,D*,E*,F*] of the dual conic C*
159  can be obtained similarly by finding f where f(DualConicParameters of C) reaches a minimum:
160 
161  f(DualConicParameters of C) = sum_{iterate lines}(w_i*(l_i^T*(C*)*l_i)^2) (Eq 1)
162 
163  li = [a_i,b_i,c_i]^T are obtained directly from the image gradient as follows:
164  a_i = dx_i
165  b_i = dy_i
166  c_i = -[dx_i,dy_i]^T*[x_i,y_i]
167 
168  when the image gradient at [x_i,y_i] is not null, it defines the normal orientation of a line
169  passing through [x_i,y_i]
170 
171  Operating in Eq 1, we obtain the following normal equations:
172  [sum{iterate lines}(sqr(w_i)*Ki*Ki^T)][DualConicParameters] = 0
173 
174  where Ki = [sqr(a_i),a_i*b_i, sqr(b_i), a_i*c_i, b_i*c_i, c_i*2]^T
175 
176  By using the constraint F* = 1 to solve the system and adding the ellipse discriminant 4*A*C - sqr(B) = 1,
177  we obtain the final system to find DualConicParameters' = [A'*,B'*,C'*,D'*,E'*,F'*]:
178 
179  sum{iterate lines}(-sqr(w_i)*K'i*sqr(c_i)) = 0
180 
181  which is the final system solved in the following code.
182  Further references to the method can be found in:
183  http://vision.gel.ulaval.ca/~hebert/pdf/ouellet07Ellipses.pdf
184  */
185 
186  Eigen::MatrixXd M = Eigen::MatrixXd::Zero(5,5);
187  Eigen::VectorXd E = Eigen::VectorXd::Zero(5);
188  Eigen::VectorXd Kprima(5);
189  Eigen::MatrixXd res1(5,5);
190  Eigen::Vector3d li;
191 
192  std::vector<Eigen::Vector3d> tangentLines((unsigned)pointsToUse.size());
193 
194  for (unsigned i=0; i<pointsToUse.size(); i++)
195  {
196  const cv::Point2d &pt = pointsToUse[i];
197  Kprima.setZero();
198  res1.setZero();
199  float a = im_dx((short)pt.y,(short)pt.x);
200  float b = im_dy((short)pt.y,(short)pt.x);
201  float grad = sqrt(a*a + b*b);
202 
203  // compute tangent lines to the dual conic directly from the image gradient
204  li << double(im_dx((short)pt.y,(short)pt.x)), double(im_dy((short)pt.y,(short)pt.x)),
205  -( double(im_dx((short)pt.y,(short)pt.x))*pt.x + double(im_dy((short)pt.y,(short)pt.x))*pt.y );
206 
207  // scale line such norm(a_i,b_i)=1
208  li /= grad;
209 
210  // normalize lines like L'=H^-T * L (-T is the inverse transposed)
211  li = HinvT * li;
212 
213  //save tangent line
214  tangentLines[i] = li;
215 
216  double weight = grad*grad;
217  Kprima[0] = li[0]*li[0];
218  Kprima[1] = li[0]*li[1];
219  Kprima[2] = li[1]*li[1];
220  Kprima[3] = li[0]*li[2];
221  Kprima[4] = li[1]*li[2];
222 
223  res1 = weight*Kprima*Kprima.transpose();
224  M += res1;
225 
226  Kprima[0] *= -weight*li[2]*li[2];
227  Kprima[1] *= -weight*li[2]*li[2];
228  Kprima[2] *= -weight*li[2]*li[2];
229  Kprima[3] *= -weight*li[2]*li[2];
230  Kprima[4] *= -weight*li[2]*li[2];
231 
232  E += Kprima;
233  }
234 
235  // solve the least square system using the pseudo inverse technique
236  // params = inv(M^T*M)*M^T*E
237 
238  Eigen::MatrixXd MT(5,5);
239  MT = M.transpose();
240 
241  Eigen::MatrixXd MPerMT(5,5), MPerMTinv(5,5);
242 
243  MPerMT = MT*M;
244  MPerMTinv = MPerMT.inverse();
245  MPerMT = MPerMTinv*MT;
246 
247  Eigen::VectorXd params(5);
248  params = MPerMT*E;
249 
250  Eigen::Matrix3d CNorm;
251  CNorm << params[0], params[1]/2., params[3]/2.,
252  params[1]/2., params[2], params[4]/2.,
253  params[3]/2., params[4]/2., 1;
254 
255  //unnormalize params
256  Eigen::Matrix3d CNormMalHInv;
257  CNormMalHInv = Hinv*CNorm*HinvT;
258 
259  //compute error in the dual space
260  //the error is proportional to distance between a tangent li and its pole xi=(C*)*li
261 
262  double dSumDistance=0;
263  Eigen::Vector3d pole;
264 
265  for (unsigned i=0; i<pointsToUse.size(); i++)
266  {
267  const Eigen::Vector3d &l = tangentLines[i];
268  //distance between li and pole
269  pole = CNorm*l;
270 
271  double x0 = pole[0]/pole[2];
272  double y0 = pole[1]/pole[2];
273  double a = l[0];
274  double b = l[1];
275  double d = abs(l[0] * x0 + l[1] * y0 + l[2]) / sqrt(a*a+b*b);
276  dSumDistance += d;
277  }
278 
279  ellipse.fit_error = dSumDistance / double(pointsToUse.size());
280 
281  // by inverting the dual conic matrix C* we obtain the original
282  // conic parameters we were seeking for
283 
284  Eigen::Matrix3d C;
285  C = CNormMalHInv.inverse();
286 
287  double Ac,Bc,Cc,Dc,Ec,Fc;
288  Ac = C(0,0);
289  Bc = C(0,1);
290  Cc = C(1,1);
291  Dc = C(2,0);
292  Ec = C(1,2);
293  Fc = C(2,2);
294 
295  // calculate ellipse parameters x/y/A/B/phi from conic equation Ax^2+Bxy+Cy^2....+F=0
296  bool ell_ok = ellipse.computeAndSetGeomFromConic(Ac,Bc,Cc,Dc,Ec,Fc);
297 
298  return ell_ok;
299 }
300 
301 
302 
307  : id(0), x(0), y(0), a(0), b(0), phi(0), fit_error(0.), support(0.), a2(0), a4(0), b2(0), b4(0), x2(0), y2(0)
308 {}
309 
311  : id(e.id), x(e.x), y(e.y), a(e.a), b(e.b), phi(e.phi), fit_error(e.fit_error), support(e.support), a2(e.a2), a4(e.a4), b2(e.b2), b4(e.b4), x2(e.x2), y2(e.y2){
312 }
313 
314 void EllipseRefinement::Ellipse::setEllipse(const double &_x, const double &_y, const double &_a, const double &_b, const double &_phi) {
315  x = _x, y = _y, a = _a, b = _b, phi = _phi;
316  a2 = a*a;
317  a4 = a2*a2;
318  b2 = b*b;
319  b4 = b2*b2;
320  x2 = 0;
321  y2 = 0;
322 }
323 
327 void EllipseRefinement::Ellipse::setEllipse(const cv::RotatedRect& rect) {
328  x = rect.center.x;
329  y = rect.center.y;
330  // box size is double the axis lengths
331  a = double(rect.size.width)/2.;
332  b = double(rect.size.height)/2.;
333  // note: the angle returned is in degrees!
334  phi = scaleAngle_0_2pi(rect.angle*M_PI/180.);
335  // note: for unknown reasons sometimes a < b!
336  if(a < b)
337  {
338  swap(a, b);
339  phi = scaleAngle_0_2pi(phi + M_PI_2);
340  }
341  setEllipse(x, y, a, b, phi);
342 }
343 
347 unsigned EllipseRefinement::Ellipse::ellipseSupport(const std::vector<cv::Point2f> &points, double inl_dist, std::vector<bool> &inl_idx)
348 {
349  double co = cos(-phi), si = sin(-phi), n, d, dist;
350  unsigned z;
351  unsigned nbInl=0;
352 
353  cv::Point2f p, q;
354 
355  fit_error = 0.;
356  inl_idx.resize(points.size());
357 
358  for(z=0; z<points.size(); z++)
359  {
360  // transform point in image coords to ellipse coords
361  // Note that this piece of code is called often.
362  // Implementing this explicitely here is faster than using
363  // TransformToEllipse(), as cos and sin only need to be evaluated once.
364  //p = Vector2(points[z].x,points[z].y);
365  p=points[z];
366  p.x -= x;
367  p.y -= y;
368  q.x = co*p.x - si*p.y;
369  q.y = si*p.x + co*p.y;
370  // calculate absolute distance to ellipse
371  if(isZero(q.x) && isZero(q.y))
372  dist = b;
373  else
374  {
375  x2 = q.x * q.x;
376  y2 = q.y * q.y;
377  n = fabs(x2/a2 + y2/b2 - 1.);
378  d = 2.*sqrt(x2/a4 + y2/b4);
379  dist = n/d;
380  }
381  if(dist <= inl_dist)
382  {
383  inl_idx[z]=true;
384  nbInl++;
385  fit_error += dist;
386  }
387  else
388  inl_idx[z]=false;
389  }
390 
391  double circumference = ellipseCircumference(a, b);
392 
393  if (nbInl>0 && !isZero(circumference))
394  {
395  fit_error /= (double)nbInl;
396  support = (double)nbInl / circumference;
397  }
398  else
399  {
400  fit_error = DBL_MAX;
401  support=0;
402  }
403 
404  return nbInl;
405 }
406 
407 
408 void EllipseRefinement::Ellipse::get(cv::RotatedRect &r) {
409  r.center.x = x, r.center.y = y;
410  r.size.width = a*2., r.size.height = b*2.;
411  r.angle = phi*180./M_PI;
412 }
413 
414 
418 bool EllipseRefinement::Ellipse::computeAndSetGeomFromConic(double Ac, double Bc, double Cc, double Dc, double Ec, double Fc) {
419 
420  double BcBcAcCc = (Bc*Bc - Ac*Cc);
421  if (BcBcAcCc == 0) {
422  return false;
423  }
424  double x0 = (Cc*Dc - Bc*Ec) / BcBcAcCc;
425  double y0 = (Ac*Ec - Bc*Dc) / BcBcAcCc;
426  double a0, a0_2, b0, b0_2, phi0;
427  double d = Ac-Cc;
428  double SqrAcCc4BcBc = d*d + 4*Bc*Bc;
429  if (SqrAcCc4BcBc < 0) {
430  return false;
431  }
432  a0_2 = (2*(Ac*Ec*Ec + Cc*Dc*Dc + Fc*Bc*Bc - 2*Bc*Dc*Ec - Ac*Cc*Fc)) / ((Bc*Bc - Ac*Cc)*((sqrt(SqrAcCc4BcBc)) - (Ac+Cc)));
433  b0_2 = (2*(Ac*Ec*Ec + Cc*Dc*Dc + Fc*Bc*Bc - 2*Bc*Dc*Ec - Ac*Cc*Fc)) / ((Bc*Bc - Ac*Cc)*(-(sqrt(SqrAcCc4BcBc)) - (Ac+Cc)));
434  if ((a0_2 < 0) || (b0_2 < 0)) {
435  return false;
436  }
437  a0 = sqrt(a0_2);
438  b0 = sqrt(b0_2);
439 
440  // double tempPhi=phi;
441  phi0=0;
442  if (Bc == 0) {
443  if (Ac < Cc) {
444  //cout << "1" << endl;
445  phi0=M_PI_2;
446  }
447  } else {
448  double z = (Ac-Cc) / (2*Bc);
449  if (Ac < Cc) {
450  phi0=0.5*(atan(1./z));
451  //cout << "m2" << endl;
452  }
453  if (Ac > Cc) {
454  phi0=M_PI_2 + 0.5*(atan(1./z));
455  //cout << "m3" << endl;
456  }
457  }
458 
459  x = x0;
460  y = y0;
461  a = a0;
462  b = b0;
463 
464  //phi = scaleAngle_0_2pi(phi0);
465  if (a < b) {
466  swap(a, b);
467  //phi = scaleAngle_0_2pi(phi + M_PI_2);
468  }
469 
470  //phi=tempPhi;
471 
472  return true;
473 }
474 
480 {
481  return M_PI*(1.5*(a + b) - sqrt(a*b));
482 }
483 
484 
488 bool EllipseRefinement::Ellipse::insideEllipse(double _a, double _b, double _x0, double _y0, double _phi, double _x, double _y) const
489 {
490  double dx = ((_x - _x0)*cos(_phi) + (_y-_y0)*sin(_phi)) / _a;
491  double dy = (-(_x - _x0)*sin(_phi) + (_y-_y0)*cos(_phi)) / _b;
492  double distance = dx * dx + dy * dy;
493  return (distance < 1.0) ? 1 : 0;
494 
495 }
496 } // -- THE END --
497 
d
bool param(const std::string &param_name, T &param_val, const T &default_val)
bool isZero(const T d)
double scaleAngle_0_2pi(double a)
TFSIMD_FORCE_INLINE tfScalar distance(const Vector3 &v) const
EllipseRefinement(const Parameter &_param=Parameter())
TFSIMD_FORCE_INLINE const tfScalar & y() const
double ellipseCircumference(double a, double b)
bool refine(const cv::Mat_< short > &im_dx, const cv::Mat_< short > &im_dy, const std::vector< cv::Point2f > &points, Ellipse &ellipse)
TFSIMD_FORCE_INLINE const tfScalar & x() const
void setEllipse(const double &_x, const double &_y, const double &_a, const double &_b, const double &_phi)
TFSIMD_FORCE_INLINE const tfScalar & z() const
bool computeAndSetGeomFromConic(double A, double B, double C, double D, double E, double F)
bool insideEllipse(double _a, double _b, double _x0, double _y0, double _phi, double _x, double _y) const
unsigned ellipseSupport(const std::vector< cv::Point2f > &points, double inl_dist, std::vector< bool > &inl_idx)
std::vector< cv::Point2d > pointsToUse


tuw_ellipses
Author(s): Markus Bader
autogenerated on Mon Jun 10 2019 15:42:10