From 97bcb06d7febfc433bfc5e6403623fc34c30d6ad Mon Sep 17 00:00:00 2001 From: rlagneau Date: Mon, 16 Oct 2023 11:09:04 +0200 Subject: [PATCH 01/13] [FIX] Fixed a bug due to the signedness of the updateAccumulator function. Added a check to the magnitude of the gradient to avoid dividing by 0 --- .../imgproc/src/vpCircleHoughTransform.cpp | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index dadfff341e..f390f9c7de 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -322,10 +322,11 @@ vpCircleHoughTransform::computeCenterCandidates() int offsetY = minimumYposition; int accumulatorHeight = maximumYposition - minimumYposition + 1; if (accumulatorHeight <= 0) { - throw(vpException(vpException::dimensionError, "[vpCircleHoughTransform::computeCenterCandidates] Accumulator height <= 0!")); + std::string errMsg("[vpCircleHoughTransform::computeCenterCandidates] Accumulator height <= 0!"); + throw(vpException(vpException::dimensionError, errMsg)); } - vpImage centersAccum(accumulatorHeight, accumulatorWidth + 1, 0.); /*!< Matrix that contains the votes for the center candidates.*/ + vpImage centersAccum(accumulatorHeight, accumulatorWidth + 1, 0.); /*!< Votes for the center candidates.*/ for (unsigned int r = 0; r < nbRows; r++) { for (unsigned int c = 0; c < nbCols; c++) { @@ -337,8 +338,11 @@ vpCircleHoughTransform::computeCenterCandidates() // Step from min_radius to max_radius in both directions of the gradient float mag = std::sqrt(m_dIx[r][c] * m_dIx[r][c] + m_dIy[r][c] * m_dIy[r][c]); - float sx = m_dIx[r][c] / mag; - float sy = m_dIy[r][c] / mag; + float sx = 0.f, sy = 0.f; + if (std::abs(mag) >= std::numeric_limits::epsilon()) { + sx = m_dIx[r][c] / mag; + sy = m_dIy[r][c] / mag; + } int int_minRad = (int)m_algoParams.m_minRadius; int int_maxRad = (int)m_algoParams.m_maxRadius; @@ -376,11 +380,13 @@ vpCircleHoughTransform::computeCenterCandidates() auto updateAccumulator = [](const float &x_orig, const float &y_orig, - const unsigned int &x, const unsigned int &y, + const int &x, const int &y, const int &offsetX, const int &offsetY, - const unsigned int &nbCols, const unsigned int &nbRows, + const int &nbCols, const int &nbRows, vpImage &accum, bool &hasToStop) { - if (x - offsetX >= nbCols || + if (x - offsetX < 0 || + x - offsetX >= nbCols || + y - offsetY < 0 || y - offsetY >= nbRows ) { hasToStop = true; @@ -441,6 +447,9 @@ vpCircleHoughTransform::computeCenterCandidates() int cx = (int)((left + x - 1) * 0.5f); m_centerCandidatesList.push_back(std::pair(y + offsetY, cx + offsetX)); m_centerVotes.push_back(nbVotes); + if (nbVotes < 0) { + throw(vpException(vpException::badValue, "nbVotes (" + std::to_string(nbVotes) + ") < 0, thresh = " + std::to_string(m_algoParams.m_centerThresh))); + } left = -1; nbVotes = -1; } From 5015f6837ad77abfedfb2c7f1f056ab4efea3ad1 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Mon, 16 Oct 2023 11:17:35 +0200 Subject: [PATCH 02/13] Removed constraint on the maximum radius that is not necessary --- modules/imgproc/src/vpCircleHoughTransform.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index f390f9c7de..3fb55ed315 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -295,8 +295,6 @@ vpCircleHoughTransform::computeCenterCandidates() unsigned int nbRows = m_edgeMap.getRows(); unsigned int nbCols = m_edgeMap.getCols(); - m_algoParams.m_maxRadius = std::min(m_algoParams.m_maxRadius, std::min(nbCols, nbRows)); - // Computing the minimum and maximum horizontal position of the center candidates // The miminum horizontal position of the center is at worst -maxRadius outside the image // The maxinum horizontal position of the center is at worst +maxRadiusoutside the image From 0f0564a33559b2a226a1a46816dae4d617cb9462 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Tue, 17 Oct 2023 16:14:36 +0200 Subject: [PATCH 03/13] [CORPS] Changed min and max radii into float + ensure that r_max - r_min > min_value due to pixelization --- .../visp3/imgproc/vpCircleHoughTransform.h | 24 +++++------ .../imgproc/src/vpCircleHoughTransform.cpp | 40 ++++++++++++++++--- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index 9c503700c4..38eca3e944 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -89,8 +89,8 @@ class VISP_EXPORT vpCircleHoughTransform // // Center candidates computation attributes std::pair m_centerXlimits; /*!< Minimum and maximum position on the horizontal axis of the center of the circle we want to detect.*/ std::pair m_centerYlimits; /*!< Minimum and maximum position on the vertical axis of the center of the circle we want to detect.*/ - unsigned int m_minRadius; /*!< Minimum radius of the circles we want to detect.*/ - unsigned int m_maxRadius; /*!< Maximum radius of the circles we want to detect.*/ + float m_minRadius; /*!< Minimum radius of the circles we want to detect.*/ + float m_maxRadius; /*!< Maximum radius of the circles we want to detect.*/ int m_dilatationNbIter; /*!< Number of times dilatation is performed to detect the maximum number of votes for the center candidates.*/ float m_centerThresh; /*!< Minimum number of votes a point must exceed to be considered as center candidate.*/ @@ -116,8 +116,8 @@ class VISP_EXPORT vpCircleHoughTransform , m_edgeMapFilteringNbIter(1) , m_centerXlimits(std::pair(std::numeric_limits::min(), std::numeric_limits::max())) , m_centerYlimits(std::pair(std::numeric_limits::min(), std::numeric_limits::max())) - , m_minRadius(0) - , m_maxRadius(1000) + , m_minRadius(0.f) + , m_maxRadius(1000.f) , m_dilatationNbIter(1) , m_centerThresh(50.f) , m_circleProbaThresh(0.9f) @@ -159,8 +159,8 @@ class VISP_EXPORT vpCircleHoughTransform , const int &edgeMapFilterNbIter , const std::pair ¢erXlimits , const std::pair ¢erYlimits - , const unsigned int &minRadius - , const unsigned int &maxRadius + , const float &minRadius + , const float &maxRadius , const int &dilatationNbIter , const float ¢erThresh , const float &circleProbabilityThresh @@ -287,7 +287,7 @@ class VISP_EXPORT vpCircleHoughTransform params.m_centerXlimits = j.value("centerXlimits", params.m_centerXlimits); params.m_centerYlimits = j.value("centerYlimits", params.m_centerYlimits); - std::pair radiusLimits = j.value("radiusLimits", std::pair(params.m_minRadius, params.m_maxRadius)); + std::pair radiusLimits = j.value("radiusLimits", std::pair(params.m_minRadius, params.m_maxRadius)); params.m_minRadius = std::min(radiusLimits.first, radiusLimits.second); params.m_maxRadius = std::max(radiusLimits.first, radiusLimits.second); @@ -325,7 +325,7 @@ class VISP_EXPORT vpCircleHoughTransform */ inline friend void to_json(json &j, const vpCircleHoughTransformParameters ¶ms) { - std::pair radiusLimits = { params.m_minRadius, params.m_maxRadius }; + std::pair radiusLimits = { params.m_minRadius, params.m_maxRadius }; j = json { {"gaussianKernelSize", params.m_gaussianKernelSize}, @@ -547,7 +547,7 @@ class VISP_EXPORT vpCircleHoughTransform */ inline void setCircleMinRadius(const float &circle_min_radius) { - m_algoParams.m_minRadius = static_cast(circle_min_radius); + m_algoParams.m_minRadius = circle_min_radius; } /*! @@ -556,7 +556,7 @@ class VISP_EXPORT vpCircleHoughTransform */ inline void setCircleMaxRadius(const float &circle_max_radius) { - m_algoParams.m_maxRadius = static_cast(circle_max_radius); + m_algoParams.m_maxRadius = circle_max_radius; } /*! @@ -709,7 +709,7 @@ class VISP_EXPORT vpCircleHoughTransform /*! * Get circles min radius in pixels. */ - inline unsigned int getCircleMinRadius() const + inline float getCircleMinRadius() const { return m_algoParams.m_minRadius; } @@ -717,7 +717,7 @@ class VISP_EXPORT vpCircleHoughTransform /*! * Get circles max radius in pixels. */ - inline unsigned int getCircleMaxRadius() const + inline float getCircleMaxRadius() const { return m_algoParams.m_maxRadius; } diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 3fb55ed315..10539de0a6 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -180,6 +180,32 @@ vpCircleHoughTransform::detect(const vpImage &I) m_finalCircles.clear(); m_finalCircleVotes.clear(); + // Ensuring that the difference between the max and min radii is big enough to take into account + // the pixelization of the image + const float minRadiusDiff = 3.f; + if (m_algoParams.m_maxRadius - m_algoParams.m_minRadius < minRadiusDiff) { + if (m_algoParams.m_minRadius > minRadiusDiff / 2.f) { + m_algoParams.m_maxRadius += minRadiusDiff / 2.f; + m_algoParams.m_minRadius -= minRadiusDiff / 2.f; + } + else { + m_algoParams.m_maxRadius += minRadiusDiff - m_algoParams.m_minRadius; + m_algoParams.m_minRadius = 0.f; + } + } + + // Ensuring that the difference between the max and min center position is big enough to take into account + // the pixelization of the image + const float minCenterPositionDiff = 3.f; + if (m_algoParams.m_centerXlimits.second - m_algoParams.m_centerXlimits.first < minCenterPositionDiff) { + m_algoParams.m_centerXlimits.second += minCenterPositionDiff / 2.f; + m_algoParams.m_centerXlimits.first -= minCenterPositionDiff / 2.f; + } + if (m_algoParams.m_centerYlimits.second - m_algoParams.m_centerYlimits.first < minCenterPositionDiff) { + m_algoParams.m_centerYlimits.second += minCenterPositionDiff / 2.f; + m_algoParams.m_centerYlimits.first -= minCenterPositionDiff / 2.f; + } + // First thing, we need to apply a Gaussian filter on the image to remove some spurious noise // Then, we need to compute the image gradients in order to be able to perform edge detection computeGradientsAfterGaussianSmoothing(I); @@ -303,6 +329,7 @@ vpCircleHoughTransform::computeCenterCandidates() int maximumXposition = std::min(m_algoParams.m_centerXlimits.second, (int)(m_algoParams.m_maxRadius + nbCols)); minimumXposition = std::min(minimumXposition, maximumXposition - 1); float minimumXpositionFloat = static_cast(minimumXposition); + float maximumXpositionFloat = static_cast(maximumXposition); int offsetX = minimumXposition; int accumulatorWidth = maximumXposition - minimumXposition + 1; if (accumulatorWidth <= 0) { @@ -317,6 +344,7 @@ vpCircleHoughTransform::computeCenterCandidates() int maximumYposition = std::min(m_algoParams.m_centerYlimits.second, (int)(m_algoParams.m_maxRadius + nbRows)); minimumYposition = std::min(minimumYposition, maximumYposition - 1); float minimumYpositionFloat = static_cast(minimumYposition); + float maximumYpositionFloat = static_cast(maximumYposition); int offsetY = minimumYposition; int accumulatorHeight = maximumYposition - minimumYposition + 1; if (accumulatorHeight <= 0) { @@ -351,8 +379,9 @@ vpCircleHoughTransform::computeCenterCandidates() float x1 = (float)c + (float)rad * sx; float y1 = (float)r + (float)rad * sy; - if (x1 < minimumXpositionFloat || y1 < minimumYpositionFloat) { - continue; // If either value is lower than maxRadius, it means that the center is outside the search region. + if (x1 < minimumXpositionFloat || y1 < minimumYpositionFloat + || x1 > maximumXpositionFloat || y1 > maximumYpositionFloat) { + continue; // It means that the center is outside the search region. } int x_low, x_high; @@ -421,7 +450,7 @@ vpCircleHoughTransform::computeCenterCandidates() vpImage centerCandidatesMaxima = centersAccum; int niters = std::max(m_algoParams.m_dilatationNbIter, 1); // Ensure at least one dilatation operation for (int i = 0; i < niters; i++) { - vpImageMorphology::dilatation(centerCandidatesMaxima, vpImageMorphology::CONNEXITY_4); + vpImageMorphology::dilatation(centerCandidatesMaxima, vpImageMorphology::CONNEXITY_8); } // Look for the image points that correspond to the accumulator maxima @@ -464,8 +493,8 @@ vpCircleHoughTransform::computeCircleCandidates() std::vector radiusAccumList; /*!< Radius accumulator for each center candidates.*/ std::vector radiusActualValueList; /*!< Vector that contains the actual distance between the edge points and the center candidates.*/ - unsigned int rmin2 = m_algoParams.m_minRadius * m_algoParams.m_minRadius; - unsigned int rmax2 = static_cast(m_algoParams.m_maxRadius * m_algoParams.m_maxRadius); + float rmin2 = m_algoParams.m_minRadius * m_algoParams.m_minRadius; + float rmax2 = static_cast(m_algoParams.m_maxRadius * m_algoParams.m_maxRadius); int circlePerfectness2 = static_cast(m_algoParams.m_circlePerfectness * m_algoParams.m_circlePerfectness); for (size_t i = 0; i < nbCenterCandidates; i++) { @@ -481,7 +510,6 @@ vpCircleHoughTransform::computeCircleCandidates() unsigned int rx = edgePoint.first - centerCandidate.first; unsigned int ry = edgePoint.second - centerCandidate.second; unsigned int r2 = rx * rx + ry * ry; - if ((r2 > rmin2) && (r2 < rmax2)) { float gx = m_dIx[edgePoint.first][edgePoint.second]; float gy = m_dIy[edgePoint.first][edgePoint.second]; From 78eaf403ef2d0b86de824987f7adeb0169d43d7d Mon Sep 17 00:00:00 2001 From: rlagneau Date: Tue, 24 Oct 2023 09:30:51 +0200 Subject: [PATCH 04/13] [FIX] Fix duplicated votes for the same center candidates from the same edge points + fix computation of radius bins --- modules/imgproc/src/vpCircleHoughTransform.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 10539de0a6..7979df593a 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -375,6 +375,8 @@ vpCircleHoughTransform::computeCenterCandidates() for (int k1 = 0; k1 < 2; k1++) { bool hasToStopLoop = false; + int x_low_prev = std::numeric_limits::max(), y_low_prev, y_high_prev; + int x_high_prev = y_low_prev = y_high_prev = x_low_prev; for (int rad = int_minRad; rad <= int_maxRad && !hasToStopLoop; rad++) { float x1 = (float)c + (float)rad * sx; float y1 = (float)r + (float)rad * sy; @@ -405,6 +407,17 @@ vpCircleHoughTransform::computeCenterCandidates() y_high = -(static_cast(std::floor(-1. * y1))); } + if (x_low_prev == x_low && x_high_prev == x_high && y_low_prev == y_low && y_high_prev == y_high) { + // Avoid duplicated votes to the same center candidate + continue; + } + else { + x_low_prev = x_low; + x_high_prev = x_high; + y_low_prev = y_low; + y_high_prev = y_high; + } + auto updateAccumulator = [](const float &x_orig, const float &y_orig, const int &x, const int &y, @@ -488,7 +501,7 @@ void vpCircleHoughTransform::computeCircleCandidates() { size_t nbCenterCandidates = m_centerCandidatesList.size(); - unsigned int nbBins = static_cast((m_algoParams.m_maxRadius - m_algoParams.m_minRadius + 1)/ m_algoParams.m_centerMinDist); + unsigned int nbBins = static_cast((m_algoParams.m_maxRadius - m_algoParams.m_minRadius + 1)/ m_algoParams.m_mergingRadiusDiffThresh); nbBins = std::max((unsigned int)1, nbBins); // Avoid having 0 bins, which causes segfault std::vector radiusAccumList; /*!< Radius accumulator for each center candidates.*/ std::vector radiusActualValueList; /*!< Vector that contains the actual distance between the edge points and the center candidates.*/ @@ -520,7 +533,7 @@ vpCircleHoughTransform::computeCircleCandidates() if (scalProd2 >= circlePerfectness2 * r2 * grad2) { // Look for the Radius Candidate Bin RCB_k to which d_ij is "the closest" will have an additionnal vote float r = static_cast(std::sqrt(r2)); - unsigned int r_bin = static_cast(std::ceil((r - m_algoParams.m_minRadius)/ m_algoParams.m_centerMinDist)); + unsigned int r_bin = static_cast(std::floor((r - m_algoParams.m_minRadius)/ m_algoParams.m_mergingRadiusDiffThresh)); r_bin = std::min(r_bin, nbBins - 1); radiusAccumList[r_bin]++; radiusActualValueList[r_bin] += r; From edfcdf81c595a9c3eb2743e46401620774b29b56 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Tue, 24 Oct 2023 14:01:12 +0200 Subject: [PATCH 05/13] [CORPS] Added an averaging window to compute the center candidates + [FIX] Fixed problems in the type of data in the circle candidates computation --- .../visp3/imgproc/vpCircleHoughTransform.h | 52 ++++++++++++++----- .../imgproc/src/vpCircleHoughTransform.cpp | 37 ++++++++----- .../hough-transform/config/detector_img.json | 13 ++--- .../hough-transform/tutorial-circle-hough.cpp | 4 +- 4 files changed, 73 insertions(+), 33 deletions(-) diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index 38eca3e944..107e7e9f73 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -92,7 +92,9 @@ class VISP_EXPORT vpCircleHoughTransform float m_minRadius; /*!< Minimum radius of the circles we want to detect.*/ float m_maxRadius; /*!< Maximum radius of the circles we want to detect.*/ int m_dilatationNbIter; /*!< Number of times dilatation is performed to detect the maximum number of votes for the center candidates.*/ - float m_centerThresh; /*!< Minimum number of votes a point must exceed to be considered as center candidate.*/ + int m_averagingWindowSize; /*!< Size of the averaging window around the maximum number of votes to compute the + center candidate such as it is the barycenter of the window. Must be odd.*/ + float m_centerMinThresh; /*!< Minimum number of votes a point must exceed to be considered as center candidate.*/ // // Circle candidates computation attributes float m_circleProbaThresh; /*!< Probability threshold in order to keep a circle candidate.*/ @@ -119,7 +121,8 @@ class VISP_EXPORT vpCircleHoughTransform , m_minRadius(0.f) , m_maxRadius(1000.f) , m_dilatationNbIter(1) - , m_centerThresh(50.f) + , m_averagingWindowSize(5) + , m_centerMinThresh(50.f) , m_circleProbaThresh(0.9f) , m_circlePerfectness(0.9f) , m_centerMinDist(15.f) @@ -149,6 +152,8 @@ class VISP_EXPORT vpCircleHoughTransform * \param[in] circlePerfectness The scalar product radius RC_ij . gradient(Ep_j) >= m_circlePerfectness * || RC_ij || * || gradient(Ep_j) || to add a vote for the radius RC_ij. * \param[in] centerMinDistThresh Two circle candidates whose centers are closer than this threshold are considered for merging. * \param[in] mergingRadiusDiffThresh Maximum radius difference between two circle candidates to consider merging them. + * \param[in] averagingWindowSize Size of the averaging window around the maximum number of votes to compute the + center candidate such as it is the barycenter of the window. Must be odd. */ vpCircleHoughTransformParameters( const int &gaussianKernelSize @@ -167,6 +172,7 @@ class VISP_EXPORT vpCircleHoughTransform , const float &circlePerfectness , const float ¢erMinDistThresh , const float &mergingRadiusDiffThresh + , const int &averagingWindowSize = 5 ) : m_gaussianKernelSize(gaussianKernelSize) , m_gaussianStdev(gaussianStdev) @@ -179,7 +185,8 @@ class VISP_EXPORT vpCircleHoughTransform , m_minRadius(std::min(minRadius, maxRadius)) , m_maxRadius(std::max(minRadius, maxRadius)) , m_dilatationNbIter(dilatationNbIter) - , m_centerThresh(centerThresh) + , m_averagingWindowSize(averagingWindowSize) + , m_centerMinThresh(centerThresh) , m_circleProbaThresh(circleProbabilityThresh) , m_circlePerfectness(circlePerfectness) , m_centerMinDist(centerMinDistThresh) @@ -201,7 +208,8 @@ class VISP_EXPORT vpCircleHoughTransform txt += "\tCenter vertical position limits: min = " + std::to_string(m_centerYlimits.first) + "\tmax = " + std::to_string(m_centerYlimits.second) +"\n"; txt += "\tRadius limits: min = " + std::to_string(m_minRadius) + "\tmax = " + std::to_string(m_maxRadius) +"\n"; txt += "\tNumber of repetitions of the dilatation filter = " + std::to_string(m_dilatationNbIter) + "\n"; - txt += "\tCenters votes threshold = " + std::to_string(m_centerThresh) + "\n"; + txt += "\tAveraging window size for center detection = " + std::to_string(m_averagingWindowSize) + "\n"; + txt += "\tCenters votes threshold = " + std::to_string(m_centerMinThresh) + "\n"; txt += "\tCircle probability threshold = " + std::to_string(m_circleProbaThresh) + "\n"; txt += "\tCircle perfectness threshold = " + std::to_string(m_circlePerfectness) + "\n"; txt += "\tCenters minimum distance = " + std::to_string(m_centerMinDist) + "\n"; @@ -293,8 +301,13 @@ class VISP_EXPORT vpCircleHoughTransform params.m_dilatationNbIter = j.value("dilatationNbIter", params.m_dilatationNbIter); - params.m_centerThresh = j.value("centerThresh", params.m_centerThresh); - if (params.m_centerThresh <= 0) { + params.m_averagingWindowSize = j.value("averagingWindowSize", params.m_averagingWindowSize); + if (params.m_averagingWindowSize <= 0 || params.m_averagingWindowSize % 2 == 0) { + throw vpException(vpException::badValue, "Averaging window size must be positive and odd."); + } + + params.m_centerMinThresh = j.value("centerThresh", params.m_centerMinThresh); + if (params.m_centerMinThresh <= 0.f) { throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive."); } @@ -338,7 +351,8 @@ class VISP_EXPORT vpCircleHoughTransform {"centerYlimits", params.m_centerYlimits}, {"radiusLimits", radiusLimits}, {"dilatationNbIter", params.m_dilatationNbIter}, - {"centerThresh", params.m_centerThresh}, + {"averagingWindowSize", params.m_averagingWindowSize}, + {"centerThresh", params.m_centerMinThresh}, {"circleProbabilityThreshold", params.m_circleProbaThresh}, {"circlePerfectnessThreshold", params.m_circlePerfectness}, {"centerMinDistance", params.m_centerMinDist}, @@ -576,15 +590,27 @@ class VISP_EXPORT vpCircleHoughTransform * * \param[in] dilatationRepet Number of repetition of the dilatation operation to detect the maxima in the center accumulator. * \param[in] centerThresh Minimum number of votes a point must exceed to be considered as center candidate. + * \param[in] averagingWindowSize Size of the averaging window around the maximum number of votes to compute the + center candidate such as it is the barycenter of the window. Must be odd. */ - inline void setCenterComputationParameters(const int &dilatationRepet, const float ¢erThresh) + inline void setCenterComputationParameters(const int &dilatationRepet, const float ¢erThresh, + const int &averagingWindowSize = 5) { m_algoParams.m_dilatationNbIter = dilatationRepet; - m_algoParams.m_centerThresh = centerThresh; + m_algoParams.m_centerMinThresh = centerThresh; + m_algoParams.m_averagingWindowSize = averagingWindowSize; + + if (m_algoParams.m_dilatationNbIter < 0) { + throw vpException(vpException::badValue, "Dilatations for center detection must be positive."); + } - if (m_algoParams.m_centerThresh <= 0) { + if (m_algoParams.m_centerMinThresh <= 0.f) { throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive."); } + + if (m_algoParams.m_averagingWindowSize <= 0 || m_algoParams.m_averagingWindowSize % 2 == 0) { + throw vpException(vpException::badValue, "Averaging window size must be positive and odd."); + } } /** @@ -621,9 +647,9 @@ class VISP_EXPORT vpCircleHoughTransform /** * \brief Get the list of Center Candidates, stored as pair * - * \return std::vector > The list of Center Candidates, stored as pair + * \return std::vector > The list of Center Candidates, stored as pair */ - inline std::vector > getCenterCandidatesList() + inline std::vector > getCenterCandidatesList() { return m_centerCandidatesList; } @@ -831,7 +857,7 @@ class VISP_EXPORT vpCircleHoughTransform // // Center candidates computation attributes std::vector > m_edgePointsList; /*!< Vector that contains the list of edge points, to make faster some parts of the algo. They are stored as pair<#row, #col>.*/ - std::vector > m_centerCandidatesList; /*!< Vector that contains the list of center candidates. They are stored as pair<#row, #col>.*/ + std::vector > m_centerCandidatesList; /*!< Vector that contains the list of center candidates. They are stored as pair<#row, #col>.*/ std::vector m_centerVotes; /*!< Number of votes for the center candidates that are kept.*/ // // Circle candidates computation attributes diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 7979df593a..f727316222 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -475,7 +475,7 @@ vpCircleHoughTransform::computeCenterCandidates() for (int y = 0; y < nbRowsAccum; y++) { int left = -1; for (int x = 0; x < nbColsAccum; x++) { - if (centersAccum[y][x] >= m_algoParams.m_centerThresh + if (centersAccum[y][x] >= m_algoParams.m_centerMinThresh && centersAccum[y][x] == centerCandidatesMaxima[y][x] && centersAccum[y][x] > centersAccum[y][x + 1] ) { @@ -485,10 +485,23 @@ vpCircleHoughTransform::computeCenterCandidates() } else if (left >= 0) { int cx = (int)((left + x - 1) * 0.5f); - m_centerCandidatesList.push_back(std::pair(y + offsetY, cx + offsetX)); - m_centerVotes.push_back(nbVotes); + float sumVotes = 0.; + float x_avg = 0., y_avg = 0.; + int averagingWindowHalfSize = m_algoParams.m_averagingWindowSize / 2; + for (int r = -averagingWindowHalfSize; r < averagingWindowHalfSize + 1; r++) { + for (int c = -averagingWindowHalfSize; c < averagingWindowHalfSize + 1; c++) { + sumVotes += centersAccum[y + r][cx + c]; + x_avg += centersAccum[y + r][cx + c] * (cx + c); + y_avg += centersAccum[y + r][cx + c] * (y + r); + } + } + float avgVotes = sumVotes / (float)(m_algoParams.m_averagingWindowSize * m_algoParams.m_averagingWindowSize); + x_avg /= (float)(sumVotes); + y_avg /= (float)(sumVotes); + m_centerCandidatesList.push_back(std::pair(y_avg + (float)offsetY, x_avg + (float)offsetX)); + m_centerVotes.push_back(avgVotes); if (nbVotes < 0) { - throw(vpException(vpException::badValue, "nbVotes (" + std::to_string(nbVotes) + ") < 0, thresh = " + std::to_string(m_algoParams.m_centerThresh))); + throw(vpException(vpException::badValue, "nbVotes (" + std::to_string(nbVotes) + ") < 0, thresh = " + std::to_string(m_algoParams.m_centerMinThresh))); } left = -1; nbVotes = -1; @@ -507,11 +520,11 @@ vpCircleHoughTransform::computeCircleCandidates() std::vector radiusActualValueList; /*!< Vector that contains the actual distance between the edge points and the center candidates.*/ float rmin2 = m_algoParams.m_minRadius * m_algoParams.m_minRadius; - float rmax2 = static_cast(m_algoParams.m_maxRadius * m_algoParams.m_maxRadius); - int circlePerfectness2 = static_cast(m_algoParams.m_circlePerfectness * m_algoParams.m_circlePerfectness); + float rmax2 = m_algoParams.m_maxRadius * m_algoParams.m_maxRadius; + float circlePerfectness2 = m_algoParams.m_circlePerfectness * m_algoParams.m_circlePerfectness; for (size_t i = 0; i < nbCenterCandidates; i++) { - std::pair centerCandidate = m_centerCandidatesList[i]; + std::pair centerCandidate = m_centerCandidatesList[i]; // Initialize the radius accumulator of the candidate with 0s radiusAccumList.clear(); radiusAccumList.resize(nbBins, 0); @@ -520,9 +533,9 @@ vpCircleHoughTransform::computeCircleCandidates() for (auto edgePoint : m_edgePointsList) { // For each center candidate CeC_i, compute the distance with each edge point EP_j d_ij = dist(CeC_i; EP_j) - unsigned int rx = edgePoint.first - centerCandidate.first; - unsigned int ry = edgePoint.second - centerCandidate.second; - unsigned int r2 = rx * rx + ry * ry; + float rx = edgePoint.first - centerCandidate.first; + float ry = edgePoint.second - centerCandidate.second; + float r2 = rx * rx + ry * ry; if ((r2 > rmin2) && (r2 < rmax2)) { float gx = m_dIx[edgePoint.first][edgePoint.second]; float gy = m_dIy[edgePoint.first][edgePoint.second]; @@ -532,7 +545,7 @@ vpCircleHoughTransform::computeCircleCandidates() float scalProd2 = scalProd * scalProd; if (scalProd2 >= circlePerfectness2 * r2 * grad2) { // Look for the Radius Candidate Bin RCB_k to which d_ij is "the closest" will have an additionnal vote - float r = static_cast(std::sqrt(r2)); + float r = std::sqrt(r2); unsigned int r_bin = static_cast(std::floor((r - m_algoParams.m_minRadius)/ m_algoParams.m_mergingRadiusDiffThresh)); r_bin = std::min(r_bin, nbBins - 1); radiusAccumList[r_bin]++; @@ -544,7 +557,7 @@ vpCircleHoughTransform::computeCircleCandidates() for (unsigned int idBin = 0; idBin < nbBins; idBin++) { // If the circle of center CeC_i and radius RCB_k has enough votes, it is added to the list // of Circle Candidates - if (radiusAccumList[idBin] > m_algoParams.m_centerThresh) { + if (radiusAccumList[idBin] > m_algoParams.m_centerMinThresh) { float r_effective = radiusActualValueList[idBin] / (float)radiusAccumList[idBin]; vpImageCircle candidateCircle(vpImagePoint(centerCandidate.first, centerCandidate.second), r_effective); float proba = computeCircleProbability(candidateCircle, radiusAccumList[idBin]); diff --git a/tutorial/imgproc/hough-transform/config/detector_img.json b/tutorial/imgproc/hough-transform/config/detector_img.json index febadcdcbe..c03729b583 100644 --- a/tutorial/imgproc/hough-transform/config/detector_img.json +++ b/tutorial/imgproc/hough-transform/config/detector_img.json @@ -1,8 +1,9 @@ { + "averagingWindowSize": 5, "lowerCannyThresh": 100.0, "upperCannyThresh": 200.0, "centerMinDistance": 5.0, - "centerThresh": 100.0, + "centerThresh": 50.0, "centerXlimits": [ 0, 1920 @@ -11,16 +12,16 @@ 0, 1080 ], - "circlePerfectnessThreshold": 0.95, + "circlePerfectnessThreshold": 0.7, "dilatationNbIter": 1, "edgeMapFilteringNbIter" : 5, - "gaussianKernelSize": 25, - "gaussianStdev": 2.5, + "gaussianKernelSize": 5, + "gaussianStdev": 1.0, "mergingRadiusDiffThresh": 5.0, "radiusLimits": [ 34, 75 ], - "circleProbabilityThreshold": 0.75, - "sobelKernelSize": 7 + "circleProbabilityThreshold": 0.725, + "sobelKernelSize": 3 } diff --git a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp index 105c71a613..28ba6c52bb 100644 --- a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp +++ b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp @@ -109,13 +109,13 @@ generateImage(const TypeInputImage &inputType) const double rightFull = 3 * width / 4; // // Disks position when Half of circles - const double topHalf = 1; // m_centerThresh(25) , m_radiusBinSize(10) , m_radiusRatioThresh(50) , m_mergingDistanceThresh(15) , m_mergingRadiusDiffThresh(1.5 * (double) m_radiusBinSize) + const double topHalf = 1; // m_centerMinThresh(25) , m_radiusBinSize(10) , m_radiusRatioThresh(50) , m_mergingDistanceThresh(15) , m_mergingRadiusDiffThresh(1.5 * (double) m_radiusBinSize) const double bottomHalf = height - 1; const double leftHalf = width / 4; const double rightHalf = 3 * width / 4; // // Disks position when Quarter of circles - const double topQuarter = 1; // m_centerThresh(15) , m_radiusBinSize(10) , m_radiusRatioThresh(50) , m_mergingDistanceThresh(15) , m_mergingRadiusDiffThresh(1.5 * (double) m_radiusBinSize) + const double topQuarter = 1; // m_centerMinThresh(15) , m_radiusBinSize(10) , m_radiusRatioThresh(50) , m_mergingDistanceThresh(15) , m_mergingRadiusDiffThresh(1.5 * (double) m_radiusBinSize) const double bottomQuarter = height - 1; const double leftQuarter = 1; const double rightQuarter = width - 1; From 41b9a38b73b6a23d20636f781910a56c4e3c1b33 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Tue, 24 Oct 2023 14:13:12 +0200 Subject: [PATCH 06/13] [CLEAN] Cleaned the merging method, to ease future modifications --- .../visp3/imgproc/vpCircleHoughTransform.h | 11 +++- .../imgproc/src/vpCircleHoughTransform.cpp | 60 ++++++------------- 2 files changed, 28 insertions(+), 43 deletions(-) diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index 107e7e9f73..d34151e132 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -832,14 +832,23 @@ class VISP_EXPORT vpCircleHoughTransform */ void computeCircleCandidates(); + /** + * \brief For each circle candidate CiC_i, check if similar circles have also been detected and if so merges them. + */ + void mergeCircleCandidates(); + /** * \brief For each circle candidate CiC_i do: * - For each other circle candidate CiC_j do: * +- Compute the similarity between CiC_i and CiC_j * +- If the similarity exceeds a threshold, merge the circle candidates CiC_i and CiC_j and remove CiC_j of the list * - Add the circle candidate CiC_i to the final list of detected circles + * \param[out] circleCandidates List of circle candidates in which we want to merge the similar circles. + * \param[out] circleCandidatesVotes List of votes of the circle candidates. + * \param[out] circleCandidatesProba List of probabilities of the circle candidates. */ - void mergeCircleCandidates(); + void mergeCandidates(std::vector &circleCandidates, std::vector &circleCandidatesVotes, + std::vector &circleCandidatesProba); vpCircleHoughTransformParameters m_algoParams; /*!< Attributes containing all the algorithm parameters.*/ diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index f727316222..80e767d251 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -589,11 +589,26 @@ vpCircleHoughTransform::computeCircleProbability(const vpImageCircle &circle, co void vpCircleHoughTransform::mergeCircleCandidates() { - // For each circle candidate CiC_i do: std::vector circleCandidates = m_circleCandidates; std::vector circleCandidatesVotes = m_circleCandidatesVotes; std::vector circleCandidatesProba = m_circleCandidatesProbabilities; - size_t nbCandidates = m_circleCandidates.size(); + // First iteration of merge + mergeCandidates(circleCandidates, circleCandidatesVotes, circleCandidatesProba); + + // Second iteration of merge + mergeCandidates(circleCandidates, circleCandidatesVotes, circleCandidatesProba); + + // Saving the results + m_finalCircles = circleCandidates; + m_finalCircleVotes = circleCandidatesVotes; + m_finalCirclesProbabilities = circleCandidatesProba; +} + +void +vpCircleHoughTransform::mergeCandidates(std::vector &circleCandidates, std::vector &circleCandidatesVotes, + std::vector &circleCandidatesProba) +{ + size_t nbCandidates = circleCandidates.size(); for (size_t i = 0; i < nbCandidates; i++) { vpImageCircle cic_i = circleCandidates[i]; // // For each other circle candidate CiC_j do: @@ -627,47 +642,8 @@ vpCircleHoughTransform::mergeCircleCandidates() } } // // Add the circle candidate CiC_i, potentially merged with other circle candidates, to the final list of detected circles - m_finalCircles.push_back(cic_i); + circleCandidates[i] = cic_i; } - - nbCandidates = m_finalCircles.size(); - for (size_t i = 0; i < nbCandidates; i++) { - vpImageCircle cic_i = m_finalCircles[i]; - // // For each other circle candidate CiC_j do: - for (size_t j = i + 1; j < nbCandidates; j++) { - vpImageCircle cic_j = m_finalCircles[j]; - // // // Compute the similarity between CiC_i and CiC_j - double distanceBetweenCenters = vpImagePoint::distance(cic_i.getCenter(), cic_j.getCenter()); - double radiusDifference = std::abs(cic_i.getRadius() - cic_j.getRadius()); - bool areCirclesSimilar = (distanceBetweenCenters < m_algoParams.m_centerMinDist - && radiusDifference < m_algoParams.m_mergingRadiusDiffThresh - ); - - if (areCirclesSimilar) { - // // // If the similarity exceeds a threshold, merge the circle candidates CiC_i and CiC_j and remove CiC_j of the list - unsigned int totalVotes = circleCandidatesVotes[i] + circleCandidatesVotes[j]; - float totalProba = circleCandidatesProba[i] + circleCandidatesProba[j]; - float newProba = 0.5f * totalProba; - float newRadius = (cic_i.getRadius() * circleCandidatesProba[i] + cic_j.getRadius() * circleCandidatesProba[j]) / totalProba; - vpImagePoint newCenter = (cic_i.getCenter() * circleCandidatesProba[i]+ cic_j.getCenter() * circleCandidatesProba[j]) / totalProba; - cic_i = vpImageCircle(newCenter, newRadius); - m_finalCircles[j] = m_finalCircles[nbCandidates - 1]; - circleCandidatesVotes[i] = totalVotes / 2; - circleCandidatesVotes[j] = circleCandidatesVotes[nbCandidates - 1]; - circleCandidatesProba[i] = newProba; - circleCandidatesProba[j] = circleCandidatesProba[nbCandidates - 1]; - m_finalCircles.pop_back(); - circleCandidatesVotes.pop_back(); - circleCandidatesProba.pop_back(); - nbCandidates--; - j--; - } - } - // // Add the circle candidate CiC_i, potentially merged with other circle candidates, to the final list of detected circles - m_finalCircles[i] = cic_i; - } - m_finalCircleVotes = circleCandidatesVotes; - m_finalCirclesProbabilities = circleCandidatesProba; } std::string From 1e4169d9ec884f4e26afc89f6edd20c5c03cc1c0 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Tue, 24 Oct 2023 15:31:50 +0200 Subject: [PATCH 07/13] [CORPS] Modified the class to permit inheritance --- .../visp3/imgproc/vpCircleHoughTransform.h | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index d34151e132..1b7950292f 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -387,7 +387,7 @@ class VISP_EXPORT vpCircleHoughTransform * \param[in] I The input gray scale image. * \return std::vector The list of 2D circles detected in the image. */ - std::vector detect(const cv::Mat &cv_I); + virtual std::vector detect(const cv::Mat &cv_I); #endif /** @@ -397,7 +397,7 @@ class VISP_EXPORT vpCircleHoughTransform * \param[in] I The input color image. * \return std::vector The list of 2D circles detected in the image. */ - std::vector detect(const vpImage &I); + virtual std::vector detect(const vpImage &I); /** * \brief Perform Circle Hough Transform to detect the circles in a gray-scale image @@ -405,7 +405,7 @@ class VISP_EXPORT vpCircleHoughTransform * \param[in] I The input gray scale image. * \return std::vector The list of 2D circles detected in the image. */ - std::vector detect(const vpImage &I); + virtual std::vector detect(const vpImage &I); #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) /** @@ -418,7 +418,7 @@ class VISP_EXPORT vpCircleHoughTransform * \return std::vector The list of 2D circles with the most number * of votes detected in the image. */ - std::vector detect(const vpImage &I, const int &nbCircles); + virtual std::vector detect(const vpImage &I, const int &nbCircles); #endif // // Configuration from files @@ -438,7 +438,7 @@ class VISP_EXPORT vpCircleHoughTransform * * \param[in] jsonPath The path towards the JSON configuration file. */ - void initFromJSON(const std::string &jsonPath); + virtual void initFromJSON(const std::string &jsonPath); /** * \brief Save the configuration of the detector in a JSON file @@ -447,7 +447,7 @@ class VISP_EXPORT vpCircleHoughTransform * * \param[in] jsonPath The path towards the JSON output file. */ - void saveConfigurationInJSON(const std::string &jsonPath) const; + virtual void saveConfigurationInJSON(const std::string &jsonPath) const; /** * \brief Read the detector configuration from JSON. All values are optional and if an argument is not present, @@ -774,12 +774,12 @@ class VISP_EXPORT vpCircleHoughTransform */ friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpCircleHoughTransform &detector); -private: +protected: /** * \brief Initialize the Gaussian filters used to blur the image and * compute the gradient images. */ - void initGaussianFilters(); + virtual void initGaussianFilters(); /** * \brief Perform Gaussian smoothing on the input image to reduce the noise @@ -788,7 +788,7 @@ class VISP_EXPORT vpCircleHoughTransform * * \param[in] I The input gray scale image. */ - void computeGradientsAfterGaussianSmoothing(const vpImage &I); + virtual void computeGradientsAfterGaussianSmoothing(const vpImage &I); /** * \brief Perform edge detection based on the computed gradients. @@ -796,19 +796,19 @@ class VISP_EXPORT vpCircleHoughTransform * * \param[in] I The input gray scale image. */ - void edgeDetection(const vpImage &I); + virtual void edgeDetection(const vpImage &I); /** * \brief Filter the edge map in order to remove isolated edge points. */ - void filterEdgeMap(); + virtual void filterEdgeMap(); /** * \brief Determine the image points that are circle center candidates. * Increment the center accumulator based on the edge points and gradient information. * Perform thresholding to keep only the center candidates that exceed the threshold. */ - void computeCenterCandidates(); + virtual void computeCenterCandidates(); /** * \brief Compute the probability of \b circle given the number of pixels voting for @@ -820,7 +820,7 @@ class VISP_EXPORT vpCircleHoughTransform * @param nbVotes The number of visible pixels of the given circle. * @return float The probability of the circle. */ - float computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes); + virtual float computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes); /** * \brief For each center candidate CeC_i, do: @@ -830,12 +830,12 @@ class VISP_EXPORT vpCircleHoughTransform * - If accum_rc[CeC_i][RCB_k] > radius_count_thresh, add the circle candidate (CeC_i, RCB_k) * to the list of circle candidates */ - void computeCircleCandidates(); + virtual void computeCircleCandidates(); /** * \brief For each circle candidate CiC_i, check if similar circles have also been detected and if so merges them. */ - void mergeCircleCandidates(); + virtual void mergeCircleCandidates(); /** * \brief For each circle candidate CiC_i do: @@ -847,7 +847,7 @@ class VISP_EXPORT vpCircleHoughTransform * \param[out] circleCandidatesVotes List of votes of the circle candidates. * \param[out] circleCandidatesProba List of probabilities of the circle candidates. */ - void mergeCandidates(std::vector &circleCandidates, std::vector &circleCandidatesVotes, + virtual void mergeCandidates(std::vector &circleCandidates, std::vector &circleCandidatesVotes, std::vector &circleCandidatesProba); From 48a2fa17676f2e871a8c5d6f24d8e69563033d15 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 25 Oct 2023 14:39:58 +0200 Subject: [PATCH 08/13] [CORPS] Added accessors for vpCircleHoughTransformParameters class --- .../visp3/imgproc/vpCircleHoughTransform.h | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index 1b7950292f..c511b4a9fb 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -193,6 +193,180 @@ class VISP_EXPORT vpCircleHoughTransform , m_mergingRadiusDiffThresh(mergingRadiusDiffThresh) { } + /** + * \brief Get the size of the Gaussian filter kernel used to smooth the input image. + * + * \return int The size of the kernel. + */ + inline int getGaussianKernelSize() const + { + return m_gaussianKernelSize; + } + + /** + * \brief Get the standard deviation of the Gaussian filter. + * + * \return float The standard deviation. + */ + inline float getGaussianStdev() const + { + return m_gaussianStdev; + } + + /** + * \brief Get the size of the gradient kernel filters used to compute the gradients. + * + * \return int The size of the kernel. + */ + inline int getGradientKernelSize() const + { + return m_sobelKernelSize; + } + + /** + * \brief Get the lower threshold for the Canny operator. Values lower than this value are rejected. + * A negative value means that the algorithm computes the lower threshold automatically. + * + * \return float The lower Canny threshold. + */ + inline float getLowerCannyThreshold() const + { + return m_lowerCannyThresh; + } + + /** + * \brief Get the upper threshold for the Canny operator. Values lower than this value are rejected. + * A negative value means that the algorithm computes the lower and upper thresholds automatically. + * + * \return float The upper Canny threshold. + */ + inline float getUpperCannyThreshold() const + { + return m_upperCannyThresh; + } + + /** + * \brief Get the number of iterations of 8-neighbor connectivity filtering to apply to the edge map. + * + * \return int The number of iterations. + */ + inline int getEdgeMapFilteringNbIter() const + { + return m_edgeMapFilteringNbIter; + } + + /** + * \brief Get the minimum and maximum position on the horizontal axis of the center of the circle we want to detect. + * + * \return std::pair The min and max x positions. + */ + inline std::pair getCenterXLimits() const + { + return m_centerXlimits; + } + + /** + * \brief Get the minimum and maximum position on the vertical axis of the center of the circle we want to detect. + * + * \return std::pair The min and max y positions. + */ + inline std::pair getCenterYLimits() const + { + return m_centerYlimits; + } + + /** + * \brief Get the minimum radius of the circles we want to detect. + * + * \return float The radius min. + */ + inline float getMinRadius() const + { + return m_minRadius; + } + + /** + * \brief Get the maximum radius of the circles we want to detect. + * + * \return float The radius max. + */ + inline float getMaxRadius() const + { + return m_maxRadius; + } + + /** + * \brief Get the number of times dilatation is performed to detect the maximum number of votes + * for the center candidates. + * + * \return int The number of iterations. + */ + inline int getDilatationNbIter() const + { + return m_dilatationNbIter; + } + + /** + * \brief Get the size of the averaging window around the maximum number of votes to compute the + * center candidate such as it is the barycenter of the window. + * + * \return int The size of the averaging window. + */ + inline int getAveragingWindowSize() const + { + return m_averagingWindowSize; + } + + /** + * \brief Get the minimum number of votes a point must exceed to be considered as center candidate. + * + * \return float The threshold. + */ + inline float getCenterMinThreshold() const + { + return m_centerMinThresh; + } + + /** + * \brief Get the probability threshold in order to keep a circle candidate. + * + * \return float The threshold. + */ + inline float getProbabilityThreshold() const + { + return m_circleProbaThresh; + } + + /** + * \brief Get the threshold for the scalar product between the radius and the gradient to count a vote. + * + * \return float The threshold. + */ + inline float getCirclePerfectness() const + { + return m_circlePerfectness; + } + + /** + * \brief Get the Maximum distance between two circle candidates centers to consider merging them. + * + * \return float The maximum distance between two centers. + */ + inline float getCenterMinDist() const + { + return m_centerMinDist; + } + + /** + * \brief Get the Maximum radius difference between two circle candidates to consider merging them. + * + * @return float The merging radius difference. + */ + inline float getMergingRadiusDiff() const + { + return m_mergingRadiusDiffThresh; + } + /** * Create a string with all the Hough transform parameters. */ From f1b68ef3b3876aa33a22d49add88dd8fd70aafdc Mon Sep 17 00:00:00 2001 From: rlagneau Date: Mon, 30 Oct 2023 08:45:44 +0100 Subject: [PATCH 09/13] [FIX] Fix renaming of m_gradientFilterKernelSize --- modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index e90886642f..ec2d07459b 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -250,7 +250,7 @@ class VISP_EXPORT vpCircleHoughTransform */ inline int getGradientKernelSize() const { - return m_sobelKernelSize; + return m_gradientFilterKernelSize; } /** From f414a5dc7c2b9880ceeae6dab71a6e50ad83c58f Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 8 Nov 2023 11:03:55 +0100 Subject: [PATCH 10/13] [WIP] Added a parameter m_expectedNbCenters which permits to the user to give a clue to the vpCircleHoughTransform of how many different centers there are in the image. Still working on the merging phase of similar centers --- .../include/visp3/core/vpImageMorphology.h | 121 ++++++++++++++- .../visp3/imgproc/vpCircleHoughTransform.h | 64 +++++--- .../imgproc/src/vpCircleHoughTransform.cpp | 146 ++++++++++++++++-- .../hough-transform/config/detector_full.json | 3 +- .../hough-transform/config/detector_half.json | 3 +- .../hough-transform/config/detector_img.json | 3 +- .../config/detector_quarter.json | 3 +- 7 files changed, 298 insertions(+), 45 deletions(-) diff --git a/modules/core/include/visp3/core/vpImageMorphology.h b/modules/core/include/visp3/core/vpImageMorphology.h index 2fd2b6408b..641111cd68 100644 --- a/modules/core/include/visp3/core/vpImageMorphology.h +++ b/modules/core/include/visp3/core/vpImageMorphology.h @@ -76,19 +76,31 @@ class VISP_EXPORT vpImageMorphology private: /** - * @brief Modify the image by applying the \b operation on each of its elements on a 3x3 + * \brief Modify the image by applying the \b operation on each of its elements on a 3x3 * grid. * - * @param T Either a class such as vpRGBa or a type such as double, unsigned char ... - * @param I The image we want to modify. - * @param null_value The value that is padded to the input image to manage the borders. - * @param operation The operation to apply to its elements on a 3x3 grid. - * @param connexity Either a 4-connexity, if we want to take into account only the horizontal + * \tparam T Either a class such as vpRGBa or a type such as double, unsigned char ... + * \param[out] I The image we want to modify. + * \param[in] null_value The value that is padded to the input image to manage the borders. + * \param[in] operation The operation to apply to its elements on a 3x3 grid. + * \param[in] connexity Either a 4-connexity, if we want to take into account only the horizontal * and vertical neighbors, or a 8-connexity, if we want to also take into account the diagonal neighbors. */ template static void imageOperation(vpImage &I, const T &null_value, const T &(*operation)(const T &, const T &), const vpConnexityType &connexity = CONNEXITY_4); + /** + * \brief Modify the image by applying the \b operation on each of its elements on a \b size x \b size + * grid. + * + * \tparam T Any type such as double, unsigned char ... + * \param[out] I The image we want to modify. + * \param[in] operation The operation to apply to its elements on a the grid. + * \param[in] size Size of the kernel of the operation. + */ + template + static void imageOperation(vpImage &I, const T &(*operation)(const T &, const T &), const int &size = 3); + public: template static void erosion(vpImage &I, Type value, Type value_out, vpConnexityType connexity = CONNEXITY_4); @@ -102,6 +114,12 @@ class VISP_EXPORT vpImageMorphology template static void dilatation(vpImage &I, const vpConnexityType &connexity = CONNEXITY_4); + template + static void erosion(vpImage &I, const int size = 3); + + template + static void dilatation(vpImage &I, const int size = 3); + #if defined(VISP_BUILD_DEPRECATED_FUNCTIONS) /*! @name Deprecated functions @@ -351,7 +369,7 @@ void vpImageMorphology::imageOperation(vpImage &I, const T &null_value, const \param I : Image to process. \param connexity : Type of connexity: 4 or 8. - \sa dilatation(vpImage &, const vpConnexityType &) + \sa dilatation(vpImage &, const vpConnexityType &) */ template void vpImageMorphology::erosion(vpImage &I, const vpConnexityType &connexity) @@ -389,6 +407,95 @@ void vpImageMorphology::dilatation(vpImage &I, const vpConnexityType &connexi const T &(*operation)(const T & a, const T & b) = std::max; vpImageMorphology::imageOperation(I, std::numeric_limits::min(), operation, connexity); } + +template +void vpImageMorphology::imageOperation(vpImage &I, const T &(*operation)(const T &, const T &), const int &size) +{ + if (size % 2 != 1) { + throw(vpException(vpException::badValue, "Dilatation kernel must be odd.")); + } + + const int width_in = I.getWidth(); + const int height_in = I.getHeight(); + int halfKernelSize = size / 2; + vpImage J = I; + + for (int r = 0; r < height_in; r++) { + // Computing the rows we can explore without going outside the limits of the image + int r_iterator_start = -halfKernelSize, r_iterator_stop = halfKernelSize + 1; + if (r - halfKernelSize < 0) { + r_iterator_start = -r; + } + else if (r + halfKernelSize >= height_in) { + r_iterator_stop = height_in - r; + } + for (int c = 0; c < width_in; c++) { + T value = I[r][c]; + // Computing the columns we can explore without going outside the limits of the image + int c_iterator_start = -halfKernelSize, c_iterator_stop = halfKernelSize + 1; + if (c - halfKernelSize < 0) { + c_iterator_start = -c; + } + else if (c + halfKernelSize >= width_in) { + c_iterator_stop = width_in - c; + } + for (int r_iterator = r_iterator_start; r_iterator < r_iterator_stop; r_iterator++) { + for (int c_iterator = c_iterator_start; c_iterator < c_iterator_stop; c_iterator++) { + value = operation(value, J[r + r_iterator][c + c_iterator]); + } + } + I[r][c] = value; + } + } +} + +/*! + * \brief Erosion of \b size >=3 with 8-connectivity. + + In our case, the erosion is performed with a flat structuring element + \f$ \left( B \left( x,y \right) = 0 \right) \f$. The erosion using + such a structuring element is equivalent to a local-minimum operator: \f[ + \left ( A \ominus B \right ) \left( x,y \right) = \textbf{min} \left \{ A + \left ( x+x', y+y' \right ) | \left ( x', y'\right ) \subseteq D_B \right \} + \f] + + * \tparam T Any type of image, except vpRGBa . + * \param[out] I The image to which the erosion must be applied, where the erosion corresponds + * to a min operator on a window of size \b size. + * \param[in] size The size of the window on which is performed the min operator for each pixel. + + \sa dilatation(vpImage &, const int &) +*/ +template +void vpImageMorphology::erosion(vpImage &I, const int size) +{ + const T &(*operation)(const T & a, const T & b) = std::min; + vpImageMorphology::imageOperation(I, operation, size); +} + +/** + * \brief Dilatation of \b size >=3 with 8-connectivity. + * + * In our case, the dilatation is performed with a flat structuring element + \f$ \left( B \left( x,y \right) = 0 \right) \f$. The dilatation using + such a structuring element is equivalent to a local-maximum operator: \f[ + \left ( A \oplus B \right ) \left( x,y \right) = \textbf{max} \left \{ A + \left ( x-x', y-y' \right ) | \left ( x', y'\right ) \subseteq D_B \right \} + \f] + * + * \tparam T Any type of image, except vpRGBa . + * \param[out] I The image to which the dilatation must be applied, where the dilatation corresponds + * to a max operator on a window of size \b size. + * \param[in] size The size of the window on which is performed the max operator for each pixel. + * + * \sa erosion(vpImage &, const int &) + */ +template +void vpImageMorphology::dilatation(vpImage &I, const int size) +{ + const T &(*operation)(const T & a, const T & b) = std::max; + vpImageMorphology::imageOperation(I, operation, size); +} #endif /* diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index ec2d07459b..492ccedb9c 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -101,10 +101,12 @@ class VISP_EXPORT vpCircleHoughTransform std::pair m_centerYlimits; /*!< Minimum and maximum position on the vertical axis of the center of the circle we want to detect.*/ float m_minRadius; /*!< Minimum radius of the circles we want to detect.*/ float m_maxRadius; /*!< Maximum radius of the circles we want to detect.*/ - int m_dilatationNbIter; /*!< Number of times dilatation is performed to detect the maximum number of votes for the center candidates.*/ + int m_dilatationKernelSize; /*!< Kernel size of the dilatation that is performed to detect the maximum number of votes for the center candidates.*/ int m_averagingWindowSize; /*!< Size of the averaging window around the maximum number of votes to compute the center candidate such as it is the barycenter of the window. Must be odd.*/ float m_centerMinThresh; /*!< Minimum number of votes a point must exceed to be considered as center candidate.*/ + int m_expectedNbCenters; /*!< Expected number of different centers in the image. If negative, all candidates centers + are kept, otherwise only up to this number are kept.*/ // // Circle candidates computation attributes float m_circleProbaThresh; /*!< Probability threshold in order to keep a circle candidate.*/ @@ -134,9 +136,10 @@ class VISP_EXPORT vpCircleHoughTransform , m_centerYlimits(std::pair(std::numeric_limits::min(), std::numeric_limits::max())) , m_minRadius(0.f) , m_maxRadius(1000.f) - , m_dilatationNbIter(1) + , m_dilatationKernelSize(3) , m_averagingWindowSize(5) , m_centerMinThresh(50.f) + , m_expectedNbCenters(-1) , m_circleProbaThresh(0.9f) , m_circlePerfectness(0.9f) , m_centerMinDist(15.f) @@ -160,7 +163,7 @@ class VISP_EXPORT vpCircleHoughTransform * \param[in] centerYlimits Minimum and maximum position on the vertical axis of the center of the circle we want to detect. * \param[in] minRadius Minimum radius of the circles we want to detect. * \param[in] maxRadius Maximum radius of the circles we want to detect. - * \param[in] dilatationNbIter Number of times dilatation is performed to detect the maximum number of votes for the center candidates + * \param[in] dilatationKernelSize Kernel size of the dilatation that is performed to detect the maximum number of votes for the center candidates. * \param[in] centerThresh Minimum number of votes a point must exceed to be considered as center candidate. * \param[in] circleProbabilityThresh Probability threshold in order to keep a circle candidate. * \param[in] circlePerfectness The scalar product radius RC_ij . gradient(Ep_j) >= m_circlePerfectness * || RC_ij || * || gradient(Ep_j) || to add a vote for the radius RC_ij. @@ -176,6 +179,8 @@ class VISP_EXPORT vpCircleHoughTransform * \param[in] upperCannyThreshRatio If the thresholds must be computed,the upper threshold will be equal to the value * such as the number of pixels of the image times \b upperThresholdRatio have an absolute gradient lower than the * upper threshold. + * \param[in] expectedNbCenters Expected number of centers in the image. If the number is negative, all the centers + * are kept. Otherwise, maximum up to this number of centers are kept. */ vpCircleHoughTransformParameters( const int &gaussianKernelSize @@ -188,7 +193,7 @@ class VISP_EXPORT vpCircleHoughTransform , const std::pair ¢erYlimits , const float &minRadius , const float &maxRadius - , const int &dilatationNbIter + , const int &dilatationKernelSize , const float ¢erThresh , const float &circleProbabilityThresh , const float &circlePerfectness @@ -199,6 +204,7 @@ class VISP_EXPORT vpCircleHoughTransform , const vpImageFilter::vpCannyBackendType &backendType = vpImageFilter::CANNY_OPENCV_BACKEND , const float &lowerCannyThreshRatio = 0.6f , const float &upperCannyThreshRatio = 0.8f + , const int &expectedNbCenters = -1 ) : m_filteringAndGradientType(filteringAndGradientMethod) , m_gaussianKernelSize(gaussianKernelSize) @@ -214,9 +220,10 @@ class VISP_EXPORT vpCircleHoughTransform , m_centerYlimits(centerYlimits) , m_minRadius(std::min(minRadius, maxRadius)) , m_maxRadius(std::max(minRadius, maxRadius)) - , m_dilatationNbIter(dilatationNbIter) + , m_dilatationKernelSize(dilatationKernelSize) , m_averagingWindowSize(averagingWindowSize) , m_centerMinThresh(centerThresh) + , m_expectedNbCenters(expectedNbCenters) , m_circleProbaThresh(circleProbabilityThresh) , m_circlePerfectness(circlePerfectness) , m_centerMinDist(centerMinDistThresh) @@ -326,14 +333,14 @@ class VISP_EXPORT vpCircleHoughTransform } /** - * \brief Get the number of times dilatation is performed to detect the maximum number of votes + * \brief Get the kernel size of the dilatation that is performed to detect the maximum number of votes * for the center candidates. * - * \return int The number of iterations. + * \return int The kernel size. */ - inline int getDilatationNbIter() const + inline int getDilatationKernelSize() const { - return m_dilatationNbIter; + return m_dilatationKernelSize; } /** @@ -357,6 +364,17 @@ class VISP_EXPORT vpCircleHoughTransform return m_centerMinThresh; } + /** + * \brief Get the expected number of centers in the image. If the number is negative, all the centers + * are kept. Otherwise, maximum up to this number of centers are kept. + * + * \return int The expected number of centers. + */ + inline int getExpectedNbCenters() const + { + return m_expectedNbCenters; + } + /** * \brief Get the probability threshold in order to keep a circle candidate. * @@ -414,9 +432,10 @@ class VISP_EXPORT vpCircleHoughTransform txt += "\tCenter horizontal position limits: min = " + std::to_string(m_centerXlimits.first) + "\tmax = " + std::to_string(m_centerXlimits.second) +"\n"; txt += "\tCenter vertical position limits: min = " + std::to_string(m_centerYlimits.first) + "\tmax = " + std::to_string(m_centerYlimits.second) +"\n"; txt += "\tRadius limits: min = " + std::to_string(m_minRadius) + "\tmax = " + std::to_string(m_maxRadius) +"\n"; - txt += "\tNumber of repetitions of the dilatation filter = " + std::to_string(m_dilatationNbIter) + "\n"; + txt += "\tKernel size of the dilatation filter = " + std::to_string(m_dilatationKernelSize) + "\n"; txt += "\tAveraging window size for center detection = " + std::to_string(m_averagingWindowSize) + "\n"; txt += "\tCenters votes threshold = " + std::to_string(m_centerMinThresh) + "\n"; + txt += "\tExpected number of centers = " + (m_expectedNbCenters > 0 ? std::to_string(m_expectedNbCenters) : "no limits") + "\n"; txt += "\tCircle probability threshold = " + std::to_string(m_circleProbaThresh) + "\n"; txt += "\tCircle perfectness threshold = " + std::to_string(m_circlePerfectness) + "\n"; txt += "\tCenters minimum distance = " + std::to_string(m_centerMinDist) + "\n"; @@ -515,13 +534,15 @@ class VISP_EXPORT vpCircleHoughTransform params.m_minRadius = std::min(radiusLimits.first, radiusLimits.second); params.m_maxRadius = std::max(radiusLimits.first, radiusLimits.second); - params.m_dilatationNbIter = j.value("dilatationNbIter", params.m_dilatationNbIter); + params.m_dilatationKernelSize = j.value("dilatationKernelSize", params.m_dilatationKernelSize); params.m_averagingWindowSize = j.value("averagingWindowSize", params.m_averagingWindowSize); if (params.m_averagingWindowSize <= 0 || params.m_averagingWindowSize % 2 == 0) { throw vpException(vpException::badValue, "Averaging window size must be positive and odd."); } + params.m_expectedNbCenters = j.value("expectedNbCenters", params.m_expectedNbCenters); + params.m_centerMinThresh = j.value("centerThresh", params.m_centerMinThresh); if (params.m_centerMinThresh <= 0.f) { throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive."); @@ -570,9 +591,10 @@ class VISP_EXPORT vpCircleHoughTransform {"centerXlimits", params.m_centerXlimits}, {"centerYlimits", params.m_centerYlimits}, {"radiusLimits", radiusLimits}, - {"dilatationNbIter", params.m_dilatationNbIter}, + {"dilatationKernelSize", params.m_dilatationKernelSize}, {"averagingWindowSize", params.m_averagingWindowSize}, {"centerThresh", params.m_centerMinThresh}, + {"expectedNbCenters", params.m_expectedNbCenters}, {"circleProbabilityThreshold", params.m_circleProbaThresh}, {"circlePerfectnessThreshold", params.m_circlePerfectness}, {"centerMinDistance", params.m_centerMinDist}, @@ -866,20 +888,26 @@ class VISP_EXPORT vpCircleHoughTransform /** * \brief Set the parameters of the computation of the circle center candidates. * - * \param[in] dilatationRepet Number of repetition of the dilatation operation to detect the maxima in the center accumulator. + * \param[in] dilatationSize Kernel size of the dilatation operation used to detect the maxima in the center accumulator. * \param[in] centerThresh Minimum number of votes a point must exceed to be considered as center candidate. * \param[in] averagingWindowSize Size of the averaging window around the maximum number of votes to compute the center candidate such as it is the barycenter of the window. Must be odd. + * \param[in] expectedNbCenters Expected number of centers in the image. If the number is negative, all the centers + * are kept. Otherwise, maximum up to this number of centers are kept. */ - inline void setCenterComputationParameters(const int &dilatationRepet, const float ¢erThresh, - const int &averagingWindowSize = 5) + inline void setCenterComputationParameters(const int &dilatationSize, const float ¢erThresh, + const int &averagingWindowSize = 5, const int expectedNbCenters = -1) { - m_algoParams.m_dilatationNbIter = dilatationRepet; + m_algoParams.m_dilatationKernelSize = dilatationSize; m_algoParams.m_centerMinThresh = centerThresh; m_algoParams.m_averagingWindowSize = averagingWindowSize; + m_algoParams.m_expectedNbCenters = expectedNbCenters; - if (m_algoParams.m_dilatationNbIter < 0) { - throw vpException(vpException::badValue, "Dilatations for center detection must be positive."); + if (m_algoParams.m_dilatationKernelSize < 3) { + throw vpException(vpException::badValue, "Dilatation kernel size for center detection must be greater or equal to 3."); + } + else if ((m_algoParams.m_dilatationKernelSize % 2) == 0) { + throw vpException(vpException::badValue, "Dilatation kernel size for center detection must be odd."); } if (m_algoParams.m_centerMinThresh <= 0.f) { diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 9f69d370e9..c4a7725ca6 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -286,8 +286,8 @@ vpCircleHoughTransform::computeGradientsAfterGaussianSmoothing(const vpImage(r, c)); - // Voting for points in both direction of the gradient // Step from min_radius to max_radius in both directions of the gradient float mag = std::sqrt(m_dIx[r][c] * m_dIx[r][c] + m_dIy[r][c] * m_dIy[r][c]); @@ -416,15 +413,54 @@ vpCircleHoughTransform::computeCenterCandidates() sx = m_dIx[r][c] / mag; sy = m_dIy[r][c] / mag; } + else { + continue; + } - int int_minRad = (int)m_algoParams.m_minRadius; - int int_maxRad = (int)m_algoParams.m_maxRadius; + // Saving the edge point for further use + m_edgePointsList.push_back(std::pair(r, c)); for (int k1 = 0; k1 < 2; k1++) { bool hasToStopLoop = false; int x_low_prev = std::numeric_limits::max(), y_low_prev, y_high_prev; int x_high_prev = y_low_prev = y_high_prev = x_low_prev; - for (int rad = int_minRad; rad <= int_maxRad && !hasToStopLoop; rad++) { + + float rstart = m_algoParams.m_minRadius, rstop = m_algoParams.m_maxRadius; + float min_minus_c = minimumXpositionFloat - (float)c; + float min_minus_r = minimumYpositionFloat - (float)r; + float max_minus_c = maximumXpositionFloat - (float)c; + float max_minus_r = maximumYpositionFloat - (float)r; + if (sx > 0) { + float rmin = (min_minus_c) / sx; + rstart = std::max(rmin, m_algoParams.m_minRadius); + float rmax = (max_minus_c) / sx; + rstop = std::min(rmax, m_algoParams.m_maxRadius); + } + else if (sx < 0) { + float rmin = (max_minus_c) / sx; + rstart = std::max(rmin, m_algoParams.m_minRadius); + float rmax = (min_minus_c) / sx; + rstop = std::min(rmax, m_algoParams.m_maxRadius); + } + + if (sy > 0) { + float rmin = (min_minus_r) / sy; + rstart = std::max(rmin, rstart); + float rmax = (max_minus_r) / sy; + rstop = std::min(rmax, rstop); + } + else if (sy < 0) { + float rmin = (max_minus_r) / sy; + rstart = std::max(rmin, rstart); + float rmax = (min_minus_r) / sy; + rstop = std::min(rmax, rstop); + } + + float deltar_x = 1.f / std::abs(sx); + float deltar_y = 1.f / std::abs(sy); + float deltar = std::min(deltar_x, deltar_y); + + for (float rad = rstart; rad <= rstop && !hasToStopLoop; rad += deltar) { float x1 = (float)c + (float)rad * sx; float y1 = (float)r + (float)rad * sy; @@ -508,10 +544,8 @@ vpCircleHoughTransform::computeCenterCandidates() // Use dilatation with large kernel in order to determine the // accumulator maxima vpImage centerCandidatesMaxima = centersAccum; - int niters = std::max(m_algoParams.m_dilatationNbIter, 1); // Ensure at least one dilatation operation - for (int i = 0; i < niters; i++) { - vpImageMorphology::dilatation(centerCandidatesMaxima, vpImageMorphology::CONNEXITY_8); - } + int dilatationKernelSize = std::max(m_algoParams.m_dilatationKernelSize, 3); // Ensure at least a 3x3 dilatation operation is performed + vpImageMorphology::dilatation(centerCandidatesMaxima, dilatationKernelSize); // Look for the image points that correspond to the accumulator maxima // These points will become the center candidates @@ -519,6 +553,7 @@ vpCircleHoughTransform::computeCenterCandidates() int nbColsAccum = centersAccum.getCols(); int nbRowsAccum = centersAccum.getRows(); int nbVotes = -1; + std::vector, float>> peak_positions_votes; for (int y = 0; y < nbRowsAccum; y++) { int left = -1; for (int x = 0; x < nbColsAccum; x++) { @@ -547,10 +582,13 @@ vpCircleHoughTransform::computeCenterCandidates() } } float avgVotes = sumVotes / (float)(m_algoParams.m_averagingWindowSize * m_algoParams.m_averagingWindowSize); - x_avg /= (float)(sumVotes); - y_avg /= (float)(sumVotes); - m_centerCandidatesList.push_back(std::pair(y_avg + (float)offsetY, x_avg + (float)offsetX)); - m_centerVotes.push_back(avgVotes); + if (avgVotes > m_algoParams.m_centerMinThresh) { + x_avg /= (float)(sumVotes); + y_avg /= (float)(sumVotes); + std::pair position(y_avg + (float)offsetY, x_avg + (float)offsetX); + std::pair, float> position_vote(position, avgVotes); + peak_positions_votes.push_back(position_vote); + } if (nbVotes < 0) { throw(vpException(vpException::badValue, "nbVotes (" + std::to_string(nbVotes) + ") < 0, thresh = " + std::to_string(m_algoParams.m_centerMinThresh))); } @@ -559,6 +597,82 @@ vpCircleHoughTransform::computeCenterCandidates() } } } + + unsigned int nbPeaks = peak_positions_votes.size(); + if (nbPeaks > 0) { + std::vector has_been_merged(nbPeaks, false); + std::vector, float>> merged_peaks_position_votes; + float squared_distance_max = m_algoParams.m_centerMinDist * m_algoParams.m_centerMinDist; + for (unsigned int idPeak = 0; idPeak < nbPeaks; idPeak++) { + float votes = peak_positions_votes[idPeak].second; + if (has_been_merged[idPeak]) { + // Ignoring peak that has already been merged + continue; + } + else if (votes < m_algoParams.m_centerMinThresh) { + // Ignoring peak whose number of votes is lower than the threshold + has_been_merged[idPeak] = true; + continue; + } + std::pair position = peak_positions_votes[idPeak].first; + std::pair barycenter; + barycenter.first = position.first * peak_positions_votes[idPeak].second; + barycenter.second = position.second * peak_positions_votes[idPeak].second; + float total_votes = peak_positions_votes[idPeak].second; + float nb_electors = 1.f; + // Looking for potential similar peak in the following peaks + for (unsigned int idCandidate = idPeak + 1; idCandidate < nbPeaks; idCandidate++) { + float votes_candidate = peak_positions_votes[idCandidate].second; + if (has_been_merged[idCandidate]) { + continue; + } + else if (votes_candidate < m_algoParams.m_centerMinThresh) { + // Ignoring peak whose number of votes is lower than the threshold + has_been_merged[idCandidate] = true; + continue; + } + // Computing the distance with the peak of insterest + std::pair position_candidate = peak_positions_votes[idCandidate].first; + double squared_distance = (position.first - position_candidate.first) * (position.first - position_candidate.first) + + (position.second - position_candidate.second) * (position.second - position_candidate.second); + + // If the peaks are similar, update the barycenter peak between them and corresponding votes + if (squared_distance < squared_distance_max) { + barycenter.first += position_candidate.first * votes_candidate; + barycenter.second += position_candidate.second * votes_candidate; + total_votes += votes_candidate; + nb_electors += 1.f; + has_been_merged[idCandidate] = true; + } + } + + float avg_votes = total_votes / nb_electors; + // Only the centers having enough votes are considered + if (avg_votes > m_algoParams.m_centerMinThresh) { + barycenter.first /= total_votes; + barycenter.second /= total_votes; + std::pair, float> barycenter_votes(barycenter, avg_votes); + merged_peaks_position_votes.push_back(barycenter_votes); + } + } + + auto sortingCenters = [](const std::pair, float> &position_vote_a, + const std::pair, float> &position_vote_b) + { + return position_vote_a.second > position_vote_b.second; + }; + + std::sort(merged_peaks_position_votes.begin(), merged_peaks_position_votes.end(), sortingCenters); + + nbPeaks = merged_peaks_position_votes.size(); + int nbPeaksToKeep = (m_algoParams.m_expectedNbCenters > 0 ? m_algoParams.m_expectedNbCenters : nbPeaks); + nbPeaksToKeep = std::min(nbPeaksToKeep, (int)nbPeaks); + for (int i = 0; i < nbPeaksToKeep; i++) { + std::cout << "Peak : (" << merged_peaks_position_votes[i].first.first << " ; " << merged_peaks_position_votes[i].first.second << ")\tVotes = " << merged_peaks_position_votes[i].second << std::endl; + m_centerCandidatesList.push_back(merged_peaks_position_votes[i].first); + m_centerVotes.push_back(merged_peaks_position_votes[i].second); + } + } } void diff --git a/tutorial/imgproc/hough-transform/config/detector_full.json b/tutorial/imgproc/hough-transform/config/detector_full.json index b6bce7e38a..bff2f20969 100644 --- a/tutorial/imgproc/hough-transform/config/detector_full.json +++ b/tutorial/imgproc/hough-transform/config/detector_full.json @@ -8,6 +8,7 @@ "upperCannyThresh": -1.0, "centerMinDistance": 15.0, "centerThresh": 100.0, + "expectedNbCenters": 4, "centerXlimits": [ 0, 640 @@ -17,7 +18,7 @@ 480 ], "circlePerfectnessThreshold": 0.9, - "dilatationNbIter": 5, + "dilatationKernelSize": 5, "edgeMapFilteringNbIter" : 0, "gaussianKernelSize": 5, "gaussianStdev": 1.0, diff --git a/tutorial/imgproc/hough-transform/config/detector_half.json b/tutorial/imgproc/hough-transform/config/detector_half.json index b0879fc258..afda9ce625 100644 --- a/tutorial/imgproc/hough-transform/config/detector_half.json +++ b/tutorial/imgproc/hough-transform/config/detector_half.json @@ -8,6 +8,7 @@ "upperCannyThresh": -1.0, "centerMinDistance": 15.0, "centerThresh": 50.0, + "expectedNbCenters": 4, "centerXlimits": [ 0, 640 @@ -17,7 +18,7 @@ 480 ], "circlePerfectnessThreshold": 0.9, - "dilatationNbIter": 5, + "dilatationKernelSize": 5, "edgeMapFilteringNbIter" : 0, "gaussianKernelSize": 5, "gaussianStdev": 1.0, diff --git a/tutorial/imgproc/hough-transform/config/detector_img.json b/tutorial/imgproc/hough-transform/config/detector_img.json index 7505b4c284..e2271e3d08 100644 --- a/tutorial/imgproc/hough-transform/config/detector_img.json +++ b/tutorial/imgproc/hough-transform/config/detector_img.json @@ -8,6 +8,7 @@ "upperThresholdRatio": 0.9, "centerMinDistance": 5.0, "centerThresh": 50.0, + "expectedNbCenters": -1, "centerXlimits": [ 0, 1920 @@ -17,7 +18,7 @@ 1080 ], "circlePerfectnessThreshold": 0.7, - "dilatationNbIter": 5, + "dilatationKernelSize": 5, "edgeMapFilteringNbIter" : 5, "gaussianKernelSize": 5, "gaussianStdev": 1.0, diff --git a/tutorial/imgproc/hough-transform/config/detector_quarter.json b/tutorial/imgproc/hough-transform/config/detector_quarter.json index e5ec55d976..cc0f6b5761 100644 --- a/tutorial/imgproc/hough-transform/config/detector_quarter.json +++ b/tutorial/imgproc/hough-transform/config/detector_quarter.json @@ -8,6 +8,7 @@ "upperCannyThresh": -1.0, "centerMinDistance": 15.0, "centerThresh": 25.0, + "expectedNbCenters": 4, "centerXlimits": [ 0, 640 @@ -17,7 +18,7 @@ 480 ], "circlePerfectnessThreshold": 0.9, - "dilatationNbIter": 5, + "dilatationKernelSize": 5, "edgeMapFilteringNbIter" : 0, "gaussianKernelSize": 5, "gaussianStdev": 1.0, From 07a0510492e6691e57c7b594536095a2b2e3af4b Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 8 Nov 2023 11:33:26 +0100 Subject: [PATCH 11/13] [CLEAN] Removed debug message --- modules/core/src/image/vpImageCircle.cpp | 1 - modules/imgproc/src/vpCircleHoughTransform.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/modules/core/src/image/vpImageCircle.cpp b/modules/core/src/image/vpImageCircle.cpp index dfd8b6d7c3..e63c0545fa 100644 --- a/modules/core/src/image/vpImageCircle.cpp +++ b/modules/core/src/image/vpImageCircle.cpp @@ -965,7 +965,6 @@ float vpImageCircle::computeAngularCoverageInRoI(const vpRect &roi, const float computeIntersectionsTopRight(u_c, v_c, vmin_roi, umax_roi, radius, delta_theta); } else if (touchBottomBorder && touchTopBorder && touchLeftBorder && !touchRightBorder) { - std::cout << "DEBUG ici" << std::endl; // Touches/intersects the top, left and bottom borders of the RoI computeIntersectionsTopLeftBottom(u_c, v_c, umin_roi, vmin_roi, vmax_roi, radius, delta_theta); } diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 643eb9a6ee..095344ec71 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -668,7 +668,6 @@ vpCircleHoughTransform::computeCenterCandidates() int nbPeaksToKeep = (m_algoParams.m_expectedNbCenters > 0 ? m_algoParams.m_expectedNbCenters : nbPeaks); nbPeaksToKeep = std::min(nbPeaksToKeep, (int)nbPeaks); for (int i = 0; i < nbPeaksToKeep; i++) { - std::cout << "Peak : (" << merged_peaks_position_votes[i].first.first << " ; " << merged_peaks_position_votes[i].first.second << ")\tVotes = " << merged_peaks_position_votes[i].second << std::endl; m_centerCandidatesList.push_back(merged_peaks_position_votes[i].first); m_centerVotes.push_back(merged_peaks_position_votes[i].second); } From 6102392774d28c83f6393a590ef9e128182ba169 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Thu, 7 Dec 2023 16:07:18 +0100 Subject: [PATCH 12/13] [TUTO] Improved tutorial and removed synthetic images --- doc/tutorial/imgproc/tutorial-imgproc-cht.dox | 106 ++++--- .../hough-transform/config/detector_img.json | 2 +- .../hough-transform/tutorial-circle-hough.cpp | 295 ++++-------------- 3 files changed, 118 insertions(+), 285 deletions(-) diff --git a/doc/tutorial/imgproc/tutorial-imgproc-cht.dox b/doc/tutorial/imgproc/tutorial-imgproc-cht.dox index 852fdf6fe2..0c7411425a 100644 --- a/doc/tutorial/imgproc/tutorial-imgproc-cht.dox +++ b/doc/tutorial/imgproc/tutorial-imgproc-cht.dox @@ -22,7 +22,9 @@ we vote along a straight line that follows the gradient. Then, during the step where the algorithm votes for radius candidates for each center candidate, we check the colinearity between the gradient at a considered point and the line which links the point towards the center candidate. If they are "enough" colinear, we increment the corresponding -radius bin vote by 1. The "enough" characteristic is controlled by the circle perfectness +radius bin vote by 1. (*NB*: instead of incrementing one bin by one, we increment two bins by a number between +0 and 1 in our implementation to be more robust against the limits min and max of the radius and +the bin size). The "enough" characteristic is controlled by the circle perfectness parameter. \image html img-tutorial-cht-radius-votes.png @@ -40,44 +42,53 @@ $ cd tutorial/imgproc/hough-transform $ ./tutorial-circle-hough --help ``` -\subsection imgproc_cht_howto_synthetic How to use synthetic images - - -To run the software on the synthetic images using a JSON configuration file, -please run: +To run the software on an image like `coins2.jpg` provided with the tutorial and using a JSON configuration file, please run: ``` -$ TARGET=full # or TARGET=half # or TARGET=quarter -$ ./tutorial-circle-hough --input ${TARGET}_disks --config config/detector_${TARGET}.json +$ ./tutorial-circle-hough --input coins2.jpg --config config/detector_img.json ``` -To run the software on the synthetic images using the default parameters, -please run: -``` -$ TARGET=full # or TARGET=half # or TARGET=quarter -$ ./tutorial-circle-hough --input ${TARGET}_disks -``` - -\subsection imgproc_cht_howto_images How to use actual images - -To run the software on an actual image like `coins2.jpg` provided with the tutorial and using a JSON configuration file, please run: -``` -$ ./tutorial-circle-hough --input coins2.jpg --config config/detector_img.json +If you would rather use the command line arguments, please run: +``` +$ ./tutorial-circle-hough --input coins2.jpg \ + --averaging-window-size 5 \ + --canny-backend opencv-backend \ + --filtering-type gaussianblur+scharr-filtering \ + --canny-thresh -1 -1 \ + --lower-canny-ratio 0.6 \ + --upper-canny-ratio 0.9 \ + --gaussian-kernel 5 \ + --gaussian-sigma 1 \ + --dilatation-kernel-size 5 \ + --center-thresh 70 \ + --circle-probability-thresh 0.725 \ + --radius-limits 34 75 \ + --merging-thresh 5 5 \ + --circle-perfectness 0.65 \ + --circle-probability-thresh 0.725 \ + --center-xlim 0 1920 \ + --center-ylim 0 1080 \ + --expected-nb-centers -1 \ + --edge-filter 3 \ + --gradient-kernel 3 ``` \note The configuration file `config/detector_img.json` has been tuned to detect circles in the image `coins2.jpg`. -If the detections seem a bit off, you might need to change the parameters in `config/detector_img.json`. +If the detections seem a bit off, you might need to change the parameters in `config/detector_img.json` or in the +command line. -To run the software on an actual image using command line arguments instead, please run: +\note The default values of the program corresponds to these fine-tuned parameters. Running the program +without any additionnal parameters should give the same result: ``` -$ ./tutorial-circle-hough --input /path/to/my/image --gaussian-kernel 5 --gaussian-sigma 1 --canny-thresh 100. 200. --dilatation-repet 1 --center-thresh 200 --radius-bin 2 --circle-probability-thresh 0.75 --radius-limits 80 90 --merging-thresh 15 2 --circle-perfectness 0.9 +./tutorial-circle-hough ``` -If the detections seem a bit off, you might need to change the parameters - \subsection imgproc_cht_howto_video How to use a video You can use the software to run circle detection on a video saved as a -sequence of images that are named `${BASENAME}%d.png`. +sequence of images that are named +``` +${BASENAME}%d.png +``` For instance with `${BASENAME}` = `video_`, you can have the following list of images: `video_0001.png`, `video_0002.png` and so on. @@ -88,22 +99,31 @@ $ ./tutorial-circle-hough --input /path/to/video/${BASENAME}%d.png --config conf To run the software using the command arguments, please run: ``` -./tutorial-circle-hough --input /path/to/video/${BASENAME}%d.png --gaussian-kernel 5 --gaussian-sigma 1 --canny-thresh -1. --dilatation-repet 1 --center-thresh 200 --radius-bin 2 --radius-thresh 2 --radius-limits 80 90 --merging-thresh 15 2 --circle-perfectness 0.9 +$ ./tutorial-circle-hough --input /path/to/video/${BASENAME}%d.png \ + --averaging-window-size 5 \ + --canny-backend opencv-backend \ + --filtering-type gaussianblur+scharr-filtering \ + --canny-thresh -1 -1 \ + --lower-canny-ratio 0.6 \ + --upper-canny-ratio 0.9 \ + --gaussian-kernel 5 \ + --gaussian-sigma 1 \ + --dilatation-kernel-size 5 \ + --center-thresh 70 \ + --circle-probability-thresh 0.725 \ + --radius-limits 34 75 \ + --merging-thresh 5 5 \ + --circle-perfectness 0.65 \ + --circle-probability-thresh 0.725 \ + --center-xlim 0 1920 \ + --center-ylim 0 1080 \ + --expected-nb-centers -1 \ + --edge-filter 3 \ + --gradient-kernel 3 ``` \section imgproc_cht_explanations Detailed explanations about the tutorial -An enumeration permits to choose between the different types of synthetic images -or using actual images or videos: - -\snippet tutorial-circle-hough.cpp Enum input - -You can choose the type you want using the command line arguments. To know how to do it, -please run: -``` -$ ./tutorial-circle-hough --help -``` - If you decide to use a video as input, the relevant piece of code that permits to perform circle detection on the successive images of the video is the following: \snippet tutorial-circle-hough.cpp Manage video @@ -112,16 +132,6 @@ If you decide to use a single image as input, the relevant piece of code that pe perform circle detection on the image is the following: \snippet tutorial-circle-hough.cpp Manage single image -If you decide to use a synthetic image as input, the relevant piece of code that -launches the detection on the synthetic image is the following: -\snippet tutorial-circle-hough.cpp Manage synthetic image - -The function that draws the synthetic image is the following: -\snippet tutorial-circle-hough.cpp Draw synthetic - -It relies on the following function to draw the disks: -\snippet tutorial-circle-hough.cpp Draw disks - If you did not use a JSON file to configure the `vpCircleHoughTransform` detector, the following structure defines the parameters of the algorithm based on the command line arguments: diff --git a/tutorial/imgproc/hough-transform/config/detector_img.json b/tutorial/imgproc/hough-transform/config/detector_img.json index c9e9306ea9..aa428820c2 100644 --- a/tutorial/imgproc/hough-transform/config/detector_img.json +++ b/tutorial/imgproc/hough-transform/config/detector_img.json @@ -19,7 +19,7 @@ ], "circlePerfectnessThreshold": 0.65, "dilatationKernelSize": 5, - "edgeMapFilteringNbIter" : 5, + "edgeMapFilteringNbIter" : 3, "gaussianKernelSize": 5, "gaussianStdev": 1.0, "mergingRadiusDiffThresh": 5.0, diff --git a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp index 37b5d3a690..0134174c11 100644 --- a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp +++ b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp @@ -17,148 +17,7 @@ #include "drawingHelpers.h" -//! [Enum input] -typedef enum TypeInputImage -{ - FULL_DISKS = 0, - HALF_DISKS = 1, - QUARTER_DISKS = 2, - USER_IMG = 3 -}TypeInputImage; - -std::string typeInputImageToString(const TypeInputImage &type) -{ - std::string name; - switch (type) { - case FULL_DISKS: - name = "full_disks"; - break; - case HALF_DISKS: - name = "half_disks"; - break; - case QUARTER_DISKS: - name = "quarter_disks"; - break; - case USER_IMG: - name = "path/to/your/image"; - } - return name; -} -//! [Enum input] - -TypeInputImage typeInputImageFromString(const std::string &name) -{ - TypeInputImage type(USER_IMG); - bool hasFound(false); - for (unsigned int id = 0; id < USER_IMG && !hasFound; id++) { - TypeInputImage candidate = (TypeInputImage)id; - if (name == typeInputImageToString(candidate)) { - type = candidate; - hasFound = true; - } - } - return type; -} - -std::string getAvailableTypeInputImage(const std::string &prefix = "<", const std::string &sep = " , ", const std::string &suffix = ">") -{ - std::string list(prefix); - for (unsigned int id = 0; id < USER_IMG; id++) { - list += typeInputImageToString((TypeInputImage)id) + sep; - } - list += typeInputImageToString(USER_IMG) + suffix; - return list; -} - -//! [Draw disks] -void -drawDisk(vpImage &I, const vpImagePoint ¢er, const unsigned int &radius, - const unsigned int &borderColor, const unsigned int &fillingColor, const unsigned int &thickness, const unsigned int &bckg) - //! [Draw disks] -{ - vpImageDraw::drawCircle(I, center, radius, borderColor, thickness); - vp::floodFill(I, - center, - bckg, - fillingColor, - vpImageMorphology::CONNEXITY_4 - ); -} - -//! [Draw synthetic] -vpImage -generateImage(const TypeInputImage &inputType) -//! [Draw synthetic] -{ - // // Image dimensions and background - const unsigned int width = 640; - const unsigned int height = 480; - const unsigned int bckg = 0; - - // // Disks parameters - const unsigned int circleColor = 128; - const unsigned int circleRadius = 50; - const unsigned int circleThickness = 1; - - // // Disks position when full circles - const double topFull = height / 4; - const double bottomFull = 3 * height / 4; - const double leftFull = width / 4; - const double rightFull = 3 * width / 4; - - // // Disks position when Half of circles - const double topHalf = 1; // m_centerMinThresh(25) , m_radiusBinSize(10) , m_radiusRatioThresh(50) , m_mergingDistanceThresh(15) , m_mergingRadiusDiffThresh(1.5 * (double) m_radiusBinSize) - const double bottomHalf = height - 1; - const double leftHalf = width / 4; - const double rightHalf = 3 * width / 4; - - // // Disks position when Quarter of circles - const double topQuarter = 1; // m_centerMinThresh(15) , m_radiusBinSize(10) , m_radiusRatioThresh(50) , m_mergingDistanceThresh(15) , m_mergingRadiusDiffThresh(1.5 * (double) m_radiusBinSize) - const double bottomQuarter = height - 1; - const double leftQuarter = 1; - const double rightQuarter = width - 1; - vpImage I_src(height, width, bckg); - - // // Selecting position of the disks depending on their visibility - double top, left, bottom, right; - switch (inputType) { - case FULL_DISKS: - top = topFull; - left = leftFull; - bottom = bottomFull; - right = rightFull; - break; - case HALF_DISKS: - top = topHalf; - left = leftHalf; - bottom = bottomHalf; - right = rightHalf; - break; - case QUARTER_DISKS: - top = topQuarter; - left = leftQuarter; - bottom = bottomQuarter; - right = rightQuarter; - break; - default: - throw(vpException(vpException::badValue, "Using other type of input than the one that has been implemented to generate disks.")); - break; - } - - drawDisk(I_src, vpImagePoint(top, left), circleRadius, circleColor, circleColor, circleThickness, bckg); - drawDisk(I_src, vpImagePoint(top, left), circleRadius / 2, circleColor / 2, circleColor / 2, circleThickness, circleColor); - drawDisk(I_src, vpImagePoint(bottom, left), circleRadius, circleColor, circleColor, circleThickness, bckg); - drawDisk(I_src, vpImagePoint(bottom, left), circleRadius / 2, circleColor / 2, circleColor / 2, circleThickness, circleColor); - drawDisk(I_src, vpImagePoint(top, right), circleRadius, circleColor, circleColor, circleThickness, bckg); - drawDisk(I_src, vpImagePoint(top, right), circleRadius / 2, circleColor / 2, circleColor / 2, circleThickness, circleColor); - drawDisk(I_src, vpImagePoint(bottom, right), circleRadius, circleColor, circleColor, circleThickness, bckg); - drawDisk(I_src, vpImagePoint(bottom, right), circleRadius / 2, circleColor / 2, circleColor / 2, circleThickness, circleColor); - - std::cout << "Done drawing" << std::endl << std::flush; - return I_src; -} - -bool test_detection(const vpImage &I_src, vpCircleHoughTransform &detector, const int &nbCirclesToDetect, const bool &blockingMode, const bool &displayCanny) +bool run_detection(const vpImage &I_src, vpCircleHoughTransform &detector, const int &nbCirclesToDetect, const bool &blockingMode, const bool &displayCanny) { double t0 = vpTime::measureTimeMicros(); //! [Run detection] @@ -195,32 +54,27 @@ bool test_detection(const vpImage &I_src, vpCircleHoughTransform int main(int argc, char **argv) { - const std::string def_input(typeInputImageToString(FULL_DISKS)); + const std::string def_input("coins2.jpg"); const std::string def_jsonFilePath = std::string(""); const int def_nbCirclesToDetect = -1; const int def_gaussianKernelSize = 5; const float def_gaussianSigma = 1.f; const int def_sobelKernelSize = 3; -#ifdef HAVE_OPENCV_IMGPROC - const float def_lowerCannyThresh = 50.f; - const float def_upperCannyThresh = 150.f; -#else - const float def_lowerCannyThresh = 8.f; - const float def_upperCannyThresh = 25.f; -#endif - const int def_nbEdgeFilteringIter = 2; - const std::pair def_centerXlimits = std::pair(0, 640); - const std::pair def_centerYlimits = std::pair(0, 480); - const unsigned int def_minRadius = 0; - const unsigned int def_maxRadius = 1000; - const int def_dilatationRepet = 1; - const float def_centerThresh = -1.f; - const float def_circleProbaThresh = 0.9f; - const float def_circlePerfectness = 0.85f; - const float def_centerDistanceThresh = 15.f; - const float def_radiusDifferenceThresh = 15.f; + const float def_lowerCannyThresh = -1.f; + const float def_upperCannyThresh = -1.f; + const int def_nbEdgeFilteringIter = 3; + const std::pair def_centerXlimits = std::pair(0, 1920); + const std::pair def_centerYlimits = std::pair(0, 1080); + const unsigned int def_minRadius = 34; + const unsigned int def_maxRadius = 75; + const int def_dilatationKernelSize = 5; + const float def_centerThresh = 70.f; + const float def_circleProbaThresh = 0.725f; + const float def_circlePerfectness = 0.65f; + const float def_centerDistanceThresh = 5.f; + const float def_radiusDifferenceThresh = 5.f; const int def_averagingWindowSize = 5; - const vpImageFilter::vpCannyFilteringAndGradientType def_filteringAndGradientType = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING; + const vpImageFilter::vpCannyFilteringAndGradientType def_filteringAndGradientType = vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING; const vpImageFilter::vpCannyBackendType def_cannyBackendType = vpImageFilter::CANNY_OPENCV_BACKEND; const float def_lowerCannyThreshRatio = 0.6f; const float def_upperCannyThreshRatio = 0.9f; @@ -239,7 +93,7 @@ int main(int argc, char **argv) std::pair opt_centerYlimits = def_centerYlimits; unsigned int opt_minRadius = def_minRadius; unsigned int opt_maxRadius = def_maxRadius; - int opt_dilatationRepet = def_dilatationRepet; + int opt_dilatationKerneSize = def_dilatationKernelSize; float opt_centerThresh = def_centerThresh; float opt_circleProbaThresh = def_circleProbaThresh; float opt_circlePerfectness = def_circlePerfectness; @@ -277,7 +131,7 @@ int main(int argc, char **argv) opt_gaussianSigma = static_cast(atof(argv[i + 1])); i++; } - else if (argName == "--sobel-kernel" && i + 1 < argc) { + else if (argName == "--gradient-kernel" && i + 1 < argc) { opt_sobelKernelSize = atoi(argv[i + 1]); i++; } @@ -290,8 +144,8 @@ int main(int argc, char **argv) opt_nbEdgeFilteringIter = atoi(argv[i + 1]); i++; } - else if (argName == "--dilatation-repet" && i + 1 < argc) { - opt_dilatationRepet = atoi(argv[i + 1]); + else if (argName == "--dilatation-kernel-size" && i + 1 < argc) { + opt_dilatationKerneSize = atoi(argv[i + 1]); i++; } else if (argName == "--averaging-window-size" && i + 1 < argc) { @@ -357,20 +211,20 @@ int main(int argc, char **argv) << std::endl; std::cout << "SYNOPSIS" << std::endl; std::cout << "\t" << argv[0] - << "\t [--input " << getAvailableTypeInputImage() << "]" << std::endl + << "\t [--input ]" << std::endl #ifdef VISP_HAVE_NLOHMANN_JSON << "\t [--config ] (default: " << (def_jsonFilePath.empty() ? "unused" : def_jsonFilePath) << ")" << std::endl #endif << "\t [--nb-circles ] (default: " << def_nbCirclesToDetect << ")" << std::endl << "\t [--gaussian-kernel ] (default: " << def_gaussianKernelSize << ")" << std::endl << "\t [--gaussian-sigma ] (default: " << def_gaussianSigma << ")" << std::endl - << "\t [--sobel-kernel ] (default: " << def_sobelKernelSize << ")" << std::endl + << "\t [--gradient-kernel ] (default: " << def_sobelKernelSize << ")" << std::endl << "\t [--canny-thresh ] (default: " << def_lowerCannyThresh << " ; " << def_upperCannyThresh << ")" << std::endl << "\t [--edge-filter ] (default: " << def_nbEdgeFilteringIter << ")" << std::endl << "\t [--radius-limits ] (default: min = " << def_minRadius << ", max = " << def_maxRadius << ")" << std::endl - << "\t [--dilatation-repet ] (default: " << def_dilatationRepet << ")" << std::endl + << "\t [--dilatation-kernel-size ] (default: " << def_dilatationKernelSize << ")" << std::endl << "\t [--averaging-window-size ] (default: " << def_averagingWindowSize << ")" << std::endl - << "\t [--center-thresh ] (default: " << (def_centerThresh < 0 ? "auto" : std::to_string(def_centerThresh)) << ")" << std::endl + << "\t [--center-thresh ] (default: " << def_centerThresh << ")" << std::endl << "\t [--center-xlim ] (default: " << def_centerXlimits.first << " , " << def_centerXlimits.second << ")" << std::endl << "\t [--center-ylim ] (default: " << def_centerYlimits.first << " , " << def_centerYlimits.second << ")" << std::endl << "\t [--circle-probability-thresh ] (default: " << def_circleProbaThresh << ")" << std::endl @@ -392,7 +246,8 @@ int main(int argc, char **argv) std::cout << "DESCRIPTION" << std::endl << "\t--input" << std::endl - << "\t\tPermit to choose the type of input of the Hough Circle Algorithm" << std::endl + << "\t\tPermit to choose the input of the Hough Circle Algorithm." << std::endl + << "\t\tIf you want to use a succession of images as video, their name must be in the format ${BASENAME}\%d.{jpg, png}." << std::endl << "\t\tDefault: " << def_input << std::endl << std::endl #ifdef VISP_HAVE_NLOHMANN_JSON @@ -416,6 +271,11 @@ int main(int argc, char **argv) << "\t\tMust be a positive value." << std::endl << "\t\tDefault: " << def_gaussianSigma << std::endl << std::endl + << "\t--gradient-kernel" << std::endl + << "\t\tPermit to set the size of the Gaussian filter used to smooth the input image and compute its gradients." << std::endl + << "\t\tMust be an odd value." << std::endl + << "\t\tDefault: " << def_gaussianKernelSize << std::endl + << std::endl << "\t--canny-thresh" << std::endl << "\t\tPermit to set the lower and upper thresholds of the Canny edge detector." << std::endl << "\t\tIf a value is negative, it will be automatically computed." << std::endl @@ -430,10 +290,10 @@ int main(int argc, char **argv) << "\t\tPermit to set the minimum and maximum radii of the circles we are looking for." << std::endl << "\t\tDefault: min = " << def_minRadius << ", max = " << def_maxRadius << std::endl << std::endl - << "\t--dilatation-repet" << std::endl - << "\t\tPermit to set the number of iterations of the dilatation operation used to detect the maxima of the centers votes." << std::endl + << "\t--dilatation-kernel-size" << std::endl + << "\t\tPermit to set the size of the kernel of the dilatation operation used to detect the maxima of the centers votes." << std::endl << "\t\tMinimum tolerated value is 1." << std::endl - << "\t\tDefault: " << def_dilatationRepet << std::endl + << "\t\tDefault: " << def_dilatationKernelSize << std::endl << std::endl << "\t--averaging-window-size" << std::endl << "\t\tPermit to set the number size of the averaging window used to detect the maxima of the centers votes." << std::endl @@ -444,7 +304,7 @@ int main(int argc, char **argv) << "\t\tPermit to set the minimum number of votes a point must reach to be considered as a center candidate." << std::endl << "\t\tIf the input is a real image, must be a positive value." << std::endl << "\t\tOtherwise, if the input is a synthetic image and the value is negative, a fine-tuned value will be used." << std::endl - << "\t\tDefault: " << (def_centerThresh < 0 ? "auto" : std::to_string(def_centerThresh)) << std::endl + << "\t\tDefault: " << def_centerThresh << std::endl << std::endl << "\t--center-xlim" << std::endl << "\t\tPermit to set the minimum and maximum horizontal position to be considered as a center candidate." << std::endl @@ -502,36 +362,6 @@ int main(int argc, char **argv) } } - if (opt_centerThresh < 0 && opt_jsonFilePath.empty()) { - // The user asked to use the parameter value that has been fine-tuned - TypeInputImage inputType = typeInputImageFromString(opt_input); - switch (inputType) { - case TypeInputImage::FULL_DISKS: -#ifdef HAVE_OPENCV_IMGPROC - opt_centerThresh = 100.; -#else - opt_centerThresh = 75.; -#endif - break; - case TypeInputImage::HALF_DISKS: -#ifdef HAVE_OPENCV_IMGPROC - opt_centerThresh = 50.; -#else - opt_centerThresh = 25.; -#endif - break; - case TypeInputImage::QUARTER_DISKS: -#ifdef HAVE_OPENCV_IMGPROC - opt_centerThresh = 25.; -#else - opt_centerThresh = 15.; -#endif - break; - default: - throw(vpException(vpException::badValue, "Missing center threshold value to use with actual pictures as input. See the help for more information.")); - } - } - //! [Algo params] vpCircleHoughTransform::vpCircleHoughTransformParameters algoParams(opt_gaussianKernelSize @@ -544,7 +374,7 @@ int main(int argc, char **argv) , opt_centerYlimits , opt_minRadius , opt_maxRadius - , opt_dilatationRepet + , opt_dilatationKerneSize , opt_centerThresh , opt_circleProbaThresh , opt_circlePerfectness @@ -578,39 +408,32 @@ int main(int argc, char **argv) detector.saveConfigurationInJSON("runConfiguration.json"); vpImage I_src; - TypeInputImage inputType = typeInputImageFromString(opt_input); - if (inputType == USER_IMG) { - //! [Manage video] - if (opt_input.find("%") != std::string::npos) { - // The user wants to read a sequence of images from different files - bool hasToContinue = true; - vpVideoReader g; - g.setFileName(opt_input); - g.open(I_src); - while (!g.end() && hasToContinue) { - g.acquire(I_src); - hasToContinue = test_detection(I_src, detector, opt_nbCirclesToDetect, false, opt_displayCanny); - vpTime::wait(40); - } - } - //! [Manage video] - else { - //! [Manage single image] - // Check if opt_input exists - if (!vpIoTools::checkFilename(opt_input)) { - throw(vpException(vpException::ioError, "Input file \"" + opt_input + "\" does not exist !")); - } - // Read the image and perform detection on it - vpImageIo::read(I_src, opt_input); - test_detection(I_src, detector, opt_nbCirclesToDetect, true, opt_displayCanny); - //! [Manage single image] + + //! [Manage video] + if (opt_input.find("%") != std::string::npos) { + // The user wants to read a sequence of images from different files + bool hasToContinue = true; + vpVideoReader g; + g.setFileName(opt_input); + g.open(I_src); + while (!g.end() && hasToContinue) { + g.acquire(I_src); + hasToContinue = run_detection(I_src, detector, opt_nbCirclesToDetect, false, opt_displayCanny); + vpTime::wait(40); } } + //! [Manage video] else { - //! [Manage synthetic image] - I_src = generateImage(inputType); - test_detection(I_src, detector, opt_nbCirclesToDetect, true, opt_displayCanny); - //! [Manage synthetic image] + //! [Manage single image] + // Check if opt_input exists + if (!vpIoTools::checkFilename(opt_input)) { + throw(vpException(vpException::ioError, "Input file \"" + opt_input + "\" does not exist !")); + } + // Read the image and perform detection on it + vpImageIo::read(I_src, opt_input); + run_detection(I_src, detector, opt_nbCirclesToDetect, true, opt_displayCanny); + //! [Manage single image] } + return EXIT_SUCCESS; } From ad17d8819be08b0626fe48b584394a228009a549 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Thu, 7 Dec 2023 16:59:56 +0100 Subject: [PATCH 13/13] [FIX] Fixed compilation failure in tuto --- tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp index 0134174c11..d89f7bcb51 100644 --- a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp +++ b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp @@ -405,7 +405,6 @@ int main(int argc, char **argv) } //! [Algo init] std::cout << detector; - detector.saveConfigurationInJSON("runConfiguration.json"); vpImage I_src;