Visual Servoing Platform  version 3.6.1 under development (2024-05-19)
tutorial-circle-hough.cpp
1 
3 #include <iostream>
4 
5 // ViSP includes
6 #include <visp3/core/vpConfig.h>
7 #include <visp3/core/vpException.h>
8 #include <visp3/core/vpImage.h>
9 #include <visp3/core/vpImageConvert.h>
10 #include <visp3/core/vpImageDraw.h>
11 #include <visp3/core/vpIoTools.h>
12 #include <visp3/core/vpTime.h>
13 #include <visp3/imgproc/vpCircleHoughTransform.h>
14 #include <visp3/imgproc/vpImgproc.h>
15 #include <visp3/io/vpImageIo.h>
16 #include <visp3/io/vpVideoReader.h>
17 
18 #include "drawingHelpers.h"
19 
20 bool run_detection(const vpImage<unsigned char> &I_src, vpCircleHoughTransform &detector, const int &nbCirclesToDetect, const bool &blockingMode, const bool &displayCanny)
21 {
22  double t0 = vpTime::measureTimeMicros();
24  std::vector<vpImageCircle> detectedCircles = detector.detect(I_src, nbCirclesToDetect);
25  std::vector<float> probas = detector.getDetectionsProbabilities();
27  double tF = vpTime::measureTimeMicros();
28  std::cout << "Process time = " << (tF - t0) * 0.001 << "ms" << std::endl << std::flush;
30  vpImageConvert::convert(I_src, I_disp);
31 
32  unsigned int id = 0;
33 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
34  std::vector<vpColor> v_colors = { vpColor::red, vpColor::purple, vpColor::orange, vpColor::yellow, vpColor::blue };
35 #else
36  std::vector<vpColor> v_colors;
37  v_colors.push_back(vpColor::red);
38  v_colors.push_back(vpColor::purple);
39  v_colors.push_back(vpColor::orange);
40  v_colors.push_back(vpColor::yellow);
41  v_colors.push_back(vpColor::blue);
42 #endif
43  unsigned int idColor = 0;
45  const unsigned int nbCircle = static_cast<unsigned int>(detectedCircles.size());
46  for (unsigned int idCircle = 0; idCircle < nbCircle; ++idCircle) {
47  const vpImageCircle &circleCandidate = detectedCircles[idCircle];
48  vpImageDraw::drawCircle(I_disp, circleCandidate, v_colors[idColor], 2);
49  std::cout << "Circle #" << id << ":" << std::endl;
50  std::cout << "\tCenter: (" << circleCandidate.getCenter() << ")" << std::endl;
51  std::cout << "\tRadius: (" << circleCandidate.getRadius() << ")" << std::endl;
52  std::cout << "\tProba: " << probas[id] << std::endl;
53  std::cout << "\tTheoretical arc length: " << circleCandidate.computeArcLengthInRoI(vpRect(0, 0, I_src.getWidth(), I_src.getHeight())) << std::endl;
54  id++;
55  idColor = (idColor + 1) % v_colors.size();
56  }
57 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17)
58  std::optional<vpImage<bool>> opt_mask = std::nullopt;
59  std::optional<std::vector<std::vector<std::pair<unsigned int, unsigned int>>>> opt_votingPoints = std::nullopt;
60 #elif (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
61  vpImage<bool> *opt_mask = nullptr;
62  std::vector<std::vector<std::pair<unsigned int, unsigned int>>> *opt_votingPoints = nullptr;
63  detector.computeVotingMask(I_src, detectedCircles, &opt_mask, &opt_votingPoints); // Get, if available, the voting points
64 #else
65  vpImage<bool> *opt_mask = NULL;
66  std::vector<std::vector<std::pair<unsigned int, unsigned int> > > *opt_votingPoints = NULL;
67  detector.computeVotingMask(I_src, detectedCircles, &opt_mask, &opt_votingPoints); // Get, if available, the voting points
68 #endif
69 
70 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17)
71  if (opt_votingPoints)
72 #elif (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
73  if (opt_votingPoints != nullptr)
74 #else
75  if (opt_votingPoints != NULL)
76 #endif
77  {
78  const unsigned int crossSize = 3;
79  const unsigned int crossThickness = 1;
80  unsigned int nbVotedCircles = static_cast<unsigned int>(opt_votingPoints->size());
81  for (unsigned int idCircle = 0; idCircle < nbVotedCircles; ++idCircle) {
82  // Get the voting points of a detected circle
83  const std::vector<std::pair<unsigned int, unsigned int> > &votingPoints = (*opt_votingPoints)[idCircle];
84  unsigned int nbVotingPoints = static_cast<unsigned int>(votingPoints.size());
85  for (unsigned int idPoint = 0; idPoint < nbVotingPoints; ++idPoint) {
86  // Draw the voting points
87  const std::pair<unsigned int, unsigned int> &pt = votingPoints[idPoint];
88  vpImageDraw::drawCross(I_disp, vpImagePoint(pt.first, pt.second), crossSize, vpColor::red, crossThickness);
89  }
90  }
91  }
92 #if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_17)
93 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
94  if (opt_mask != nullptr)
95 #else
96  if (opt_mask != NULL)
97 #endif
98  {
99  delete opt_mask;
100  }
101 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
102  if (opt_votingPoints != nullptr)
103 #else
104  if (opt_votingPoints != NULL)
105 #endif
106  {
107  delete opt_votingPoints;
108  }
109 #endif
111 
112  if (displayCanny) {
113  vpImage<unsigned char> edgeMap = detector.getEdgeMap();
114  drawingHelpers::display(edgeMap, "Edge map", true);
115  }
116  return drawingHelpers::display(I_disp, "Detection results", blockingMode);
117 }
118 
119 int main(int argc, char **argv)
120 {
121  const std::string def_input("coins2.jpg");
122  const std::string def_jsonFilePath = std::string("");
123  const int def_nbCirclesToDetect = -1;
124  const int def_gaussianKernelSize = 5;
125  const float def_gaussianSigma = 1.f;
126  const int def_sobelKernelSize = 3;
127  const float def_lowerCannyThresh = -1.f;
128  const float def_upperCannyThresh = -1.f;
129  const int def_nbEdgeFilteringIter = 3;
130  const std::pair<int, int> def_centerXlimits = std::pair<int, int>(0, 1920);
131  const std::pair<int, int> def_centerYlimits = std::pair<int, int>(0, 1080);
132  const unsigned int def_minRadius = 34;
133  const unsigned int def_maxRadius = 75;
134  const int def_dilatationKernelSize = 5;
135  const float def_centerThresh = 70.f;
136  const float def_circleProbaThresh = 0.725f;
137  const float def_circlePerfectness = 0.85f;
138  const float def_centerDistanceThresh = 5.f;
139  const float def_radiusDifferenceThresh = 5.f;
140  const int def_averagingWindowSize = 5;
143  const float def_lowerCannyThreshRatio = 0.6f;
144  const float def_upperCannyThreshRatio = 0.9f;
145  const int def_expectedNbCenters = -1;
146  const bool def_recordVotingPoints = false;
147  const float def_visibilityRatioThresh = 0.1f;
148 
149  std::string opt_input(def_input);
150  std::string opt_jsonFilePath = def_jsonFilePath;
151  int opt_nbCirclesToDetect = def_nbCirclesToDetect;
152  int opt_gaussianKernelSize = def_gaussianKernelSize;
153  float opt_gaussianSigma = def_gaussianSigma;
154  int opt_sobelKernelSize = def_sobelKernelSize;
155  float opt_lowerCannyThresh = def_lowerCannyThresh;
156  float opt_upperCannyThresh = def_upperCannyThresh;
157  int opt_nbEdgeFilteringIter = def_nbEdgeFilteringIter;
158  std::pair<int, int> opt_centerXlimits = def_centerXlimits;
159  std::pair<int, int> opt_centerYlimits = def_centerYlimits;
160  unsigned int opt_minRadius = def_minRadius;
161  unsigned int opt_maxRadius = def_maxRadius;
162  int opt_dilatationKerneSize = def_dilatationKernelSize;
163  float opt_centerThresh = def_centerThresh;
164  float opt_circleProbaThresh = def_circleProbaThresh;
165  float opt_circlePerfectness = def_circlePerfectness;
166  float opt_centerDistanceThresh = def_centerDistanceThresh;
167  float opt_radiusDifferenceThresh = def_radiusDifferenceThresh;
168  int opt_averagingWindowSize = def_averagingWindowSize;
169  vpImageFilter::vpCannyFilteringAndGradientType opt_filteringAndGradientType = def_filteringAndGradientType;
170  vpImageFilter::vpCannyBackendType opt_cannyBackendType = def_cannyBackendType;
171  float opt_lowerCannyThreshRatio = def_lowerCannyThreshRatio;
172  float opt_upperCannyThreshRatio = def_upperCannyThreshRatio;
173  int opt_expectedNbCenters = def_expectedNbCenters;
174  bool opt_recordVotingPoints = def_recordVotingPoints;
175  float opt_visibilityRatioThresh = def_visibilityRatioThresh;
176  bool opt_displayCanny = false;
177 
178  for (int i = 1; i < argc; i++) {
179  std::string argName(argv[i]);
180  if (argName == "--input" && i + 1 < argc) {
181  opt_input = std::string(argv[i + 1]);
182  i++;
183  }
184 #ifdef VISP_HAVE_NLOHMANN_JSON
185  else if (argName == "--config" && i + 1 < argc) {
186  opt_jsonFilePath = std::string(argv[i + 1]);
187  i++;
188  }
189 #endif
190  else if (argName == "--nb-circles" && i + 1 < argc) {
191  opt_nbCirclesToDetect = atoi(argv[i + 1]);
192  i++;
193  }
194  else if (argName == "--gaussian-kernel" && i + 1 < argc) {
195  opt_gaussianKernelSize = atoi(argv[i + 1]);
196  i++;
197  }
198  else if (argName == "--gaussian-sigma" && i + 1 < argc) {
199  opt_gaussianSigma = static_cast<float>(atof(argv[i + 1]));
200  i++;
201  }
202  else if (argName == "--gradient-kernel" && i + 1 < argc) {
203  opt_sobelKernelSize = atoi(argv[i + 1]);
204  i++;
205  }
206  else if (argName == "--canny-thresh" && i + 2 < argc) {
207  opt_lowerCannyThresh = static_cast<float>(atof(argv[i + 1]));
208  opt_upperCannyThresh = static_cast<float>(atof(argv[i + 2]));
209  i += 2;
210  }
211  else if (argName == "--edge-filter" && i + 1 < argc) {
212  opt_nbEdgeFilteringIter = atoi(argv[i + 1]);
213  i++;
214  }
215  else if (argName == "--dilatation-kernel-size" && i + 1 < argc) {
216  opt_dilatationKerneSize = atoi(argv[i + 1]);
217  i++;
218  }
219  else if (argName == "--averaging-window-size" && i + 1 < argc) {
220  opt_averagingWindowSize = atoi(argv[i + 1]);
221  i++;
222  }
223  else if (argName == "--radius-limits" && i + 2 < argc) {
224  opt_minRadius = atoi(argv[i + 1]);
225  opt_maxRadius = atoi(argv[i + 2]);
226  i += 2;
227  }
228  else if (argName == "--center-thresh" && i + 1 < argc) {
229  opt_centerThresh = static_cast<float>(atof(argv[i + 1]));
230  i++;
231  }
232  else if (argName == "--center-xlim" && i + 2 < argc) {
233  opt_centerXlimits = std::pair<int, int>(atoi(argv[i + 1]), atoi(argv[i + 2]));
234  i += 2;
235  }
236  else if (argName == "--center-ylim" && i + 2 < argc) {
237  opt_centerYlimits = std::pair<int, int>(atoi(argv[i + 1]), atoi(argv[i + 2]));
238  i += 2;
239  }
240  else if (argName == "--circle-probability-thresh" && i + 1 < argc) {
241  opt_circleProbaThresh = static_cast<float>(atof(argv[i + 1]));
242  i++;
243  }
244  else if (argName == "--circle-perfectness" && i + 1 < argc) {
245  opt_circlePerfectness = static_cast<float>(atof(argv[i + 1]));
246  i++;
247  }
248  else if (argName == "--merging-thresh" && i + 2 < argc) {
249  opt_centerDistanceThresh = static_cast<float>(atof(argv[i + 1]));
250  opt_radiusDifferenceThresh = static_cast<float>(atof(argv[i + 2]));
251  i += 2;
252  }
253  else if (argName == "--filtering-type" && i + 1 < argc) {
254  opt_filteringAndGradientType = vpImageFilter::vpCannyFiltAndGradTypeFromStr(std::string(argv[i+1]));
255  i++;
256  }
257  else if (argName == "--canny-backend" && i + 1 < argc) {
258  opt_cannyBackendType = vpImageFilter::vpCannyBackendTypeFromString(std::string(argv[i+1]));
259  i++;
260  }
261  else if (argName == "--lower-canny-ratio" && i + 1 < argc) {
262  opt_lowerCannyThreshRatio = static_cast<float>(atof(argv[i + 1]));
263  i++;
264  }
265  else if (argName == "--upper-canny-ratio" && i + 1 < argc) {
266  opt_upperCannyThreshRatio = static_cast<float>(atof(argv[i + 1]));
267  i++;
268  }
269  else if (argName == "--expected-nb-centers" && i + 1 < argc) {
270  opt_expectedNbCenters = atoi(argv[i + 1]);
271  i++;
272  }
273  else if (argName == "--visibility-ratio-thresh" && i + 1 < argc) {
274  opt_visibilityRatioThresh = static_cast<float>(atof(argv[i + 1]));
275  i++;
276  }
277  else if (argName == "--record-voting-points") {
278  opt_recordVotingPoints = true;
279  }
280  else if (argName == "--display-edge-map") {
281  opt_displayCanny = true;
282  }
283  else if (argName == "--help" || argName == "-h") {
284  std::cout << "NAME" << std::endl;
285  std::cout << "\t" << argv[0] << " Test program for the home-made Hough Circle Detection algorithm" << std::endl
286  << std::endl;
287  std::cout << "SYNOPSIS" << std::endl;
288  std::cout << "\t" << argv[0]
289  << "\t [--input <path/to/file>]" << std::endl
290 #ifdef VISP_HAVE_NLOHMANN_JSON
291  << "\t [--config <path/to/json/file>] (default: " << (def_jsonFilePath.empty() ? "unused" : def_jsonFilePath) << ")" << std::endl
292 #endif
293  << "\t [--nb-circles <number-circles-to-detect>] (default: " << def_nbCirclesToDetect << ")" << std::endl
294  << "\t [--gaussian-kernel <kernel-size>] (default: " << def_gaussianKernelSize << ")" << std::endl
295  << "\t [--gaussian-sigma <stddev>] (default: " << def_gaussianSigma << ")" << std::endl
296  << "\t [--gradient-kernel <kernel-size>] (default: " << def_sobelKernelSize << ")" << std::endl
297  << "\t [--canny-thresh <lower-canny-thresh upper-canny-thresh>] (default: " << def_lowerCannyThresh << " ; " << def_upperCannyThresh << ")" << std::endl
298  << "\t [--edge-filter <nb-iter>] (default: " << def_nbEdgeFilteringIter << ")" << std::endl
299  << "\t [--radius-limits <radius-min> <radius-max>] (default: min = " << def_minRadius << ", max = " << def_maxRadius << ")" << std::endl
300  << "\t [--dilatation-kernel-size <kernel-size>] (default: " << def_dilatationKernelSize << ")" << std::endl
301  << "\t [--averaging-window-size <size>] (default: " << def_averagingWindowSize << ")" << std::endl
302  << "\t [--center-thresh <center-detection-threshold>] (default: " << def_centerThresh << ")" << std::endl
303  << "\t [--center-xlim <center-horizontal-min center-horizontal-max>] (default: " << def_centerXlimits.first << " , " << def_centerXlimits.second << ")" << std::endl
304  << "\t [--center-ylim <center-vertical-min center-vertical-max>] (default: " << def_centerYlimits.first << " , " << def_centerYlimits.second << ")" << std::endl
305  << "\t [--circle-probability-thresh <probability-threshold>] (default: " << def_circleProbaThresh << ")" << std::endl
306  << "\t [--circle-perfectness <circle-perfectness-threshold>] (default: " << def_circlePerfectness << ")" << std::endl
307  << "\t [--merging-thresh <center-distance-thresh> <radius-difference-thresh>] (default: centers distance threshold = " << def_centerDistanceThresh << ", radius difference threshold = " << def_radiusDifferenceThresh << ")" << std::endl
308  << "\t [--filtering-type <type-name>]"
309  << " (default: " << vpImageFilter::vpCannyFiltAndGradTypeToStr(def_filteringAndGradientType) << ")" << std::endl
310  << "\t [--canny-backend <backend-name>]"
311  << " (default: " << vpImageFilter::vpCannyBackendTypeToString(def_cannyBackendType) << ")" << std::endl
312  << "\t [--lower-canny-ratio <value>]"
313  << " (default: " << def_lowerCannyThreshRatio<< ")" << std::endl
314  << "\t [--upper-canny-ratio <value>]"
315  << " (default: " << def_upperCannyThreshRatio << ")" << std::endl
316  << "\t [--expected-nb-centers <number>]"
317 #if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
318  << " (default: " << (def_expectedNbCenters < 0 ? "no limits" : std::to_string(def_expectedNbCenters)) << ")" << std::endl
319 #else
320  << std::endl
321 #endif
322  << "\t [--visibility-ratio-thresh <ratio ]0; 1[> ]"
323  << " (default: " << def_visibilityRatioThresh << ")" << std::endl
324  << "\t [--record-voting-points]" << std::endl
325  << "\t [--display-edge-map]" << std::endl
326  << "\t [--help, -h]" << std::endl
327  << std::endl;
328 
329  std::cout << "DESCRIPTION" << std::endl
330  << "\t--input" << std::endl
331  << "\t\tPermit to choose the input of the Hough Circle Algorithm." << std::endl
332  << "\t\tIf you want to use a succession of images as video, their name must be in the format ${BASENAME}%d.{jpg, png}." << std::endl
333  << "\t\tDefault: " << def_input << std::endl
334  << std::endl
335 #ifdef VISP_HAVE_NLOHMANN_JSON
336  << "\t--config" << std::endl
337  << "\t\tPermit to configure the Hough Circle Algorithm using a JSON file." << std::endl
338  << "\t\tDefault: " << (def_jsonFilePath.empty() ? "unused" : def_jsonFilePath) << std::endl
339  << std::endl
340 #endif
341  << "\t--nb-circles" << std::endl
342  << "\t\tPermit to choose the number of circles we want to detect in the image" << std::endl
343  << "\t\tThe results will be the circles having the greatest number of votes." << std::endl
344  << "\t\tDefault: " << def_nbCirclesToDetect << std::endl
345  << std::endl
346  << "\t--gaussian-kernel" << std::endl
347  << "\t\tPermit to set the size of the Gaussian filter used to smooth the input image and compute its gradients." << std::endl
348  << "\t\tMust be an odd value." << std::endl
349  << "\t\tDefault: " << def_gaussianKernelSize << std::endl
350  << std::endl
351  << "\t--gaussian-sigma" << std::endl
352  << "\t\tPermit to set the standard deviation of the Gaussian filter." << std::endl
353  << "\t\tMust be a positive value." << std::endl
354  << "\t\tDefault: " << def_gaussianSigma << std::endl
355  << std::endl
356  << "\t--gradient-kernel" << std::endl
357  << "\t\tPermit to set the size of the Gaussian filter used to smooth the input image and compute its gradients." << std::endl
358  << "\t\tMust be an odd value." << std::endl
359  << "\t\tDefault: " << def_gaussianKernelSize << std::endl
360  << std::endl
361  << "\t--canny-thresh" << std::endl
362  << "\t\tPermit to set the lower and upper thresholds of the Canny edge detector." << std::endl
363  << "\t\tIf a value is negative, it will be automatically computed." << std::endl
364  << "\t\tDefault: " << def_lowerCannyThresh << " ; " << def_upperCannyThresh << std::endl
365  << std::endl
366  << "\t--edge-filter" << std::endl
367  << "\t\tPermit to set the number of iteration of 8-neighbor filter iterations of the result of the Canny edge detector." << std::endl
368  << "\t\tIf negative, no filtering is performed." << std::endl
369  << "\t\tDefault: " << def_nbEdgeFilteringIter << std::endl
370  << std::endl
371  << "\t--radius-limits" << std::endl
372  << "\t\tPermit to set the minimum and maximum radii of the circles we are looking for." << std::endl
373  << "\t\tDefault: min = " << def_minRadius << ", max = " << def_maxRadius << std::endl
374  << std::endl
375  << "\t--dilatation-kernel-size" << std::endl
376  << "\t\tPermit to set the size of the kernel of the dilatation operation used to detect the maxima of the centers votes." << std::endl
377  << "\t\tMinimum tolerated value is 1." << std::endl
378  << "\t\tDefault: " << def_dilatationKernelSize << std::endl
379  << std::endl
380  << "\t--averaging-window-size" << std::endl
381  << "\t\tPermit to set the number size of the averaging window used to detect the maxima of the centers votes." << std::endl
382  << "\t\tMust be odd." << std::endl
383  << "\t\tDefault: " << def_averagingWindowSize << std::endl
384  << std::endl
385  << "\t--center-thresh" << std::endl
386  << "\t\tPermit to set the minimum number of votes a point must reach to be considered as a center candidate." << std::endl
387  << "\t\tIf the input is a real image, must be a positive value." << std::endl
388  << "\t\tOtherwise, if the input is a synthetic image and the value is negative, a fine-tuned value will be used." << std::endl
389  << "\t\tDefault: " << def_centerThresh << std::endl
390  << std::endl
391  << "\t--center-xlim" << std::endl
392  << "\t\tPermit to set the minimum and maximum horizontal position to be considered as a center candidate." << std::endl
393  << "\t\tThe search area is limited to [-maxRadius; +image.width + maxRadius]." << std::endl
394  << "\t\tDefault: " << def_centerXlimits.first << " , " << def_centerXlimits.second << std::endl
395  << std::endl
396  << "\t--center-ylim" << std::endl
397  << "\t\tPermit to set the minimum and maximum vertical position to be considered as a center candidate." << std::endl
398  << "\t\tThe search area is limited to [-maxRadius; +image.height + maxRadius]." << std::endl
399  << "\t\tDefault: " << def_centerYlimits.first << " , " << def_centerYlimits.second << std::endl
400  << std::endl
401  << "\t--circle-probability-thresh" << std::endl
402  << "\t\tPermit to to set the minimum probability a circle must reach to be kept." << std::endl
403  << "\t\tDefault: " << def_circleProbaThresh << std::endl
404  << std::endl
405  << "\t--circle-perfectness" << std::endl
406  << "\t\tPermit to set the set the circle perfectness threshold." << std::endl
407  << "\t\tThis parameter is used during the radius candidates computation." << std::endl
408  << "\t\tThe scalar product radius RC_ij . gradient(Ep_j) >= m_circlePerfectness * || RC_ij || * || gradient(Ep_j) || to add a vote for the radius RC_ij." << std::endl
409  << "\t\tDefault: " << def_circlePerfectness << std::endl
410  << std::endl
411  << "\t--merging-thresh" << std::endl
412  << "\t\tPermit to set the thresholds used during the merging stage of the algorithm." << std::endl
413  << "\t\tThe center distance threshold indicates the maximum distance the centers can be in order to be merged." << std::endl
414  << "\t\tThe radius difference threshold indicates the maximum absolute difference between the two circle candidates in order to be merged." << std::endl
415  << "\t\tTwo circle candidates must met these two conditions in order to be merged together." << std::endl
416  << "\t\tDefault: centers distance threshold = " << def_centerDistanceThresh << ", radius difference threshold = " << def_radiusDifferenceThresh << std::endl
417  << std::endl
418  << "\t--filtering-type" << std::endl
419  << "\t\tPermit to choose the gradient filters." << std::endl
420  << "\t\tDefault: " << vpImageFilter::vpCannyFiltAndGradTypeToStr(def_filteringAndGradientType) << ", available: " << vpImageFilter::vpGetCannyFiltAndGradTypes() << std::endl
421  << std::endl
422  << "\t--canny-backend" << std::endl
423  << "\t\tPermit to choose the backend used to compute the edge map." << std::endl
424  << "\t\tDefault: " << vpImageFilter::vpCannyBackendTypeToString(def_cannyBackendType) << ", available: " << vpImageFilter::vpCannyBackendTypeList() << std::endl
425  << std::endl
426  << "\t--lower-canny-ratio" << std::endl
427  << "\t\tPermit to choose the ratio for the lower threshold if automatic thresholding is chosen." << std::endl
428  << "\t\tDefault: " << def_lowerCannyThreshRatio << std::endl
429  << std::endl
430  << "\t--upper-canny-ratio" << std::endl
431  << "\t\tPermit to choose the ratio for the upper threshold if automatic thresholding is chosen." << std::endl
432  << "\t\tDefault: " << def_upperCannyThreshRatio << std::endl
433  << std::endl
434  << "\t--expected-nb-centers" << std::endl
435  << "\t\tPermit to choose the maximum number of centers having more votes than the threshold that are kept." << std::endl
436  << "\t\tA negative value makes that all the centers having more votes than the threshold are kept." << std::endl
437 #if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
438  << "\t\tDefault: " << (def_expectedNbCenters < 0 ? "no limits" : std::to_string(def_expectedNbCenters)) << std::endl
439 #else
440  << std::endl
441 #endif
442  << std::endl
443  << "\t--expected-nb-centers" << std::endl
444  << "\t\tPermit to choose the maximum number of centers having more votes than the threshold that are kept." << std::endl
445  << "\t\tA negative value makes that all the centers having more votes than the threshold are kept." << std::endl
446 #if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
447  << "\t\tDefault: " << (def_expectedNbCenters < 0 ? "no limits" : std::to_string(def_expectedNbCenters)) << std::endl
448 #else
449  << std::endl
450 #endif
451  << std::endl
452  << "\t--record-voting-points" << std::endl
453  << "\t\tPermit to display the edge map used to detect the circles" << std::endl
454  << "\t\tDefault: off" << std::endl
455  << std::endl
456  << "\t--display-edge-map" << std::endl
457  << "\t\tPermit to display the edge map used to detect the circles" << std::endl
458  << "\t\tDefault: off" << std::endl
459  << std::endl;
460  return EXIT_SUCCESS;
461  }
462  }
463 
466  algoParams(opt_gaussianKernelSize
467  , opt_gaussianSigma
468  , opt_sobelKernelSize
469  , opt_lowerCannyThresh
470  , opt_upperCannyThresh
471  , opt_nbEdgeFilteringIter
472  , opt_centerXlimits
473  , opt_centerYlimits
474  , static_cast<float>(opt_minRadius)
475  , static_cast<float>(opt_maxRadius)
476  , opt_dilatationKerneSize
477  , opt_averagingWindowSize
478  , opt_centerThresh
479  , opt_circleProbaThresh
480  , opt_circlePerfectness
481  , opt_centerDistanceThresh
482  , opt_radiusDifferenceThresh
483  , opt_filteringAndGradientType
484  , opt_cannyBackendType
485  , opt_lowerCannyThreshRatio
486  , opt_upperCannyThreshRatio
487  , opt_expectedNbCenters
488  , opt_recordVotingPoints
489  , opt_visibilityRatioThresh
490  );
492 
494  vpCircleHoughTransform detector;
495  if (opt_jsonFilePath.empty()) {
496  std::cout << "Initializing detector from the program arguments [...]" << std::endl;
497  detector.init(algoParams);
498  }
499  else {
500 #ifdef VISP_HAVE_NLOHMANN_JSON
501  std::cout << "Initializing detector from JSON file \"" << opt_jsonFilePath << "\", some of the program arguments will be ignored [...]" << std::endl;
502  detector.initFromJSON(opt_jsonFilePath);
503 #else
504  throw(vpException(vpException::functionNotImplementedError, "You must install nlohmann JSON library to use this feature, see https://visp-doc.inria.fr/doxygen/visp-daily/supported-third-parties.html#soft_tool_json for more information."));
505 #endif
506  }
508  std::cout << detector;
509 
511 
513  if (opt_input.find("%") != std::string::npos) {
514  // The user wants to read a sequence of images from different files
515  bool hasToContinue = true;
516  vpVideoReader g;
517  g.setFileName(opt_input);
518  g.open(I_src);
519  while (!g.end() && hasToContinue) {
520  g.acquire(I_src);
521  hasToContinue = run_detection(I_src, detector, opt_nbCirclesToDetect, false, opt_displayCanny);
522  vpTime::wait(40);
523  }
524  }
526  else {
528  // Check if opt_input exists
529  if (!vpIoTools::checkFilename(opt_input)) {
530  throw(vpException(vpException::ioError, "Input file \"" + opt_input + "\" does not exist !"));
531  }
532  // Read the image and perform detection on it
533  vpImageIo::read(I_src, opt_input);
534  run_detection(I_src, detector, opt_nbCirclesToDetect, true, opt_displayCanny);
536  }
537 
538  return EXIT_SUCCESS;
539 }
Class that permits to detect 2D circles in a image using the gradient-based Circle Hough transform....
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.
vpImage< unsigned char > getEdgeMap() const
Get the Edge Map computed thanks to the Canny edge filter.
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< float > getDetectionsProbabilities() const
void init(const vpCircleHoughTransformParameters &algoParams)
Initialize all the algorithm parameters.
virtual void initFromJSON(const std::string &jsonPath)
Initialize all the algorithm parameters using the JSON file whose path is jsonPath....
static const vpColor red
Definition: vpColor.h:211
static const vpColor orange
Definition: vpColor.h:221
static const vpColor blue
Definition: vpColor.h:217
static const vpColor purple
Definition: vpColor.h:222
static const vpColor yellow
Definition: vpColor.h:219
error that can be emitted by ViSP classes.
Definition: vpException.h:59
@ ioError
I/O error.
Definition: vpException.h:79
@ functionNotImplementedError
Function not implemented.
Definition: vpException.h:78
Class that defines a 2D circle in an image.
Definition: vpImageCircle.h:56
float getRadius() const
vpImagePoint getCenter() const
float computeArcLengthInRoI(const vpRect &roi, const float &roundingTolerance=0.001f) const
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
static void drawCircle(vpImage< unsigned char > &I, const vpImageCircle &circle, unsigned char color, unsigned int thickness=1)
static void drawCross(vpImage< unsigned char > &I, const vpImagePoint &ip, unsigned int size, unsigned char color, unsigned int thickness=1)
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.
@ CANNY_GBLUR_SCHARR_FILTERING
Apply Gaussian blur + Scharr operator on the input image.
vpCannyBackendType
Canny filter backends for the edge detection operations.
@ CANNY_OPENCV_BACKEND
Use OpenCV.
static std::string vpCannyBackendTypeList(const std::string &pref="<", const std::string &sep=" , ", const std::string &suf=">")
Get the list of available vpCannyBackendType.
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.
static std::string vpGetCannyFiltAndGradTypes(const std::string &pref="<", const std::string &sep=" , ", const std::string &suf=">")
Get the list of available vpCannyFilteringAndGradientType.
static void read(vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
Definition: vpImageIo.cpp:143
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
Definition: vpImagePoint.h:82
unsigned int getWidth() const
Definition: vpImage.h:245
unsigned int getHeight() const
Definition: vpImage.h:184
static bool checkFilename(const std::string &filename)
Definition: vpIoTools.cpp:1215
Defines a rectangle in the plane.
Definition: vpRect.h:76
Class that enables to manipulate easily a video file or a sequence of images. As it inherits from the...
void acquire(vpImage< vpRGBa > &I)
void open(vpImage< vpRGBa > &I)
void setFileName(const std::string &filename)
vpImage< vpRGBa > I_disp
void display(vpImage< unsigned char > &I, const std::string &title)
Display a gray-scale image.
VISP_EXPORT int wait(double t0, double t)
VISP_EXPORT double measureTimeMicros()