Visual Servoing Platform  version 3.6.1 under development (2024-05-02)
vpCircleHoughTransform.h
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2023 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 _vpCircleHoughTransform_h_
32 #define _vpCircleHoughTransform_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 <nlohmann/json.hpp>
48 #endif
49 
50 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17)
51 #include <optional>
52 #endif
53 
62 class VISP_EXPORT vpCircleHoughTransform
63 {
64 public:
69  {
70  private:
71  // // Filtering + gradient operators to use
72  vpImageFilter::vpCannyFilteringAndGradientType m_filteringAndGradientType;
75  // // Gaussian smoothing attributes
76  int m_gaussianKernelSize;
78  float m_gaussianStdev;
80  // // Gradient computation attributes
81  int m_gradientFilterKernelSize;
83  // // Edge detection attributes
84  float m_lowerCannyThresh;
86  float m_upperCannyThresh;
88  int m_edgeMapFilteringNbIter;
89  vpImageFilter::vpCannyBackendType m_cannyBackendType;
90  float m_lowerCannyThreshRatio;
92  float m_upperCannyThreshRatio;
95  // // Center candidates computation attributes
96  std::pair<int, int> m_centerXlimits;
97  std::pair<int, int> m_centerYlimits;
98  float m_minRadius;
99  float m_maxRadius;
100  int m_dilatationKernelSize;
101  int m_averagingWindowSize;
103  float m_centerMinThresh;
104  int m_expectedNbCenters;
107  // // Circle candidates computation attributes
108  float m_circleProbaThresh;
109  float m_circlePerfectness;
112  float m_circleVisibilityRatioThresh;
113  bool m_recordVotingPoints;
115  // // Circle candidates merging attributes
116  float m_centerMinDist;
117  float m_mergingRadiusDiffThresh;
120  public:
125  : m_filteringAndGradientType(vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
126  , m_gaussianKernelSize(5)
127  , m_gaussianStdev(1.f)
128  , m_gradientFilterKernelSize(3)
129  , m_lowerCannyThresh(-1.f)
130  , m_upperCannyThresh(-1.f)
131  , m_edgeMapFilteringNbIter(1)
132  , m_cannyBackendType(vpImageFilter::CANNY_OPENCV_BACKEND)
133  , m_lowerCannyThreshRatio(0.6f)
134  , m_upperCannyThreshRatio(0.8f)
135  , m_centerXlimits(std::pair<int, int>(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()))
136  , m_centerYlimits(std::pair<int, int>(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()))
137  , m_minRadius(0.f)
138  , m_maxRadius(1000.f)
139  , m_dilatationKernelSize(3)
140  , m_averagingWindowSize(5)
141  , m_centerMinThresh(50.f)
142  , m_expectedNbCenters(-1)
143  , m_circleProbaThresh(0.9f)
144  , m_circlePerfectness(0.9f)
145  , m_circleVisibilityRatioThresh(0.1f)
146  , m_recordVotingPoints(false)
147  , m_centerMinDist(15.f)
148  , m_mergingRadiusDiffThresh(1.5f * m_centerMinDist)
149  {
150 
151  }
152 
192  const int &gaussianKernelSize
193  , const float &gaussianStdev
194  , const int &gradientFilterKernelSize
195  , const float &lowerCannyThresh
196  , const float &upperCannyThresh
197  , const int &edgeMapFilterNbIter
198  , const std::pair<int, int> &centerXlimits
199  , const std::pair<int, int> &centerYlimits
200  , const float &minRadius
201  , const float &maxRadius
202  , const int &dilatationKernelSize
203  , const int &averagingWindowSize
204  , const float &centerThresh
205  , const float &circleProbabilityThresh
206  , const float &circlePerfectness
207  , const float &centerMinDistThresh
208  , const float &mergingRadiusDiffThresh
211  , const float &lowerCannyThreshRatio = 0.6f
212  , const float &upperCannyThreshRatio = 0.8f
213  , const int &expectedNbCenters = -1
214  , const bool &recordVotingPoints = false
215  , const float &visibilityRatioThresh = 0.1f
216  )
217  : m_filteringAndGradientType(filteringAndGradientMethod)
218  , m_gaussianKernelSize(gaussianKernelSize)
219  , m_gaussianStdev(gaussianStdev)
220  , m_gradientFilterKernelSize(gradientFilterKernelSize)
221  , m_lowerCannyThresh(lowerCannyThresh)
222  , m_upperCannyThresh(upperCannyThresh)
223  , m_edgeMapFilteringNbIter(edgeMapFilterNbIter)
224  , m_cannyBackendType(backendType)
225  , m_lowerCannyThreshRatio(lowerCannyThreshRatio)
226  , m_upperCannyThreshRatio(upperCannyThreshRatio)
227  , m_centerXlimits(centerXlimits)
228  , m_centerYlimits(centerYlimits)
229  , m_minRadius(std::min<float>(minRadius, maxRadius))
230  , m_maxRadius(std::max<float>(minRadius, maxRadius))
231  , m_dilatationKernelSize(dilatationKernelSize)
232  , m_averagingWindowSize(averagingWindowSize)
233  , m_centerMinThresh(centerThresh)
234  , m_expectedNbCenters(expectedNbCenters)
235  , m_circleProbaThresh(circleProbabilityThresh)
236  , m_circlePerfectness(circlePerfectness)
237  , m_circleVisibilityRatioThresh(visibilityRatioThresh)
238  , m_recordVotingPoints(recordVotingPoints)
239  , m_centerMinDist(centerMinDistThresh)
240  , m_mergingRadiusDiffThresh(mergingRadiusDiffThresh)
241  { }
242 
248  inline int getGaussianKernelSize() const
249  {
250  return m_gaussianKernelSize;
251  }
252 
258  inline float getGaussianStdev() const
259  {
260  return m_gaussianStdev;
261  }
262 
268  inline int getGradientKernelSize() const
269  {
270  return m_gradientFilterKernelSize;
271  }
272 
279  inline float getLowerCannyThreshold() const
280  {
281  return m_lowerCannyThresh;
282  }
283 
290  inline float getUpperCannyThreshold() const
291  {
292  return m_upperCannyThresh;
293  }
294 
300  inline int getEdgeMapFilteringNbIter() const
301  {
302  return m_edgeMapFilteringNbIter;
303  }
304 
310  inline std::pair<int, int> getCenterXLimits() const
311  {
312  return m_centerXlimits;
313  }
314 
320  inline std::pair<int, int> getCenterYLimits() const
321  {
322  return m_centerYlimits;
323  }
324 
330  inline float getMinRadius() const
331  {
332  return m_minRadius;
333  }
334 
340  inline float getMaxRadius() const
341  {
342  return m_maxRadius;
343  }
344 
351  inline int getDilatationKernelSize() const
352  {
353  return m_dilatationKernelSize;
354  }
355 
362  inline int getAveragingWindowSize() const
363  {
364  return m_averagingWindowSize;
365  }
366 
372  inline float getCenterMinThreshold() const
373  {
374  return m_centerMinThresh;
375  }
376 
383  inline int getExpectedNbCenters() const
384  {
385  return m_expectedNbCenters;
386  }
387 
393  inline float getProbabilityThreshold() const
394  {
395  return m_circleProbaThresh;
396  }
397 
403  inline float getVisibilityRatioThreshold() const
404  {
405  return m_circleVisibilityRatioThresh;
406  }
407 
415  inline float getCirclePerfectness() const
416  {
417  return m_circlePerfectness;
418  }
419 
425  inline bool getRecordVotingPoints() const
426  {
427  return m_recordVotingPoints;
428  }
429 
435  inline float getCenterMinDist() const
436  {
437  return m_centerMinDist;
438  }
439 
445  inline float getMergingRadiusDiff() const
446  {
447  return m_mergingRadiusDiffThresh;
448  }
449 
453  std::string toString() const
454  {
455  std::stringstream txt;
456  txt << "Hough Circle Transform Configuration:\n";
457  txt << "\tFiltering + gradient operators = " << vpImageFilter::vpCannyFilteringAndGradientTypeToString(m_filteringAndGradientType) << "\n";
458  txt << "\tGaussian filter kernel size = " << m_gaussianKernelSize << "\n";
459  txt << "\tGaussian filter standard deviation = " << m_gaussianStdev << "\n";
460  txt << "\tGradient filter kernel size = " << m_gradientFilterKernelSize << "\n";
461  txt << "\tCanny backend = " << vpImageFilter::vpCannyBackendTypeToString(m_cannyBackendType) << "\n";
462  txt << "\tCanny edge filter thresholds = [" << m_lowerCannyThresh << " ; " << m_upperCannyThresh << "]\n";
463  txt << "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" << m_lowerCannyThreshRatio << " ; " << m_upperCannyThreshRatio << "]\n";
464  txt << "\tEdge map 8-neighbor connectivity filtering number of iterations = " << m_edgeMapFilteringNbIter << "\n";
465  txt << "\tCenter horizontal position limits: min = " << m_centerXlimits.first << "\tmax = " << m_centerXlimits.second << "\n";
466  txt << "\tCenter vertical position limits: min = " << m_centerYlimits.first << "\tmax = " << m_centerYlimits.second << "\n";
467  txt << "\tRadius limits: min = " << m_minRadius << "\tmax = " << m_maxRadius << "\n";
468  txt << "\tKernel size of the dilatation filter = " << m_dilatationKernelSize << "\n";
469  txt << "\tAveraging window size for center detection = " << m_averagingWindowSize << "\n";
470  txt << "\tCenters votes threshold = " << m_centerMinThresh << "\n";
471  txt << "\tExpected number of centers = ";
472  if (m_expectedNbCenters > 0) {
473  txt << m_expectedNbCenters;
474  }
475  else {
476  txt << "no limits";
477  }
478  txt << "\n";
479  txt << "\tCircle probability threshold = " << m_circleProbaThresh << "\n";
480  txt << "\tCircle visibility ratio threshold = " << m_circleVisibilityRatioThresh << "\n";
481  txt << "\tCircle perfectness threshold = " << m_circlePerfectness << "\n";
482  txt << "\tRecord voting points = " + (m_recordVotingPoints ? std::string("true") : std::string("false")) << "\n";
483  txt << "\tCenters minimum distance = " << m_centerMinDist << "\n";
484  txt << "\tRadius difference merging threshold = " << m_mergingRadiusDiffThresh << "\n";
485  return txt.str();
486  }
487 
488  // // Configuration from files
489 #ifdef VISP_HAVE_NLOHMANN_JSON
496  inline static vpCircleHoughTransformParameters createFromJSON(const std::string &jsonFile)
497  {
498  using json = nlohmann::json;
499 
500  std::ifstream file(jsonFile);
501  if (!file.good()) {
502  std::stringstream ss;
503  ss << "Problem opening file " << jsonFile << ". Make sure it exists and is readable" << std::endl;
504  throw vpException(vpException::ioError, ss.str());
505  }
506  json j;
507  try {
508  j = json::parse(file);
509  }
510  catch (json::parse_error &e) {
511  std::stringstream msg;
512  msg << "Could not parse JSON file : \n";
513 
514  msg << e.what() << std::endl;
515  msg << "Byte position of error: " << e.byte;
516  throw vpException(vpException::ioError, msg.str());
517  }
518  vpCircleHoughTransformParameters params = j; // Call from_json(const json& j, vpDetectorDNN& *this) to read json
519  file.close();
520  return params;
521  }
522 
530  inline void saveConfigurationInJSON(const std::string &jsonPath) const
531  {
532  using json = nlohmann::json;
533  std::ofstream file(jsonPath);
534  const json j = *this;
535  file << j.dump(4);
536  file.close();
537  }
538 
546  friend inline void from_json(const nlohmann::json &j, vpCircleHoughTransformParameters &params)
547  {
548  std::string filteringAndGradientName = vpImageFilter::vpCannyFilteringAndGradientTypeToString(params.m_filteringAndGradientType);
549  filteringAndGradientName = j.value("filteringAndGradientType", filteringAndGradientName);
550  params.m_filteringAndGradientType = vpImageFilter::vpCannyFilteringAndGradientTypeFromString(filteringAndGradientName);
551 
552  params.m_gaussianKernelSize = j.value("gaussianKernelSize", params.m_gaussianKernelSize);
553  if ((params.m_gaussianKernelSize % 2) != 1) {
554  throw vpException(vpException::badValue, "Gaussian Kernel size should be odd.");
555  }
556 
557  params.m_gaussianStdev = j.value("gaussianStdev", params.m_gaussianStdev);
558  if (params.m_gaussianStdev <= 0) {
559  throw vpException(vpException::badValue, "Standard deviation should be > 0");
560  }
561 
562  params.m_gradientFilterKernelSize = j.value("gradientFilterKernelSize", params.m_gradientFilterKernelSize);
563  if ((params.m_gradientFilterKernelSize % 2) != 1) {
564  throw vpException(vpException::badValue, "Gradient filter kernel (Sobel or Scharr) size should be odd.");
565  }
566 
567  std::string cannyBackendName = vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType);
568  cannyBackendName = j.value("cannyBackendType", cannyBackendName);
569  params.m_cannyBackendType = vpImageFilter::vpCannyBackendTypeFromString(cannyBackendName);
570  params.m_lowerCannyThresh = j.value("lowerCannyThresh", params.m_lowerCannyThresh);
571  params.m_lowerCannyThreshRatio = j.value("lowerThresholdRatio", params.m_lowerCannyThreshRatio);
572  params.m_upperCannyThresh = j.value("upperCannyThresh", params.m_upperCannyThresh);
573  params.m_upperCannyThreshRatio = j.value("upperThresholdRatio", params.m_upperCannyThreshRatio);
574  params.m_edgeMapFilteringNbIter = j.value("edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter);
575 
576  params.m_centerXlimits = j.value("centerXlimits", params.m_centerXlimits);
577  params.m_centerYlimits = j.value("centerYlimits", params.m_centerYlimits);
578  std::pair<float, float> radiusLimits = j.value("radiusLimits", std::pair<float, float>(params.m_minRadius, params.m_maxRadius));
579  params.m_minRadius = std::min<float>(radiusLimits.first, radiusLimits.second);
580  params.m_maxRadius = std::max<float>(radiusLimits.first, radiusLimits.second);
581 
582  params.m_dilatationKernelSize = j.value("dilatationKernelSize", params.m_dilatationKernelSize);
583  params.m_averagingWindowSize = j.value("averagingWindowSize", params.m_averagingWindowSize);
584  if (params.m_averagingWindowSize <= 0 || (params.m_averagingWindowSize % 2) == 0) {
585  throw vpException(vpException::badValue, "Averaging window size must be positive and odd.");
586  }
587 
588  params.m_centerMinThresh = j.value("centerThresh", params.m_centerMinThresh);
589  if (params.m_centerMinThresh <= 0) {
590  throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive.");
591  }
592 
593  params.m_expectedNbCenters = j.value("expectedNbCenters", params.m_expectedNbCenters);
594 
595 
596  params.m_circleProbaThresh = j.value("circleProbabilityThreshold", params.m_circleProbaThresh);
597  params.m_circleVisibilityRatioThresh = j.value("circleVisibilityRatioThreshold", params.m_circleVisibilityRatioThresh);
598 
599  params.m_circlePerfectness = j.value("circlePerfectnessThreshold", params.m_circlePerfectness);
600 
601  if (params.m_circlePerfectness <= 0 || params.m_circlePerfectness > 1) {
602  throw vpException(vpException::badValue, "Circle perfectness must be in the interval ] 0; 1].");
603  }
604 
605  params.m_recordVotingPoints = j.value("recordVotingPoints", params.m_recordVotingPoints);
606 
607  params.m_centerMinDist = j.value("centerMinDistance", params.m_centerMinDist);
608  if (params.m_centerMinDist <= 0) {
609  throw vpException(vpException::badValue, "Centers minimum distance threshold must be positive.");
610  }
611 
612  params.m_mergingRadiusDiffThresh = j.value("mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh);
613  if (params.m_mergingRadiusDiffThresh <= 0) {
614  throw vpException(vpException::badValue, "Radius difference merging threshold must be positive.");
615  }
616  }
617 
624  friend inline void to_json(nlohmann::json &j, const vpCircleHoughTransformParameters &params)
625  {
626  std::pair<float, float> radiusLimits = { params.m_minRadius, params.m_maxRadius };
627 
628  j = nlohmann::json {
629  {"filteringAndGradientType", vpImageFilter::vpCannyFilteringAndGradientTypeToString(params.m_filteringAndGradientType)},
630  {"gaussianKernelSize", params.m_gaussianKernelSize},
631  {"gaussianStdev", params.m_gaussianStdev},
632  {"gradientFilterKernelSize", params.m_gradientFilterKernelSize},
633  {"cannyBackendType", vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType)},
634  {"lowerCannyThresh", params.m_lowerCannyThresh},
635  {"lowerThresholdRatio", params.m_lowerCannyThreshRatio},
636  {"upperCannyThresh", params.m_upperCannyThresh},
637  {"upperThresholdRatio", params.m_upperCannyThreshRatio},
638  {"edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter},
639  {"centerXlimits", params.m_centerXlimits},
640  {"centerYlimits", params.m_centerYlimits},
641  {"radiusLimits", radiusLimits},
642  {"dilatationKernelSize", params.m_dilatationKernelSize},
643  {"averagingWindowSize", params.m_averagingWindowSize},
644  {"centerThresh", params.m_centerMinThresh},
645  {"expectedNbCenters", params.m_expectedNbCenters},
646  {"circleProbabilityThreshold", params.m_circleProbaThresh},
647  {"circleVisibilityRatioThreshold", params.m_circleVisibilityRatioThresh},
648  {"circlePerfectnessThreshold", params.m_circlePerfectness},
649  {"recordVotingPoints", params.m_recordVotingPoints},
650  {"centerMinDistance", params.m_centerMinDist},
651  {"mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh} };
652  }
653 #endif
654  };
655 
660 
666  vpCircleHoughTransform(const vpCircleHoughTransformParameters &algoParams);
667 
671  virtual ~vpCircleHoughTransform();
672 
675 #ifdef HAVE_OPENCV_CORE
682  virtual std::vector<vpImageCircle> detect(const cv::Mat &cv_I);
683 #endif
684 
692  virtual std::vector<vpImageCircle> detect(const vpImage<vpRGBa> &I);
693 
700  virtual std::vector<vpImageCircle> detect(const vpImage<unsigned char> &I);
701 
712  virtual std::vector<vpImageCircle> detect(const vpImage<unsigned char> &I, const int &nbCircles);
713 
721 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17)
722  void computeVotingMask(const vpImage<unsigned char> &I, const std::vector<vpImageCircle> &detections,
723  std::optional< vpImage<bool> > &mask, std::optional<std::vector<std::vector<std::pair<unsigned int, unsigned int>>>> &opt_votingPoints) const;
724 #else
725  void computeVotingMask(const vpImage<unsigned char> &I, const std::vector<vpImageCircle> &detections,
726  vpImage<bool> **mask, std::vector<std::vector<std::pair<unsigned int, unsigned int> > > **opt_votingPoints) const;
727 #endif
728 
731 #ifdef VISP_HAVE_NLOHMANN_JSON
738  vpCircleHoughTransform(const std::string &jsonPath);
739 
747  virtual void initFromJSON(const std::string &jsonPath);
748 
756  virtual void saveConfigurationInJSON(const std::string &jsonPath) const;
757 
765  friend inline void from_json(const nlohmann::json &j, vpCircleHoughTransform &detector)
766  {
767  detector.m_algoParams = j;
768  }
769 
776  friend inline void to_json(nlohmann::json &j, const vpCircleHoughTransform &detector)
777  {
778  j = detector.m_algoParams;
779  }
780 #endif
782 
790  void init(const vpCircleHoughTransformParameters &algoParams);
791 
798  {
799  m_algoParams.m_filteringAndGradientType = type;
802  }
803 
811  inline void setGaussianParameters(const int &kernelSize, const float &stdev)
812  {
813  m_algoParams.m_gaussianKernelSize = kernelSize;
814  m_algoParams.m_gaussianStdev = stdev;
815 
816  if ((m_algoParams.m_gaussianKernelSize % 2) != 1) {
817  throw vpException(vpException::badValue, "Gaussian Kernel size should be odd.");
818  }
819 
820  if (m_algoParams.m_gaussianStdev <= 0) {
821  throw vpException(vpException::badValue, "Standard deviation should be > 0");
822  }
823 
825  }
826 
832  inline void setGradientFilterAperture(const unsigned int &apertureSize)
833  {
834  m_algoParams.m_gradientFilterKernelSize = apertureSize;
835 
836  if ((m_algoParams.m_gradientFilterKernelSize % 2) != 1) {
837  throw vpException(vpException::badValue, "Gradient filter (Sobel or Scharr) Kernel size should be odd.");
838  }
839 
841  }
842 
849  {
850  m_algoParams.m_cannyBackendType = type;
851  }
852 
862  inline void setCannyThreshold(const float &lowerCannyThreshold, const float &upperCannyThreshold)
863  {
864  m_algoParams.m_lowerCannyThresh = lowerCannyThreshold;
865  m_algoParams.m_upperCannyThresh = upperCannyThreshold;
866  }
867 
877  inline void setCannyThresholdRatio(const float &lowerThreshRatio, const float &upperThreshRatio)
878  {
879  m_algoParams.m_lowerCannyThreshRatio = lowerThreshRatio;
880  m_algoParams.m_upperCannyThreshRatio = upperThreshRatio;
881  m_cannyVisp.setCannyThresholdsRatio(lowerThreshRatio, upperThreshRatio);
882  }
883 
890  inline void setCircleCenterMinDist(const float &center_min_dist)
891  {
892  m_algoParams.m_centerMinDist = center_min_dist;
893 
894  if (m_algoParams.m_centerMinDist <= 0) {
895  throw vpException(vpException::badValue, "Circles center min distance must be positive.");
896  }
897  }
898 
911  void setCircleCenterBoundingBox(const int &center_min_x, const int &center_max_x,
912  const int &center_min_y, const int &center_max_y)
913  {
914  m_algoParams.m_centerXlimits.first = center_min_x;
915  m_algoParams.m_centerXlimits.second = center_max_x;
916  m_algoParams.m_centerYlimits.first = center_min_y;
917  m_algoParams.m_centerYlimits.second = center_max_y;
918  }
919 
924  inline void setCircleMinRadius(const float &circle_min_radius)
925  {
926  m_algoParams.m_minRadius = circle_min_radius;
927  }
928 
933  inline void setCircleMaxRadius(const float &circle_max_radius)
934  {
935  m_algoParams.m_maxRadius = circle_max_radius;
936  }
937 
945  void setCirclePerfectness(const float &circle_perfectness)
946  {
947  m_algoParams.m_circlePerfectness = circle_perfectness;
948  if (m_algoParams.m_circlePerfectness <= 0 || m_algoParams.m_circlePerfectness > 1) {
949  throw vpException(vpException::badValue, "Circle perfectness must be in the interval ] 0; 1].");
950  }
951  }
952 
963  inline void setCenterComputationParameters(const int &dilatationSize, const float &centerThresh,
964  const int &averagingWindowSize = 5, const int expectedNbCenters = -1)
965  {
966  m_algoParams.m_dilatationKernelSize = dilatationSize;
967  m_algoParams.m_centerMinThresh = centerThresh;
968  m_algoParams.m_averagingWindowSize = averagingWindowSize;
969  m_algoParams.m_expectedNbCenters = expectedNbCenters;
970 
971  if (m_algoParams.m_dilatationKernelSize < 3) {
972  throw vpException(vpException::badValue, "Dilatation kernel size for center detection must be greater or equal to 3.");
973  }
974  else if ((m_algoParams.m_dilatationKernelSize % 2) == 0) {
975  throw vpException(vpException::badValue, "Dilatation kernel size for center detection must be odd.");
976  }
977 
978  if (m_algoParams.m_centerMinThresh <= 0.f) {
979  throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive.");
980  }
981 
982  if ((m_algoParams.m_averagingWindowSize <= 0) || ((m_algoParams.m_averagingWindowSize % 2) == 0)) {
983  throw vpException(vpException::badValue, "Averaging window size must be positive and odd.");
984  }
985  }
986 
992  inline void setRadiusRatioThreshold(const float &radiusRatioThresh)
993  {
994  m_algoParams.m_circleProbaThresh = radiusRatioThresh;
995 
996  if (m_algoParams.m_circleProbaThresh <= 0) {
997  throw vpException(vpException::badValue, "Radius ratio threshold must be > 0.");
998  }
999  }
1000 
1007  inline void setRadiusMergingThresholds(const float &radiusDifferenceThresh)
1008  {
1009  m_algoParams.m_mergingRadiusDiffThresh = radiusDifferenceThresh;
1010 
1011  if (m_algoParams.m_mergingRadiusDiffThresh <= 0) {
1012  throw vpException(vpException::badValue, "Radius difference merging threshold must be positive.");
1013  }
1014  }
1015 
1016  inline void setMask(const vpImage<bool> &mask)
1017  {
1018  mp_mask = &mask;
1019  }
1021 
1029  inline std::vector<std::pair<float, float> > getCenterCandidatesList() const
1030  {
1031  return m_centerCandidatesList;
1032  }
1033 
1039  inline std::vector<int> getCenterCandidatesVotes() const
1040  {
1041  return m_centerVotes;
1042  }
1043 
1050  inline std::vector<vpImageCircle> getCircleCandidates() const
1051  {
1052  return m_circleCandidates;
1053  }
1054 
1060  inline std::vector<float> getCircleCandidatesProbabilities() const
1061  {
1063  }
1064 
1070  inline std::vector<unsigned int> getCircleCandidatesVotes() const
1071  {
1072  return m_circleCandidatesVotes;
1073  }
1074 
1081  {
1082  return m_dIx;
1083  }
1084 
1091  {
1092  return m_dIy;
1093  }
1094 
1101  {
1102  return m_edgeMap;
1103  }
1104 
1109  inline float getCannyThreshold() const
1110  {
1111  return m_algoParams.m_upperCannyThresh;
1112  }
1113 
1117  inline float getCircleCenterMinDist() const
1118  {
1119  return m_algoParams.m_centerMinDist;
1120  }
1121 
1125  inline float getCircleMinRadius() const
1126  {
1127  return m_algoParams.m_minRadius;
1128  }
1129 
1133  inline float getCircleMaxRadius() const
1134  {
1135  return m_algoParams.m_maxRadius;
1136  }
1137 
1141  inline std::vector<float> getDetectionsProbabilities() const
1142  {
1144  }
1145 
1149  inline std::vector<unsigned int> getDetectionsVotes() const
1150  {
1151  return m_finalCircleVotes;
1152  }
1154 
1158  std::string toString() const;
1159 
1163  friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpCircleHoughTransform &detector);
1164 
1165  static const unsigned char edgeMapOn;
1166  static const unsigned char edgeMapOff;
1167 
1168 protected:
1173  virtual void initGaussianFilters();
1174 
1178  virtual void initGradientFilters();
1179 
1187  virtual void computeGradients(const vpImage<unsigned char> &I);
1188 
1195  virtual void edgeDetection(const vpImage<unsigned char> &I);
1196 
1200  virtual void filterEdgeMap();
1201 
1207  virtual void computeCenterCandidates();
1208 
1219  virtual float computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes);
1220 
1229  virtual void computeCircleCandidates();
1230 
1234  virtual void mergeCircleCandidates();
1235 
1247  virtual void mergeCandidates(std::vector<vpImageCircle> &circleCandidates, std::vector<unsigned int> &circleCandidatesVotes,
1248  std::vector<float> &circleCandidatesProba, std::vector<std::vector<std::pair<unsigned int, unsigned int> > > &votingPoints);
1249 
1251  // // Gaussian smoothing attributes
1253 
1254  // // Gradient computation attributes
1261  // // Edge detection attributes
1265  // // Center candidates computation attributes
1266  std::vector<std::pair<unsigned int, unsigned int> > m_edgePointsList;
1267  std::vector<std::pair<float, float> > m_centerCandidatesList;
1268  std::vector<int> m_centerVotes;
1270  // // Circle candidates computation attributes
1271  std::vector<vpImageCircle> m_circleCandidates;
1272  std::vector<float> m_circleCandidatesProbabilities;
1273  std::vector<unsigned int> m_circleCandidatesVotes;
1274  std::vector<std::vector<std::pair<unsigned int, unsigned int> > > m_circleCandidatesVotingPoints;
1276  // // Circle candidates merging attributes
1277  std::vector<vpImageCircle> m_finalCircles;
1278  std::vector<float> m_finalCirclesProbabilities;
1279  std::vector<unsigned int> m_finalCircleVotes;
1280  std::vector<std::vector<std::pair<unsigned int, unsigned int> > > m_finalCirclesVotingPoints;
1281 };
1282 #endif
void setFilteringAndGradientType(const vpImageFilter::vpCannyFilteringAndGradientType &type)
Set the Filtering And Gradient operators to apply to the image before the edge detection operation.
void setCannyThresholdsRatio(const float &lowerThreshRatio, const float &upperThreshRatio)
Set the lower and upper Canny Thresholds ratio that are used to compute them automatically....
int getExpectedNbCenters() const
Get the expected number of centers in the image. If the number is negative, all the centers are kept....
int getGradientKernelSize() const
Get the size of the gradient kernel filters used to compute the gradients.
friend void to_json(nlohmann::json &j, const vpCircleHoughTransformParameters &params)
Parse a vpCircleHoughTransform into JSON format.
int getGaussianKernelSize() const
Get the size of the Gaussian filter kernel used to smooth the input image.
float getCenterMinDist() const
Get the Maximum distance between two circle candidates centers to consider merging them.
static vpCircleHoughTransformParameters createFromJSON(const std::string &jsonFile)
Create a new vpCircleHoughTransformParameters from a JSON file.
vpCircleHoughTransformParameters()
Construct a new vpCircleHoughTransformParameters object with default parameters.
float getCirclePerfectness() const
Get the threshold for the colinearity between the gradient of a point and the radius it would form wi...
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...
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...
int getEdgeMapFilteringNbIter() const
Get the number of iterations of 8-neighbor connectivity filtering to apply to the edge map.
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 getProbabilityThreshold() const
Get the probability threshold in order to keep a circle candidate.
float getMinRadius() const
Get the minimum radius of the circles we want to detect.
float getVisibilityRatioThreshold() const
Get the visibility ratio threshold in order to keep a circle candidate.
int getAveragingWindowSize() const
Get the size of the averaging window around the maximum number of votes to compute the center candida...
void saveConfigurationInJSON(const std::string &jsonPath) const
Save the configuration of the detector in a JSON file described by the path jsonPath....
float getUpperCannyThreshold() const
Get the upper threshold for the Canny operator. Values lower than this value are rejected....
float getGaussianStdev() const
Get the standard deviation of the Gaussian filter.
float getLowerCannyThreshold() const
Get the lower threshold for the Canny operator. Values lower than this value are rejected....
int getDilatationKernelSize() const
Get the kernel size of the dilatation that is performed to detect the maximum number of votes for the...
friend void from_json(const nlohmann::json &j, vpCircleHoughTransformParameters &params)
Read the detector configuration from JSON. All values are optional and if an argument is not present,...
vpCircleHoughTransformParameters(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 vpCircleHoughTransformParameters object.
float getCenterMinThreshold() const
Get the minimum number of votes a point must exceed to be considered as center candidate.
float getMaxRadius() const
Get the maximum radius of the circles we want to detect.
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...
vpCircleHoughTransform()
Construct a new vpCircleHoughTransform object with default parameters.
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > m_circleCandidatesVotingPoints
void computeVotingMask(const vpImage< unsigned char > &I, const std::vector< vpImageCircle > &detections, std::optional< vpImage< bool > > &mask, std::optional< std::vector< std::vector< std::pair< unsigned int, unsigned int >>>> &opt_votingPoints) const
Compute the mask containing pixels that voted for the detections.
friend VISP_EXPORT std::ostream & operator<<(std::ostream &os, const vpCircleHoughTransform &detector)
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 > > > 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
virtual ~vpCircleHoughTransform()
Destroy the vp Circle Hough Transform object.
std::vector< int > getCenterCandidatesVotes() const
Get the number of votes of each Center Candidates.
virtual void saveConfigurationInJSON(const std::string &jsonPath) const
Save the configuration of the detector in a JSON file described by the path jsonPath....
std::vector< std::pair< unsigned int, unsigned int > > m_edgePointsList
virtual void initGaussianFilters()
Initialize the Gaussian filters used to blur the image and compute the gradient images.
static const unsigned char edgeMapOn
virtual void computeCenterCandidates()
Determine the image points that are circle center candidates. Increment the center accumulator based ...
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.
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
virtual float computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes)
Compute the probability of circle given the number of pixels voting for it nbVotes....
virtual void edgeDetection(const vpImage< unsigned char > &I)
Perform edge detection based on the computed gradients. Stores the edge points and the edge points co...
vpArray2D< float > m_gradientFilterX
virtual void computeGradients(const vpImage< unsigned char > &I)
Perform Gaussian smoothing on the input image to reduce the noise that would perturbate the edge dete...
virtual std::vector< vpImageCircle > detect(const vpImage< vpRGBa > &I)
Convert the input image in a gray-scale image and then perform Circle Hough Transform to detect the c...
std::vector< int > m_centerVotes
static const unsigned char edgeMapOff
vpImage< float > getGradientY() const
Get the gradient along the vertical axis of the image.
vpCircleHoughTransformParameters m_algoParams
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)
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)
virtual void filterEdgeMap()
Filter the edge map in order to remove isolated edge points.
void setMask(const vpImage< bool > &mask)
void init(const vpCircleHoughTransformParameters &algoParams)
Initialize all the algorithm parameters.
virtual void mergeCircleCandidates()
For each circle candidate CiC_i, check if similar circles have also been detected and if so merges th...
virtual void initGradientFilters()
Initialize the gradient filters used to compute the gradient images.
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 ...
virtual void initFromJSON(const std::string &jsonPath)
Initialize all the algorithm parameters using the JSON file whose path is jsonPath....
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)
virtual void computeCircleCandidates()
For each center candidate CeC_i, do:
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)
virtual void mergeCandidates(std::vector< vpImageCircle > &circleCandidates, std::vector< unsigned int > &circleCandidatesVotes, std::vector< float > &circleCandidatesProba, std::vector< std::vector< std::pair< unsigned int, unsigned int > > > &votingPoints)
For each circle candidate CiC_i do:
vpArray2D< float > m_gradientFilterY
std::vector< vpImageCircle > m_circleCandidates
error that can be emitted by ViSP classes.
Definition: vpException.h:59
@ ioError
I/O error.
Definition: vpException.h:79
@ badValue
Used to indicate that a value is not in the allowed range.
Definition: vpException.h:85
Class that defines a 2D circle in an image.
Definition: vpImageCircle.h:56
Various image filter, convolution, etc...
Definition: vpImageFilter.h:70
static std::string vpCannyBackendTypeToString(const vpCannyBackendType &type)
Cast a vpImageFilter::vpCannyBackendTypeToString into a string, to know its name.
vpCannyFilteringAndGradientType
Canny filter and gradient operators to apply on the image before the edge detection stage.
@ CANNY_GBLUR_SOBEL_FILTERING
Apply Gaussian blur + Sobel operator on the input image.
static vpCannyFilteringAndGradientType vpCannyFilteringAndGradientTypeFromString(const std::string &name)
Cast a string into a vpImageFilter::vpCannyFilteringAndGradientType.
static std::string vpCannyFilteringAndGradientTypeToString(const vpCannyFilteringAndGradientType &type)
Cast a vpImageFilter::vpCannyFilteringAndGradientType into a string, to know its name.
vpCannyBackendType
Canny filter backends for the edge detection operations.
@ CANNY_OPENCV_BACKEND
Use OpenCV.
static vpCannyBackendType vpCannyBackendTypeFromString(const std::string &name)
Cast a string into a vpImageFilter::vpCannyBackendTypeToString.