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