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