Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpCircleHoughTransform_common.cpp
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2024 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#include <visp3/core/vpImageConvert.h>
32#include <visp3/core/vpImageMorphology.h>
33
34#include <visp3/imgproc/vpCircleHoughTransform.h>
35
37
38#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98)
39namespace
40{
41
42// Sorting by decreasing probabilities
43bool hasBetterProba(std::pair<size_t, float> a, std::pair<size_t, float> b)
44{
45 return (a.second > b.second);
46}
47
48
49void
50scaleFilter(vpArray2D<float> &filter, const float &scale)
51{
52 unsigned int nbRows = filter.getRows();
53 unsigned int nbCols = filter.getCols();
54 for (unsigned int r = 0; r < nbRows; ++r) {
55 for (unsigned int c = 0; c < nbCols; ++c) {
56 filter[r][c] = filter[r][c] * scale;
57 }
58 }
59}
60}
61#endif
62
70
78
79void
86
89
90#ifdef VISP_HAVE_NLOHMANN_JSON
91using json = nlohmann::json;
92
94 : mp_mask(nullptr)
95{
96 initFromJSON(jsonPath);
97}
98
99void
100vpCircleHoughTransform::initFromJSON(const std::string &jsonPath)
101{
102 std::ifstream file(jsonPath);
103 if (!file.good()) {
104 std::stringstream ss;
105 ss << "Problem opening file " << jsonPath << ". Make sure it exists and is readable" << std::endl;
106 throw vpException(vpException::ioError, ss.str());
107 }
108 json j;
109 try {
110 j = json::parse(file);
111 }
112 catch (json::parse_error &e) {
113 std::stringstream msg;
114 msg << "Could not parse JSON file : \n";
115
116 msg << e.what() << std::endl;
117 msg << "Byte position of error: " << e.byte;
118 throw vpException(vpException::ioError, msg.str());
119 }
120 m_algoParams = j; // Call from_json(const json& j, vpDetectorDNN& *this) to read json
121 file.close();
124}
125
126void
127vpCircleHoughTransform::saveConfigurationInJSON(const std::string &jsonPath) const
128{
129 m_algoParams.saveConfigurationInJSON(jsonPath);
130}
131#endif
132
133void
135{
136 const int filterHalfSize = (m_algoParams.m_gaussianKernelSize + 1) / 2;
137 m_fg.resize(1, filterHalfSize);
138 vpImageFilter::getGaussianKernel(m_fg.data, m_algoParams.m_gaussianKernelSize, m_algoParams.m_gaussianStdev, true);
139 m_cannyVisp.setGaussianFilterParameters(m_algoParams.m_gaussianKernelSize, m_algoParams.m_gaussianStdev);
140}
141
142void
144{
145#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) // Check if cxx11 or higher
146 // Helper to apply the scale to the raw values of the filters
147 auto scaleFilter = [](vpArray2D<float> &filter, const float &scale) {
148 const unsigned int nbRows = filter.getRows();
149 const unsigned int nbCols = filter.getCols();
150 for (unsigned int r = 0; r < nbRows; ++r) {
151 for (unsigned int c = 0; c < nbCols; ++c) {
152 filter[r][c] = filter[r][c] * scale;
153 }
154 }
155 };
156#endif
157
158 const int moduloCheckForOddity = 2;
159 if ((m_algoParams.m_gradientFilterKernelSize % moduloCheckForOddity) != 1) {
160 throw vpException(vpException::badValue, "Gradient filters Kernel size should be odd.");
161 }
162 m_gradientFilterX.resize(m_algoParams.m_gradientFilterKernelSize, m_algoParams.m_gradientFilterKernelSize);
163 m_gradientFilterY.resize(m_algoParams.m_gradientFilterKernelSize, m_algoParams.m_gradientFilterKernelSize);
164 m_cannyVisp.setGradientFilterAperture(m_algoParams.m_gradientFilterKernelSize);
165
166 float scaleX = 1.f;
167 float scaleY = 1.f;
168 unsigned int filterHalfSize = (m_algoParams.m_gradientFilterKernelSize - 1) / 2;
169
170 if (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) {
171 // Compute the Sobel filters
172 scaleX = vpImageFilter::getSobelKernelX(m_gradientFilterX.data, filterHalfSize);
173 scaleY = vpImageFilter::getSobelKernelY(m_gradientFilterY.data, filterHalfSize);
174 }
175 else if (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) {
176 // Compute the Scharr filters
177 scaleX = vpImageFilter::getScharrKernelX(m_gradientFilterX.data, filterHalfSize);
178 scaleY = vpImageFilter::getScharrKernelY(m_gradientFilterY.data, filterHalfSize);
179 }
180 else {
181 std::string errMsg = "[vpCircleHoughTransform::initGradientFilters] Error: gradient filtering method \"";
182 errMsg += vpImageFilter::vpCannyFiltAndGradTypeToStr(m_algoParams.m_filteringAndGradientType);
183 errMsg += "\" has not been implemented yet\n";
185 }
186 scaleFilter(m_gradientFilterX, scaleX);
187 scaleFilter(m_gradientFilterY, scaleY);
188}
189
190std::vector<vpImageCircle>
192{
194 vpImageConvert::convert(I, I_gray);
195 return detect(I_gray);
196}
197
198#ifdef HAVE_OPENCV_CORE
199std::vector<vpImageCircle>
200vpCircleHoughTransform::detect(const cv::Mat &cv_I)
201{
203 vpImageConvert::convert(cv_I, I_gray);
204 return detect(I_gray);
205}
206#endif
207
208std::vector<vpImageCircle>
210{
211 std::vector<vpImageCircle> detections = detect(I);
212 size_t nbDetections = detections.size();
213
214 // Prepare vector of tuple to sort by decreasing probabilities
215 std::vector<std::pair<size_t, float> > v_id_proba;
216 for (size_t i = 0; i < nbDetections; ++i) {
217 std::pair<size_t, float> id_proba(i, m_finalCirclesProbabilities[i]);
218 v_id_proba.push_back(id_proba);
219 }
220
221#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
222 // Sorting by decreasing probabilities
223 auto hasBetterProba
224 = [](std::pair<size_t, float> a, std::pair<size_t, float> b) {
225 return (a.second > b.second);
226 };
227#endif
228 std::sort(v_id_proba.begin(), v_id_proba.end(), hasBetterProba);
229
230 // Clearing the storages containing the detection results
231 // to have it sorted by decreasing probabilities
232 size_t limitMin;
233 if (nbCircles < 0) {
234 limitMin = nbDetections;
235 }
236 else {
237 limitMin = std::min(nbDetections, static_cast<size_t>(nbCircles));
238 }
239
240 std::vector<vpImageCircle> bestCircles;
241 std::vector<vpImageCircle> copyFinalCircles = m_finalCircles;
242 std::vector<unsigned int> copyFinalCirclesVotes = m_finalCircleVotes;
243 std::vector<float> copyFinalCirclesProbas = m_finalCirclesProbabilities;
244 std::vector<std::vector<std::pair<unsigned int, unsigned int> > > copyFinalCirclesVotingPoints = m_finalCirclesVotingPoints;
245 for (size_t i = 0; i < nbDetections; ++i) {
246 size_t id = v_id_proba[i].first;
247 m_finalCircles[i] = copyFinalCircles[id];
248 m_finalCircleVotes[i] = copyFinalCirclesVotes[id];
249 m_finalCirclesProbabilities[i] = copyFinalCirclesProbas[id];
250 if (m_algoParams.m_recordVotingPoints) {
251 m_finalCirclesVotingPoints[i] = copyFinalCirclesVotingPoints[id];
252 }
253 if (i < limitMin) {
254 bestCircles.push_back(m_finalCircles[i]);
255 }
256 }
257
258 return bestCircles;
259}
260
261std::vector<vpImageCircle>
263{
264 // Cleaning results of potential previous detection
266 m_centerVotes.clear();
267 m_edgePointsList.clear();
268 m_circleCandidates.clear();
272 m_finalCircles.clear();
273 m_finalCircleVotes.clear();
276
277 // Ensuring that the difference between the max and min radii is big enough to take into account
278 // the pixelization of the image
279 const float minRadiusDiff = 3.f;
280 if ((m_algoParams.m_maxRadius - m_algoParams.m_minRadius) < minRadiusDiff) {
281 if (m_algoParams.m_minRadius > (minRadiusDiff / 2.f)) {
282 m_algoParams.m_maxRadius += minRadiusDiff / 2.f;
283 m_algoParams.m_minRadius -= minRadiusDiff / 2.f;
284 }
285 else {
286 m_algoParams.m_maxRadius += minRadiusDiff - m_algoParams.m_minRadius;
287 m_algoParams.m_minRadius = 0.f;
288 }
289 }
290
291 // Ensuring that the difference between the max and min center position is big enough to take into account
292 // the pixelization of the image
293 const float minCenterPositionDiff = 3.f;
294 if ((m_algoParams.m_centerXlimits.second - m_algoParams.m_centerXlimits.first) < minCenterPositionDiff) {
295 m_algoParams.m_centerXlimits.second += static_cast<int>(minCenterPositionDiff / 2.f);
296 m_algoParams.m_centerXlimits.first -= static_cast<int>(minCenterPositionDiff / 2.f);
297 }
298 if ((m_algoParams.m_centerYlimits.second - m_algoParams.m_centerYlimits.first) < minCenterPositionDiff) {
299 m_algoParams.m_centerYlimits.second += static_cast<int>(minCenterPositionDiff / 2.f);
300 m_algoParams.m_centerYlimits.first -= static_cast<int>(minCenterPositionDiff / 2.f);
301 }
302
303 // First thing, we need to apply a Gaussian filter on the image to remove some spurious noise
304 // Then, we need to compute the image gradients in order to be able to perform edge detection
306
307 // Using the gradients, it is now possible to perform edge detection
308 // We rely on the Canny edge detector
309 // It will also give us the connected edged points
310 edgeDetection(I);
311
312 // From the edge map and gradient information, it is possible to compute
313 // the center point candidates
315
316 // From the edge map and center point candidates, we can compute candidate
317 // circles. These candidate circles are circles whose center belong to
318 // the center point candidates and whose radius is a "radius bin" that got
319 // enough votes by computing the distance between each point of the edge map
320 // and the center point candidate
322
323 // Finally, we perform a merging operation that permits to merge circles
324 // respecting similarity criteria (distance between centers and similar radius)
326
327 return m_finalCircles;
328}
329
330VISP_EXPORT bool operator==(const vpImageCircle &a, const vpImageCircle &b)
331{
332 vpImagePoint aCenter = a.getCenter();
333 vpImagePoint bCenter = b.getCenter();
334 bool haveSameCenter = (std::abs(aCenter.get_u() - bCenter.get_u())
335 + std::abs(aCenter.get_v() - bCenter.get_v())) <= (2. * std::numeric_limits<double>::epsilon());
336 bool haveSameRadius = std::abs(a.getRadius() - b.getRadius()) <= (2.f * std::numeric_limits<float>::epsilon());
337 return (haveSameCenter && haveSameRadius);
338}
339
340std::string
342{
343 return m_algoParams.toString();
344}
345
346std::ostream &
347operator<<(std::ostream &os, const vpCircleHoughTransform &detector)
348{
349 os << detector.toString();
350 std::cout << "\tUse mask: " << (detector.mp_mask == nullptr ? "false" : "true") << std::endl;
351 return os;
352}
353
354END_VISP_NAMESPACE
Implementation of a generic 2D array used as base class for matrices and vectors.
Definition vpArray2D.h:146
unsigned int getCols() const
Definition vpArray2D.h:423
unsigned int getRows() const
Definition vpArray2D.h:433
Class that gather the algorithm parameters.
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > m_circleCandidatesVotingPoints
std::vector< std::pair< float, float > > m_centerCandidatesList
vpCannyEdgeDetection m_cannyVisp
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > m_finalCirclesVotingPoints
std::vector< float > m_circleCandidatesProbabilities
std::vector< vpImageCircle > m_finalCircles
virtual ~vpCircleHoughTransform()
Destroy the vp Circle Hough Transform object.
virtual void saveConfigurationInJSON(const std::string &jsonPath) const
Save the configuration of the detector in a JSON file described by the path jsonPath....
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.
virtual void computeCenterCandidates()
Determine the image points that are circle center candidates. Increment the center accumulator based ...
virtual void computeCircleCandidates()
For each center candidate CeC_i, do:
friend VISP_EXPORT std::ostream & operator<<(std::ostream &os, const vpCircleHoughTransform &detector)
virtual void edgeDetection(const vpImage< unsigned char > &I)
Perform edge detection based on the computed gradients. Stores the edge points and the edge points co...
vpArray2D< float > m_gradientFilterX
virtual void computeGradients(const vpImage< unsigned char > &I)
Perform Gaussian smoothing on the input image to reduce the noise that would perturbate the edge dete...
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...
const vpImage< bool > * mp_mask
std::vector< unsigned int > m_circleCandidatesVotes
std::vector< unsigned int > m_finalCircleVotes
vpCircleHoughTransformParams m_algoParams
std::vector< float > m_finalCirclesProbabilities
void init(const vpCircleHoughTransformParams &algoParams)
Initialize all the algorithm parameters.
virtual void mergeCircleCandidates()
For each circle candidate CiC_i, check if similar circles have also been detected and if so merges th...
virtual void initGradientFilters()
Initialize the gradient filters used to compute the gradient images.
virtual void initFromJSON(const std::string &jsonPath)
Initialize all the algorithm parameters using the JSON file whose path is jsonPath....
vpCircleHoughTransform()
Construct a new vpCircleHoughTransform object with default parameters.
vpArray2D< float > m_gradientFilterY
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
@ notImplementedError
Not implemented.
Definition vpException.h:69
Class that defines a 2D circle in an image.
float getRadius() const
vpImagePoint getCenter() const
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
static FilterType getSobelKernelX(FilterType *filter, unsigned int size)
static std::string vpCannyFiltAndGradTypeToStr(const vpCannyFilteringAndGradientType &type)
Cast a vpImageFilter::vpCannyFilteringAndGradientType into a string, to know its name.
@ CANNY_GBLUR_SOBEL_FILTERING
Apply Gaussian blur + Sobel operator on the input image.
@ CANNY_GBLUR_SCHARR_FILTERING
Apply Gaussian blur + Scharr operator on the input image.
static void getGaussianKernel(FilterType *filter, unsigned int size, FilterType sigma=0., bool normalize=true)
static FilterType getScharrKernelY(FilterType *filter, unsigned int size)
static FilterType getSobelKernelY(FilterType *filter, unsigned int size)
static FilterType getScharrKernelX(FilterType *filter, unsigned int size)
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
double get_u() const
double get_v() const
Definition of the vpImage class member functions.
Definition vpImage.h:131