Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpCircleHoughTransform_centers.cpp
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#include <visp3/core/vpImageConvert.h>
32#include <visp3/core/vpImageMorphology.h>
33
34#include <visp3/imgproc/vpCircleHoughTransform.h>
35
37
38#ifndef DOXYGEN_SHOULD_SKIP_THIS
39namespace
40{
41#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98)
42bool sortingCenters(const vpCircleHoughTransform::vpCenterVotes &position_vote_a,
43 const vpCircleHoughTransform::vpCenterVotes &position_vote_b)
44{
45 return position_vote_a.m_votes > position_vote_b.m_votes;
46}
47#endif
48}
49#endif
50
51
52// Static variables
53const unsigned char vpCircleHoughTransform::edgeMapOn = 255;
54const unsigned char vpCircleHoughTransform::edgeMapOff = 0;
55
56void
58{
59 if ((m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
60 || (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING)) {
61 // Computing the Gaussian blurr
62 vpImage<float> Iblur, GIx;
63 vpImageFilter::filterX(I, GIx, m_fg.data, m_algoParams.m_gaussianKernelSize, mp_mask);
64 vpImageFilter::filterY(GIx, Iblur, m_fg.data, m_algoParams.m_gaussianKernelSize, mp_mask);
65
66 // Computing the gradients
69 }
70 else {
71 std::string errMsg("[computeGradients] The filtering + gradient operators \"");
72 errMsg += vpImageFilter::vpCannyFiltAndGradTypeToStr(m_algoParams.m_filteringAndGradientType);
73 errMsg += "\" is not implemented (yet).";
75 }
76}
77
78void
80{
81 if (m_algoParams.m_cannyBackendType == vpImageFilter::CANNY_VISP_BACKEND) {
82 // This is done to increase the time performances, because it avoids to
83 // recompute the gradient in the vpImageFilter::canny method
84 m_cannyVisp.setFilteringAndGradientType(m_algoParams.m_filteringAndGradientType);
85 m_cannyVisp.setCannyThresholds(m_algoParams.m_lowerCannyThresh, m_algoParams.m_upperCannyThresh);
86 m_cannyVisp.setCannyThresholdsRatio(m_algoParams.m_lowerCannyThreshRatio, m_algoParams.m_upperCannyThreshRatio);
87 m_cannyVisp.setGradients(m_dIx, m_dIy);
88 m_cannyVisp.setMask(mp_mask);
89 m_cannyVisp.setStoreEdgePoints(true);
90 m_edgeMap = m_cannyVisp.detect(I);
91 }
92 else {
93 if (mp_mask != nullptr) {
94 // Delete pixels that fall outside the mask
95 vpImage<unsigned char> I_masked(I);
96 unsigned int nbRows = I_masked.getHeight();
97 unsigned int nbCols = I_masked.getWidth();
98 for (unsigned int r = 0; r < nbRows; ++r) {
99 for (unsigned int c = 0; c < nbCols; ++c) {
100 if (!((*mp_mask)[r][c])) {
101 I_masked[r][c] = 0;
102 }
103 }
104 }
105
106 // We will have to recompute the gradient in the desired backend format anyway so we let
107 // the vpImageFilter::canny method take care of it
108 vpImageFilter::canny(I_masked, m_edgeMap, m_algoParams.m_gaussianKernelSize, m_algoParams.m_lowerCannyThresh,
109 m_algoParams.m_upperCannyThresh, m_algoParams.m_gradientFilterKernelSize, m_algoParams.m_gaussianStdev,
110 m_algoParams.m_lowerCannyThreshRatio, m_algoParams.m_upperCannyThreshRatio, true,
111 m_algoParams.m_cannyBackendType, m_algoParams.m_filteringAndGradientType);
112 }
113 else {
114 vpImageFilter::canny(I, m_edgeMap, m_algoParams.m_gaussianKernelSize, m_algoParams.m_lowerCannyThresh,
115 m_algoParams.m_upperCannyThresh, m_algoParams.m_gradientFilterKernelSize, m_algoParams.m_gaussianStdev,
116 m_algoParams.m_lowerCannyThreshRatio, m_algoParams.m_upperCannyThreshRatio, true,
117 m_algoParams.m_cannyBackendType, m_algoParams.m_filteringAndGradientType);
118 }
119 }
120
121 for (int i = 0; i < m_algoParams.m_edgeMapFilteringNbIter; ++i) {
123 }
124}
125
126void
128{
130 const unsigned int height = J.getHeight();
131 const unsigned int width = J.getWidth();
132 const int minNbContiguousPts = 2;
133
134 for (unsigned int i = 1; i < (height - 1); ++i) {
135 for (unsigned int j = 1; j < (width - 1); ++j) {
136 if (J[i][j] == vpCircleHoughTransform::edgeMapOn) {
137 // Consider 8 neighbors
138 int topLeftPixel = static_cast<int>(J[i - 1][j - 1]);
139 int topPixel = static_cast<int>(J[i - 1][j]);
140 int topRightPixel = static_cast<int>(J[i - 1][j + 1]);
141 int botLeftPixel = static_cast<int>(J[i + 1][j - 1]);
142 int bottomPixel = static_cast<int>(J[i + 1][j]);
143 int botRightPixel = static_cast<int>(J[i + 1][j + 1]);
144 int leftPixel = static_cast<int>(J[i][j - 1]);
145 int rightPixel = static_cast<int>(J[i][j + 1]);
146 if ((topLeftPixel + topPixel + topRightPixel
147 + botLeftPixel + bottomPixel + botRightPixel
148 + leftPixel + rightPixel
149 ) >= (minNbContiguousPts * static_cast<int>(vpCircleHoughTransform::edgeMapOn))) {
150 // At least minNbContiguousPts of the 8-neighbor points are also an edge point
151 // so we keep the edge point
153 }
154 else {
155 // The edge point is isolated => we erase it
157 }
158 }
159 }
160 }
161}
162
163void
165{
166 // For each edge point EP_i, check the image gradient at EP_i
167 // Then, for each image point in the direction of the gradient,
168 // increment the accumulator
169 // We can perform bilinear interpolation in order not to vote for a "line" of
170 // points, but for an "area" of points
171 unsigned int nbRows = m_edgeMap.getRows(), nbCols = m_edgeMap.getCols();
172
173 // Computing the minimum and maximum horizontal position of the center candidates
174 // The miminum horizontal position of the center is at worst -maxRadius outside the image
175 // The maxinum horizontal position of the center is at worst +maxRadiusoutside the image
176 // The width of the accumulator is the difference between the max and the min
177 int minimumXposition = std::max<int>(m_algoParams.m_centerXlimits.first, -1 * static_cast<int>(m_algoParams.m_maxRadius));
178 int maximumXposition = std::min<int>(m_algoParams.m_centerXlimits.second, static_cast<int>(m_algoParams.m_maxRadius + nbCols));
179 minimumXposition = std::min<int>(minimumXposition, maximumXposition - 1);
180 float minimumXpositionFloat = static_cast<float>(minimumXposition);
181 float maximumXpositionFloat = static_cast<float>(maximumXposition);
182 int offsetX = minimumXposition;
183 int accumulatorWidth = (maximumXposition - minimumXposition) + 1;
184 if (accumulatorWidth <= 0) {
185 throw(vpException(vpException::dimensionError, "[vpCircleHoughTransform::computeCenterCandidates] Accumulator width <= 0!"));
186 }
187
188 // Computing the minimum and maximum vertical position of the center candidates
189 // The miminum vertical position of the center is at worst -maxRadius outside the image
190 // The maxinum vertical position of the center is at worst +maxRadiusoutside the image
191 // The height of the accumulator is the difference between the max and the min
192 int minimumYposition = std::max<int>(m_algoParams.m_centerYlimits.first, -1 * static_cast<int>(m_algoParams.m_maxRadius));
193 int maximumYposition = std::min<int>(m_algoParams.m_centerYlimits.second, static_cast<int>(m_algoParams.m_maxRadius + nbRows));
194 minimumYposition = std::min<int>(minimumYposition, maximumYposition - 1);
195 float minimumYpositionFloat = static_cast<float>(minimumYposition);
196 float maximumYpositionFloat = static_cast<float>(maximumYposition);
197 int offsetY = minimumYposition;
198 int accumulatorHeight = (maximumYposition - minimumYposition) + 1;
199 if (accumulatorHeight <= 0) {
200 throw(vpException(vpException::dimensionError, "[vpCircleHoughTransform::computeCenterCandidates] Accumulator height <= 0!"));
201 }
202
203 vpImage<float> centersAccum(accumulatorHeight, accumulatorWidth + 1, 0.);
204 vpDataForAccumLoop data;
205 data.accumulatorHeight = accumulatorHeight;
206 data.accumulatorWidth = accumulatorWidth;
207 data.maximumXpositionFloat = maximumXpositionFloat;
208 data.maximumYpositionFloat = maximumYpositionFloat;
209 data.maxRadius = m_algoParams.m_maxRadius;
210 data.minimumXpositionFloat = minimumXpositionFloat;
211 data.minimumYpositionFloat = minimumYpositionFloat;
212 data.minRadius = m_algoParams.m_minRadius;
213 data.offsetX = offsetX;
214 data.offsetY = offsetY;
215
216 if (m_algoParams.m_cannyBackendType == vpImageFilter::CANNY_VISP_BACKEND) {
217 const std::vector<vpImagePoint> &edgePoints = m_cannyVisp.getEdgePointsList();
218 size_t nbEdgePoints = edgePoints.size();
219 m_edgePointsList.reserve(nbEdgePoints);
220 for (size_t i = 0; i < nbEdgePoints; ++i) {
221 data.r = static_cast<unsigned int>(edgePoints[i].get_i());
222 data.c = static_cast<unsigned int>(edgePoints[i].get_j());
223 workOnAccumulator(data, centersAccum);
224 }
225 }
226 else {
227 for (unsigned int r = 0; r < nbRows; ++r) {
228 for (unsigned int c = 0; c < nbCols; ++c) {
230 data.c = c;
231 data.r = r;
232 workOnAccumulator(data, centersAccum);
233 }
234 }
235 }
236 }
237
238 // Use dilatation with large kernel in order to determine the
239 // accumulator maxima
240 vpImage<float> centerCandidatesMaxima = centersAccum;
241 int dilatationKernelSize = std::max<int>(m_algoParams.m_dilatationKernelSize, 3); // Ensure at least a 3x3 dilatation operation is performed
242 vpImageMorphology::dilatation(centerCandidatesMaxima, dilatationKernelSize);
243
244 // Look for the image points that correspond to the accumulator maxima
245 // These points will become the center candidates
246 // find the possible circle centers
247 int nbColsAccum = centersAccum.getCols();
248 int nbRowsAccum = centersAccum.getRows();
249 int nbVotes = -1;
250 std::vector<vpCenterVotes> peak_positions_votes;
251
252 for (int y = 0; y < nbRowsAccum; ++y) {
253 int left = -1;
254 for (int x = 0; x < nbColsAccum; ++x) {
255 if ((centersAccum[y][x] >= m_algoParams.m_centerMinThresh)
256 && (vpMath::equal(centersAccum[y][x], centerCandidatesMaxima[y][x]))
257 && (centersAccum[y][x] > centersAccum[y][x + 1])
258 ) {
259 if (left < 0) {
260 left = x;
261 }
262 nbVotes = std::max<int>(nbVotes, static_cast<int>(centersAccum[y][x]));
263 }
264 else if (left >= 0) {
265 int cx = static_cast<int>(((left + x) - 1) * 0.5f);
266 float sumVotes = 0., x_avg = 0., y_avg = 0.;
267 int averagingWindowHalfSize = m_algoParams.m_averagingWindowSize / 2;
268 int startingRow = std::max<int>(0, y - averagingWindowHalfSize);
269 int startingCol = std::max<int>(0, cx - averagingWindowHalfSize);
270 int endRow = std::min<int>(accumulatorHeight, y + averagingWindowHalfSize + 1);
271 int endCol = std::min<int>(accumulatorWidth, cx + averagingWindowHalfSize + 1);
272 for (int r = startingRow; r < endRow; ++r) {
273 for (int c = startingCol; c < endCol; ++c) {
274 sumVotes += centersAccum[r][c];
275 x_avg += centersAccum[r][c] * c;
276 y_avg += centersAccum[r][c] * r;
277 }
278 }
279 float avgVotes = sumVotes / static_cast<float>(m_algoParams.m_averagingWindowSize * m_algoParams.m_averagingWindowSize);
280 if (avgVotes > m_algoParams.m_centerMinThresh) {
281 x_avg /= static_cast<float>(sumVotes);
282 y_avg /= static_cast<float>(sumVotes);
283 std::pair<float, float> position(y_avg + static_cast<float>(offsetY), x_avg + static_cast<float>(offsetX));
284 vpCenterVotes position_vote;
285 position_vote.m_position = position;
286 position_vote.m_votes = avgVotes;
287 peak_positions_votes.push_back(position_vote);
288 }
289 if (nbVotes < 0) {
290 std::stringstream errMsg;
291 errMsg << "nbVotes (" << nbVotes << ") < 0, thresh = " << m_algoParams.m_centerMinThresh;
292 throw(vpException(vpException::badValue, errMsg.str()));
293 }
294 left = -1;
295 nbVotes = -1;
296 }
297 }
298 }
299 filterCenterCandidates(peak_positions_votes);
300}
301
302void vpCircleHoughTransform::updateAccumulator(const vpCoordinatesForAccumStep &coord,
303 const vpCircleHoughTransform::vpDataForAccumLoop &data,
304 vpImage<float> &accum, bool &hasToStop)
305{
306 if (((coord.x - data.offsetX) < 0) ||
307 ((coord.x - data.offsetX) >= data.accumulatorWidth) ||
308 ((coord.y - data.offsetY) < 0) ||
309 ((coord.y - data.offsetY) >= data.accumulatorHeight)
310 ) {
311 hasToStop = true;
312 }
313 else {
314 float dx = (coord.x_orig - static_cast<float>(coord.x));
315 float dy = (coord.y_orig - static_cast<float>(coord.y));
316 accum[coord.y - data.offsetY][coord.x - data.offsetX] += std::abs(dx) + std::abs(dy);
317 }
318}
319
320void
321vpCircleHoughTransform::updateAccumAlongGradientDir(const vpCircleHoughTransform::vpDataForAccumLoop &data, float &sx, float &sy, vpImage<float> &centersAccum)
322{
323 static const int nbDirections = 2;
324 for (int k1 = 0; k1 < nbDirections; ++k1) {
325 bool hasToStopLoop = false;
326 int x_low_prev = std::numeric_limits<int>::max(), y_low_prev, y_high_prev;
327 int x_high_prev = (y_low_prev = (y_high_prev = x_low_prev));
328
329 float rstart = data.minRadius, rstop = data.maxRadius;
330 float min_minus_c = data.minimumXpositionFloat - static_cast<float>(data.c);
331 float min_minus_r = data.minimumYpositionFloat - static_cast<float>(data.r);
332 float max_minus_c = data.maximumXpositionFloat - static_cast<float>(data.c);
333 float max_minus_r = data.maximumYpositionFloat - static_cast<float>(data.r);
334 if (sx > 0) {
335 float rmin = min_minus_c / sx;
336 rstart = std::max<float>(rmin, data.minRadius);
337 float rmax = max_minus_c / sx;
338 rstop = std::min<float>(rmax, data.maxRadius);
339 }
340 else if (sx < 0) {
341 float rmin = max_minus_c / sx;
342 rstart = std::max<float>(rmin, data.minRadius);
343 float rmax = min_minus_c / sx;
344 rstop = std::min<float>(rmax, data.maxRadius);
345 }
346
347 if (sy > 0) {
348 float rmin = min_minus_r / sy;
349 rstart = std::max<float>(rmin, rstart);
350 float rmax = max_minus_r / sy;
351 rstop = std::min<float>(rmax, rstop);
352 }
353 else if (sy < 0) {
354 float rmin = max_minus_r / sy;
355 rstart = std::max<float>(rmin, rstart);
356 float rmax = min_minus_r / sy;
357 rstop = std::min<float>(rmax, rstop);
358 }
359
360 float deltar_x = 1.f / std::abs(sx), deltar_y = 1.f / std::abs(sy);
361 float deltar = std::min<float>(deltar_x, deltar_y);
362
363 float rad = rstart;
364 while ((rad <= rstop) && (!hasToStopLoop)) {
365 float x1 = static_cast<float>(data.c) + (rad * sx);
366 float y1 = static_cast<float>(data.r) + (rad * sy);
367 rad += deltar; // Update rad that is not used below not to forget it
368
369 bool xOutsideRoI = (x1 < data.minimumXpositionFloat) || (x1 > data.maximumXpositionFloat);
370 bool yOutsideRoI = (y1 < data.minimumYpositionFloat) || (y1 > data.maximumYpositionFloat);
371 // Continue only if the center is inside the search region.
372 if (!(xOutsideRoI || yOutsideRoI)) {
373 int x_low, x_high, y_low, y_high;
374
375 if (x1 > 0.) {
376 x_low = static_cast<int>(std::floor(x1));
377 x_high = static_cast<int>(std::ceil(x1));
378 }
379 else {
380 x_low = -(static_cast<int>(std::ceil(-x1)));
381 x_high = -(static_cast<int>(std::floor(-x1)));
382 }
383
384 if (y1 > 0.) {
385 y_low = static_cast<int>(std::floor(y1));
386 y_high = static_cast<int>(std::ceil(y1));
387 }
388 else {
389 y_low = -(static_cast<int>(std::ceil(-1. * y1)));
390 y_high = -(static_cast<int>(std::floor(-1. * y1)));
391 }
392
393 bool xHasNotChanged = (x_low_prev == x_low) && (x_high_prev == x_high);
394 bool yHasNotChanged = (y_low_prev == y_low) && (y_high_prev == y_high);
395
396 // Avoid duplicated votes to the same center candidate
397 if (!(xHasNotChanged && yHasNotChanged)) {
398 x_low_prev = x_low;
399 x_high_prev = x_high;
400 y_low_prev = y_low;
401 y_high_prev = y_high;
402
403 vpCoordinatesForAccumStep coords;
404 coords.x_orig = x1;
405 coords.y_orig = y1;
406 coords.x = x_low;
407 coords.y = y_low;
408 updateAccumulator(coords, data, centersAccum, hasToStopLoop);
409
410 coords.x = x_high;
411 coords.y = y_high;
412 updateAccumulator(coords, data, centersAccum, hasToStopLoop);
413 }
414 }
415 }
416 sx = -sx;
417 sy = -sy;
418 }
419}
420
421void
422vpCircleHoughTransform::workOnAccumulator(vpCircleHoughTransform::vpDataForAccumLoop &data, vpImage<float> &centersAccum)
423{
424 // Voting for points in both direction of the gradient
425 // Step from min_radius to max_radius in both directions of the gradient
426 float mag = std::sqrt((m_dIx[data.r][data.c] * m_dIx[data.r][data.c]) + (m_dIy[data.r][data.c] * m_dIy[data.r][data.c]));
427
428 float sx = 0.f, sy = 0.f;
429 if (std::abs(mag) >= std::numeric_limits<float>::epsilon()) {
430 sx = m_dIx[data.r][data.c] / mag;
431 sy = m_dIy[data.r][data.c] / mag;
432
433 // Saving the edge point for further use
434 m_edgePointsList.push_back(std::pair<unsigned int, unsigned int>(data.r, data.c));
435 updateAccumAlongGradientDir(data, sx, sy, centersAccum);
436 }
437}
438
439void
440vpCircleHoughTransform::filterCenterCandidates(const std::vector<vpCenterVotes> &peak_positions_votes)
441{
442 unsigned int nbPeaks = static_cast<unsigned int>(peak_positions_votes.size());
443 if (nbPeaks > 0) {
444 std::vector<bool> has_been_merged(nbPeaks, false);
445 std::vector<vpCenterVotes> merged_peaks_position_votes;
446 float squared_distance_max = m_algoParams.m_centerMinDist * m_algoParams.m_centerMinDist;
447 for (unsigned int idPeak = 0; idPeak < nbPeaks; ++idPeak) {
448 float votes = peak_positions_votes[idPeak].m_votes;
449 // Ignoring peak that has already been merged
450 if (!has_been_merged[idPeak]) {
451 if (votes < m_algoParams.m_centerMinThresh) {
452 // Ignoring peak whose number of votes is lower than the threshold
453 has_been_merged[idPeak] = true;
454 }
455 else {
456 vpCentersBarycenter barycenter = mergeSimilarCenters(idPeak, nbPeaks, squared_distance_max, peak_positions_votes, has_been_merged);
457
458 float avg_votes = barycenter.m_totalVotes / barycenter.m_nbElectors;
459 // Only the centers having enough votes are considered
460 if (avg_votes > m_algoParams.m_centerMinThresh) {
461 barycenter.m_position.first /= barycenter.m_totalVotes;
462 barycenter.m_position.second /= barycenter.m_totalVotes;
463 vpCenterVotes barycenter_votes;
464 barycenter_votes.m_position = barycenter.m_position;
465 barycenter_votes.m_votes = avg_votes;
466 merged_peaks_position_votes.push_back(barycenter_votes);
467 }
468 }
469 }
470 }
471
472#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
473 auto sortingCenters = [](const vpCenterVotes &position_vote_a,
474 const vpCenterVotes &position_vote_b) {
475 return position_vote_a.m_votes > position_vote_b.m_votes;
476 };
477#endif
478
479 std::sort(merged_peaks_position_votes.begin(), merged_peaks_position_votes.end(), sortingCenters);
480
481 nbPeaks = static_cast<unsigned int>(merged_peaks_position_votes.size());
482 int nbPeaksToKeep = (m_algoParams.m_expectedNbCenters > 0 ? m_algoParams.m_expectedNbCenters : static_cast<int>(nbPeaks));
483 nbPeaksToKeep = std::min<int>(nbPeaksToKeep, static_cast<int>(nbPeaks));
484 for (int i = 0; i < nbPeaksToKeep; ++i) {
485 m_centerCandidatesList.push_back(merged_peaks_position_votes[i].m_position);
486 m_centerVotes.push_back(static_cast<int>(merged_peaks_position_votes[i].m_votes));
487 }
488 }
489}
490
491vpCircleHoughTransform::vpCentersBarycenter
492vpCircleHoughTransform::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)
493{
494 std::pair<float, float> position = peak_positions_votes[idPeak].m_position;
495 vpCentersBarycenter barycenter;
496 barycenter.m_position.first = position.first * peak_positions_votes[idPeak].m_votes;
497 barycenter.m_position.second = position.second * peak_positions_votes[idPeak].m_votes;
498 barycenter.m_totalVotes = peak_positions_votes[idPeak].m_votes;
499 barycenter.m_nbElectors = 1.f;
500 // Looking for potential similar peak in the following peaks
501 for (unsigned int idCandidate = idPeak + 1; idCandidate < nbPeaks; ++idCandidate) {
502 float votes_candidate = peak_positions_votes[idCandidate].m_votes;
503 // Ignoring peaks that have already been merged
504 if (!has_been_merged[idCandidate]) {
505 if (votes_candidate < m_algoParams.m_centerMinThresh) {
506 // Ignoring peak whose number of votes is lower than the threshold
507 has_been_merged[idCandidate] = true;
508 }
509 else {
510 // Computing the distance with the peak of insterest
511 std::pair<float, float> position_candidate = peak_positions_votes[idCandidate].m_position;
512 float squared_distance = ((position.first - position_candidate.first) * (position.first - position_candidate.first))
513 + ((position.second - position_candidate.second) * (position.second - position_candidate.second));
514
515 // If the peaks are similar, update the barycenter peak between them and corresponding votes
516 if (squared_distance < squared_distance_max) {
517 barycenter.m_position.first += position_candidate.first * votes_candidate;
518 barycenter.m_position.second += position_candidate.second * votes_candidate;
519 barycenter.m_totalVotes += votes_candidate;
520 barycenter.m_nbElectors += 1.f;
521 has_been_merged[idCandidate] = true;
522 }
523 }
524 }
525 }
526 return barycenter;
527}
528
529END_VISP_NAMESPACE
std::vector< std::pair< float, float > > m_centerCandidatesList
vpCannyEdgeDetection m_cannyVisp
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)
Look in the list containing the raw center candidates if one is closed to the center candidate that i...
std::vector< std::pair< unsigned int, unsigned int > > m_edgePointsList
virtual void computeCenterCandidates()
Determine the image points that are circle center candidates. Increment the center accumulator based ...
void updateAccumulator(const vpCoordinatesForAccumStep &coord, const vpDataForAccumLoop &data, vpImage< float > &accum, bool &hasToStop)
vpImage< unsigned char > m_edgeMap
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 workOnAccumulator(vpDataForAccumLoop &data, vpImage< float > &centersAccum)
Voting for points in both direction of the gradient.
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...
static const unsigned char edgeMapOff
const vpImage< bool > * mp_mask
vpCircleHoughTransformParams m_algoParams
virtual void filterEdgeMap()
Filter the edge map in order to remove isolated edge points.
void updateAccumAlongGradientDir(const vpDataForAccumLoop &data, float &sx, float &sy, vpImage< float > &centersAccum)
Update the center accumulator along the positive and negative gradient direction starting from an edg...
static const unsigned char edgeMapOn
virtual void filterCenterCandidates(const std::vector< vpCenterVotes > &peak_positions_votes)
Aggregate center candidates that are close to each other.
vpArray2D< float > m_gradientFilterY
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ badValue
Used to indicate that a value is not in the allowed range.
Definition vpException.h:73
@ dimensionError
Bad dimension.
Definition vpException.h:71
@ notImplementedError
Not implemented.
Definition vpException.h:69
static void canny(const vpImage< unsigned char > &I, vpImage< unsigned char > &Ic, const unsigned int &gaussianFilterSize, const float &thresholdCanny, const unsigned int &apertureSobel)
static void filterY(const vpImage< ImageType > &I, vpImage< OutputType > &dIy, const FilterType *filter, unsigned int size, const vpImage< bool > *p_mask=nullptr)
Filter along the vertical direction.
static void filterX(const vpImage< ImageType > &I, vpImage< OutputType > &dIx, const FilterType *filter, unsigned int size, const vpImage< bool > *p_mask=nullptr)
Filter along the horizontal direction.
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 filter(const vpImage< ImageType > &I, vpImage< FilterType > &If, const vpArray2D< FilterType > &M, bool convolve=false, const vpImage< bool > *p_mask=nullptr)
@ CANNY_VISP_BACKEND
Use ViSP.
static void dilatation(vpImage< Type > &I, Type value, Type value_out, vpConnexityType connexity=CONNEXITY_4)
Definition of the vpImage class member functions.
Definition vpImage.h:131
unsigned int getWidth() const
Definition vpImage.h:242
unsigned int getCols() const
Definition vpImage.h:171
unsigned int getHeight() const
Definition vpImage.h:181
unsigned int getRows() const
Definition vpImage.h:212
static bool equal(double x, double y, double threshold=0.001)
Definition vpMath.h:470