31#include <visp3/core/vpImageConvert.h>
32#include <visp3/core/vpImageMorphology.h>
34#include <visp3/imgproc/vpCircleHoughTransform.h>
38#ifndef DOXYGEN_SHOULD_SKIP_THIS
41#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98)
42bool sortingCenters(
const vpCircleHoughTransform::vpCenterVotes &position_vote_a,
43 const vpCircleHoughTransform::vpCenterVotes &position_vote_b)
45 return position_vote_a.m_votes > position_vote_b.m_votes;
71 std::string errMsg(
"[computeGradients] The filtering + gradient operators \"");
73 errMsg +=
"\" is not implemented (yet).";
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) {
121 for (
int i = 0; i <
m_algoParams.m_edgeMapFilteringNbIter; ++i) {
130 const unsigned int height = J.
getHeight();
131 const unsigned int width = J.
getWidth();
132 const int minNbContiguousPts = 2;
134 for (
unsigned int i = 1; i < (height - 1); ++i) {
135 for (
unsigned int j = 1; j < (width - 1); ++j) {
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
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) {
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) {
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;
210 data.minimumXpositionFloat = minimumXpositionFloat;
211 data.minimumYpositionFloat = minimumYpositionFloat;
213 data.offsetX = offsetX;
214 data.offsetY = offsetY;
217 const std::vector<vpImagePoint> &edgePoints =
m_cannyVisp.getEdgePointsList();
218 size_t nbEdgePoints = edgePoints.size();
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());
227 for (
unsigned int r = 0; r < nbRows; ++r) {
228 for (
unsigned int c = 0; c < nbCols; ++c) {
241 int dilatationKernelSize = std::max<int>(
m_algoParams.m_dilatationKernelSize, 3);
247 int nbColsAccum = centersAccum.
getCols();
248 int nbRowsAccum = centersAccum.
getRows();
250 std::vector<vpCenterVotes> peak_positions_votes;
252 for (
int y = 0; y < nbRowsAccum; ++y) {
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])
262 nbVotes = std::max<int>(nbVotes,
static_cast<int>(centersAccum[y][x]));
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;
279 float avgVotes = sumVotes /
static_cast<float>(
m_algoParams.m_averagingWindowSize *
m_algoParams.m_averagingWindowSize);
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);
290 std::stringstream errMsg;
291 errMsg <<
"nbVotes (" << nbVotes <<
") < 0, thresh = " <<
m_algoParams.m_centerMinThresh;
303 const vpCircleHoughTransform::vpDataForAccumLoop &data,
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)
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);
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));
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);
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);
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);
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);
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);
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);
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);
369 bool xOutsideRoI = (x1 < data.minimumXpositionFloat) || (x1 > data.maximumXpositionFloat);
370 bool yOutsideRoI = (y1 < data.minimumYpositionFloat) || (y1 > data.maximumYpositionFloat);
372 if (!(xOutsideRoI || yOutsideRoI)) {
373 int x_low, x_high, y_low, y_high;
376 x_low =
static_cast<int>(std::floor(x1));
377 x_high =
static_cast<int>(std::ceil(x1));
380 x_low = -(
static_cast<int>(std::ceil(-x1)));
381 x_high = -(
static_cast<int>(std::floor(-x1)));
385 y_low =
static_cast<int>(std::floor(y1));
386 y_high =
static_cast<int>(std::ceil(y1));
389 y_low = -(
static_cast<int>(std::ceil(-1. * y1)));
390 y_high = -(
static_cast<int>(std::floor(-1. * y1)));
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);
397 if (!(xHasNotChanged && yHasNotChanged)) {
399 x_high_prev = x_high;
401 y_high_prev = y_high;
403 vpCoordinatesForAccumStep coords;
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]));
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;
434 m_edgePointsList.push_back(std::pair<unsigned int, unsigned int>(data.r, data.c));
442 unsigned int nbPeaks =
static_cast<unsigned int>(peak_positions_votes.size());
444 std::vector<bool> has_been_merged(nbPeaks,
false);
445 std::vector<vpCenterVotes> merged_peaks_position_votes;
447 for (
unsigned int idPeak = 0; idPeak < nbPeaks; ++idPeak) {
448 float votes = peak_positions_votes[idPeak].m_votes;
450 if (!has_been_merged[idPeak]) {
453 has_been_merged[idPeak] =
true;
456 vpCentersBarycenter barycenter =
mergeSimilarCenters(idPeak, nbPeaks, squared_distance_max, peak_positions_votes, has_been_merged);
458 float avg_votes = barycenter.m_totalVotes / barycenter.m_nbElectors;
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);
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;
479 std::sort(merged_peaks_position_votes.begin(), merged_peaks_position_votes.end(), sortingCenters);
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) {
486 m_centerVotes.push_back(
static_cast<int>(merged_peaks_position_votes[i].m_votes));
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)
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;
501 for (
unsigned int idCandidate = idPeak + 1; idCandidate < nbPeaks; ++idCandidate) {
502 float votes_candidate = peak_positions_votes[idCandidate].m_votes;
504 if (!has_been_merged[idCandidate]) {
507 has_been_merged[idCandidate] =
true;
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));
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;
error that can be emitted by ViSP classes.
@ badValue
Used to indicate that a value is not in the allowed range.
@ dimensionError
Bad dimension.
@ notImplementedError
Not implemented.
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.
unsigned int getWidth() const
unsigned int getCols() const
unsigned int getHeight() const
unsigned int getRows() const
static bool equal(double x, double y, double threshold=0.001)