vgl_h_matrix_2d_compute_linear.cxx
Go to the documentation of this file.
1 // This is core/vgl/algo/vgl_h_matrix_2d_compute_linear.cxx
2 #include <iostream>
3 #include <cmath>
5 //:
6 // \file
7 
8 #ifdef _MSC_VER
9 # include <vcl_msvc_warnings.h>
10 #endif
11 #include <cassert>
12 #include <vnl/vnl_inverse.h>
13 #include <vnl/vnl_transpose.h>
14 #include <vnl/algo/vnl_svd.h>
16 
17 //: Construct a vgl_h_matrix_2d_compute_linear object.
18 // The allow_ideal_points flag is described below.
20  allow_ideal_points_(allow_ideal_points)
21 {
22 }
23 
24 
25 // Should provide:
26 // Points-only method
27 // Lines-only
28 // Points&lines
29 //
30 // FSM - this is now done by vgl_h_matrix_2d_compute_design.
31 
32 constexpr int TM_UNKNOWNS_COUNT = 9;
33 constexpr double DEGENERACY_THRESHOLD = 0.00001; // FSM. see below.
34 
35 //-----------------------------------------------------------------------------
36 //
37 //: Compute a plane-plane projectivity using linear least squares.
38 // Returns false if the calculation fails or there are fewer than four point
39 // matches in the list. The algorithm finds the nullvector of the $2 n \times 9$ design
40 // matrix:
41 // \f[
42 // \left(\begin{array}{ccccccccc}
43 // 0 & 0 & 0 & x_1 z_1' & y_1 z_1' & z_1 z_1' & -x_1 y_1' & -y_1 y_1' & -z_1 y_1' \cr
44 // x_1 z_1' & y_1 z_1' & z_1 z_1' & 0 & 0 & 0 & -x_1 x_1' & -y_1 x_1' & -z_1 x_1' \cr
45 // \multicolumn{9}{c}{\cdots} \cr
46 // 0 & 0 & 0 & x_n z_n' & y_n z_n' & z_n z_n' & -x_n y_n' & -y_n y_n' & -z_n y_n'\cr
47 // x_n z_n' & y_n z_n' & z_n z_n' & 0 & 0 & 0 & -x_n x_n' & -y_n x_n' & -z_n x_n'
48 // \end{array}\right)
49 // \f]
50 // If \t allow_ideal_points was set at construction, the $3 \times 9$ version which
51 // allows for ideal points is used.
53 solve_linear_problem(int equ_count,
54  std::vector<vgl_homg_point_2d<double> > const& p1,
55  std::vector<vgl_homg_point_2d<double> > const& p2,
57 {
58  //transform the point sets and fill the design matrix
59  vnl_matrix<double> D(equ_count, TM_UNKNOWNS_COUNT);
60  int n = p1.size();
61  int row = 0;
62  for (int i = 0; i < n; i++) {
63  D(row, 0) = p1[i].x() * p2[i].w();
64  D(row, 1) = p1[i].y() * p2[i].w();
65  D(row, 2) = p1[i].w() * p2[i].w();
66  D(row, 3) = 0;
67  D(row, 4) = 0;
68  D(row, 5) = 0;
69  D(row, 6) = -p1[i].x() * p2[i].x();
70  D(row, 7) = -p1[i].y() * p2[i].x();
71  D(row, 8) = -p1[i].w() * p2[i].x();
72  ++row;
73 
74  D(row, 0) = 0;
75  D(row, 1) = 0;
76  D(row, 2) = 0;
77  D(row, 3) = p1[i].x() * p2[i].w();
78  D(row, 4) = p1[i].y() * p2[i].w();
79  D(row, 5) = p1[i].w() * p2[i].w();
80  D(row, 6) = -p1[i].x() * p2[i].y();
81  D(row, 7) = -p1[i].y() * p2[i].y();
82  D(row, 8) = -p1[i].w() * p2[i].y();
83  ++row;
84 
85  if (allow_ideal_points_) {
86  D(row, 0) = p1[i].x() * p2[i].y();
87  D(row, 1) = p1[i].y() * p2[i].y();
88  D(row, 2) = p1[i].w() * p2[i].y();
89  D(row, 3) = -p1[i].x() * p2[i].x();
90  D(row, 4) = -p1[i].y() * p2[i].x();
91  D(row, 5) = -p1[i].w() * p2[i].x();
92  D(row, 6) = 0;
93  D(row, 7) = 0;
94  D(row, 8) = 0;
95  ++row;
96  }
97  }
98 
99  D.normalize_rows();
100  vnl_svd<double> svd(D);
101 
102  //
103  // FSM added :
104  //
105  if (svd.W(7)<DEGENERACY_THRESHOLD*svd.W(8)) {
106  std::cerr << "vgl_h_matrix_2d_compute_linear : design matrix has rank < 8\n"
107  << "vgl_h_matrix_2d_compute_linear : probably due to degenerate point configuration\n";
108  return false;
109  }
110  // form the matrix from the nullvector
111  H.set(svd.nullvector().data_block());
112  return true;
113 }
114 
116 compute_p(std::vector<vgl_homg_point_2d<double> > const& points1,
117  std::vector<vgl_homg_point_2d<double> > const& points2,
119 {
120  //number of points must be the same
121  assert(points1.size() == points2.size());
122  int n = points1.size();
123 
124  int equ_count = n * (allow_ideal_points_ ? 3 : 2);
125  if (n * 2 < TM_UNKNOWNS_COUNT - 1) {
126  std::cerr << "vgl_h_matrix_2d_compute_linear: Need at least 4 matches.\n";
127  if (n == 0) std::cerr << "Could be std::vector setlength idiosyncrasies!\n";
128  return false;
129  }
130  //compute the normalizing transforms
131  vgl_norm_trans_2d<double> tr1, tr2;
132  if (!tr1.compute_from_points(points1))
133  return false;
134  if (!tr2.compute_from_points(points2))
135  return false;
136  std::vector<vgl_homg_point_2d<double> > tpoints1, tpoints2;
137  for (int i = 0; i<n; i++)
138  {
139  tpoints1.push_back(tr1(points1[i]));
140  tpoints2.push_back(tr2(points2[i]));
141  }
143  if (!solve_linear_problem(equ_count, tpoints1, tpoints2, hh))
144  return false;
145  //
146  // Next, hh has to be transformed back to the coordinate system of
147  // the original point sets, i.e.,
148  // p1' = tr1 p1 , p2' = tr2 p2
149  // hh was determined from the transform relation
150  // p2' = hh p1', thus
151  // (tr2 p2) = hh (tr1 p1)
152  // p2 = (tr2^-1 hh tr1) p1 = H p1
153  vgl_h_matrix_2d<double> tr2_inv = tr2.get_inverse();
154  H = tr2_inv*hh*tr1;
155  return true;
156 }
157 
159 compute_l(std::vector<vgl_homg_line_2d<double> > const& lines1,
160  std::vector<vgl_homg_line_2d<double> > const& lines2,
162 {
163  //number of lines must be the same
164  assert(lines1.size() == lines2.size());
165  int n = lines1.size();
166  int equ_count = 2*n;
167  //compute the normalizing transforms. By convention, these are point
168  //transformations.
169  vgl_norm_trans_2d<double> tr1, tr2;
170  if (!tr1.compute_from_lines(lines1))
171  return false;
172  if (!tr2.compute_from_lines(lines2))
173  return false;
174  std::vector<vgl_homg_point_2d<double> > tlines1, tlines2;
175  for (const auto & lit : lines1)
176  {
177  // transform the lines according to the normalizing transform
178  vgl_homg_line_2d<double> l = tr1(lit);
179  // convert the line to a point to use the same linear code
180  vgl_homg_point_2d<double> p(l.a(), l.b(), l.c());
181  tlines1.push_back(p);
182  }
183  for (const auto & lit : lines2)
184  {
185  // transform the lines according to the normalizing transform
186  vgl_homg_line_2d<double> l = tr2(lit);
187  // convert the line to a point to use the same linear code
188  vgl_homg_point_2d<double> p(l.a(), l.b(), l.c());
189  tlines2.push_back(p);
190  }
191 
192  vgl_h_matrix_2d<double> hl,hp,tr2inv;
193  if (!solve_linear_problem(equ_count, tlines1, tlines2, hl))
194  return false;
195  // The result is a transform on lines so we need to convert it to
196  // a point transform, i.e., hp = hl^-t.
197  vnl_matrix_fixed<double, 3, 3> const & Ml = hl.get_matrix();
198  vnl_matrix_fixed<double, 3, 3> Mp = vnl_inverse_transpose(Ml);
199  hp.set(Mp);
200  //
201  // Next, hp has to be transformed back to the coordinate system of
202  // the original lines, i.e.,
203  // l1' = tr1 l1 , l2' = tr2 l2
204  // hp was determined from the transform relation
205  // l2' = hh l1', thus
206  // (tr2 l2) = hh (tr1 l1)
207  // p2 = (tr2^-1 hh tr1) p1 = H p1
208  tr2inv = tr2.get_inverse();
209  H = tr2inv*hp*tr1;
210  return true;
211 }
212 
214 compute_pl(std::vector<vgl_homg_point_2d<double> > const& points1,
215  std::vector<vgl_homg_point_2d<double> > const& points2,
216  std::vector<vgl_homg_line_2d<double> > const& lines1,
217  std::vector<vgl_homg_line_2d<double> > const& lines2,
219 {
220  //number of points must be the same
221  assert(points1.size() == points2.size());
222  int np = points1.size();
223  //number of lines must be the same
224  assert(lines1.size() == lines2.size());
225  int nl = lines1.size();
226 
227  int equ_count = np * (allow_ideal_points_ ? 3 : 2) + 2*nl;
228  if ((np+nl)*2+1 < TM_UNKNOWNS_COUNT)
229  {
230  std::cerr << "vgl_h_matrix_2d_compute_linear: Need at least 4 matches.\n";
231  if (np+nl == 0) std::cerr << "Could be std::vector setlength idiosyncrasies!\n";
232  return false;
233  }
234  //compute the normalizing transforms
235  vgl_norm_trans_2d<double> tr1, tr2;
236  if (!tr1.compute_from_points_and_lines(points1,lines1))
237  return false;
238  if (!tr2.compute_from_points_and_lines(points2,lines2))
239  return false;
240  std::vector<vgl_homg_point_2d<double> > tpoints1, tpoints2;
241  for (int i = 0; i<np; i++)
242  {
243  tpoints1.push_back(tr1(points1[i]));
244  tpoints2.push_back(tr2(points2[i]));
245  }
246  for (int i = 0; i<nl; i++)
247  {
248  double a=lines1[i].a(), b=lines1[i].b(), c=lines1[i].c(), d=std::sqrt(a*a+b*b);
249  tpoints1.push_back(tr1(vgl_homg_point_2d<double>(-a*c,-b*c,d)));
250  a=lines2[i].a(), b=lines2[i].b(), c=lines2[i].c(), d = std::sqrt(a*a+b*b);
251  tpoints2.push_back(tr2(vgl_homg_point_2d<double>(-a*c,-b*c,d)));
252  }
254  if (!solve_linear_problem(equ_count, tpoints1, tpoints2, hh))
255  return false;
256 
257  vgl_h_matrix_2d<double> tr2_inv = tr2.get_inverse();
258  H = tr2_inv*hh*tr1;
259  return true;
260 }
261 
262 //--------------------------------------------------------
263 //:
264 // The solution equations should be weighted by the length of
265 // the corresponding line matches. This weighting is given by w.
266 //
267 // The two equations resulting from l1i<->l2i should be
268 // weighted by wi. Form a m x m diagonal matrix W with elements from w,
269 // with m = 2*Nc, where Nc=l1.size()=l2.size() is the number of
270 // corresponding line pairs. The weighted least squares problem is
271 // expressed as:
272 //
273 // (D^tWD)x = Mx = 0
274 //
275 // where D is the design matrix and x is the 9 element vector of unknown
276 // homography matrix elements. This problem can be solved using SVD as in the
277 // case of unweighted least squares.
278 //
281  std::vector<vgl_homg_line_2d<double> > const& l2,
282  std::vector<double> const& w,
284 {
285  int Nc = l1.size();
286  // Note the w has size Nc so we need to form a 2*Nc vector with
287  // repeated values
288  vnl_vector<double> two_w(2*Nc);
289  int j =0;
290  for (int i = 0; i<Nc; i++, j+=2)
291  {
292  two_w[j]=w[i];
293  two_w[j+1]=w[i];
294  }
295  vnl_diag_matrix<double> W(two_w);
296 
297  //Form the design matrix, D
298  vnl_matrix<double> D(2*Nc, TM_UNKNOWNS_COUNT);
299  vnl_matrix<double> M(TM_UNKNOWNS_COUNT, TM_UNKNOWNS_COUNT);
300 
301  int row = 0;
302  for (int i = 0; i < Nc; i++)
303  {
304  D(row, 0) = l1[i].a() * l2[i].c();
305  D(row, 1) = l1[i].b() * l2[i].c();
306  D(row, 2) = l1[i].c() * l2[i].c();
307  D(row, 3) = 0;
308  D(row, 4) = 0;
309  D(row, 5) = 0;
310  D(row, 6) = -l1[i].a() * l2[i].a();
311  D(row, 7) = -l1[i].b() * l2[i].a();
312  D(row, 8) = -l1[i].c() * l2[i].a();
313  ++row;
314 
315  D(row, 0) = 0;
316  D(row, 1) = 0;
317  D(row, 2) = 0;
318  D(row, 3) = l1[i].a() * l2[i].c();
319  D(row, 4) = l1[i].b() * l2[i].c();
320  D(row, 5) = l1[i].c() * l2[i].c();
321  D(row, 6) = -l1[i].a() * l2[i].b();
322  D(row, 7) = -l1[i].b() * l2[i].b();
323  D(row, 8) = -l1[i].c() * l2[i].b();
324  ++row;
325  }
326  M = vnl_transpose(D)*W*D;
327  D.normalize_rows();
328  vnl_svd<double> svd(D);
329 
330  //
331  // FSM added :
332  //
333  if (svd.W(7)<DEGENERACY_THRESHOLD*svd.W(8)) {
334  std::cerr << "vgl_h_matrix_2d_compute_linear : design matrix has rank < 8\n"
335  << "vgl_h_matrix_2d_compute_linear : probably due to degenerate point configuration\n";
336  return false;
337  }
338  // form the matrix from the nullvector
339  H.set(svd.nullvector().data_block());
340  return true;
341 }
342 
344 compute_l(std::vector<vgl_homg_line_2d<double> > const& lines1,
345  std::vector<vgl_homg_line_2d<double> > const& lines2,
346  std::vector<double> const & weights,
348 {
349  //number of lines must be the same
350  assert(lines1.size() == lines2.size());
351 //int n = lines1.size();
352  //compute the normalizing transforms. By convention, these are point
353  //transformations.
354  vgl_norm_trans_2d<double> tr1, tr2;
355  if (!tr1.compute_from_lines(lines1))
356  return false;
357  if (!tr2.compute_from_lines(lines2))
358  return false;
359  std::vector<vgl_homg_line_2d<double> > tlines1, tlines2;
360  for (const auto & lit : lines1)
361  {
362  // transform the lines according to the normalizing transform
363  vgl_homg_line_2d<double> l = tr1(lit);
364  tlines1.push_back(l);
365  }
366  for (const auto & lit : lines2)
367  {
368  // transform the lines according to the normalizing transform
369  vgl_homg_line_2d<double> l = tr2(lit);
370  tlines2.push_back(l);
371  }
372 
373  vgl_h_matrix_2d<double> hl,hp,tr2inv;
374  if (!solve_weighted_least_squares(tlines1, tlines2, weights, hl))
375  return false;
376  // The result is a transform on lines so we need to convert it to
377  // a point transform, i.e., hp = hl^-t.
378  vnl_matrix_fixed<double, 3, 3> const & Ml = hl.get_matrix();
379  // make sure Ml can be inverted
380  if( vnl_det(Ml) == 0.0 )
381  return false;
382  vnl_matrix_fixed<double, 3, 3> Mp = vnl_inverse_transpose(Ml);
383  hp.set(Mp);
384  //
385  // Next, hp has to be transformed back to the coordinate system of
386  // the original lines, i.e.,
387  // l1' = tr1 l1 , l2' = tr2 l2
388  // hp was determined from the transform relation
389  // l2' = hh l1', thus
390  // (tr2 l2) = hh (tr1 l1)
391  // p2 = (tr2^-1 hh tr1) p1 = H p1
392  tr2inv = tr2.get_inverse();
393  H = tr2inv*hp*tr1;
394  return true;
395 }
vnl_matrix_fixed< T, 3, 3 > const & get_matrix() const
Return the 3x3 homography matrix.
constexpr double DEGENERACY_THRESHOLD
Represents a homogeneous 2D line.
Definition: vgl_fwd.h:14
vgl_h_matrix_2d & set(unsigned int row_index, unsigned int col_index, T value)
Set an element of the 3x3 homography matrix.
contains class vgl_h_matrix_2d_compute_linear
The similarity transform that normalizes a point set.
bool compute_p(std::vector< vgl_homg_point_2d< double > > const &points1, std::vector< vgl_homg_point_2d< double > > const &points2, vgl_h_matrix_2d< double > &H) override
compute from matched points.
vgl_h_matrix_2d_compute_linear(bool allow_ideal_points=false)
Construct a vgl_h_matrix_2d_compute_linear object.
bool compute_from_lines(std::vector< vgl_homg_line_2d< T > > const &lines, bool isotropic=true)
The normalizing transform for lines is computed from the set of points defined by the intersection of...
bool solve_weighted_least_squares(std::vector< vgl_homg_line_2d< double > > const &l1, std::vector< vgl_homg_line_2d< double > > const &l2, std::vector< double > const &w, vgl_h_matrix_2d< double > &H)
for lines, the solution should be weighted by line length.
bool compute_from_points_and_lines(std::vector< vgl_homg_point_2d< T > > const &pts, std::vector< vgl_homg_line_2d< T > > const &lines, bool isotropic=true)
The normalizing transform for points and lines is computed from the set of points used by compute_fro...
bool solve_linear_problem(int equ_count, std::vector< vgl_homg_point_2d< double > > const &p1, std::vector< vgl_homg_point_2d< double > > const &p2, vgl_h_matrix_2d< double > &H)
Assumes all corresponding points have equal weight.
bool compute_from_points(std::vector< vgl_homg_point_2d< T > > const &points, bool isotropic=true)
compute the normalizing transform.
#define l
constexpr int TM_UNKNOWNS_COUNT
vgl_h_matrix_2d get_inverse() const
Return the inverse homography.
bool compute_pl(std::vector< vgl_homg_point_2d< double > > const &points1, std::vector< vgl_homg_point_2d< double > > const &points2, std::vector< vgl_homg_line_2d< double > > const &lines1, std::vector< vgl_homg_line_2d< double > > const &lines2, vgl_h_matrix_2d< double > &H) override
compute from matched points and lines.
Represents a homogeneous 2D point.
Definition: vgl_fwd.h:8
bool compute_l(std::vector< vgl_homg_line_2d< double > > const &lines1, std::vector< vgl_homg_line_2d< double > > const &lines2, vgl_h_matrix_2d< double > &H) override
compute from matched lines.