31 #ifndef VP_CIRCLE_HOUGH_TRANSFORM_H
32 #define VP_CIRCLE_HOUGH_TRANSFORM_H
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>
46 #ifdef VISP_HAVE_NLOHMANN_JSON
47 #include VISP_NLOHMANN_JSON(json.hpp)
50 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17)
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)
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()))
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)
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;
103 m_gaussianKernelSize = gaussianKernelSize_default;
104 m_gradientFilterKernelSize = gradientFilterKernelSize_default;
105 m_dilatationKernelSize = dilatationKernelSize_default;
106 m_averagingWindowSize = averagingWindowSize_default;
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> ¢erXlimits
155 ,
const std::pair<int, int> ¢erYlimits
156 ,
const float &minRadius
157 ,
const float &maxRadius
158 ,
const int &dilatationKernelSize
159 ,
const int &averagingWindowSize
160 ,
const float ¢erThresh
161 ,
const float &circleProbabilityThresh
162 ,
const float &circlePerfectness
163 ,
const float ¢erMinDistThresh
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
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)
206 return m_gaussianKernelSize;
216 return m_gaussianStdev;
226 return m_gradientFilterKernelSize;
237 return m_lowerCannyThresh;
248 return m_upperCannyThresh;
258 return m_edgeMapFilteringNbIter;
268 return m_centerXlimits;
278 return m_centerYlimits;
309 return m_dilatationKernelSize;
320 return m_averagingWindowSize;
330 return m_centerMinThresh;
341 return m_expectedNbCenters;
351 return m_circleProbaThresh;
361 return m_circleVisibilityRatioThresh;
373 return m_circlePerfectness;
383 return m_recordVotingPoints;
393 return m_centerMinDist;
403 return m_mergingRadiusDiffThresh;
411 std::stringstream txt;
412 txt <<
"Hough Circle Transform Configuration:\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";
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;
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";
446 #ifdef VISP_HAVE_NLOHMANN_JSON
455 using json = nlohmann::json;
457 std::ifstream file(jsonFile);
459 std::stringstream ss;
460 ss <<
"Problem opening file " << jsonFile <<
". Make sure it exists and is readable" << std::endl;
465 j = json::parse(file);
467 catch (json::parse_error &e) {
468 std::stringstream msg;
469 msg <<
"Could not parse JSON file : \n";
471 msg << e.what() << std::endl;
472 msg <<
"Byte position of error: " << e.byte;
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);
507 filteringAndGradientName = j.value(
"filteringAndGradientType", filteringAndGradientName);
510 params.m_gaussianKernelSize = j.value(
"gaussianKernelSize", params.m_gaussianKernelSize);
511 const int checkEvenModulo = 2;
512 if ((params.m_gaussianKernelSize % checkEvenModulo) != 1) {
516 params.m_gaussianStdev = j.value(
"gaussianStdev", params.m_gaussianStdev);
517 if (params.m_gaussianStdev <= 0) {
521 params.m_gradientFilterKernelSize = j.value(
"gradientFilterKernelSize", params.m_gradientFilterKernelSize);
522 if ((params.m_gradientFilterKernelSize % checkEvenModulo) != 1) {
527 cannyBackendName = j.value(
"cannyBackendType", 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);
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);
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)) {
547 params.m_centerMinThresh = j.value(
"centerThresh", params.m_centerMinThresh);
548 if (params.m_centerMinThresh <= 0) {
552 params.m_expectedNbCenters = j.value(
"expectedNbCenters", params.m_expectedNbCenters);
555 params.m_circleProbaThresh = j.value(
"circleProbabilityThreshold", params.m_circleProbaThresh);
556 params.m_circleVisibilityRatioThresh = j.value(
"circleVisibilityRatioThreshold", params.m_circleVisibilityRatioThresh);
558 params.m_circlePerfectness = j.value(
"circlePerfectnessThreshold", params.m_circlePerfectness);
560 if ((params.m_circlePerfectness <= 0) || (params.m_circlePerfectness > 1)) {
564 params.m_recordVotingPoints = j.value(
"recordVotingPoints", params.m_recordVotingPoints);
566 params.m_centerMinDist = j.value(
"centerMinDistance", params.m_centerMinDist);
567 if (params.m_centerMinDist <= 0) {
571 params.m_mergingRadiusDiffThresh = j.value(
"mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh);
572 if (params.m_mergingRadiusDiffThresh <= 0) {
585 std::pair<float, float> radiusLimits = { params.m_minRadius, params.m_maxRadius };
589 {
"gaussianKernelSize", params.m_gaussianKernelSize},
590 {
"gaussianStdev", params.m_gaussianStdev},
591 {
"gradientFilterKernelSize", params.m_gradientFilterKernelSize},
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} };
620 int m_gaussianKernelSize;
622 float m_gaussianStdev;
625 int m_gradientFilterKernelSize;
628 float m_lowerCannyThresh;
630 float m_upperCannyThresh;
632 int m_edgeMapFilteringNbIter;
634 float m_lowerCannyThreshRatio;
636 float m_upperCannyThreshRatio;
640 std::pair<int, int> m_centerXlimits;
641 std::pair<int, int> m_centerYlimits;
644 int m_dilatationKernelSize;
645 int m_averagingWindowSize;
647 float m_centerMinThresh;
648 int m_expectedNbCenters;
652 float m_circleProbaThresh;
653 float m_circlePerfectness;
656 float m_circleVisibilityRatioThresh;
657 bool m_recordVotingPoints;
660 float m_centerMinDist;
661 float m_mergingRadiusDiffThresh;
666 #ifdef VISP_BUILD_DEPRECATED_FUNCTIONS
670 #ifndef DOXYGEN_SHOULD_SKIP_THIS
674 typedef struct vpCenterVotes
676 std::pair<float, float> m_position;
700 #ifdef HAVE_OPENCV_CORE
707 virtual std::vector<vpImageCircle> detect(
const cv::Mat &cv_I);
746 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17)
748 std::optional<
vpImage<bool> > &mask, std::optional<std::vector<std::vector<std::pair<unsigned int, unsigned int>>>> &opt_votingPoints)
const;
751 vpImage<bool> **mask, std::vector<std::vector<std::pair<unsigned int, unsigned int> > > **opt_votingPoints)
const;
756 #ifdef VISP_HAVE_NLOHMANN_JSON
772 virtual void initFromJSON(
const std::string &jsonPath);
781 virtual void saveConfigurationInJSON(
const std::string &jsonPath)
const;
815 void init(
const vpCircleHoughTransformParams &algoParams);
824 m_algoParams.m_filteringAndGradientType = type;
825 m_cannyVisp.setFilteringAndGradientType(type);
826 initGradientFilters();
838 m_algoParams.m_gaussianKernelSize = kernelSize;
839 m_algoParams.m_gaussianStdev = stdev;
841 const unsigned int checkEvenModulo = 2;
842 if ((m_algoParams.m_gaussianKernelSize % checkEvenModulo) != 1) {
846 if (m_algoParams.m_gaussianStdev <= 0) {
850 initGaussianFilters();
860 m_algoParams.m_gradientFilterKernelSize = apertureSize;
862 const unsigned int checkEvenModulo = 2;
863 if ((m_algoParams.m_gradientFilterKernelSize % checkEvenModulo) != 1) {
867 initGradientFilters();
877 m_algoParams.m_cannyBackendType = type;
889 inline void setCannyThreshold(
const float &lowerCannyThreshold,
const float &upperCannyThreshold)
891 m_algoParams.m_lowerCannyThresh = lowerCannyThreshold;
892 m_algoParams.m_upperCannyThresh = upperCannyThreshold;
906 m_algoParams.m_lowerCannyThreshRatio = lowerThreshRatio;
907 m_algoParams.m_upperCannyThreshRatio = upperThreshRatio;
908 m_cannyVisp.setCannyThresholdsRatio(lowerThreshRatio, upperThreshRatio);
919 m_algoParams.m_centerMinDist = center_min_dist;
921 if (m_algoParams.m_centerMinDist <= 0) {
939 const int ¢er_min_y,
const int ¢er_max_y)
941 m_algoParams.m_centerXlimits.first = center_min_x;
942 m_algoParams.m_centerXlimits.second = center_max_x;
943 m_algoParams.m_centerYlimits.first = center_min_y;
944 m_algoParams.m_centerYlimits.second = center_max_y;
953 m_algoParams.m_minRadius = circle_min_radius;
962 m_algoParams.m_maxRadius = circle_max_radius;
974 m_algoParams.m_circlePerfectness = circle_perfectness;
975 if ((m_algoParams.m_circlePerfectness <= 0) || (m_algoParams.m_circlePerfectness > 1)) {
991 const int &averagingWindowSize = 5,
const int expectedNbCenters = -1)
993 m_algoParams.m_dilatationKernelSize = dilatationSize;
994 m_algoParams.m_centerMinThresh = centerThresh;
995 m_algoParams.m_averagingWindowSize = averagingWindowSize;
996 m_algoParams.m_expectedNbCenters = expectedNbCenters;
998 const int minDilatationKernel = 3;
999 const unsigned int checkEvenModulo = 2;
1000 if (m_algoParams.m_dilatationKernelSize < minDilatationKernel) {
1003 else if ((m_algoParams.m_dilatationKernelSize % checkEvenModulo) == 0) {
1007 if (m_algoParams.m_centerMinThresh <= 0.f) {
1011 if ((m_algoParams.m_averagingWindowSize <= 0) || ((m_algoParams.m_averagingWindowSize % checkEvenModulo) == 0)) {
1023 m_algoParams.m_circleProbaThresh = radiusRatioThresh;
1025 if (m_algoParams.m_circleProbaThresh <= 0) {
1038 m_algoParams.m_mergingRadiusDiffThresh = radiusDifferenceThresh;
1040 if (m_algoParams.m_mergingRadiusDiffThresh <= 0) {
1076 m_algoParams.m_recordVotingPoints = record;
1089 return m_centerCandidatesList;
1099 return m_centerVotes;
1110 return m_circleCandidates;
1120 return m_circleCandidatesProbabilities;
1130 return m_circleCandidatesVotes;
1169 return m_algoParams.m_upperCannyThresh;
1177 return m_algoParams.m_centerMinDist;
1185 return m_algoParams.m_minRadius;
1193 return m_algoParams.m_maxRadius;
1201 return m_finalCirclesProbabilities;
1209 return m_finalCircleVotes;
1217 if (!m_algoParams.m_recordVotingPoints) {
1220 return m_finalCirclesVotingPoints;
1228 return m_algoParams.getRecordVotingPoints();
1235 std::string toString()
const;
1247 #ifndef DOXYGEN_SHOULD_SKIP_THIS
1251 typedef struct vpCentersBarycenter
1253 std::pair<float, float> m_position;
1256 }vpCentersBarycenter;
1263 virtual void initGaussianFilters();
1268 virtual void initGradientFilters();
1290 virtual void filterEdgeMap();
1297 virtual void computeCenterCandidates();
1303 virtual void filterCenterCandidates(
const std::vector<vpCenterVotes> &peak_positions_votes);
1316 virtual vpCentersBarycenter mergeSimilarCenters(
const unsigned int &idPeak,
const unsigned int &nbPeaks,
const float &squared_distance_max,
const std::vector<vpCenterVotes> &peak_positions_votes, std::vector<bool> &has_been_merged);
1328 virtual float computeCircleProbability(
const vpImageCircle &circle,
const unsigned int &nbVotes);
1338 virtual void computeCircleCandidates();
1343 virtual void mergeCircleCandidates();
1356 virtual void mergeCandidates(std::vector<vpImageCircle> &circleCandidates, std::vector<unsigned int> &circleCandidatesVotes,
1357 std::vector<float> &circleCandidatesProba, std::vector<std::vector<std::pair<unsigned int, unsigned int> > > &votingPoints);
Class that implements the Canny's edge detector. It is possible to use a boolean mask to ignore some ...
error that can be emitted by ViSP classes.
@ badValue
Used to indicate that a value is not in the allowed range.
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.