Visual Servoing Platform  version 3.6.1 under development (2024-12-17)
vpCircleHoughTransform.h
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2024 by Inria. All rights reserved.
4  *
5  * This software is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * See the file LICENSE.txt at the root directory of this source
10  * distribution for additional information about the GNU GPL.
11  *
12  * For using ViSP with software that can not be combined with the GNU
13  * GPL, please contact Inria about acquiring a ViSP Professional
14  * Edition License.
15  *
16  * See https://visp.inria.fr for more information.
17  *
18  * This software was developed at:
19  * Inria Rennes - Bretagne Atlantique
20  * Campus Universitaire de Beaulieu
21  * 35042 Rennes Cedex
22  * France
23  *
24  * If you have questions regarding the use of this file, please contact
25  * Inria at visp@inria.fr
26  *
27  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29  */
30 
31 #ifndef VP_CIRCLE_HOUGH_TRANSFORM_H
32 #define VP_CIRCLE_HOUGH_TRANSFORM_H
33 
34 // System includes
35 #include <utility>
36 #include <vector>
37 
38 // ViSP includes
39 #include <visp3/core/vpConfig.h>
40 #include <visp3/core/vpCannyEdgeDetection.h>
41 #include <visp3/core/vpImage.h>
42 #include <visp3/core/vpImageCircle.h>
43 #include <visp3/core/vpMatrix.h>
44 
45 // 3rd parties inclue
46 #ifdef VISP_HAVE_NLOHMANN_JSON
47 #include VISP_NLOHMANN_JSON(json.hpp)
48 #endif
49 
50 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17)
51 #include <optional>
52 #endif
53 
54 BEGIN_VISP_NAMESPACE
55 
64 class VISP_EXPORT vpCircleHoughTransform
65 {
66 public:
71  {
72  public:
77  : m_filteringAndGradientType(vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
78  , m_gaussianStdev(1.f)
79  , m_lowerCannyThresh(-1.f)
80  , m_upperCannyThresh(-1.f)
81  , m_edgeMapFilteringNbIter(1)
82  , m_cannyBackendType(vpImageFilter::CANNY_OPENCV_BACKEND)
83  , m_lowerCannyThreshRatio(0.6f)
84  , m_upperCannyThreshRatio(0.8f)
85  , m_centerXlimits(std::pair<int, int>(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()))
86  , m_centerYlimits(std::pair<int, int>(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()))
87  , m_minRadius(0.f)
88  , m_maxRadius(1000.f)
89  , m_centerMinThresh(50.f)
90  , m_expectedNbCenters(-1)
91  , m_circleProbaThresh(0.9f)
92  , m_circlePerfectness(0.9f)
93  , m_circleVisibilityRatioThresh(0.1f)
94  , m_recordVotingPoints(false)
95  , m_centerMinDist(15.f)
96  , m_mergingRadiusDiffThresh(1.5f * m_centerMinDist)
97  {
98  const unsigned int gaussianKernelSize_default = 5;
99  const unsigned int gradientFilterKernelSize_default = 3;
100  const unsigned int dilatationKernelSize_default = 3;
101  const unsigned int averagingWindowSize_default = 5;
102 
103  m_gaussianKernelSize = gaussianKernelSize_default;
104  m_gradientFilterKernelSize = gradientFilterKernelSize_default;
105  m_dilatationKernelSize = dilatationKernelSize_default;
106  m_averagingWindowSize = averagingWindowSize_default;
107  }
108 
148  const int &gaussianKernelSize
149  , const float &gaussianStdev
150  , const int &gradientFilterKernelSize
151  , const float &lowerCannyThresh
152  , const float &upperCannyThresh
153  , const int &edgeMapFilterNbIter
154  , const std::pair<int, int> &centerXlimits
155  , const std::pair<int, int> &centerYlimits
156  , const float &minRadius
157  , const float &maxRadius
158  , const int &dilatationKernelSize
159  , const int &averagingWindowSize
160  , const float &centerThresh
161  , const float &circleProbabilityThresh
162  , const float &circlePerfectness
163  , const float &centerMinDistThresh
164  , const float &mergingRadiusDiffThresh
167  , const float &lowerCannyThreshRatio = 0.6f
168  , const float &upperCannyThreshRatio = 0.8f
169  , const int &expectedNbCenters = -1
170  , const bool &recordVotingPoints = false
171  , const float &visibilityRatioThresh = 0.1f
172  )
173  : m_filteringAndGradientType(filteringAndGradientMethod)
174  , m_gaussianKernelSize(gaussianKernelSize)
175  , m_gaussianStdev(gaussianStdev)
176  , m_gradientFilterKernelSize(gradientFilterKernelSize)
177  , m_lowerCannyThresh(lowerCannyThresh)
178  , m_upperCannyThresh(upperCannyThresh)
179  , m_edgeMapFilteringNbIter(edgeMapFilterNbIter)
180  , m_cannyBackendType(backendType)
181  , m_lowerCannyThreshRatio(lowerCannyThreshRatio)
182  , m_upperCannyThreshRatio(upperCannyThreshRatio)
183  , m_centerXlimits(centerXlimits)
184  , m_centerYlimits(centerYlimits)
185  , m_minRadius(std::min<float>(minRadius, maxRadius))
186  , m_maxRadius(std::max<float>(minRadius, maxRadius))
187  , m_dilatationKernelSize(dilatationKernelSize)
188  , m_averagingWindowSize(averagingWindowSize)
189  , m_centerMinThresh(centerThresh)
190  , m_expectedNbCenters(expectedNbCenters)
191  , m_circleProbaThresh(circleProbabilityThresh)
192  , m_circlePerfectness(circlePerfectness)
193  , m_circleVisibilityRatioThresh(visibilityRatioThresh)
194  , m_recordVotingPoints(recordVotingPoints)
195  , m_centerMinDist(centerMinDistThresh)
196  , m_mergingRadiusDiffThresh(mergingRadiusDiffThresh)
197  { }
198 
204  inline int getGaussianKernelSize() const
205  {
206  return m_gaussianKernelSize;
207  }
208 
214  inline float getGaussianStdev() const
215  {
216  return m_gaussianStdev;
217  }
218 
224  inline int getGradientKernelSize() const
225  {
226  return m_gradientFilterKernelSize;
227  }
228 
235  inline float getLowerCannyThreshold() const
236  {
237  return m_lowerCannyThresh;
238  }
239 
246  inline float getUpperCannyThreshold() const
247  {
248  return m_upperCannyThresh;
249  }
250 
256  inline int getEdgeMapFilteringNbIter() const
257  {
258  return m_edgeMapFilteringNbIter;
259  }
260 
266  inline std::pair<int, int> getCenterXLimits() const
267  {
268  return m_centerXlimits;
269  }
270 
276  inline std::pair<int, int> getCenterYLimits() const
277  {
278  return m_centerYlimits;
279  }
280 
286  inline float getMinRadius() const
287  {
288  return m_minRadius;
289  }
290 
296  inline float getMaxRadius() const
297  {
298  return m_maxRadius;
299  }
300 
307  inline int getDilatationKernelSize() const
308  {
309  return m_dilatationKernelSize;
310  }
311 
318  inline int getAveragingWindowSize() const
319  {
320  return m_averagingWindowSize;
321  }
322 
328  inline float getCenterMinThreshold() const
329  {
330  return m_centerMinThresh;
331  }
332 
339  inline int getExpectedNbCenters() const
340  {
341  return m_expectedNbCenters;
342  }
343 
349  inline float getProbabilityThreshold() const
350  {
351  return m_circleProbaThresh;
352  }
353 
359  inline float getVisibilityRatioThreshold() const
360  {
361  return m_circleVisibilityRatioThresh;
362  }
363 
371  inline float getCirclePerfectness() const
372  {
373  return m_circlePerfectness;
374  }
375 
381  inline bool getRecordVotingPoints() const
382  {
383  return m_recordVotingPoints;
384  }
385 
391  inline float getCenterMinDist() const
392  {
393  return m_centerMinDist;
394  }
395 
401  inline float getMergingRadiusDiff() const
402  {
403  return m_mergingRadiusDiffThresh;
404  }
405 
409  std::string toString() const
410  {
411  std::stringstream txt;
412  txt << "Hough Circle Transform Configuration:\n";
413  txt << "\tFiltering + gradient operators = " << vpImageFilter::vpCannyFiltAndGradTypeToStr(m_filteringAndGradientType) << "\n";
414  txt << "\tGaussian filter kernel size = " << m_gaussianKernelSize << "\n";
415  txt << "\tGaussian filter standard deviation = " << m_gaussianStdev << "\n";
416  txt << "\tGradient filter kernel size = " << m_gradientFilterKernelSize << "\n";
417  txt << "\tCanny backend = " << vpImageFilter::vpCannyBackendTypeToString(m_cannyBackendType) << "\n";
418  txt << "\tCanny edge filter thresholds = [" << m_lowerCannyThresh << " ; " << m_upperCannyThresh << "]\n";
419  txt << "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" << m_lowerCannyThreshRatio << " ; " << m_upperCannyThreshRatio << "]\n";
420  txt << "\tEdge map 8-neighbor connectivity filtering number of iterations = " << m_edgeMapFilteringNbIter << "\n";
421  txt << "\tCenter horizontal position limits: min = " << m_centerXlimits.first << "\tmax = " << m_centerXlimits.second << "\n";
422  txt << "\tCenter vertical position limits: min = " << m_centerYlimits.first << "\tmax = " << m_centerYlimits.second << "\n";
423  txt << "\tRadius limits: min = " << m_minRadius << "\tmax = " << m_maxRadius << "\n";
424  txt << "\tKernel size of the dilatation filter = " << m_dilatationKernelSize << "\n";
425  txt << "\tAveraging window size for center detection = " << m_averagingWindowSize << "\n";
426  txt << "\tCenters votes threshold = " << m_centerMinThresh << "\n";
427  txt << "\tExpected number of centers = ";
428  if (m_expectedNbCenters > 0) {
429  txt << m_expectedNbCenters;
430  }
431  else {
432  txt << "no limits";
433  }
434  txt << "\n";
435  txt << "\tCircle probability threshold = " << m_circleProbaThresh << "\n";
436  txt << "\tCircle visibility ratio threshold = " << m_circleVisibilityRatioThresh << "\n";
437  txt << "\tCircle perfectness threshold = " << m_circlePerfectness << "\n";
438  txt << "\tRecord voting points = ";
439  txt << (m_recordVotingPoints ? std::string("true") : std::string("false")) << "\n";
440  txt << "\tCenters minimum distance = " << m_centerMinDist << "\n";
441  txt << "\tRadius difference merging threshold = " << m_mergingRadiusDiffThresh << "\n";
442  return txt.str();
443  }
444 
445  // // Configuration from files
446 #ifdef VISP_HAVE_NLOHMANN_JSON
453  inline static vpCircleHoughTransformParams createFromJSON(const std::string &jsonFile)
454  {
455  using json = nlohmann::json;
456 
457  std::ifstream file(jsonFile);
458  if (!file.good()) {
459  std::stringstream ss;
460  ss << "Problem opening file " << jsonFile << ". Make sure it exists and is readable" << std::endl;
461  throw vpException(vpException::ioError, ss.str());
462  }
463  json j;
464  try {
465  j = json::parse(file);
466  }
467  catch (json::parse_error &e) {
468  std::stringstream msg;
469  msg << "Could not parse JSON file : \n";
470 
471  msg << e.what() << std::endl;
472  msg << "Byte position of error: " << e.byte;
473  throw vpException(vpException::ioError, msg.str());
474  }
475  vpCircleHoughTransformParams params = j; // Call from_json(const json& j, vpDetectorDNN& *this) to read json
476  file.close();
477  return params;
478  }
479 
487  inline void saveConfigurationInJSON(const std::string &jsonPath) const
488  {
489  using json = nlohmann::json;
490  std::ofstream file(jsonPath);
491  const json j = *this;
492  const int indent = 4;
493  file << j.dump(indent);
494  file.close();
495  }
496 
504  friend inline void from_json(const nlohmann::json &j, vpCircleHoughTransformParams &params)
505  {
506  std::string filteringAndGradientName = vpImageFilter::vpCannyFiltAndGradTypeToStr(params.m_filteringAndGradientType);
507  filteringAndGradientName = j.value("filteringAndGradientType", filteringAndGradientName);
508  params.m_filteringAndGradientType = vpImageFilter::vpCannyFiltAndGradTypeFromStr(filteringAndGradientName);
509 
510  params.m_gaussianKernelSize = j.value("gaussianKernelSize", params.m_gaussianKernelSize);
511  const int checkEvenModulo = 2;
512  if ((params.m_gaussianKernelSize % checkEvenModulo) != 1) {
513  throw vpException(vpException::badValue, "Gaussian Kernel size should be odd.");
514  }
515 
516  params.m_gaussianStdev = j.value("gaussianStdev", params.m_gaussianStdev);
517  if (params.m_gaussianStdev <= 0) {
518  throw vpException(vpException::badValue, "Standard deviation should be > 0");
519  }
520 
521  params.m_gradientFilterKernelSize = j.value("gradientFilterKernelSize", params.m_gradientFilterKernelSize);
522  if ((params.m_gradientFilterKernelSize % checkEvenModulo) != 1) {
523  throw vpException(vpException::badValue, "Gradient filter kernel (Sobel or Scharr) size should be odd.");
524  }
525 
526  std::string cannyBackendName = vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType);
527  cannyBackendName = j.value("cannyBackendType", cannyBackendName);
528  params.m_cannyBackendType = vpImageFilter::vpCannyBackendTypeFromString(cannyBackendName);
529  params.m_lowerCannyThresh = j.value("lowerCannyThresh", params.m_lowerCannyThresh);
530  params.m_lowerCannyThreshRatio = j.value("lowerThresholdRatio", params.m_lowerCannyThreshRatio);
531  params.m_upperCannyThresh = j.value("upperCannyThresh", params.m_upperCannyThresh);
532  params.m_upperCannyThreshRatio = j.value("upperThresholdRatio", params.m_upperCannyThreshRatio);
533  params.m_edgeMapFilteringNbIter = j.value("edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter);
534 
535  params.m_centerXlimits = j.value("centerXlimits", params.m_centerXlimits);
536  params.m_centerYlimits = j.value("centerYlimits", params.m_centerYlimits);
537  std::pair<float, float> radiusLimits = j.value("radiusLimits", std::pair<float, float>(params.m_minRadius, params.m_maxRadius));
538  params.m_minRadius = std::min<float>(radiusLimits.first, radiusLimits.second);
539  params.m_maxRadius = std::max<float>(radiusLimits.first, radiusLimits.second);
540 
541  params.m_dilatationKernelSize = j.value("dilatationKernelSize", params.m_dilatationKernelSize);
542  params.m_averagingWindowSize = j.value("averagingWindowSize", params.m_averagingWindowSize);
543  if ((params.m_averagingWindowSize <= 0) || ((params.m_averagingWindowSize % checkEvenModulo) == 0)) {
544  throw vpException(vpException::badValue, "Averaging window size must be positive and odd.");
545  }
546 
547  params.m_centerMinThresh = j.value("centerThresh", params.m_centerMinThresh);
548  if (params.m_centerMinThresh <= 0) {
549  throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive.");
550  }
551 
552  params.m_expectedNbCenters = j.value("expectedNbCenters", params.m_expectedNbCenters);
553 
554 
555  params.m_circleProbaThresh = j.value("circleProbabilityThreshold", params.m_circleProbaThresh);
556  params.m_circleVisibilityRatioThresh = j.value("circleVisibilityRatioThreshold", params.m_circleVisibilityRatioThresh);
557 
558  params.m_circlePerfectness = j.value("circlePerfectnessThreshold", params.m_circlePerfectness);
559 
560  if ((params.m_circlePerfectness <= 0) || (params.m_circlePerfectness > 1)) {
561  throw vpException(vpException::badValue, "Circle perfectness must be in the interval ] 0; 1].");
562  }
563 
564  params.m_recordVotingPoints = j.value("recordVotingPoints", params.m_recordVotingPoints);
565 
566  params.m_centerMinDist = j.value("centerMinDistance", params.m_centerMinDist);
567  if (params.m_centerMinDist <= 0) {
568  throw vpException(vpException::badValue, "Centers minimum distance threshold must be positive.");
569  }
570 
571  params.m_mergingRadiusDiffThresh = j.value("mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh);
572  if (params.m_mergingRadiusDiffThresh <= 0) {
573  throw vpException(vpException::badValue, "Radius difference merging threshold must be positive.");
574  }
575  }
576 
583  friend inline void to_json(nlohmann::json &j, const vpCircleHoughTransformParams &params)
584  {
585  std::pair<float, float> radiusLimits = { params.m_minRadius, params.m_maxRadius };
586 
587  j = nlohmann::json {
588  {"filteringAndGradientType", vpImageFilter::vpCannyFiltAndGradTypeToStr(params.m_filteringAndGradientType)},
589  {"gaussianKernelSize", params.m_gaussianKernelSize},
590  {"gaussianStdev", params.m_gaussianStdev},
591  {"gradientFilterKernelSize", params.m_gradientFilterKernelSize},
592  {"cannyBackendType", vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType)},
593  {"lowerCannyThresh", params.m_lowerCannyThresh},
594  {"lowerThresholdRatio", params.m_lowerCannyThreshRatio},
595  {"upperCannyThresh", params.m_upperCannyThresh},
596  {"upperThresholdRatio", params.m_upperCannyThreshRatio},
597  {"edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter},
598  {"centerXlimits", params.m_centerXlimits},
599  {"centerYlimits", params.m_centerYlimits},
600  {"radiusLimits", radiusLimits},
601  {"dilatationKernelSize", params.m_dilatationKernelSize},
602  {"averagingWindowSize", params.m_averagingWindowSize},
603  {"centerThresh", params.m_centerMinThresh},
604  {"expectedNbCenters", params.m_expectedNbCenters},
605  {"circleProbabilityThreshold", params.m_circleProbaThresh},
606  {"circleVisibilityRatioThreshold", params.m_circleVisibilityRatioThresh},
607  {"circlePerfectnessThreshold", params.m_circlePerfectness},
608  {"recordVotingPoints", params.m_recordVotingPoints},
609  {"centerMinDistance", params.m_centerMinDist},
610  {"mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh} };
611  }
612 #endif
613 
614  private:
615  // // Filtering + gradient operators to use
616  vpImageFilter::vpCannyFilteringAndGradientType m_filteringAndGradientType;
619  // // Gaussian smoothing attributes
620  int m_gaussianKernelSize;
622  float m_gaussianStdev;
624  // // Gradient computation attributes
625  int m_gradientFilterKernelSize;
627  // // Edge detection attributes
628  float m_lowerCannyThresh;
630  float m_upperCannyThresh;
632  int m_edgeMapFilteringNbIter;
633  vpImageFilter::vpCannyBackendType m_cannyBackendType;
634  float m_lowerCannyThreshRatio;
636  float m_upperCannyThreshRatio;
639  // // Center candidates computation attributes
640  std::pair<int, int> m_centerXlimits;
641  std::pair<int, int> m_centerYlimits;
642  float m_minRadius;
643  float m_maxRadius;
644  int m_dilatationKernelSize;
645  int m_averagingWindowSize;
647  float m_centerMinThresh;
648  int m_expectedNbCenters;
651  // // Circle candidates computation attributes
652  float m_circleProbaThresh;
653  float m_circlePerfectness;
656  float m_circleVisibilityRatioThresh;
657  bool m_recordVotingPoints;
659  // // Circle candidates merging attributes
660  float m_centerMinDist;
661  float m_mergingRadiusDiffThresh;
664  };
665 
666 #ifdef VISP_BUILD_DEPRECATED_FUNCTIONS
667  typedef vpCircleHoughTransformParams vpCircleHoughTransformParameters;
668 #endif
669 
670 #ifndef DOXYGEN_SHOULD_SKIP_THIS
674  typedef struct vpCenterVotes
675  {
676  std::pair<float, float> m_position;
677  float m_votes;
678  } vpCenterVotes;
679 #endif
680 
685 
691  VP_EXPLICIT vpCircleHoughTransform(const vpCircleHoughTransformParams &algoParams);
692 
696  virtual ~vpCircleHoughTransform();
697 
700 #ifdef HAVE_OPENCV_CORE
707  virtual std::vector<vpImageCircle> detect(const cv::Mat &cv_I);
708 #endif
709 
717  virtual std::vector<vpImageCircle> detect(const vpImage<vpRGBa> &I);
718 
725  virtual std::vector<vpImageCircle> detect(const vpImage<unsigned char> &I);
726 
737  virtual std::vector<vpImageCircle> detect(const vpImage<unsigned char> &I, const int &nbCircles);
738 
746 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17)
747  void computeVotingMask(const vpImage<unsigned char> &I, const std::vector<vpImageCircle> &detections,
748  std::optional< vpImage<bool> > &mask, std::optional<std::vector<std::vector<std::pair<unsigned int, unsigned int>>>> &opt_votingPoints) const;
749 #else
750  void computeVotingMask(const vpImage<unsigned char> &I, const std::vector<vpImageCircle> &detections,
751  vpImage<bool> **mask, std::vector<std::vector<std::pair<unsigned int, unsigned int> > > **opt_votingPoints) const;
752 #endif
753 
756 #ifdef VISP_HAVE_NLOHMANN_JSON
763  VP_EXPLICIT vpCircleHoughTransform(const std::string &jsonPath);
764 
772  virtual void initFromJSON(const std::string &jsonPath);
773 
781  virtual void saveConfigurationInJSON(const std::string &jsonPath) const;
782 
790  friend inline void from_json(const nlohmann::json &j, vpCircleHoughTransform &detector)
791  {
792  detector.m_algoParams = j;
793  }
794 
801  friend inline void to_json(nlohmann::json &j, const vpCircleHoughTransform &detector)
802  {
803  j = detector.m_algoParams;
804  }
805 #endif
807 
815  void init(const vpCircleHoughTransformParams &algoParams);
816 
823  {
824  m_algoParams.m_filteringAndGradientType = type;
825  m_cannyVisp.setFilteringAndGradientType(type);
826  initGradientFilters();
827  }
828 
836  inline void setGaussianParameters(const int &kernelSize, const float &stdev)
837  {
838  m_algoParams.m_gaussianKernelSize = kernelSize;
839  m_algoParams.m_gaussianStdev = stdev;
840 
841  const unsigned int checkEvenModulo = 2;
842  if ((m_algoParams.m_gaussianKernelSize % checkEvenModulo) != 1) {
843  throw vpException(vpException::badValue, "Gaussian Kernel size should be odd.");
844  }
845 
846  if (m_algoParams.m_gaussianStdev <= 0) {
847  throw vpException(vpException::badValue, "Standard deviation should be > 0");
848  }
849 
850  initGaussianFilters();
851  }
852 
858  inline void setGradientFilterAperture(const unsigned int &apertureSize)
859  {
860  m_algoParams.m_gradientFilterKernelSize = apertureSize;
861 
862  const unsigned int checkEvenModulo = 2;
863  if ((m_algoParams.m_gradientFilterKernelSize % checkEvenModulo) != 1) {
864  throw vpException(vpException::badValue, "Gradient filter (Sobel or Scharr) Kernel size should be odd.");
865  }
866 
867  initGradientFilters();
868  }
869 
876  {
877  m_algoParams.m_cannyBackendType = type;
878  }
879 
889  inline void setCannyThreshold(const float &lowerCannyThreshold, const float &upperCannyThreshold)
890  {
891  m_algoParams.m_lowerCannyThresh = lowerCannyThreshold;
892  m_algoParams.m_upperCannyThresh = upperCannyThreshold;
893  }
894 
904  inline void setCannyThresholdRatio(const float &lowerThreshRatio, const float &upperThreshRatio)
905  {
906  m_algoParams.m_lowerCannyThreshRatio = lowerThreshRatio;
907  m_algoParams.m_upperCannyThreshRatio = upperThreshRatio;
908  m_cannyVisp.setCannyThresholdsRatio(lowerThreshRatio, upperThreshRatio);
909  }
910 
917  inline void setCircleCenterMinDist(const float &center_min_dist)
918  {
919  m_algoParams.m_centerMinDist = center_min_dist;
920 
921  if (m_algoParams.m_centerMinDist <= 0) {
922  throw vpException(vpException::badValue, "Circles center min distance must be positive.");
923  }
924  }
925 
938  void setCircleCenterBoundingBox(const int &center_min_x, const int &center_max_x,
939  const int &center_min_y, const int &center_max_y)
940  {
941  m_algoParams.m_centerXlimits.first = center_min_x;
942  m_algoParams.m_centerXlimits.second = center_max_x;
943  m_algoParams.m_centerYlimits.first = center_min_y;
944  m_algoParams.m_centerYlimits.second = center_max_y;
945  }
946 
951  inline void setCircleMinRadius(const float &circle_min_radius)
952  {
953  m_algoParams.m_minRadius = circle_min_radius;
954  }
955 
960  inline void setCircleMaxRadius(const float &circle_max_radius)
961  {
962  m_algoParams.m_maxRadius = circle_max_radius;
963  }
964 
972  void setCirclePerfectness(const float &circle_perfectness)
973  {
974  m_algoParams.m_circlePerfectness = circle_perfectness;
975  if ((m_algoParams.m_circlePerfectness <= 0) || (m_algoParams.m_circlePerfectness > 1)) {
976  throw vpException(vpException::badValue, "Circle perfectness must be in the interval ] 0; 1].");
977  }
978  }
979 
990  inline void setCenterComputationParameters(const int &dilatationSize, const float &centerThresh,
991  const int &averagingWindowSize = 5, const int expectedNbCenters = -1)
992  {
993  m_algoParams.m_dilatationKernelSize = dilatationSize;
994  m_algoParams.m_centerMinThresh = centerThresh;
995  m_algoParams.m_averagingWindowSize = averagingWindowSize;
996  m_algoParams.m_expectedNbCenters = expectedNbCenters;
997 
998  const int minDilatationKernel = 3;
999  const unsigned int checkEvenModulo = 2;
1000  if (m_algoParams.m_dilatationKernelSize < minDilatationKernel) {
1001  throw vpException(vpException::badValue, "Dilatation kernel size for center detection must be greater or equal to 3.");
1002  }
1003  else if ((m_algoParams.m_dilatationKernelSize % checkEvenModulo) == 0) {
1004  throw vpException(vpException::badValue, "Dilatation kernel size for center detection must be odd.");
1005  }
1006 
1007  if (m_algoParams.m_centerMinThresh <= 0.f) {
1008  throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive.");
1009  }
1010 
1011  if ((m_algoParams.m_averagingWindowSize <= 0) || ((m_algoParams.m_averagingWindowSize % checkEvenModulo) == 0)) {
1012  throw vpException(vpException::badValue, "Averaging window size must be positive and odd.");
1013  }
1014  }
1015 
1021  inline void setRadiusRatioThreshold(const float &radiusRatioThresh)
1022  {
1023  m_algoParams.m_circleProbaThresh = radiusRatioThresh;
1024 
1025  if (m_algoParams.m_circleProbaThresh <= 0) {
1026  throw vpException(vpException::badValue, "Radius ratio threshold must be > 0.");
1027  }
1028  }
1029 
1036  inline void setRadiusMergingThresholds(const float &radiusDifferenceThresh)
1037  {
1038  m_algoParams.m_mergingRadiusDiffThresh = radiusDifferenceThresh;
1039 
1040  if (m_algoParams.m_mergingRadiusDiffThresh <= 0) {
1041  throw vpException(vpException::badValue, "Radius difference merging threshold must be positive.");
1042  }
1043  }
1044 
1051  inline void setMask(const vpImage<bool> &mask)
1052  {
1053  mp_mask = &mask;
1054  }
1055 
1063  inline void setMask(const vpImage<bool> *mask)
1064  {
1065  mp_mask = mask;
1066  }
1067 
1074  inline void setRecordVotingPoints(const bool &record)
1075  {
1076  m_algoParams.m_recordVotingPoints = record;
1077  }
1079 
1087  inline std::vector<std::pair<float, float> > getCenterCandidatesList() const
1088  {
1089  return m_centerCandidatesList;
1090  }
1091 
1097  inline std::vector<int> getCenterCandidatesVotes() const
1098  {
1099  return m_centerVotes;
1100  }
1101 
1108  inline std::vector<vpImageCircle> getCircleCandidates() const
1109  {
1110  return m_circleCandidates;
1111  }
1112 
1118  inline std::vector<float> getCircleCandidatesProbabilities() const
1119  {
1120  return m_circleCandidatesProbabilities;
1121  }
1122 
1128  inline std::vector<unsigned int> getCircleCandidatesVotes() const
1129  {
1130  return m_circleCandidatesVotes;
1131  }
1132 
1139  {
1140  return m_dIx;
1141  }
1142 
1149  {
1150  return m_dIy;
1151  }
1152 
1159  {
1160  return m_edgeMap;
1161  }
1162 
1167  inline float getCannyThreshold() const
1168  {
1169  return m_algoParams.m_upperCannyThresh;
1170  }
1171 
1175  inline float getCircleCenterMinDist() const
1176  {
1177  return m_algoParams.m_centerMinDist;
1178  }
1179 
1183  inline float getCircleMinRadius() const
1184  {
1185  return m_algoParams.m_minRadius;
1186  }
1187 
1191  inline float getCircleMaxRadius() const
1192  {
1193  return m_algoParams.m_maxRadius;
1194  }
1195 
1199  inline std::vector<float> getDetectionsProbabilities() const
1200  {
1201  return m_finalCirclesProbabilities;
1202  }
1203 
1207  inline std::vector<unsigned int> getDetectionsVotes() const
1208  {
1209  return m_finalCircleVotes;
1210  }
1211 
1215  inline std::vector<std::vector<std::pair<unsigned int, unsigned int> > > getDetectionsVotingPoints() const
1216  {
1217  if (!m_algoParams.m_recordVotingPoints) {
1218  throw(vpException(vpException::fatalError, "Asking voting points when it was not asked to remember them."));
1219  }
1220  return m_finalCirclesVotingPoints;
1221  }
1222 
1226  inline bool getRecordVotingPoints() const
1227  {
1228  return m_algoParams.getRecordVotingPoints();
1229  }
1231 
1235  std::string toString() const;
1236 
1240  friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpCircleHoughTransform &detector);
1241 
1242  static const unsigned char edgeMapOn;
1243  static const unsigned char edgeMapOff;
1244 
1245 protected:
1246 
1247 #ifndef DOXYGEN_SHOULD_SKIP_THIS
1251  typedef struct vpCentersBarycenter
1252  {
1253  std::pair<float, float> m_position;
1254  float m_totalVotes;
1255  float m_nbElectors;
1256  }vpCentersBarycenter;
1257 #endif
1258 
1263  virtual void initGaussianFilters();
1264 
1268  virtual void initGradientFilters();
1269 
1277  virtual void computeGradients(const vpImage<unsigned char> &I);
1278 
1285  virtual void edgeDetection(const vpImage<unsigned char> &I);
1286 
1290  virtual void filterEdgeMap();
1291 
1297  virtual void computeCenterCandidates();
1298 
1303  virtual void filterCenterCandidates(const std::vector<vpCenterVotes> &peak_positions_votes);
1304 
1316  virtual vpCentersBarycenter mergeSimilarCenters(const unsigned int &idPeak, const unsigned int &nbPeaks, const float &squared_distance_max, const std::vector<vpCenterVotes> &peak_positions_votes, std::vector<bool> &has_been_merged);
1317 
1328  virtual float computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes);
1329 
1338  virtual void computeCircleCandidates();
1339 
1343  virtual void mergeCircleCandidates();
1344 
1356  virtual void mergeCandidates(std::vector<vpImageCircle> &circleCandidates, std::vector<unsigned int> &circleCandidatesVotes,
1357  std::vector<float> &circleCandidatesProba, std::vector<std::vector<std::pair<unsigned int, unsigned int> > > &votingPoints);
1358 
1360  // // Gaussian smoothing attributes
1362 
1363  // // Gradient computation attributes
1370  // // Edge detection attributes
1374  // // Center candidates computation attributes
1375  std::vector<std::pair<unsigned int, unsigned int> > m_edgePointsList;
1376  std::vector<std::pair<float, float> > m_centerCandidatesList;
1377  std::vector<int> m_centerVotes;
1379  // // Circle candidates computation attributes
1380  std::vector<vpImageCircle> m_circleCandidates;
1381  std::vector<float> m_circleCandidatesProbabilities;
1382  std::vector<unsigned int> m_circleCandidatesVotes;
1383  std::vector<std::vector<std::pair<unsigned int, unsigned int> > > m_circleCandidatesVotingPoints;
1385  // // Circle candidates merging attributes
1386  std::vector<vpImageCircle> m_finalCircles;
1387  std::vector<float> m_finalCirclesProbabilities;
1388  std::vector<unsigned int> m_finalCircleVotes;
1389  std::vector<std::vector<std::pair<unsigned int, unsigned int> > > m_finalCirclesVotingPoints;
1390 };
1391 
1392 END_VISP_NAMESPACE
1393 
1394 #endif
Class that implements the Canny's edge detector. It is possible to use a boolean mask to ignore some ...
Class that gather the algorithm parameters.
float getVisibilityRatioThreshold() const
Get the visibility ratio threshold in order to keep a circle candidate.
int getExpectedNbCenters() const
Get the expected number of centers in the image. If the number is negative, all the centers are kept....
int getDilatationKernelSize() const
Get the kernel size of the dilatation that is performed to detect the maximum number of votes for the...
friend void to_json(nlohmann::json &j, const vpCircleHoughTransformParams &params)
Parse a vpCircleHoughTransform into JSON format.
vpCircleHoughTransformParams()
Construct a new vpCircleHoughTransformParams object with default parameters.
static vpCircleHoughTransformParams createFromJSON(const std::string &jsonFile)
Create a new vpCircleHoughTransformParams from a JSON file.
std::pair< int, int > getCenterXLimits() const
Get the minimum and maximum position on the horizontal axis of the center of the circle we want to de...
int getEdgeMapFilteringNbIter() const
Get the number of iterations of 8-neighbor connectivity filtering to apply to the edge map.
void saveConfigurationInJSON(const std::string &jsonPath) const
Save the configuration of the detector in a JSON file described by the path jsonPath....
std::pair< int, int > getCenterYLimits() const
Get the minimum and maximum position on the vertical axis of the center of the circle we want to dete...
float getUpperCannyThreshold() const
Get the upper threshold for the Canny operator. Values lower than this value are rejected....
float getCenterMinDist() const
Get the Maximum distance between two circle candidates centers to consider merging them.
friend void from_json(const nlohmann::json &j, vpCircleHoughTransformParams &params)
Read the detector configuration from JSON. All values are optional and if an argument is not present,...
float getMinRadius() const
Get the minimum radius of the circles we want to detect.
vpCircleHoughTransformParams(const int &gaussianKernelSize, const float &gaussianStdev, const int &gradientFilterKernelSize, const float &lowerCannyThresh, const float &upperCannyThresh, const int &edgeMapFilterNbIter, const std::pair< int, int > &centerXlimits, const std::pair< int, int > &centerYlimits, const float &minRadius, const float &maxRadius, const int &dilatationKernelSize, const int &averagingWindowSize, const float &centerThresh, const float &circleProbabilityThresh, const float &circlePerfectness, const float &centerMinDistThresh, const float &mergingRadiusDiffThresh, const vpImageFilter::vpCannyFilteringAndGradientType &filteringAndGradientMethod=vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING, const vpImageFilter::vpCannyBackendType &backendType=vpImageFilter::CANNY_OPENCV_BACKEND, const float &lowerCannyThreshRatio=0.6f, const float &upperCannyThreshRatio=0.8f, const int &expectedNbCenters=-1, const bool &recordVotingPoints=false, const float &visibilityRatioThresh=0.1f)
Construct a new vpCircleHoughTransformParams object.
int getAveragingWindowSize() const
Get the size of the averaging window around the maximum number of votes to compute the center candida...
int getGradientKernelSize() const
Get the size of the gradient kernel filters used to compute the gradients.
int getGaussianKernelSize() const
Get the size of the Gaussian filter kernel used to smooth the input image.
float getCirclePerfectness() const
Get the threshold for the colinearity between the gradient of a point and the radius it would form wi...
float getLowerCannyThreshold() const
Get the lower threshold for the Canny operator. Values lower than this value are rejected....
float getMaxRadius() const
Get the maximum radius of the circles we want to detect.
float getProbabilityThreshold() const
Get the probability threshold in order to keep a circle candidate.
float getCenterMinThreshold() const
Get the minimum number of votes a point must exceed to be considered as center candidate.
bool getRecordVotingPoints() const
Get the boolean indicating if we have to record the edge-map points having voted for the circles.
float getMergingRadiusDiff() const
Get the Maximum radius difference between two circle candidates to consider merging them.
float getGaussianStdev() const
Get the standard deviation of the Gaussian filter.
Class that permits to detect 2D circles in a image using the gradient-based Circle Hough transform....
void setCannyThresholdRatio(const float &lowerThreshRatio, const float &upperThreshRatio)
Set the Canny thresholds ratio that are used to automatically compute the Canny thresholds in case th...
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > m_circleCandidatesVotingPoints
vpImage< unsigned char > getEdgeMap() const
Get the Edge Map computed thanks to the Canny edge filter.
std::vector< std::pair< float, float > > m_centerCandidatesList
std::vector< unsigned int > getCircleCandidatesVotes() const
Get the votes of the circle candidates.
vpCannyEdgeDetection m_cannyVisp
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > getDetectionsVotingPoints() const
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > m_finalCirclesVotingPoints
std::vector< std::pair< float, float > > getCenterCandidatesList() const
Get the list of Center Candidates, stored as pair <idRow, idCol>
std::vector< float > m_circleCandidatesProbabilities
std::vector< vpImageCircle > m_finalCircles
std::vector< int > getCenterCandidatesVotes() const
Get the number of votes of each Center Candidates.
std::vector< std::pair< unsigned int, unsigned int > > m_edgePointsList
void setCirclePerfectness(const float &circle_perfectness)
Set circles perfectness, which corresponds to the threshold of the colinearity between the gradient o...
std::vector< float > getCircleCandidatesProbabilities() const
Get the probabilities of the Circle Candidates.
void setRecordVotingPoints(const bool &record)
Permits to either activate or deactivate the memorization of the points that voted for the detected c...
friend void to_json(nlohmann::json &j, const vpCircleHoughTransform &detector)
Parse a vpCircleHoughTransform into JSON format.
vpImage< float > getGradientX() const
Get the gradient along the horizontal axis of the image.
void setGradientFilterAperture(const unsigned int &apertureSize)
Set the parameters of the gradient filter (Sobel or Scharr) kernel size filters.
friend void from_json(const nlohmann::json &j, vpCircleHoughTransform &detector)
Read the detector configuration from JSON. All values are optional and if an argument is not present,...
vpImage< unsigned char > m_edgeMap
vpArray2D< float > m_gradientFilterX
std::vector< int > m_centerVotes
static const unsigned char edgeMapOff
vpImage< float > getGradientY() const
Get the gradient along the vertical axis of the image.
void setCannyBackend(const vpImageFilter::vpCannyBackendType &type)
Set the backend to use to perform the Canny edge detection.
void setCenterComputationParameters(const int &dilatationSize, const float &centerThresh, const int &averagingWindowSize=5, const int expectedNbCenters=-1)
Set the parameters of the computation of the circle center candidates.
const vpImage< bool > * mp_mask
std::vector< unsigned int > m_circleCandidatesVotes
std::vector< unsigned int > m_finalCircleVotes
void setCannyThreshold(const float &lowerCannyThreshold, const float &upperCannyThreshold)
vpCircleHoughTransformParams m_algoParams
std::vector< float > m_finalCirclesProbabilities
std::vector< float > getDetectionsProbabilities() const
void setCircleCenterBoundingBox(const int &center_min_x, const int &center_max_x, const int &center_min_y, const int &center_max_y)
void setMask(const vpImage< bool > &mask)
Set the mask that permits to ignore some pixels when performing the circle detection.
void setRadiusRatioThreshold(const float &radiusRatioThresh)
Set the parameters of the computation of the circle radius candidates.
void setRadiusMergingThresholds(const float &radiusDifferenceThresh)
Set the radius merging threshold used during the merging step in order to merge the circles that are ...
std::vector< vpImageCircle > getCircleCandidates() const
Get the Circle Candidates before merging step.
void setCircleMaxRadius(const float &circle_max_radius)
void setCircleMinRadius(const float &circle_min_radius)
static const unsigned char edgeMapOn
void setFilteringAndGradientType(const vpImageFilter::vpCannyFilteringAndGradientType &type)
Permits to choose the filtering + gradient operators to use.
std::vector< unsigned int > getDetectionsVotes() const
void setGaussianParameters(const int &kernelSize, const float &stdev)
Set the parameters of the Gaussian filter, that permits to blur the gradients of the image.
void setCircleCenterMinDist(const float &center_min_dist)
vpArray2D< float > m_gradientFilterY
void setMask(const vpImage< bool > *mask)
Set the mask that permits to ignore some pixels when performing the circle detection.
std::vector< vpImageCircle > m_circleCandidates
error that can be emitted by ViSP classes.
Definition: vpException.h:60
@ ioError
I/O error.
Definition: vpException.h:67
@ badValue
Used to indicate that a value is not in the allowed range.
Definition: vpException.h:73
@ fatalError
Fatal error.
Definition: vpException.h:72
Class that defines a 2D circle in an image.
Definition: vpImageCircle.h:57
Various image filter, convolution, etc...
Definition: vpImageFilter.h:71
static std::string vpCannyBackendTypeToString(const vpCannyBackendType &type)
Cast a vpImageFilter::vpCannyBackendTypeToString into a string, to know its name.
static std::string vpCannyFiltAndGradTypeToStr(const vpCannyFilteringAndGradientType &type)
Cast a vpImageFilter::vpCannyFilteringAndGradientType into a string, to know its name.
vpCannyFilteringAndGradientType
Canny filter and gradient operators to apply on the image before the edge detection stage.
Definition: vpImageFilter.h:90
@ CANNY_GBLUR_SOBEL_FILTERING
Apply Gaussian blur + Sobel operator on the input image.
Definition: vpImageFilter.h:91
vpCannyBackendType
Canny filter backends for the edge detection operations.
Definition: vpImageFilter.h:75
@ CANNY_OPENCV_BACKEND
Use OpenCV.
Definition: vpImageFilter.h:76
static vpCannyFilteringAndGradientType vpCannyFiltAndGradTypeFromStr(const std::string &name)
Cast a string into a vpImageFilter::vpCannyFilteringAndGradientType.
static vpCannyBackendType vpCannyBackendTypeFromString(const std::string &name)
Cast a string into a vpImageFilter::vpCannyBackendTypeToString.