Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpHomographyRansac.cpp
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2024 by Inria. All rights reserved.
4 *
5 * This software is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 * See the file LICENSE.txt at the root directory of this source
10 * distribution for additional information about the GNU GPL.
11 *
12 * For using ViSP with software that can not be combined with the GNU
13 * GPL, please contact Inria about acquiring a ViSP Professional
14 * Edition License.
15 *
16 * See https://visp.inria.fr for more information.
17 *
18 * This software was developed at:
19 * Inria Rennes - Bretagne Atlantique
20 * Campus Universitaire de Beaulieu
21 * 35042 Rennes Cedex
22 * France
23 *
24 * If you have questions regarding the use of this file, please contact
25 * Inria at visp@inria.fr
26 *
27 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29 *
30 * Description:
31 * Homography estimation.
32 */
33
34#include <visp3/core/vpDebug.h>
35#include <visp3/core/vpColVector.h>
36#include <visp3/core/vpRansac.h>
37#include <visp3/vision/vpHomography.h>
38
39#include <visp3/core/vpDisplay.h>
40#include <visp3/core/vpImage.h>
41#include <visp3/core/vpMeterPixelConversion.h>
42
43#define VPEPS 1e-6
44
46
51
52#ifndef DOXYGEN_SHOULD_SKIP_THIS
53
54bool iscolinear(double *x1, double *x2, double *x3);
55bool isColinear(vpColVector &p1, vpColVector &p2, vpColVector &p3);
56
57bool iscolinear(double *x1, double *x2, double *x3)
58{
59 vpColVector p1(3), p2(3), p3(3);
60 p1 << x1;
61 p2 << x2;
62 p3 << x3;
63 // Assume inhomogeneous coords, or homogeneous coords with equal
64 // scale.
65 return ((vpColVector::cross(p2 - p1, p3 - p1).sumSquare()) < VPEPS);
66}
67bool isColinear(vpColVector &p1, vpColVector &p2, vpColVector &p3)
68{
69 return ((vpColVector::cross(p2 - p1, p3 - p1).sumSquare()) < VPEPS);
70}
71
72bool vpHomography::degenerateConfiguration(const vpColVector &x, unsigned int *ind, double threshold_area)
73{
74
75 unsigned int i, j, k;
76
77 for (i = 1; i < 4; ++i) {
78 for (j = 0; j < i; ++j) {
79 if (ind[i] == ind[j]) {
80 return true;
81 }
82 }
83 }
84
85 unsigned int n = x.getRows() / 4;
86 double pa[4][3];
87 double pb[4][3];
88
89 for (i = 0; i < 4; ++i) {
90 pb[i][0] = x[2 * ind[i]];
91 pb[i][1] = x[(2 * ind[i]) + 1];
92 pb[i][2] = 1;
93
94 pa[i][0] = x[(2 * n) + (2 * ind[i])];
95 pa[i][1] = x[(2 * n) + (2 * ind[i]) + 1];
96 pa[i][2] = 1;
97 }
98
99 i = 0, j = 1, k = 2;
100
101 double area012 = ((((((-pa[j][0] * pa[i][1]) + (pa[k][0] * pa[i][1])) + (pa[i][0] * pa[j][1])) - (pa[k][0] * pa[j][1])) +
102 (-pa[i][0] * pa[k][1])) + (pa[1][j] * pa[k][1]));
103
104 i = 0;
105 j = 1, k = 3;
106 double area013 = ((((((-pa[j][0] * pa[i][1]) + (pa[k][0] * pa[i][1])) + (pa[i][0] * pa[j][1])) - (pa[k][0] * pa[j][1])) +
107 (-pa[i][0] * pa[k][1])) + (pa[1][j] * pa[k][1]));
108
109 i = 0;
110 j = 2, k = 3;
111 double area023 = ((((((-pa[j][0] * pa[i][1]) + (pa[k][0] * pa[i][1])) + (pa[i][0] * pa[j][1])) - (pa[k][0] * pa[j][1])) +
112 (-pa[i][0] * pa[k][1])) + (pa[1][j] * pa[k][1]));
113
114 i = 1;
115 j = 2, k = 3;
116 double area123 = ((((((-pa[j][0] * pa[i][1]) + (pa[k][0] * pa[i][1])) + (pa[i][0] * pa[j][1])) - (pa[k][0] * pa[j][1])) +
117 (-pa[i][0] * pa[k][1])) + (pa[1][j] * pa[k][1]));
118
119 double sum_area = area012 + area013 + area023 + area123;
120
121 if (sum_area < threshold_area) {
122 return true;
123 }
124 else if (iscolinear(pa[0], pa[1], pa[2])) {
125 return true;
126 }
127 else if (iscolinear(pa[0], pa[1], pa[3])) {
128 return true;
129 }
130 else if (iscolinear(pa[0], pa[2], pa[3])) {
131 return true;
132 }
133 else if (iscolinear(pa[1], pa[2], pa[3])) {
134 return true;
135 }
136 else if (iscolinear(pb[0], pb[1], pb[2])) {
137 return true;
138 }
139 else if (iscolinear(pb[0], pb[1], pb[3])) {
140 return true;
141 }
142 else if (iscolinear(pb[0], pb[2], pb[3])) {
143 return true;
144 }
145 else if (iscolinear(pb[1], pb[2], pb[3])) {
146 return true;
147 }
148 else {
149 return false;
150 }
151}
152
153/*
154 \brief
155 Function to determine if a set of 4 pairs of matched points give rise
156 to a degeneracy in the calculation of a homography as needed by RANSAC.
157 This involves testing whether any 3 of the 4 points in each set is
158 colinear.
159
160 point are coded this way
161 x1b,y1b, x2b, y2b, ... xnb, ynb
162 x1a,y1a, x2a, y2a, ... xna, yna
163 leading to 2*2*n
164*/
165bool vpHomography::degenerateConfiguration(const vpColVector &x, unsigned int *ind)
166{
167 for (unsigned int i = 1; i < 4; ++i) {
168 for (unsigned int j = 0; j < i; ++j) {
169 if (ind[i] == ind[j]) {
170 return true;
171 }
172 }
173 }
174
175 const unsigned int val_4 = 4;
176 unsigned int n = x.getRows() / val_4;
177 double pa[4][3];
178 double pb[4][3];
179 unsigned int n2 = 2 * n;
180 for (unsigned int i = 0; i < val_4; ++i) {
181 unsigned int ind2 = 2 * ind[i];
182 pb[i][0] = x[ind2];
183 pb[i][1] = x[ind2 + 1];
184 pb[i][2] = 1;
185
186 pa[i][0] = x[n2 + ind2];
187 pa[i][1] = x[n2 + ind2 + 1];
188 pa[i][2] = 1;
189 }
190
191 if (iscolinear(pa[0], pa[1], pa[2])) {
192 return true;
193 }
194 else if (iscolinear(pa[0], pa[1], pa[3])) {
195 return true;
196 }
197 else if (iscolinear(pa[0], pa[2], pa[3])) {
198 return true;
199 }
200 else if (iscolinear(pa[1], pa[2], pa[3])) {
201 return true;
202 }
203 else if (iscolinear(pb[0], pb[1], pb[2])) {
204 return true;
205 }
206 else if (iscolinear(pb[0], pb[1], pb[3])) {
207 return true;
208 }
209 else if (iscolinear(pb[0], pb[2], pb[3])) {
210 return true;
211 }
212 else if (iscolinear(pb[1], pb[2], pb[3])) {
213 return true;
214 }
215 else {
216 return false;
217 }
218}
219
220bool vpHomography::degenerateConfiguration(const std::vector<double> &xb, const std::vector<double> &yb,
221 const std::vector<double> &xa, const std::vector<double> &ya)
222{
223 unsigned int n = static_cast<unsigned int>(xb.size());
224 if (n < 4) {
225 throw(vpException(vpException::fatalError, "There must be at least 4 matched points"));
226 }
227
228 std::vector<vpColVector> pa(n), pb(n);
229 for (unsigned i = 0; i < n; ++i) {
230 pa[i].resize(3);
231 pa[i][0] = xa[i];
232 pa[i][1] = ya[i];
233 pa[i][2] = 1;
234 pb[i].resize(3);
235 pb[i][0] = xb[i];
236 pb[i][1] = yb[i];
237 pb[i][2] = 1;
238 }
239
240 for (unsigned int i = 0; i < (n - 2); ++i) {
241 for (unsigned int j = i + 1; j < (n - 1); ++j) {
242 for (unsigned int k = j + 1; k < n; ++k) {
243 if (isColinear(pa[i], pa[j], pa[k])) {
244 return true;
245 }
246 if (isColinear(pb[i], pb[j], pb[k])) {
247 return true;
248 }
249 }
250 }
251 }
252 return false;
253}
254// Fit model to this random selection of data points.
255void vpHomography::computeTransformation(vpColVector &x, unsigned int *ind, vpColVector &M)
256{
257 const unsigned int val_4 = 4;
258 unsigned int n = x.getRows() / val_4;
259 std::vector<double> xa(4), xb(4);
260 std::vector<double> ya(4), yb(4);
261 unsigned int n2 = n * 2;
262 for (unsigned int i = 0; i < val_4; ++i) {
263 unsigned int ind2 = 2 * ind[i];
264 xb[i] = x[ind2];
265 yb[i] = x[ind2 + 1];
266
267 xa[i] = x[n2 + ind2];
268 ya[i] = x[n2 + ind2 + 1];
269 }
270
271 vpHomography aHb;
272 try {
273 vpHomography::HLM(xb, yb, xa, ya, true, aHb);
274 }
275 catch (...) {
276 aHb.eye();
277 }
278
279 M.resize(9);
280 for (unsigned int i = 0; i < 9; ++i) {
281 M[i] = aHb.data[i];
282 }
283 aHb /= aHb[2][2];
284}
285
286// Evaluate distances between points and model.
287double vpHomography::computeResidual(vpColVector &x, vpColVector &M, vpColVector &d)
288{
289 unsigned int n = x.getRows() / 4;
290 unsigned int n2 = n * 2;
291 vpColVector *pa;
292 vpColVector *pb;
293
294 pa = new vpColVector[n];
295 pb = new vpColVector[n];
296
297 for (unsigned int i = 0; i < n; ++i) {
298 unsigned int i2 = 2 * i;
299 pb[i].resize(3);
300 pb[i][0] = x[i2];
301 pb[i][1] = x[i2 + 1];
302 pb[i][2] = 1;
303
304 pa[i].resize(3);
305 pa[i][0] = x[n2 + i2];
306 pa[i][1] = x[n2 + i2 + 1];
307 pa[i][2] = 1;
308 }
309
310 vpMatrix aHb(3, 3);
311
312 for (unsigned int i = 0; i < 9; ++i) {
313 aHb.data[i] = M[i];
314 }
315
316 aHb /= aHb[2][2];
317
318 d.resize(n);
319
320 vpColVector Hpb;
321 for (unsigned int i = 0; i < n; ++i) {
322 Hpb = aHb * pb[i];
323 Hpb /= Hpb[2];
324 d[i] = sqrt((pa[i] - Hpb).sumSquare());
325 }
326
327 delete[] pa;
328 delete[] pb;
329
330 return 0;
331}
332#endif //#ifndef DOXYGEN_SHOULD_SKIP_THIS
333
334void vpHomography::initRansac(unsigned int n, double *xb, double *yb, double *xa, double *ya, vpColVector &x)
335{
336 x.resize(4 * n);
337 unsigned int n2 = n * 2;
338 for (unsigned int i = 0; i < n; ++i) {
339 unsigned int i2 = 2 * i;
340 x[i2] = xb[i];
341 x[i2 + 1] = yb[i];
342 x[n2 + i2] = xa[i];
343 x[n2 + i2 + 1] = ya[i];
344 }
345}
346
347bool vpHomography::ransac(const std::vector<double> &xb, const std::vector<double> &yb, const std::vector<double> &xa,
348 const std::vector<double> &ya, vpHomography &aHb, std::vector<bool> &inliers,
349 double &residual, unsigned int nbInliersConsensus, double threshold, bool normalization)
350{
351 unsigned int n = static_cast<unsigned int>(xb.size());
352 if ((yb.size() != n) || (xa.size() != n) || (ya.size() != n)) {
353 throw(vpException(vpException::dimensionError, "Bad dimension for robust homography estimation"));
354 }
355
356 // 4 point are required
357 if (n < 4) {
358 throw(vpException(vpException::fatalError, "There must be at least 4 matched points"));
359 }
360
361 vpUniRand random(static_cast<long>(time(nullptr)));
362
363 std::vector<unsigned int> best_consensus;
364 std::vector<unsigned int> cur_consensus;
365 std::vector<unsigned int> cur_outliers;
366 std::vector<unsigned int> cur_randoms;
367
368 std::vector<unsigned int> rand_ind;
369
370 unsigned int nbMinRandom = 4;
371 unsigned int ransacMaxTrials = 1000;
372 unsigned int maxDegenerateIter = 1000;
373
374 unsigned int nbTrials = 0;
375 unsigned int nbDegenerateIter = 0;
376 unsigned int nbInliers = 0;
377
378 bool foundSolution = false;
379
380 std::vector<double> xa_rand(nbMinRandom);
381 std::vector<double> ya_rand(nbMinRandom);
382 std::vector<double> xb_rand(nbMinRandom);
383 std::vector<double> yb_rand(nbMinRandom);
384
385 if (inliers.size() != n) {
386 inliers.resize(n);
387 }
388
389 while ((nbTrials < ransacMaxTrials) && (nbInliers < nbInliersConsensus)) {
390 cur_outliers.clear();
391 cur_randoms.clear();
392
393 bool degenerate = true;
394 while (degenerate == true) {
395 std::vector<bool> usedPt(n, false);
396
397 rand_ind.clear();
398 for (unsigned int i = 0; i < nbMinRandom; ++i) {
399 // Generate random indicies in the range 0..n
400 unsigned int r = static_cast<unsigned int>(ceil(random() * n)) - 1;
401 while (usedPt[r]) {
402 r = static_cast<unsigned int>(ceil(random() * n)) - 1;
403 }
404 usedPt[r] = true;
405 rand_ind.push_back(r);
406
407 xa_rand[i] = xa[r];
408 ya_rand[i] = ya[r];
409 xb_rand[i] = xb[r];
410 yb_rand[i] = yb[r];
411 }
412
413 try {
414 if (!vpHomography::degenerateConfiguration(xb_rand, yb_rand, xa_rand, ya_rand)) {
415 vpHomography::DLT(xb_rand, yb_rand, xa_rand, ya_rand, aHb, normalization);
416 degenerate = false;
417 }
418 }
419 catch (...) {
420 degenerate = true;
421 }
422
423 ++nbDegenerateIter;
424
425 if (nbDegenerateIter > maxDegenerateIter) {
426 vpERROR_TRACE("Unable to select a nondegenerate data set");
427 throw(vpException(vpException::fatalError, "Unable to select a nondegenerate data set"));
428 }
429 }
430
431 aHb /= aHb[2][2];
432
433 // Computing Residual
434 double r = 0;
435 vpColVector a(3), b(3), c(3);
436 for (unsigned int i = 0; i < nbMinRandom; ++i) {
437 a[0] = xa_rand[i];
438 a[1] = ya_rand[i];
439 a[2] = 1;
440 b[0] = xb_rand[i];
441 b[1] = yb_rand[i];
442 b[2] = 1;
443
444 c = aHb * b;
445 c /= c[2];
446 r += (a - c).sumSquare();
447 }
448
449 // Finding inliers & ouliers
450 r = sqrt(r / nbMinRandom);
451 if (r < threshold) {
452 unsigned int nbInliersCur = 0;
453 for (unsigned int i = 0; i < n; ++i) {
454 a[0] = xa[i];
455 a[1] = ya[i];
456 a[2] = 1;
457 b[0] = xb[i];
458 b[1] = yb[i];
459 b[2] = 1;
460
461 c = aHb * b;
462 c /= c[2];
463 double error = sqrt((a - c).sumSquare());
464 if (error <= threshold) {
465 ++nbInliersCur;
466 cur_consensus.push_back(i);
467 inliers[i] = true;
468 }
469 else {
470 cur_outliers.push_back(i);
471 inliers[i] = false;
472 }
473 }
474
475 if (nbInliersCur > nbInliers) {
476 foundSolution = true;
477 best_consensus = cur_consensus;
478 nbInliers = nbInliersCur;
479 }
480
481 cur_consensus.clear();
482 }
483
484 ++nbTrials;
485 if (nbTrials >= ransacMaxTrials) {
486 vpERROR_TRACE("Ransac reached the maximum number of trials");
487 foundSolution = true;
488 }
489 }
490
491 if (foundSolution) {
492 if (nbInliers >= nbInliersConsensus) {
493 const unsigned int nbConsensus = static_cast<unsigned int>(best_consensus.size());
494 std::vector<double> xa_best(nbConsensus);
495 std::vector<double> ya_best(nbConsensus);
496 std::vector<double> xb_best(nbConsensus);
497 std::vector<double> yb_best(nbConsensus);
498
499 for (unsigned i = 0; i < nbConsensus; ++i) {
500 xa_best[i] = xa[best_consensus[i]];
501 ya_best[i] = ya[best_consensus[i]];
502 xb_best[i] = xb[best_consensus[i]];
503 yb_best[i] = yb[best_consensus[i]];
504 }
505
506 vpHomography::DLT(xb_best, yb_best, xa_best, ya_best, aHb, normalization);
507 aHb /= aHb[2][2];
508
509 residual = 0;
510 vpColVector a(3), b(3), c(3);
511
512 for (unsigned int i = 0; i < nbConsensus; ++i) {
513 a[0] = xa_best[i];
514 a[1] = ya_best[i];
515 a[2] = 1;
516 b[0] = xb_best[i];
517 b[1] = yb_best[i];
518 b[2] = 1;
519
520 c = aHb * b;
521 c /= c[2];
522 residual += (a - c).sumSquare();
523 }
524
525 residual = sqrt(residual / nbConsensus);
526 return true;
527 }
528 else {
529 return false;
530 }
531 }
532 else {
533 return false;
534 }
535}
536
537END_VISP_NAMESPACE
Type * data
Address of the first element of the data array.
Definition vpArray2D.h:149
Implementation of column vector and the associated operations.
static vpColVector cross(const vpColVector &a, const vpColVector &b)
void resize(unsigned int i, bool flagNullify=true)
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ dimensionError
Bad dimension.
Definition vpException.h:71
@ fatalError
Fatal error.
Definition vpException.h:72
static void DLT(const std::vector< double > &xb, const std::vector< double > &yb, const std::vector< double > &xa, const std::vector< double > &ya, vpHomography &aHb, bool normalization=true)
static void HLM(const std::vector< double > &xb, const std::vector< double > &yb, const std::vector< double > &xa, const std::vector< double > &ya, bool isplanar, vpHomography &aHb)
static bool ransac(const std::vector< double > &xb, const std::vector< double > &yb, const std::vector< double > &xa, const std::vector< double > &ya, vpHomography &aHb, std::vector< bool > &inliers, double &residual, unsigned int nbInliersConsensus, double threshold, bool normalization=true)
Class for generating random numbers with uniform probability density.
Definition vpUniRand.h:127
#define vpERROR_TRACE
Definition vpDebug.h:423