contrib/oxl/mvl/HomgOperator2D.cxx
Go to the documentation of this file.
00001 // This is oxl/mvl/HomgOperator2D.cxx
00002 #ifdef VCL_NEEDS_PRAGMA_INTERFACE
00003 #pragma implementation
00004 #endif
00005 //:
00006 // \file
00007 
00008 #include "HomgOperator2D.h"
00009 
00010 #include <vcl_cassert.h>
00011 #include <vcl_iostream.h>
00012 #include <vcl_vector.h>
00013 
00014 #include <vnl/vnl_math.h>
00015 #include <vnl/vnl_matrix.h>
00016 #include <vnl/algo/vnl_scatter_3x3.h>
00017 #include <vnl/algo/vnl_svd.h>
00018 
00019 #include <mvl/HomgLine2D.h>
00020 #include <mvl/HomgPoint2D.h>
00021 #include <mvl/HomgLineSeg2D.h>
00022 
00023 // == BASICS ==
00024 
00025 //-----------------------------------------------------------------------------
00026 //: Cross product of two Homg2Ds
00027 void HomgOperator2D::cross(const Homg2D& a, const Homg2D& b, Homg2D* a_cross_b)
00028 {
00029   double x1 = a.x();
00030   double y1 = a.y();
00031   double w1 = a.w();
00032 
00033   double x2 = b.x();
00034   double y2 = b.y();
00035   double w2 = b.w();
00036 
00037   a_cross_b->set (y1 * w2 - w1 * y2,
00038                   w1 * x2 - x1 * w2,
00039                   x1 * y2 - y1 * x2);
00040 }
00041 
00042 //-----------------------------------------------------------------------------
00043 //: Dot product of two Homg2Ds
00044 double HomgOperator2D::dot(const Homg2D& a, const Homg2D& b)
00045 {
00046   double x1 = a.x();
00047   double y1 = a.y();
00048   double w1 = a.w();
00049 
00050   double x2 = b.x();
00051   double y2 = b.y();
00052   double w2 = b.w();
00053 
00054   return x1*x2 + y1*y2 + w1*w2;
00055 }
00056 
00057 //-----------------------------------------------------------------------------
00058 //: Normalize Homg2D to unit magnitude
00059 void HomgOperator2D::unitize(Homg2D* a)
00060 {
00061   double norm = a->x()*a->x() + a->y()*a->y() + a->w()*a->w();
00062 
00063   if (norm == 0.0) {
00064     vcl_cerr << "HomgOperator2D::unitize() -- Zero length vector\n";
00065     return;
00066   }
00067 
00068   norm = 1.0/vcl_sqrt(norm);
00069   a->set(a->x()*norm, a->y()*norm, a->w()*norm);
00070 }
00071 
00072 // == DISTANCE MEASUREMENTS IN IMAGE COORDINATES ==
00073 
00074 //: Get the square of the 2D distance between the two points.
00075 double HomgOperator2D::distance_squared(const HomgPoint2D& point1,
00076                                         const HomgPoint2D& point2)
00077 {
00078   double x1 = point1.x();
00079   double y1 = point1.y();
00080   double z1 = point1.w();
00081 
00082   double x2 = point2.x();
00083   double y2 = point2.y();
00084   double z2 = point2.w();
00085 
00086   if (z1 == 0 || z2 == 0) {
00087     vcl_cerr << "HomgOperator2D::distance_squared() -- point at infinity";
00088     return Homg::infinity;
00089   }
00090 
00091   double scale1 = 1.0/z1;
00092   double scale2 = 1.0/z2;
00093 
00094   return vnl_math_sqr (x1 * scale1 - x2 * scale2) +
00095          vnl_math_sqr (y1 * scale1 - y2 * scale2);
00096 }
00097 
00098 //-----------------------------------------------------------------------------
00099 //
00100 //: Get the square of the perpendicular distance to a line.
00101 // This is just the homogeneous form of the familiar
00102 // $\frac{a x + b y + c}{\sqrt{a^2+b^2}}$:
00103 // \f\[ d = \frac{(l^\top p)}{p_z\sqrt{l_x^2 + l_y^2}} \f\]
00104 // If either the point or the line are at infinity an error message is
00105 // printed and Homg::infinity is returned.
00106 double HomgOperator2D::perp_dist_squared (const HomgPoint2D& point, const HomgLine2D& line)
00107 {
00108   if (line.ideal(0.0) || point.ideal(0.0)) {
00109     vcl_cerr << "HomgOperator2D::perp_dist_squared() -- line or point at infinity\n";
00110     return Homg::infinity;
00111   }
00112 
00113   double numerator = vnl_math_sqr (dot(line, point));
00114   double denominator = (vnl_math_sqr(line.x()) + vnl_math_sqr(line.y())) * vnl_math_sqr(point.w());
00115 
00116   return numerator / denominator;
00117 }
00118 
00119 //: Return the distance of a line segment to a line.
00120 //  This is defined as the maximum of the distances of the two endpoints to the line.
00121 double HomgOperator2D::distance_squared(const HomgLineSeg2D& segment, const HomgLine2D& line)
00122 {
00123   return vnl_math_max(perp_dist_squared(segment.get_point1(), line),
00124                    perp_dist_squared(segment.get_point2(), line));
00125 }
00126 
00127 
00128 //: Return distance between line segments.
00129 double HomgOperator2D::distance(const HomgLineSeg2D& ls, const HomgLineSeg2D& ll, double OVERLAP_THRESH)
00130 {
00131   double norm = vcl_sqrt(ll.get_line().x()*ll.get_line().x()+ll.get_line().y()*ll.get_line().y());
00132   HomgLine2D lll;
00133   lll.set(ll.get_line().x()/norm,ll.get_line().y()/norm,ll.get_line().w()/norm);
00134   double dist1 = ls.get_point1().x()/ls.get_point1().w() * lll.x() +
00135                  ls.get_point1().y()/ls.get_point1().w() * lll.y() + lll.w();
00136   double dist2 = ls.get_point2().x()/ls.get_point2().w() * lll.x() +
00137                  ls.get_point2().y()/ls.get_point2().w() * lll.y() + lll.w();
00138 #ifdef DEBUG
00139   vcl_cerr << "dist 1 is " <<dist1 << " dist 2 is " <<dist2 << vcl_endl;
00140 #endif
00141   double dist = (vcl_fabs(dist1) + vcl_fabs(dist2))/2;
00142 
00143   // compute overlap
00144   // if smaller than OVERLAP_THRESH then reject
00145 
00146   //project ls.point1 and point2 onto ll
00147   vnl_double_2 p1(ls.get_point1().x()/ls.get_point1().w()-dist1*lll.x(),
00148                   ls.get_point1().y()/ls.get_point1().w()-dist1*lll.y());
00149 
00150   vnl_double_2 p2(ls.get_point2().x()/ls.get_point2().w()-dist2*(lll.x()),
00151                   ls.get_point2().y()/ls.get_point2().w()-dist2*(lll.y()));
00152 
00153   vnl_double_2 p3(ll.get_point1().x()/ll.get_point1().w(),
00154                   ll.get_point1().y()/ll.get_point1().w());
00155 
00156   vnl_double_2 p4(ll.get_point2().x()/ll.get_point2().w(),
00157                   ll.get_point2().y()/ll.get_point2().w());
00158 
00159   vnl_double_2 v1 = p2 - p1, v2 = p3 - p1, v3 = p4 - p1;
00160   double r3 = v2(0)/v1(0); if (r3 > 1) r3 = 1; else if (r3 < 0) r3 =0;
00161   double r4 = v3(0)/v1(0); if (r4 > 1) r4 = 1; else if (r4 < 0) r4 =0;
00162 
00163   double r = vcl_fabs(r3-r4) * v1.two_norm();
00164 
00165   if (r < OVERLAP_THRESH)
00166     dist = 1000000;
00167 
00168   return dist;
00169 }
00170 
00171 
00172 //: Return the "Schmid distance" from a point to a line segment.
00173 //  This is the distance to the closest point on the segment, be it endpoint or interior.
00174 // UNTESTED.
00175 double HomgOperator2D::distance_squared (const HomgLineSeg2D& lineseg, const HomgPoint2D& p)
00176 {
00177   const HomgPoint2D& p1 = lineseg.get_point1();
00178   const HomgPoint2D& p2 = lineseg.get_point2();
00179 
00180   double p1x = p1[0] / p1[2];
00181   double p1y = p1[1] / p1[2];
00182 
00183   double p2x = p2[0] / p2[2];
00184   double p2y = p2[1] / p2[2];
00185 
00186   double dx = p2x - p1x;
00187   double dy = p2y - p1y;
00188 
00189   double l = vcl_sqrt(dx*dx + dy*dy);
00190 
00191   double px = p[0] / p[2];
00192   double py = p[1] / p[2];
00193 
00194   double d = ((px - p1x)*dx + (py - p1y)*dy);
00195   if (d < 0)
00196     return distance_squared(p, p1);
00197   if (d > l)
00198     return distance_squared(p, p2);
00199 
00200   return perp_dist_squared(p, lineseg);
00201 }
00202 
00203 // == ANGLES ==
00204 
00205 //-----------------------------------------------------------------------------
00206 //: Get the anticlockwise angle between a line and the x axis.
00207 double HomgOperator2D::line_angle(const HomgLine2D& line)
00208 {
00209   return vcl_atan2(line.y(), line.x());
00210 }
00211 
00212 //-----------------------------------------------------------------------------
00213 //: Get the 0 to pi/2 angle between two lines
00214 double HomgOperator2D::abs_angle(const HomgLine2D& line1, const HomgLine2D& line2)
00215 {
00216   double angle1 = line_angle (line1);
00217   double angle2 = line_angle (line2);
00218 
00219   double diff = vnl_math_abs(angle2 - angle1);
00220 
00221   if (diff > vnl_math::pi)
00222     diff -= vnl_math::pi;
00223 
00224   if (diff > vnl_math::pi/2)
00225     return vnl_math::pi - diff;
00226   else
00227     return diff;
00228 }
00229 
00230 //-----------------------------------------------------------------------------
00231 //
00232 //: Get the angle between two lines.
00233 // Although homogeneous coordinates are only defined up to scale, here it is
00234 // assumed that a line with homogeneous coordinates (m) is at 180 degrees to
00235 // a line (-m), and this is why the term "oriented_line" is used.  However,
00236 // the overall scale (apart from sign) is not significant.
00237 
00238 double HomgOperator2D::angle_between_oriented_lines (const HomgLine2D& line1,
00239                                                      const HomgLine2D& line2)
00240 {
00241   double angle1 = line_angle (line1);
00242   double angle2 = line_angle (line2);
00243 
00244   double diff = angle2 - angle1;
00245 
00246   if (diff > vnl_math::pi)
00247     return diff - 2.0 * vnl_math::pi;
00248 
00249   if (diff < -vnl_math::pi)
00250     return diff + 2.0 * vnl_math::pi;
00251 
00252   return diff;
00253 }
00254 
00255 // == JOINS/INTERSECTIONS ==
00256 
00257 //-----------------------------------------------------------------------------
00258 //
00259 //: Get the line through two points (the cross-product).
00260 //
00261 
00262 HomgLine2D HomgOperator2D::join (const HomgPoint2D& point1, const HomgPoint2D& point2)
00263 {
00264   HomgLine2D answer;
00265   cross(point1, point2, &answer);
00266   return answer;
00267 }
00268 
00269 //-----------------------------------------------------------------------------
00270 //
00271 //: Get the line through two points (the cross-product).
00272 //  In this case, we assume that the points are oriented, and ensure the cross
00273 // is computed with positive point omegas.
00274 
00275 HomgLine2D HomgOperator2D::join_oriented (const HomgPoint2D& point1, const HomgPoint2D& point2)
00276 {
00277   double x1 = point1.x();
00278   double y1 = point1.y();
00279   double w1 = point1.w();
00280   bool s1 = w1 < 0;
00281 
00282   double x2 = point2.x();
00283   double y2 = point2.y();
00284   double w2 = point2.w();
00285   bool s2 = w2 < 0;
00286 
00287   if (s1 ^ s2)
00288     return HomgLine2D(-y1 * w2 + w1 * y2, -w1 * x2 + x1 * w2, -x1 * y2 + y1 * x2);
00289   else
00290     return HomgLine2D( y1 * w2 - w1 * y2,  w1 * x2 - x1 * w2,  x1 * y2 - y1 * x2);
00291 }
00292 
00293 //-----------------------------------------------------------------------------
00294 //
00295 //: Get the intersection point of two lines (the cross-product).
00296 //
00297 
00298 HomgPoint2D HomgOperator2D::intersection (const HomgLine2D& line1, const HomgLine2D& line2)
00299 {
00300   HomgPoint2D answer;
00301   cross(line1, line2, &answer);
00302   return answer;
00303 }
00304 
00305 //-----------------------------------------------------------------------------
00306 //
00307 //: Get the perpendicular line to line which passes through point.
00308 // Params are line $(a,b,c)$ and point $(x,y,1)$.
00309 // Then the cross product of $(x,y,1)$ and the line's direction $(a,b,0)$,
00310 // called $(p,q,r)$ satisfies
00311 //
00312 //   $ap+bq=0$ (perpendicular condition) and
00313 //
00314 //   $px+qy+r=0$ (incidence condition).
00315 
00316 HomgLine2D HomgOperator2D::perp_line_through_point (const HomgLine2D& line,
00317                                                     const HomgPoint2D& point)
00318 {
00319   HomgLine2D direction(line.x(), line.y(), 0);
00320   HomgLine2D answer;
00321   cross(direction, point, &answer);
00322   unitize(&answer);
00323   return answer;
00324 }
00325 
00326 //-----------------------------------------------------------------------------
00327 //
00328 //: Get the perpendicular projection of point onto line.
00329 //
00330 
00331 HomgPoint2D HomgOperator2D::perp_projection (const HomgLine2D& line,
00332                                              const HomgPoint2D& point)
00333 {
00334   HomgLine2D perpline = perp_line_through_point (line, point);
00335   HomgPoint2D answer;
00336   cross(line, perpline, &answer);
00337   unitize(&answer);
00338   return answer;
00339 }
00340 
00341 //: Return the midpoint of the line joining two homogeneous points
00342 HomgPoint2D HomgOperator2D::midpoint (const HomgPoint2D& p1, const HomgPoint2D& p2)
00343 {
00344   return HomgPoint2D(p1 * (1/(2*p1[2])) + p2*(1/(2*p2[2])));
00345 }
00346 
00347 // == FITTING ==
00348 
00349 // - Kanatani sect 2.2.2.
00350 static vnl_vector<double> most_orthogonal_vector(const vcl_vector<HomgLine2D>& inpoints)
00351 {
00352   vnl_scatter_3x3<double> scatter_matrix;
00353 
00354   for (unsigned i = 0; i < inpoints.size(); i++)
00355     scatter_matrix.add_outer_product(inpoints[i].get_vector());
00356 
00357   return scatter_matrix.minimum_eigenvector().as_ref();
00358 }
00359 
00360 static vnl_vector<double> most_orthogonal_vector_svd(const vcl_vector<HomgLine2D>& lines)
00361 {
00362   vnl_matrix<double> D(lines.size(), 3);
00363 
00364   for (unsigned i = 0; i < lines.size(); i++)
00365     D.set_row(i, lines[i].get_vector().as_ref());
00366 
00367   vnl_svd<double> svd(D);
00368   vcl_cerr << "[movrank " << svd.W() << ']';
00369 
00370   return svd.nullvector();
00371 }
00372 
00373 bool lines_to_point_use_svd = false;
00374 
00375 //: Intersect a set of 2D lines to find the least-square point of intersection.
00376 // This finds the point $\bf x$ that minimizes $\|\tt L \bf x\|$, where $\tt L$
00377 // is the matrix whose rows are the lines. The current implementation uses the
00378 // vnl_scatter_3x3<double> class from vnl to accumulate and compute the
00379 // nullspace of $\tt L^\top \tt L$.
00380 HomgPoint2D HomgOperator2D::lines_to_point(const vcl_vector<HomgLine2D>& lines)
00381 {
00382   // ho_triveccam_aspect_lines_to_point
00383   assert(lines.size() >= 2);
00384 
00385   if (lines_to_point_use_svd)
00386     return HomgPoint2D(most_orthogonal_vector_svd(lines));
00387   else
00388     return HomgPoint2D(most_orthogonal_vector(lines));
00389 }
00390 
00391 // == MISCELLANEOUS ==
00392 //
00393 //: Clip line to lineseg.
00394 // The infinite line is clipped against the viewport with
00395 // lower left corner (x0,y0) and upper right corner (x1,y1)
00396 
00397 HomgLineSeg2D HomgOperator2D::clip_line_to_lineseg(const HomgLine2D& line,
00398                                                    double x0, double y0,
00399                                                    double x1, double y1)
00400 {
00401   double nx = line.x();
00402   double ny = line.y();
00403   double nz = line.w();
00404 
00405   bool intersect_lr = vnl_math_abs(ny) > vnl_math_abs(nx);
00406 
00407   if (intersect_lr) {
00408     // Clip against verticals
00409     HomgPoint2D p1(x0 * ny, -(nz + x0 * nx), ny);
00410     HomgPoint2D p2(x1 * ny, -(nz + x1 * nx), ny);
00411     return HomgLineSeg2D(p1, p2);
00412   }
00413   else {
00414     HomgPoint2D p1(-(nz + y0 * ny), y0 * nx, nx);
00415     HomgPoint2D p2(-(nz + y1 * ny), y1 * nx, nx);
00416     return HomgLineSeg2D(p1, p2);
00417   }
00418 }
00419 
00420 //-----------------------------------------------------------------------------
00421 //: Calculates the crossratio of four collinear points p1, p2, p3 and p4.
00422 // This number is projectively invariant, and it is the coordinate of p4
00423 // in the reference frame where p2 is the origin (coordinate 0), p3 is
00424 // the unity (coordinate 1) and p1 is the point at infinity.
00425 // This cross ratio is often denoted as ((p1, p2; p3, p4)) (which also
00426 // equals ((p3, p4; p1, p2)) or ((p2, p1; p4, p3)) or ((p4, p3; p2, p1)) )
00427 // and is calculated as
00428 //                      p1 - p3   p2 - p3      (p1-p3)(p2-p4)
00429 //                      ------- : --------  =  --------------
00430 //                      p1 - p4   p2 - p4      (p1-p4)(p2-p3)
00431 //
00432 // In principle, any single nonhomogeneous coordinate from the four points
00433 // can be used as parameters for CrossRatio (but of course the same for all
00434 // points). The most reliable answer will be obtained when the coordinate with
00435 // the largest spacing is used, i.e., the one with smallest slope.
00436 //
00437 double HomgOperator2D::CrossRatio(const Homg2D& a, const Homg2D& b, const Homg2D& c, const Homg2D& d)
00438 {
00439   double x1 = a.x(), y1 = a.y(), w1 = a.w();
00440   double x2 = b.x(), y2 = b.y(), w2 = b.w();
00441   double x3 = c.x(), y3 = c.y(), w3 = c.w();
00442   double x4 = d.x(), y4 = d.y(), w4 = d.w();
00443   double x = x1 - x2; if (x<0) x = -x; // assuming a != b ;-)
00444   double y = y1 - y2; if (y<0) y = -y;
00445   double n = (x>y) ? (x1*w3-x3*w1)*(x2*w4-x4*w2) : (y1*w3-y3*w1)*(y2*w4-y4*w2);
00446   double m = (x>y) ? (x1*w4-x4*w1)*(x2*w3-x3*w2) : (y1*w4-y4*w1)*(y2*w3-y3*w2);
00447   if (n == 0 && m == 0)
00448     vcl_cerr << "CrossRatio not defined: three of the given points coincide\n";
00449   return n/m;
00450 }
00451 
00452 //: Conjugate point of three given collinear points.
00453 // If cross ratio cr is given (default: -1), the generalized conjugate point
00454 // returned is such that ((x1,x2;x3,answer)) = cr.
00455 Homg2D HomgOperator2D::Conjugate(const Homg2D& a, const Homg2D& b, const Homg2D& c, double cr)
00456 // Default for cr is -1.
00457 {
00458   double x1 = a.x(), y1 = a.y(), w1 = a.w();
00459   double x2 = b.x(), y2 = b.y(), w2 = b.w();
00460   double x3 = c.x(), y3 = c.y(), w3 = c.w();
00461   double kx = x1*w3 - x3*w1, mx = x2*w3 - x3*w2, nx = kx*w2-cr*mx*w1;
00462   double ky = y1*w3 - y3*w1, my = y2*w3 - y3*w2, ny = ky*w2-cr*my*w1;
00463   return Homg2D((x2*kx-cr*x1*mx)*ny, (y2*ky-cr*y1*my)*nx, nx*ny);
00464 }