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