1 /****************************************************************************
2 *
3 * ViSP, open source Visual Servoing Platform software.
4 * Copyright (C) 2005 - 2019 by Inria. All rights reserved.
5 *
6 * This software is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 * See the file LICENSE.txt at the root directory of this source
11 * distribution for additional information about the GNU GPL.
12 *
13 * For using ViSP with software that can not be combined with the GNU
14 * GPL, please contact Inria about acquiring a ViSP Professional
15 * Edition License.
16 *
17 * See http://visp.inria.fr for more information.
18 *
19 * This software was developed at:
20 * Inria Rennes - Bretagne Atlantique
21 * Campus Universitaire de Beaulieu
22 * 35042 Rennes Cedex
23 * France
24 *
25 * If you have questions regarding the use of this file, please contact
26 * Inria at visp@inria.fr
27 *
28 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30 *
31 * Description:
32 * Track a white dot.
33 *
34 * Authors:
35 * Fabien Spindler
36 * Anthony Saunier
37 *
38 *****************************************************************************/
39
40 /*!
41 \file vpDot2.cpp
42 \brief Track a dot.
43 */
44
45 //#define DEBUG
46
47 #include <visp3/core/vpDisplay.h>
48
49 // exception handling
50 #include <visp3/core/vpIoTools.h>
51 #include <visp3/core/vpMath.h>
52 #include <visp3/core/vpTrackingException.h>
53
54 #include <cmath> // std::fabs
55 #include <iostream>
56 #include <limits> // numeric_limits
57 #include <math.h>
58 #include <visp3/blob/vpDot2.h>
59
60 /******************************************************************************
61 *
62 * CONSTRUCTORS AND DESTRUCTORS
63 *
64 *****************************************************************************/
65 /*!
66
67 Initialize the tracker with default parameters.
68
69 */
init()70 void vpDot2::init()
71 {
72 cog.set_u(0);
73 cog.set_v(0);
74
75 width = 0;
76 height = 0;
77 surface = 0;
78 mean_gray_level = 0;
79 gray_level_min = 128;
80 gray_level_max = 255;
81 grayLevelPrecision = 0.80;
82 gamma = 1.5;
83
84 sizePrecision = 0.65;
85 ellipsoidShapePrecision = 0.65;
86 maxSizeSearchDistancePrecision = 0.65;
87 setEllipsoidBadPointsPercentage();
88 m00 = m11 = m02 = m20 = m10 = m01 = 0.;
89 mu11 = mu02 = mu20 = 0.;
90
91 bbox_u_min = bbox_u_max = bbox_v_min = bbox_v_max = 0;
92
93 firstBorder_u = 0;
94 firstBorder_v = 0;
95
96 compute_moment = false;
97 graphics = false;
98 thickness = 1;
99 }
100
101 /*!
102 Default constructor. Just do basic default initialization.
103 */
vpDot2()104 vpDot2::vpDot2()
105 : m00(0.), m10(0.), m01(0.), m11(0.), m20(0.), m02(0.), mu11(0.), mu20(0.), mu02(0.), cog(), width(0), height(0),
106 surface(0), gray_level_min(128), gray_level_max(255), mean_gray_level(0), grayLevelPrecision(0.8), gamma(1.5),
107 sizePrecision(0.65), ellipsoidShapePrecision(0.65), maxSizeSearchDistancePrecision(0.65),
108 allowedBadPointsPercentage_(0.), area(), direction_list(), ip_edges_list(), compute_moment(false), graphics(false),
109 thickness(1), bbox_u_min(0), bbox_u_max(0), bbox_v_min(0), bbox_v_max(0), firstBorder_u(0), firstBorder_v()
110 {
111 }
112
113 /*!
114
115 Constructor initialize the coordinates of the gravity center of the dot to
116 the image point \e ip. Rest is the same as the default constructor.
117
118 \param ip : An image point with sub-pixel coordinates.
119
120 */
vpDot2(const vpImagePoint & ip)121 vpDot2::vpDot2(const vpImagePoint &ip)
122 : m00(0.), m10(0.), m01(0.), m11(0.), m20(0.), m02(0.), mu11(0.), mu20(0.), mu02(0.), cog(ip), width(0), height(0),
123 surface(0), gray_level_min(128), gray_level_max(255), mean_gray_level(0), grayLevelPrecision(0.8), gamma(1.5),
124 sizePrecision(0.65), ellipsoidShapePrecision(0.65), maxSizeSearchDistancePrecision(0.65),
125 allowedBadPointsPercentage_(0.), area(), direction_list(), ip_edges_list(), compute_moment(false), graphics(false),
126 thickness(1), bbox_u_min(0), bbox_u_max(0), bbox_v_min(0), bbox_v_max(0), firstBorder_u(0), firstBorder_v()
127 {
128 }
129
130 /*!
131 Copy constructor.
132 */
vpDot2(const vpDot2 & twinDot)133 vpDot2::vpDot2(const vpDot2 &twinDot)
134 : vpTracker(twinDot), m00(0.), m10(0.), m01(0.), m11(0.), m20(0.), m02(0.), mu11(0.), mu20(0.), mu02(0.), cog(),
135 width(0), height(0), surface(0), gray_level_min(128), gray_level_max(255), mean_gray_level(0),
136 grayLevelPrecision(0.8), gamma(1.5), sizePrecision(0.65), ellipsoidShapePrecision(0.65),
137 maxSizeSearchDistancePrecision(0.65), allowedBadPointsPercentage_(0.), area(), direction_list(), ip_edges_list(),
138 compute_moment(false), graphics(false), thickness(1), bbox_u_min(0), bbox_u_max(0), bbox_v_min(0), bbox_v_max(0),
139 firstBorder_u(0), firstBorder_v()
140 {
141 *this = twinDot;
142 }
143
144 /*!
145 = operator.
146 */
operator =(const vpDot2 & twinDot)147 vpDot2 &vpDot2::operator=(const vpDot2 &twinDot)
148 {
149 cog = twinDot.cog;
150
151 width = twinDot.width;
152 height = twinDot.height;
153 surface = twinDot.surface;
154 gray_level_min = twinDot.gray_level_min;
155 gray_level_max = twinDot.gray_level_max;
156 mean_gray_level = twinDot.mean_gray_level;
157 grayLevelPrecision = twinDot.grayLevelPrecision;
158 gamma = twinDot.gamma;
159 ;
160 sizePrecision = twinDot.sizePrecision;
161 ellipsoidShapePrecision = twinDot.ellipsoidShapePrecision;
162 maxSizeSearchDistancePrecision = twinDot.maxSizeSearchDistancePrecision;
163 allowedBadPointsPercentage_ = twinDot.allowedBadPointsPercentage_;
164 area = twinDot.area;
165
166 direction_list = twinDot.direction_list;
167 ip_edges_list = twinDot.ip_edges_list;
168
169 compute_moment = twinDot.compute_moment;
170 graphics = twinDot.graphics;
171 thickness = twinDot.thickness;
172
173 bbox_u_min = twinDot.bbox_u_min;
174 bbox_u_max = twinDot.bbox_u_max;
175 bbox_v_min = twinDot.bbox_v_min;
176 bbox_v_max = twinDot.bbox_v_max;
177
178 firstBorder_u = twinDot.firstBorder_u;
179 firstBorder_v = twinDot.firstBorder_v;
180
181 m00 = twinDot.m00;
182 m01 = twinDot.m01;
183 m11 = twinDot.m11;
184 m10 = twinDot.m10;
185 m02 = twinDot.m02;
186 m20 = twinDot.m20;
187
188 mu11 = twinDot.mu11;
189 mu20 = twinDot.mu20;
190 mu02 = twinDot.mu02;
191
192 return (*this);
193 }
194
195 /*!
196 Destructor... do nothing for the moment.
197 */
~vpDot2()198 vpDot2::~vpDot2() {}
199
200 /******************************************************************************
201 *
202 * PUBLIC METHODS
203 *****************************************************************************/
204
205 /*!
206 Display in overlay the dot edges and center of gravity.
207
208 \param I : Image.
209 \param color : The color used for the display.
210 \param t : Thickness of the displayed cross located at the dot cog.
211 */
display(const vpImage<unsigned char> & I,vpColor color,unsigned int t) const212 void vpDot2::display(const vpImage<unsigned char> &I, vpColor color, unsigned int t) const
213 {
214 vpDisplay::displayCross(I, cog, 3 * t + 8, color, t);
215 std::list<vpImagePoint>::const_iterator it;
216
217 for (it = ip_edges_list.begin(); it != ip_edges_list.end(); ++it) {
218 vpDisplay::displayPoint(I, *it, color);
219 }
220 }
221
222 /*!
223
224 Initialize the tracking with a mouse click on the image and update the dot
225 characteristics (center of gravity, moments) by a call to track().
226
227 Wait a user click in a white area in the image. The clicked pixel
228 will be the starting point from which the dot will be tracked.
229
230 To get center of gravity of the dot, see getCog(). To compute the
231 moments see setComputeMoments(). To get the width or height of the
232 dot, call getWidth() and getHeight(). The area of the dot is
233 given by getArea().
234
235 \param I : Image.
236 \param size : Size of the dot to track.
237
238 If no valid dot was found in the window, return an exception.
239
240 \exception vpTrackingException::featureLostError : If the dot initialization
241 failed. The initialization can fail if the following characteristics are
242 not valid;
243 - The gray level is between gray level min and gray level max.
244
245 - The shape should be ellipsoid if
246 setEllipsoidShapePrecision(ellipsoidShapePrecision) is used.
247 This is the default case. To track a non ellipsoid shape use
248 setEllipsoidShapePrecision(0).
249
250 \sa track()
251
252 */
initTracking(const vpImage<unsigned char> & I,unsigned int size)253 void vpDot2::initTracking(const vpImage<unsigned char> &I, unsigned int size)
254 {
255 while (vpDisplay::getClick(I, cog) != true) {}
256
257 unsigned int i = (unsigned int)cog.get_i();
258 unsigned int j = (unsigned int)cog.get_j();
259
260 double Ip = pow((double)I[i][j] / 255, 1 / gamma);
261
262 if (Ip - (1 - grayLevelPrecision) < 0) {
263 gray_level_min = 0;
264 } else {
265 gray_level_min = (unsigned int)(255 * pow(Ip - (1 - grayLevelPrecision), gamma));
266 if (gray_level_min > 255)
267 gray_level_min = 255;
268 }
269 gray_level_max = (unsigned int)(255 * pow(Ip + (1 - grayLevelPrecision), gamma));
270 if (gray_level_max > 255)
271 gray_level_max = 255;
272
273 setWidth(size);
274 setHeight(size);
275
276 try {
277 track(I);
278 } catch (const vpException &e) {
279 // vpERROR_TRACE("Error caught") ;
280 throw(e);
281 }
282 }
283
284 /*!
285
286 Initialize the tracking for a dot supposed to be located at (u,v) and update
287 the dot characteristics (center of gravity, moments) by a call to track().
288
289 \param I : Image to process.
290
291 \param ip : Location of the starting point from which the dot will be
292 tracked in the image.
293
294 \param size : Size of the dot to track.
295
296 To get center of gravity of the dot, see getCog(). To compute the
297 moments see setComputeMoments().
298
299 If no valid dot was found in the window, return an exception.
300
301 \exception vpTrackingException::featureLostError : If the dot initialization
302 failed. The initialization can fail if the following characteristics are
303 not valid;
304 - The gray level is between gray level min and gray level max.
305
306 - The shape should be ellipsoid if
307 setEllipsoidShapePrecision(ellipsoidShapePrecision) is used.
308 This is the default case. To track a non ellipsoid shape use
309 setEllipsoidShapePrecision(0).
310 */
initTracking(const vpImage<unsigned char> & I,const vpImagePoint & ip,unsigned int size)311 void vpDot2::initTracking(const vpImage<unsigned char> &I, const vpImagePoint &ip, unsigned int size)
312 {
313 cog = ip;
314
315 unsigned int i = (unsigned int)cog.get_i();
316 unsigned int j = (unsigned int)cog.get_j();
317
318 double Ip = pow((double)I[i][j] / 255, 1 / gamma);
319
320 if (Ip - (1 - grayLevelPrecision) < 0) {
321 gray_level_min = 0;
322 } else {
323 gray_level_min = (unsigned int)(255 * pow(Ip - (1 - grayLevelPrecision), gamma));
324 if (gray_level_min > 255)
325 gray_level_min = 255;
326 }
327 gray_level_max = (unsigned int)(255 * pow(Ip + (1 - grayLevelPrecision), gamma));
328 if (gray_level_max > 255)
329 gray_level_max = 255;
330
331 setWidth(size);
332 setHeight(size);
333
334 try {
335 track(I);
336 } catch (const vpException &e) {
337 // vpERROR_TRACE("Error caught") ;
338 throw(e);
339 }
340 }
341
342 /*!
343
344 Initialize the tracking for a dot supposed to be located at (u,v) and update
345 the dot characteristics (center of gravity, moments) by a call to track().
346
347 The sub pixel coordinates of the dot are updated. To get the center
348 of gravity coordinates of the dot, use getCog(). To
349 compute the moments use setComputeMoments(true) before a call to
350 initTracking().
351
352 \param I : Image to process.
353
354 \param ip : Location of the starting point from which the dot will
355 be tracked in the image.
356
357 \param gray_lvl_min : Minimum gray level threshold used to segment the dot;
358 value comprised between 0 and 255.
359
360 \param gray_lvl_max : Maximum gray level threshold used to segment the
361 dot; value comprised between 0 and 255. \e gray_level_max should be
362 greater than \e gray_level_min.
363
364 \param size : Size of the dot to track.
365
366 If no valid dot was found in the window, return an exception.
367
368 \exception vpTrackingException::featureLostError : If the dot initialization
369 failed. The initialization can fail if the following characteristics are
370 not valid;
371 - The gray level is between gray level min and gray level max.
372
373 - The shape should be ellipsoid if
374 setEllipsoidShapePrecision(ellipsoidShapePrecision) is used.
375 This is the default case. To track a non ellipsoid shape use
376 setEllipsoidShapePrecision(0).
377
378 \sa track(), getCog()
379
380 */
initTracking(const vpImage<unsigned char> & I,const vpImagePoint & ip,unsigned int gray_lvl_min,unsigned int gray_lvl_max,unsigned int size)381 void vpDot2::initTracking(const vpImage<unsigned char> &I, const vpImagePoint &ip, unsigned int gray_lvl_min,
382 unsigned int gray_lvl_max, unsigned int size)
383 {
384 cog = ip;
385
386 this->gray_level_min = gray_lvl_min;
387 this->gray_level_max = gray_lvl_max;
388
389 setWidth(size);
390 setHeight(size);
391
392 try {
393 track(I);
394 } catch (const vpException &e) {
395 // vpERROR_TRACE("Error caught") ;
396 throw(e);
397 }
398 }
399
400 /*!
401
402 Try to locate the dot in the image:
403
404 - First, estimate the new position of the dot, using its previous position.
405 - Then compute the center of gravity (surface, width height) of the
406 tracked entity from the Freeman chain elements.
407 - If the dot is lost (estimated point too dark, too much surface
408 change,...), search the dot in a window around the previous position.
409 - If no valid dot was found in the window, return an exception.
410
411 \param I : Image.
412
413 \param canMakeTheWindowGrow: if true, the size of the searching area is
414 increased if the blob is not found, otherwise it stays the same. Default
415 value is true.
416
417 \exception vpTrackingException::featureLostError : If the dot tracking
418 failed. The tracking can fail if the following characteristics are not
419 valid;
420 - The gray level is between gray level min and gray level max.
421
422 - The size (width or height) and the surface (in terms of number of
423 pixels) should not differ to much with the previous dot.
424
425 - The shape should be ellipsoid if
426 setEllipsoidShapePrecision(ellipsoidShapePrecision) is used.
427 This is the default case. To track a non ellipsoid shape use
428 setEllipsoidShapePrecision(0).
429
430 To get the center of gravity of the dot, call getCog(). To get the
431 width or height of the dot, call getWidth() and getHeight(). The area of the
432 dot is given by getArea().
433
434 To compute all the inertia moments associated to the dot see
435 setComputeMoments().
436
437 To get the pixels coordinates on the dot boundary, see getList_u() and
438 getList_v().
439
440 */
track(const vpImage<unsigned char> & I,bool canMakeTheWindowGrow)441 void vpDot2::track(const vpImage<unsigned char> &I, bool canMakeTheWindowGrow)
442 {
443 m00 = m11 = m02 = m20 = m10 = m01 = 0;
444
445 // First, we will estimate the position of the tracked point
446
447 // Set the search area to the entire image
448 setArea(I);
449
450 // create a copy of the dot to search
451 // This copy can be saw as the previous dot used to check if the current one
452 // found with computeParameters() is similar to the previous one (see
453 // isValid() function). If the found dot is not similar (or valid), we use
454 // this copy to set the current found dot to the previous one (see below).
455 vpDot2 wantedDot(*this);
456
457 // vpDEBUG_TRACE(0, "Previous dot: ");
458 // vpDEBUG_TRACE(0, "u: %f v: %f", get_u(), get_v());
459 // vpDEBUG_TRACE(0, "w: %f h: %f", getWidth(), getHeight());
460 bool found = computeParameters(I, cog.get_u(), cog.get_v());
461
462 if (found) {
463 // test if the found dot is valid (ie similar to the previous one)
464 found = isValid(I, wantedDot);
465 if (!found) {
466 *this = wantedDot;
467 // std::cout << "The found dot is not valid" << std::endl;
468 }
469 }
470
471 if (!found) {
472 // vpDEBUG_TRACE(0, "Search the dot in a biggest window around the
473 // last position"); vpDEBUG_TRACE(0, "Bad computed dot: ");
474 // vpDEBUG_TRACE(0, "u: %f v: %f", get_u(), get_v());
475 // vpDEBUG_TRACE(0, "w: %f h: %f", getWidth(), getHeight());
476
477 // if estimation was wrong (get an error tracking), look for the dot
478 // closest from the estimation,
479 // i.e. search for dots in an a region of interest around the this dot and
480 // get the first element in the area.
481
482 // first get the size of the search window from the dot size
483 double searchWindowWidth = 0.0, searchWindowHeight = 0.0;
484 // if( getWidth() == 0 || getHeight() == 0 )
485 if (std::fabs(getWidth()) <= std::numeric_limits<double>::epsilon() ||
486 std::fabs(getHeight()) <= std::numeric_limits<double>::epsilon()) {
487 searchWindowWidth = 80.;
488 searchWindowHeight = 80.;
489 } else if (canMakeTheWindowGrow) {
490 searchWindowWidth = getWidth() * 5;
491 searchWindowHeight = getHeight() * 5;
492 } else {
493 searchWindowWidth = getWidth();
494 searchWindowHeight = getHeight();
495 }
496
497 std::list<vpDot2> candidates;
498 searchDotsInArea(I, (int)(this->cog.get_u() - searchWindowWidth / 2.0),
499 (int)(this->cog.get_v() - searchWindowHeight / 2.0), (unsigned int)searchWindowWidth,
500 (unsigned int)searchWindowHeight, candidates);
501
502 // if the vector is empty, that mean we didn't find any candidate
503 // in the area, return an error tracking.
504 if (candidates.empty()) {
505 // vpERROR_TRACE("No dot was found") ;
506 throw(vpTrackingException(vpTrackingException::featureLostError, "No dot was found"));
507 }
508
509 // otherwise we've got our dot, update this dot's parameters
510 vpDot2 movingDot = candidates.front();
511
512 setCog(movingDot.getCog());
513 setArea(movingDot.getArea());
514 setWidth(movingDot.getWidth());
515 setHeight(movingDot.getHeight());
516
517 // Update the moments
518 m00 = movingDot.m00;
519 m01 = movingDot.m01;
520 m10 = movingDot.m10;
521 m11 = movingDot.m11;
522 m20 = movingDot.m20;
523 m02 = movingDot.m02;
524
525 // Update the bounding box
526 bbox_u_min = movingDot.bbox_u_min;
527 bbox_u_max = movingDot.bbox_u_max;
528 bbox_v_min = movingDot.bbox_v_min;
529 bbox_v_max = movingDot.bbox_v_max;
530 }
531 // else {
532 // // test if the found dot is valid,
533 // if( ! isValid( I, wantedDot ) ) {
534 // *this = wantedDot;
535 // vpERROR_TRACE("The found dot is invalid:",
536 // "- could be a problem of size (width or height) or "
537 // " surface (number of pixels) which differ too much "
538 // " to the previous one "
539 // "- or a problem of the shape which is not ellipsoid if "
540 // " use setEllipsoidShapePrecision(double
541 // ellipsoidShapePrecision) "
542 // " which is the default case. "
543 // " To track a non ellipsoid shape use
544 // setEllipsoidShapePrecision(0)") ;
545 // throw(vpTrackingException(vpTrackingException::featureLostError,
546 // "The found dot is invalid")) ;
547 // }
548 // }
549
550 // if this dot is partially out of the image, return an error tracking.
551 if (!isInImage(I)) {
552 // vpERROR_TRACE("The center of gravity of the dot is not in the image") ;
553 throw(vpTrackingException(vpTrackingException::featureLostError,
554 "The center of gravity of the dot is not in the image"));
555 }
556
557 // Get dots center of gravity
558 // unsigned int u = (unsigned int) this->cog.get_u();
559 // unsigned int v = (unsigned int) this->cog.get_v();
560 // Updates the min and max gray levels for the next iteration
561 // double Ip = pow((double)I[v][u]/255,1/gamma);
562 double Ip = pow(getMeanGrayLevel() / 255, 1 / gamma);
563 // printf("current value of gray level center : %i\n", I[v][u]);
564
565 // getMeanGrayLevel(I);
566 if (Ip - (1 - grayLevelPrecision) < 0) {
567 gray_level_min = 0;
568 } else {
569 gray_level_min = (unsigned int)(255 * pow(Ip - (1 - grayLevelPrecision), gamma));
570 if (gray_level_min > 255)
571 gray_level_min = 255;
572 }
573 gray_level_max = (unsigned int)(255 * pow(Ip + (1 - grayLevelPrecision), gamma));
574 if (gray_level_max > 255)
575 gray_level_max = 255;
576
577 // printf("%i %i \n",gray_level_max,gray_level_min);
578 if (graphics) {
579 // display a red cross at the center of gravity's location in the image.
580
581 vpDisplay::displayCross(I, this->cog, 3 * thickness + 8, vpColor::red, thickness);
582 // vpDisplay::flush(I);
583 }
584 }
585
586 /*!
587
588 Track and get the new dot coordinates. See track() for a more complete
589 description
590
591 \param[in] I : Image to process.
592
593 \param[out] ip : Sub pixel coordinate of the tracked dot center of gravity.
594
595 \param[in] canMakeTheWindowGrow : if true, the size of the searching area is
596 increased if the blob is not found, otherwise it stays the same. Default
597 value is true.
598
599 The behavior of this method is similar to the following code:
600 \code
601 vpDot2 d;
602 d.track(I);
603 vpImagePoint cog = d.getCog();
604 \endcode
605
606 \sa track()
607 */
track(const vpImage<unsigned char> & I,vpImagePoint & ip,bool canMakeTheWindowGrow)608 void vpDot2::track(const vpImage<unsigned char> &I, vpImagePoint &ip, bool canMakeTheWindowGrow)
609 {
610 track(I, canMakeTheWindowGrow);
611
612 ip = this->cog;
613 }
614
615 ///// GET METHODS
616 ////////////////////////////////////////////////////////////////
617
618 /*!
619 Return the width of the dot.
620
621 \sa getHeight()
622 */
getWidth() const623 double vpDot2::getWidth() const { return width; }
624
625 /*!
626 Return the height of the dot.
627
628 \sa getWidth()
629 */
getHeight() const630 double vpDot2::getHeight() const { return height; }
631
632 /*!
633 Return the area of the dot.
634
635 The area of the dot is also given by \f$|m00|\f$.
636 */
getArea() const637 double vpDot2::getArea() const { return fabs(surface); }
638
639 /*!
640 Return the precision of the gray level of the dot. It is a double
641 precision float which value is in [0,1]. 1 means full precision, whereas
642 values close to 0 show a very bad precision.
643 */
getGrayLevelPrecision() const644 double vpDot2::getGrayLevelPrecision() const { return grayLevelPrecision; }
645
646 /*!
647 Return the precision of the size of the dot. It is a double
648 precision float which value is in [0.05,1]. 1 means full precision, whereas
649 values close to 0 show a very bad precision.
650 */
getSizePrecision() const651 double vpDot2::getSizePrecision() const { return sizePrecision; }
652
653 /*!
654 Return the precision of the ellipsoid shape of the dot. It is a double
655 precision float which value is in [0,1]. 1 means full precision, whereas
656 values close to 0 show a very bad precision.
657
658 \sa setEllipsoidShapePrecision()
659 */
getEllipsoidShapePrecision() const660 double vpDot2::getEllipsoidShapePrecision() const { return ellipsoidShapePrecision; }
661
662 /*!
663 Return the precision of the search maximum distance to get the starting
664 point on a dot border. It is a double precision float which value is in
665 [0.05,1]. 1 means full precision, whereas values close to 0 show a very bad
666 precision.
667 */
getMaxSizeSearchDistancePrecision() const668 double vpDot2::getMaxSizeSearchDistancePrecision() const { return maxSizeSearchDistancePrecision; }
669
670 /*!
671 Return the distance between the two center of dots.
672 */
getDistance(const vpDot2 & distantDot) const673 double vpDot2::getDistance(const vpDot2 &distantDot) const
674 {
675 vpImagePoint cogDistantDot = distantDot.getCog();
676 double diff_u = this->cog.get_u() - cogDistantDot.get_u();
677 double diff_v = this->cog.get_v() - cogDistantDot.get_v();
678 return sqrt(diff_u * diff_u + diff_v * diff_v);
679 }
680
681 ///// SET METHODS ////////////////////////////////////////////////////////////
682
683 /*!
684
685 Set the width of the dot. This is meant to be used to search a dot in an
686 area.
687
688 \param w : Width of a dot to search in a region of interest.
689
690 \sa setHeight(), setArea(), setSizePrecision()
691 */
setWidth(const double & w)692 void vpDot2::setWidth(const double &w) { this->width = w; }
693
694 /*!
695
696 Set the height of the dot. This is meant to be used to search a dot in an
697 area.
698
699 \param h : Height of a dot to search in a region of interest.
700
701 \sa setWidth(), setArea(), setSizePrecision()
702
703 */
setHeight(const double & h)704 void vpDot2::setHeight(const double &h) { this->height = h; }
705
706 /*!
707
708 Set the area of the dot. This is meant to be used to search a dot in a
709 region of interest.
710
711 \param a : Area of a dot to search in a region of interest.
712
713 \sa setWidth(), setHeight(), setSizePrecision()
714
715 */
setArea(const double & a)716 void vpDot2::setArea(const double &a) { this->surface = a; }
717
718 /*!
719
720 Set the precision of the gray level of the dot.
721
722 \param precision : It is a double precision float which value is in [0.05,1]:
723 - 1 means full precision, whereas values close to 0 show a very bad
724 accuracy.
725 - Values lower or equal to 0.05 are brought back to 0.05.
726 - Values higher than 1 are brought back to 1
727 If the initial gray level is I, the gray levels of the dot will be between :
728 \f$Imin=255*\big((\frac{I}{255})^{{\gamma}^{-1}}-(1-grayLevelPrecision)\big)^{\gamma}\f$
729 and
730 \f$Imax=255*\big((\frac{I}{255})^{{\gamma}^{-1}}+(1-grayLevelPrecision)\big)^{\gamma}\f$
731 with \f$\gamma=1.5\f$ .
732
733 \sa setGrayLevelMin(), setGrayLevelMax()
734 */
setGrayLevelPrecision(const double & precision)735 void vpDot2::setGrayLevelPrecision(const double &precision)
736 {
737 double epsilon = 0.05;
738 if (grayLevelPrecision < epsilon) {
739 this->grayLevelPrecision = epsilon;
740 } else if (grayLevelPrecision > 1) {
741 this->grayLevelPrecision = 1.0;
742 } else {
743 this->grayLevelPrecision = precision;
744 }
745 }
746 /*!
747
748 Set the precision of the size of the dot. Used to test the validity of the
749 dot
750
751 \param precision : It is a double precision float which value is in [0,1]:
752 - this is the limit ratio between the tested parameter and the measured one.
753 minSize = sizePrecision*originalSize ; maxSize =
754 originalSize/sizePrecision ;
755 - 1 means full precision, whereas values close to 0 show a very bad
756 accuracy.
757 - Values lower or equal to 0 are brought back to 0.
758 - Values higher than 1 are brought back to 1.
759 - To desactivate validity test set sizePrecision to 0
760
761 \sa setWidth(), setHeight(), setArea()
762 */
setSizePrecision(const double & precision)763 void vpDot2::setSizePrecision(const double &precision)
764 {
765 if (sizePrecision < 0) {
766 this->sizePrecision = 0;
767 } else if (sizePrecision > 1) {
768 this->sizePrecision = 1.0;
769 } else {
770 this->sizePrecision = precision;
771 }
772 }
773
774 /*!
775 Indicates if the dot should have an ellipsoid shape to be valid.
776 \param precision : It is a double precision float which value is in [0,1]:
777
778 - 1 means full precision, whereas values close to 0 show a very bad
779 accuracy.
780 - Values lower or equal to 0 are brought back to 0.
781 - Values higher than 1 are brought back to 1.
782 To track a non ellipsoid shape use setEllipsoidShapePrecision(0).
783
784 The following example show how to track a blob with a height constraint on
785 an ellipsoid shape. The tracking will fail if the shape is not ellipsoid.
786 \code
787 vpDot2 dot;
788 dot.setEllipsoidShapePrecision(0.9); // to track a blob with a height
789 constraint attendee on a circle shape
790 ...
791 dot.track();
792 \endcode
793
794 This other example shows how to remove any constraint on the shape. Here the
795 tracker will be able to track any shape, including square or rectangular
796 shapes.
797 \code
798 vpDot2 dot;
799 dot.setEllipsoidShapePrecision(0.); // to track a blob without any constraint on the shape
800 ...
801 dot.track();
802 \endcode
803
804 \sa getEllipsoidShapePrecision()
805 */
setEllipsoidShapePrecision(const double & precision)806 void vpDot2::setEllipsoidShapePrecision(const double &precision)
807 {
808
809 if (ellipsoidShapePrecision < 0) {
810 this->ellipsoidShapePrecision = 0;
811 } else if (ellipsoidShapePrecision > 1) {
812 this->ellipsoidShapePrecision = 1.0;
813 } else {
814 this->ellipsoidShapePrecision = precision;
815 }
816 }
817
818 /*!
819
820 Set the precision of the search maximum distance to get the starting point
821 on a dot border. A too low value mean a large search area.
822
823 \param precision : It is a double precision float which value is in
824 [0.05,1]:
825 - this is the limit ratio between the tested parameter and the measured one.
826 distance < getWidth()/(getSizePrecision()+epsilon);
827 - 1 means full precision, whereas values close to 0 show a very bad
828 accuracy.
829 - Values lower or equal to 0.05 are brought back to 0.05
830 - Values higher than 1 are brought back to 1.
831
832 */
setMaxSizeSearchDistancePrecision(const double & precision)833 void vpDot2::setMaxSizeSearchDistancePrecision(const double &precision)
834 {
835 double epsilon = 0.05;
836 if (maxSizeSearchDistancePrecision < epsilon) {
837 this->maxSizeSearchDistancePrecision = epsilon;
838 } else if (maxSizeSearchDistancePrecision > 1) {
839 this->maxSizeSearchDistancePrecision = 1.0;
840 } else {
841 this->maxSizeSearchDistancePrecision = precision;
842 }
843 }
844
845 /*!
846
847 Set the parameters of the area in which a dot is search to the image
848 dimension.
849
850 \param I : Image.
851
852 */
setArea(const vpImage<unsigned char> & I)853 void vpDot2::setArea(const vpImage<unsigned char> &I) { setArea(I, 0, 0, I.getWidth(), I.getHeight()); }
854
855 /*!
856
857 Set the parameters of an area by setting the upper-left corner coordinates
858 (u, v), width and height.
859
860 \param I : The image we are working with.
861 \param u : Area horizontal left coordinate.
862 \param v : Area vertical top coordinate.
863 \param w : Area width.
864 \param h : Area height.
865
866 */
setArea(const vpImage<unsigned char> & I,int u,int v,unsigned int w,unsigned int h)867 void vpDot2::setArea(const vpImage<unsigned char> &I, int u, int v, unsigned int w, unsigned int h)
868 {
869 unsigned int image_w = I.getWidth();
870 unsigned int image_h = I.getHeight();
871
872 // Bounds the area to the image
873 if (u < 0)
874 u = 0;
875 else if (u >= (int)image_w)
876 u = (int)image_w - 1;
877 if (v < 0)
878 v = 0;
879 else if (v >= (int)image_h)
880 v = (int)image_h - 1;
881
882 if (((unsigned int)u + w) > image_w)
883 w = image_w - (unsigned int)u - 1;
884 if (((unsigned int)v + h) > image_h)
885 h = image_h - (unsigned int)v - 1;
886
887 area.setRect(u, v, w, h);
888 }
889
890 /*!
891
892 Set the parameters of the area.
893
894 \param a : Area.
895
896 */
setArea(const vpRect & a)897 void vpDot2::setArea(const vpRect &a) { area = a; }
898
899 ///// CLASS FUNCTIONALITY ////////////////////////////////////////////////////
900
901 /*!
902
903 Look for a list of dot matching this dot parameters within the entire
904 image.
905
906 \warning Allocates memory for the list of dots returned by this method.
907 Desallocation has to be done by yourself.
908
909 \param I : Image.
910
911 \param niceDots: List of the dots that are found.
912
913 Before calling this method, dot characteristics to found have to be set
914 like:
915
916 \code
917 vpDot2 d;
918
919 // Set dot characteristics for the auto detection
920 d.setWidth(24);
921 d.setHeight(23);
922 d.setArea(412);
923 d.setGrayLevelMin(160);
924 d.setGrayLevelMax(255);
925 d.setGrayLevelPrecision(0.8);
926 d.setSizePrecision(0.65);
927 d.setEllipsoidShapePrecision(0.65);
928 \endcode
929
930 To search dots in the whole image:
931 \code
932 std::list<vpDot2> list_d;
933 d.searchDotsInArea(I, 0, 0, I.getWidth(), I.getHeight(), list_d) ;
934 \endcode
935
936 The number of dots found in the area is given by:
937 \code
938 std::cout << list_d.size();
939 \endcode
940
941 To parse all the dots:
942 \code
943 std::list<vpDot2>::iterator it;
944 for (it = list_d.begin(); it != list_d.end(); ++it) {
945 vpDot2 tmp_d = *it;
946 }
947 \endcode
948
949 \sa searchDotsInArea(vpImage<unsigned char>&, int, int, unsigned int,
950 unsigned int, std::list<vpDot2> &)
951 */
searchDotsInArea(const vpImage<unsigned char> & I,std::list<vpDot2> & niceDots)952 void vpDot2::searchDotsInArea(const vpImage<unsigned char> &I, std::list<vpDot2> &niceDots)
953 {
954 searchDotsInArea(I, 0, 0, I.getWidth(), I.getHeight(), niceDots);
955 }
956
957 /*!
958
959 Look for a list of dot matching this dot parameters within a region of
960 interest defined by a rectangle in the image. The rectangle upper-left
961 coordinates are given by
962 (\e area_u, \e area_v). The size of the rectangle is given by \e area_w and
963 \e area_h.
964
965 \param I : Image to process.
966 \param area_u : Coordinate (column) of the upper-left area corner.
967 \param area_v : Coordinate (row) of the upper-left area corner.
968
969 \param area_w : Width or the area in which a dot is searched.
970 \param area_h : Height or the area in which a dot is searched.
971
972 \param niceDots: List of the dots that are found.
973
974 \warning Allocates memory for the list of vpDot2 returned by this method.
975 Desallocation has to be done by yourself, see searchDotsInArea()
976
977 \sa searchDotsInArea(vpImage<unsigned char>& I, std::list<vpDot2> &)
978 */
searchDotsInArea(const vpImage<unsigned char> & I,int area_u,int area_v,unsigned int area_w,unsigned int area_h,std::list<vpDot2> & niceDots)979 void vpDot2::searchDotsInArea(const vpImage<unsigned char> &I, int area_u, int area_v, unsigned int area_w,
980 unsigned int area_h, std::list<vpDot2> &niceDots)
981
982 {
983 // clear the list of nice dots
984 niceDots.clear();
985
986 // Fit the input area in the image; we keep only the common part between
987 // this area and the image.
988 setArea(I, area_u, area_v, area_w, area_h);
989
990 // compute the size of the search grid
991 unsigned int gridWidth;
992 unsigned int gridHeight;
993 getGridSize(gridWidth, gridHeight);
994
995 if (graphics) {
996 // Display the area were the dot is search
997 vpDisplay::displayRectangle(I, area, vpColor::blue, false, thickness);
998 // vpDisplay::flush(I);
999 }
1000
1001 #ifdef DEBUG
1002 vpDisplay::displayRectangle(I, area, vpColor::blue);
1003 vpDisplay::flush(I);
1004 #endif
1005 // start the search loop; for all points of the search grid,
1006 // test if the pixel belongs to a valid dot.
1007 // if it is so eventually add it to the vector of valid dots.
1008 std::list<vpDot2> badDotsVector;
1009 std::list<vpDot2>::iterator itnice;
1010 std::list<vpDot2>::iterator itbad;
1011
1012 vpDot2 *dotToTest = NULL;
1013 vpDot2 tmpDot;
1014
1015 unsigned int area_u_min = (unsigned int)area.getLeft();
1016 unsigned int area_u_max = (unsigned int)area.getRight();
1017 unsigned int area_v_min = (unsigned int)area.getTop();
1018 unsigned int area_v_max = (unsigned int)area.getBottom();
1019
1020 unsigned int u, v;
1021 vpImagePoint cogTmpDot;
1022
1023 for (v = area_v_min; v < area_v_max; v = v + gridHeight) {
1024 for (u = area_u_min; u < area_u_max; u = u + gridWidth) {
1025 // if the pixel we're in doesn't have the right color (outside the
1026 // graylevel interval), no need to check further, just get to the
1027 // next grid intersection.
1028 if (!hasGoodLevel(I, u, v))
1029 continue;
1030
1031 // Test if an other germ is inside the bounding box of a dot previously
1032 // detected
1033 bool good_germ = true;
1034
1035 itnice = niceDots.begin();
1036 while (itnice != niceDots.end() && good_germ == true) {
1037 tmpDot = *itnice;
1038
1039 cogTmpDot = tmpDot.getCog();
1040 double u0 = cogTmpDot.get_u();
1041 double v0 = cogTmpDot.get_v();
1042 double half_w = tmpDot.getWidth() / 2.;
1043 double half_h = tmpDot.getHeight() / 2.;
1044
1045 if (u >= (u0 - half_w) && u <= (u0 + half_w) && v >= (v0 - half_h) && v <= (v0 + half_h)) {
1046 // Germ is in a previously detected dot
1047 good_germ = false;
1048 }
1049 ++itnice;
1050 }
1051
1052 if (!good_germ)
1053 continue;
1054
1055 // Compute the right border position for this possible germ
1056 unsigned int border_u;
1057 unsigned int border_v;
1058 if (findFirstBorder(I, u, v, border_u, border_v) == false) {
1059 // germ is not good.
1060 // Jump all the pixels between v,u and v,
1061 // dotToTest->getFirstBorder_u()
1062 u = border_u;
1063 v = border_v;
1064 continue;
1065 }
1066
1067 itbad = badDotsVector.begin();
1068 #define vpBAD_DOT_VALUE (*itbad)
1069 vpImagePoint cogBadDot;
1070
1071 while (itbad != badDotsVector.end() && good_germ == true) {
1072 if ((double)u >= vpBAD_DOT_VALUE.bbox_u_min && (double)u <= vpBAD_DOT_VALUE.bbox_u_max &&
1073 (double)v >= vpBAD_DOT_VALUE.bbox_v_min && (double)v <= vpBAD_DOT_VALUE.bbox_v_max) {
1074 std::list<vpImagePoint>::const_iterator it_edges = ip_edges_list.begin();
1075 while (it_edges != ip_edges_list.end() && good_germ == true) {
1076 // Test if the germ belong to a previously detected dot:
1077 // - from the germ go right to the border and compare this
1078 // position to the list of pixels of previously detected dots
1079 cogBadDot = *it_edges;
1080 // if( border_u == cogBadDot.get_u() && v == cogBadDot.get_v()) {
1081 if ((std::fabs(border_u - cogBadDot.get_u()) <=
1082 vpMath::maximum(std::fabs((double)border_u), std::fabs(cogBadDot.get_u())) *
1083 std::numeric_limits<double>::epsilon()) &&
1084 (std::fabs(v - cogBadDot.get_v()) <=
1085 vpMath::maximum(std::fabs((double)v), std::fabs(cogBadDot.get_v())) *
1086 std::numeric_limits<double>::epsilon())) {
1087 good_germ = false;
1088 }
1089 ++it_edges;
1090 }
1091 }
1092 ++itbad;
1093 }
1094 #undef vpBAD_DOT_VALUE
1095
1096 if (!good_germ) {
1097 // Jump all the pixels between v,u and v,
1098 // dotToTest->getFirstBorder_u()
1099 u = border_u;
1100 v = border_v;
1101 continue;
1102 }
1103
1104 vpTRACE(4, "Try germ (%d, %d)", u, v);
1105
1106 vpImagePoint germ;
1107 germ.set_u(u);
1108 germ.set_v(v);
1109
1110 // otherwise estimate the width, height and surface of the dot we
1111 // created, and test it.
1112 if (dotToTest != NULL)
1113 delete dotToTest;
1114 dotToTest = getInstance();
1115 dotToTest->setCog(germ);
1116 dotToTest->setGrayLevelMin(getGrayLevelMin());
1117 dotToTest->setGrayLevelMax(getGrayLevelMax());
1118 dotToTest->setGrayLevelPrecision(getGrayLevelPrecision());
1119 dotToTest->setSizePrecision(getSizePrecision());
1120 dotToTest->setGraphics(graphics);
1121 dotToTest->setGraphicsThickness(thickness);
1122 dotToTest->setComputeMoments(true);
1123 dotToTest->setArea(area);
1124 dotToTest->setEllipsoidShapePrecision(ellipsoidShapePrecision);
1125 dotToTest->setEllipsoidBadPointsPercentage(allowedBadPointsPercentage_);
1126
1127 // first compute the parameters of the dot.
1128 // if for some reasons this caused an error tracking
1129 // (dot partially out of the image...), check the next intersection
1130 if (dotToTest->computeParameters(I) == false) {
1131 // Jump all the pixels between v,u and v,
1132 // dotToTest->getFirstBorder_u()
1133 u = border_u;
1134 v = border_v;
1135 continue;
1136 }
1137 // if the dot to test is valid,
1138 if (dotToTest->isValid(I, *this)) {
1139 vpImagePoint cogDotToTest = dotToTest->getCog();
1140 // Compute the distance to the center. The center used here is not the
1141 // area center available by area.getCenter(area_center_u,
1142 // area_center_v) but the center of the input area which may be
1143 // partially outside the image.
1144
1145 double area_center_u = area_u + area_w / 2.0 - 0.5;
1146 double area_center_v = area_v + area_h / 2.0 - 0.5;
1147
1148 double thisDiff_u = cogDotToTest.get_u() - area_center_u;
1149 double thisDiff_v = cogDotToTest.get_v() - area_center_v;
1150 double thisDist = sqrt(thisDiff_u * thisDiff_u + thisDiff_v * thisDiff_v);
1151
1152 bool stopLoop = false;
1153 itnice = niceDots.begin();
1154
1155 while (itnice != niceDots.end() && stopLoop == false) {
1156 tmpDot = *itnice;
1157
1158 // double epsilon = 0.001; // detecte +sieurs points
1159 double epsilon = 3.0;
1160 // if the center of the dot is the same than the current
1161 // don't add it, test the next point of the grid
1162 cogTmpDot = tmpDot.getCog();
1163
1164 if (fabs(cogTmpDot.get_u() - cogDotToTest.get_u()) < epsilon &&
1165 fabs(cogTmpDot.get_v() - cogDotToTest.get_v()) < epsilon) {
1166 stopLoop = true;
1167 // Jump all the pixels between v,u and v,
1168 // tmpDot->getFirstBorder_u()
1169 u = border_u;
1170 v = border_v;
1171 continue;
1172 }
1173
1174 double otherDiff_u = cogTmpDot.get_u() - area_center_u;
1175 double otherDiff_v = cogTmpDot.get_v() - area_center_v;
1176 double otherDist = sqrt(otherDiff_u * otherDiff_u + otherDiff_v * otherDiff_v);
1177
1178 // if the distance of the curent vector element to the center
1179 // is greater than the distance of this dot to the center,
1180 // then add this dot before the current vector element.
1181 if (otherDist > thisDist) {
1182 niceDots.insert(itnice, *dotToTest);
1183 ++itnice;
1184 stopLoop = true;
1185 // Jump all the pixels between v,u and v,
1186 // tmpDot->getFirstBorder_u()
1187 u = border_u;
1188 v = border_v;
1189 continue;
1190 }
1191 ++itnice;
1192 }
1193 vpTRACE(4, "End while (%d, %d)", u, v);
1194
1195 // if we reached the end of the vector without finding the dot
1196 // or inserting it, insert it now.
1197 if (itnice == niceDots.end() && stopLoop == false) {
1198 niceDots.push_back(*dotToTest);
1199 }
1200 } else {
1201 // Store bad dots
1202 badDotsVector.push_front(*dotToTest);
1203 }
1204 }
1205 }
1206 if (dotToTest != NULL)
1207 delete dotToTest;
1208 }
1209
1210 /*!
1211
1212 Check if the dot is "like" the wanted dot passed in.
1213
1214 Compare the following characteristics of the dot to the wanted dot;
1215 - the size (width or height)
1216 - the surface (number of pixels)
1217 - the geometry; the shape should be ellipsoid if
1218 setEllipsoidShapePrecision(double ellispoidShapePrecision) is used.
1219
1220 \return If it is so, return true, otherwise return false.
1221
1222 \warning Parameters of the wanted dot (width, height, surface, in level, out
1223 level, accuracy) must already be set before; see
1224 searchDotsInArea(vpImage<unsigned char>& I)
1225
1226 \param I : Image.
1227 \param wantedDot : Wanted dot passed in.
1228
1229 */
isValid(const vpImage<unsigned char> & I,const vpDot2 & wantedDot)1230 bool vpDot2::isValid(const vpImage<unsigned char> &I, const vpDot2 &wantedDot)
1231 {
1232 double size_precision = wantedDot.getSizePrecision();
1233 double ellipsoidShape_precision = wantedDot.getEllipsoidShapePrecision();
1234
1235 //
1236 // First, check the width, height and surface of the dot. Those parameters
1237 // must be the same.
1238 //
1239 // if ( (wantedDot.getWidth() != 0)
1240 // && (wantedDot.getHeight() != 0)
1241 // && (wantedDot.getArea() != 0) )
1242 if ((std::fabs(wantedDot.getWidth()) > std::numeric_limits<double>::epsilon()) &&
1243 (std::fabs(wantedDot.getHeight()) > std::numeric_limits<double>::epsilon()) &&
1244 (std::fabs(wantedDot.getArea()) > std::numeric_limits<double>::epsilon()))
1245 // if (size_precision!=0){
1246 {
1247 if (std::fabs(size_precision) > std::numeric_limits<double>::epsilon()) {
1248 double epsilon = 0.001;
1249 #ifdef DEBUG
1250 std::cout << "test size precision......................\n";
1251 std::cout << "wanted dot: "
1252 << "w=" << wantedDot.getWidth() << " h=" << wantedDot.getHeight() << " s=" << wantedDot.getArea()
1253 << " precision=" << size_precision << " epsilon=" << epsilon << std::endl;
1254 std::cout << "dot found: "
1255 << "w=" << getWidth() << " h=" << getHeight() << " s=" << getArea() << std::endl;
1256 #endif
1257
1258 if ((wantedDot.getWidth() * size_precision - epsilon < getWidth()) == false) {
1259 vpDEBUG_TRACE(3, "Bad width > for dot (%g, %g)", cog.get_u(), cog.get_v());
1260 #ifdef DEBUG
1261 printf("Bad width > for dot (%g, %g)\n", cog.get_u(), cog.get_v());
1262 #endif
1263 return false;
1264 }
1265
1266 if ((getWidth() < wantedDot.getWidth() / (size_precision + epsilon)) == false) {
1267 vpDEBUG_TRACE(3, "Bad width > for dot (%g, %g)", cog.get_u(), cog.get_v());
1268 #ifdef DEBUG
1269 printf("Bad width %g > %g for dot (%g, %g)\n", getWidth(), wantedDot.getWidth() / (size_precision + epsilon),
1270 cog.get_u(), cog.get_v());
1271 #endif
1272 return false;
1273 }
1274
1275 if ((wantedDot.getHeight() * size_precision - epsilon < getHeight()) == false) {
1276 vpDEBUG_TRACE(3, "Bad height > for dot (%g, %g)", cog.get_u(), cog.get_v());
1277 #ifdef DEBUG
1278 printf("Bad height %g > %g for dot (%g, %g)\n", wantedDot.getHeight() * size_precision - epsilon, getHeight(),
1279 cog.get_u(), cog.get_v());
1280 #endif
1281 return false;
1282 }
1283
1284 if ((getHeight() < wantedDot.getHeight() / (size_precision + epsilon)) == false) {
1285 vpDEBUG_TRACE(3, "Bad height > for dot (%g, %g)", cog.get_u(), cog.get_v());
1286 #ifdef DEBUG
1287 printf("Bad height %g > %g for dot (%g, %g)\n", getHeight(), wantedDot.getHeight() / (size_precision + epsilon),
1288 cog.get_u(), cog.get_v());
1289 #endif
1290 return false;
1291 }
1292
1293 if ((wantedDot.getArea() * (size_precision * size_precision) - epsilon < getArea()) == false) {
1294 vpDEBUG_TRACE(3, "Bad surface > for dot (%g, %g)", cog.get_u(), cog.get_v());
1295 #ifdef DEBUG
1296 printf("Bad surface %g > %g for dot (%g, %g)\n",
1297 wantedDot.getArea() * (size_precision * size_precision) - epsilon, getArea(), cog.get_u(), cog.get_v());
1298 #endif
1299 return false;
1300 }
1301
1302 if ((getArea() < wantedDot.getArea() / (size_precision * size_precision + epsilon)) == false) {
1303 vpDEBUG_TRACE(3, "Bad surface > for dot (%g, %g)", cog.get_u(), cog.get_v());
1304 #ifdef DEBUG
1305 printf("Bad surface %g < %g for dot (%g, %g)\n", getArea(),
1306 wantedDot.getArea() / (size_precision * size_precision + epsilon), cog.get_u(), cog.get_v());
1307 #endif
1308 return false;
1309 }
1310 }
1311 }
1312 //
1313 // Now we can proceed to more advanced (and costy) checks.
1314 // First check there is a white (>level) elipse within dot
1315 // Then check the dot is surrounded by a black ellipse.
1316 //
1317 int nb_point_to_test = 20; // Nb points to test on inner and outside ellipsoid
1318 int nb_bad_points = 0;
1319 int nb_max_bad_points = (int)(nb_point_to_test * allowedBadPointsPercentage_);
1320 double step_angle = 2 * M_PI / nb_point_to_test;
1321
1322 // if (ellipsoidShape_precision != 0 && compute_moment) {
1323 if (std::fabs(ellipsoidShape_precision) > std::numeric_limits<double>::epsilon() && compute_moment) {
1324 // Chaumette, Image Moments: A General and Useful Set of Features for Visual Servoing, TRO 2004, eq 15
1325
1326 // mu11 = m11 - m00 * xg * yg = m11 - m00 * m10/m00 * m01/m00
1327 // = m11 - m10 * m01 / m00
1328 // mu20 = m20 - m00 * xg^2 = m20 - m00 * m10/m00 * m10/m00
1329 // = m20 - m10^2 / m00
1330 // mu02 = m02 - m01^2 / m00
1331 // alpha = 1/2 arctan( 2 * mu11 / (mu20 - mu02) )
1332 //
1333 // a1^2 = 2 / m00 * (mu02 + mu20 + sqrt( (mu20 - mu02)^2 + 4mu11^2) )
1334 //
1335 // a2^2 = 2 / m00 * (mu02 + mu20 - sqrt( (mu20 - mu02)^2 + 4mu11^2) )
1336
1337 // we compute parameters of the estimated ellipse
1338 double tmp1 = (m01 * m01 - m10 * m10) / m00 + (m20 - m02);
1339 double tmp2 = m11 - m10 * m01 / m00;
1340 double Sqrt = sqrt(tmp1 * tmp1 + 4 * tmp2 * tmp2);
1341 double a1 = sqrt(2 / m00 * ((m20 + m02) - (m10 * m10 + m01 * m01) / m00 + Sqrt));
1342 double a2 = sqrt(2 / m00 * ((m20 + m02) - (m10 * m10 + m01 * m01) / m00 - Sqrt));
1343 double alpha = 0.5 * atan2(2 * (m11 * m00 - m10 * m01), ((m20 - m02) * m00 - m10 * m10 + m01 * m01));
1344
1345 // to be able to track small dots, minorize the ellipsoid radius for the
1346 // inner test
1347 a1 -= 1.0;
1348 a2 -= 1.0;
1349
1350 double innerCoef = ellipsoidShape_precision;
1351 unsigned int u, v;
1352 double cog_u = this->cog.get_u();
1353 double cog_v = this->cog.get_v();
1354
1355 vpImagePoint ip;
1356 nb_bad_points = 0;
1357 for (double theta = 0.; theta < 2 * M_PI; theta += step_angle) {
1358 u = (unsigned int)(cog_u + innerCoef * (a1 * cos(alpha) * cos(theta) - a2 * sin(alpha) * sin(theta)));
1359 v = (unsigned int)(cog_v + innerCoef * (a1 * sin(alpha) * cos(theta) + a2 * cos(alpha) * sin(theta)));
1360 if (!this->hasGoodLevel(I, u, v)) {
1361 // vpTRACE("Inner circle pixel (%d, %d) has bad level for dot (%g, %g)",
1362 // u, v, cog_u, cog_v);
1363 #ifdef DEBUG
1364 printf("Inner circle pixel (%u, %u) has bad level for dot (%g, %g): "
1365 "%d not in [%u, %u]\n",
1366 u, v, cog_u, cog_v, I[v][u], gray_level_min, gray_level_max);
1367 #endif
1368 // return false;
1369 nb_bad_points++;
1370 }
1371 if (graphics) {
1372 for (unsigned int t = 0; t < thickness; t++) {
1373 ip.set_u(u + t);
1374 ip.set_v(v);
1375 vpDisplay::displayPoint(I, ip, vpColor::green);
1376 }
1377 }
1378 #ifdef DEBUG
1379 vpDisplay::displayPoint(I, ip, vpColor::green);
1380 vpDisplay::flush(I);
1381 #endif
1382 }
1383 if (nb_bad_points > nb_max_bad_points) {
1384 #ifdef DEBUG
1385 printf("Inner ellipse has %d bad points. Max allowed is %d\n", nb_bad_points, nb_max_bad_points);
1386 #endif
1387 return false;
1388 }
1389 // to be able to track small dots, maximize the ellipsoid radius for the
1390 // inner test
1391 a1 += 2.0;
1392 a2 += 2.0;
1393
1394 double outCoef = 2 - ellipsoidShape_precision; // 1.6;
1395 nb_bad_points = 0;
1396 for (double theta = 0.; theta < 2 * M_PI; theta += step_angle) {
1397 u = (unsigned int)(cog_u + outCoef * (a1 * cos(alpha) * cos(theta) - a2 * sin(alpha) * sin(theta)));
1398 v = (unsigned int)(cog_v + outCoef * (a1 * sin(alpha) * cos(theta) + a2 * cos(alpha) * sin(theta)));
1399 #ifdef DEBUG
1400 // vpDisplay::displayRectangle(I, area, vpColor::yellow);
1401 vpDisplay::displayCross(I, (int)v, (int)u, 7, vpColor::purple);
1402 vpDisplay::flush(I);
1403 #endif
1404 // If outside the area, continue
1405 if ((double)u < area.getLeft() || (double)u > area.getRight() || (double)v < area.getTop() ||
1406 (double)v > area.getBottom()) {
1407 continue;
1408 }
1409 if (!this->hasReverseLevel(I, u, v)) {
1410 // vpTRACE("Outside circle pixel (%d, %d) has bad level for dot (%g,
1411 // %g)", u, v, cog_u, cog_v);
1412 #ifdef DEBUG
1413 printf("Outside circle pixel (%u, %u) has bad level for dot (%g, "
1414 "%g): %d not in [%u, %u]\n",
1415 u, v, cog_u, cog_v, I[v][u], gray_level_min, gray_level_max);
1416 #endif
1417 nb_bad_points++;
1418 // return false;
1419 }
1420 if (graphics) {
1421 for (unsigned int t = 0; t < thickness; t++) {
1422 ip.set_u(u + t);
1423 ip.set_v(v);
1424
1425 vpDisplay::displayPoint(I, ip, vpColor::green);
1426 }
1427 }
1428 }
1429 }
1430 if (nb_bad_points > nb_max_bad_points) {
1431 #ifdef DEBUG
1432 printf("Outside ellipse has %d bad points. Max allowed is %d\n", nb_bad_points, nb_max_bad_points);
1433 #endif
1434 return false;
1435 }
1436
1437 return true;
1438 }
1439
1440 /*!
1441
1442 Check if a the pixel of coordinates (u, v) is in the image and has
1443 a good level to belong to this dot.
1444
1445 \param I : Image.
1446 \param u : Pixel to test.
1447 \param v : Pixel to test.
1448
1449 \return true : If the pixel of coordinates (u, v) is in the area and
1450 has a value between the min and max levels fixed by setGrayLevelMin() and
1451 setGrayLevelMax().
1452
1453 \return false : Otherwise
1454
1455 \sa setGrayLevelMin(), setGrayLevelMax()
1456
1457 */
hasGoodLevel(const vpImage<unsigned char> & I,const unsigned int & u,const unsigned int & v) const1458 bool vpDot2::hasGoodLevel(const vpImage<unsigned char> &I, const unsigned int &u, const unsigned int &v) const
1459 {
1460 if (!isInArea(u, v))
1461 return false;
1462
1463 if (I[v][u] >= gray_level_min && I[v][u] <= gray_level_max) {
1464 return true;
1465 } else {
1466 return false;
1467 }
1468 }
1469
1470 /*!
1471
1472 Check if a the pixel of coordinates (u, v) in the image has a good level to
1473 be a dark zone around the dot.
1474
1475 \param I : Image.
1476 \param u : Pixel to test.
1477 \param v : Pixel to test.
1478
1479 \return true if it is so, and false otherwise.
1480
1481 */
hasReverseLevel(const vpImage<unsigned char> & I,const unsigned int & u,const unsigned int & v) const1482 bool vpDot2::hasReverseLevel(const vpImage<unsigned char> &I, const unsigned int &u, const unsigned int &v) const
1483 {
1484
1485 if (!isInArea(u, v))
1486 return false;
1487
1488 if (I[v][u] < gray_level_min || I[v][u] > gray_level_max) {
1489 return true;
1490 } else {
1491 return false;
1492 }
1493 }
1494
1495 /*!
1496 Return a new instance of vpDot2.
1497
1498 Should be used by child classed to return their own instance of vpDot2.
1499
1500 \return An instance of vpDot2.
1501
1502 */
getInstance()1503 vpDot2 *vpDot2::getInstance() { return new vpDot2(); }
1504
1505 /*!
1506
1507 Returns the list of Freeman chain code used to turn around the dot
1508 counterclockwise.
1509
1510 \return List of Freeman chain list [0, ..., 7]
1511 - 0 : right
1512 - 1 : top right
1513 - 2 : top
1514 - 3 : top left
1515 - 4 : left
1516 - 5 : down left
1517 - 6 : down
1518 - 7 : down right
1519 */
getFreemanChain(std::list<unsigned int> & freeman_chain) const1520 void vpDot2::getFreemanChain(std::list<unsigned int> &freeman_chain) const { freeman_chain = direction_list; }
1521
1522 /******************************************************************************
1523 *
1524 * PRIVATE METHODS
1525 *
1526 ******************************************************************************/
1527
1528 /*!
1529
1530 Compute all the parameters of the dot (center, width, height, surface,
1531 inertia moments...).
1532
1533 This is done the following way:
1534
1535 - First, we check the point (_u, _v) passed in has the right level in the
1536 image
1537
1538 - Then we cross the tracked entity from left to right until we reach it's
1539 border.
1540
1541 - We follow this border until we come back to the first point or we get to
1542 border of the image. Each time we update variables used to compute the
1543 dot parameters
1544
1545 \param I : The image we are working with.
1546
1547 \param _u : A pixel coordinate inside the dot.
1548
1549 \param _v : A pixel coordinate inside the dot.
1550
1551 \return false : If a dot can't be found around pixel coordinates given as
1552 parameter
1553
1554 \return true : If a dot was found.
1555
1556 \sa getFirstBorder_u(), getFirstBorder_v()
1557
1558 */
computeParameters(const vpImage<unsigned char> & I,const double & _u,const double & _v)1559 bool vpDot2::computeParameters(const vpImage<unsigned char> &I, const double &_u, const double &_v)
1560 {
1561 direction_list.clear();
1562 ip_edges_list.clear();
1563
1564 double est_u = _u; // estimated
1565 double est_v = _v;
1566
1567 // if u has default value, set it to the actual center value
1568 // if( est_u == -1.0 )
1569 if (std::fabs(est_u + 1.0) <= vpMath::maximum(std::fabs(est_u), 1.) * std::numeric_limits<double>::epsilon()) {
1570 est_u = this->cog.get_u();
1571 }
1572
1573 // if v has default value, set it to the actual center value
1574 // if( est_v == -1.0 )
1575 if (std::fabs(est_v + 1.0) <= vpMath::maximum(std::fabs(est_v), 1.) * std::numeric_limits<double>::epsilon()) {
1576 est_v = this->cog.get_v();
1577 }
1578
1579 // if the estimated position of the dot is out of the image, not need to
1580 // continue, return an error tracking
1581 if (!isInArea((unsigned int)est_u, (unsigned int)est_v)) {
1582 vpDEBUG_TRACE(3,
1583 "Initial pixel coordinates (%d, %d) for dot tracking are "
1584 "not in the area",
1585 (int)est_u, (int)est_v);
1586 return false;
1587 }
1588
1589 bbox_u_min = (int)I.getWidth();
1590 bbox_u_max = 0;
1591 bbox_v_min = (int)I.getHeight();
1592 bbox_v_max = 0;
1593
1594 // if the first point doesn't have the right level then there's no point to
1595 // continue.
1596 if (!hasGoodLevel(I, (unsigned int)est_u, (unsigned int)est_v)) {
1597 vpDEBUG_TRACE(3, "Can't find a dot from pixel (%d, %d) coordinates", (int)est_u, (int)est_v);
1598 return false;
1599 }
1600
1601 // find the border
1602
1603 if (!findFirstBorder(I, (unsigned int)est_u, (unsigned int)est_v, this->firstBorder_u, this->firstBorder_v)) {
1604
1605 vpDEBUG_TRACE(3, "Can't find first border (%d, %d) coordinates", (int)est_u, (int)est_v);
1606 return false;
1607 }
1608
1609 unsigned int dir = 6;
1610
1611 // Determine the first element of the Freeman chain
1612 computeFreemanChainElement(I, this->firstBorder_u, this->firstBorder_v, dir);
1613 unsigned int firstDir = dir;
1614
1615 // if we are now out of the image, return an error tracking
1616 if (!isInArea(this->firstBorder_u, this->firstBorder_v)) {
1617 vpDEBUG_TRACE(3, "Border pixel coordinates (%d, %d) of the dot are not in the area", this->firstBorder_u,
1618 this->firstBorder_v);
1619 return false;
1620 }
1621
1622 // store the new direction and dot border coordinates.
1623 direction_list.push_back(dir);
1624 vpImagePoint ip;
1625 ip.set_u(this->firstBorder_u);
1626 ip.set_v(this->firstBorder_v);
1627
1628 ip_edges_list.push_back(ip);
1629
1630 int border_u = (int)this->firstBorder_u;
1631 int border_v = (int)this->firstBorder_v;
1632
1633 // vpTRACE("-----------------------------------------");
1634 // vpTRACE("first border_u: %d border_v: %d dir: %d",
1635 // this->firstBorder_u, this->firstBorder_v,firstDir);
1636 int du, dv;
1637 float dS, dMu, dMv, dMuv, dMu2, dMv2;
1638 m00 = 0.0;
1639 m10 = 0.0;
1640 m01 = 0.0;
1641 m11 = 0.0;
1642 m20 = 0.0;
1643 m02 = 0.0;
1644 // while we didn't come back to the first point, follow the border
1645 do {
1646 // if it was asked, show the border
1647 if (graphics) {
1648 for (int t = 0; t < (int)thickness; t++) {
1649 ip.set_u(border_u + t);
1650 ip.set_v(border_v);
1651
1652 vpDisplay::displayPoint(I, ip, vpColor::red);
1653 }
1654 // vpDisplay::flush(I);
1655 }
1656 #ifdef DEBUG
1657 vpDisplay::displayPoint(I, border_v, border_u, vpColor::red);
1658 vpDisplay::flush(I);
1659 #endif
1660 // Determine the increments for the parameters
1661 computeFreemanParameters(border_u, border_v, dir, du, dv,
1662 dS, // surface
1663 dMu, dMv, // first order moments
1664 dMuv, dMu2, dMv2); // second order moment
1665
1666 // Update the parameters
1667 border_u += du; // Next position on the border
1668 border_v += dv;
1669 m00 += dS; // enclosed area
1670 m10 += dMu; // First order moment along v axis
1671 m01 += dMv; // First order moment along u axis
1672 if (compute_moment) {
1673 m11 += dMuv; // Second order moment
1674 m20 += dMu2; // Second order moment along v axis
1675 m02 += dMv2; // Second order moment along u axis
1676 }
1677 // if we are now out of the image, return an error tracking
1678 if (!isInArea((unsigned int)border_u, (unsigned int)border_v)) {
1679
1680 vpDEBUG_TRACE(3, "Dot (%d, %d) is not in the area", border_u, border_v);
1681 // Can Occur on a single pixel dot located on the top border
1682 return false;
1683 }
1684
1685 // store the new direction and dot border coordinates.
1686
1687 direction_list.push_back(dir);
1688
1689 ip.set_u(border_u);
1690 ip.set_v(border_v);
1691 ip_edges_list.push_back(ip);
1692
1693 // vpDisplay::getClick(I);
1694
1695 // update the extreme point of the dot.
1696 if (border_v < bbox_v_min)
1697 bbox_v_min = border_v;
1698 if (border_v > bbox_v_max)
1699 bbox_v_max = border_v;
1700 if (border_u < bbox_u_min)
1701 bbox_u_min = border_u;
1702 if (border_u > bbox_u_max)
1703 bbox_u_max = border_u;
1704
1705 // move around the tracked entity by following the border.
1706 if (computeFreemanChainElement(I, (unsigned int)border_u, (unsigned int)border_v, dir) == false) {
1707 vpDEBUG_TRACE(3, "Can't compute Freeman chain for dot (%d, %d)", border_u, border_v);
1708 return false;
1709 }
1710
1711 // vpTRACE("border_u: %d border_v: %d dir: %d", border_u, border_v,
1712 // dir);
1713
1714 } while ((getFirstBorder_u() != (unsigned int)border_u || getFirstBorder_v() != (unsigned int)border_v ||
1715 firstDir != dir) &&
1716 isInArea((unsigned int)border_u, (unsigned int)border_v));
1717
1718 #ifdef VP_DEBUG
1719 #if VP_DEBUG_MODE == 3
1720 vpDisplay::flush(I);
1721 #endif
1722 #endif
1723
1724 // if the surface is one or zero , the center of gravity wasn't properly
1725 // detected. Return an error tracking.
1726 // if( m00 == 0 || m00 == 1 )
1727 if (std::fabs(m00) <= std::numeric_limits<double>::epsilon() ||
1728 std::fabs(m00 - 1.) <= vpMath::maximum(std::fabs(m00), 1.) * std::numeric_limits<double>::epsilon()) {
1729 vpDEBUG_TRACE(3, "The center of gravity of the dot wasn't properly detected");
1730 return false;
1731 } else // compute the center
1732 {
1733 // this magic formula gives the coordinates of the center of gravity
1734 double tmpCenter_u = m10 / m00;
1735 double tmpCenter_v = m01 / m00;
1736
1737 // Updates the second order centered moments
1738 if (compute_moment) {
1739 mu11 = m11 - tmpCenter_u * m01;
1740 mu02 = m02 - tmpCenter_v * m01;
1741 mu20 = m20 - tmpCenter_u * m10;
1742 }
1743
1744 // check the center is in the image... never know...
1745 // if( !hasGoodLevel( I, (unsigned int)tmpCenter_u,
1746 // (unsigned int)tmpCenter_v ) )
1747 // {
1748 // vpDEBUG_TRACE(3, "The center of gravity of the dot (%g, %g) has
1749 // not a good in level", tmpCenter_u, tmpCenter_v); return false;
1750 // }
1751
1752 cog.set_u(tmpCenter_u);
1753 cog.set_v(tmpCenter_v);
1754 }
1755
1756 width = bbox_u_max - bbox_u_min + 1;
1757 height = bbox_v_max - bbox_v_min + 1;
1758 surface = m00;
1759
1760 computeMeanGrayLevel(I);
1761 return true;
1762 }
1763
1764 /*!
1765 Find the starting point on a dot border from an other point in the dot.
1766 the dot border is computed from this point.
1767
1768 \param I : Image.
1769 \param u : The row coordinate of a pixel in the dot.
1770 \param v : The column coordinate of a pixel in the dot.
1771 \param border_u : The row coordinate of the found starting point.
1772 \param border_v : The column coordinate of the found starting point.
1773
1774 \return false if the width of this dot was initialised and we already
1775 crossed the dot on more than the max possible width. Return true if success.
1776
1777 \sa computeParameters()
1778 */
findFirstBorder(const vpImage<unsigned char> & I,const unsigned int & u,const unsigned int & v,unsigned int & border_u,unsigned int & border_v)1779 bool vpDot2::findFirstBorder(const vpImage<unsigned char> &I, const unsigned int &u, const unsigned int &v,
1780 unsigned int &border_u, unsigned int &border_v)
1781 {
1782 // find the border
1783
1784 // NOTE:
1785 // from here we use int and not double. This is because we don't have
1786 // rounding problems and it's actually more a trouble than smth else to
1787 // work with double when navigating around the dot.
1788 border_u = u;
1789 border_v = v;
1790 double epsilon = 0.001;
1791
1792 #ifdef DEBUG
1793 std::cout << "gray level: " << gray_level_min << " " << gray_level_max << std::endl;
1794 #endif
1795 while (hasGoodLevel(I, border_u + 1, border_v) && border_u < area.getRight() /*I.getWidth()*/) {
1796 // if the width of this dot was initialised and we already crossed the dot
1797 // on more than the max possible width, no need to continue, return an
1798 // error tracking
1799 if (getWidth() > 0 && (border_u - u) > getWidth() / (getMaxSizeSearchDistancePrecision() + epsilon)) {
1800 vpDEBUG_TRACE(3,
1801 "The found dot (%d, %d, %d) has a greater width than the "
1802 "required one",
1803 u, v, border_u);
1804 return false;
1805 }
1806 #ifdef DEBUG
1807 vpDisplay::displayPoint(I, (int)border_v, (int)border_u + 1, vpColor::green);
1808 vpDisplay::flush(I);
1809 #endif
1810
1811 border_u++;
1812 }
1813 return true;
1814 }
1815
1816 /*!
1817
1818 Considering a pixel (u, v) compute the next element of the Freeman chain
1819 code.
1820
1821 According to the gray level of pixel (u, v) and his eight neighbors
1822 determine the next element of the chain in order to turn around the dot
1823 counterclockwise.
1824
1825 \param I : The image we are working with.
1826 \param v : The row coordinate of a pixel on a border.
1827 \param u : The column coordinate of the pixel on a border.
1828 \param element : The next freeman element chain code (0, 1, 2, 3, 4, 5, 6,
1829 7) with 0 for right moving, 2 for down, 4 for left and 6 for up moving.
1830
1831 \return false if an element cannot be found. Occurs for example with an area
1832 constituted by a single pixel. Return true if success.
1833 */
computeFreemanChainElement(const vpImage<unsigned char> & I,const unsigned int & u,const unsigned int & v,unsigned int & element)1834 bool vpDot2::computeFreemanChainElement(const vpImage<unsigned char> &I, const unsigned int &u, const unsigned int &v,
1835 unsigned int &element)
1836 {
1837
1838 if (hasGoodLevel(I, u, v)) {
1839 unsigned int _u = u;
1840 unsigned int _v = v;
1841 // get the point on the right of the point passed in
1842 updateFreemanPosition(_u, _v, (element + 2) % 8);
1843 if (hasGoodLevel(I, _u, _v)) {
1844 element = (element + 2) % 8; // turn right
1845 } else {
1846 unsigned int _u1 = u;
1847 unsigned int _v1 = v;
1848 updateFreemanPosition(_u1, _v1, (element + 1) % 8);
1849
1850 if (hasGoodLevel(I, _u1, _v1)) {
1851 element = (element + 1) % 8; // turn diag right
1852 } else {
1853 unsigned int _u2 = u;
1854 unsigned int _v2 = v;
1855 updateFreemanPosition(_u2, _v2, element); // same direction
1856
1857 if (hasGoodLevel(I, _u2, _v2)) {
1858 // element = element; // keep same dir
1859 } else {
1860 unsigned int _u3 = u;
1861 unsigned int _v3 = v;
1862 updateFreemanPosition(_u3, _v3, (element + 7) % 8); // diag left
1863
1864 if (hasGoodLevel(I, _u3, _v3)) {
1865 element = (element + 7) % 8; // turn diag left
1866 } else {
1867 unsigned int _u4 = u;
1868 unsigned int _v4 = v;
1869 updateFreemanPosition(_u4, _v4, (element + 6) % 8); // left
1870
1871 if (hasGoodLevel(I, _u4, _v4)) {
1872 element = (element + 6) % 8; // turn left
1873 } else {
1874 unsigned int _u5 = u;
1875 unsigned int _v5 = v;
1876 updateFreemanPosition(_u5, _v5, (element + 5) % 8); // left
1877
1878 if (hasGoodLevel(I, _u5, _v5)) {
1879 element = (element + 5) % 8; // turn diag down
1880 } else {
1881 unsigned int _u6 = u;
1882 unsigned int _v6 = v;
1883 updateFreemanPosition(_u6, _v6, (element + 4) % 8); // left
1884
1885 if (hasGoodLevel(I, _u6, _v6)) {
1886 element = (element + 4) % 8; // turn down
1887 } else {
1888 unsigned int _u7 = u;
1889 unsigned int _v7 = v;
1890 updateFreemanPosition(_u7, _v7, (element + 3) % 8); // diag
1891
1892 if (hasGoodLevel(I, _u7, _v7)) {
1893 element = (element + 3) % 8; // turn diag right down
1894 } else {
1895 // No neighbor with a good level
1896 //
1897 return false;
1898 }
1899 }
1900 }
1901 }
1902 }
1903 }
1904 }
1905 }
1906 }
1907
1908 else {
1909 return false;
1910 }
1911
1912 return true;
1913 }
1914
1915 /*!
1916
1917 Given the previous position of a pixel (u_p, v_p) on the dot border and the
1918 direction to reach the next pixel on the border, compute Freeman parameters.
1919
1920 \param u_p : Previous value of the row coordinate of a pixel on a border.
1921 \param v_p : Previous value of the column coordinate of a pixel on a border.
1922 \param du : Increment to go from previous to next pixel on the dot border.
1923 \param dv : Increment to go from previous to next pixel on the dot border.
1924
1925 \param dS : Enclosed area increases. Cumulated values of dS gives m00.
1926
1927 \param dMu : First order moment along v axis increases. Cumulated values of
1928 dMu gives m10.
1929
1930 \param dMv : First order moment along u axis increases. Cumulated values of
1931 dMv gives m01.
1932
1933 \param dMuv : Moment increases. Cumulated values of dMuv gives m11.
1934
1935 \param dMu2 : Second order moment along v axis increases. Cumulated values
1936 of dMu2 gives m20.
1937
1938 \param dMv2 : Second order moment along u axis increases. Cumulated values
1939 of dMv2 gives m02.
1940
1941 Considering the previous coordinates (u_p, v_p) of a pixel on a border, the
1942 next coordinates (u, v) are given by: u = u_p + du and v = v_p + dv
1943
1944
1945 */
computeFreemanParameters(const int & u_p,const int & v_p,unsigned int & element,int & du,int & dv,float & dS,float & dMu,float & dMv,float & dMuv,float & dMu2,float & dMv2)1946 void vpDot2::computeFreemanParameters(const int &u_p, const int &v_p, unsigned int &element, int &du, int &dv,
1947 float &dS, float &dMu, float &dMv, float &dMuv, float &dMu2, float &dMv2)
1948 {
1949 du = 0;
1950 dv = 0;
1951 dMuv = 0;
1952 dMu2 = 0;
1953 dMv2 = 0;
1954
1955 /*
1956 3 2 1
1957 \ | /
1958 \|/
1959 4 ------- 0
1960 /|\
1961 / | \
1962 5 6 7
1963 */
1964 switch (element) {
1965 case 0: // go right
1966 du = 1;
1967 dS = (float)v_p;
1968 dMu = 0.0;
1969 dMv = (float)(0.5 * v_p * v_p);
1970 if (compute_moment) {
1971 dMuv = (float)(0.25 * v_p * v_p * (2 * u_p + 1));
1972 dMu2 = 0;
1973 dMv2 = (float)(1.0 / 3. * v_p * v_p * v_p);
1974 }
1975 break;
1976
1977 case 1: // go right top
1978 du = 1;
1979 dv = 1;
1980 dS = (float)(v_p + 0.5);
1981 dMu = -(float)(0.5 * u_p * (u_p + 1) + 1.0 / 6.0);
1982 dMv = (float)(0.5 * v_p * (v_p + 1) + 1.0 / 6.0);
1983 if (compute_moment) {
1984 float half_u_p = (float)(0.5 * u_p);
1985 dMuv = (float)(v_p * v_p * (0.25 + half_u_p) + v_p * (1. / 3. + half_u_p) + 1. / 6. * u_p + 0.125);
1986 dMu2 = (float)(-1. / 3. * u_p * (u_p * u_p + 1.5 * u_p + 1.) - 1. / 12.0);
1987 dMv2 = (float)(1. / 3. * v_p * (v_p * v_p + 1.5 * v_p + 1.) + 1. / 12.0);
1988 }
1989 break;
1990
1991 case 2: // go top
1992 dv = 1;
1993 dS = 0.0;
1994 dMu = (float)(-0.5 * u_p * u_p);
1995 dMv = 0.0;
1996 if (compute_moment) {
1997 dMuv = 0;
1998 dMu2 = (float)(-1.0 / 3. * u_p * u_p * u_p);
1999 dMv2 = 0;
2000 }
2001 break;
2002
2003 case 3:
2004 du = -1;
2005 dv = 1;
2006 dS = (float)(-v_p - 0.5);
2007 dMu = -(float)(0.5 * u_p * (u_p - 1) + 1.0 / 6.0);
2008 dMv = -(float)(0.5 * v_p * (v_p + 1) + 1.0 / 6.0);
2009 if (compute_moment) {
2010 float half_u_p = (float)(0.5 * u_p);
2011 dMuv = (float)(v_p * v_p * (0.25 - half_u_p) + v_p * (1. / 3. - half_u_p) - 1. / 6. * u_p + 0.125);
2012 dMu2 = (float)(-1. / 3. * u_p * (u_p * u_p - 1.5 * u_p + 1.) - 1. / 12.0);
2013 dMv2 = (float)(-1. / 3. * v_p * (v_p * v_p + 1.5 * v_p + 1.) - 1. / 12.0);
2014 }
2015 break;
2016
2017 case 4:
2018 du = -1;
2019 dS = (float)(-v_p);
2020 dMv = (float)(-0.5 * v_p * v_p);
2021 dMu = 0.0;
2022 if (compute_moment) {
2023 dMuv = (float)(-0.25 * v_p * v_p * (2 * u_p - 1));
2024 dMu2 = 0;
2025 dMv2 = (float)(-1.0 / 3. * v_p * v_p * v_p);
2026 }
2027 break;
2028
2029 case 5:
2030 du = -1;
2031 dv = -1;
2032 dS = (float)(-v_p + 0.5);
2033 dMu = (float)(0.5 * u_p * (u_p - 1) + 1.0 / 6.0);
2034 dMv = (float)(-(0.5 * v_p * (v_p - 1) + 1.0 / 6.0));
2035 if (compute_moment) {
2036 float half_u_p = (float)(0.5 * u_p);
2037 dMuv = (float)(v_p * v_p * (0.25 - half_u_p) - v_p * (1. / 3. - half_u_p) - 1. / 6. * u_p + 0.125);
2038 dMu2 = (float)(1. / 3. * u_p * (u_p * u_p - 1.5 * u_p + 1.) - 1. / 12.0);
2039 dMv2 = (float)(-1. / 3. * v_p * (v_p * v_p - 1.5 * v_p + 1.) - 1. / 12.0);
2040 }
2041 break;
2042
2043 case 6:
2044 dv = -1;
2045 dS = 0.0;
2046 dMu = (float)(0.5 * u_p * u_p);
2047 dMv = 0.0;
2048 if (compute_moment) {
2049 dMuv = 0;
2050 dMu2 = (float)(1.0 / 3. * u_p * u_p * u_p);
2051 dMv2 = 0;
2052 }
2053 break;
2054
2055 case 7:
2056 du = 1;
2057 dv = -1;
2058 dS = (float)(v_p - 0.5);
2059 dMu = (float)(0.5 * u_p * (u_p + 1) + 1.0 / 6.0);
2060 dMv = (float)(0.5 * v_p * (v_p - 1) + 1.0 / 6.0);
2061 if (compute_moment) {
2062 float half_u_p = (float)(0.5 * u_p);
2063 dMuv = (float)(v_p * v_p * (0.25 + half_u_p) - v_p * (1. / 3. + half_u_p) + 1. / 6. * u_p + 0.125);
2064 dMu2 = (float)(1. / 3. * u_p * (u_p * u_p + 1.5 * u_p + 1.) + 1. / 12.0);
2065 dMv2 = (float)(1. / 3. * v_p * (v_p * v_p - 1.5 * v_p + 1.) - 1. / 12.0);
2066 }
2067 break;
2068 }
2069 }
2070
2071 /*!
2072
2073 From a pixel coordinate and a direction, update the pixel coordinates after
2074 moving forward.
2075
2076 \param v : The row coordinate of the pixel, updated by this method.
2077
2078 \param u : The column coordinate of the pixel, updated by this method.
2079
2080 \param dir : The direction in the image, 0=right, 1, 2=down, 3, 4=left, 5,
2081 6=up and 7.
2082
2083 */
updateFreemanPosition(unsigned int & u,unsigned int & v,const unsigned int & dir)2084 void vpDot2::updateFreemanPosition(unsigned int &u, unsigned int &v, const unsigned int &dir)
2085 {
2086 switch (dir) {
2087 case 0:
2088 u += 1;
2089 break;
2090 case 1:
2091 u += 1;
2092 v += 1;
2093 break;
2094 case 2:
2095 v += 1;
2096 break;
2097 case 3:
2098 u -= 1;
2099 v += 1;
2100 break;
2101 case 4:
2102 u -= 1;
2103 break;
2104 case 5:
2105 u -= 1;
2106 v -= 1;
2107 break;
2108 case 6:
2109 v -= 1;
2110 break;
2111 case 7:
2112 u += 1;
2113 v -= 1;
2114 break;
2115 }
2116 }
2117
2118 /*!
2119
2120 Test if a pixel is in the image. Points of the border are not
2121 considered to be in the image. Call the isInImage( vpImage<unsigned
2122 char> &I, const vpImagePoint &) method.
2123
2124 \param I : The image.
2125
2126 \return true if the pixel of coordinates (posI, posJ) is in the image and
2127 false otherwise.
2128 */
isInImage(const vpImage<unsigned char> & I) const2129 bool vpDot2::isInImage(const vpImage<unsigned char> &I) const { return isInImage(I, cog); }
2130
2131 /*!
2132
2133 Test if a pixel is in the image. Points of the border are not considered to
2134 be in the image.
2135
2136 \param I : The image.
2137 \param ip : An image point.
2138
2139 \return true if the image point \e ip is in the image and false
2140 otherwise.
2141 */
isInImage(const vpImage<unsigned char> & I,const vpImagePoint & ip) const2142 bool vpDot2::isInImage(const vpImage<unsigned char> &I, const vpImagePoint &ip) const
2143 {
2144 unsigned int h = I.getHeight();
2145 unsigned int w = I.getWidth();
2146 double u = ip.get_u();
2147 double v = ip.get_v();
2148
2149 if (u < 0 || u >= w)
2150 return false;
2151 if (v < 0 || v >= h)
2152 return false;
2153 return true;
2154 }
2155
2156 /*!
2157
2158 Test if a pixel is in a region of interest. Points of the border are not
2159 considered to be in the area.
2160
2161 \param u : The column coordinate of the pixel.
2162 \param v : The row coordinate of the pixel .
2163
2164 \return true if the pixel of coordinates (u, v) is in the image and false
2165 otherwise.
2166 */
isInArea(const unsigned int & u,const unsigned int & v) const2167 bool vpDot2::isInArea(const unsigned int &u, const unsigned int &v) const
2168 {
2169 unsigned int area_u_min = (unsigned int)area.getLeft();
2170 unsigned int area_u_max = (unsigned int)area.getRight();
2171 unsigned int area_v_min = (unsigned int)area.getTop();
2172 unsigned int area_v_max = (unsigned int)area.getBottom();
2173
2174 if (u < area_u_min || u > area_u_max)
2175 return false;
2176 if (v < area_v_min || v > area_v_max)
2177 return false;
2178 return true;
2179 }
2180
2181 /*!
2182
2183 Get the search grid size used to found a dot in a region of interest. This
2184 grid is used to parse only some pixels of the search area.
2185
2186 \param gridWidth : Number of pixels between to vertical lines of the grid
2187
2188 \param gridHeight : Number of pixels between to horizontal lines of the grid
2189
2190
2191 */
getGridSize(unsigned int & gridWidth,unsigned int & gridHeight)2192 void vpDot2::getGridSize(unsigned int &gridWidth, unsigned int &gridHeight)
2193 {
2194 // first get the research grid width and height Note that
2195 // 1/sqrt(2)=cos(pi/4). The grid squares should be small enough to be
2196 // contained in the dot. We gent this here if the dot is a perfect disc.
2197 // More accurate criterium to define the grid should be implemented if
2198 // necessary
2199 gridWidth = (unsigned int)(getWidth() * getMaxSizeSearchDistancePrecision() / sqrt(2.));
2200 gridHeight = (unsigned int)(getHeight() * getMaxSizeSearchDistancePrecision() / sqrt(2.0));
2201
2202 if (gridWidth == 0)
2203 gridWidth = 1;
2204 if (gridHeight == 0)
2205 gridHeight = 1;
2206 }
2207
2208 /*!
2209
2210 Compute an approximation of mean gray level of the dot.
2211 We compute it by searching the mean of vertical and diagonal points
2212 which gray is between min and max gray level.
2213
2214 \param I: The image.
2215
2216 \return the mean gray level
2217
2218
2219 */
computeMeanGrayLevel(const vpImage<unsigned char> & I)2220 void vpDot2::computeMeanGrayLevel(const vpImage<unsigned char> &I)
2221 {
2222 int cog_u = (int)cog.get_u();
2223 int cog_v = (int)cog.get_v();
2224
2225 unsigned int sum_value = 0;
2226 unsigned int nb_pixels = 0;
2227
2228 for (unsigned int i = (unsigned int)this->bbox_u_min; i <= (unsigned int)this->bbox_u_max; i++) {
2229 unsigned int pixel_gray = (unsigned int)I[(unsigned int)cog_v][i];
2230 if (pixel_gray >= getGrayLevelMin() && pixel_gray <= getGrayLevelMax()) {
2231 sum_value += pixel_gray;
2232 nb_pixels++;
2233 }
2234 }
2235 for (unsigned int i = (unsigned int)this->bbox_v_min; i <= (unsigned int)this->bbox_v_max; i++) {
2236 unsigned char pixel_gray = I[i][(unsigned int)cog_u];
2237 if (pixel_gray >= getGrayLevelMin() && pixel_gray <= getGrayLevelMax()) {
2238 sum_value += pixel_gray;
2239 nb_pixels++;
2240 }
2241 }
2242 if (nb_pixels < 10) { // could be good to choose the min nb points from area of dot
2243 // add diagonals points to have enough point
2244 int imin, imax;
2245 if ((cog_u - bbox_u_min) > (cog_v - bbox_v_min)) {
2246 imin = cog_v - bbox_v_min;
2247 } else {
2248 imin = cog_u - bbox_u_min;
2249 }
2250 if ((bbox_u_max - cog_u) > (bbox_v_max - cog_v)) {
2251 imax = bbox_v_max - cog_v;
2252 } else {
2253 imax = bbox_u_max - cog_u;
2254 }
2255 for (int i = -imin; i <= imax; i++) {
2256 unsigned int pixel_gray = (unsigned int)I[(unsigned int)(cog_v + i)][(unsigned int)(cog_u + i)];
2257 if (pixel_gray >= getGrayLevelMin() && pixel_gray <= getGrayLevelMax()) {
2258 sum_value += pixel_gray;
2259 nb_pixels++;
2260 }
2261 }
2262
2263 if ((cog_u - bbox_u_min) > (bbox_v_max - cog_v)) {
2264 imin = bbox_v_max - cog_v;
2265 } else {
2266 imin = cog_u - bbox_u_min;
2267 }
2268 if ((bbox_u_max - cog_u) > (cog_v - bbox_v_min)) {
2269 imax = cog_v - bbox_v_min;
2270 } else {
2271 imax = bbox_u_max - cog_u;
2272 }
2273
2274 for (int i = -imin; i <= imax; i++) {
2275 unsigned char pixel_gray = I[(unsigned int)(cog_v - i)][(unsigned int)(cog_u + i)];
2276 if (pixel_gray >= getGrayLevelMin() && pixel_gray <= getGrayLevelMax()) {
2277 sum_value += pixel_gray;
2278 nb_pixels++;
2279 }
2280 }
2281 }
2282
2283 if (nb_pixels == 0) {
2284 // should never happen
2285 throw(vpTrackingException(vpTrackingException::notEnoughPointError, "No point was found"));
2286 } else {
2287 mean_gray_level = sum_value / nb_pixels;
2288 }
2289 }
2290
2291 /*!
2292 Define a number of dots from a file.
2293 If the file does not exist, define it by clicking an image, the dots are
2294 then saved into the file.
2295
2296 If the dots from the file cannot be tracked in the image, will ask to click
2297 them.
2298
2299 \param dot : dot2 array
2300 \param n : number of dots, array dimension
2301 \param dotFile : path for the file
2302 \param I : image
2303 \param col : color to print the dots (default Blue)
2304 \param trackDot : if true, tracks the dots in the image, if false
2305 simply loads the coordinates (default true)
2306
2307 \return an nx2 matrix with the coordinates of the dots
2308 */
defineDots(vpDot2 dot[],const unsigned int & n,const std::string & dotFile,vpImage<unsigned char> & I,vpColor col,bool trackDot)2309 vpMatrix vpDot2::defineDots(vpDot2 dot[], const unsigned int &n, const std::string &dotFile, vpImage<unsigned char> &I,
2310 vpColor col, bool trackDot)
2311 {
2312 vpMatrix Cogs(n, 2);
2313 vpImagePoint cog;
2314 unsigned int i;
2315 bool fromFile = vpIoTools::checkFilename(dotFile.c_str());
2316 if (fromFile) {
2317 vpMatrix::loadMatrix(dotFile, Cogs);
2318 std::cout << Cogs.getRows() << " dots loaded from file " << dotFile << std::endl;
2319 }
2320
2321 // test number of cogs in file
2322 if (Cogs.getRows() < n) {
2323 std::cout << "Dot file has a wrong number of dots : redefining them" << std::endl;
2324 fromFile = false;
2325 }
2326
2327 // read from file and tracks the dots
2328 if (fromFile) {
2329 try {
2330 for (i = 0; i < n; ++i) {
2331 cog.set_uv(Cogs[i][0], Cogs[i][1]);
2332 dot[i].setGraphics(true);
2333 dot[i].setCog(cog);
2334 if (trackDot) {
2335 dot[i].initTracking(I, cog);
2336 dot[i].track(I);
2337 vpDisplay::displayCross(I, cog, 10, col);
2338 }
2339 }
2340 } catch (...) {
2341 std::cout << "Cannot track dots from file" << std::endl;
2342 fromFile = false;
2343 }
2344 vpDisplay::flush(I);
2345
2346 // check that dots are far away ones from the other
2347 for (i = 0; i < n && fromFile; ++i) {
2348 double d = sqrt(vpMath::sqr(dot[i].getHeight()) + vpMath::sqr(dot[i].getWidth()));
2349 for (unsigned int j = 0; j < n && fromFile; ++j)
2350 if (j != i)
2351 if (dot[i].getDistance(dot[j]) < d) {
2352 fromFile = false;
2353 std::cout << "Dots from file seem incoherent" << std::endl;
2354 }
2355 }
2356 }
2357
2358 if (!fromFile) {
2359 vpDisplay::display(I);
2360 vpDisplay::flush(I);
2361
2362 std::cout << "Click on the " << n << " dots clockwise starting from upper/left dot..." << std::endl;
2363 for (i = 0; i < n; i++) {
2364 if (trackDot) {
2365 dot[i].setGraphics(true);
2366 dot[i].initTracking(I);
2367 cog = dot[i].getCog();
2368 } else {
2369 vpDisplay::getClick(I, cog);
2370 dot[i].setCog(cog);
2371 }
2372 Cogs[i][0] = cog.get_u();
2373 Cogs[i][1] = cog.get_v();
2374 vpDisplay::displayCross(I, cog, 10, col);
2375 vpDisplay::flush(I);
2376 }
2377 }
2378
2379 if (!fromFile && (dotFile != "")) {
2380 vpMatrix::saveMatrix(dotFile, Cogs);
2381 std::cout << Cogs.getRows() << " dots written to file " << dotFile << std::endl;
2382 }
2383
2384 // back to non graphic mode
2385 for (i = 0; i < n; ++i)
2386 dot[i].setGraphics(false);
2387
2388 return Cogs;
2389 }
2390
2391 /*!
2392 Tracks a number of dots in an image and displays their trajectories
2393
2394 \param dot : dot2 array
2395
2396 \param n : number of dots, array dimension
2397
2398 \param I : image
2399
2400 \param cogs : vector of vpImagePoint that will be updated with the new
2401 dots, will be displayed in green
2402
2403 \param cogStar (optional) : array of
2404 vpImagePoint indicating the desired position (default NULL), will be
2405 displayed in red
2406 */
trackAndDisplay(vpDot2 dot[],const unsigned int & n,vpImage<unsigned char> & I,std::vector<vpImagePoint> & cogs,vpImagePoint * cogStar)2407 void vpDot2::trackAndDisplay(vpDot2 dot[], const unsigned int &n, vpImage<unsigned char> &I,
2408 std::vector<vpImagePoint> &cogs, vpImagePoint *cogStar)
2409 {
2410 unsigned int i;
2411 // tracking
2412 for (i = 0; i < n; ++i) {
2413 dot[i].track(I);
2414 cogs.push_back(dot[i].getCog());
2415 }
2416 // trajectories
2417 for (i = n; i < cogs.size(); ++i)
2418 vpDisplay::displayCircle(I, cogs[i], 4, vpColor::green, true);
2419 // initial position
2420 for (i = 0; i < n; ++i)
2421 vpDisplay::displayCircle(I, cogs[i], 4, vpColor::blue, true);
2422 // if exists, desired position
2423 if (cogStar != NULL)
2424 for (i = 0; i < n; ++i) {
2425 vpDisplay::displayDotLine(I, cogStar[i], dot[i].getCog(), vpColor::red);
2426 vpDisplay::displayCircle(I, cogStar[i], 4, vpColor::red, true);
2427 }
2428 vpDisplay::flush(I);
2429 }
2430
2431 /*!
2432
2433 Display the dot center of gravity and its list of edges.
2434
2435 \param I : The image used as background.
2436
2437 \param cog : The center of gravity.
2438
2439 \param edges_list : The list of edges;
2440
2441 \param color : Color used to display the dot.
2442
2443 \param thickness : Thickness of the dot.
2444 */
display(const vpImage<unsigned char> & I,const vpImagePoint & cog,const std::list<vpImagePoint> & edges_list,vpColor color,unsigned int thickness)2445 void vpDot2::display(const vpImage<unsigned char> &I, const vpImagePoint &cog,
2446 const std::list<vpImagePoint> &edges_list, vpColor color, unsigned int thickness)
2447 {
2448 vpDisplay::displayCross(I, cog, 3 * thickness + 8, color, thickness);
2449 std::list<vpImagePoint>::const_iterator it;
2450
2451 for (it = edges_list.begin(); it != edges_list.end(); ++it) {
2452 vpDisplay::displayPoint(I, *it, color);
2453 }
2454 }
2455
2456 /*!
2457
2458 Display the dot center of gravity and its list of edges.
2459
2460 \param I : The image used as background.
2461
2462 \param cog : The center of gravity.
2463
2464 \param edges_list : The list of edges;
2465
2466 \param color : Color used to display the dot.
2467
2468 \param thickness : Thickness of the dot.
2469 */
display(const vpImage<vpRGBa> & I,const vpImagePoint & cog,const std::list<vpImagePoint> & edges_list,vpColor color,unsigned int thickness)2470 void vpDot2::display(const vpImage<vpRGBa> &I, const vpImagePoint &cog, const std::list<vpImagePoint> &edges_list,
2471 vpColor color, unsigned int thickness)
2472 {
2473 vpDisplay::displayCross(I, cog, 3 * thickness + 8, color, thickness);
2474 std::list<vpImagePoint>::const_iterator it;
2475
2476 for (it = edges_list.begin(); it != edges_list.end(); ++it) {
2477 vpDisplay::displayPoint(I, *it, color);
2478 }
2479 }
2480
2481 /*!
2482 Writes the dot center of gravity coordinates in the frame (i,j) (For more
2483 details about the orientation of the frame see the vpImagePoint
2484 documentation) to the stream \e os, and returns a reference to the stream.
2485 */
operator <<(std::ostream & os,vpDot2 & d)2486 VISP_EXPORT std::ostream &operator<<(std::ostream &os, vpDot2 &d) { return (os << "(" << d.getCog() << ")"); }
2487