Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpCircleHoughTransform.h
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2025 by Inria. All rights reserved.
4 *
5 * This software is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 * See the file LICENSE.txt at the root directory of this source
10 * distribution for additional information about the GNU GPL.
11 *
12 * For using ViSP with software that can not be combined with the GNU
13 * GPL, please contact Inria about acquiring a ViSP Professional
14 * Edition License.
15 *
16 * See https://visp.inria.fr for more information.
17 *
18 * This software was developed at:
19 * Inria Rennes - Bretagne Atlantique
20 * Campus Universitaire de Beaulieu
21 * 35042 Rennes Cedex
22 * France
23 *
24 * If you have questions regarding the use of this file, please contact
25 * Inria at visp@inria.fr
26 *
27 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29 */
30
31#ifndef VP_CIRCLE_HOUGH_TRANSFORM_H
32#define VP_CIRCLE_HOUGH_TRANSFORM_H
33
34// System includes
35#include <utility>
36#include <vector>
37
38// ViSP includes
39#include <visp3/core/vpConfig.h>
40#include <visp3/core/vpCannyEdgeDetection.h>
41#include <visp3/core/vpImage.h>
42#include <visp3/core/vpImageCircle.h>
43#include <visp3/core/vpMatrix.h>
44
45// 3rd parties inclue
46#ifdef VISP_HAVE_NLOHMANN_JSON
47#include VISP_NLOHMANN_JSON(json.hpp)
48#endif
49
50#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) // Check if cxx17 or higher
51#include <optional>
52#endif
53
55
64class VISP_EXPORT vpCircleHoughTransform
65{
66public:
71 {
72 public:
77 : m_filteringAndGradientType(vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
78 , m_gaussianStdev(1.f)
79 , m_lowerCannyThresh(-1.f)
80 , m_upperCannyThresh(-1.f)
81 , m_edgeMapFilteringNbIter(1)
82 , m_cannyBackendType(vpImageFilter::CANNY_OPENCV_BACKEND)
83 , m_lowerCannyThreshRatio(0.6f)
84 , m_upperCannyThreshRatio(0.8f)
85 , m_centerXlimits(std::pair<int, int>(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()))
86 , m_centerYlimits(std::pair<int, int>(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()))
87 , m_minRadius(0.f)
88 , m_maxRadius(1000.f)
89 , m_centerMinThresh(50.f)
90 , m_expectedNbCenters(-1)
91 , m_circleProbaThresh(0.9f)
92 , m_circlePerfectness(0.9f)
93 , m_circleVisibilityRatioThresh(0.1f)
94 , m_recordVotingPoints(false)
95 , m_centerMinDist(15.f)
96 , m_mergingRadiusDiffThresh(1.5f * m_centerMinDist)
97 {
98 const unsigned int gaussianKernelSize_default = 5;
99 const unsigned int gradientFilterKernelSize_default = 3;
100 const unsigned int dilatationKernelSize_default = 3;
101 const unsigned int averagingWindowSize_default = 5;
102
103 m_gaussianKernelSize = gaussianKernelSize_default;
104 m_gradientFilterKernelSize = gradientFilterKernelSize_default;
105 m_dilatationKernelSize = dilatationKernelSize_default;
106 m_averagingWindowSize = averagingWindowSize_default;
107 }
108
148 const int &gaussianKernelSize
149 , const float &gaussianStdev
150 , const int &gradientFilterKernelSize
151 , const float &lowerCannyThresh
152 , const float &upperCannyThresh
153 , const int &edgeMapFilterNbIter
154 , const std::pair<int, int> &centerXlimits
155 , const std::pair<int, int> &centerYlimits
156 , const float &minRadius
157 , const float &maxRadius
158 , const int &dilatationKernelSize
159 , const int &averagingWindowSize
160 , const float &centerThresh
161 , const float &circleProbabilityThresh
162 , const float &circlePerfectness
163 , const float &centerMinDistThresh
164 , const float &mergingRadiusDiffThresh
167 , const float &lowerCannyThreshRatio = 0.6f
168 , const float &upperCannyThreshRatio = 0.8f
169 , const int &expectedNbCenters = -1
170 , const bool &recordVotingPoints = false
171 , const float &visibilityRatioThresh = 0.1f
172 )
173 : m_filteringAndGradientType(filteringAndGradientMethod)
174 , m_gaussianKernelSize(gaussianKernelSize)
175 , m_gaussianStdev(gaussianStdev)
176 , m_gradientFilterKernelSize(gradientFilterKernelSize)
177 , m_lowerCannyThresh(lowerCannyThresh)
178 , m_upperCannyThresh(upperCannyThresh)
179 , m_edgeMapFilteringNbIter(edgeMapFilterNbIter)
180 , m_cannyBackendType(backendType)
181 , m_lowerCannyThreshRatio(lowerCannyThreshRatio)
182 , m_upperCannyThreshRatio(upperCannyThreshRatio)
183 , m_centerXlimits(centerXlimits)
184 , m_centerYlimits(centerYlimits)
185 , m_minRadius(std::min<float>(minRadius, maxRadius))
186 , m_maxRadius(std::max<float>(minRadius, maxRadius))
187 , m_dilatationKernelSize(dilatationKernelSize)
188 , m_averagingWindowSize(averagingWindowSize)
189 , m_centerMinThresh(centerThresh)
190 , m_expectedNbCenters(expectedNbCenters)
191 , m_circleProbaThresh(circleProbabilityThresh)
192 , m_circlePerfectness(circlePerfectness)
193 , m_circleVisibilityRatioThresh(visibilityRatioThresh)
194 , m_recordVotingPoints(recordVotingPoints)
195 , m_centerMinDist(centerMinDistThresh)
196 , m_mergingRadiusDiffThresh(mergingRadiusDiffThresh)
197 { }
198
204 inline int getGaussianKernelSize() const
205 {
206 return m_gaussianKernelSize;
207 }
208
214 inline float getGaussianStdev() const
215 {
216 return m_gaussianStdev;
217 }
218
224 inline int getGradientKernelSize() const
225 {
226 return m_gradientFilterKernelSize;
227 }
228
235 inline float getLowerCannyThreshold() const
236 {
237 return m_lowerCannyThresh;
238 }
239
246 inline float getUpperCannyThreshold() const
247 {
248 return m_upperCannyThresh;
249 }
250
256 inline int getEdgeMapFilteringNbIter() const
257 {
258 return m_edgeMapFilteringNbIter;
259 }
260
266 inline std::pair<int, int> getCenterXLimits() const
267 {
268 return m_centerXlimits;
269 }
270
276 inline std::pair<int, int> getCenterYLimits() const
277 {
278 return m_centerYlimits;
279 }
280
286 inline float getMinRadius() const
287 {
288 return m_minRadius;
289 }
290
296 inline float getMaxRadius() const
297 {
298 return m_maxRadius;
299 }
300
307 inline int getDilatationKernelSize() const
308 {
309 return m_dilatationKernelSize;
310 }
311
318 inline int getAveragingWindowSize() const
319 {
320 return m_averagingWindowSize;
321 }
322
328 inline float getCenterMinThreshold() const
329 {
330 return m_centerMinThresh;
331 }
332
339 inline int getExpectedNbCenters() const
340 {
341 return m_expectedNbCenters;
342 }
343
349 inline float getProbabilityThreshold() const
350 {
351 return m_circleProbaThresh;
352 }
353
359 inline float getVisibilityRatioThreshold() const
360 {
361 return m_circleVisibilityRatioThresh;
362 }
363
371 inline float getCirclePerfectness() const
372 {
373 return m_circlePerfectness;
374 }
375
381 inline bool getRecordVotingPoints() const
382 {
383 return m_recordVotingPoints;
384 }
385
391 inline float getCenterMinDist() const
392 {
393 return m_centerMinDist;
394 }
395
401 inline float getMergingRadiusDiff() const
402 {
403 return m_mergingRadiusDiffThresh;
404 }
405
409 std::string toString() const
410 {
411 std::stringstream txt;
412 txt << "Hough Circle Transform Configuration:\n";
413 txt << "\tFiltering + gradient operators = " << vpImageFilter::vpCannyFiltAndGradTypeToStr(m_filteringAndGradientType) << "\n";
414 txt << "\tGaussian filter kernel size = " << m_gaussianKernelSize << "\n";
415 txt << "\tGaussian filter standard deviation = " << m_gaussianStdev << "\n";
416 txt << "\tGradient filter kernel size = " << m_gradientFilterKernelSize << "\n";
417 txt << "\tCanny backend = " << vpImageFilter::vpCannyBackendTypeToString(m_cannyBackendType) << "\n";
418 txt << "\tCanny edge filter thresholds = [" << m_lowerCannyThresh << " ; " << m_upperCannyThresh << "]\n";
419 txt << "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" << m_lowerCannyThreshRatio << " ; " << m_upperCannyThreshRatio << "]\n";
420 txt << "\tEdge map 8-neighbor connectivity filtering number of iterations = " << m_edgeMapFilteringNbIter << "\n";
421 txt << "\tCenter horizontal position limits: min = " << m_centerXlimits.first << "\tmax = " << m_centerXlimits.second << "\n";
422 txt << "\tCenter vertical position limits: min = " << m_centerYlimits.first << "\tmax = " << m_centerYlimits.second << "\n";
423 txt << "\tRadius limits: min = " << m_minRadius << "\tmax = " << m_maxRadius << "\n";
424 txt << "\tKernel size of the dilatation filter = " << m_dilatationKernelSize << "\n";
425 txt << "\tAveraging window size for center detection = " << m_averagingWindowSize << "\n";
426 txt << "\tCenters votes threshold = " << m_centerMinThresh << "\n";
427 txt << "\tExpected number of centers = ";
428 if (m_expectedNbCenters > 0) {
429 txt << m_expectedNbCenters;
430 }
431 else {
432 txt << "no limits";
433 }
434 txt << "\n";
435 txt << "\tCircle probability threshold = " << m_circleProbaThresh << "\n";
436 txt << "\tCircle visibility ratio threshold = " << m_circleVisibilityRatioThresh << "\n";
437 txt << "\tCircle perfectness threshold = " << m_circlePerfectness << "\n";
438 txt << "\tRecord voting points = ";
439 txt << (m_recordVotingPoints ? std::string("true") : std::string("false")) << "\n";
440 txt << "\tCenters minimum distance = " << m_centerMinDist << "\n";
441 txt << "\tRadius difference merging threshold = " << m_mergingRadiusDiffThresh << "\n";
442 return txt.str();
443 }
444
445 // // Configuration from files
446#ifdef VISP_HAVE_NLOHMANN_JSON
453 inline static vpCircleHoughTransformParams createFromJSON(const std::string &jsonFile)
454 {
455 using json = nlohmann::json;
456
457 std::ifstream file(jsonFile);
458 if (!file.good()) {
459 std::stringstream ss;
460 ss << "Problem opening file " << jsonFile << ". Make sure it exists and is readable" << std::endl;
461 throw vpException(vpException::ioError, ss.str());
462 }
463 json j;
464 try {
465 j = json::parse(file);
466 }
467 catch (json::parse_error &e) {
468 std::stringstream msg;
469 msg << "Could not parse JSON file : \n";
470
471 msg << e.what() << std::endl;
472 msg << "Byte position of error: " << e.byte;
473 throw vpException(vpException::ioError, msg.str());
474 }
475 vpCircleHoughTransformParams params = j; // Call from_json(const json& j, vpDetectorDNN& *this) to read json
476 file.close();
477 return params;
478 }
479
487 inline void saveConfigurationInJSON(const std::string &jsonPath) const
488 {
489 using json = nlohmann::json;
490 std::ofstream file(jsonPath);
491 const json j = *this;
492 const int indent = 4;
493 file << j.dump(indent);
494 file.close();
495 }
496
504 friend inline void from_json(const nlohmann::json &j, vpCircleHoughTransformParams &params)
505 {
506 std::string filteringAndGradientName = vpImageFilter::vpCannyFiltAndGradTypeToStr(params.m_filteringAndGradientType);
507 filteringAndGradientName = j.value("filteringAndGradientType", filteringAndGradientName);
508 params.m_filteringAndGradientType = vpImageFilter::vpCannyFiltAndGradTypeFromStr(filteringAndGradientName);
509
510 params.m_gaussianKernelSize = j.value("gaussianKernelSize", params.m_gaussianKernelSize);
511 const int checkEvenModulo = 2;
512 if ((params.m_gaussianKernelSize % checkEvenModulo) != 1) {
513 throw vpException(vpException::badValue, "Gaussian Kernel size should be odd.");
514 }
515
516 params.m_gaussianStdev = j.value("gaussianStdev", params.m_gaussianStdev);
517 if (params.m_gaussianStdev <= 0) {
518 throw vpException(vpException::badValue, "Standard deviation should be > 0");
519 }
520
521 params.m_gradientFilterKernelSize = j.value("gradientFilterKernelSize", params.m_gradientFilterKernelSize);
522 if ((params.m_gradientFilterKernelSize % checkEvenModulo) != 1) {
523 throw vpException(vpException::badValue, "Gradient filter kernel (Sobel or Scharr) size should be odd.");
524 }
525
526 std::string cannyBackendName = vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType);
527 cannyBackendName = j.value("cannyBackendType", cannyBackendName);
528 params.m_cannyBackendType = vpImageFilter::vpCannyBackendTypeFromString(cannyBackendName);
529 params.m_lowerCannyThresh = j.value("lowerCannyThresh", params.m_lowerCannyThresh);
530 params.m_lowerCannyThreshRatio = j.value("lowerThresholdRatio", params.m_lowerCannyThreshRatio);
531 params.m_upperCannyThresh = j.value("upperCannyThresh", params.m_upperCannyThresh);
532 params.m_upperCannyThreshRatio = j.value("upperThresholdRatio", params.m_upperCannyThreshRatio);
533 params.m_edgeMapFilteringNbIter = j.value("edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter);
534
535 params.m_centerXlimits = j.value("centerXlimits", params.m_centerXlimits);
536 params.m_centerYlimits = j.value("centerYlimits", params.m_centerYlimits);
537 std::pair<float, float> radiusLimits = j.value("radiusLimits", std::pair<float, float>(params.m_minRadius, params.m_maxRadius));
538 params.m_minRadius = std::min<float>(radiusLimits.first, radiusLimits.second);
539 params.m_maxRadius = std::max<float>(radiusLimits.first, radiusLimits.second);
540
541 params.m_dilatationKernelSize = j.value("dilatationKernelSize", params.m_dilatationKernelSize);
542 params.m_averagingWindowSize = j.value("averagingWindowSize", params.m_averagingWindowSize);
543 if ((params.m_averagingWindowSize <= 0) || ((params.m_averagingWindowSize % checkEvenModulo) == 0)) {
544 throw vpException(vpException::badValue, "Averaging window size must be positive and odd.");
545 }
546
547 params.m_centerMinThresh = j.value("centerThresh", params.m_centerMinThresh);
548 if (params.m_centerMinThresh <= 0) {
549 throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive.");
550 }
551
552 params.m_expectedNbCenters = j.value("expectedNbCenters", params.m_expectedNbCenters);
553
554
555 params.m_circleProbaThresh = j.value("circleProbabilityThreshold", params.m_circleProbaThresh);
556 params.m_circleVisibilityRatioThresh = j.value("circleVisibilityRatioThreshold", params.m_circleVisibilityRatioThresh);
557
558 params.m_circlePerfectness = j.value("circlePerfectnessThreshold", params.m_circlePerfectness);
559
560 if ((params.m_circlePerfectness <= 0) || (params.m_circlePerfectness > 1)) {
561 throw vpException(vpException::badValue, "Circle perfectness must be in the interval ] 0; 1].");
562 }
563
564 params.m_recordVotingPoints = j.value("recordVotingPoints", params.m_recordVotingPoints);
565
566 params.m_centerMinDist = j.value("centerMinDistance", params.m_centerMinDist);
567 if (params.m_centerMinDist <= 0) {
568 throw vpException(vpException::badValue, "Centers minimum distance threshold must be positive.");
569 }
570
571 params.m_mergingRadiusDiffThresh = j.value("mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh);
572 if (params.m_mergingRadiusDiffThresh <= 0) {
573 throw vpException(vpException::badValue, "Radius difference merging threshold must be positive.");
574 }
575 }
576
583 friend inline void to_json(nlohmann::json &j, const vpCircleHoughTransformParams &params)
584 {
585 std::pair<float, float> radiusLimits = { params.m_minRadius, params.m_maxRadius };
586
587 j = nlohmann::json {
588 {"filteringAndGradientType", vpImageFilter::vpCannyFiltAndGradTypeToStr(params.m_filteringAndGradientType)},
589 {"gaussianKernelSize", params.m_gaussianKernelSize},
590 {"gaussianStdev", params.m_gaussianStdev},
591 {"gradientFilterKernelSize", params.m_gradientFilterKernelSize},
592 {"cannyBackendType", vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType)},
593 {"lowerCannyThresh", params.m_lowerCannyThresh},
594 {"lowerThresholdRatio", params.m_lowerCannyThreshRatio},
595 {"upperCannyThresh", params.m_upperCannyThresh},
596 {"upperThresholdRatio", params.m_upperCannyThreshRatio},
597 {"edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter},
598 {"centerXlimits", params.m_centerXlimits},
599 {"centerYlimits", params.m_centerYlimits},
600 {"radiusLimits", radiusLimits},
601 {"dilatationKernelSize", params.m_dilatationKernelSize},
602 {"averagingWindowSize", params.m_averagingWindowSize},
603 {"centerThresh", params.m_centerMinThresh},
604 {"expectedNbCenters", params.m_expectedNbCenters},
605 {"circleProbabilityThreshold", params.m_circleProbaThresh},
606 {"circleVisibilityRatioThreshold", params.m_circleVisibilityRatioThresh},
607 {"circlePerfectnessThreshold", params.m_circlePerfectness},
608 {"recordVotingPoints", params.m_recordVotingPoints},
609 {"centerMinDistance", params.m_centerMinDist},
610 {"mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh} };
611 }
612#endif
613 friend VISP_EXPORT bool operator==(const vpImageCircle &a, const vpImageCircle &b);
614
615 private:
616 // // Filtering + gradient operators to use
617 vpImageFilter::vpCannyFilteringAndGradientType m_filteringAndGradientType;
619
620 // // Gaussian smoothing attributes
621 int m_gaussianKernelSize;
623 float m_gaussianStdev;
624
625 // // Gradient computation attributes
626 int m_gradientFilterKernelSize;
627
628 // // Edge detection attributes
629 float m_lowerCannyThresh;
631 float m_upperCannyThresh;
633 int m_edgeMapFilteringNbIter;
634 vpImageFilter::vpCannyBackendType m_cannyBackendType;
635 float m_lowerCannyThreshRatio;
637 float m_upperCannyThreshRatio;
639
640 // // Center candidates computation attributes
641 std::pair<int, int> m_centerXlimits;
642 std::pair<int, int> m_centerYlimits;
643 float m_minRadius;
644 float m_maxRadius;
645 int m_dilatationKernelSize;
646 int m_averagingWindowSize;
648 float m_centerMinThresh;
649 int m_expectedNbCenters;
651
652 // // Circle candidates computation attributes
653 float m_circleProbaThresh;
654 float m_circlePerfectness;
657 float m_circleVisibilityRatioThresh;
658 bool m_recordVotingPoints;
659
660 // // Circle candidates merging attributes
661 float m_centerMinDist;
662 float m_mergingRadiusDiffThresh;
663
665 };
666
667#ifdef VISP_BUILD_DEPRECATED_FUNCTIONS
669#endif
670
671#ifndef DOXYGEN_SHOULD_SKIP_THIS
675 typedef struct vpCenterVotes
676 {
677 std::pair<float, float> m_position;
678 float m_votes;
679 } vpCenterVotes;
680#endif
681
686
692 VP_EXPLICIT vpCircleHoughTransform(const vpCircleHoughTransformParams &algoParams);
693
697 virtual ~vpCircleHoughTransform();
698
701#ifdef HAVE_OPENCV_CORE
708 virtual std::vector<vpImageCircle> detect(const cv::Mat &cv_I);
709#endif
710
718 virtual std::vector<vpImageCircle> detect(const vpImage<vpRGBa> &I);
719
726 virtual std::vector<vpImageCircle> detect(const vpImage<unsigned char> &I);
727
738 virtual std::vector<vpImageCircle> detect(const vpImage<unsigned char> &I, const int &nbCircles);
739
747#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) // Check if cxx17 or higher
748 inline void computeVotingMask(const vpImage<unsigned char> &I, const std::vector<vpImageCircle> &detections,
749 std::optional< vpImage<bool> > &mask,
750 std::optional<std::vector<std::vector<std::pair<unsigned int, unsigned int>>>> &opt_votingPoints) const
751#else
752 inline void computeVotingMask(const vpImage<unsigned char> &I, const std::vector<vpImageCircle> &detections,
753 vpImage<bool> **mask,
754 std::vector<std::vector<std::pair<unsigned int, unsigned int> > > **opt_votingPoints) const
755#endif
756 {
757 if (!m_algoParams.m_recordVotingPoints) {
758 // We weren't asked to remember the voting points
759#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) // Check if cxx17 or higher
760 mask = std::nullopt;
761 opt_votingPoints = std::nullopt;
762#else
763 *mask = nullptr;
764 *opt_votingPoints = nullptr;
765#endif
766 return;
767 }
768
769#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) // Check if cxx17 or higher
770 mask = vpImage<bool>(I.getHeight(), I.getWidth(), false);
771 opt_votingPoints = std::vector<std::vector<std::pair<unsigned int, unsigned int>>>();
772#else
773 *mask = new vpImage<bool>(I.getHeight(), I.getWidth(), false);
774 *opt_votingPoints = new std::vector<std::vector<std::pair<unsigned int, unsigned int> > >();
775#endif
776
777#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) // Check if cxx17 or higher
778 for (const auto &detection : detections)
779#else
780 const size_t nbDetections = detections.size();
781 for (size_t i = 0; i < nbDetections; ++i)
782#endif
783 {
784 bool hasFoundSimilarCircle = false;
785 unsigned int nbPreviouslyDetected = static_cast<unsigned int>(m_finalCircles.size());
786 unsigned int id = 0;
787 // Looking for a circle that was detected and is similar to the one given to the function
788 while ((id < nbPreviouslyDetected) && (!hasFoundSimilarCircle)) {
789 const vpImageCircle previouslyDetected = m_finalCircles[id];
790#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) // Check if cxx17 or higher
791 if (previouslyDetected == detection)
792#else
793 if (previouslyDetected == detections[i])
794#endif
795 {
796 hasFoundSimilarCircle = true;
797 // We found a circle that is similar to the one given to the function => updating the mask
798 const unsigned int nbVotingPoints = static_cast<unsigned int>(m_finalCirclesVotingPoints[id].size());
799 for (unsigned int idPoint = 0; idPoint < nbVotingPoints; ++idPoint) {
800 const std::pair<unsigned int, unsigned int> &votingPoint = m_finalCirclesVotingPoints[id][idPoint];
801#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) // Check if cxx17 or higher
802 (*mask)[votingPoint.first][votingPoint.second] = true;
803#else
804 (**mask)[votingPoint.first][votingPoint.second] = true;
805#endif
806 }
807#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) // Check if cxx17 or higher
808 opt_votingPoints->push_back(m_finalCirclesVotingPoints[id]);
809#else
810 (**opt_votingPoints).push_back(m_finalCirclesVotingPoints[id]);
811#endif
812 }
813 ++id;
814 }
815 }
816 }
817
820#ifdef VISP_HAVE_NLOHMANN_JSON
827 VP_EXPLICIT vpCircleHoughTransform(const std::string &jsonPath);
828
836 virtual void initFromJSON(const std::string &jsonPath);
837
845 virtual void saveConfigurationInJSON(const std::string &jsonPath) const;
846
854 friend inline void from_json(const nlohmann::json &j, vpCircleHoughTransform &detector)
855 {
856 detector.m_algoParams = j;
857 }
858
865 friend inline void to_json(nlohmann::json &j, const vpCircleHoughTransform &detector)
866 {
867 j = detector.m_algoParams;
868 }
869#endif
871
874
879 void init(const vpCircleHoughTransformParams &algoParams);
880
887 {
888 m_algoParams.m_filteringAndGradientType = type;
889 m_cannyVisp.setFilteringAndGradientType(type);
891 }
892
900 inline void setGaussianParameters(const int &kernelSize, const float &stdev)
901 {
902 m_algoParams.m_gaussianKernelSize = kernelSize;
903 m_algoParams.m_gaussianStdev = stdev;
904
905 const unsigned int checkEvenModulo = 2;
906 if ((m_algoParams.m_gaussianKernelSize % checkEvenModulo) != 1) {
907 throw vpException(vpException::badValue, "Gaussian Kernel size should be odd.");
908 }
909
910 if (m_algoParams.m_gaussianStdev <= 0) {
911 throw vpException(vpException::badValue, "Standard deviation should be > 0");
912 }
913
915 }
916
922 inline void setGradientFilterAperture(const unsigned int &apertureSize)
923 {
924 m_algoParams.m_gradientFilterKernelSize = apertureSize;
925
926 const unsigned int checkEvenModulo = 2;
927 if ((m_algoParams.m_gradientFilterKernelSize % checkEvenModulo) != 1) {
928 throw vpException(vpException::badValue, "Gradient filter (Sobel or Scharr) Kernel size should be odd.");
929 }
930
932 }
933
940 {
941 m_algoParams.m_cannyBackendType = type;
942 }
943
953 inline void setCannyThreshold(const float &lowerCannyThreshold, const float &upperCannyThreshold)
954 {
955 m_algoParams.m_lowerCannyThresh = lowerCannyThreshold;
956 m_algoParams.m_upperCannyThresh = upperCannyThreshold;
957 }
958
968 inline void setCannyThresholdRatio(const float &lowerThreshRatio, const float &upperThreshRatio)
969 {
970 m_algoParams.m_lowerCannyThreshRatio = lowerThreshRatio;
971 m_algoParams.m_upperCannyThreshRatio = upperThreshRatio;
972 m_cannyVisp.setCannyThresholdsRatio(lowerThreshRatio, upperThreshRatio);
973 }
974
981 inline void setCircleCenterMinDist(const float &center_min_dist)
982 {
983 m_algoParams.m_centerMinDist = center_min_dist;
984
985 if (m_algoParams.m_centerMinDist <= 0) {
986 throw vpException(vpException::badValue, "Circles center min distance must be positive.");
987 }
988 }
989
1002 void setCircleCenterBoundingBox(const int &center_min_x, const int &center_max_x,
1003 const int &center_min_y, const int &center_max_y)
1004 {
1005 m_algoParams.m_centerXlimits.first = center_min_x;
1006 m_algoParams.m_centerXlimits.second = center_max_x;
1007 m_algoParams.m_centerYlimits.first = center_min_y;
1008 m_algoParams.m_centerYlimits.second = center_max_y;
1009 }
1010
1015 inline void setCircleMinRadius(const float &circle_min_radius)
1016 {
1017 m_algoParams.m_minRadius = circle_min_radius;
1018 }
1019
1024 inline void setCircleMaxRadius(const float &circle_max_radius)
1025 {
1026 m_algoParams.m_maxRadius = circle_max_radius;
1027 }
1028
1036 void setCirclePerfectness(const float &circle_perfectness)
1037 {
1038 m_algoParams.m_circlePerfectness = circle_perfectness;
1039 if ((m_algoParams.m_circlePerfectness <= 0) || (m_algoParams.m_circlePerfectness > 1)) {
1040 throw vpException(vpException::badValue, "Circle perfectness must be in the interval ] 0; 1].");
1041 }
1042 }
1043
1054 inline void setCenterComputationParameters(const int &dilatationSize, const float &centerThresh,
1055 const int &averagingWindowSize = 5, const int expectedNbCenters = -1)
1056 {
1057 m_algoParams.m_dilatationKernelSize = dilatationSize;
1058 m_algoParams.m_centerMinThresh = centerThresh;
1059 m_algoParams.m_averagingWindowSize = averagingWindowSize;
1060 m_algoParams.m_expectedNbCenters = expectedNbCenters;
1061
1062 const int minDilatationKernel = 3;
1063 const unsigned int checkEvenModulo = 2;
1064 if (m_algoParams.m_dilatationKernelSize < minDilatationKernel) {
1065 throw vpException(vpException::badValue, "Dilatation kernel size for center detection must be greater or equal to 3.");
1066 }
1067 else if ((m_algoParams.m_dilatationKernelSize % checkEvenModulo) == 0) {
1068 throw vpException(vpException::badValue, "Dilatation kernel size for center detection must be odd.");
1069 }
1070
1071 if (m_algoParams.m_centerMinThresh <= 0.f) {
1072 throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive.");
1073 }
1074
1075 if ((m_algoParams.m_averagingWindowSize <= 0) || ((m_algoParams.m_averagingWindowSize % checkEvenModulo) == 0)) {
1076 throw vpException(vpException::badValue, "Averaging window size must be positive and odd.");
1077 }
1078 }
1079
1085 inline void setRadiusRatioThreshold(const float &radiusRatioThresh)
1086 {
1087 m_algoParams.m_circleProbaThresh = radiusRatioThresh;
1088
1089 if (m_algoParams.m_circleProbaThresh <= 0) {
1090 throw vpException(vpException::badValue, "Radius ratio threshold must be > 0.");
1091 }
1092 }
1093
1100 inline void setRadiusMergingThresholds(const float &radiusDifferenceThresh)
1101 {
1102 m_algoParams.m_mergingRadiusDiffThresh = radiusDifferenceThresh;
1103
1104 if (m_algoParams.m_mergingRadiusDiffThresh <= 0) {
1105 throw vpException(vpException::badValue, "Radius difference merging threshold must be positive.");
1106 }
1107 }
1108
1115 inline void setMask(const vpImage<bool> &mask)
1116 {
1117 mp_mask = &mask;
1118 }
1119
1127 inline void setMask(const vpImage<bool> *mask)
1128 {
1129 mp_mask = mask;
1130 }
1131
1138 inline void setRecordVotingPoints(const bool &record)
1139 {
1140 m_algoParams.m_recordVotingPoints = record;
1141 }
1142
1143
1146
1151 inline std::vector<std::pair<float, float> > getCenterCandidatesList() const
1152 {
1154 }
1155
1161 inline std::vector<int> getCenterCandidatesVotes() const
1162 {
1163 return m_centerVotes;
1164 }
1165
1172 inline std::vector<vpImageCircle> getCircleCandidates() const
1173 {
1174 return m_circleCandidates;
1175 }
1176
1182 inline std::vector<float> getCircleCandidatesProbabilities() const
1183 {
1185 }
1186
1192 inline std::vector<unsigned int> getCircleCandidatesVotes() const
1193 {
1195 }
1196
1203 {
1204 return m_dIx;
1205 }
1206
1213 {
1214 return m_dIy;
1215 }
1216
1223 {
1224 return m_edgeMap;
1225 }
1226
1231 inline float getCannyThreshold() const
1232 {
1233 return m_algoParams.m_upperCannyThresh;
1234 }
1235
1239 inline float getCircleCenterMinDist() const
1240 {
1241 return m_algoParams.m_centerMinDist;
1242 }
1243
1247 inline float getCircleMinRadius() const
1248 {
1249 return m_algoParams.m_minRadius;
1250 }
1251
1255 inline float getCircleMaxRadius() const
1256 {
1257 return m_algoParams.m_maxRadius;
1258 }
1259
1263 inline std::vector<float> getDetectionsProbabilities() const
1264 {
1266 }
1267
1271 inline std::vector<unsigned int> getDetectionsVotes() const
1272 {
1273 return m_finalCircleVotes;
1274 }
1275
1279 inline std::vector<std::vector<std::pair<unsigned int, unsigned int> > > getDetectionsVotingPoints() const
1280 {
1281 if (!m_algoParams.m_recordVotingPoints) {
1282 throw(vpException(vpException::fatalError, "Asking voting points when it was not asked to remember them."));
1283 }
1285 }
1286
1290 inline bool getRecordVotingPoints() const
1291 {
1292 return m_algoParams.getRecordVotingPoints();
1293 }
1294
1295
1299 std::string toString() const;
1300
1304 friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpCircleHoughTransform &detector);
1305
1306 static const unsigned char edgeMapOn;
1307 static const unsigned char edgeMapOff;
1308
1309protected:
1310
1311#ifndef DOXYGEN_SHOULD_SKIP_THIS
1315 typedef struct vpCentersBarycenter
1316 {
1317 std::pair<float, float> m_position;
1318 float m_totalVotes;
1319 float m_nbElectors;
1320 }vpCentersBarycenter;
1321
1325 typedef struct vpCoordinatesForAccumStep
1326 {
1327 float x_orig;
1328 float y_orig;
1329 int x;
1330 int y;
1331 }vpCoordinatesForAccumStep;
1332
1337 typedef struct vpDataForAccumLoop
1338 {
1339 unsigned int r;
1340 unsigned int c;
1341 float minRadius;
1342 float maxRadius;
1343 float minimumXpositionFloat;
1344 float minimumYpositionFloat;
1345 float maximumXpositionFloat;
1346 float maximumYpositionFloat;
1347 int offsetX;
1348 int offsetY;
1349 int accumulatorWidth;
1350 int accumulatorHeight;
1351 }vpDataForAccumLoop;
1352#endif
1353
1358 virtual void initGaussianFilters();
1359
1363 virtual void initGradientFilters();
1364
1372 virtual void computeGradients(const vpImage<unsigned char> &I);
1373
1380 virtual void edgeDetection(const vpImage<unsigned char> &I);
1381
1385 virtual void filterEdgeMap();
1386
1392 virtual void computeCenterCandidates();
1393
1394 void updateAccumulator(const vpCoordinatesForAccumStep &coord, const vpDataForAccumLoop &data, vpImage<float> &accum, bool &hasToStop);
1395
1405 void updateAccumAlongGradientDir(const vpDataForAccumLoop &data, float &sx, float &sy, vpImage<float> &centersAccum);
1406
1413 virtual void workOnAccumulator(vpDataForAccumLoop &data, vpImage<float> &centersAccum);
1414
1419 virtual void filterCenterCandidates(const std::vector<vpCenterVotes> &peak_positions_votes);
1420
1432 virtual vpCentersBarycenter mergeSimilarCenters(const unsigned int &idPeak, const unsigned int &nbPeaks, const float &squared_distance_max, const std::vector<vpCenterVotes> &peak_positions_votes, std::vector<bool> &has_been_merged);
1433
1444 virtual float computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes);
1445
1454 virtual void computeCircleCandidates();
1455
1459 virtual void mergeCircleCandidates();
1460
1472 virtual void mergeCandidates(std::vector<vpImageCircle> &circleCandidates, std::vector<unsigned int> &circleCandidatesVotes,
1473 std::vector<float> &circleCandidatesProba, std::vector<std::vector<std::pair<unsigned int, unsigned int> > > &votingPoints);
1474
1476 // // Gaussian smoothing attributes
1478
1479 // // Gradient computation attributes
1485
1486 // // Edge detection attributes
1489
1490 // // Center candidates computation attributes
1491 std::vector<std::pair<unsigned int, unsigned int> > m_edgePointsList;
1492 std::vector<std::pair<float, float> > m_centerCandidatesList;
1493 std::vector<int> m_centerVotes;
1494
1495 // // Circle candidates computation attributes
1496 std::vector<vpImageCircle> m_circleCandidates;
1498 std::vector<unsigned int> m_circleCandidatesVotes;
1499 std::vector<std::vector<std::pair<unsigned int, unsigned int> > > m_circleCandidatesVotingPoints;
1500
1501 // // Circle candidates merging attributes
1502 std::vector<vpImageCircle> m_finalCircles;
1503 std::vector<float> m_finalCirclesProbabilities;
1504 std::vector<unsigned int> m_finalCircleVotes;
1505 std::vector<std::vector<std::pair<unsigned int, unsigned int> > > m_finalCirclesVotingPoints;
1506};
1507
1508END_VISP_NAMESPACE
1509
1510#endif
Implementation of a generic 2D array used as base class for matrices and vectors.
Definition vpArray2D.h:146
Class that implements the Canny's edge detector. It is possible to use a boolean mask to ignore some ...
Class that gather the algorithm parameters.
float getVisibilityRatioThreshold() const
Get the visibility ratio threshold in order to keep a circle candidate.
int getExpectedNbCenters() const
Get the expected number of centers in the image. If the number is negative, all the centers are kept....
int getDilatationKernelSize() const
Get the kernel size of the dilatation that is performed to detect the maximum number of votes for the...
friend void to_json(nlohmann::json &j, const vpCircleHoughTransformParams &params)
Parse a vpCircleHoughTransform into JSON format.
vpCircleHoughTransformParams()
Construct a new vpCircleHoughTransformParams object with default parameters.
static vpCircleHoughTransformParams createFromJSON(const std::string &jsonFile)
Create a new vpCircleHoughTransformParams from a JSON file.
int getEdgeMapFilteringNbIter() const
Get the number of iterations of 8-neighbor connectivity filtering to apply to the edge map.
void saveConfigurationInJSON(const std::string &jsonPath) const
Save the configuration of the detector in a JSON file described by the path jsonPath....
std::pair< int, int > getCenterXLimits() const
Get the minimum and maximum position on the horizontal axis of the center of the circle we want to de...
float getUpperCannyThreshold() const
Get the upper threshold for the Canny operator. Values lower than this value are rejected....
float getCenterMinDist() const
Get the Maximum distance between two circle candidates centers to consider merging them.
friend void from_json(const nlohmann::json &j, vpCircleHoughTransformParams &params)
Read the detector configuration from JSON. All values are optional and if an argument is not present,...
float getMinRadius() const
Get the minimum radius of the circles we want to detect.
vpCircleHoughTransformParams(const int &gaussianKernelSize, const float &gaussianStdev, const int &gradientFilterKernelSize, const float &lowerCannyThresh, const float &upperCannyThresh, const int &edgeMapFilterNbIter, const std::pair< int, int > &centerXlimits, const std::pair< int, int > &centerYlimits, const float &minRadius, const float &maxRadius, const int &dilatationKernelSize, const int &averagingWindowSize, const float &centerThresh, const float &circleProbabilityThresh, const float &circlePerfectness, const float &centerMinDistThresh, const float &mergingRadiusDiffThresh, const vpImageFilter::vpCannyFilteringAndGradientType &filteringAndGradientMethod=vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING, const vpImageFilter::vpCannyBackendType &backendType=vpImageFilter::CANNY_OPENCV_BACKEND, const float &lowerCannyThreshRatio=0.6f, const float &upperCannyThreshRatio=0.8f, const int &expectedNbCenters=-1, const bool &recordVotingPoints=false, const float &visibilityRatioThresh=0.1f)
Construct a new vpCircleHoughTransformParams object.
int getAveragingWindowSize() const
Get the size of the averaging window around the maximum number of votes to compute the center candida...
int getGradientKernelSize() const
Get the size of the gradient kernel filters used to compute the gradients.
int getGaussianKernelSize() const
Get the size of the Gaussian filter kernel used to smooth the input image.
float getCirclePerfectness() const
Get the threshold for the colinearity between the gradient of a point and the radius it would form wi...
float getLowerCannyThreshold() const
Get the lower threshold for the Canny operator. Values lower than this value are rejected....
float getMaxRadius() const
Get the maximum radius of the circles we want to detect.
float getProbabilityThreshold() const
Get the probability threshold in order to keep a circle candidate.
float getCenterMinThreshold() const
Get the minimum number of votes a point must exceed to be considered as center candidate.
bool getRecordVotingPoints() const
Get the boolean indicating if we have to record the edge-map points having voted for the circles.
float getMergingRadiusDiff() const
Get the Maximum radius difference between two circle candidates to consider merging them.
float getGaussianStdev() const
Get the standard deviation of the Gaussian filter.
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...
Class that permits to detect 2D circles in a image using the gradient-based Circle Hough transform....
void setCannyThresholdRatio(const float &lowerThreshRatio, const float &upperThreshRatio)
Set the Canny thresholds ratio that are used to automatically compute the Canny thresholds in case th...
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > m_circleCandidatesVotingPoints
std::vector< std::pair< float, float > > m_centerCandidatesList
std::vector< unsigned int > getDetectionsVotes() const
vpCannyEdgeDetection m_cannyVisp
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > m_finalCirclesVotingPoints
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > getDetectionsVotingPoints() const
std::vector< float > m_circleCandidatesProbabilities
vpCircleHoughTransformParams vpCircleHoughTransformParameters
std::vector< vpImageCircle > m_finalCircles
std::vector< vpImageCircle > getCircleCandidates() const
Get the Circle Candidates before merging step.
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.
vpImage< unsigned char > getEdgeMap() const
Get the Edge Map computed thanks to the Canny edge filter.
void setCirclePerfectness(const float &circle_perfectness)
Set circles perfectness, which corresponds to the threshold of the colinearity between the gradient o...
void setRecordVotingPoints(const bool &record)
Permits to either activate or deactivate the memorization of the points that voted for the detected c...
friend void to_json(nlohmann::json &j, const vpCircleHoughTransform &detector)
Parse a vpCircleHoughTransform into JSON format.
void setGradientFilterAperture(const unsigned int &apertureSize)
Set the parameters of the gradient filter (Sobel or Scharr) kernel size filters.
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 void from_json(const nlohmann::json &j, vpCircleHoughTransform &detector)
Read the detector configuration from JSON. All values are optional and if an argument is not present,...
vpImage< unsigned char > m_edgeMap
vpArray2D< float > m_gradientFilterX
std::vector< unsigned int > getCircleCandidatesVotes() const
Get the votes of the circle candidates.
static const unsigned char edgeMapOff
vpImage< float > getGradientX() const
Get the gradient along the horizontal axis of the image.
std::vector< std::pair< float, float > > getCenterCandidatesList() const
Get the list of Center Candidates, stored as pair <idRow, idCol>.
void setCannyBackend(const vpImageFilter::vpCannyBackendType &type)
Set the backend to use to perform the Canny edge detection.
void setCenterComputationParameters(const int &dilatationSize, const float &centerThresh, const int &averagingWindowSize=5, const int expectedNbCenters=-1)
Set the parameters of the computation of the circle center candidates.
const vpImage< bool > * mp_mask
std::vector< unsigned int > m_circleCandidatesVotes
std::vector< unsigned int > m_finalCircleVotes
void setCannyThreshold(const float &lowerCannyThreshold, const float &upperCannyThreshold)
vpCircleHoughTransformParams m_algoParams
std::vector< float > m_finalCirclesProbabilities
std::vector< float > getCircleCandidatesProbabilities() const
Get the probabilities of the Circle Candidates.
std::vector< int > getCenterCandidatesVotes() const
Get the number of votes of each Center Candidates.
void setCircleCenterBoundingBox(const int &center_min_x, const int &center_max_x, const int &center_min_y, const int &center_max_y)
void setMask(const vpImage< bool > &mask)
Set the mask that permits to ignore some pixels when performing the circle detection.
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 ...
void setCircleMaxRadius(const float &circle_max_radius)
void setCircleMinRadius(const float &circle_min_radius)
static const unsigned char edgeMapOn
vpCircleHoughTransform()
Construct a new vpCircleHoughTransform object with default parameters.
void setFilteringAndGradientType(const vpImageFilter::vpCannyFilteringAndGradientType &type)
Permits to choose the filtering + gradient operators to use.
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)
vpImage< float > getGradientY() const
Get the gradient along the vertical axis of the image.
vpArray2D< float > m_gradientFilterY
void setMask(const vpImage< bool > *mask)
Set the mask that permits to ignore some pixels when performing the circle detection.
std::vector< float > getDetectionsProbabilities() const
std::vector< vpImageCircle > m_circleCandidates
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ ioError
I/O error.
Definition vpException.h:67
@ badValue
Used to indicate that a value is not in the allowed range.
Definition vpException.h:73
@ fatalError
Fatal error.
Definition vpException.h:72
Class that defines a 2D circle in an image.
Various image filter, convolution, etc...
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_SOBEL_FILTERING
Apply Gaussian blur + Sobel 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.
Definition of the vpImage class member functions.
Definition vpImage.h:131