Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
photometricMappingVisualServoing.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
36
37#include <visp3/core/vpImage.h>
38#include <visp3/core/vpImageTools.h>
39#include <visp3/core/vpCameraParameters.h>
40#include <visp3/core/vpTime.h>
41#include <visp3/core/vpHomogeneousMatrix.h>
42#include <visp3/core/vpMath.h>
43#include <visp3/core/vpUniRand.h>
44#include <visp3/core/vpIoTools.h>
45#include <visp3/robot/vpSimulatorCamera.h>
46#include <visp3/robot/vpImageSimulator.h>
47#include <visp3/io/vpImageIo.h>
48#include <visp3/io/vpParseArgv.h>
49#include <visp3/gui/vpDisplayFactory.h>
50#include <visp3/visual_features/vpFeatureLuminanceMapping.h>
51
52#include <stdlib.h>
53
54#ifdef ENABLE_VISP_NAMESPACE
55using namespace VISP_NAMESPACE_NAME;
56#endif
57
58
59// List of allowed command line options
60#define GETOPTARGS "cdi:n:p:m:k:hl:"
61
62void usage(const char *name, const char *badparam, const std::string &ipath, int niter, const std::string &method, unsigned numDbImages, const unsigned numComponents, const double lambda);
63bool getOptions(int argc, const char **argv, std::string &ipath, bool &click_allowed, bool &display, int &niter, std::string &method, unsigned &numDbImages, unsigned &numComponents, double &lambda);
64
75void usage(const char *name, const char *badparam, const std::string &ipath, int niter, const std::string &method, unsigned numDbImages, const unsigned numComponents, const double lambda)
76{
77 fprintf(stdout, "\n\
78Visual servoing with compressed photometric features.\n\
79Use either PCA or DCT representations\n\
80\n\
81\n\
82SYNOPSIS\n\
83 %s [-i <input image path>] [-m pca|dct] [-p <v>] [-c] [-d] [-n <number of iterations>] [-h]\n",
84 name);
85
86 fprintf(stdout, "\n\
87OPTIONS: Default\n\
88 -i <input image path> %s\n\
89 Set image input path.\n\
90 From this path read \"doisneau/doisneau.jpg\"\n\
91 images. \n\
92 Setting the VISP_INPUT_IMAGE_PATH environment\n\
93 variable produces the same behaviour than using\n\
94 this option.\n\
95 \n\
96 -m\n\
97 Method to use: either 'PCA' or 'DCT'\n\
98 PCA first requires learning a projection from a base of images. see the -p option.\n\
99 Default: %s\n\
100 -k\n\
101 Number of visual servoing features (i.e., PCA or DCT components)\n\
102 Default: %d\n\
103\n\
104 -p\n\
105 Number of images to use to compute PCA. If method is DCT, this option is ignored.\n\
106 Default: %d\n\
107\n\
108 -c\n\
109 Disable the mouse click. Useful to automate the \n\
110 execution of this program without human intervention.\n\
111\n\
112 -d \n\
113 Turn off the display.\n\
114\n\
115 -n %%d %d\n\
116 Number of visual servoing iterations.\n\
117\n\
118 -l %%f %f\n\
119 Number of visual servoing iterations.\n\
120\n\
121 -h\n\
122 Print the help.\n",
123 ipath.c_str(), method.c_str(), numComponents, numDbImages, niter, lambda);
124
125 if (badparam)
126 fprintf(stdout, "\nERROR: Bad parameter [%s]\n", badparam);
127}
142bool getOptions(int argc, const char **argv, std::string &ipath, bool &click_allowed, bool &display,
143 int &niter, std::string &method, unsigned &numDbImages, unsigned &numComponents, double &lambda)
144{
145 const char *optarg_;
146 int c;
147 while ((c = vpParseArgv::parse(argc, argv, GETOPTARGS, &optarg_)) > 1) {
148
149 switch (c) {
150 case 'c':
151 click_allowed = false;
152 break;
153 case 'd':
154 display = false;
155 break;
156 case 'i':
157 ipath = optarg_;
158 break;
159 case 'm':
160 method = std::string(optarg_);
161 break;
162 case 'p':
163 numDbImages = atoi(optarg_);
164 break;
165 case 'k':
166 numComponents = atoi(optarg_);
167 break;
168 case 'n':
169 niter = atoi(optarg_);
170 break;
171 case 'l':
172 lambda = atof(optarg_);
173 break;
174 case 'h':
175 usage(argv[0], nullptr, ipath, niter, method, numDbImages, numComponents, lambda);
176 return false;
177
178 default:
179 usage(argv[0], optarg_, ipath, niter, method, numDbImages, numComponents, lambda);
180 return false;
181 }
182 }
183
184 if ((c == 1) || (c == -1)) {
185 // standalone param or error
186 usage(argv[0], nullptr, ipath, niter, method, numDbImages, numComponents, lambda);
187 std::cerr << "ERROR: " << std::endl;
188 std::cerr << " Bad argument " << optarg_ << std::endl << std::endl;
189 return false;
190 }
191
192 return true;
193}
194
195int main(int argc, const char **argv)
196{
197#if (defined(VISP_HAVE_LAPACK) || defined(VISP_HAVE_EIGEN3) || defined(VISP_HAVE_OPENCV)) && (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
198#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
199 vpDisplay *d = nullptr;
200 vpDisplay *d1 = nullptr;
201 vpDisplay *d2 = nullptr;
202#endif
203 try {
204 std::string env_ipath;
205 std::string opt_ipath;
206 std::string ipath;
207 std::string filename;
208 bool opt_click_allowed = true;
209 bool opt_display = true;
210 int opt_niter = 400;
211 std::string opt_method = "dct";
212 unsigned opt_numDbImages = 2000;
213 unsigned opt_numComponents = 32;
214 double opt_lambda = 5.0;
215
216 double mu = 0.01; // mu = 0 : Gauss Newton ; mu != 0 : LM
217 double lambdaGN = opt_lambda;
218
219
220
221 const double Z = 0.8;
222 const unsigned ih = 240;
223 const unsigned iw = 320;
224 const double scenew = 0.6;
225 const double sceneh = 0.42;
226
227 // Get the visp-images-data package path or VISP_INPUT_IMAGE_PATH
228 // environment variable value
230
231 // Set the default input path
232 if (!env_ipath.empty())
233 ipath = env_ipath;
234
235 // Read the command line options
236 if (getOptions(argc, argv, opt_ipath, opt_click_allowed, opt_display, opt_niter, opt_method,
237 opt_numDbImages, opt_numComponents, opt_lambda) == false) {
238 return EXIT_FAILURE;
239 }
240
241 // Get the option values
242 if (!opt_ipath.empty())
243 ipath = opt_ipath;
244
245 // Compare ipath and env_ipath. If they differ, we take into account
246 // the input path coming from the command line option
247 if (!opt_ipath.empty() && !env_ipath.empty()) {
248 if (ipath != env_ipath) {
249 std::cout << std::endl << "WARNING: " << std::endl;
250 std::cout << " Since -i <visp image path=" << ipath << "> "
251 << " is different from VISP_IMAGE_PATH=" << env_ipath << std::endl
252 << " we skip the environment variable." << std::endl;
253 }
254 }
255
256 // Test if an input path is set
257 if (opt_ipath.empty() && env_ipath.empty()) {
258 usage(argv[0], nullptr, ipath, opt_niter, opt_method, opt_numDbImages, opt_numComponents, opt_lambda);
259 std::cerr << std::endl << "ERROR:" << std::endl;
260 std::cerr << " Use -i <visp image path> option or set VISP_INPUT_IMAGE_PATH " << std::endl
261 << " environment variable to specify the location of the " << std::endl
262 << " image path where test images are located." << std::endl
263 << std::endl;
264 return EXIT_FAILURE;
265 }
266
267 vpImage<unsigned char> Itexture;
268 filename = vpIoTools::createFilePath(ipath, "Klimt/Klimt.pgm");
269 vpImageIo::read(Itexture, filename);
270
271 vpColVector X[4];
272 for (int i = 0; i < 4; i++)
273 X[i].resize(3);
274 // Top left corner
275 X[0][0] = -(scenew / 2.0);
276 X[0][1] = -(sceneh / 2.0);
277 X[0][2] = 0;
278
279 // Top right corner
280 X[1][0] = (scenew / 2.0);
281 X[1][1] = -(sceneh / 2.0);
282 X[1][2] = 0;
283
284 // Bottom right corner
285 X[2][0] = (scenew / 2.0);
286 X[2][1] = (sceneh / 2.0);
287 X[2][2] = 0;
288
289 // Bottom left corner
290 X[3][0] = -(scenew / 2.0);
291 X[3][1] = (sceneh / 2.0);
292 X[3][2] = 0;
293
295
298 sim.init(Itexture, X);
299 // ----------------------------------------------------------
300 // Create the framegraber (here a simulated image)
301 vpImage<unsigned char> I(ih, iw, 0);
303
305 // camera desired position
307 cdMo[2][3] = Z;
308
309
310 vpCameraParameters cam(870, 870, 160, 120);
311 std::shared_ptr<vpLuminanceMapping> sMapping = nullptr;
312 std::shared_ptr<vpLuminanceMapping> sdMapping = nullptr;
313
314 // Setup mapping
315 if (opt_method == "pca") {
316 vpUniRand random(17);
317 std::cout << "Building image database for PCA computation with " << opt_numDbImages << " images" << std::endl;
318#if defined(VISP_HAVE_DISPLAY)
319#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
320 std::shared_ptr<vpDisplay> d = vpDisplayFactory::createDisplay();
321#else
323#endif
324 if (opt_display) {
325 d->init(I, 0, 0, "Image database (subsample)");
326 }
327#endif
328 std::vector<vpImage<unsigned char>> images(opt_numDbImages);
329 for (unsigned i = 0; i < opt_numDbImages; ++i) {
330 vpColVector to(3, 0.0), positionNoise(3, 0.0);
331 const double noiseDiv = 16.0;
332 positionNoise[0] = random.uniform(-scenew / noiseDiv, scenew / noiseDiv);
333 positionNoise[1] = random.uniform(-sceneh / noiseDiv, sceneh / noiseDiv);
334 positionNoise[2] = random.uniform(0.0, Z / noiseDiv);
335 const double noiseDivTo = 16.0;
336 to[0] = random.uniform(-scenew / noiseDivTo, scenew / noiseDivTo);
337 to[1] = random.uniform(-sceneh / noiseDivTo, sceneh / noiseDivTo);
338 const vpColVector from = vpColVector(cdMo.getTranslationVector()) + positionNoise;
339 vpRotationMatrix Rrot(0.0, 0.0, vpMath::rad(random.uniform(-10, 10)));
340 vpHomogeneousMatrix dbMo = vpMath::lookAt(from, to, Rrot * vpColVector({ 0.0, 1.0, 0.0 }));
341 sim.setCameraPosition(dbMo);
342 sim.getImage(I, cam);
343 images[i] = I;
344 if (i % 20 == 0 && opt_display) {
347 }
348 }
349 std::cout << "Computing PCA, this may take some time!" << std::endl;
350 // create two distinct objects: if the projection is stateful, using a single mapping could lead to undesired behaviour
352 std::cout << "Explained variance: " << pca.getExplainedVariance().sum() * 100.0 << "%" << std::endl;
353 sMapping = std::shared_ptr<vpLuminanceMapping>(new vpLuminancePCA(pca));
354 sdMapping = std::shared_ptr<vpLuminanceMapping>(new vpLuminancePCA(pca));
355
356#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
357 if (d != nullptr) {
358 delete d;
359 }
360#endif
361 }
362 else if (opt_method == "dct") {
363 sMapping = std::shared_ptr<vpLuminanceMapping>(new vpLuminanceDCT(opt_numComponents));
364 sdMapping = std::shared_ptr<vpLuminanceMapping>(new vpLuminanceDCT(opt_numComponents));
365 }
366 else {
367 throw vpException(vpException::badValue, "Method must be pca or dct!");
368 }
369
370 // set the robot at the desired position
371 sim.setCameraPosition(cdMo);
372 sim.getImage(I, cam); // and aquire the image Id
373 Id = I;
374
375#if defined(VISP_HAVE_DISPLAY)
376#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
377 std::shared_ptr<vpDisplay> d = vpDisplayFactory::createDisplay();
378#else
380#endif
381 if (opt_display) {
382 // display the image
383 d->init(I, 20, 10, "Current image");
386 if (opt_click_allowed) {
387 std::cout << "Click in the image to continue..." << std::endl;
389 }
390 }
391#endif
392
393 // ----------------------------------------------------------
394 // position the robot at the initial position
395 // ----------------------------------------------------------
396
397 // camera desired position
399 cMo.buildFrom(0.0, 0, Z + 0.2, vpMath::rad(15), vpMath::rad(-5), vpMath::rad(5));
400 vpHomogeneousMatrix wMo; // Set to identity
401 vpHomogeneousMatrix wMc; // Camera position in the world frame
402
403 // set the robot at the desired position
404 sim.setCameraPosition(cMo);
405 I = 0u;
406 sim.getImage(I, cam); // and aquire the image Id
407
408#if defined(VISP_HAVE_DISPLAY)
409 if (opt_display) {
412 }
413 if (opt_display && opt_click_allowed) {
414 std::cout << "Click in the image to continue..." << std::endl;
416 }
417#endif
418
420 Idiff = I;
421
422 vpImageTools::imageDifference(I, Id, Idiff);
423
424 // Display image difference
425#if defined(VISP_HAVE_DISPLAY)
426#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
427 std::shared_ptr<vpDisplay> d1 = vpDisplayFactory::createDisplay();
428 std::shared_ptr<vpDisplay> d2 = vpDisplayFactory::createDisplay();
429#else
432#endif
433 if (opt_display) {
434 d1->init(Idiff, 40 + static_cast<int>(I.getWidth()), 10, "photometric visual servoing : s-s* ");
435 d2->init(Irec, 40 + static_cast<int>(I.getWidth()) * 2, 10, "Reconstructed image");
436
437 vpDisplay::display(Idiff);
438 vpDisplay::flush(Idiff);
439 vpDisplay::display(Irec);
440 vpDisplay::flush(Irec);
441 }
442#endif
443 // create the robot (here a simulated free flying camera)
444 vpSimulatorCamera robot;
445 robot.setSamplingTime(0.04);
446 wMc = wMo * cMo.inverse();
447 robot.setPosition(wMc);
448
449 // ------------------------------------------------------
450 // Visual feature, interaction matrix, error
451 // s, Ls, Lsd, Lt, Lp, etc
452 // ------------------------------------------------------
453
454 // current visual feature built from the image
455 vpFeatureLuminance luminanceI;
456 luminanceI.init(I.getHeight(), I.getWidth(), Z);
457 luminanceI.setCameraParameters(cam);
458 vpFeatureLuminanceMapping sI(luminanceI, sMapping);
459 sI.buildFrom(I);
460 sI.getMapping()->inverse(sI.get_s(), Irec);
461
462 // desired visual feature built from the image
463 vpFeatureLuminance luminanceId;
464 luminanceId.init(I.getHeight(), I.getWidth(), Z);
465 luminanceId.setCameraParameters(cam);
466 vpFeatureLuminanceMapping sId(luminanceId, sdMapping);
467 sId.buildFrom(Id);
468
469 // set a velocity control mode
470 robot.setRobotState(vpRobot::STATE_VELOCITY_CONTROL);
471
472 int iter = 1;
473 int iterGN = opt_niter / 8;
474 double normError = 0;
475 vpColVector v; // camera velocity sent to the robot
476 vpColVector error(sI.dimension_s(), 0);
477
478 unsigned int n = 6;
479 vpMatrix L;
480 vpMatrix Hs(n, n);
481 vpMatrix H;
482 vpMatrix diagHs(n, n);
483
484 vpChrono chrono;
485 chrono.start();
486 std::cout << "Starting VS loop" << std::endl;
487 do {
488 std::cout << "--------------------------------------------" << iter++ << std::endl;
489
490 // Acquire the new image
491 sim.setCameraPosition(cMo);
492 sim.getImage(I, cam);
493 vpImageTools::imageDifference(I, Id, Idiff);
494
495 // Compute current visual features
496 sI.buildFrom(I);
497 sI.getMapping()->inverse(sI.get_s(), Irec);
498
499 if (iter > iterGN) {
500 mu = 0.0001;
501 opt_lambda = lambdaGN;
502 }
503 sI.interaction(L);
504 sI.error(sId, error);
505
506 Hs = L.AtA();
507 for (unsigned int i = 0; i < n; i++) {
508 diagHs[i][i] = Hs[i][i];
509 }
510 H = ((mu * diagHs) + Hs).inverseByLU();
511 // Compute the control law
512 v = -opt_lambda * H * L.t() * error;
513 normError = error.sumSquare();
514
515 std::cout << " |e| = " << normError << std::endl;
516 std::cout << " |v| = " << sqrt(v.sumSquare()) << std::endl;
517
518#if defined(VISP_HAVE_DISPLAY)
519 if (opt_display) {
522 vpDisplay::display(Irec);
523 vpDisplay::flush(Irec);
524 vpDisplay::display(Idiff);
525 vpDisplay::flush(Idiff);
526 }
527#endif
528
529 // send the robot velocity
530 robot.setVelocity(vpRobot::CAMERA_FRAME, v);
531 wMc = robot.getPosition();
532 cMo = wMc.inverse() * wMo;
533 } while (normError > 200 && iter < opt_niter);
534
535 chrono.stop();
536 std::cout << "Time to convergence: " << chrono.getDurationMs() << " ms" << std::endl;
537
538 v = 0;
539 robot.setVelocity(vpRobot::CAMERA_FRAME, v);
540
541#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
542 if (d1 != nullptr) {
543 delete d1;
544 }
545
546 if (d2 != nullptr) {
547 delete d2;
548 }
549#endif
550
551 if (normError > 200) {
552 return EXIT_FAILURE;
553 }
554 return EXIT_SUCCESS;
555 }
556 catch (const vpException &e) {
557 std::cout << "Catch an exception: " << e << std::endl;
558#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
559 if (d != nullptr) {
560 delete d;
561 }
562 if (d1 != nullptr) {
563 delete d1;
564 }
565
566 if (d2 != nullptr) {
567 delete d2;
568 }
569#endif
570 return EXIT_FAILURE;
571 }
572#else
573 (void)argc;
574 (void)argv;
575 std::cout << "Cannot run this example: install Lapack, Eigen3 or OpenCV" << std::endl;
576 return EXIT_SUCCESS;
577#endif
578}
Generic class defining intrinsic camera parameters.
void start(bool reset=true)
Definition vpTime.cpp:411
void stop()
Definition vpTime.cpp:426
double getDurationMs()
Definition vpTime.cpp:400
Implementation of column vector and the associated operations.
double sum() const
static const vpColor black
Definition vpColor.h:192
Class that defines generic functionalities for display.
Definition vpDisplay.h:171
static bool getClick(const vpImage< unsigned char > &I, bool blocking=true)
static void display(const vpImage< unsigned char > &I)
static void flush(const vpImage< unsigned char > &I)
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ badValue
Used to indicate that a value is not in the allowed range.
Definition vpException.h:73
Class to combine luminance features (photometric servoing).
Class that defines the image luminance visual feature.
void init(unsigned int _nbr, unsigned int _nbc, double _Z)
static const int DEFAULT_BORDER
void setCameraParameters(const vpCameraParameters &_cam)
Implementation of an homogeneous matrix and operations on such kind of matrices.
vpHomogeneousMatrix inverse() const
vpTranslationVector getTranslationVector() const
static void read(vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
Class which enables to project an image in the 3D space and get the view of a virtual camera.
void getImage(vpImage< unsigned char > &I, const vpCameraParameters &cam)
void init(const vpImage< unsigned char > &I, vpColVector *X)
void setCleanPreviousImage(const bool &clean, const vpColor &color=vpColor::white)
void setInterpolationType(const vpInterpolationType interplt)
void setCameraPosition(const vpHomogeneousMatrix &cMt)
static void imageDifference(const vpImage< unsigned char > &I1, const vpImage< unsigned char > &I2, vpImage< unsigned char > &Idiff)
Definition of the vpImage class member functions.
Definition vpImage.h:131
static std::string getViSPImagesDataPath()
static std::string createFilePath(const std::string &parent, const std::string &child)
Implementation of marchand20a.
Implementation of marchand19a.
static vpLuminancePCA learn(const std::vector< std::string > &imageFiles, const unsigned int projectionSize, const unsigned int imageBorder=0)
Compute a new Principal Component Analysis on set of images, stored on disk.
vpColVector getExplainedVariance() const
Get the values of explained variance by each of the eigen vectors.
static double rad(double deg)
Definition vpMath.h:129
static vpHomogeneousMatrix lookAt(const vpColVector &from, const vpColVector &to, vpColVector tmp)
Definition vpMath.cpp:710
Implementation of a matrix and operations on matrices.
Definition vpMatrix.h:175
vpMatrix t() const
static bool parse(int *argcPtr, const char **argv, vpArgvInfo *argTable, int flags)
virtual void setSamplingTime(const double &delta_t)
@ CAMERA_FRAME
Definition vpRobot.h:81
@ STATE_VELOCITY_CONTROL
Initialize the velocity controller.
Definition vpRobot.h:64
Implementation of a rotation matrix and operations on such kind of matrices.
Class that defines the simplest robot: a free flying camera.
Class for generating random numbers with uniform probability density.
Definition vpUniRand.h:127
std::shared_ptr< vpDisplay > createDisplay()
Return a smart pointer vpDisplay specialization if a GUI library is available or nullptr otherwise.
vpDisplay * allocateDisplay()
Return a newly allocated vpDisplay specialization if a GUI library is available or nullptr otherwise.