Visual Servoing Platform  version 3.6.1 under development (2024-04-25)
mbot-apriltag-ibvs.cpp
1 #include <visp3/core/vpMomentAreaNormalized.h>
3 #include <visp3/core/vpMomentBasic.h>
4 #include <visp3/core/vpMomentCentered.h>
5 #include <visp3/core/vpMomentDatabase.h>
6 #include <visp3/core/vpMomentGravityCenter.h>
7 #include <visp3/core/vpMomentGravityCenterNormalized.h>
8 #include <visp3/core/vpMomentObject.h>
9 #include <visp3/core/vpPixelMeterConversion.h>
10 #include <visp3/core/vpPoint.h>
11 #include <visp3/core/vpSerial.h>
12 #include <visp3/core/vpXmlParserCamera.h>
13 #include <visp3/detection/vpDetectorAprilTag.h>
14 #include <visp3/gui/vpDisplayX.h>
15 #include <visp3/io/vpImageIo.h>
16 #include <visp3/robot/vpUnicycle.h>
17 #include <visp3/sensor/vpV4l2Grabber.h>
18 #include <visp3/visual_features/vpFeatureMomentAreaNormalized.h>
19 #include <visp3/visual_features/vpFeatureMomentGravityCenterNormalized.h>
20 #include <visp3/vs/vpServo.h>
21 
22 int main(int argc, const char **argv)
23 {
24 #if defined(VISP_HAVE_APRILTAG) && defined(VISP_HAVE_V4L2)
25  int device = 0;
27  double tagSize = 0.065;
28  float quad_decimate = 4.0;
29  int nThreads = 2;
30  std::string intrinsic_file = "";
31  std::string camera_name = "";
32  bool display_tag = false;
33  bool display_on = false;
34  bool serial_off = false;
35  bool save_image = false; // Only possible if display_on = true
36 
37  for (int i = 1; i < argc; i++) {
38  if (std::string(argv[i]) == "--tag_size" && i + 1 < argc) {
39  tagSize = std::atof(argv[i + 1]);
40  }
41  else if (std::string(argv[i]) == "--input" && i + 1 < argc) {
42  device = std::atoi(argv[i + 1]);
43  }
44  else if (std::string(argv[i]) == "--quad_decimate" && i + 1 < argc) {
45  quad_decimate = (float)atof(argv[i + 1]);
46  }
47  else if (std::string(argv[i]) == "--nthreads" && i + 1 < argc) {
48  nThreads = std::atoi(argv[i + 1]);
49  }
50  else if (std::string(argv[i]) == "--intrinsic" && i + 1 < argc) {
51  intrinsic_file = std::string(argv[i + 1]);
52  }
53  else if (std::string(argv[i]) == "--camera_name" && i + 1 < argc) {
54  camera_name = std::string(argv[i + 1]);
55  }
56  else if (std::string(argv[i]) == "--display_tag") {
57  display_tag = true;
58 #if defined(VISP_HAVE_X11)
59  }
60  else if (std::string(argv[i]) == "--display_on") {
61  display_on = true;
62  }
63  else if (std::string(argv[i]) == "--save_image") {
64  save_image = true;
65 #endif
66  }
67  else if (std::string(argv[i]) == "--serial_off") {
68  serial_off = true;
69  }
70  else if (std::string(argv[i]) == "--tag_family" && i + 1 < argc) {
71  tagFamily = (vpDetectorAprilTag::vpAprilTagFamily)std::atoi(argv[i + 1]);
72  }
73  else if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") {
74  std::cout << "Usage: " << argv[0]
75  << " [--input <camera input>] [--tag_size <tag_size in m>]"
76  " [--quad_decimate <quad_decimate>] [--nthreads <nb>]"
77  " [--intrinsic <intrinsic file>] [--camera_name <camera name>]"
78  " [--tag_family <family> (0: TAG_36h11, 1: TAG_36h10, 2: "
79  "TAG_36ARTOOLKIT,"
80  " 3: TAG_25h9, 4: TAG_25h7, 5: TAG_16h5)]"
81  " [--display_tag]";
82 #if defined(VISP_HAVE_X11)
83  std::cout << " [--display_on] [--save_image]";
84 #endif
85  std::cout << " [--serial_off] [--help]" << std::endl;
86  return EXIT_SUCCESS;
87  }
88  }
89 
90  // Me Auriga led ring
91  // if serial com ok: led 1 green
92  // if exception: led 1 red
93  // if tag detected: led 2 green, else led 2 red
94  // if motor left: led 3 blue
95  // if motor right: led 4 blue
96 
97  vpSerial *serial = nullptr;
98  if (!serial_off) {
99  serial = new vpSerial("/dev/ttyAMA0", 115200);
100 
101  serial->write("LED_RING=0,0,0,0\n"); // Switch off all led
102  serial->write("LED_RING=1,0,10,0\n"); // Switch on led 1 to green: serial ok
103  }
104 
105  try {
107 
108  vpV4l2Grabber g;
109  std::ostringstream device_name;
110  device_name << "/dev/video" << device;
111  g.setDevice(device_name.str());
112  g.setScale(1);
113  g.acquire(I);
114 
115  vpDisplay *d = nullptr;
116  vpImage<vpRGBa> O;
117 #ifdef VISP_HAVE_X11
118  if (display_on) {
119  d = new vpDisplayX(I);
120  }
121 #endif
122 
123  vpCameraParameters cam;
124  cam.initPersProjWithoutDistortion(615.1674805, 615.1675415, I.getWidth() / 2., I.getHeight() / 2.);
125 
126 #if defined(VISP_HAVE_PUGIXML)
127  vpXmlParserCamera parser;
128  if (!intrinsic_file.empty() && !camera_name.empty()) {
129  parser.parse(cam, intrinsic_file, camera_name, vpCameraParameters::perspectiveProjWithoutDistortion);
130  }
131 #endif
132 
133  std::cout << "cam:\n" << cam << std::endl;
134  std::cout << "tagFamily: " << tagFamily << std::endl;
135  std::cout << "tagSize: " << tagSize << std::endl;
136 
137  vpDetectorAprilTag detector(tagFamily);
138 
139  detector.setAprilTagQuadDecimate(quad_decimate);
140  detector.setAprilTagNbThreads(nThreads);
141  detector.setDisplayTag(display_tag);
142 
143  vpServo task;
144  vpAdaptiveGain lambda;
145  if (display_on)
146  lambda.initStandard(2.5, 0.4, 30); // lambda(0)=2.5, lambda(oo)=0.4 and lambda'(0)=30
147  else
148  lambda.initStandard(4, 0.4, 30); // lambda(0)=4, lambda(oo)=0.4 and lambda'(0)=30
149 
150  vpUnicycle robot;
153  task.setLambda(lambda);
154  vpRotationMatrix cRe;
155  cRe[0][0] = 0;
156  cRe[0][1] = -1;
157  cRe[0][2] = 0;
158  cRe[1][0] = 0;
159  cRe[1][1] = 0;
160  cRe[1][2] = -1;
161  cRe[2][0] = 1;
162  cRe[2][1] = 0;
163  cRe[2][2] = 0;
164 
166  vpVelocityTwistMatrix cVe(cMe);
167  task.set_cVe(cVe);
168 
169  vpMatrix eJe(6, 2, 0);
170  eJe[0][0] = eJe[5][1] = 1.0;
171 
172  std::cout << "eJe: \n" << eJe << std::endl;
173 
174  // Desired distance to the target
175  double Z_d = 0.4;
176 
177  // Define the desired polygon corresponding the the AprilTag CLOCKWISE
178  double X[4] = { tagSize / 2., tagSize / 2., -tagSize / 2., -tagSize / 2. };
179  double Y[4] = { tagSize / 2., -tagSize / 2., -tagSize / 2., tagSize / 2. };
180  std::vector<vpPoint> vec_P, vec_P_d;
181 
182  for (int i = 0; i < 4; i++) {
183  vpPoint P_d(X[i], Y[i], 0);
184  vpHomogeneousMatrix cdMo(0, 0, Z_d, 0, 0, 0);
185  P_d.track(cdMo); //
186  vec_P_d.push_back(P_d);
187  }
188 
189  vpMomentObject m_obj(3), m_obj_d(3);
190  vpMomentDatabase mdb, mdb_d;
191  vpMomentBasic mb_d; // Here only to get the desired area m00
192  vpMomentGravityCenter mg, mg_d;
193  vpMomentCentered mc, mc_d;
194  vpMomentAreaNormalized man(0, Z_d),
195  man_d(0, Z_d); // Declare normalized area. Desired area parameter will be updated below with m00
196  vpMomentGravityCenterNormalized mgn, mgn_d; // Declare normalized gravity center
197 
198  // Desired moments
199  m_obj_d.setType(vpMomentObject::DENSE_POLYGON); // Consider the AprilTag as a polygon
200  m_obj_d.fromVector(vec_P_d); // Initialize the object with the points coordinates
201 
202  mb_d.linkTo(mdb_d); // Add basic moments to database
203  mg_d.linkTo(mdb_d); // Add gravity center to database
204  mc_d.linkTo(mdb_d); // Add centered moments to database
205  man_d.linkTo(mdb_d); // Add area normalized to database
206  mgn_d.linkTo(mdb_d); // Add gravity center normalized to database
207  mdb_d.updateAll(m_obj_d); // All of the moments must be updated, not just an_d
208  mg_d.compute(); // Compute gravity center moment
209  mc_d.compute(); // Compute centered moments AFTER gravity center
210 
211  double area = 0;
212  if (m_obj_d.getType() == vpMomentObject::DISCRETE)
213  area = mb_d.get(2, 0) + mb_d.get(0, 2);
214  else
215  area = mb_d.get(0, 0);
216  // Update an moment with the desired area
217  man_d.setDesiredArea(area);
218 
219  man_d.compute(); // Compute area normalized moment AFTER centered moments
220  mgn_d.compute(); // Compute gravity center normalized moment AFTER area normalized moment
221 
222  // Desired plane
223  double A = 0.0;
224  double B = 0.0;
225  double C = 1.0 / Z_d;
226 
227  // Construct area normalized features
228  vpFeatureMomentGravityCenterNormalized s_mgn(mdb, A, B, C), s_mgn_d(mdb_d, A, B, C);
229  vpFeatureMomentAreaNormalized s_man(mdb, A, B, C), s_man_d(mdb_d, A, B, C);
230 
231  // Add the features
233  task.addFeature(s_man, s_man_d);
234 
235  // Update desired gravity center normalized feature
236  s_mgn_d.update(A, B, C);
237  s_mgn_d.compute_interaction();
238  // Update desired area normalized feature
239  s_man_d.update(A, B, C);
240  s_man_d.compute_interaction();
241 
242  std::vector<double> time_vec;
243  for (;;) {
244  g.acquire(I);
245 
247 
248  double t = vpTime::measureTimeMs();
249  std::vector<vpHomogeneousMatrix> cMo_vec;
250  detector.detect(I, tagSize, cam, cMo_vec);
251  t = vpTime::measureTimeMs() - t;
252  time_vec.push_back(t);
253 
254  {
255  std::stringstream ss;
256  ss << "Detection time: " << t << " ms";
257  vpDisplay::displayText(I, 40, 20, ss.str(), vpColor::red);
258  }
259 
260  if (detector.getNbObjects() == 1) {
261  if (!serial_off) {
262  serial->write("LED_RING=2,0,10,0\n"); // Switch on led 2 to green: tag detected
263  }
264 
265  // Update current points used to compute the moments
266  std::vector<vpImagePoint> vec_ip = detector.getPolygon(0);
267  vec_P.clear();
268  for (size_t i = 0; i < vec_ip.size(); i++) { // size = 4
269  double x = 0, y = 0;
270  vpPixelMeterConversion::convertPoint(cam, vec_ip[i], x, y);
271  vpPoint P;
272  P.set_x(x);
273  P.set_y(y);
274  vec_P.push_back(P);
275  }
276 
277  // Display visual features
278  vpDisplay::displayPolygon(I, vec_ip, vpColor::green, 3); // Current polygon used to compure an moment
279  vpDisplay::displayCross(I, detector.getCog(0), 15, vpColor::green,
280  3); // Current polygon used to compure an moment
281  vpDisplay::displayLine(I, 0, cam.get_u0(), I.getHeight() - 1, cam.get_u0(), vpColor::red,
282  3); // Vertical line as desired x position
283 
284  // Current moments
285  m_obj.setType(vpMomentObject::DENSE_POLYGON); // Consider the AprilTag as a polygon
286  m_obj.fromVector(vec_P); // Initialize the object with the points coordinates
287 
288  mg.linkTo(mdb); // Add gravity center to database
289  mc.linkTo(mdb); // Add centered moments to database
290  man.linkTo(mdb); // Add area normalized to database
291  mgn.linkTo(mdb); // Add gravity center normalized to database
292  mdb.updateAll(m_obj); // All of the moments must be updated, not just an_d
293  mg.compute(); // Compute gravity center moment
294  mc.compute(); // Compute centered moments AFTER gravity center
295  man.setDesiredArea(
296  area); // Since desired area was init at 0, because unknow at construction, need to be updated here
297  man.compute(); // Compute area normalized moment AFTER centered moment
298  mgn.compute(); // Compute gravity center normalized moment AFTER area normalized moment
299 
300  s_mgn.update(A, B, C);
301  s_mgn.compute_interaction();
302  s_man.update(A, B, C);
303  s_man.compute_interaction();
304 
305  task.set_cVe(cVe);
306  task.set_eJe(eJe);
307 
308  // Compute the control law. Velocities are computed in the mobile robot reference frame
309  vpColVector v = task.computeControlLaw();
310 
311  std::cout << "Send velocity to the mbot: " << v[0] << " m/s " << vpMath::deg(v[1]) << " deg/s" << std::endl;
312 
313  task.print();
314  double radius = 0.0325;
315  double L = 0.0725;
316  double motor_left = (-v[0] - L * v[1]) / radius;
317  double motor_right = (v[0] - L * v[1]) / radius;
318  std::cout << "motor left vel: " << motor_left << " motor right vel: " << motor_right << std::endl;
319  if (!serial_off) {
320  // serial->write("LED_RING=3,0,0,10\n"); // Switch on led 3 to blue: motor left servoed
321  // serial->write("LED_RING=4,0,0,10\n"); // Switch on led 4 to blue: motor right servoed
322  }
323  std::stringstream ss;
324  double rpm_left = motor_left * 30. / M_PI;
325  double rpm_right = motor_right * 30. / M_PI;
326  ss << "MOTOR_RPM=" << vpMath::round(rpm_left) << "," << vpMath::round(rpm_right) << "\n";
327  std::cout << "Send: " << ss.str() << std::endl;
328  if (!serial_off) {
329  serial->write(ss.str());
330  }
331  }
332  else {
333  // stop the robot
334  if (!serial_off) {
335  serial->write("LED_RING=2,10,0,0\n"); // Switch on led 2 to red: tag not detected
336  // serial->write("LED_RING=3,0,0,0\n"); // Switch on led 3 to blue: motor left not servoed
337  // serial->write("LED_RING=4,0,0,0\n"); // Switch on led 4 to blue: motor right not servoed
338  serial->write("MOTOR_RPM=0,-0\n"); // Stop the robot
339  }
340  }
341 
342  vpDisplay::displayText(I, 20, 20, "Click to quit.", vpColor::red);
343  vpDisplay::flush(I);
344 
345  if (display_on && save_image) {
346  vpDisplay::getImage(I, O);
347  vpImageIo::write(O, "image.png");
348  }
349  if (vpDisplay::getClick(I, false))
350  break;
351  }
352 
353  if (!serial_off) {
354  serial->write("LED_RING=0,0,0,0\n"); // Switch off all led
355  }
356 
357  std::cout << "Benchmark computation time" << std::endl;
358  std::cout << "Mean / Median / Std: " << vpMath::getMean(time_vec) << " ms"
359  << " ; " << vpMath::getMedian(time_vec) << " ms"
360  << " ; " << vpMath::getStdev(time_vec) << " ms" << std::endl;
361 
362  if (display_on)
363  delete d;
364  if (!serial_off) {
365  delete serial;
366  }
367  }
368  catch (const vpException &e) {
369  std::cerr << "Catch an exception: " << e.getMessage() << std::endl;
370  if (!serial_off) {
371  serial->write("LED_RING=1,10,0,0\n"); // Switch on led 1 to red
372  }
373  }
374 
375  return EXIT_SUCCESS;
376 #else
377  (void)argc;
378  (void)argv;
379 #ifndef VISP_HAVE_APRILTAG
380  std::cout << "ViSP is not build with Apriltag support" << std::endl;
381 #endif
382 #ifndef VISP_HAVE_V4L2
383  std::cout << "ViSP is not build with v4l2 support" << std::endl;
384 #endif
385  std::cout << "Install missing 3rd parties, configure and build ViSP to run this tutorial" << std::endl;
386  return EXIT_SUCCESS;
387 #endif
388 }
Adaptive gain computation.
void initStandard(double gain_at_zero, double gain_at_infinity, double slope_at_zero)
Generic class defining intrinsic camera parameters.
void initPersProjWithoutDistortion(double px, double py, double u0, double v0)
@ perspectiveProjWithoutDistortion
Perspective projection without distortion model.
Implementation of column vector and the associated operations.
Definition: vpColVector.h:163
static const vpColor red
Definition: vpColor.h:211
static const vpColor green
Definition: vpColor.h:214
@ TAG_36h11
AprilTag 36h11 pattern (recommended)
Use the X11 console to display images on unix-like OS. Thus to enable this class X11 should be instal...
Definition: vpDisplayX.h:128
Class that defines generic functionalities for display.
Definition: vpDisplay.h:173
static bool getClick(const vpImage< unsigned char > &I, bool blocking=true)
static void display(const vpImage< unsigned char > &I)
static void displayLine(const vpImage< unsigned char > &I, const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int thickness=1, bool segment=true)
static void getImage(const vpImage< unsigned char > &Is, vpImage< vpRGBa > &Id)
Definition: vpDisplay.cpp:138
static void displayCross(const vpImage< unsigned char > &I, const vpImagePoint &ip, unsigned int size, const vpColor &color, unsigned int thickness=1)
static void flush(const vpImage< unsigned char > &I)
static void displayText(const vpImage< unsigned char > &I, const vpImagePoint &ip, const std::string &s, const vpColor &color)
static void displayPolygon(const vpImage< unsigned char > &I, const std::vector< vpImagePoint > &vip, const vpColor &color, unsigned int thickness=1, bool closed=true)
error that can be emitted by ViSP classes.
Definition: vpException.h:59
const char * getMessage() const
Definition: vpException.cpp:64
Functionality computation for normalized surface moment feature. Computes the interaction matrix asso...
Functionality computation for centered and normalized moment feature. Computes the interaction matrix...
Implementation of an homogeneous matrix and operations on such kind of matrices.
static void write(const vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
Definition: vpImageIo.cpp:287
unsigned int getWidth() const
Definition: vpImage.h:245
unsigned int getHeight() const
Definition: vpImage.h:184
static double getMedian(const std::vector< double > &v)
Definition: vpMath.cpp:323
static double getStdev(const std::vector< double > &v, bool useBesselCorrection=false)
Definition: vpMath.cpp:354
static int round(double x)
Definition: vpMath.h:403
static double getMean(const std::vector< double > &v)
Definition: vpMath.cpp:303
static double deg(double rad)
Definition: vpMath.h:117
Implementation of a matrix and operations on matrices.
Definition: vpMatrix.h:146
Class handling the normalized surface moment that is invariant in scale and used to estimate depth.
This class defines the 2D basic moment . This class is a wrapper for vpMomentObject wich allows to us...
Definition: vpMomentBasic.h:70
const std::vector< double > & get() const
This class defines the double-indexed centered moment descriptor .
This class allows to register all vpMoments so they can access each other according to their dependen...
virtual void updateAll(vpMomentObject &object)
Class describing 2D normalized gravity center moment.
Class describing 2D gravity center moment.
Class for generic objects.
void linkTo(vpMomentDatabase &moments)
Definition: vpMoment.cpp:95
static void convertPoint(const vpCameraParameters &cam, const double &u, const double &v, double &x, double &y)
Class that defines a 3D point in the object frame and allows forward projection of a 3D point in the ...
Definition: vpPoint.h:77
void set_x(double x)
Set the point x coordinate in the image plane.
Definition: vpPoint.cpp:504
void set_y(double y)
Set the point y coordinate in the image plane.
Definition: vpPoint.cpp:506
Implementation of a rotation matrix and operations on such kind of matrices.
void write(const std::string &s)
Definition: vpSerial.cpp:313
void setInteractionMatrixType(const vpServoIteractionMatrixType &interactionMatrixType, const vpServoInversionType &interactionMatrixInversion=PSEUDO_INVERSE)
Definition: vpServo.cpp:378
@ EYEINHAND_L_cVe_eJe
Definition: vpServo.h:162
void addFeature(vpBasicFeature &s_cur, vpBasicFeature &s_star, unsigned int select=vpBasicFeature::FEATURE_ALL)
Definition: vpServo.cpp:329
void set_cVe(const vpVelocityTwistMatrix &cVe_)
Definition: vpServo.h:1028
void print(const vpServo::vpServoPrintType display_level=ALL, std::ostream &os=std::cout)
Definition: vpServo.cpp:169
void setLambda(double c)
Definition: vpServo.h:976
void set_eJe(const vpMatrix &eJe_)
Definition: vpServo.h:1091
void setServo(const vpServoType &servo_type)
Definition: vpServo.cpp:132
vpColVector computeControlLaw()
Definition: vpServo.cpp:703
@ CURRENT
Definition: vpServo.h:196
Class that consider the case of a translation vector.
Generic functions for unicycle mobile robots.
Definition: vpUnicycle.h:50
Class that is a wrapper over the Video4Linux2 (V4L2) driver.
void setScale(unsigned scale=vpV4l2Grabber::DEFAULT_SCALE)
void setDevice(const std::string &devname)
void acquire(vpImage< unsigned char > &I)
XML parser to load and save intrinsic camera parameters.
int parse(vpCameraParameters &cam, const std::string &filename, const std::string &camera_name, const vpCameraParameters::vpCameraParametersProjType &projModel, unsigned int image_width=0, unsigned int image_height=0, bool verbose=true)
VISP_EXPORT double measureTimeMs()