31 #include <visp3/core/vpImageConvert.h>
32 #include <visp3/core/vpImageMorphology.h>
34 #include <visp3/imgproc/vpCircleHoughTransform.h>
38 #ifndef DOXYGEN_SHOULD_SKIP_THIS
45 typedef struct vpDataForAccumLoop
51 float minimumXpositionFloat;
52 float minimumYpositionFloat;
53 float maximumXpositionFloat;
54 float maximumYpositionFloat;
58 int accumulatorHeight;
64 typedef struct vpCoordinatesForAccumStep
70 }vpCoordinatesForAccumStep;
72 #if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98)
73 void updateAccumulator(
const vpCoordinatesForAccumStep &coord,
74 const vpDataForAccumLoop &data,
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)
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);
91 bool sortingCenters(
const vpCircleHoughTransform::vpCenterVotes &position_vote_a,
92 const vpCircleHoughTransform::vpCenterVotes &position_vote_b)
94 return position_vote_a.m_votes > position_vote_b.m_votes;
108 updateAccumAlongGradientDir(
const vpDataForAccumLoop &data,
float &sx,
float &sy,
vpImage<float> ¢ersAccum)
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));
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);
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);
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);
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);
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);
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);
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);
156 bool xOutsideRoI = (x1 < data.minimumXpositionFloat) || (x1 > data.maximumXpositionFloat);
157 bool yOutsideRoI = (y1 < data.minimumYpositionFloat) || (y1 > data.maximumYpositionFloat);
159 if (!(xOutsideRoI || yOutsideRoI)) {
160 int x_low, x_high, y_low, y_high;
163 x_low =
static_cast<int>(std::floor(x1));
164 x_high =
static_cast<int>(std::ceil(x1));
167 x_low = -(
static_cast<int>(std::ceil(-x1)));
168 x_high = -(
static_cast<int>(std::floor(-x1)));
172 y_low =
static_cast<int>(std::floor(y1));
173 y_high =
static_cast<int>(std::ceil(y1));
176 y_low = -(
static_cast<int>(std::ceil(-1. * y1)));
177 y_high = -(
static_cast<int>(std::floor(-1. * y1)));
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);
184 if (!(xHasNotChanged && yHasNotChanged)) {
186 x_high_prev = x_high;
188 y_high_prev = y_high;
190 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
191 auto updateAccumulator =
192 [](
const vpCoordinatesForAccumStep &coord,
193 const vpDataForAccumLoop &data,
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)
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);
210 vpCoordinatesForAccumStep coords;
215 updateAccumulator(coords, data, centersAccum, hasToStopLoop);
219 updateAccumulator(coords, data, centersAccum, hasToStopLoop);
250 std::string errMsg(
"[computeGradients] The filtering + gradient operators \"");
252 errMsg +=
"\" is not implemented (yet).";
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) {
299 for (
int i = 0; i <
m_algoParams.m_edgeMapFilteringNbIter; ++i) {
308 const unsigned int height = J.
getHeight();
309 const unsigned int width = J.
getWidth();
310 const int minNbContiguousPts = 2;
312 for (
unsigned int i = 1; i < (height - 1); ++i) {
313 for (
unsigned int j = 1; j < (width - 1); ++j) {
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
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) {
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) {
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) {
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;
398 vpDataForAccumLoop data;
399 data.accumulatorHeight = accumulatorHeight;
400 data.accumulatorWidth = accumulatorWidth;
402 data.maximumXpositionFloat = maximumXpositionFloat;
403 data.maximumYpositionFloat = maximumYpositionFloat;
405 data.minimumXpositionFloat = minimumXpositionFloat;
406 data.minimumYpositionFloat = minimumYpositionFloat;
408 data.offsetX = offsetX;
409 data.offsetY = offsetY;
411 updateAccumAlongGradientDir(data, sx, sy, centersAccum);
420 int dilatationKernelSize = std::max<int>(
m_algoParams.m_dilatationKernelSize, 3);
426 int nbColsAccum = centersAccum.
getCols();
427 int nbRowsAccum = centersAccum.
getRows();
429 std::vector<vpCenterVotes> peak_positions_votes;
431 for (
int y = 0; y < nbRowsAccum; ++y) {
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])
441 nbVotes = std::max<int>(nbVotes,
static_cast<int>(centersAccum[y][x]));
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;
458 float avgVotes = sumVotes /
static_cast<float>(
m_algoParams.m_averagingWindowSize *
m_algoParams.m_averagingWindowSize);
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);
469 std::stringstream errMsg;
470 errMsg <<
"nbVotes (" << nbVotes <<
") < 0, thresh = " <<
m_algoParams.m_centerMinThresh;
484 unsigned int nbPeaks =
static_cast<unsigned int>(peak_positions_votes.size());
486 std::vector<bool> has_been_merged(nbPeaks,
false);
487 std::vector<vpCenterVotes> merged_peaks_position_votes;
489 for (
unsigned int idPeak = 0; idPeak < nbPeaks; ++idPeak) {
490 float votes = peak_positions_votes[idPeak].m_votes;
492 if (!has_been_merged[idPeak]) {
495 has_been_merged[idPeak] =
true;
498 vpCentersBarycenter barycenter =
mergeSimilarCenters(idPeak, nbPeaks, squared_distance_max, peak_positions_votes, has_been_merged);
500 float avg_votes = barycenter.m_totalVotes / barycenter.m_nbElectors;
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);
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;
521 std::sort(merged_peaks_position_votes.begin(), merged_peaks_position_votes.end(), sortingCenters);
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) {
528 m_centerVotes.push_back(
static_cast<int>(merged_peaks_position_votes[i].m_votes));
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)
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;
543 for (
unsigned int idCandidate = idPeak + 1; idCandidate < nbPeaks; ++idCandidate) {
544 float votes_candidate = peak_positions_votes[idCandidate].m_votes;
546 if (!has_been_merged[idCandidate]) {
549 has_been_merged[idCandidate] =
true;
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));
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;
Type * data
Address of the first element of the data array.
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....
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 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 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
unsigned int getCols() const
unsigned int getHeight() const
unsigned int getRows() const
static bool equal(double x, double y, double threshold=0.001)