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  *
37  *****************************************************************************/
38 
39 /*
40   \file vpDot2.h
41   \brief This tracker is meant to track some zones on a vpImage.
42 */
43 
44 #ifndef vpDot2_hh
45 #define vpDot2_hh
46 
47 #include <visp3/core/vpColor.h>
48 #include <visp3/core/vpImage.h>
49 #include <visp3/core/vpImagePoint.h>
50 #include <visp3/core/vpPolygon.h>
51 #include <visp3/core/vpRect.h>
52 #include <visp3/core/vpTracker.h>
53 
54 #include <list>
55 #include <vector>
56 
57 /*!
58   \class vpDot2
59 
60   \ingroup module_blob
61 
62   \brief This tracker is meant to track a blob (connex pixels with same
63   gray level) on a vpImage.
64 
65   The underground algorithm is based on a binarisation of the image
66   and then on a contour detection using the Freeman chain coding to
67   determine the blob characteristics (location, moments, size...).
68 
69   The binarisation is done using gray level minimum and maximum values
70   that define the admissible gray levels of the blob. You can specify these
71   levels by setGrayLevelMin() and setGrayLevelMax(). These levels are also
72   set automatically by setGrayLevelPrecision(). The algorithm allows
73   to track white objects on a black background and vice versa.
74 
75   When a blob is found, some tests are done to see if it is valid:
76   - A blob is considered by default as ellipsoid. The found blob could
77   be rejected if the shape is not ellipsoid. To determine if the shape
78   is ellipsoid the algorithm consider an inner and outside ellipse.
79   Sampled points on these two ellipses should have the right gray levels.
80   Along the inner ellipse the sampled points should have gray levels
81   that are in the gray level minimum and maximum bounds, while
82   on the outside ellipse, the gray levels should be out of the gray level
83   bounds. To set the percentage of the sample points which should have the
84   right levels use setEllipsoidBadPointsPercentage(). The distance between the
85   inner ellpsoid and the blob contour, as well the distance between the
86   blob contour and the outside ellipse is fixed by
87   setEllipsoidShapePrecision(). If you want to track a non ellipsoid shape,
88   and turn off this validation test, you have to call
89   setEllipsoidShapePrecision(0).
90   - The width, height and surface of the blob are compared to the
91   corresponding values of the previous blob. If they differ to much
92   the blob could be rejected. To set the admissible distance you can
93   use setSizePrecision().
94 
95   Note that track() and searchDotsInArea() are the most important features
96   of this class.
97 
98   - track() estimate the current position of the dot using its previous
99     position, then try to compute the new parameters of the dot. If everything
100     went ok, tracking succeeds, otherwise we search this dot in a window
101     around the last position of the dot.
102 
103   - searchDotsInArea() enable to find dots similar to this dot in a window. It
104     is used when there was a problem performing basic tracking of the dot, but
105     can also be used to find a certain type of dots in the full image.
106 
107   The following sample code available in
108   tutorial-blob-tracker-live-firewire.cpp shows how to grab images from a
109   firewire camera, track a blob and display the tracking results.
110 
111   \include tutorial-blob-tracker-live-firewire.cpp
112   A line by line explanation of the previous example is provided in
113   \ref tutorial-tracking-blob.
114 
115   This other example available in tutorial-blob-auto-tracker.cpp shows firstly
116   how to detect in the first image all the blobs that match some
117   characteristics in terms of size, area, gray level. Secondly, it shows how
118   to track all the dots that are detected.
119 
120   \include tutorial-blob-auto-tracker.cpp
121   A line by line explanation of this last example is also provided in
122   \ref tutorial-tracking-blob, section \ref tracking_blob_tracking.
123 
124   \sa vpDot
125 */
126 class VISP_EXPORT vpDot2 : public vpTracker
127 {
128 public:
129   vpDot2();
130   explicit vpDot2(const vpImagePoint &ip);
131   vpDot2(const vpDot2 &twinDot);
132   virtual ~vpDot2();
133 
134   static vpMatrix defineDots(vpDot2 dot[], const unsigned int &n, const std::string &dotFile, vpImage<unsigned char> &I,
135                              vpColor col = vpColor::blue, bool trackDot = true);
136 
137   void display(const vpImage<unsigned char> &I, vpColor color = vpColor::red, unsigned int thickness = 1) const;
138 
139   /*!
140     Gets the second order normalized centered moment \f$ n_{ij} \f$
141     as a 3-dim vector containing \f$ n_{20}, n_{11}, n_{02} \f$
142     such as \f$ n_{ij}  = \mu_{ij}/m_{00} \f$
143 
144     \return The 3-dim vector containing \f$ n_{20}, n_{11}, n_{02} \f$.
145 
146     \sa getCog(), getArea()
147   */
get_nij()148   inline vpColVector get_nij() const
149   {
150     vpColVector nij(3);
151     nij[0] = mu20 / m00;
152     nij[1] = mu11 / m00;
153     nij[2] = mu02 / m00;
154 
155     return nij;
156   }
157 
158   double getArea() const;
159   /*!
160 
161     Return the dot bounding box.
162 
163     \sa getWidth(), getHeight()
164 
165   */
getBBox()166   inline vpRect getBBox() const
167   {
168     vpRect bbox;
169 
170     bbox.setRect(this->bbox_u_min, this->bbox_v_min, this->bbox_u_max - this->bbox_u_min + 1,
171                  this->bbox_v_max - this->bbox_v_min + 1);
172 
173     return (bbox);
174   };
175   /*!
176     Return the location of the dot center of gravity.
177 
178     \return The coordinates of the center of gravity.
179   */
getCog()180   inline vpImagePoint getCog() const { return cog; }
181 
182   double getDistance(const vpDot2 &distantDot) const;
183   /*!
184 
185     Return the list of all the image points on the dot
186     border.
187 
188     \param edges_list : The list of all the images points on the dot
189     border. This list is update after a call to track().
190 
191   */
getEdges(std::list<vpImagePoint> & edges_list)192   void getEdges(std::list<vpImagePoint> &edges_list) const { edges_list = this->ip_edges_list; };
193   /*!
194 
195     Return the list of all the image points on the dot
196     border.
197 
198     \return The list of all the images points on the dot
199     border. This list is update after a call to track().
200 
201   */
getEdges()202   std::list<vpImagePoint> getEdges() const { return (this->ip_edges_list); };
203   /*!
204     Get the percentage of sampled points that are considered non conform
205     in terms of the gray level on the inner and the ouside ellipses.
206 
207     \sa setEllipsoidBadPointsPercentage()
208     */
getEllipsoidBadPointsPercentage()209   double getEllipsoidBadPointsPercentage() const { return allowedBadPointsPercentage_; }
210 
211   double getEllipsoidShapePrecision() const;
212   void getFreemanChain(std::list<unsigned int> &freeman_chain) const;
213 
getGamma()214   inline double getGamma() const { return this->gamma; };
215   /*!
216     Return the color level of pixels inside the dot.
217 
218     \sa getGrayLevelMax()
219   */
getGrayLevelMin()220   inline unsigned int getGrayLevelMin() const { return gray_level_min; };
221   /*!
222     Return the color level of pixels inside the dot.
223 
224     \sa getGrayLevelMin()
225   */
getGrayLevelMax()226   inline unsigned int getGrayLevelMax() const { return gray_level_max; };
227   double getGrayLevelPrecision() const;
228 
229   double getHeight() const;
230   double getMaxSizeSearchDistancePrecision() const;
231   /*!
232   \return The mean gray level value of the dot.
233   */
getMeanGrayLevel()234   double getMeanGrayLevel() const { return (this->mean_gray_level); };
235   /*!
236   \return a vpPolygon made from the edges of the dot.
237   */
getPolygon()238   vpPolygon getPolygon() const { return (vpPolygon(ip_edges_list)); };
239   double getSizePrecision() const;
240   double getWidth() const;
241 
242   void initTracking(const vpImage<unsigned char> &I, unsigned int size = 0);
243   void initTracking(const vpImage<unsigned char> &I, const vpImagePoint &ip, unsigned int size = 0);
244   void initTracking(const vpImage<unsigned char> &I, const vpImagePoint &ip, unsigned int gray_lvl_min,
245                     unsigned int gray_lvl_max, unsigned int size = 0);
246 
247   vpDot2 &operator=(const vpDot2 &twinDot);
248   friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, vpDot2 &d);
249 
print(std::ostream & os)250   void print(std::ostream &os) { os << *this << std::endl; }
251   void searchDotsInArea(const vpImage<unsigned char> &I, int area_u, int area_v, unsigned int area_w,
252                         unsigned int area_h, std::list<vpDot2> &niceDots);
253 
254   void searchDotsInArea(const vpImage<unsigned char> &I, std::list<vpDot2> &niceDots);
255 
256   void setArea(const double &area);
257   /*!
258     Initialize the dot coordinates with \e ip.
259   */
setCog(const vpImagePoint & ip)260   inline void setCog(const vpImagePoint &ip) { this->cog = ip; }
261   /*!
262 
263     Activates the dot's moments computation.
264 
265     \param activate true, if you want to compute the moments. If false,
266     moments are not computed.
267 
268     Computed moment are vpDot::m00, vpDot::m10, vpDot::m01, vpDot::m11,
269     vpDot::m20, vpDot::m02 and second order centereed moments vpDot::mu11,
270     vpDot::mu20, vpDot::mu02 computed with respect to the blob centroid.
271 
272     The coordinates of the region's centroid (u, v) can be computed from the
273     moments by \f$u=\frac{m10}{m00}\f$ and \f$v=\frac{m01}{m00}\f$.
274 
275   */
setComputeMoments(bool activate)276   void setComputeMoments(bool activate) { compute_moment = activate; }
277 
278   /*!
279     Set the percentage of sampled points that are considered non conform
280     in terms of the gray level on the inner and the ouside ellipses.
281     Points located on the inner ellipse should have the same gray level
282     than the blob, while points located on the outside ellipse should
283     have a different gray level.
284 
285     \param percentage : Percentage of points sampled with bad gray level
286     on the inner and outside ellipses that are admissible. 0 means
287     that all the points should have a right level, while a value of 1
288     means that all the points can have a bad gray level.
289     */
290   void setEllipsoidBadPointsPercentage(const double &percentage = 0.0)
291   {
292     if (percentage < 0.)
293       allowedBadPointsPercentage_ = 0.;
294     else if (percentage > 1.)
295       allowedBadPointsPercentage_ = 1.;
296     else
297       allowedBadPointsPercentage_ = percentage;
298   }
299 
300   void setEllipsoidShapePrecision(const double &ellipsoidShapePrecision);
301   /*!
302     Activates the display of the border of the dot during the tracking.
303     The default thickness of the overlayed drawings can be modified using
304     setGraphicsThickness().
305 
306     \warning To effectively display the dot graphics a call to
307     vpDisplay::flush() is needed.
308 
309     \param activate If true, the border of the dot will be painted. false to
310     turn off border painting.
311 
312     \sa setGraphicsThickness()
313   */
setGraphics(bool activate)314   void setGraphics(bool activate) { graphics = activate; }
315   /*!
316     Modify the default thickness that is set to 1 of the drawings in overlay
317     when setGraphics() is enabled.
318 
319     \sa setGraphics()
320     */
setGraphicsThickness(unsigned int t)321   void setGraphicsThickness(unsigned int t) { this->thickness = t; };
322   /*!
323 
324   Set the color level of the dot to search a dot in a region of interest. This
325   level will be used to know if a pixel in the image belongs to the dot or
326   not. Only pixels with higher level can belong to the dot.  If the level is
327   lower than the minimum level for a dot, set the level to MIN_IN_LEVEL.
328 
329   \param min : Color level of a dot to search in a region of interest.
330 
331   \sa setGrayLevelMax(), setGrayLevelPrecision()
332 
333   */
setGrayLevelMin(const unsigned int & min)334   inline void setGrayLevelMin(const unsigned int &min)
335   {
336     if (min > 255)
337       this->gray_level_min = 255;
338     else
339       this->gray_level_min = min;
340   };
341 
342   /*!
343 
344   Set the color level of pixels surrounding the dot. This is meant to be used
345   to search a dot in a region of interest.
346 
347   \param max : Intensity level of a dot to search in a region of interest.
348 
349   \sa  setGrayLevelMin(), setGrayLevelPrecision()
350   */
setGrayLevelMax(const unsigned int & max)351   inline void setGrayLevelMax(const unsigned int &max)
352   {
353     if (max > 255)
354       this->gray_level_max = 255;
355     else
356       this->gray_level_max = max;
357   };
358   void setGrayLevelPrecision(const double &grayLevelPrecision);
359   void setHeight(const double &height);
360   void setMaxSizeSearchDistancePrecision(const double &maxSizeSearchDistancePrecision);
361   void setSizePrecision(const double &sizePrecision);
362   void setWidth(const double &width);
363 
364   void track(const vpImage<unsigned char> &I, bool canMakeTheWindowGrow = true);
365   void track(const vpImage<unsigned char> &I, vpImagePoint &cog, bool canMakeTheWindowGrow = true);
366 
367   static void trackAndDisplay(vpDot2 dot[], const unsigned int &n, vpImage<unsigned char> &I,
368                               std::vector<vpImagePoint> &cogs, vpImagePoint *cogStar = NULL);
369 
370 #ifdef VISP_BUILD_DEPRECATED_FUNCTIONS
371 public:
372 #else
373 private:
374 #endif
375   double m00;  /*!< Considering the general distribution moments for \f$ N \f$
376      points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N}
377      u_h^i v_h^j \f$, \f$ m_{00} \f$ is a zero order moment obtained
378      with \f$i = j = 0 \f$. This moment corresponds to the dot
379      surface.
380 
381      \sa setComputeMoments()
382          */
383   double m10;  /*!< Considering the general distribution moments for \f$ N \f$
384      points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N}
385      u_h^i v_h^j \f$, \f$ m_{10} \f$ is a first order moment
386      obtained with \f$i = 1 \f$ and \f$ j = 0 \f$. \f$ m_{10} \f$
387      corresponds to the inertia first order moment along the v axis.
388 
389      \sa setComputeMoments()
390          */
391   double m01;  /*!< Considering the general distribution moments for \f$ N \f$
392      points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N}
393      u_h^i v_h^j \f$, \f$ m_{01} \f$ is a first order moment
394      obtained with \f$i = 0 \f$ and \f$ j = 1 \f$. \f$ m_{01} \f$
395      corresponds to the inertia first order moment along the u axis.
396 
397      \sa setComputeMoments()
398          */
399   double m11;  /*!< Considering the general distribution moments for \f$ N \f$
400      points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N}
401      u_h^i v_h^j \f$, \f$ m_{11} \f$ is a first order moment
402      obtained with \f$i = 1 \f$ and \f$ j = 1 \f$.
403 
404      \sa setComputeMoments()
405          */
406   double m20;  /*!< Considering the general distribution moments for \f$ N \f$
407      points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N}
408      u_h^i v_h^j \f$, \f$ m_{20} \f$ is a second order moment
409      obtained with \f$i = 2 \f$ and \f$ j = 0 \f$. \f$ m_{20} \f$
410      corresponds to the inertia second order moment along the v
411      axis.
412 
413      \sa setComputeMoments()
414          */
415   double m02;  /*!< Considering the general distribution moments for \f$ N \f$
416      points defined by the relation \f$ m_{ij} = \sum_{h=0}^{N}
417      u_h^i v_h^j \f$, \f$ m_{02} \f$ is a second order moment
418      obtained with \f$i = 0 \f$ and \f$ j = 2 \f$. \f$ m_{02} \f$
419      corresponds to the inertia second order moment along the u
420      axis.
421 
422      \sa setComputeMoments()
423          */
424   double mu11; /*!< \f$ \mu_{11} \f$ is a second order centered moment defined
425      by: \f$ \mu_{11} = m_{11} - \frac{m_{10}}{m_{00}}m_{01} \f$
426 
427      \sa setComputeMoments()
428          */
429   double mu20; /*!< \f$ \mu_{20} \f$ is a second order centered moment defined
430      by: \f$ \mu_{20} = m_{20} - \frac{m_{10}}{m_{00}}m_{10} \f$
431 
432      \sa setComputeMoments()
433          */
434   double mu02; /*!< \f$ \mu_{02} \f$ is a second order centered moments defined
435      by: \f$ \mu_{02} = m_{02} - \frac{m_{01}}{m_{00}}m_{01} \f$
436 
437      \sa setComputeMoments()
438          */
439 
440 private:
441   virtual bool isValid(const vpImage<unsigned char> &I, const vpDot2 &wantedDot);
442 
443   virtual bool hasGoodLevel(const vpImage<unsigned char> &I, const unsigned int &u, const unsigned int &v) const;
444   virtual bool hasReverseLevel(const vpImage<unsigned char> &I, const unsigned int &u, const unsigned int &v) const;
445 
446   virtual vpDot2 *getInstance();
447 
448   void init();
449 
450   bool computeParameters(const vpImage<unsigned char> &I, const double &u = -1.0, const double &v = -1.0);
451 
452   bool findFirstBorder(const vpImage<unsigned char> &I, const unsigned int &u, const unsigned int &v,
453                        unsigned int &border_u, unsigned int &border_v);
454   void computeMeanGrayLevel(const vpImage<unsigned char> &I);
455 
456   /*!
457 
458   Get the starting point on a dot border. The dot border is
459   computed from this point.
460 
461   \sa getFirstBorder_v()
462 
463   */
getFirstBorder_u()464   unsigned int getFirstBorder_u() const { return this->firstBorder_u; }
465   /*!
466 
467   Get the starting point on a dot border. The dot border is
468   computed from this point.
469 
470   \sa getFirstBorder_u()
471 
472   */
getFirstBorder_v()473   unsigned int getFirstBorder_v() const { return this->firstBorder_v; }
474 
475   bool computeFreemanChainElement(const vpImage<unsigned char> &I, const unsigned int &u, const unsigned int &v,
476                                   unsigned int &element);
477   void computeFreemanParameters(const int &u_p, const int &v_p, unsigned int &element, int &du, int &dv, float &dS,
478                                 float &dMu, float &dMv, float &dMuv, float &dMu2, float &dMv2);
479   void updateFreemanPosition(unsigned int &u, unsigned int &v, const unsigned int &dir);
480 
481   bool isInImage(const vpImage<unsigned char> &I) const;
482   bool isInImage(const vpImage<unsigned char> &I, const vpImagePoint &ip) const;
483 
484   bool isInArea(const unsigned int &u, const unsigned int &v) const;
485 
486   void getGridSize(unsigned int &gridWidth, unsigned int &gridHeight);
487   void setArea(const vpImage<unsigned char> &I, int u, int v, unsigned int w, unsigned int h);
488   void setArea(const vpImage<unsigned char> &I);
489   void setArea(const vpRect &a);
490 
491   unsigned char getMeanGrayLevel(vpImage<unsigned char> &I) const;
492   //! coordinates (float) of the point center of gravity
493   vpImagePoint cog;
494 
495   double width;
496   double height;
497   double surface;
498   unsigned int gray_level_min; // minumum gray level for the dot.
499                                // pixel with lower level don't belong
500                                // to this dot.
501 
502   unsigned int gray_level_max; // maximum gray level for the dot.
503                                // pixel with higher level don't belong
504                                // to this dot.
505   double mean_gray_level;      // Mean gray level of the dot
506   double grayLevelPrecision;
507   double gamma;
508   double sizePrecision;
509   double ellipsoidShapePrecision;
510   double maxSizeSearchDistancePrecision;
511   double allowedBadPointsPercentage_;
512   // Area where the dot is to search
513   vpRect area;
514 
515   // other
516   std::list<unsigned int> direction_list;
517   std::list<vpImagePoint> ip_edges_list;
518 
519   // flag
520   bool compute_moment; // true moment are computed
521   bool graphics;       // true for graphic overlay display
522 
523   unsigned int thickness; // Graphics thickness
524 
525   // Bounding box
526   int bbox_u_min, bbox_u_max, bbox_v_min, bbox_v_max;
527 
528   // The first point coodinate on the dot border
529   unsigned int firstBorder_u;
530   unsigned int firstBorder_v;
531 
532   // Static funtions
533 public:
534   static void display(const vpImage<unsigned char> &I, const vpImagePoint &cog,
535                       const std::list<vpImagePoint> &edges_list, vpColor color = vpColor::red,
536                       unsigned int thickness = 1);
537   static void display(const vpImage<vpRGBa> &I, const vpImagePoint &cog, const std::list<vpImagePoint> &edges_list,
538                       vpColor color = vpColor::red, unsigned int thickness = 1);
539 };
540 
541 #endif
542