Visual Servoing Platform version 3.6.0
Loading...
Searching...
No Matches
vpCircleHoughTransform.h
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2023 by Inria. All rights reserved.
4 *
5 * This software is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 * See the file LICENSE.txt at the root directory of this source
10 * distribution for additional information about the GNU GPL.
11 *
12 * For using ViSP with software that can not be combined with the GNU
13 * GPL, please contact Inria about acquiring a ViSP Professional
14 * Edition License.
15 *
16 * See https://visp.inria.fr for more information.
17 *
18 * This software was developed at:
19 * Inria Rennes - Bretagne Atlantique
20 * Campus Universitaire de Beaulieu
21 * 35042 Rennes Cedex
22 * France
23 *
24 * If you have questions regarding the use of this file, please contact
25 * Inria at visp@inria.fr
26 *
27 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29 */
30
31#ifndef _vpCircleHoughTransform_h_
32#define _vpCircleHoughTransform_h_
33
34 // System includes
35#include <utility>
36#include <vector>
37
38// ViSP includes
39#include <visp3/core/vpConfig.h>
40#include <visp3/core/vpCannyEdgeDetection.h>
41#include <visp3/core/vpImage.h>
42#include <visp3/core/vpImageCircle.h>
43#include <visp3/core/vpImageDraw.h>
44#include <visp3/core/vpImageFilter.h>
45#include <visp3/core/vpImagePoint.h>
46#include <visp3/core/vpMatrix.h>
47#include <visp3/core/vpRect.h>
48
49#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
50
51// 3rd parties inclue
52#ifdef VISP_HAVE_NLOHMANN_JSON
53#include <nlohmann/json.hpp>
55using json = nlohmann::json;
56#endif
57
66class VISP_EXPORT vpCircleHoughTransform
67{
68public:
73 {
74 private:
75 // // Gaussian smoothing attributes
76 int m_gaussianKernelSize;
77 float m_gaussianStdev;
79 // // Gradient computation attributes
80 int m_sobelKernelSize;
82 // // Edge detection attributes
83 float m_lowerCannyThresh;
85 float m_upperCannyThresh;
87 int m_edgeMapFilteringNbIter;
89 // // Center candidates computation attributes
90 std::pair<int, int> m_centerXlimits;
91 std::pair<int, int> m_centerYlimits;
92 unsigned int m_minRadius;
93 unsigned int m_maxRadius;
94 int m_dilatationNbIter;
95 float m_centerThresh;
97 // // Circle candidates computation attributes
98 float m_radiusRatioThresh;
99 float m_circlePerfectness;
101 // // Circle candidates merging attributes
102 float m_centerMinDist;
103 float m_mergingRadiusDiffThresh;
106 public:
111 : m_gaussianKernelSize(5)
112 , m_gaussianStdev(1.f)
113 , m_sobelKernelSize(3)
114 , m_lowerCannyThresh(-1.f)
115 , m_upperCannyThresh(-1.f)
116 , m_edgeMapFilteringNbIter(1)
117 , m_centerXlimits(std::pair<int, int>(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()))
118 , m_centerYlimits(std::pair<int, int>(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()))
119 , m_minRadius(0)
120 , m_maxRadius(1000)
121 , m_dilatationNbIter(1)
122 , m_centerThresh(50.f)
123 , m_radiusRatioThresh(2.f)
124 , m_circlePerfectness(0.9f)
125 , m_centerMinDist(15.f)
126 , m_mergingRadiusDiffThresh(1.5f * m_centerMinDist)
127 {
128
129 }
130
154 const int &gaussianKernelSize
155 , const float &gaussianStdev
156 , const int &sobelKernelSize
157 , const float &lowerCannyThresh
158 , const float &upperCannyThresh
159 , const int &edgeMapFilterNbIter
160 , const std::pair<int, int> &centerXlimits
161 , const std::pair<int, int> &centerYlimits
162 , const unsigned int &minRadius
163 , const unsigned int &maxRadius
164 , const int &dilatationNbIter
165 , const float &centerThresh
166 , const float &radiusThreshRatio
167 , const float &circlePerfectness
168 , const float &centerMinDistThresh
169 , const float &mergingRadiusDiffThresh
170 )
171 : m_gaussianKernelSize(gaussianKernelSize)
172 , m_gaussianStdev(gaussianStdev)
173 , m_sobelKernelSize(sobelKernelSize)
174 , m_lowerCannyThresh(lowerCannyThresh)
175 , m_upperCannyThresh(upperCannyThresh)
176 , m_edgeMapFilteringNbIter(edgeMapFilterNbIter)
177 , m_centerXlimits(centerXlimits)
178 , m_centerYlimits(centerYlimits)
179 , m_minRadius(std::min(minRadius, maxRadius))
180 , m_maxRadius(std::max(minRadius, maxRadius))
181 , m_dilatationNbIter(dilatationNbIter)
182 , m_centerThresh(centerThresh)
183 , m_radiusRatioThresh(radiusThreshRatio)
184 , m_circlePerfectness(circlePerfectness)
185 , m_centerMinDist(centerMinDistThresh)
186 , m_mergingRadiusDiffThresh(mergingRadiusDiffThresh)
187 {
188
189 }
190
194 std::string toString() const
195 {
196 std::string txt("Hough Circle Transform Configuration:\n");
197 txt += "\tGaussian filter kernel size = " + std::to_string(m_gaussianKernelSize) + "\n";
198 txt += "\tGaussian filter standard deviation = " + std::to_string(m_gaussianStdev) + "\n";
199 txt += "\tSobel filter kernel size = " + std::to_string(m_sobelKernelSize) + "\n";
200 txt += "\tCanny edge filter thresholds = [" + std::to_string(m_lowerCannyThresh) + " ; " + std::to_string(m_upperCannyThresh) + "]\n";
201 txt += "\tEdge map 8-neighbor connectivity filtering number of iterations = " + std::to_string(m_edgeMapFilteringNbIter) + "\n";
202 txt += "\tCenter horizontal position limits: min = " + std::to_string(m_centerXlimits.first) + "\tmax = " + std::to_string(m_centerXlimits.second) +"\n";
203 txt += "\tCenter vertical position limits: min = " + std::to_string(m_centerYlimits.first) + "\tmax = " + std::to_string(m_centerYlimits.second) +"\n";
204 txt += "\tRadius limits: min = " + std::to_string(m_minRadius) + "\tmax = " + std::to_string(m_maxRadius) +"\n";
205 txt += "\tNumber of repetitions of the dilatation filter = " + std::to_string(m_dilatationNbIter) + "\n";
206 txt += "\tCenters votes threshold = " + std::to_string(m_centerThresh) + "\n";
207 txt += "\tRadius votes per radian threshold = " + std::to_string(m_radiusRatioThresh) + "\n";
208 txt += "\tCircle perfectness threshold = " + std::to_string(m_circlePerfectness) + "\n";
209 txt += "\tCenters minimum distance = " + std::to_string(m_centerMinDist) + "\n";
210 txt += "\tRadius difference merging threshold = " + std::to_string(m_mergingRadiusDiffThresh) + "\n";
211 return txt;
212 }
213
214 // // Configuration from files
215#ifdef VISP_HAVE_NLOHMANN_JSON
222 inline static vpCircleHoughTransformParameters createFromJSON(const std::string &jsonFile)
223 {
224 std::ifstream file(jsonFile);
225 if (!file.good()) {
226 std::stringstream ss;
227 ss << "Problem opening file " << jsonFile << ". Make sure it exists and is readable" << std::endl;
228 throw vpException(vpException::ioError, ss.str());
229 }
230 json j;
231 try {
232 j = json::parse(file);
233 }
234 catch (json::parse_error &e) {
235 std::stringstream msg;
236 msg << "Could not parse JSON file : \n";
237
238 msg << e.what() << std::endl;
239 msg << "Byte position of error: " << e.byte;
240 throw vpException(vpException::ioError, msg.str());
241 }
242 vpCircleHoughTransformParameters params = j; // Call from_json(const json& j, vpDetectorDNN& *this) to read json
243 file.close();
244 return params;
245 }
246
254 inline void saveConfigurationInJSON(const std::string &jsonPath) const
255 {
256 std::ofstream file(jsonPath);
257 const json j = *this;
258 file << j.dump(4);
259 file.close();
260 }
261
269 inline friend void from_json(const json &j, vpCircleHoughTransformParameters &params)
270 {
271 params.m_gaussianKernelSize = j.value("gaussianKernelSize", params.m_gaussianKernelSize);
272 if ((params.m_gaussianKernelSize % 2) != 1) {
273 throw vpException(vpException::badValue, "Gaussian Kernel size should be odd.");
274 }
275
276 params.m_gaussianStdev = j.value("gaussianStdev", params.m_gaussianStdev);
277 if (params.m_gaussianStdev <= 0) {
278 throw vpException(vpException::badValue, "Standard deviation should be > 0");
279 }
280
281 params.m_sobelKernelSize = j.value("sobelKernelSize", params.m_sobelKernelSize);
282 if ((params.m_sobelKernelSize % 2) != 1) {
283 throw vpException(vpException::badValue, "Sobel Kernel size should be odd.");
284 }
285
286 params.m_lowerCannyThresh = j.value("lowerCannyThresh", params.m_lowerCannyThresh);
287 params.m_upperCannyThresh = j.value("upperCannyThresh", params.m_upperCannyThresh);
288 params.m_edgeMapFilteringNbIter = j.value("edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter);
289
290 params.m_centerXlimits = j.value("centerXlimits", params.m_centerXlimits);
291 params.m_centerYlimits = j.value("centerYlimits", params.m_centerYlimits);
292 std::pair<unsigned int, unsigned int> radiusLimits = j.value("radiusLimits", std::pair<unsigned int, unsigned int>(params.m_minRadius, params.m_maxRadius));
293 params.m_minRadius = std::min(radiusLimits.first, radiusLimits.second);
294 params.m_maxRadius = std::max(radiusLimits.first, radiusLimits.second);
295
296 params.m_dilatationNbIter = j.value("dilatationNbIter", params.m_dilatationNbIter);
297
298 params.m_centerThresh = j.value("centerThresh", params.m_centerThresh);
299 if (params.m_centerThresh <= 0) {
300 throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive.");
301 }
302
303 params.m_radiusRatioThresh = j.value("radiusThreshRatio", params.m_radiusRatioThresh);
304
305 params.m_circlePerfectness = j.value("circlePerfectnessThreshold", params.m_circlePerfectness);
306
307 if (params.m_circlePerfectness <= 0 || params.m_circlePerfectness > 1) {
308 throw vpException(vpException::badValue, "Circle perfectness must be in the interval ] 0; 1].");
309 }
310
311 params.m_centerMinDist = j.value("centerMinDistance", params.m_centerMinDist);
312 if (params.m_centerMinDist <= 0) {
313 throw vpException(vpException::badValue, "Centers minimum distance threshold must be positive.");
314 }
315
316 params.m_mergingRadiusDiffThresh = j.value("mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh);
317 if (params.m_mergingRadiusDiffThresh <= 0) {
318 throw vpException(vpException::badValue, "Radius difference merging threshold must be positive.");
319 }
320 }
321
328 inline friend void to_json(json &j, const vpCircleHoughTransformParameters &params)
329 {
330 std::pair<unsigned int, unsigned int> radiusLimits = { params.m_minRadius, params.m_maxRadius };
331
332 j = json {
333 {"gaussianKernelSize", params.m_gaussianKernelSize},
334 {"gaussianStdev", params.m_gaussianStdev},
335 {"sobelKernelSize", params.m_sobelKernelSize},
336 {"lowerCannyThresh", params.m_lowerCannyThresh},
337 {"upperCannyThresh", params.m_upperCannyThresh},
338 {"edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter},
339 {"centerXlimits", params.m_centerXlimits},
340 {"centerYlimits", params.m_centerYlimits},
341 {"radiusLimits", radiusLimits},
342 {"dilatationNbIter", params.m_dilatationNbIter},
343 {"centerThresh", params.m_centerThresh},
344 {"radiusThreshRatio", params.m_radiusRatioThresh},
345 {"circlePerfectnessThreshold", params.m_circlePerfectness},
346 {"centerMinDistance", params.m_centerMinDist},
347 {"mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh} };
348 }
349#endif
350};
351
356
362 vpCircleHoughTransform(const vpCircleHoughTransformParameters &algoParams);
363
367 virtual ~vpCircleHoughTransform();
368
369 // // Detection methods
370
371#ifdef HAVE_OPENCV_CORE
378 std::vector<vpImageCircle> detect(const cv::Mat &cv_I);
379#endif
380
388 std::vector<vpImageCircle> detect(const vpImage<vpRGBa> &I);
389
396 std::vector<vpImageCircle> detect(const vpImage<unsigned char> &I);
397
398#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
409 std::vector<vpImageCircle> detect(const vpImage<unsigned char> &I, const int &nbCircles);
410#endif
411
412 // // Configuration from files
413#ifdef VISP_HAVE_NLOHMANN_JSON
420 vpCircleHoughTransform(const std::string &jsonPath);
421
429 void initFromJSON(const std::string &jsonPath);
430
438 void saveConfigurationInJSON(const std::string &jsonPath) const;
439
447 inline friend void from_json(const json &j, vpCircleHoughTransform &detector)
448 {
449 detector.m_algoParams = j;
450 }
451
458 inline friend void to_json(json &j, const vpCircleHoughTransform &detector)
459 {
460 j = detector.m_algoParams;
461 }
462#endif
463
464 // // Setters
470 void init(const vpCircleHoughTransformParameters &algoParams);
471
479 inline void setGaussianParameters(const int &kernelSize, const float &stdev)
480 {
481 m_algoParams.m_gaussianKernelSize = kernelSize;
482 m_algoParams.m_gaussianStdev = stdev;
483
484 if ((m_algoParams.m_gaussianKernelSize % 2) != 1) {
485 throw vpException(vpException::badValue, "Gaussian Kernel size should be odd.");
486 }
487
488 if (m_algoParams.m_gaussianStdev <= 0) {
489 throw vpException(vpException::badValue, "Standard deviation should be > 0");
490 }
491
492 initGaussianFilters();
493 }
494
504 inline void setCannyThreshold(const float &lowerCannyThreshold, const float &upperCannyThreshold)
505 {
506 m_algoParams.m_lowerCannyThresh = lowerCannyThreshold;
507 m_algoParams.m_upperCannyThresh = upperCannyThreshold;
508 }
509
516 inline void setCircleCenterMinDist(const float &center_min_dist)
517 {
518 m_algoParams.m_centerMinDist = center_min_dist;
519
520 if (m_algoParams.m_centerMinDist <= 0) {
521 throw vpException(vpException::badValue, "Circles center min distance must be positive.");
522 }
523 }
524
537 void setCircleCenterBoundingBox(const int &center_min_x, const int &center_max_x,
538 const int &center_min_y, const int &center_max_y)
539 {
540 m_algoParams.m_centerXlimits.first = center_min_x;
541 m_algoParams.m_centerXlimits.second = center_max_x;
542 m_algoParams.m_centerYlimits.first = center_min_y;
543 m_algoParams.m_centerYlimits.second = center_max_y;
544 }
545
550 inline void setCircleMinRadius(const float &circle_min_radius)
551 {
552 m_algoParams.m_minRadius = static_cast<unsigned int>(circle_min_radius);
553 }
554
559 inline void setCircleMaxRadius(const float &circle_max_radius)
560 {
561 m_algoParams.m_maxRadius = static_cast<unsigned int>(circle_max_radius);
562 }
563
568 void setCirclePerfectness(const float &circle_perfectness)
569 {
570 m_algoParams.m_circlePerfectness = circle_perfectness;
571 if (m_algoParams.m_circlePerfectness <= 0 || m_algoParams.m_circlePerfectness > 1) {
572 throw vpException(vpException::badValue, "Circle perfectness must be in the interval ] 0; 1].");
573 }
574 }
575
582 inline void setCenterComputationParameters(const int &dilatationRepet, const float &centerThresh)
583 {
584 m_algoParams.m_dilatationNbIter = dilatationRepet;
585 m_algoParams.m_centerThresh = centerThresh;
586
587 if (m_algoParams.m_centerThresh <= 0) {
588 throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive.");
589 }
590 }
591
597 inline void setRadiusRatioThreshold(const float &radiusRatioThresh)
598 {
599 m_algoParams.m_radiusRatioThresh = radiusRatioThresh;
600
601 if (m_algoParams.m_radiusRatioThresh <= 0) {
602 throw vpException(vpException::badValue, "Radius ratio threshold must be > 0.");
603 }
604 }
605
612 inline void setRadiusMergingThresholds(const float &radiusDifferenceThresh)
613 {
614 m_algoParams.m_mergingRadiusDiffThresh = radiusDifferenceThresh;
615
616 if (m_algoParams.m_mergingRadiusDiffThresh <= 0) {
617 throw vpException(vpException::badValue, "Radius difference merging threshold must be positive.");
618 }
619 }
620
621 // // Getters
622
628 inline std::vector<std::pair<int, int> > getCenterCandidatesList()
629 {
630 return m_centerCandidatesList;
631 }
632
638 inline std::vector<int> getCenterCandidatesVotes()
639 {
640 return m_centerVotes;
641 }
642
649 inline std::vector<vpImageCircle> getCircleCandidates()
650 {
651 return m_circleCandidates;
652 }
653
659 inline std::vector<unsigned int> getCircleCandidatesVotes()
660 {
661 return m_circleCandidatesVotes;
662 }
663
670 {
671 return m_dIx;
672 }
673
680 {
681 return m_dIy;
682 }
683
690 {
691 return m_edgeMap;
692 }
693
698 inline float getCannyThreshold() const
699 {
700 return m_algoParams.m_upperCannyThresh;
701 }
702
706 inline float getCircleCenterMinDist() const
707 {
708 return m_algoParams.m_centerMinDist;
709 }
710
714 inline unsigned int getCircleMinRadius() const
715 {
716 return m_algoParams.m_minRadius;
717 }
718
722 inline unsigned int getCircleMaxRadius() const
723 {
724 return m_algoParams.m_maxRadius;
725 }
726
730 std::string toString() const;
731
735 friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpCircleHoughTransform &detector);
736
737private:
742 void initGaussianFilters();
743
751 void computeGradientsAfterGaussianSmoothing(const vpImage<unsigned char> &I);
752
759 void edgeDetection(const vpImage<unsigned char> &I);
760
764 void filterEdgeMap();
765
771 void computeCenterCandidates();
772
781 void computeCircleCandidates();
782
790 void mergeCircleCandidates();
791
792
793 vpCircleHoughTransformParameters m_algoParams;
794 // // Gaussian smoothing attributes
795 vpArray2D<float> m_fg;
796 vpArray2D<float> m_fgDg;
797 vpImage<float> m_Ifilt;
799 // // Gradient computation attributes
800 vpImage<float> m_dIx;
801 vpImage<float> m_dIy;
803 // // Edge detection attributes
804 vpCannyEdgeDetection m_cannyVisp;
805 vpImage<unsigned char> m_edgeMap;
807 // // Center candidates computation attributes
808 std::vector<std::pair<unsigned int, unsigned int> > m_edgePointsList;
809 std::vector<std::pair<int, int> > m_centerCandidatesList;
810 std::vector<int> m_centerVotes;
812 // // Circle candidates computation attributes
813 std::vector<vpImageCircle> m_circleCandidates;
814 std::vector<unsigned int> m_circleCandidatesVotes;
816 // // Circle candidates merging attributes
817 std::vector<vpImageCircle> m_finalCircles;
818 std::vector<unsigned int> m_finalCircleVotes;
819};
820#endif
821#endif
Implementation of a generic 2D array used as base class for matrices and vectors.
Definition vpArray2D.h:131
json namespace shortcut
static vpCircleHoughTransformParameters createFromJSON(const std::string &jsonFile)
Create a new vpCircleHoughTransformParameters from a JSON file.
vpCircleHoughTransformParameters()
Construct a new vpCircleHoughTransformParameters object with default parameters.
friend void to_json(json &j, const vpCircleHoughTransformParameters &params)
Parse a vpCircleHoughTransform into JSON format.
void saveConfigurationInJSON(const std::string &jsonPath) const
Save the configuration of the detector in a JSON file described by the path jsonPath....
vpCircleHoughTransformParameters(const int &gaussianKernelSize, const float &gaussianStdev, const int &sobelKernelSize, const float &lowerCannyThresh, const float &upperCannyThresh, const int &edgeMapFilterNbIter, const std::pair< int, int > &centerXlimits, const std::pair< int, int > &centerYlimits, const unsigned int &minRadius, const unsigned int &maxRadius, const int &dilatationNbIter, const float &centerThresh, const float &radiusThreshRatio, const float &circlePerfectness, const float &centerMinDistThresh, const float &mergingRadiusDiffThresh)
Construct a new vpCircleHoughTransformParameters object.
friend void from_json(const json &j, vpCircleHoughTransformParameters &params)
Read the detector configuration from JSON. All values are optional and if an argument is not present,...
Class that permits to detect 2D circles in a image using the gradient-based Circle Hough transform....
std::vector< vpImageCircle > getCircleCandidates()
Get the Circle Candidates before merging step.
unsigned int getCircleMinRadius() const
void setCenterComputationParameters(const int &dilatationRepet, const float &centerThresh)
Set the parameters of the computation of the circle center candidates.
vpImage< unsigned char > getEdgeMap()
Get the Edge Map computed thanks to the Canny edge filter.
void setCirclePerfectness(const float &circle_perfectness)
vpImage< float > getGradientY()
Get the gradient along the vertical axis of the image.
std::vector< std::pair< int, int > > getCenterCandidatesList()
Get the list of Center Candidates, stored as pair <idRow, idCol>
vpImage< float > getGradientX()
Get the gradient along the horizontal axis of the image.
unsigned int getCircleMaxRadius() const
friend void from_json(const json &j, vpCircleHoughTransform &detector)
Read the detector configuration from JSON. All values are optional and if an argument is not present,...
void setCannyThreshold(const float &lowerCannyThreshold, const float &upperCannyThreshold)
void setCircleCenterBoundingBox(const int &center_min_x, const int &center_max_x, const int &center_min_y, const int &center_max_y)
std::vector< int > getCenterCandidatesVotes()
Get the number of votes of each Center Candidates.
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 ...
friend void to_json(json &j, const vpCircleHoughTransform &detector)
Parse a vpCircleHoughTransform into JSON format.
void setCircleMaxRadius(const float &circle_max_radius)
void setCircleMinRadius(const float &circle_min_radius)
std::vector< unsigned int > getCircleCandidatesVotes()
Get the votes accumulator of the Circle Candidates.
void setGaussianParameters(const int &kernelSize, const float &stdev)
Set the parameters of the Gaussian filter, that computes the gradients of the image.
void setCircleCenterMinDist(const float &center_min_dist)
error that can be emitted by ViSP classes.
Definition vpException.h:59
@ ioError
I/O error.
Definition vpException.h:79
@ badValue
Used to indicate that a value is not in the allowed range.
Definition vpException.h:85
Definition of the vpImage class member functions.
Definition vpImage.h:135