From b629f68b60bf34b2536792484ac45d507a31e7c5 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 17 Jan 2024 12:36:31 +0100 Subject: [PATCH 01/10] [CORPS] Possibility to use boolean mask to perform filtering operations, image histogram, canny filtering and circle Hough detection --- .../include/visp3/core/vpCannyEdgeDetection.h | 16 + modules/core/include/visp3/core/vpHistogram.h | 60 +- .../core/include/visp3/core/vpImageCircle.h | 9 + .../core/include/visp3/core/vpImageFilter.h | 879 +++++++++++------- .../core/src/image/vpCannyEdgeDetection.cpp | 19 +- modules/core/src/image/vpImageCircle.cpp | 91 ++ modules/core/src/image/vpImageFilter.cpp | 337 ++++--- .../core/src/tools/histogram/vpHistogram.cpp | 294 +++--- .../visp3/imgproc/vpCircleHoughTransform.h | 171 +++- .../imgproc/src/vpCircleHoughTransform.cpp | 416 ++++++--- .../hough-transform/tutorial-circle-hough.cpp | 8 +- 11 files changed, 1491 insertions(+), 809 deletions(-) diff --git a/modules/core/include/visp3/core/vpCannyEdgeDetection.h b/modules/core/include/visp3/core/vpCannyEdgeDetection.h index 7eae38dc72..cae0d4e109 100644 --- a/modules/core/include/visp3/core/vpCannyEdgeDetection.h +++ b/modules/core/include/visp3/core/vpCannyEdgeDetection.h @@ -89,6 +89,7 @@ class VISP_EXPORT vpCannyEdgeDetection std::map, EdgeType> m_edgePointsCandidates; /*!< Map that contains the strong edge points, i.e. the points for which we know for sure they are edge points, and the weak edge points, i.e. the points for which we still must determine if they are actual edge points.*/ vpImage m_edgeMap; /*!< Final edge map that results from the whole Canny algorithm.*/ + const vpImage *mp_mask; /*!< Mask that permits to consider only the pixels for which the mask is true.*/ /** @name Constructors and initialization */ //@{ @@ -366,6 +367,21 @@ class VISP_EXPORT vpCannyEdgeDetection m_gradientFilterKernelSize = apertureSize; initGradientFilters(); } + + /** + * \brief Set a mask to ignore pixels for which the mask is false. + * + * \warning The mask must be reset manually by the user (either for another mask + * or set to \b nullptr ) before computing the edge-map of another image. + * + * @param p_mask If different of \b nullptr , a mask of booleans where \b true + * indicates that a pixel must be considered and \b false that the pixel should + * be ignored. + */ + inline void setMask(const vpImage *p_mask) + { + mp_mask = p_mask; + } //@} }; #endif diff --git a/modules/core/include/visp3/core/vpHistogram.h b/modules/core/include/visp3/core/vpHistogram.h index 20dabd6a1d..9672ef90a7 100644 --- a/modules/core/include/visp3/core/vpHistogram.h +++ b/modules/core/include/visp3/core/vpHistogram.h @@ -110,6 +110,7 @@ class VISP_EXPORT vpHistogram vpHistogram(); vpHistogram(const vpHistogram &h); explicit vpHistogram(const vpImage &I); + explicit vpHistogram(const vpImage &I, const vpImage *p_mask); virtual ~vpHistogram(); vpHistogram &operator=(const vpHistogram &h); @@ -136,12 +137,12 @@ class VISP_EXPORT vpHistogram */ inline unsigned operator[](const unsigned char level) const { - if (level < size) { - return histogram[level]; + if (level < m_size) { + return m_histogram[level]; } std::stringstream ss; - ss << "Level is > to size (" << size << ") !"; + ss << "Level is > to size (" << m_size << ") !"; throw vpException(vpException::dimensionError, ss.str().c_str()); }; /*! @@ -166,12 +167,12 @@ class VISP_EXPORT vpHistogram */ inline unsigned operator()(const unsigned char level) const { - if (level < size) { - return histogram[level]; + if (level < m_size) { + return m_histogram[level]; } std::stringstream ss; - ss << "Level is > to size (" << size << ") !"; + ss << "Level is > to size (" << m_size << ") !"; throw vpException(vpException::dimensionError, ss.str().c_str()); }; /*! @@ -196,12 +197,12 @@ class VISP_EXPORT vpHistogram */ inline unsigned get(const unsigned char level) const { - if (level < size) { - return histogram[level]; + if (level < m_size) { + return m_histogram[level]; } std::stringstream ss; - ss << "Level is > to size (" << size << ") !"; + ss << "Level is > to size (" << m_size << ") !"; throw vpException(vpException::dimensionError, ss.str().c_str()); }; @@ -224,15 +225,31 @@ class VISP_EXPORT vpHistogram */ inline void set(const unsigned char level, unsigned int value) { - if (level < size) { - histogram[level] = value; - } else { + if (level < m_size) { + m_histogram[level] = value; + } + else { std::stringstream ss; - ss << "Level is > to size (" << size << ") !"; + ss << "Level is > to size (" << m_size << ") !"; throw vpException(vpException::dimensionError, ss.str().c_str()); } }; + /** + * \brief Set a mask to ignore pixels for which the mask is false. + * + * \warning The mask must be reset manually by the user (either for another mask + * or set to \b nullptr ) before computing the histogram of another image. + * + * @param p_mask If different of \b nullptr , a mask of booleans where \b true + * indicates that a pixel must be considered and \b false that the pixel should + * be ignored. + */ + inline void setMask(const vpImage *p_mask) + { + mp_mask = p_mask; + } + void calculate(const vpImage &I, unsigned int nbins = 256, unsigned int nbThreads = 1); void equalize(const vpImage &I, vpImage &Iout); @@ -260,7 +277,7 @@ class VISP_EXPORT vpHistogram \sa getValues() */ - inline unsigned getSize() const { return size; }; + inline unsigned getSize() const { return m_size; }; /*! @@ -283,13 +300,22 @@ class VISP_EXPORT vpHistogram \sa getSize() */ - inline unsigned *getValues() { return histogram; }; + inline unsigned *getValues() { return m_histogram; }; + + /** + * \brief Get the total number of pixels in the input image. + * + * \return unsigned int Cumulated number of pixels in the input image. + */ + inline unsigned int getTotal() { return m_total; }; private: void init(unsigned size = 256); - unsigned int *histogram; - unsigned size; // Histogram size (max allowed 256) + unsigned int *m_histogram; /*!< The storage for the histogram.*/ + unsigned m_size; /*!< Histogram size (max allowed 256).*/ + const vpImage *mp_mask; /*!< Mask that permits to consider only the pixels for which the mask is true.*/ + unsigned int m_total; /*!< Cumulated number of pixels in the input image. */ }; #endif diff --git a/modules/core/include/visp3/core/vpImageCircle.h b/modules/core/include/visp3/core/vpImageCircle.h index 04a454fea8..80a7f167a3 100644 --- a/modules/core/include/visp3/core/vpImageCircle.h +++ b/modules/core/include/visp3/core/vpImageCircle.h @@ -92,6 +92,15 @@ class VISP_EXPORT vpImageCircle */ float computeArcLengthInRoI(const vpRect &roi, const float &roundingTolerance = 0.001f) const; + /** + * \brief Count the number of pixels of the circle whose value in the mask is true. + * + * \param mask A mask where true indicates that a pixel must be taken into account and false + * that it must be ignored. + * \return unsigned int The number of pixels in the mask. + */ + unsigned int computePixelsInMask(const vpImage &mask) const; + /*! * Get the center of the image (2D) circle * \return The center of the image (2D) circle. diff --git a/modules/core/include/visp3/core/vpImageFilter.h b/modules/core/include/visp3/core/vpImageFilter.h index 560c79b673..ca87b4a1c9 100644 --- a/modules/core/include/visp3/core/vpImageFilter.h +++ b/modules/core/include/visp3/core/vpImageFilter.h @@ -34,10 +34,10 @@ #ifndef _vpImageFilter_h_ #define _vpImageFilter_h_ -/*! - * \file vpImageFilter.h - * \brief Various image filter, convolution, etc... - */ + /*! + * \file vpImageFilter.h + * \brief Various image filter, convolution, etc... + */ #include #include @@ -58,15 +58,39 @@ #include #endif -/*! - * \class vpImageFilter - * - * \ingroup group_core_image - * - * \brief Various image filter, convolution, etc... - */ + /*! + * \class vpImageFilter + * + * \ingroup group_core_image + * + * \brief Various image filter, convolution, etc... + */ class VISP_EXPORT vpImageFilter { +private: + /** + * \brief Resize the image \b I to the desired size and, if \b p_mask is different from nullptr, initialize + * \b I with 0s. + * + * @tparam ImageType Any numerical type (int, float, ...) + * @param p_mask If different from nullptr, a boolean mask that tells which pixels must be computed. + * @param height The desired height. + * @param width The desired width. + * @param I The image that must be resized and potentially initialized. + */ + template + static void resizeAndInitializeIfNeeded(const vpImage *p_mask, const unsigned int height, const unsigned int width, vpImage &I) + { + if (p_mask == nullptr) { + // Just need to resize the output image, values will be computed and overwrite what is inside the image + I.resize(height, width); + } + else { + // Need to reset the image because some points will not be computed + I.resize(height, width, static_cast(0.)); + } + } + public: //! Canny filter backends for the edge detection operations typedef enum vpCannyBackendType @@ -77,7 +101,7 @@ class VISP_EXPORT vpImageFilter } vpCannyBackendType; static std::string vpCannyBackendTypeList(const std::string &pref = "<", const std::string &sep = " , ", - const std::string &suf = ">"); + const std::string &suf = ">"); static std::string vpCannyBackendTypeToString(const vpCannyBackendType &type); @@ -109,7 +133,8 @@ class VISP_EXPORT vpImageFilter const float &lowerThresholdCanny, const float &higherThresholdCanny, const unsigned int &apertureSobel, const float &gaussianStdev, const float &lowerThresholdRatio, const float &upperThresholdRatio, const bool &normalizeGradients, - const vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps); + const vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps, + const vpImage *p_mask = nullptr); #if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) static float computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_dIx, const cv::Mat *p_cv_dIy, @@ -144,22 +169,24 @@ class VISP_EXPORT vpImageFilter * \param[in] apertureGradient The size of the kernel of the gradient filter. * \param[in] filteringType The type of filters to apply to compute the gradients. * \param[in] backend The type of backend to use to compute the gradients. + * \param[in] p_mask If different from nullptr, mask indicating which points to consider (true) or to ignore(false). */ template inline static void computePartialDerivatives(const vpImage &I, - vpImage &dIx, vpImage &dIy, - const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, - const unsigned int &gaussianKernelSize = 5, const FilterType &gaussianStdev = 2.f, - const unsigned int &apertureGradient = 3, - const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, - const vpCannyBackendType &backend = CANNY_VISP_BACKEND) + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const FilterType &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, + const vpImage *p_mask = nullptr) { if (backend == CANNY_OPENCV_BACKEND) { #if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) cv::Mat cv_I, cv_dIx, cv_dIy; vpImageConvert::convert(I, cv_I); computePartialDerivatives(cv_I, cv_dIx, cv_dIy, computeDx, computeDy, normalize, gaussianKernelSize, - gaussianStdev, apertureGradient, filteringType); + gaussianStdev, apertureGradient, filteringType); if (computeDx) { vpImageConvert::convert(cv_dIx, dIx); } @@ -177,15 +204,17 @@ class VISP_EXPORT vpImageFilter // Computing the Gaussian blur + gradients of the image vpImage Iblur; - vpImageFilter::gaussianBlur(I, Iblur, gaussianKernelSize, gaussianStdev); + vpImageFilter::gaussianBlur(I, Iblur, gaussianKernelSize, gaussianStdev, true, p_mask); vpArray2D gradientFilterX(apertureGradient, apertureGradient); // Gradient filter along the X-axis vpArray2D gradientFilterY(apertureGradient, apertureGradient); // Gradient filter along the Y-axis // Helper to apply the scale to the raw values of the filters auto scaleFilter = [](vpArray2D &filter, const float &scale) { - for (unsigned int r = 0; r < filter.getRows(); r++) { - for (unsigned int c = 0; c < filter.getCols(); c++) { + const unsigned int nbRows = filter.getRows(); + const unsigned int nbCols = filter.getCols(); + for (unsigned int r = 0; r < nbRows; ++r) { + for (unsigned int c = 0; c < nbCols; ++c) { filter[r][c] = filter[r][c] * scale; } }}; @@ -197,18 +226,18 @@ class VISP_EXPORT vpImageFilter if (filteringType == CANNY_GBLUR_SOBEL_FILTERING) { if (computeDx) { - scaleX = static_cast(vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1)/2)); + scaleX = static_cast(vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1) / 2)); } if (computeDy) { - scaleY = static_cast(vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1)/2)); + scaleY = static_cast(vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1) / 2)); } } else if (filteringType == CANNY_GBLUR_SCHARR_FILTERING) { if (computeDx) { - scaleX = static_cast(vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1)/2)); + scaleX = static_cast(vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1) / 2)); } if (computeDy) { - scaleY = static_cast(vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1)/2)); + scaleY = static_cast(vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1) / 2)); } } @@ -224,11 +253,11 @@ class VISP_EXPORT vpImageFilter // Apply the gradient filters to get the gradients if (computeDx) { - vpImageFilter::filter(Iblur, dIx, gradientFilterX); + vpImageFilter::filter(Iblur, dIx, gradientFilterX, true, p_mask); } if (computeDy) { - vpImageFilter::filter(Iblur, dIy, gradientFilterY); + vpImageFilter::filter(Iblur, dIy, gradientFilterY, true, p_mask); } } else { @@ -242,30 +271,30 @@ class VISP_EXPORT vpImageFilter template inline static void computePartialDerivatives(const vpImage &I, - vpImage &dIx, vpImage &dIy, - const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, - const unsigned int &gaussianKernelSize = 5, const FilterType &gaussianStdev = 2.f, - const unsigned int &apertureGradient = 3, - const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, - const vpCannyBackendType &backend = CANNY_VISP_BACKEND) = delete; + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const FilterType &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr) = delete; template inline static void computePartialDerivatives(const vpImage &I, - vpImage &dIx, vpImage &dIy, - const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, - const unsigned int &gaussianKernelSize = 5, const unsigned char &gaussianStdev = 2.f, - const unsigned int &apertureGradient = 3, - const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, - const vpCannyBackendType &backend = CANNY_VISP_BACKEND) = delete; + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const unsigned char &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr) = delete; template inline static void computePartialDerivatives(const vpImage &I, - vpImage &dIx, vpImage &dIy, - const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, - const unsigned int gaussianKernelSize = 5, const vpRGBa gaussianStdev = vpRGBa(), - const unsigned int apertureGradient = 3, - const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, - const vpCannyBackendType &backend = CANNY_VISP_BACKEND) = delete; + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int gaussianKernelSize = 5, const vpRGBa gaussianStdev = vpRGBa(), + const unsigned int apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr) = delete; /** * \brief Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel or + Scharr operators to compute @@ -284,57 +313,67 @@ class VISP_EXPORT vpImageFilter * the upper threshold. * \param[in] filteringType : The gradient filter to apply to compute the gradient, if \b p_dIx and \b p_dIy are * nullptr. + * \param[in] p_mask : If different from \b nullptr , only the pixels for which \b p_mask is true will be considered. * \return The upper Canny edge filter threshold. */ template inline static float computeCannyThreshold(const vpImage &I, float &lowerThresh, - const vpImage *p_dIx = nullptr, const vpImage *p_dIy = nullptr, - const unsigned int &gaussianKernelSize = 5, - const OutType &gaussianStdev = 2.f, const unsigned int &apertureGradient = 3, - const float &lowerThresholdRatio = 0.6, const float &upperThresholdRatio = 0.8, - const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING) + const vpImage *p_dIx = nullptr, const vpImage *p_dIy = nullptr, + const unsigned int &gaussianKernelSize = 5, + const OutType &gaussianStdev = 2.f, const unsigned int &apertureGradient = 3, + const float &lowerThresholdRatio = 0.6, const float &upperThresholdRatio = 0.8, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpImage *p_mask = nullptr + ) { - unsigned int w = static_cast(I.getWidth()); - unsigned int h = static_cast(I.getHeight()); + const unsigned int w = I.getWidth(); + const unsigned int h = I.getHeight(); vpImage dI(h, w); vpImage dIx(h, w), dIy(h, w); - if (p_dIx != nullptr && p_dIy != nullptr) { + if ((p_dIx != nullptr) && (p_dIy != nullptr)) { dIx = *p_dIx; dIy = *p_dIy; } else { computePartialDerivatives(I, dIx, dIy, true, true, true, gaussianKernelSize, gaussianStdev, - apertureGradient, filteringType); + apertureGradient, filteringType, vpImageFilter::CANNY_VISP_BACKEND, p_mask); } // Computing the absolute gradient of the image G = |dIx| + |dIy| - for (unsigned int r = 0; r < h; r++) { - for (unsigned int c = 0; c < w; c++) { - float dx = static_cast(dIx[r][c]); - float dy = static_cast(dIy[r][c]); - float gradient = std::abs(dx) + std::abs(dy); - float gradientClamped = std::min(gradient, static_cast(std::numeric_limits::max())); - dI[r][c] = static_cast(gradientClamped); + for (unsigned int r = 0; r < h; ++r) { + for (unsigned int c = 0; c < w; ++c) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[r][c]); + if (computeVal) { + float dx = static_cast(dIx[r][c]); + float dy = static_cast(dIy[r][c]); + float gradient = std::abs(dx) + std::abs(dy); + float gradientClamped = std::min(gradient, static_cast(std::numeric_limits::max())); + dI[r][c] = static_cast(gradientClamped); + } } } // Compute the histogram vpHistogram hist; + hist.setMask(p_mask); const unsigned int nbBins = 256; hist.calculate(dI, nbBins); + float totalNbPixels = static_cast(hist.getTotal()); float accu = 0; - float t = (float)(upperThresholdRatio * w * h); + float t = upperThresholdRatio * totalNbPixels; float bon = 0; for (unsigned int i = 0; i < nbBins; ++i) { - float tf = static_cast(hist[i]); + float tf = hist[i]; accu = accu + tf; if (accu > t) { bon = (float)i; break; } } - float upperThresh = std::max(bon, 1.f); + float upperThresh = std::max(bon, 1.f); lowerThresh = lowerThresholdRatio * bon; return upperThresh; } @@ -381,12 +420,13 @@ class VISP_EXPORT vpImageFilter template static FilterType derivativeFilterX(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { + const unsigned int stop = (size - 1) / 2; unsigned int i; FilterType result; result = 0; - for (i = 1; i <= ((size - 1) / 2); ++i) { + for (i = 1; i <= stop; ++i) { result += filter[i] * static_cast(I[r][c + i] - I[r][c - i]); } return result; @@ -408,80 +448,98 @@ class VISP_EXPORT vpImageFilter template static FilterType derivativeFilterY(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { + const unsigned int stop = (size - 1) / 2; unsigned int i; FilterType result; result = 0; - for (i = 1; i <= ((size - 1) / 2); ++i) { + for (i = 1; i <= stop; ++i) { result += filter[i] * static_cast(I[r + i][c] - I[r - i][c]); } return result; } /*! - * Apply a filter to an image. - * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. - * \param I : Image to filter - * \param If : Filtered image. - * \param M : Filter kernel. - * \param convolve : If true, perform a convolution otherwise a correlation. - * - * \note By default it performs a correlation: - * \f[ - * \textbf{I\_filtered} \left( u,v \right) = - * \sum_{y=0}^{\textbf{kernel\_h}} - * \sum_{x=0}^{\textbf{kernel\_w}} - * \textbf{M} \left( x,y \right ) \times - * \textbf{I} \left( - * u-\frac{\textbf{kernel\_w}}{2}+x,v-\frac{\textbf{kernel\_h}}{2}+y \right) - * \f] - * The convolution is almost the same operation: - * \f[ - * \textbf{I\_filtered} \left( u,v \right) = - * \sum_{y=0}^{\textbf{kernel\_h}} - * \sum_{x=0}^{\textbf{kernel\_w}} - * \textbf{M} \left( x,y \right ) \times - * \textbf{I} \left( - * u+\frac{\textbf{kernel\_w}}{2}-x,v+\frac{\textbf{kernel\_h}}{2}-y \right) - * \f] - * Only pixels in the input image fully covered by the kernel are considered. - */ + Apply a filter to an image. + \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + \param I : Image to filter + \param If : Filtered image. + \param M : Filter kernel. + \param convolve : If true, perform a convolution otherwise a correlation. + \param p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). + + \note By default it performs a correlation: + \f[ + \textbf{I\_filtered} \left( u,v \right) = + \sum_{y=0}^{\textbf{kernel\_h}} + \sum_{x=0}^{\textbf{kernel\_w}} + \textbf{M} \left( x,y \right ) \times + \textbf{I} \left( + u-\frac{\textbf{kernel\_w}}{2}+x,v-\frac{\textbf{kernel\_h}}{2}+y \right) + \f] + The convolution is almost the same operation: + \f[ + \textbf{I\_filtered} \left( u,v \right) = + \sum_{y=0}^{\textbf{kernel\_h}} + \sum_{x=0}^{\textbf{kernel\_w}} + \textbf{M} \left( x,y \right ) \times + \textbf{I} \left( + u+\frac{\textbf{kernel\_w}}{2}-x,v+\frac{\textbf{kernel\_h}}{2}-y \right) + \f] + Only pixels in the input image fully covered by the kernel are considered. + */ template - static void filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve = false) + static void filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve = false, + const vpImage *p_mask = nullptr) { - unsigned int size_y = M.getRows(), size_x = M.getCols(); - unsigned int half_size_y = size_y / 2, half_size_x = size_x / 2; + const unsigned int size_y = M.getRows(), size_x = M.getCols(); + const unsigned int half_size_y = size_y / 2, half_size_x = size_x / 2; - If.resize(I.getHeight(), I.getWidth(), 0.0); + const unsigned int inputHeight = I.getHeight(), inputWidth = I.getWidth(); + If.resize(inputHeight, inputWidth, 0.0); if (convolve) { - for (unsigned int i = half_size_y; i < (I.getHeight() - half_size_y); ++i) { - for (unsigned int j = half_size_x; j < (I.getWidth() - half_size_x); ++j) { - FilterType conv = 0; - - for (unsigned int a = 0; a < size_y; ++a) { - for (unsigned int b = 0; b < size_x; ++b) { - FilterType val = static_cast(I[i + half_size_y - a][j + half_size_x - b]); // Convolution - conv += M[a][b] * val; + const unsigned int stopHeight = inputHeight - half_size_y; + const unsigned int stopWidth = inputWidth - half_size_x; + for (unsigned int i = half_size_y; i < stopHeight; ++i) { + for (unsigned int j = half_size_x; j < stopWidth; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + FilterType conv = 0; + + for (unsigned int a = 0; a < size_y; ++a) { + for (unsigned int b = 0; b < size_x; ++b) { + FilterType val = static_cast(I[i + half_size_y - a][j + half_size_x - b]); // Convolution + conv += M[a][b] * val; + } } + If[i][j] = conv; } - If[i][j] = conv; } } } else { - for (unsigned int i = half_size_y; i < (I.getHeight() - half_size_y); ++i) { - for (unsigned int j = half_size_x; j < (I.getWidth() - half_size_x); ++j) { - FilterType corr = 0; - - for (unsigned int a = 0; a < size_y; ++a) { - for (unsigned int b = 0; b < size_x; ++b) { - FilterType val = static_cast(I[i - half_size_y + a][j - half_size_x + b]); // Correlation - corr += M[a][b] * val; + const unsigned int stopHeight = inputHeight - half_size_y; + const unsigned int stopWidth = inputWidth - half_size_x; + for (unsigned int i = half_size_y; i < stopHeight; ++i) { + for (unsigned int j = half_size_x; j < stopWidth; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + FilterType corr = 0; + + for (unsigned int a = 0; a < size_y; ++a) { + for (unsigned int b = 0; b < size_x; ++b) { + FilterType val = static_cast(I[i - half_size_y + a][j - half_size_x + b]); // Correlation + corr += M[a][b] * val; + } } + If[i][j] = corr; } - If[i][j] = corr; } } } @@ -501,50 +559,65 @@ class VISP_EXPORT vpImageFilter * \param Iv : Filtered image along the vertical axis (v = rows). * \param M : Filter kernel. * \param convolve : If true, perform a convolution otherwise a correlation. + * \param p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). */ template static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, - bool convolve = false) + bool convolve = false, const vpImage *p_mask = nullptr) { - unsigned int size = M.getRows(); - unsigned int half_size = size / 2; + const unsigned int size = M.getRows(); + const unsigned int half_size = size / 2; + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stopV = height - half_size; + const unsigned int stopU = width - half_size; - Iu.resize(I.getHeight(), I.getWidth(), 0.0); - Iv.resize(I.getHeight(), I.getWidth(), 0.0); + Iu.resize(height, width, 0.0); + Iv.resize(height, width, 0.0); if (convolve) { - for (unsigned int v = half_size; v < (I.getHeight() - half_size); v++) { - for (unsigned int u = half_size; u < (I.getWidth() - half_size); u++) { - FilterType conv_u = 0; - FilterType conv_v = 0; - - for (unsigned int a = 0; a < size; a++) { - for (unsigned int b = 0; b < size; b++) { - FilterType val = static_cast(I[v + half_size - a][u + half_size - b]); // Convolution - conv_u += M[a][b] * val; - conv_v += M[b][a] * val; + for (unsigned int v = half_size; v < stopV; ++v) { + for (unsigned int u = half_size; u < stopU; ++u) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[v][u]); + if (computeVal) { + FilterType conv_u = 0; + FilterType conv_v = 0; + + for (unsigned int a = 0; a < size; ++a) { + for (unsigned int b = 0; b < size; ++b) { + FilterType val = static_cast(I[v + half_size - a][u + half_size - b]); // Convolution + conv_u += M[a][b] * val; + conv_v += M[b][a] * val; + } } + Iu[v][u] = conv_u; + Iv[v][u] = conv_v; } - Iu[v][u] = conv_u; - Iv[v][u] = conv_v; } } } else { - for (unsigned int v = half_size; v < (I.getHeight() - half_size); v++) { - for (unsigned int u = half_size; u < (I.getWidth() - half_size); u++) { - FilterType conv_u = 0; - FilterType conv_v = 0; - - for (unsigned int a = 0; a < size; a++) { - for (unsigned int b = 0; b < size; b++) { - FilterType val = static_cast(I[v - half_size + a][u - half_size + b]); // Correlation - conv_u += M[a][b] * val; - conv_v += M[b][a] * val; + for (unsigned int v = half_size; v < stopV; ++v) { + for (unsigned int u = half_size; u < stopU; ++u) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[v][u]); + + if (computeVal) { + FilterType conv_u = 0; + FilterType conv_v = 0; + + for (unsigned int a = 0; a < size; ++a) { + for (unsigned int b = 0; b < size; ++b) { + FilterType val = static_cast(I[v - half_size + a][u - half_size + b]); // Correlation + conv_u += M[a][b] * val; + conv_v += M[b][a] * val; + } } + Iu[v][u] = conv_u; + Iv[v][u] = conv_v; } - Iu[v][u] = conv_u; - Iv[v][u] = conv_v; } } } @@ -565,13 +638,14 @@ class VISP_EXPORT vpImageFilter * \param GI: The filtered image. * \param filter: The separable filter. * \param size: The size of the filter. + * \param p_mask: If different from nullptr, mask indicating which points to consider (true) or to ignore(false). */ template - static void filter(const vpImage &I, vpImage &GI, const FilterType *filter, unsigned int size) + static void filter(const vpImage &I, vpImage &GI, const FilterType *filter, unsigned int size, const vpImage *p_mask = nullptr) { vpImage GIx; - filterX(I, GIx, filter, size); - filterY(GIx, GI, filter, size); + filterX(I, GIx, filter, size, p_mask); + filterY(GIx, GI, filter, size, p_mask); GIx.destroy(); } @@ -585,49 +659,65 @@ class VISP_EXPORT vpImageFilter } template - static void filterX(const vpImage &I, vpImage &dIx, const FilterType *filter, unsigned int size) + static void filterX(const vpImage &I, vpImage &dIx, const FilterType *filter, unsigned int size, + const vpImage *p_mask = nullptr) { - dIx.resize(I.getHeight(), I.getWidth()); - for (unsigned int i = 0; i < I.getHeight(); ++i) { - for (unsigned int j = 0; j < ((size - 1) / 2); ++j) { - dIx[i][j] = vpImageFilter::filterXLeftBorder(I, i, j, filter, size); + const unsigned int height = I.getHeight(); + const unsigned int width = I.getWidth(); + const unsigned int stop1J = (size - 1) / 2; + const unsigned int stop2J = width - (size - 1) / 2; + resizeAndInitializeIfNeeded(p_mask, height, width, dIx); + + for (unsigned int i = 0; i < height; ++i) { + for (unsigned int j = 0; j < stop1J; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIx[i][j] = vpImageFilter::filterXLeftBorder(I, i, j, filter, size); + } } - for (unsigned int j = (size - 1) / 2; j < (I.getWidth() - (size - 1) / 2); ++j) { - dIx[i][j] = vpImageFilter::filterX(I, i, j, filter, size); + for (unsigned int j = stop1J; j < stop2J; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIx[i][j] = vpImageFilter::filterX(I, i, j, filter, size); + } } - for (unsigned int j = I.getWidth() - (size - 1) / 2; j < I.getWidth(); ++j) { - dIx[i][j] = vpImageFilter::filterXRightBorder(I, i, j, filter, size); + for (unsigned int j = stop2J; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIx[i][j] = vpImageFilter::filterXRightBorder(I, i, j, filter, size); + } } } } - static void filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -#ifndef DOXYGEN_SHOULD_SKIP_THIS + static void filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size, const vpImage *p_mask = nullptr); static void filterXR(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); static void filterXG(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); static void filterXB(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -#endif template static inline FilterType filterX(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { - FilterType result; - - result = 0; + const unsigned int stop = (size - 1) / 2; + FilterType result = static_cast(0.); - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { result += filter[i] * static_cast(I[r][c + i] + I[r][c - i]); } return result + filter[0] * static_cast(I[r][c]); } -#ifndef DOXYGEN_SHOULD_SKIP_THIS + static inline double filterXR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; - - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + const unsigned int stop = (size - 1) / 2; + double result = 0.; + for (unsigned int i = 1; i <= stop; ++i) { result += filter[i] * static_cast(I[r][c + i].R + I[r][c - i].R); } return result + filter[0] * static_cast(I[r][c].R); @@ -635,11 +725,10 @@ class VISP_EXPORT vpImageFilter static inline double filterXG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; + const unsigned int stop = (size - 1) / 2; + double result = 0.; - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { result += filter[i] * static_cast(I[r][c + i].G + I[r][c - i].G); } return result + filter[0] * static_cast(I[r][c].G); @@ -647,11 +736,10 @@ class VISP_EXPORT vpImageFilter static inline double filterXB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; - - result = 0; + const unsigned int stop = (size - 1) / 2; + double result = 0.; - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { result += filter[i] * static_cast(I[r][c + i].B + I[r][c - i].B); } return result + filter[0] * static_cast(I[r][c].B); @@ -661,11 +749,10 @@ class VISP_EXPORT vpImageFilter static inline FilterType filterXLeftBorder(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { - FilterType result; - - result = 0; + const unsigned int stop = (size - 1) / 2; + FilterType result = static_cast(0.); - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { if (c > i) { result += filter[i] * static_cast(I[r][c + i] + I[r][c - i]); } @@ -679,11 +766,10 @@ class VISP_EXPORT vpImageFilter static inline double filterXLeftBorderR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; + const unsigned int stop = (size - 1) / 2; + double result = 0.; - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { if (c > i) { result += filter[i] * static_cast(I[r][c + i].R + I[r][c - i].R); } @@ -697,11 +783,10 @@ class VISP_EXPORT vpImageFilter static inline double filterXLeftBorderG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; - - result = 0; + const unsigned int stop = (size - 1) / 2; + double result = 0.; - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { if (c > i) { result += filter[i] * static_cast(I[r][c + i].G + I[r][c - i].G); } @@ -715,11 +800,10 @@ class VISP_EXPORT vpImageFilter static inline double filterXLeftBorderB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; + const unsigned int stop = (size - 1) / 2; + double result = 0.; - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { if (c > i) { result += filter[i] * static_cast(I[r][c + i].B + I[r][c - i].B); } @@ -734,16 +818,16 @@ class VISP_EXPORT vpImageFilter static inline FilterType filterXRightBorder(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { - FilterType result; + const unsigned int stop = (size - 1) / 2; + const unsigned int width = I.getWidth(); + FilterType result = static_cast(0.); - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { - if ((c + i) < I.getWidth()) { + for (unsigned int i = 1; i <= stop; ++i) { + if ((c + i) < width) { result += filter[i] * static_cast(I[r][c + i] + I[r][c - i]); } else { - result += filter[i] * static_cast(I[r][2 * I.getWidth() - c - i - 1] + I[r][c - i]); + result += filter[i] * static_cast(I[r][2 * width - c - i - 1] + I[r][c - i]); } } return result + filter[0] * static_cast(I[r][c]); @@ -752,16 +836,16 @@ class VISP_EXPORT vpImageFilter static inline double filterXRightBorderR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; - - result = 0; + const unsigned int stop = (size - 1) / 2; + const unsigned int width = I.getWidth(); + double result = 0.; - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { - if ((c + i) < I.getWidth()) { + for (unsigned int i = 1; i <= stop; ++i) { + if ((c + i) < width) { result += filter[i] * static_cast(I[r][c + i].R + I[r][c - i].R); } else { - result += filter[i] * static_cast(I[r][2 * I.getWidth() - c - i - 1].R + I[r][c - i].R); + result += filter[i] * static_cast(I[r][2 * width - c - i - 1].R + I[r][c - i].R); } } return result + filter[0] * static_cast(I[r][c].R); @@ -770,16 +854,16 @@ class VISP_EXPORT vpImageFilter static inline double filterXRightBorderG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; + const unsigned int stop = (size - 1) / 2; + const unsigned int width = I.getWidth(); + double result = 0.; - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { - if ((c + i) < I.getWidth()) { + for (unsigned int i = 1; i <= stop; ++i) { + if ((c + i) < width) { result += filter[i] * static_cast(I[r][c + i].G + I[r][c - i].G); } else { - result += filter[i] * static_cast(I[r][2 * I.getWidth() - c - i - 1].G + I[r][c - i].G); + result += filter[i] * static_cast(I[r][2 * width - c - i - 1].G + I[r][c - i].G); } } return result + filter[0] * static_cast(I[r][c].G); @@ -788,46 +872,63 @@ class VISP_EXPORT vpImageFilter static inline double filterXRightBorderB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; - - result = 0; + const unsigned int stop = (size - 1) / 2; + const unsigned int width = I.getWidth(); + double result = 0.; - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { - if ((c + i) < I.getWidth()) { + for (unsigned int i = 1; i <= stop; ++i) { + if ((c + i) < width) { result += filter[i] * static_cast(I[r][c + i].B + I[r][c - i].B); } else { - result += filter[i] * static_cast(I[r][2 * I.getWidth() - c - i - 1].B + I[r][c - i].B); + result += filter[i] * static_cast(I[r][2 * width - c - i - 1].B + I[r][c - i].B); } } return result + filter[0] * static_cast(I[r][c].B); } -#endif - - static void filterY(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -#ifndef DOXYGEN_SHOULD_SKIP_THIS + static void filterY(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size, const vpImage *p_mask = nullptr); static void filterYR(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); static void filterYG(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); static void filterYB(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -#endif + template - static void filterY(const vpImage &I, vpImage &dIy, const FilterType *filter, unsigned int size) + static void filterY(const vpImage &I, vpImage &dIy, const FilterType *filter, unsigned int size, + const vpImage *p_mask = nullptr) { - dIy.resize(I.getHeight(), I.getWidth()); - for (unsigned int i = 0; i < ((size - 1) / 2); ++i) { - for (unsigned int j = 0; j < I.getWidth(); ++j) { - dIy[i][j] = vpImageFilter::filterYTopBorder(I, i, j, filter, size); + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stop1I = (size - 1) / 2; + const unsigned int stop2I = height - (size - 1) / 2; + resizeAndInitializeIfNeeded(p_mask, height, width, dIy); + + for (unsigned int i = 0; i < stop1I; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j] = vpImageFilter::filterYTopBorder(I, i, j, filter, size); + } } } - for (unsigned int i = (size - 1) / 2; i < (I.getHeight() - (size - 1) / 2); ++i) { - for (unsigned int j = 0; j < I.getWidth(); ++j) { - dIy[i][j] = vpImageFilter::filterY(I, i, j, filter, size); + for (unsigned int i = stop1I; i < stop2I; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j] = vpImageFilter::filterY(I, i, j, filter, size); + } } } - for (unsigned int i = I.getHeight() - (size - 1) / 2; i < I.getHeight(); ++i) { - for (unsigned int j = 0; j < I.getWidth(); ++j) { - dIy[i][j] = vpImageFilter::filterYBottomBorder(I, i, j, filter, size); + for (unsigned int i = stop2I; i < height; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j] = vpImageFilter::filterYBottomBorder(I, i, j, filter, size); + } } } } @@ -835,34 +936,31 @@ class VISP_EXPORT vpImageFilter template static inline FilterType filterY(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { - FilterType result; + const unsigned int stop = (size - 1) / 2; + FilterType result = static_cast(0.); - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { result += filter[i] * static_cast(I[r + i][c] + I[r - i][c]); } return result + filter[0] * static_cast(I[r][c]); } -#ifndef DOXYGEN_SHOULD_SKIP_THIS + static inline double filterYR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; + const unsigned int stop = (size - 1) / 2; + double result = 0.; - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { result += filter[i] * static_cast(I[r + i][c].R + I[r - i][c].R); } return result + filter[0] * static_cast(I[r][c].R); } static inline double filterYG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; - - result = 0; + const unsigned int stop = (size - 1) / 2; + double result = 0.; - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { result += filter[i] * static_cast(I[r + i][c].G + I[r - i][c].G); } return result + filter[0] * static_cast(I[r][c].G); @@ -870,11 +968,10 @@ class VISP_EXPORT vpImageFilter static inline double filterYB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; - - result = 0; + const unsigned int stop = (size - 1) / 2; + double result = 0.; - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { result += filter[i] * static_cast(I[r + i][c].B + I[r - i][c].B); } return result + filter[0] * static_cast(I[r][c].B); @@ -884,11 +981,10 @@ class VISP_EXPORT vpImageFilter static inline FilterType filterYTopBorder(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { - FilterType result; - - result = 0; + const unsigned int stop = (size - 1) / 2; + FilterType result = static_cast(0.); - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { if (r > i) { result += filter[i] * static_cast(I[r + i][c] + I[r - i][c]); } @@ -901,11 +997,10 @@ class VISP_EXPORT vpImageFilter double static inline filterYTopBorderR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; + const unsigned int stop = (size - 1) / 2; + double result = 0.; - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { if (r > i) { result += filter[i] * static_cast(I[r + i][c].R + I[r - i][c].R); } @@ -918,11 +1013,10 @@ class VISP_EXPORT vpImageFilter double static inline filterYTopBorderG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; - - result = 0; + const unsigned int stop = (size - 1) / 2; + double result = 0.; - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { if (r > i) { result += filter[i] * static_cast(I[r + i][c].G + I[r - i][c].G); } @@ -935,11 +1029,10 @@ class VISP_EXPORT vpImageFilter double static inline filterYTopBorderB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; + const unsigned int stop = (size - 1) / 2; + double result = 0.; - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { + for (unsigned int i = 1; i <= stop; ++i) { if (r > i) { result += filter[i] * static_cast(I[r + i][c].B + I[r - i][c].B); } @@ -954,16 +1047,16 @@ class VISP_EXPORT vpImageFilter static inline FilterType filterYBottomBorder(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { - FilterType result; + const unsigned int height = I.getHeight(); + const unsigned int stop = (size - 1) / 2; + FilterType result = static_cast(0.); - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { - if ((r + i) < I.getHeight()) { + for (unsigned int i = 1; i <= stop; ++i) { + if ((r + i) < height) { result += filter[i] * static_cast(I[r + i][c] + I[r - i][c]); } else { - result += filter[i] * static_cast(I[2 * I.getHeight() - r - i - 1][c] + I[r - i][c]); + result += filter[i] * static_cast(I[2 * height - r - i - 1][c] + I[r - i][c]); } } return result + filter[0] * static_cast(I[r][c]); @@ -972,16 +1065,16 @@ class VISP_EXPORT vpImageFilter double static inline filterYBottomBorderR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; - - result = 0; + const unsigned int height = I.getHeight(); + const unsigned int stop = (size - 1) / 2; + double result = 0.; - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { - if ((r + i) < I.getHeight()) { + for (unsigned int i = 1; i <= stop; ++i) { + if ((r + i) < height) { result += filter[i] * static_cast(I[r + i][c].R + I[r - i][c].R); } else { - result += filter[i] * static_cast(I[2 * I.getHeight() - r - i - 1][c].R + I[r - i][c].R); + result += filter[i] * static_cast(I[2 * height - r - i - 1][c].R + I[r - i][c].R); } } return result + filter[0] * static_cast(I[r][c].R); @@ -990,16 +1083,16 @@ class VISP_EXPORT vpImageFilter double static inline filterYBottomBorderG(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; + const unsigned int height = I.getHeight(); + const unsigned int stop = (size - 1) / 2; + double result = 0.; - result = 0; - - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { - if ((r + i) < I.getHeight()) { + for (unsigned int i = 1; i <= stop; ++i) { + if ((r + i) < height) { result += filter[i] * static_cast(I[r + i][c].G + I[r - i][c].G); } else { - result += filter[i] * static_cast(I[2 * I.getHeight() - r - i - 1][c].G + I[r - i][c].G); + result += filter[i] * static_cast(I[2 * height - r - i - 1][c].G + I[r - i][c].G); } } return result + filter[0] * static_cast(I[r][c].G); @@ -1008,48 +1101,49 @@ class VISP_EXPORT vpImageFilter double static inline filterYBottomBorderB(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { - double result; - - result = 0; + const unsigned int height = I.getHeight(); + const unsigned int stop = (size - 1) / 2; + double result = 0.; - for (unsigned int i = 1; i <= ((size - 1) / 2); ++i) { - if ((r + i) < I.getHeight()) { + for (unsigned int i = 1; i <= stop; ++i) { + if ((r + i) < height) { result += filter[i] * static_cast(I[r + i][c].B + I[r - i][c].B); } else { - result += filter[i] * static_cast(I[2 * I.getHeight() - r - i - 1][c].B + I[r - i][c].B); + result += filter[i] * static_cast(I[2 * height - r - i - 1][c].B + I[r - i][c].B); } } return result + filter[0] * static_cast(I[r][c].B); } -#endif - /*! - * Apply a Gaussian blur to an image. - * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. - * \param I : Input image. - * \param GI : Filtered image. - * \param size : Filter size. This value should be odd. - * \param sigma : Gaussian standard deviation. If it is equal to zero or - * negative, it is computed from filter size as sigma = (size-1)/6. - * \param normalize : Flag indicating whether to normalize the filter coefficients or not. - * - * \sa getGaussianKernel() to know which kernel is used. - */ + * Apply a Gaussian blur to an image. + * \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param I : Input image. + * \param GI : Filtered image. + * \param size : Filter size. This value should be odd. + * \param sigma : Gaussian standard deviation. If it is equal to zero or + * negative, it is computed from filter size as sigma = (size-1)/6. + * \param normalize : Flag indicating whether to normalize the filter coefficients or not. + * \param p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). + * + * \sa getGaussianKernel() to know which kernel is used. + */ template - static void gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size = 7, FilterType sigma = 0., bool normalize = true) + static void gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size = 7, FilterType sigma = 0., bool normalize = true, + const vpImage *p_mask = nullptr) { FilterType *fg = new FilterType[(size + 1) / 2]; vpImageFilter::getGaussianKernel(fg, size, sigma, normalize); vpImage GIx; - vpImageFilter::filterX(I, GIx, fg, size); - vpImageFilter::filterY(GIx, GI, fg, size); + vpImageFilter::filterX(I, GIx, fg, size, p_mask); + vpImageFilter::filterY(GIx, GI, fg, size, p_mask); GIx.destroy(); delete[] fg; } - static void gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size = 7, double sigma = 0., bool normalize = true); + static void gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size = 7, double sigma = 0., bool normalize = true, + const vpImage *p_mask = nullptr); /*! * Apply a 5x5 Gaussian filter to an image pixel. @@ -1171,36 +1265,68 @@ class VISP_EXPORT vpImageFilter // Gradient along X template - static void getGradX(const vpImage &I, vpImage &dIx) + static void getGradX(const vpImage &I, vpImage &dIx, const vpImage *p_mask = nullptr) { - dIx.resize(I.getHeight(), I.getWidth()); - // dIx=0; - for (unsigned int i = 0; i < I.getHeight(); ++i) { + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stopJ = width - 3; + resizeAndInitializeIfNeeded(p_mask, height, width, dIx); + + for (unsigned int i = 0; i < height; ++i) { for (unsigned int j = 0; j < 3; ++j) { - dIx[i][j] = static_cast(0); + // If a mask is used, the image is already initialized with 0s + bool computeVal = (p_mask == nullptr); + if (computeVal) { + dIx[i][j] = static_cast(0); + } } - for (unsigned int j = 3; j < (I.getWidth() - 3); ++j) { - dIx[i][j] = static_cast(vpImageFilter::derivativeFilterX(I, i, j)); + for (unsigned int j = 3; j < stopJ; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIx[i][j] = static_cast(vpImageFilter::derivativeFilterX(I, i, j)); + } } - for (unsigned int j = I.getWidth() - 3; j < I.getWidth(); ++j) { - dIx[i][j] = static_cast(0); + for (unsigned int j = stopJ; j < width; ++j) { + // If a mask is used, the image is already initialized with 0s + bool computeVal = (p_mask == nullptr); + if (computeVal) { + dIx[i][j] = static_cast(0); + } } } } template - static void getGradX(const vpImage &I, vpImage &dIx, const FilterType *filter, unsigned int size) + static void getGradX(const vpImage &I, vpImage &dIx, const FilterType *filter, unsigned int size, const vpImage *p_mask = nullptr) { - dIx.resize(I.getHeight(), I.getWidth()); - for (unsigned int i = 0; i < I.getHeight(); ++i) { - for (unsigned int j = 0; j < ((size - 1) / 2); ++j) { - dIx[i][j] = static_cast(0); + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stop1J = (size - 1) / 2; + const unsigned int stop2J = width - (size - 1) / 2; + resizeAndInitializeIfNeeded(p_mask, height, width, dIx); + + for (unsigned int i = 0; i < height; ++i) { + for (unsigned int j = 0; j < stop1J; ++j) { + // If a mask is used, the image is already initialized with 0s + bool computeVal = (p_mask == nullptr); + if (computeVal) { + dIx[i][j] = static_cast(0); + } } - for (unsigned int j = (size - 1) / 2; j < (I.getWidth() - (size - 1) / 2); ++j) { - dIx[i][j] = vpImageFilter::derivativeFilterX(I, i, j, filter, size); + for (unsigned int j = stop1J; j < stop2J; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIx[i][j] = vpImageFilter::derivativeFilterX(I, i, j, filter, size); + } } - for (unsigned int j = I.getWidth() - (size - 1) / 2; j < I.getWidth(); ++j) { - dIx[i][j] = static_cast(0); + for (unsigned int j = stop2J; j < width; ++j) { + // If a mask is used, the image is already initialized with 0s + bool computeVal = (p_mask == nullptr); + if (computeVal) { + dIx[i][j] = static_cast(0); + } } } } @@ -1214,55 +1340,93 @@ class VISP_EXPORT vpImageFilter * \param gaussianDerivativeKernel : Gaussian derivative kernel which values should be computed using * vpImageFilter::getGaussianDerivativeKernel(). * \param size : Size of the Gaussian and Gaussian derivative kernels. + * \param p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). */ template static void getGradXGauss2D(const vpImage &I, vpImage &dIx, const FilterType *gaussianKernel, - const FilterType *gaussianDerivativeKernel, unsigned int size) + const FilterType *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask = nullptr) { vpImage GIy; - vpImageFilter::filterY(I, GIy, gaussianKernel, size); - vpImageFilter::getGradX(GIy, dIx, gaussianDerivativeKernel, size); + vpImageFilter::filterY(I, GIy, gaussianKernel, size, p_mask); + vpImageFilter::getGradX(GIy, dIx, gaussianDerivativeKernel, size, p_mask); } // Gradient along Y template - static void getGradY(const vpImage &I, vpImage &dIy) + static void getGradY(const vpImage &I, vpImage &dIy, const vpImage *p_mask = nullptr) { - dIy.resize(I.getHeight(), I.getWidth()); + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stopI = height - 3; + resizeAndInitializeIfNeeded(p_mask, height, width, dIy); + for (unsigned int i = 0; i < 3; ++i) { - for (unsigned int j = 0; j < I.getWidth(); ++j) { - dIy[i][j] = static_cast(0); + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j] = static_cast(0); + } } } - for (unsigned int i = 3; i < (I.getHeight() - 3); ++i) { - for (unsigned int j = 0; j < I.getWidth(); ++j) { - dIy[i][j] = static_cast(vpImageFilter::derivativeFilterY(I, i, j)); + for (unsigned int i = 3; i < stopI; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j] = static_cast(vpImageFilter::derivativeFilterY(I, i, j)); + } } } - for (unsigned int i = I.getHeight() - 3; i < I.getHeight(); ++i) { - for (unsigned int j = 0; j < I.getWidth(); ++j) { - dIy[i][j] = static_cast(0); + for (unsigned int i = stopI; i < height; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j] = static_cast(0); + } } } } template - static void getGradY(const vpImage &I, vpImage &dIy, const FilterType *filter, unsigned int size) + static void getGradY(const vpImage &I, vpImage &dIy, const FilterType *filter, unsigned int size, const vpImage *p_mask = nullptr) { - dIy.resize(I.getHeight(), I.getWidth()); - for (unsigned int i = 0; i < ((size - 1) / 2); ++i) { - for (unsigned int j = 0; j < I.getWidth(); ++j) { - dIy[i][j] = static_cast(0); + const unsigned int height = I.getHeight(), width = I.getWidth(); + const unsigned int stop1I = (size - 1) / 2; + const unsigned int stop2I = height - (size - 1) / 2; + resizeAndInitializeIfNeeded(p_mask, height, width, dIy); + + for (unsigned int i = 0; i < stop1I; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j] = static_cast(0); + } } } - for (unsigned int i = (size - 1) / 2; i < (I.getHeight() - (size - 1) / 2); ++i) { - for (unsigned int j = 0; j < I.getWidth(); ++j) { - dIy[i][j] = vpImageFilter::derivativeFilterY(I, i, j, filter, size); + for (unsigned int i = stop1I; i < stop2I; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j] = vpImageFilter::derivativeFilterY(I, i, j, filter, size); + } } } - for (unsigned int i = I.getHeight() - (size - 1) / 2; i < I.getHeight(); ++i) { - for (unsigned int j = 0; j < I.getWidth(); ++j) { - dIy[i][j] = static_cast(0); + for (unsigned int i = stop2I; i < height; ++i) { + for (unsigned int j = 0; j < width; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j] = static_cast(0); + } } } } @@ -1276,14 +1440,15 @@ class VISP_EXPORT vpImageFilter * \param gaussianDerivativeKernel : Gaussian derivative kernel which values should be computed using * vpImageFilter::getGaussianDerivativeKernel(). * \param size : Size of the Gaussian and Gaussian derivative kernels. + * \param p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). */ template static void getGradYGauss2D(const vpImage &I, vpImage &dIy, const FilterType *gaussianKernel, - const FilterType *gaussianDerivativeKernel, unsigned int size) + const FilterType *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask = nullptr) { vpImage GIx; - vpImageFilter::filterX(I, GIx, gaussianKernel, size); - vpImageFilter::getGradY(GIx, dIy, gaussianDerivativeKernel, size); + vpImageFilter::filterX(I, GIx, gaussianKernel, size, p_mask); + vpImageFilter::getGradY(GIx, dIy, gaussianDerivativeKernel, size, p_mask); } /*! @@ -1400,12 +1565,12 @@ class VISP_EXPORT vpImageFilter memcpy(filter, SobelY3x3, kernel_size * kernel_size * sizeof(FilterType)); return scale; } - scale *= static_cast(1./ 16.); // Sobel5x5 is the convolution of smoothingKernel, which needs 1/16 scale factor, with Sobel3x3 + scale *= static_cast(1. / 16.); // Sobel5x5 is the convolution of smoothingKernel, which needs 1/16 scale factor, with Sobel3x3 if (kernel_size == 5) { memcpy(filter, SobelY5x5, kernel_size * kernel_size * sizeof(FilterType)); return scale; } - scale *= static_cast(1./ 16.); // Sobel7x7 is the convolution of smoothingKernel, which needs 1/16 scale factor, with Sobel5x5 + scale *= static_cast(1. / 16.); // Sobel7x7 is the convolution of smoothingKernel, which needs 1/16 scale factor, with Sobel5x5 if (kernel_size == 7) { memcpy(filter, SobelY7x7, kernel_size * kernel_size * sizeof(FilterType)); return scale; @@ -1416,7 +1581,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int i = 4; i <= size; ++i) { sobelY = vpArray2D::conv2(sobelY, smoothingKernel, "full"); // Sobel(N+1)x(N+1) is the convolution of smoothingKernel, which needs 1/16 scale factor, with SobelNxN - scale *= static_cast(1./ 16.); + scale *= static_cast(1. / 16.); } memcpy(filter, sobelY.data, sobelY.getRows() * sobelY.getCols() * sizeof(FilterType)); diff --git a/modules/core/src/image/vpCannyEdgeDetection.cpp b/modules/core/src/image/vpCannyEdgeDetection.cpp index 9156591a15..3ffbea60a7 100644 --- a/modules/core/src/image/vpCannyEdgeDetection.cpp +++ b/modules/core/src/image/vpCannyEdgeDetection.cpp @@ -46,6 +46,7 @@ vpCannyEdgeDetection::vpCannyEdgeDetection() , m_lowerThresholdRatio(0.6f) , m_upperThreshold(-1.f) , m_upperThresholdRatio(0.8f) + , mp_mask(nullptr) { initGaussianFilters(); initGradientFilters(); @@ -65,6 +66,7 @@ vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const , m_lowerThresholdRatio(lowerThresholdRatio) , m_upperThreshold(upperThreshold) , m_upperThresholdRatio(upperThresholdRatio) + , mp_mask(nullptr) { initGaussianFilters(); initGradientFilters(); @@ -191,7 +193,7 @@ vpCannyEdgeDetection::detect(const vpImage &I) if (upperThreshold < 0) { upperThreshold = vpImageFilter::computeCannyThreshold(I, lowerThreshold, &m_dIx, &m_dIy, m_gaussianKernelSize, m_gaussianStdev, m_gradientFilterKernelSize, m_lowerThresholdRatio, - m_upperThresholdRatio, m_filteringAndGradientType); + m_upperThresholdRatio, m_filteringAndGradientType, mp_mask); } else if (m_lowerThreshold < 0) { // Applying Canny recommendation to have the upper threshold 3 times greater than the lower threshold. @@ -217,12 +219,12 @@ vpCannyEdgeDetection::performFilteringAndGradientComputation(const vpImage Iblur; vpImage GIx; - vpImageFilter::filterX(I, GIx, m_fg.data, m_gaussianKernelSize); - vpImageFilter::filterY(GIx, Iblur, m_fg.data, m_gaussianKernelSize); + vpImageFilter::filterX(I, GIx, m_fg.data, m_gaussianKernelSize, mp_mask); + vpImageFilter::filterY(GIx, Iblur, m_fg.data, m_gaussianKernelSize, mp_mask); // Computing the gradients - vpImageFilter::filter(Iblur, m_dIx, m_gradientFilterX, true); - vpImageFilter::filter(Iblur, m_dIy, m_gradientFilterY, true); + vpImageFilter::filter(Iblur, m_dIx, m_gradientFilterX, true, mp_mask); + vpImageFilter::filter(Iblur, m_dIy, m_gradientFilterY, true, mp_mask); } else { std::string errmsg("Currently, the filtering operation \""); @@ -354,6 +356,13 @@ vpCannyEdgeDetection::performEdgeThinning(const float &lowerThreshold) for (int row = 0; row < nbRows; row++) { for (int col = 0; col < nbCols; col++) { + if (mp_mask != nullptr) { + if (!(*mp_mask)[row][col]) { + // The mask tells us to ignore the current pixel + continue; + } + } + // Computing the gradient orientation and magnitude float grad = getManhattanGradient(m_dIx, m_dIy, row, col); diff --git a/modules/core/src/image/vpImageCircle.cpp b/modules/core/src/image/vpImageCircle.cpp index 5ac5933ae6..469fe32a1e 100644 --- a/modules/core/src/image/vpImageCircle.cpp +++ b/modules/core/src/image/vpImageCircle.cpp @@ -1022,6 +1022,97 @@ float vpImageCircle::computeArcLengthInRoI(const vpRect &roi, const float &round return delta_theta * m_radius; } +unsigned int vpImageCircle::computePixelsInMask(const vpImage &mask) const +{ + const int xm = m_center.get_u(), ym = m_center.get_v(); + const float r_float = static_cast(m_radius); + const int width = mask.getWidth(); + const int height = mask.getHeight(); + + // Increment the counter if the considered pixel (x, y) is in the mask image + auto incrementIfIsInMask = [](const vpImage &mask, const int &width, const int &height, const int &x, const int &y, + unsigned int &count) { + if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) { + // The pixel is outside the limit of the mask + return; + } + if (mask[y][x]) { + // Increment only if the pixel value of the mask is true + count++; + } + }; + unsigned int count = 0; // Count the number of pixels of the circle whose value in the mask is true + + const float thetaStop = M_PI_2f; + float theta = 0; + int x1 = 0, x2 = 0, x3 = 0, x4 = 0; + int y1 = 0, y2 = 0, y3 = 0, y4 = 0; + while (theta < thetaStop) { + float cos_theta = std::cos(theta); + float sin_theta = std::sin(theta); + float rcos_pos = r_float * cos_theta; + float rsin_pos = r_float * sin_theta; + x1 = xm + rcos_pos; // theta + y1 = ym + rsin_pos; // theta + x2 = xm - rsin_pos; // theta + pi + y2 = ym + rcos_pos; // theta + pi + x3 = xm - rcos_pos; // theta + pi/2 + y3 = ym - rsin_pos; // theta + pi/2 + x4 = xm + rsin_pos; // theta + pi + y4 = ym - rcos_pos; // theta + pi + incrementIfIsInMask(mask, width, height, x1, y1, count); + incrementIfIsInMask(mask, width, height, x2, y2, count); + incrementIfIsInMask(mask, width, height, x3, y3, count); + incrementIfIsInMask(mask, width, height, x4, y4, count); + + // Looking for dtheta such as either x or 1 increments of 1 pix exactly + // Using series expansion, we get that if we want to have an increment of + // 1 pixel for the derivative along x (resp. y), we have to respect the + // following formulae + float dthetaCosPos = 1.f / (r_float * cos_theta); + float dthetaCosNeg = -1.f / (r_float * cos_theta); + float dthetaSinPos = 1.f / (r_float * sin_theta); + float dthetaSinNeg = -1.f / (r_float * sin_theta); + float dthetaPos = 0.f; + if ((sin_theta < 0.f) && (cos_theta > 0.f)) { + // dTheta <= -1/r sin(theta) && dTheta <= 1/r cos(theta) + dthetaPos = std::min(dthetaCosPos, dthetaSinNeg); + } + else if ((sin_theta > 0.f) && (cos_theta < 0.f)) { + // dTheta <= 1/r sin(theta) && dTheta <= -1/r cos(theta) + dthetaPos = std::min(dthetaCosNeg, dthetaSinPos); + } + else if ((sin_theta < 0.f) && (cos_theta < 0.f)) { + // dTheta <= -1/r sin(theta) && dTheta <= -1/r cos(theta) + dthetaPos = std::min(dthetaCosNeg, dthetaSinNeg); + } + else if ((sin_theta > 0.f) && (cos_theta > 0.f)) { + // dTheta <= 1/r sin(theta) && dTheta <= 1/r cos(theta) + dthetaPos = std::min(dthetaCosPos, dthetaSinPos); + } + else if (sin_theta == 0.f && cos_theta !=0.f) { + // dTheta = -1 / r cos(theta) || dTheta = 1 / r cos(theta) + if (cos_theta > 0.f) { + dthetaPos = dthetaCosNeg; + } + else { + dthetaPos = dthetaCosPos; + } + } + else if (sin_theta != 0.f && cos_theta ==0.f) { + // dTheta = -1 / r sin(theta) || dTheta = 1 / r sin(theta) + if (sin_theta > 0.f) { + dthetaPos = dthetaSinNeg; + } + else { + dthetaPos = dthetaSinPos; + } + } + theta += dthetaPos; + } + return count; +} + vpImagePoint vpImageCircle::getCenter() const { return m_center; diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index 36fa00d62a..c44bca6b64 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -36,14 +36,14 @@ #include #include -/** - * \brief Get the list of available vpCannyBackendType. - * - * \param[in] pref The prefix of the list. - * \param[in] sep The separator between two elements of the list. - * \param[in] suf The suffix of the list. - * \return std::string The list of available items. - */ + /** + * \brief Get the list of available vpCannyBackendType. + * + * \param[in] pref The prefix of the list. + * \param[in] sep The separator between two elements of the list. + * \param[in] suf The suffix of the list. + * \return std::string The list of available items. + */ std::string vpImageFilter::vpCannyBackendTypeList(const std::string &pref, const std::string &sep, const std::string &suf) { @@ -115,7 +115,7 @@ vpImageFilter::vpCannyBackendType vpImageFilter::vpCannyBackendTypeFromString(co * \return std::string The list of available items. */ std::string vpImageFilter::vpCannyFilteringAndGradientTypeList(const std::string &pref, const std::string &sep, - const std::string &suf) + const std::string &suf) { std::string list(pref); for (unsigned int i = 0; i < (vpCannyFilteringAndGradientType::CANNY_COUNT_FILTERING - 1); ++i) { @@ -180,32 +180,32 @@ vpImageFilter::vpCannyFilteringAndGradientType vpImageFilter::vpCannyFilteringAn * \cond DO_NOT_DOCUMENT */ template -void vpImageFilter::filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve); +void vpImageFilter::filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve, const vpImage *p_mask); template -void vpImageFilter::filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve); +void vpImageFilter::filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve, const vpImage *p_mask); template void vpImageFilter::filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, - bool convolve); + bool convolve, const vpImage *p_mask); template void vpImageFilter::filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, - bool convolve); + bool convolve, const vpImage *p_mask); template void vpImageFilter::filter(const vpImage &I, vpImage &GI, const float *filter, - unsigned int size); + unsigned int size, const vpImage *p_mask); template void vpImageFilter::filter(const vpImage &I, vpImage &GI, const double *filter, - unsigned int size); + unsigned int size, const vpImage *p_mask); template -void vpImageFilter::filter(const vpImage &I, vpImage &GI, const float *filter, unsigned int size); +void vpImageFilter::filter(const vpImage &I, vpImage &GI, const float *filter, unsigned int size, const vpImage *p_mask); template -void vpImageFilter::filter(const vpImage &I, vpImage &GI, const double *filter, unsigned int size); +void vpImageFilter::filter(const vpImage &I, vpImage &GI, const double *filter, unsigned int size, const vpImage *p_mask); /** * \endcond */ @@ -262,11 +262,11 @@ void vpImageFilter::filter(const vpImage &I, vpImage &I, vpImage &If, const vpColVector &kernelH, - const vpColVector &kernelV) + const vpColVector &kernelV) { - unsigned int size = kernelH.size(), sizeV = kernelV.size(); - unsigned int widthI = I.getWidth(), heightI = I.getHeight(); - unsigned int half_size = size / 2; + const unsigned int size = kernelH.size(), sizeV = kernelV.size(); + const unsigned int widthI = I.getWidth(), heightI = I.getHeight(); + const unsigned int half_size = size / 2; If.resize(heightI, widthI, 0.0); vpImage I_filter(heightI, widthI, 0.0); @@ -299,40 +299,59 @@ void vpImageFilter::sepFilter(const vpImage &I, vpImage & */ template void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const float *filter, - unsigned int size); + unsigned int size, const vpImage *p_mask); template void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const double *filter, - unsigned int size); + unsigned int size, const vpImage *p_mask); template -void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); +void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size, const vpImage *p_mask); template -void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); +void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size, const vpImage *p_mask); /** * \endcond */ -void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size) +void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size, + const vpImage *p_mask) { const unsigned int heightI = I.getHeight(), widthI = I.getWidth(); - dIx.resize(heightI, widthI); + const unsigned int stop1J = (size - 1) / 2; + const unsigned int stop2J = widthI - (size - 1) / 2; + resizeAndInitializeIfNeeded(p_mask, heightI, widthI, dIx); + for (unsigned int i = 0; i < heightI; i++) { - for (unsigned int j = 0; j < ((size - 1) / 2); ++j) { - dIx[i][j].R = static_cast(vpImageFilter::filterXLeftBorderR(I, i, j, filter, size)); - dIx[i][j].G = static_cast(vpImageFilter::filterXLeftBorderG(I, i, j, filter, size)); - dIx[i][j].B = static_cast(vpImageFilter::filterXLeftBorderB(I, i, j, filter, size)); + for (unsigned int j = 0; j < stop1J; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIx[i][j].R = static_cast(vpImageFilter::filterXLeftBorderR(I, i, j, filter, size)); + dIx[i][j].G = static_cast(vpImageFilter::filterXLeftBorderG(I, i, j, filter, size)); + dIx[i][j].B = static_cast(vpImageFilter::filterXLeftBorderB(I, i, j, filter, size)); + } } - for (unsigned int j = (size - 1) / 2; j < (widthI - (size - 1) / 2); ++j) { - dIx[i][j].R = static_cast(vpImageFilter::filterXR(I, i, j, filter, size)); - dIx[i][j].G = static_cast(vpImageFilter::filterXG(I, i, j, filter, size)); - dIx[i][j].B = static_cast(vpImageFilter::filterXB(I, i, j, filter, size)); + for (unsigned int j = stop1J; j < stop2J; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIx[i][j].R = static_cast(vpImageFilter::filterXR(I, i, j, filter, size)); + dIx[i][j].G = static_cast(vpImageFilter::filterXG(I, i, j, filter, size)); + dIx[i][j].B = static_cast(vpImageFilter::filterXB(I, i, j, filter, size)); + } } - for (unsigned int j = widthI - (size - 1) / 2; j < widthI; ++j) { - dIx[i][j].R = static_cast(vpImageFilter::filterXRightBorderR(I, i, j, filter, size)); - dIx[i][j].G = static_cast(vpImageFilter::filterXRightBorderG(I, i, j, filter, size)); - dIx[i][j].B = static_cast(vpImageFilter::filterXRightBorderB(I, i, j, filter, size)); + for (unsigned int j = stop2J; j < widthI; ++j) { + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIx[i][j].R = static_cast(vpImageFilter::filterXRightBorderR(I, i, j, filter, size)); + dIx[i][j].G = static_cast(vpImageFilter::filterXRightBorderG(I, i, j, filter, size)); + dIx[i][j].B = static_cast(vpImageFilter::filterXRightBorderB(I, i, j, filter, size)); + } } } } @@ -342,44 +361,63 @@ void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, cons */ template void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const float *filter, - unsigned int size); + unsigned int size, const vpImage *p_mask); template void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, - unsigned int size); + unsigned int size, const vpImage *p_mask); template -void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); +void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size, const vpImage *p_mask); template -void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); +void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size, const vpImage *p_mask); /** * \endcond */ -void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size) +void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size, + const vpImage *p_mask) { const unsigned int heightI = I.getHeight(), widthI = I.getWidth(); - dIy.resize(heightI, widthI); - for (unsigned int i = 0; i < (size - 1) / 2; ++i) { + const unsigned int stop1I = (size - 1) / 2; + const unsigned int stop2I = heightI - (size - 1) / 2; + resizeAndInitializeIfNeeded(p_mask, heightI, widthI, dIy); + + for (unsigned int i = 0; i < stop1I; ++i) { for (unsigned int j = 0; j < widthI; ++j) { - dIy[i][j].R = static_cast(vpImageFilter::filterYTopBorderR(I, i, j, filter, size)); - dIy[i][j].G = static_cast(vpImageFilter::filterYTopBorderG(I, i, j, filter, size)); - dIy[i][j].B = static_cast(vpImageFilter::filterYTopBorderB(I, i, j, filter, size)); + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j].R = static_cast(vpImageFilter::filterYTopBorderR(I, i, j, filter, size)); + dIy[i][j].G = static_cast(vpImageFilter::filterYTopBorderG(I, i, j, filter, size)); + dIy[i][j].B = static_cast(vpImageFilter::filterYTopBorderB(I, i, j, filter, size)); + } } } - for (unsigned int i = (size - 1) / 2; i < (heightI - (size - 1) / 2); ++i) { + for (unsigned int i = stop1I; i < stop2I; ++i) { for (unsigned int j = 0; j < widthI; ++j) { - dIy[i][j].R = static_cast(vpImageFilter::filterYR(I, i, j, filter, size)); - dIy[i][j].G = static_cast(vpImageFilter::filterYG(I, i, j, filter, size)); - dIy[i][j].B = static_cast(vpImageFilter::filterYB(I, i, j, filter, size)); + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j].R = static_cast(vpImageFilter::filterYR(I, i, j, filter, size)); + dIy[i][j].G = static_cast(vpImageFilter::filterYG(I, i, j, filter, size)); + dIy[i][j].B = static_cast(vpImageFilter::filterYB(I, i, j, filter, size)); + } } } - for (unsigned int i = heightI - (size - 1) / 2; i < heightI; ++i) { + for (unsigned int i = stop2I; i < heightI; ++i) { for (unsigned int j = 0; j < widthI; ++j) { - dIy[i][j].R = static_cast(vpImageFilter::filterYBottomBorderR(I, i, j, filter, size)); - dIy[i][j].G = static_cast(vpImageFilter::filterYBottomBorderG(I, i, j, filter, size)); - dIy[i][j].B = static_cast(vpImageFilter::filterYBottomBorderB(I, i, j, filter, size)); + // We have to compute the value for each pixel if we don't have a mask or for + // pixels for which the mask is true otherwise + bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + if (computeVal) { + dIy[i][j].R = static_cast(vpImageFilter::filterYBottomBorderR(I, i, j, filter, size)); + dIy[i][j].G = static_cast(vpImageFilter::filterYBottomBorderG(I, i, j, filter, size)); + dIy[i][j].B = static_cast(vpImageFilter::filterYBottomBorderB(I, i, j, filter, size)); + } } } } @@ -388,16 +426,16 @@ void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, cons * \cond DO_NOT_DOCUMENT */ template -void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, float sigma, bool normalize); +void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, float sigma, bool normalize, const vpImage *p_mask); template -void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize); +void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize, const vpImage *p_mask); template -void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, float sigma, bool normalize); +void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, float sigma, bool normalize, const vpImage *p_mask); template -void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize); +void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize, const vpImage *p_mask); /** * \endcond */ @@ -410,16 +448,18 @@ void vpImageFilter::gaussianBlur(const vpImage &I, vpIma \param[in] sigma : Gaussian standard deviation. If it is equal to zero or negative, it is computed from filter size as sigma = (size-1)/6. \param[in] normalize : Flag indicating whether to normalize the filter coefficients or not. + \param[in] p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). \sa getGaussianKernel() to know which kernel is used. */ -void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize) +void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize, + const vpImage *p_mask) { double *fg = new double[(size + 1) / 2]; vpImageFilter::getGaussianKernel(fg, size, sigma, normalize); vpImage GIx; - vpImageFilter::filterX(I, GIx, fg, size); - vpImageFilter::filterY(GIx, GI, fg, size); + vpImageFilter::filterX(I, GIx, fg, size, p_mask); + vpImageFilter::filterY(GIx, GI, fg, size, p_mask); GIx.destroy(); delete[] fg; } @@ -437,72 +477,72 @@ template void vpImageFilter::getGaussianDerivativeKernel(double *filter, unsigned int size, double sigma, bool normalize); template -void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx); +void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const vpImage *p_mask); template -void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx); +void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const vpImage *p_mask); template -void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy); +void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const vpImage *p_mask); template -void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy); +void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const vpImage *p_mask); template -void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); +void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size, const vpImage *p_mask); template -void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); +void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size, const vpImage *p_mask); template -void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); +void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size, const vpImage *p_mask); template -void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); +void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size, const vpImage *p_mask); template -void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); +void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size, const vpImage *p_mask); template -void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); +void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size, const vpImage *p_mask); template -void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); +void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size, const vpImage *p_mask); template -void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); +void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size, const vpImage *p_mask); template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const float *gaussianKernel, - const float *gaussianDerivativeKernel, unsigned int size); + const float *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask); template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const double *gaussianKernel, - const double *gaussianDerivativeKernel, unsigned int size); + const double *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask); template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const float *gaussianKernel, - const float *gaussianDerivativeKernel, unsigned int size); + const float *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask); template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const double *gaussianKernel, - const double *gaussianDerivativeKernel, unsigned int size); + const double *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask); template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const float *gaussianKernel, - const float *gaussianDerivativeKernel, unsigned int size); + const float *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask); template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const double *gaussianKernel, - const double *gaussianDerivativeKernel, unsigned int size); + const double *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask); template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const float *gaussianKernel, - const float *gaussianDerivativeKernel, unsigned int size); + const float *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask); template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const double *gaussianKernel, - const double *gaussianDerivativeKernel, unsigned int size); + const double *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask); /** * \endcond */ @@ -579,11 +619,11 @@ float vpImageFilter::getSobelKernelY(float *filter, unsigned int size); #if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) -/** - * \brief Calculates the median value of a single channel. - * The algorithm is based on based on https://github.com/arnaudgelas/OpenCVExamples/blob/master/cvMat/Statistics/Median/Median.cpp - * \param[in] channel : Single channel image in OpenCV format. - */ + /** + * \brief Calculates the median value of a single channel. + * The algorithm is based on based on https://github.com/arnaudgelas/OpenCVExamples/blob/master/cvMat/Statistics/Median/Median.cpp + * \param[in] channel : Single channel image in OpenCV format. + */ float vpImageFilter::median(const cv::Mat &channel) { float m = (channel.rows * channel.cols) / 2.f; @@ -678,7 +718,7 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p if ((p_cv_dIx == nullptr) || (p_cv_dIy == nullptr)) { computePartialDerivatives(cv_I, dIx, dIy, true, true, true, gaussianKernelSize, gaussianStdev, apertureGradient, - filteringType); + filteringType); } else { dIx = *p_cv_dIx; @@ -712,7 +752,7 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p break; } } - float upperThresh = std::max(bon, 1.f); + float upperThresh = std::max(bon, 1.f); lowerThresh = lowerThresholdRatio * bon; return upperThresh; } @@ -741,7 +781,7 @@ void vpImageFilter::computePartialDerivatives(const cv::Mat &cv_I, const vpImageFilter::vpCannyFilteringAndGradientType &filteringType) { if ((filteringType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) - || (filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)) { + || (filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)) { cv::Mat img_blur; // Apply Gaussian blur to the image cv::Size gsz(gaussianKernelSize, gaussianKernelSize); @@ -753,7 +793,7 @@ void vpImageFilter::computePartialDerivatives(const cv::Mat &cv_I, if (normalize) { scale = 1. / 8.; if (apertureGradient > 3) { - scale *= std::pow(1./2., (static_cast(apertureGradient) * 2. - 3.)); // 1 / 2^(2 x ksize - dx - dy -2) with ksize =apertureGradient and dx xor dy = 1 + scale *= std::pow(1. / 2., (static_cast(apertureGradient) * 2. - 3.)); // 1 / 2^(2 x ksize - dx - dy -2) with ksize =apertureGradient and dx xor dy = 1 } } if (computeDx) { @@ -784,73 +824,75 @@ void vpImageFilter::computePartialDerivatives(const cv::Mat &cv_I, */ template void vpImageFilter::computePartialDerivatives(const vpImage &I, - vpImage &dIx, vpImage &dIy, - const bool &computeDx, const bool &computeDy, const bool &normalize, - const unsigned int &gaussianKernelSize, const float &gaussianStdev, - const unsigned int &apertureGradient, - const vpCannyFilteringAndGradientType &filteringType, - const vpCannyBackendType &backend); + vpImage &dIx, vpImage &dIy, + const bool &computeDx, const bool &computeDy, const bool &normalize, + const unsigned int &gaussianKernelSize, const float &gaussianStdev, + const unsigned int &apertureGradient, + const vpCannyFilteringAndGradientType &filteringType, + const vpCannyBackendType &backend, const vpImage *p_mask); template void vpImageFilter::computePartialDerivatives(const vpImage &I, - vpImage &dIx, vpImage &dIy, - const bool &computeDx, const bool &computeDy, const bool &normalize, - const unsigned int &gaussianKernelSize, const double &gaussianStdev, - const unsigned int &apertureGradient, - const vpCannyFilteringAndGradientType &filteringType, - const vpCannyBackendType &backend); + vpImage &dIx, vpImage &dIy, + const bool &computeDx, const bool &computeDy, const bool &normalize, + const unsigned int &gaussianKernelSize, const double &gaussianStdev, + const unsigned int &apertureGradient, + const vpCannyFilteringAndGradientType &filteringType, + const vpCannyBackendType &backend, const vpImage *p_mask); template void vpImageFilter::computePartialDerivatives(const vpImage &I, - vpImage &dIx, vpImage &dIy, - const bool &computeDx, const bool &computeDy, const bool &normalize, - const unsigned int &gaussianKernelSize, const float &gaussianStdev, - const unsigned int &apertureGradient, - const vpCannyFilteringAndGradientType &filteringType, - const vpCannyBackendType &backend); + vpImage &dIx, vpImage &dIy, + const bool &computeDx, const bool &computeDy, const bool &normalize, + const unsigned int &gaussianKernelSize, const float &gaussianStdev, + const unsigned int &apertureGradient, + const vpCannyFilteringAndGradientType &filteringType, + const vpCannyBackendType &backend, const vpImage *p_mask); template void vpImageFilter::computePartialDerivatives(const vpImage &I, - vpImage &dIx, vpImage &dIy, - const bool &computeDx, const bool &computeDy, const bool &normalize, - const unsigned int &gaussianKernelSize, const double &gaussianStdev, - const unsigned int &apertureGradient, - const vpCannyFilteringAndGradientType &filteringType, - const vpCannyBackendType &backend); + vpImage &dIx, vpImage &dIy, + const bool &computeDx, const bool &computeDy, const bool &normalize, + const unsigned int &gaussianKernelSize, const double &gaussianStdev, + const unsigned int &apertureGradient, + const vpCannyFilteringAndGradientType &filteringType, + const vpCannyBackendType &backend, const vpImage *p_mask); template void vpImageFilter::computePartialDerivatives(const vpImage &I, - vpImage &dIx, vpImage &dIy, - const bool &computeDx, const bool &computeDy, const bool &normalize, - const unsigned int &gaussianKernelSize, const float &gaussianStdev, - const unsigned int &apertureGradient, - const vpCannyFilteringAndGradientType &filteringType, - const vpCannyBackendType &backend); + vpImage &dIx, vpImage &dIy, + const bool &computeDx, const bool &computeDy, const bool &normalize, + const unsigned int &gaussianKernelSize, const float &gaussianStdev, + const unsigned int &apertureGradient, + const vpCannyFilteringAndGradientType &filteringType, + const vpCannyBackendType &backend, const vpImage *p_mask); template void vpImageFilter::computePartialDerivatives(const vpImage &I, - vpImage &dIx, vpImage &dIy, - const bool &computeDx, const bool &computeDy, const bool &normalize, - const unsigned int &gaussianKernelSize, const double &gaussianStdev, - const unsigned int &apertureGradient, - const vpCannyFilteringAndGradientType &filteringType, - const vpCannyBackendType &backend); + vpImage &dIx, vpImage &dIy, + const bool &computeDx, const bool &computeDy, const bool &normalize, + const unsigned int &gaussianKernelSize, const double &gaussianStdev, + const unsigned int &apertureGradient, + const vpCannyFilteringAndGradientType &filteringType, + const vpCannyBackendType &backend, const vpImage *p_mask); template float vpImageFilter::computeCannyThreshold(const vpImage &I, float &lowerThresh, - const vpImage *p_dIx, const vpImage *p_dIy, - const unsigned int &gaussianKernelSize, - const double &gaussianStdev, const unsigned int &apertureGradient, - const float &lowerThresholdRatio, const float &upperThresholdRatio, - const vpImageFilter::vpCannyFilteringAndGradientType &filteringType); + const vpImage *p_dIx, const vpImage *p_dIy, + const unsigned int &gaussianKernelSize, + const double &gaussianStdev, const unsigned int &apertureGradient, + const float &lowerThresholdRatio, const float &upperThresholdRatio, + const vpImageFilter::vpCannyFilteringAndGradientType &filteringType, + const vpImage *p_mask); template float vpImageFilter::computeCannyThreshold(const vpImage &I, float &lowerThresh, - const vpImage *p_dIx, const vpImage *p_dIy, - const unsigned int &gaussianKernelSize, - const float &gaussianStdev, const unsigned int &apertureGradient, - const float &lowerThresholdRatio, const float &upperThresholdRatio, - const vpImageFilter::vpCannyFilteringAndGradientType &filteringType); + const vpImage *p_dIx, const vpImage *p_dIy, + const unsigned int &gaussianKernelSize, + const float &gaussianStdev, const unsigned int &apertureGradient, + const float &lowerThresholdRatio, const float &upperThresholdRatio, + const vpImageFilter::vpCannyFilteringAndGradientType &filteringType, + const vpImage *p_mask); /** * \endcond */ @@ -1024,13 +1066,15 @@ void vpImageFilter::canny(const vpImage &Isrc, vpImage &Isrc, vpImage &Ires, const unsigned int &gaussianFilterSize, const float &lowerThreshold, const float &upperThreshold, const unsigned int &apertureGradient, const float &gaussianStdev, const float &lowerThresholdRatio, const float &upperThresholdRatio, const bool &normalizeGradients, - const vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps) + const vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps, + const vpImage *p_mask) { if (cannyBackend == CANNY_OPENCV_BACKEND) { #if defined(HAVE_OPENCV_IMGPROC) @@ -1042,8 +1086,8 @@ void vpImageFilter::canny(const vpImage &Isrc, vpImage &Isrc, vpImage dIx, dIy; computePartialDerivatives(Isrc, dIx, dIy, true, true, normalizeGradients, gaussianFilterSize, - gaussianStdev, apertureGradient, cannyFilteringSteps, cannyBackend); + gaussianStdev, apertureGradient, cannyFilteringSteps, cannyBackend, p_mask); if (upperCannyThresh < 0.f) { upperCannyThresh = computeCannyThreshold(Isrc, lowerCannyThresh, &dIx, &dIy, gaussianFilterSize, gaussianStdev, - apertureGradient, lowerThresholdRatio, upperThresholdRatio, - cannyFilteringSteps); + apertureGradient, lowerThresholdRatio, upperThresholdRatio, + cannyFilteringSteps, p_mask); } else if (lowerCannyThresh < 0.f) { - lowerCannyThresh = upperCannyThresh / 3.f; + lowerCannyThresh = upperCannyThresh / 3.; } vpCannyEdgeDetection edgeDetector(gaussianFilterSize, gaussianStdev, apertureGradient, lowerCannyThresh, upperCannyThresh, lowerThresholdRatio, upperThresholdRatio, cannyFilteringSteps); edgeDetector.setGradients(dIx, dIy); + edgeDetector.setMask(p_mask); Ires = edgeDetector.detect(Isrc); } } diff --git a/modules/core/src/tools/histogram/vpHistogram.cpp b/modules/core/src/tools/histogram/vpHistogram.cpp index 5fa5f6fcb2..f7004a43bf 100644 --- a/modules/core/src/tools/histogram/vpHistogram.cpp +++ b/modules/core/src/tools/histogram/vpHistogram.cpp @@ -60,11 +60,12 @@ struct vpHistogram_Param_t unsigned int m_lut[256]; unsigned int *m_histogram; const vpImage *m_I; + const vpImage *m_mask; - vpHistogram_Param_t() : m_start_index(0), m_end_index(0), m_lut(), m_histogram(nullptr), m_I(nullptr) { } + vpHistogram_Param_t() : m_start_index(0), m_end_index(0), m_lut(), m_histogram(nullptr), m_I(nullptr), m_mask(nullptr) { } - vpHistogram_Param_t(unsigned int start_index, unsigned int end_index, const vpImage *const I) - : m_start_index(start_index), m_end_index(end_index), m_lut(), m_histogram(nullptr), m_I(I) + vpHistogram_Param_t(unsigned int start_index, unsigned int end_index, const vpImage *const I, const vpImage *const mask) + : m_start_index(start_index), m_end_index(end_index), m_lut(), m_histogram(nullptr), m_I(I), m_mask(mask) { } ~vpHistogram_Param_t() @@ -87,37 +88,89 @@ vpThread::Return computeHistogramThread(vpThread::Args args) unsigned char *ptrEnd = (unsigned char *)(I->bitmap) + end_index; unsigned char *ptrCurrent = ptrStart; + const bool alwaysTrue = true; + const bool *ptrMaskCurrent = &alwaysTrue; + if (histogram_param->m_mask) { + ptrMaskCurrent = (const bool *)histogram_param->m_mask->bitmap + start_index; + } + if (end_index - start_index >= 8) { // Unroll loop version for (; ptrCurrent <= ptrEnd - 8;) { - histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + if (*ptrMaskCurrent) { + histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + } ++ptrCurrent; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } - histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + if (*ptrMaskCurrent) { + histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + } ++ptrCurrent; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } - histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + if (*ptrMaskCurrent) { + histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + } ++ptrCurrent; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } - histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + if (*ptrMaskCurrent) { + histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + } ++ptrCurrent; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } - histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + if (*ptrMaskCurrent) { + histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + } ++ptrCurrent; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } - histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + if (*ptrMaskCurrent) { + histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + } ++ptrCurrent; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } - histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + if (*ptrMaskCurrent) { + histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + } ++ptrCurrent; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } - histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + if (*ptrMaskCurrent) { + histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + } ++ptrCurrent; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } } } for (; ptrCurrent != ptrEnd; ++ptrCurrent) { - histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + if (*ptrMaskCurrent) { + histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; + } + ++ptrCurrent; + if (histogram_param->m_mask != nullptr) { + ++ptrMaskCurrent; + } } return 0; @@ -139,15 +192,15 @@ bool compare_vpHistogramPeak(vpHistogramPeak first, vpHistogramPeak second) /*! Defaut constructor for a gray level histogram. */ -vpHistogram::vpHistogram() : histogram(nullptr), size(256) { init(); } +vpHistogram::vpHistogram() : m_histogram(nullptr), m_size(256), mp_mask(nullptr), m_total(0) { init(); } /*! Copy constructor of a gray level histogram. */ -vpHistogram::vpHistogram(const vpHistogram &h) : histogram(nullptr), size(256) +vpHistogram::vpHistogram(const vpHistogram &h) : m_histogram(nullptr), m_size(256), mp_mask(h.mp_mask), m_total(h.m_total) { - init(h.size); - memcpy(histogram, h.histogram, size * sizeof(unsigned)); + init(h.m_size); + memcpy(m_histogram, h.m_histogram, m_size * sizeof(unsigned)); } /*! @@ -157,23 +210,38 @@ vpHistogram::vpHistogram(const vpHistogram &h) : histogram(nullptr), size(256) \sa calculate() */ -vpHistogram::vpHistogram(const vpImage &I) : histogram(nullptr), size(256) +vpHistogram::vpHistogram(const vpImage &I) : m_histogram(nullptr), m_size(256), mp_mask(nullptr), m_total(0) { init(); calculate(I); } +/*! + Calculates the histrogram from a gray level image using a binary mask. + + \param I : Gray level image. + \param p_mask: A pointer towards a binary mask where true indicates that the pixel should considered + and false that it should be ignored. + \sa calculate() setMask() +*/ +vpHistogram::vpHistogram(const vpImage &I, const vpImage *p_mask) : m_histogram(nullptr), m_size(256), mp_mask(nullptr), m_total(0) +{ + init(); + setMask(p_mask); + calculate(I); +} + /*! Destructor. */ vpHistogram::~vpHistogram() { - if (histogram != nullptr) { + if (m_histogram != nullptr) { // vpTRACE("free: %p", &histogram); - delete[] histogram; - histogram = nullptr; - size = 0; + delete[] m_histogram; + m_histogram = nullptr; + m_size = 0; } } @@ -192,8 +260,10 @@ vpHistogram::~vpHistogram() */ vpHistogram &vpHistogram::operator=(const vpHistogram &h) { - init(h.size); - memcpy(histogram, h.histogram, size * sizeof(unsigned)); + init(h.m_size); + memcpy(m_histogram, h.m_histogram, m_size * sizeof(unsigned)); + mp_mask = h.mp_mask; + m_total = h.m_total; return *this; } @@ -205,16 +275,18 @@ vpHistogram &vpHistogram::operator=(const vpHistogram &h) */ void vpHistogram::init(unsigned size_) { - if (histogram != nullptr) { - delete[] histogram; - histogram = nullptr; + if (m_histogram != nullptr) { + delete[] m_histogram; + m_histogram = nullptr; } - this->size = size_; - histogram = new unsigned[size]; + this->m_size = size_; + m_histogram = new unsigned[m_size]; - memset(histogram, 0, size * sizeof(unsigned)); + memset(m_histogram, 0, m_size * sizeof(unsigned)); + mp_mask = nullptr; + m_total = 0; // vpTRACE("alloc: %p", &histogram); } @@ -228,20 +300,20 @@ void vpHistogram::init(unsigned size_) */ void vpHistogram::calculate(const vpImage &I, unsigned int nbins, unsigned int nbThreads) { - if (size != nbins) { - if (histogram != nullptr) { - delete[] histogram; - histogram = nullptr; + if (m_size != nbins) { + if (m_histogram != nullptr) { + delete[] m_histogram; + m_histogram = nullptr; } - size = nbins > 256 ? 256 : (nbins > 0 ? nbins : 256); + m_size = nbins > 256 ? 256 : (nbins > 0 ? nbins : 256); if (nbins > 256 || nbins == 0) { std::cerr << "nbins=" << nbins << " , nbins should be between ]0 ; 256] ; use by default nbins=256" << std::endl; } - histogram = new unsigned int[size]; + m_histogram = new unsigned int[m_size]; } - memset(histogram, 0, size * sizeof(unsigned int)); + memset(m_histogram, 0, m_size * sizeof(unsigned int)); bool use_single_thread; #if !defined(VISP_HAVE_PTHREAD) && !defined(_WIN32) @@ -256,20 +328,32 @@ void vpHistogram::calculate(const vpImage &I, unsigned int nbins, unsigned int lut[256]; for (unsigned int i = 0; i < 256; i++) { - lut[i] = (unsigned int)(i * size / 256.0); + lut[i] = (unsigned int)(i * m_size / 256.0); } if (use_single_thread) { // Single thread + const bool alwaysTrue = true; + const bool *ptrMaskCurrent = &alwaysTrue; + if (mp_mask) { + ptrMaskCurrent = (const bool *)mp_mask->bitmap; + } unsigned int size_ = I.getWidth() * I.getHeight(); unsigned char *ptrStart = (unsigned char *)I.bitmap; unsigned char *ptrEnd = ptrStart + size_; unsigned char *ptrCurrent = ptrStart; + m_total = 0; while (ptrCurrent != ptrEnd) { - histogram[lut[*ptrCurrent]]++; + if (*ptrMaskCurrent) { + m_histogram[lut[*ptrCurrent]]++; + ++m_total; + } ++ptrCurrent; + if (mp_mask) { + ++ptrMaskCurrent; + } } } else { @@ -291,9 +375,9 @@ void vpHistogram::calculate(const vpImage &I, unsigned int nbins, end_index = start_index + last_step; } - vpHistogram_Param_t *histogram_param = new vpHistogram_Param_t(start_index, end_index, &I); - histogram_param->m_histogram = new unsigned int[size]; - memset(histogram_param->m_histogram, 0, size * sizeof(unsigned int)); + vpHistogram_Param_t *histogram_param = new vpHistogram_Param_t(start_index, end_index, &I, mp_mask); + histogram_param->m_histogram = new unsigned int[m_size]; + memset(histogram_param->m_histogram, 0, m_size * sizeof(unsigned int)); memcpy(histogram_param->m_lut, lut, 256 * sizeof(unsigned int)); histogramParams.push_back(histogram_param); @@ -308,14 +392,16 @@ void vpHistogram::calculate(const vpImage &I, unsigned int nbins, threadpool[cpt]->join(); } - for (unsigned int cpt1 = 0; cpt1 < size; cpt1++) { + m_total = 0; + for (unsigned int cpt1 = 0; cpt1 < m_size; cpt1++) { unsigned int sum = 0; for (size_t cpt2 = 0; cpt2 < histogramParams.size(); cpt2++) { sum += histogramParams[cpt2]->m_histogram[cpt1]; } - histogram[cpt1] = sum; + m_histogram[cpt1] = sum; + m_total += sum; } // Delete @@ -339,7 +425,7 @@ void vpHistogram::equalize(const vpImage &I, vpImage::max(), cdfMax = 0; unsigned int minValue = std::numeric_limits::max(), maxValue = 0; - cdf[0] = histogram[0]; + cdf[0] = m_histogram[0]; if (cdf[0] < cdfMin && cdf[0] > 0) { cdfMin = cdf[0]; @@ -347,7 +433,7 @@ void vpHistogram::equalize(const vpImage &I, vpImage 0) { cdfMin = cdf[i]; @@ -399,9 +485,9 @@ void vpHistogram::display(const vpImage &I, const vpColor &color, unsigned int maxValue = maxValue_; if (maxValue == 0) { - for (unsigned int i = 0; i < size; i++) { - if (histogram[i] > maxValue) { - maxValue = histogram[i]; + for (unsigned int i = 0; i < m_size; i++) { + if (m_histogram[i] > maxValue) { + maxValue = m_histogram[i]; } } } @@ -412,11 +498,11 @@ void vpHistogram::display(const vpImage &I, const vpColor &color, // Keep 12 free pixels at the top unsigned int max_height = height - 12; double ratio_height = max_height / (double)maxValue; - double ratio_width = (width - 1) / (double)(size - 1.0); + double ratio_width = (width - 1) / (double)(m_size - 1.0); - for (unsigned int i = 1; i < size; i++) { - unsigned int value1 = histogram[i - 1]; - unsigned int value2 = histogram[i]; + for (unsigned int i = 1; i < m_size; i++) { + unsigned int value1 = m_histogram[i - 1]; + unsigned int value2 = m_histogram[i]; vpImagePoint startPt((height - 1) - (value1 * ratio_height), (i - 1) * ratio_width); vpImagePoint endPt((height - 1) - (value2 * ratio_height), (i * ratio_width)); @@ -444,7 +530,7 @@ void vpHistogram::display(const vpImage &I, const vpColor &color, */ void vpHistogram::smooth(unsigned int fsize) { - if (histogram == nullptr) { + if (m_histogram == nullptr) { vpERROR_TRACE("Histogram array not initialised\n"); throw(vpImageException(vpImageException::notInitializedError, "Histogram array not initialised")); } @@ -454,17 +540,17 @@ void vpHistogram::smooth(unsigned int fsize) int hsize = (int)fsize / 2; // half filter size - for (unsigned i = 0; i < size; i++) { + for (unsigned i = 0; i < m_size; i++) { unsigned int sum = 0; unsigned int nb = 0; for (int j = -hsize; j <= hsize; j++) { // exploitation of the overflow to detect negative value... - if (/*(i + j) >= 0 &&*/ (i + (unsigned int)j) < size) { - sum += h.histogram[i + (unsigned int)j]; + if (/*(i + j) >= 0 &&*/ (i + (unsigned int)j) < m_size) { + sum += h.m_histogram[i + (unsigned int)j]; nb++; } } - histogram[i] = sum / nb; + m_histogram[i] = sum / nb; } } @@ -484,7 +570,7 @@ void vpHistogram::smooth(unsigned int fsize) */ unsigned vpHistogram::getPeaks(std::list &peaks) { - if (histogram == nullptr) { + if (m_histogram == nullptr) { vpERROR_TRACE("Histogram array not initialised\n"); throw(vpImageException(vpImageException::notInitializedError, "Histogram array not initialised")); } @@ -501,8 +587,8 @@ unsigned vpHistogram::getPeaks(std::list &peaks) nbpeaks = 0; prev_slope = 1; - for (unsigned i = 0; i < size - 1; i++) { - int next_slope = (int)histogram[i + 1] - (int)histogram[i]; // Next histogram inclination + for (unsigned i = 0; i < m_size - 1; i++) { + int next_slope = (int)m_histogram[i + 1] - (int)m_histogram[i]; // Next histogram inclination // if ((prev_slope < 0) && (next_slope > 0) ) { // sum_level += i; @@ -522,7 +608,7 @@ unsigned vpHistogram::getPeaks(std::list &peaks) cpt++; unsigned int level = sum_level / cpt; - p.set((unsigned char)level, histogram[level]); + p.set((unsigned char)level, m_histogram[level]); // vpTRACE("add %d %d", p.getLevel(), p.getValue()); peaks.push_back(p); @@ -534,7 +620,7 @@ unsigned vpHistogram::getPeaks(std::list &peaks) cpt = 0; } if (prev_slope > 0) { - p.set((unsigned char)size - 1u, histogram[size - 1]); + p.set((unsigned char)m_size - 1u, m_histogram[m_size - 1]); // vpTRACE("add %d %d", p.getLevel(), p.getValue()); peaks.push_back(p); nbpeaks++; @@ -630,13 +716,13 @@ bool vpHistogram::getPeaks(unsigned char dist, vpHistogramPeak &peakl, vpHistogr valey.set(0, 0); // Allocation for the - peak = new unsigned char[size]; + peak = new unsigned char[m_size]; // Parse the histogram to get the local maxima nbpeaks = 0; prev_slope = 1; - for (unsigned i = 0; i < size - 1; i++) { - int next_slope = (int)histogram[i + 1] - (int)histogram[i]; // Next histogram inclination + for (unsigned i = 0; i < m_size - 1; i++) { + int next_slope = (int)m_histogram[i + 1] - (int)m_histogram[i]; // Next histogram inclination if (next_slope == 0) continue; // Peak detection @@ -646,12 +732,12 @@ bool vpHistogram::getPeaks(unsigned char dist, vpHistogramPeak &peakl, vpHistogr prev_slope = next_slope; } if (prev_slope > 0) - peak[nbpeaks++] = (unsigned char)(size - 1); + peak[nbpeaks++] = (unsigned char)(m_size - 1); // Get the global maximum index_highest_peak = 0; for (unsigned int i = 0; i < nbpeaks; i++) { - if (histogram[peak[i]] > histogram[peak[index_highest_peak]]) { + if (m_histogram[peak[i]] > m_histogram[peak[index_highest_peak]]) { index_highest_peak = i; } } @@ -664,8 +750,8 @@ bool vpHistogram::getPeaks(unsigned char dist, vpHistogramPeak &peakl, vpHistogr if (peak[index_highest_peak] - peak[i] > dist) { prof = 0; for (int j = peak[i]; j <= peak[index_highest_peak] - dist; j++) - if ((histogram[peak[i]] - histogram[j]) > prof) - prof = histogram[peak[i]] - histogram[j]; + if ((m_histogram[peak[i]] - m_histogram[j]) > prof) + prof = m_histogram[peak[i]] - m_histogram[j]; if (prof > maxprof) { maxprof = prof; @@ -679,8 +765,8 @@ bool vpHistogram::getPeaks(unsigned char dist, vpHistogramPeak &peakl, vpHistogr if (peak[i] - peak[index_highest_peak] > dist) { prof = 0; for (int j = peak[index_highest_peak] + dist; j <= peak[i]; j++) - if ((histogram[peak[i]] - histogram[j]) > prof) - prof = histogram[peak[i]] - histogram[j]; + if ((m_histogram[peak[i]] - m_histogram[j]) > prof) + prof = m_histogram[peak[i]] - m_histogram[j]; if (prof > maxprof) { maxprof = prof; @@ -691,12 +777,12 @@ bool vpHistogram::getPeaks(unsigned char dist, vpHistogramPeak &peakl, vpHistogr // Determine position of the first and second highest peaks if (peak[index_highest_peak] < peak[index_second_peak]) { - peakr.set(peak[index_second_peak], histogram[peak[index_second_peak]]); - peakl.set(peak[index_highest_peak], histogram[peak[index_highest_peak]]); + peakr.set(peak[index_second_peak], m_histogram[peak[index_second_peak]]); + peakl.set(peak[index_highest_peak], m_histogram[peak[index_highest_peak]]); } else { - peakl.set(peak[index_second_peak], histogram[peak[index_second_peak]]); - peakr.set(peak[index_highest_peak], histogram[peak[index_highest_peak]]); + peakl.set(peak[index_second_peak], m_histogram[peak[index_second_peak]]); + peakr.set(peak[index_highest_peak], m_histogram[peak[index_highest_peak]]); } if (peakl == peakr) { @@ -711,13 +797,13 @@ bool vpHistogram::getPeaks(unsigned char dist, vpHistogramPeak &peakl, vpHistogr sumindmini = 0; nbmini = 0; for (unsigned i = peakl.getLevel(); i <= peakr.getLevel(); i++) { - if (histogram[i] < mini) { - mini = histogram[i]; + if (m_histogram[i] < mini) { + mini = m_histogram[i]; nbmini = 1; sumindmini = i; continue; } - if (histogram[i] == mini) { + if (m_histogram[i] == mini) { sumindmini += i; nbmini++; } @@ -736,7 +822,7 @@ bool vpHistogram::getPeaks(unsigned char dist, vpHistogramPeak &peakl, vpHistogr } else { mini = sumindmini / nbmini; // mean - valey.set((unsigned char)mini, histogram[mini]); + valey.set((unsigned char)mini, m_histogram[mini]); delete[] peak; @@ -757,7 +843,7 @@ bool vpHistogram::getPeaks(unsigned char dist, vpHistogramPeak &peakl, vpHistogr */ unsigned vpHistogram::getValey(std::list &valey) { - if (histogram == nullptr) { + if (m_histogram == nullptr) { vpERROR_TRACE("Histogram array not initialised\n"); throw(vpImageException(vpImageException::notInitializedError, "Histogram array not initialised")); } @@ -774,8 +860,8 @@ unsigned vpHistogram::getValey(std::list &valey) nbvaley = 0; prev_slope = -1; - for (unsigned i = 0; i < size - 1; i++) { - int next_slope = (int)histogram[i + 1] - (int)histogram[i]; // Next histogram inclination + for (unsigned i = 0; i < m_size - 1; i++) { + int next_slope = (int)m_histogram[i + 1] - (int)m_histogram[i]; // Next histogram inclination if ((prev_slope < 0) && (next_slope == 0)) { sum_level += i + 1; @@ -789,7 +875,7 @@ unsigned vpHistogram::getValey(std::list &valey) cpt++; unsigned int level = sum_level / cpt; - p.set((unsigned char)level, histogram[level]); + p.set((unsigned char)level, m_histogram[level]); // vpTRACE("add %d %d", p.getLevel(), p.getValue()); valey.push_back(p); @@ -801,7 +887,7 @@ unsigned vpHistogram::getValey(std::list &valey) cpt = 0; } if (prev_slope < 0) { - p.set((unsigned char)size - 1u, histogram[size - 1]); + p.set((unsigned char)m_size - 1u, m_histogram[m_size - 1]); // vpTRACE("add %d %d", p.getLevel(), p.getValue()); valey.push_back(p); nbvaley++; @@ -847,13 +933,13 @@ bool vpHistogram::getValey(const vpHistogramPeak &peak1, const vpHistogramPeak & sumindmini = 0; nbmini = 0; for (unsigned i = peakl.getLevel(); i <= peakr.getLevel(); i++) { - if (histogram[i] < mini) { - mini = histogram[i]; + if (m_histogram[i] < mini) { + mini = m_histogram[i]; nbmini = 1; sumindmini = i; continue; } - if (histogram[i] == mini) { + if (m_histogram[i] == mini) { sumindmini += i; nbmini++; } @@ -868,7 +954,7 @@ bool vpHistogram::getValey(const vpHistogramPeak &peak1, const vpHistogramPeak & else { unsigned int minipos = sumindmini / nbmini; // position of the minimum - valey.set((unsigned char)minipos, histogram[minipos]); + valey.set((unsigned char)minipos, m_histogram[minipos]); return true; } } @@ -906,8 +992,8 @@ unsigned vpHistogram::getValey(unsigned char dist, const vpHistogramPeak &peak, valeyl.set(0, 0); ret &= 0x01; } - if (peak.getLevel() == size - 1) { - valeyr.set((unsigned char)(size - 1), 0); + if (peak.getLevel() == m_size - 1) { + valeyr.set((unsigned char)(m_size - 1), 0); ret &= 0x10; } @@ -936,14 +1022,14 @@ unsigned vpHistogram::getValey(unsigned char dist, const vpHistogramPeak &peak, peakl.set(0, 0); } else { - // search for the nearest peak on the left that matches the distance + // search for the nearest peak on the left that matches the distance std::list::const_iterator lit; // left iterator for (lit = peaks.begin(); lit != it; ++lit) { if (abs((*lit).getLevel() - peak.getLevel()) > dist) { // peakl found peakl = *lit; found = true; // we cannot stop here, since the other peaks on the - // right may exist + // right may exist } } } @@ -955,13 +1041,13 @@ unsigned vpHistogram::getValey(unsigned char dist, const vpHistogramPeak &peak, sumindmini = 0; nbmini = 0; for (unsigned i = peakl.getLevel(); i < peak.getLevel(); i++) { - if (histogram[i] < mini) { - mini = histogram[i]; + if (m_histogram[i] < mini) { + mini = m_histogram[i]; nbmini = 1; sumindmini = i; continue; } - if (histogram[i] == mini) { + if (m_histogram[i] == mini) { sumindmini += i; nbmini++; } @@ -972,7 +1058,7 @@ unsigned vpHistogram::getValey(unsigned char dist, const vpHistogramPeak &peak, } else { unsigned int minipos = sumindmini / nbmini; // position of the minimum - valeyl.set((unsigned char)minipos, histogram[minipos]); + valeyl.set((unsigned char)minipos, m_histogram[minipos]); ret &= 0x11; } } @@ -1004,31 +1090,31 @@ unsigned vpHistogram::getValey(unsigned char dist, const vpHistogramPeak &peak, } if (!found) - peakr.set((unsigned char)(size - 1), 0); + peakr.set((unsigned char)(m_size - 1), 0); // Search the valey on the right mini = peak.getValue(); sumindmini = 0; nbmini = 0; for (unsigned i = (unsigned int)peak.getLevel() + 1; i <= (unsigned int)peakr.getLevel(); i++) { - if (histogram[i] < mini) { - mini = histogram[i]; + if (m_histogram[i] < mini) { + mini = m_histogram[i]; nbmini = 1; sumindmini = i; continue; } - if (histogram[i] == mini) { + if (m_histogram[i] == mini) { sumindmini += i; nbmini++; } } if (nbmini == 0) { - valeyr.set((unsigned char)(size - 1), 0); + valeyr.set((unsigned char)(m_size - 1), 0); ret &= 0x10; } else { unsigned int minipos = sumindmini / nbmini; // position of the minimum - valeyr.set((unsigned char)minipos, histogram[minipos]); + valeyr.set((unsigned char)minipos, m_histogram[minipos]); ret &= 0x11; } } @@ -1087,8 +1173,8 @@ bool vpHistogram::write(const char *filename) FILE *fd = fopen(filename, "w"); if (fd == nullptr) return false; - for (unsigned i = 0; i < size; i++) - fprintf(fd, "%u %u\n", i, histogram[i]); + for (unsigned i = 0; i < m_size; i++) + fprintf(fd, "%u %u\n", i, m_histogram[i]); fclose(fd); return true; diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index a434a7c813..eb4a83e5aa 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -53,6 +53,10 @@ using json = nlohmann::json; #endif +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) +#include +#endif + /** * \ingroup group_hough_transform * \brief Class that permits to detect 2D circles in a image using @@ -97,8 +101,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.*/ - float m_minRadius; /*!< Minimum radius of the circles we want to detect.*/ - float m_maxRadius; /*!< Maximum radius of the circles 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.*/ 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.*/ @@ -111,6 +115,8 @@ class VISP_EXPORT vpCircleHoughTransform float m_circlePerfectness; /*!< The threshold for the colinearity between the gradient of a point and the radius it would form with a center candidate to be able to vote. The formula to get the equivalent angle is: `angle = acos(circle_perfectness)`. */ + float m_circleVisibilityRatioThresh; /*!< Visibility ratio threshold: minimun ratio of the circle must be visible in order to keep a circle candidate.*/ + bool m_recordVotingPoints; /*!< If true, the edge-map points having voted for each circle will be stored.*/ // // Circle candidates merging attributes float m_centerMinDist; /*!< Maximum distance between two circle candidates centers to consider merging them.*/ @@ -134,14 +140,16 @@ class VISP_EXPORT vpCircleHoughTransform , m_upperCannyThreshRatio(0.8f) , 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.f) - , m_maxRadius(1000.f) + , m_minRadius(0) + , m_maxRadius(1000) , m_dilatationKernelSize(3) , m_averagingWindowSize(5) , m_centerMinThresh(50.f) , m_expectedNbCenters(-1) , m_circleProbaThresh(0.9f) , m_circlePerfectness(0.9f) + , m_circleVisibilityRatioThresh(0.1f) + , m_recordVotingPoints(false) , m_centerMinDist(15.f) , m_mergingRadiusDiffThresh(1.5f * m_centerMinDist) { @@ -164,6 +172,8 @@ class VISP_EXPORT vpCircleHoughTransform * \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] dilatationKernelSize Kernel size of the dilatation that is performed to detect the maximum number of votes for the center candidates. + * \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] 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 threshold for the colinearity between the gradient of a point @@ -171,8 +181,6 @@ class VISP_EXPORT vpCircleHoughTransform The formula to get the equivalent angle is: `angle = acos(circle_perfectness)`. * \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. * \param[in] filteringAndGradientMethod The choice of the filter and gradient operator to apply before the edge * detection step. * \param[in] backendType Permits to choose the backend used to compute the edge map. @@ -183,9 +191,11 @@ class VISP_EXPORT vpCircleHoughTransform * 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. + * \param[in] recordVotingPoints If true, the edge-map points having voted for each circle will be stored. + * \param[in] visibilityRatioThresh Visibility threshold: which minimun ratio of the circle must be visible in order to keep a circle candidate. */ vpCircleHoughTransformParameters( - const int &gaussianKernelSize + const int &gaussianKernelSize , const float &gaussianStdev , const int &gradientFilterKernelSize , const float &lowerCannyThresh @@ -193,20 +203,22 @@ class VISP_EXPORT vpCircleHoughTransform , const int &edgeMapFilterNbIter , const std::pair ¢erXlimits , const std::pair ¢erYlimits - , const float &minRadius - , const float &maxRadius + , const unsigned int &minRadius + , const unsigned int &maxRadius , const int &dilatationKernelSize + , const int &averagingWindowSize , const float ¢erThresh , const float &circleProbabilityThresh , const float &circlePerfectness , const float ¢erMinDistThresh , const float &mergingRadiusDiffThresh - , const int &averagingWindowSize = 5 , const vpImageFilter::vpCannyFilteringAndGradientType &filteringAndGradientMethod = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING , const vpImageFilter::vpCannyBackendType &backendType = vpImageFilter::CANNY_OPENCV_BACKEND , const float &lowerCannyThreshRatio = 0.6f , const float &upperCannyThreshRatio = 0.8f , const int &expectedNbCenters = -1 + , const bool &recordVotingPoints = false + , const float &visibilityRatioThresh = 0.1f ) : m_filteringAndGradientType(filteringAndGradientMethod) , m_gaussianKernelSize(gaussianKernelSize) @@ -220,14 +232,16 @@ class VISP_EXPORT vpCircleHoughTransform , m_upperCannyThreshRatio(upperCannyThreshRatio) , m_centerXlimits(centerXlimits) , m_centerYlimits(centerYlimits) - , m_minRadius(std::min(minRadius, maxRadius)) - , m_maxRadius(std::max(minRadius, maxRadius)) + , m_minRadius(std::min(minRadius, maxRadius)) + , m_maxRadius(std::max(minRadius, maxRadius)) , m_dilatationKernelSize(dilatationKernelSize) , m_averagingWindowSize(averagingWindowSize) , m_centerMinThresh(centerThresh) , m_expectedNbCenters(expectedNbCenters) , m_circleProbaThresh(circleProbabilityThresh) , m_circlePerfectness(circlePerfectness) + , m_circleVisibilityRatioThresh(visibilityRatioThresh) + , m_recordVotingPoints(recordVotingPoints) , m_centerMinDist(centerMinDistThresh) , m_mergingRadiusDiffThresh(mergingRadiusDiffThresh) { } @@ -387,6 +401,16 @@ class VISP_EXPORT vpCircleHoughTransform return m_circleProbaThresh; } + /** + * \brief Get the visibility ratio threshold in order to keep a circle candidate. + * + * \return float The threshold. + */ + inline float getVisibilityRatioThreshold() const + { + return m_circleVisibilityRatioThresh; + } + /** * \brief Get the threshold for the colinearity between the gradient of a point * and the radius it would form with a center candidate to be able to vote. @@ -399,6 +423,16 @@ class VISP_EXPORT vpCircleHoughTransform return m_circlePerfectness; } + /** + * \brief Get the boolean indicating if we have to record the edge-map points having voted for the circles. + * + * \return bool True if we have to record the voting points. + */ + inline bool getRecordVotingPoints() const + { + return m_recordVotingPoints; + } + /** * \brief Get the Maximum distance between two circle candidates centers to consider merging them. * @@ -433,15 +467,17 @@ class VISP_EXPORT vpCircleHoughTransform txt += "\tCanny edge filter thresholds = [" + std::to_string(m_lowerCannyThresh) + " ; " + std::to_string(m_upperCannyThresh) + "]\n"; txt += "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" + std::to_string(m_lowerCannyThreshRatio) + " ; " + std::to_string(m_upperCannyThreshRatio) + "]\n"; txt += "\tEdge map 8-neighbor connectivity filtering number of iterations = " + std::to_string(m_edgeMapFilteringNbIter) + "\n"; - 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 += "\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 += "\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 visibility ratio threshold = " + std::to_string(m_circleVisibilityRatioThresh) + "\n"; txt += "\tCircle perfectness threshold = " + std::to_string(m_circlePerfectness) + "\n"; + txt += "\tRecord voting points = " + (m_recordVotingPoints ? std::string("true") : std::string("false")) + "\n"; txt += "\tCenters minimum distance = " + std::to_string(m_centerMinDist) + "\n"; txt += "\tRadius difference merging threshold = " + std::to_string(m_mergingRadiusDiffThresh) + "\n"; return txt; @@ -502,7 +538,7 @@ class VISP_EXPORT vpCircleHoughTransform * \param[in] j : The JSON object, resulting from the parsing of a JSON file. * \param[out] params : The circle Hough transform parameters that will be initialized from the JSON data. */ - friend inline void from_json(const json &j, vpCircleHoughTransformParameters ¶ms) + inline friend void from_json(const json &j, vpCircleHoughTransformParameters ¶ms) { std::string filteringAndGradientName = vpImageFilter::vpCannyFilteringAndGradientTypeToString(params.m_filteringAndGradientType); filteringAndGradientName = j.value("filteringAndGradientType", filteringAndGradientName); @@ -534,25 +570,26 @@ 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)); - params.m_minRadius = std::min(radiusLimits.first, radiusLimits.second); - params.m_maxRadius = std::max(radiusLimits.first, radiusLimits.second); + 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); 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) { + 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) { + if (params.m_centerMinThresh <= 0) { throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive."); } + params.m_expectedNbCenters = j.value("expectedNbCenters", params.m_expectedNbCenters); + + params.m_circleProbaThresh = j.value("circleProbabilityThreshold", params.m_circleProbaThresh); + params.m_circleVisibilityRatioThresh = j.value("circleVisibilityRatioThreshold", params.m_circleVisibilityRatioThresh); params.m_circlePerfectness = j.value("circlePerfectnessThreshold", params.m_circlePerfectness); @@ -560,6 +597,8 @@ class VISP_EXPORT vpCircleHoughTransform throw vpException(vpException::badValue, "Circle perfectness must be in the interval ] 0; 1]."); } + params.m_recordVotingPoints = j.value("recordVotingPoints", params.m_recordVotingPoints); + params.m_centerMinDist = j.value("centerMinDistance", params.m_centerMinDist); if (params.m_centerMinDist <= 0) { throw vpException(vpException::badValue, "Centers minimum distance threshold must be positive."); @@ -577,9 +616,9 @@ class VISP_EXPORT vpCircleHoughTransform * \param[out] j : A JSON parser object. * \param[in] params : The circle Hough transform parameters that will be serialized in the json object. */ - friend inline void to_json(json &j, const vpCircleHoughTransformParameters ¶ms) + 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 { {"filteringAndGradientType", vpImageFilter::vpCannyFilteringAndGradientTypeToString(params.m_filteringAndGradientType)}, @@ -600,7 +639,9 @@ class VISP_EXPORT vpCircleHoughTransform {"centerThresh", params.m_centerMinThresh}, {"expectedNbCenters", params.m_expectedNbCenters}, {"circleProbabilityThreshold", params.m_circleProbaThresh}, + {"circleVisibilityRatioThreshold", params.m_circleVisibilityRatioThresh}, {"circlePerfectnessThreshold", params.m_circlePerfectness}, + {"recordVotingPoints", params.m_recordVotingPoints}, {"centerMinDistance", params.m_centerMinDist}, {"mergingRadiusDiffThresh", params.m_mergingRadiusDiffThresh} }; } @@ -653,6 +694,7 @@ class VISP_EXPORT vpCircleHoughTransform */ virtual std::vector detect(const vpImage &I); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) /** * \brief Perform Circle Hough Transform to detect the circles in in a gray-scale image. * Get only the \b nbCircles circles having the greatest number of votes. @@ -664,7 +706,18 @@ class VISP_EXPORT vpCircleHoughTransform * of votes detected in the image. */ virtual std::vector detect(const vpImage &I, const int &nbCircles); - //@} +#endif +//@} + + /*! + * \brief Compute the mask containing pixels that voted for the \b detections. + * \param[in] I The image for which we want to have the information. + * \param[in] detections Vector containing the list of vpImageCircle for which we want to know the voting points. + * \param[out] mask Optional mask where pixels to exclude have a value set to false. + * \param[out] opt_votingPoints Optional vector of pairs of pixel coordinates that voted for the \b detections. + */ + void computeVotingMask(const vpImage &I, const std::vector &detections, + std::optional< vpImage > &mask, std::optional>> &opt_votingPoints) const; /** @name Configuration from files */ //@{ @@ -702,7 +755,7 @@ class VISP_EXPORT vpCircleHoughTransform * \param[in] j The JSON object, resulting from the parsing of a JSON file. * \param[out] detector The detector, that will be initialized from the JSON data. */ - friend inline void from_json(const json &j, vpCircleHoughTransform &detector) + inline friend void from_json(const json &j, vpCircleHoughTransform &detector) { detector.m_algoParams = j; } @@ -713,7 +766,7 @@ class VISP_EXPORT vpCircleHoughTransform * \param[out] j A JSON parser object. * \param[in] detector The vpCircleHoughTransform that must be parsed into JSON format. */ - friend inline void to_json(json &j, const vpCircleHoughTransform &detector) + inline friend void to_json(json &j, const vpCircleHoughTransform &detector) { j = detector.m_algoParams; } @@ -849,7 +902,7 @@ class VISP_EXPORT vpCircleHoughTransform * \param[in] center_max_y : Center max location on the vertical axis, expressed in pixels. */ void setCircleCenterBoundingBox(const int ¢er_min_x, const int ¢er_max_x, - const int ¢er_min_y, const int ¢er_max_y) + const int ¢er_min_y, const int ¢er_max_y) { m_algoParams.m_centerXlimits.first = center_min_x; m_algoParams.m_centerXlimits.second = center_max_x; @@ -919,7 +972,7 @@ class VISP_EXPORT vpCircleHoughTransform throw vpException(vpException::badValue, "Votes thresholds for center detection must be positive."); } - if (m_algoParams.m_averagingWindowSize <= 0 || m_algoParams.m_averagingWindowSize % 2 == 0) { + if ((m_algoParams.m_averagingWindowSize <= 0) || ((m_algoParams.m_averagingWindowSize % 2) == 0)) { throw vpException(vpException::badValue, "Averaging window size must be positive and odd."); } } @@ -952,6 +1005,11 @@ class VISP_EXPORT vpCircleHoughTransform throw vpException(vpException::badValue, "Radius difference merging threshold must be positive."); } } + + inline void setMask(const vpImage &mask) + { + mp_mask = &mask; + } //@} /** @name Getters */ @@ -961,7 +1019,7 @@ class VISP_EXPORT vpCircleHoughTransform * * \return std::vector > The list of Center Candidates, stored as pair */ - inline std::vector > getCenterCandidatesList() + inline std::vector > getCenterCandidatesList() const { return m_centerCandidatesList; } @@ -971,7 +1029,7 @@ class VISP_EXPORT vpCircleHoughTransform * * \return std::vector The number of votes of each Center Candidates, ordered in the same way than \b m_centerCandidatesList. */ - inline std::vector getCenterCandidatesVotes() + inline std::vector getCenterCandidatesVotes() const { return m_centerVotes; } @@ -982,7 +1040,7 @@ class VISP_EXPORT vpCircleHoughTransform * \return std::vector The list of circle candidates * that were obtained before the merging step. */ - inline std::vector getCircleCandidates() + inline std::vector getCircleCandidates() const { return m_circleCandidates; } @@ -992,17 +1050,27 @@ class VISP_EXPORT vpCircleHoughTransform * * \return std::vector The votes accumulator. */ - inline std::vector getCircleCandidatesProbabilities() + inline std::vector getCircleCandidatesProbabilities() const { return m_circleCandidatesProbabilities; } + /** + * \brief Get the votes of the circle candidates. + * + * \return std::vector The votes of the circle candidates. + */ + inline std::vector getCircleCandidatesVotes() const + { + return m_circleCandidatesVotes; + } + /** * \brief Get the gradient along the horizontal axis of the image. * * \return vpImage The gradient along the horizontal axis of the image. */ - inline vpImage getGradientX() + inline vpImage getGradientX() const { return m_dIx; } @@ -1012,7 +1080,7 @@ class VISP_EXPORT vpCircleHoughTransform * * \return vpImage The gradient along the vertical axis of the image. */ - inline vpImage getGradientY() + inline vpImage getGradientY() const { return m_dIy; } @@ -1022,7 +1090,7 @@ class VISP_EXPORT vpCircleHoughTransform * * \return vpImage The edge map computed during the edge detection step. */ - inline vpImage getEdgeMap() + inline vpImage getEdgeMap() const { return m_edgeMap; } @@ -1087,16 +1155,20 @@ class VISP_EXPORT vpCircleHoughTransform */ friend VISP_EXPORT std::ostream &operator<<(std::ostream &os, const vpCircleHoughTransform &detector); + static const unsigned char edgeMapOn = 255; + static const unsigned char edgeMapOff = 0; + protected: /** - * \brief Initialize the Gaussian filters used to blur the image. + * \brief Initialize the Gaussian filters used to blur the image and + * compute the gradient images. */ virtual void initGaussianFilters(); /** * \brief Initialize the gradient filters used to compute the gradient images. */ - void initGradientFilters(); + virtual void initGradientFilters(); /** * \brief Perform Gaussian smoothing on the input image to reduce the noise @@ -1105,7 +1177,7 @@ class VISP_EXPORT vpCircleHoughTransform * * \param[in] I The input gray scale image. */ - virtual void computeGradientsAfterGaussianSmoothing(const vpImage &I); + virtual void computeGradients(const vpImage &I); /** * \brief Perform edge detection based on the computed gradients. @@ -1133,9 +1205,9 @@ class VISP_EXPORT vpCircleHoughTransform * The probability is defined as the ratio of \b nbVotes by the theoretical number of * pixel that should be visible in the image. * - * \param[in] circle The circle for which we want to evaluate the probability. - * \param[in] nbVotes The number of visible pixels of the given circle. - * \return float The probability of the circle. + * @param circle The circle for which we want to evaluate the probability. + * @param nbVotes The number of visible pixels of the given circle. + * @return float The probability of the circle. */ virtual float computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes); @@ -1163,16 +1235,17 @@ class VISP_EXPORT vpCircleHoughTransform * \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. + * \param[out] votingPoints List of edge-map points having voted of the circle candidates. */ virtual void mergeCandidates(std::vector &circleCandidates, std::vector &circleCandidatesVotes, - std::vector &circleCandidatesProba); - + std::vector &circleCandidatesProba, std::vector>> &votingPoints); vpCircleHoughTransformParameters m_algoParams; /*!< Attributes containing all the algorithm parameters.*/ // // Gaussian smoothing attributes vpArray2D m_fg; // // Gradient computation attributes + const vpImage *mp_mask; /*!< Mask that permits to avoid to compute gradients on some regions of the image.*/ vpArray2D m_gradientFilterX; /*!< Contains the coefficients of the gradient kernel along the X-axis*/ vpArray2D m_gradientFilterY; /*!< Contains the coefficients of the gradient kernel along the Y-axis*/ vpImage m_dIx; /*!< Gradient along the x-axis of the input image.*/ @@ -1183,18 +1256,20 @@ class VISP_EXPORT vpCircleHoughTransform vpImage m_edgeMap; /*!< Edge map resulting from the edge detection algorithm.*/ // // 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_edgePointsList; /*!< Vector that contains the list of edge points, to make faster some parts of the algo. They are stored as pair .*/ + std::vector > m_centerCandidatesList; /*!< Vector that contains the list of center candidates. They are stored as pair .*/ std::vector m_centerVotes; /*!< Number of votes for the center candidates that are kept.*/ // // Circle candidates computation attributes std::vector m_circleCandidates; /*!< List of the candidate circles.*/ std::vector m_circleCandidatesProbabilities; /*!< Probabilities of each candidate circle that is kept.*/ std::vector m_circleCandidatesVotes; /*!< Number of pixels voting for each candidate circle that is kept.*/ + std::vector>> m_circleCandidatesVotingPoints; /*!< Points that voted for each circle candidate.*/ // // Circle candidates merging attributes std::vector m_finalCircles; /*!< List of the final circles, i.e. the ones resulting from the merge of the circle candidates.*/ std::vector m_finalCirclesProbabilities; /*!< Probabilities of each final circle, i.e. resulting from the merge of the circle candidates.*/ std::vector m_finalCircleVotes; /*!< Number of votes for the final circles.*/ + std::vector>> m_finalCirclesVotingPoints; /*!< Points that voted for each final circle.*/ }; #endif diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index d9bd4a08b2..8d10017d0d 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -31,13 +31,13 @@ #include #include -#include #include #include vpCircleHoughTransform::vpCircleHoughTransform() : m_algoParams() + , mp_mask(nullptr) { initGaussianFilters(); initGradientFilters(); @@ -45,6 +45,7 @@ vpCircleHoughTransform::vpCircleHoughTransform() vpCircleHoughTransform::vpCircleHoughTransform(const vpCircleHoughTransformParameters &algoParams) : m_algoParams(algoParams) + , mp_mask(nullptr) { initGaussianFilters(); initGradientFilters(); @@ -88,7 +89,7 @@ vpCircleHoughTransform::initFromJSON(const std::string &jsonPath) msg << "Byte position of error: " << e.byte; throw vpException(vpException::ioError, msg.str()); } - m_algoParams = j; // Call from_json(const json& j, vpCircleHoughTransformParameters&) to read json + m_algoParams = j; // Call from_json(const json& j, vpDetectorDNN& *this) to read json file.close(); initGaussianFilters(); initGradientFilters(); @@ -99,17 +100,29 @@ vpCircleHoughTransform::saveConfigurationInJSON(const std::string &jsonPath) con { m_algoParams.saveConfigurationInJSON(jsonPath); } - #endif void vpCircleHoughTransform::initGaussianFilters() { - m_fg.resize(1, (m_algoParams.m_gaussianKernelSize + 1)/2); + const int filterHalfSize = (m_algoParams.m_gaussianKernelSize + 1) / 2; + m_fg.resize(1, filterHalfSize); vpImageFilter::getGaussianKernel(m_fg.data, m_algoParams.m_gaussianKernelSize, m_algoParams.m_gaussianStdev, true); m_cannyVisp.setGaussianFilterParameters(m_algoParams.m_gaussianKernelSize, m_algoParams.m_gaussianStdev); } +void +scaleFilter(vpArray2D &filter, const float &scale) +{ + unsigned int nbRows = filter.getRows(); + unsigned int nbCols = filter.getCols(); + for (unsigned int r = 0; r < nbRows; ++r) { + for (unsigned int c = 0; c < nbCols; ++c) { + filter[r][c] = filter[r][c] * scale; + } + } +} + void vpCircleHoughTransform::initGradientFilters() { @@ -120,25 +133,19 @@ vpCircleHoughTransform::initGradientFilters() m_gradientFilterY.resize(m_algoParams.m_gradientFilterKernelSize, m_algoParams.m_gradientFilterKernelSize); m_cannyVisp.setGradientFilterAperture(m_algoParams.m_gradientFilterKernelSize); - auto scaleFilter = [](vpArray2D &filter, const float &scale) { - for (unsigned int r = 0; r < filter.getRows(); r++) { - for (unsigned int c = 0; c < filter.getCols(); c++) { - filter[r][c] = filter[r][c] * scale; - } - }}; - float scaleX = 1.f; float scaleY = 1.f; + unsigned int filterHalfSize = (m_algoParams.m_gradientFilterKernelSize - 1) / 2; if (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { // Compute the Sobel filters - scaleX = vpImageFilter::getSobelKernelX(m_gradientFilterX.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); - scaleY = vpImageFilter::getSobelKernelY(m_gradientFilterY.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); + scaleX = vpImageFilter::getSobelKernelX(m_gradientFilterX.data, filterHalfSize); + scaleY = vpImageFilter::getSobelKernelY(m_gradientFilterY.data, filterHalfSize); } else if (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { // Compute the Scharr filters - scaleX = vpImageFilter::getScharrKernelX(m_gradientFilterX.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); - scaleY = vpImageFilter::getScharrKernelY(m_gradientFilterY.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); + scaleX = vpImageFilter::getScharrKernelX(m_gradientFilterX.data, filterHalfSize); + scaleY = vpImageFilter::getScharrKernelY(m_gradientFilterY.data, filterHalfSize); } else { std::string errMsg = "[vpCircleHoughTransform::initGradientFilters] Error: gradient filtering method \""; @@ -195,18 +202,22 @@ vpCircleHoughTransform::detect(const vpImage &I, const int &nbCir limitMin = nbDetections; } else { - limitMin = std::min(nbDetections, (size_t)nbCircles); + limitMin = std::min(nbDetections, static_cast(nbCircles)); } std::vector bestCircles; auto copyFinalCircles = m_finalCircles; auto copyFinalCirclesVotes = m_finalCircleVotes; auto copyFinalCirclesProbas = m_finalCirclesProbabilities; + auto copyFinalCirclesVotingPoints = m_finalCirclesVotingPoints; for (size_t i = 0; i < nbDetections; ++i) { size_t id = v_id_proba[i].first; m_finalCircles[i] = copyFinalCircles[id]; m_finalCircleVotes[i] = copyFinalCirclesVotes[id]; m_finalCirclesProbabilities[i] = copyFinalCirclesProbas[id]; + if (m_algoParams.m_recordVotingPoints) { + m_finalCirclesVotingPoints[i] = copyFinalCirclesVotingPoints[id]; + } if (i < limitMin) { bestCircles.push_back(m_finalCircles[i]); } @@ -231,8 +242,8 @@ vpCircleHoughTransform::detect(const vpImage &I) // 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) { + 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; } @@ -245,18 +256,18 @@ vpCircleHoughTransform::detect(const vpImage &I) // 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) { + 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) { + 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); + computeGradients(I); // Using the gradients, it is now possible to perform edge detection // We rely on the Canny edge detector @@ -281,22 +292,63 @@ vpCircleHoughTransform::detect(const vpImage &I) return m_finalCircles; } +bool +operator==(const vpImageCircle &a, const vpImageCircle &b) +{ + vpImagePoint aCenter = a.getCenter(); + vpImagePoint bCenter = b.getCenter(); + bool haveSameCenter = (std::abs(aCenter.get_u() - bCenter.get_u()) + + std::abs(aCenter.get_v() - bCenter.get_v())) <= (2. * std::numeric_limits::epsilon()); + bool haveSameRadius = std::abs(a.getRadius() - b.getRadius()) <= (2.f * std::numeric_limits::epsilon()); + return (haveSameCenter && haveSameRadius); +} + void -vpCircleHoughTransform::computeGradientsAfterGaussianSmoothing(const vpImage &I) +vpCircleHoughTransform::computeVotingMask(const vpImage &I, const std::vector &detections, std::optional< vpImage > &mask, std::optional>> &opt_votingPoints) const { - if (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING - || m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { + if (!m_algoParams.m_recordVotingPoints) { + // We weren't asked to remember the voting points + mask = std::nullopt; + opt_votingPoints = std::nullopt; + return; + } + mask = vpImage(I.getHeight(), I.getWidth(), false); + for (const auto &detection : detections) { + bool hasFoundSimilarCircle = false; + unsigned int nbPreviouslyDetected = m_finalCircles.size(); + unsigned int id = 0; + // Looking for a circle that was detected and is similar to the one given to the function + while ((id < nbPreviouslyDetected) && (!hasFoundSimilarCircle)) { + vpImageCircle previouslyDetected = m_finalCircles[id]; + if (previouslyDetected == detection) { + hasFoundSimilarCircle = true; + // We found a circle that is similar to the one given to the function => updating the mask + for (const auto &votingPoint : m_finalCirclesVotingPoints[id]) { + mask.value()[votingPoint.first][votingPoint.second] = true; + } + opt_votingPoints = m_finalCirclesVotingPoints[id]; + } + ++id; + } + } +} + +void +vpCircleHoughTransform::computeGradients(const vpImage &I) +{ + if ((m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) + || (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING)) { // Computing the Gaussian blurr vpImage Iblur, GIx; - vpImageFilter::filterX(I, GIx, m_fg.data, m_algoParams.m_gaussianKernelSize); - vpImageFilter::filterY(GIx, Iblur, m_fg.data, m_algoParams.m_gaussianKernelSize); + vpImageFilter::filterX(I, GIx, m_fg.data, m_algoParams.m_gaussianKernelSize, mp_mask); + vpImageFilter::filterY(GIx, Iblur, m_fg.data, m_algoParams.m_gaussianKernelSize, mp_mask); // Computing the gradients - vpImageFilter::filter(Iblur, m_dIx, m_gradientFilterX, true); - vpImageFilter::filter(Iblur, m_dIy, m_gradientFilterY, true); + vpImageFilter::filter(Iblur, m_dIx, m_gradientFilterX, true, mp_mask); + vpImageFilter::filter(Iblur, m_dIy, m_gradientFilterY, true, mp_mask); } else { - std::string errMsg("[computeGradientsAfterGaussianSmoothing] The filtering + gradient operators \""); + std::string errMsg("[computeGradients] The filtering + gradient operators \""); errMsg += vpImageFilter::vpCannyFilteringAndGradientTypeToString(m_algoParams.m_filteringAndGradientType); errMsg += "\" is not implemented (yet)."; throw(vpException(vpException::notImplementedError, errMsg)); @@ -313,18 +365,39 @@ vpCircleHoughTransform::edgeDetection(const vpImage &I) m_cannyVisp.setCannyThresholds(m_algoParams.m_lowerCannyThresh, m_algoParams.m_upperCannyThresh); m_cannyVisp.setCannyThresholdsRatio(m_algoParams.m_lowerCannyThreshRatio, m_algoParams.m_upperCannyThreshRatio); m_cannyVisp.setGradients(m_dIx, m_dIy); + m_cannyVisp.setMask(mp_mask); m_edgeMap = m_cannyVisp.detect(I); } else { - // We will have to recompute the gradient in the desired backend format anyway so we let - // the vpImageFilter::canny method take care of it - vpImageFilter::canny(I, m_edgeMap, m_algoParams.m_gaussianKernelSize, m_algoParams.m_lowerCannyThresh, - m_algoParams.m_upperCannyThresh, m_algoParams.m_gradientFilterKernelSize, m_algoParams.m_gaussianStdev, - m_algoParams.m_lowerCannyThreshRatio, m_algoParams.m_upperCannyThreshRatio, true, - m_algoParams.m_cannyBackendType, m_algoParams.m_filteringAndGradientType); + if (mp_mask != nullptr) { + // Delete pixels that fall outside the mask + vpImage I_masked(I); + unsigned int nbRows = I_masked.getHeight(); + unsigned int nbCols = I_masked.getWidth(); + for (unsigned int r = 0; r < nbRows; ++r) { + for (unsigned int c = 0; c < nbCols; ++c) { + if (!((*mp_mask)[r][c])) { + I_masked[r][c] = 0; + } + } + } + + // We will have to recompute the gradient in the desired backend format anyway so we let + // the vpImageFilter::canny method take care of it + vpImageFilter::canny(I_masked, m_edgeMap, m_algoParams.m_gaussianKernelSize, m_algoParams.m_lowerCannyThresh, + m_algoParams.m_upperCannyThresh, m_algoParams.m_gradientFilterKernelSize, m_algoParams.m_gaussianStdev, + m_algoParams.m_lowerCannyThreshRatio, m_algoParams.m_upperCannyThreshRatio, true, + m_algoParams.m_cannyBackendType, m_algoParams.m_filteringAndGradientType); + } + else { + vpImageFilter::canny(I, m_edgeMap, m_algoParams.m_gaussianKernelSize, m_algoParams.m_lowerCannyThresh, + m_algoParams.m_upperCannyThresh, m_algoParams.m_gradientFilterKernelSize, m_algoParams.m_gaussianStdev, + m_algoParams.m_lowerCannyThreshRatio, m_algoParams.m_upperCannyThreshRatio, true, + m_algoParams.m_cannyBackendType, m_algoParams.m_filteringAndGradientType); + } } - for (int i = 0; i < m_algoParams.m_edgeMapFilteringNbIter; i++) { + for (int i = 0; i < m_algoParams.m_edgeMapFilteringNbIter; ++i) { filterEdgeMap(); } } @@ -333,30 +406,33 @@ void vpCircleHoughTransform::filterEdgeMap() { vpImage J = m_edgeMap; + const unsigned int height = J.getHeight(); + const unsigned int width = J.getWidth(); + const int minNbContiguousPts = 2; - for (unsigned int i = 1; i < J.getHeight() - 1; i++) { - for (unsigned int j = 1; j < J.getWidth() - 1; j++) { - if (J[i][j] == 255) { + for (unsigned int i = 1; i < (height - 1); ++i) { + for (unsigned int j = 1; j < (width - 1); ++j) { + if (J[i][j] == vpCircleHoughTransform::edgeMapOn) { // Consider 8 neighbors - int topLeftPixel = (int)J[i - 1][j - 1]; - int topPixel = (int)J[i - 1][j]; - int topRightPixel = (int)J[i - 1][j + 1]; - int botLeftPixel = (int)J[i + 1][j - 1]; - int bottomPixel = (int)J[i + 1][j]; - int botRightPixel = (int)J[i + 1][j + 1]; - int leftPixel = (int)J[i][j - 1]; - int rightPixel = (int)J[i][j + 1]; + int topLeftPixel = static_cast(J[i - 1][j - 1]); + int topPixel = static_cast(J[i - 1][j]); + int topRightPixel = static_cast(J[i - 1][j + 1]); + int botLeftPixel = static_cast(J[i + 1][j - 1]); + int bottomPixel = static_cast(J[i + 1][j]); + int botRightPixel = static_cast(J[i + 1][j + 1]); + int leftPixel = static_cast(J[i][j - 1]); + int rightPixel = static_cast(J[i][j + 1]); if ((topLeftPixel + topPixel + topRightPixel + botLeftPixel + bottomPixel + botRightPixel + leftPixel + rightPixel - ) >= 2 * 255) { - // At least 2 of the 8-neighbor points are also an edge point + ) >= (minNbContiguousPts * static_cast(vpCircleHoughTransform::edgeMapOn))) { + // At least minNbContiguousPts of the 8-neighbor points are also an edge point // so we keep the edge point - m_edgeMap[i][j] = 255; + m_edgeMap[i][j] = vpCircleHoughTransform::edgeMapOn; } else { // The edge point is isolated => we erase it - m_edgeMap[i][j] = 0; + m_edgeMap[i][j] = vpCircleHoughTransform::edgeMapOff; } } } @@ -384,7 +460,7 @@ vpCircleHoughTransform::computeCenterCandidates() float minimumXpositionFloat = static_cast(minimumXposition); float maximumXpositionFloat = static_cast(maximumXposition); int offsetX = minimumXposition; - int accumulatorWidth = maximumXposition - minimumXposition + 1; + int accumulatorWidth = (maximumXposition - minimumXposition) + 1; if (accumulatorWidth <= 0) { throw(vpException(vpException::dimensionError, "[vpCircleHoughTransform::computeCenterCandidates] Accumulator width <= 0!")); } @@ -394,12 +470,12 @@ vpCircleHoughTransform::computeCenterCandidates() // The maxinum vertical position of the center is at worst +maxRadiusoutside the image // The height of the accumulator is the difference between the max and the min int minimumYposition = std::max(m_algoParams.m_centerYlimits.first, -1 * static_cast(m_algoParams.m_maxRadius)); - int maximumYposition = std::min(m_algoParams.m_centerYlimits.second, static_cast(m_algoParams.m_maxRadius + nbCols)); + int maximumYposition = std::min(m_algoParams.m_centerYlimits.second, static_cast(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; + int accumulatorHeight = (maximumYposition - minimumYposition) + 1; if (accumulatorHeight <= 0) { std::string errMsg("[vpCircleHoughTransform::computeCenterCandidates] Accumulator height <= 0!"); throw(vpException(vpException::dimensionError, errMsg)); @@ -410,10 +486,10 @@ vpCircleHoughTransform::computeCenterCandidates() const int nbDirections = 2; for (unsigned int r = 0; r < nbRows; ++r) { for (unsigned int c = 0; c < nbCols; ++c) { - if (m_edgeMap[r][c] == 255) { + if (m_edgeMap[r][c] == vpCircleHoughTransform::edgeMapOn) { // 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]); + float mag = std::sqrt((m_dIx[r][c] * m_dIx[r][c]) + (m_dIy[r][c] * m_dIy[r][c])); float sx = 0.f, sy = 0.f; if (std::abs(mag) >= std::numeric_limits::epsilon()) { @@ -427,27 +503,30 @@ vpCircleHoughTransform::computeCenterCandidates() // Saving the edge point for further use m_edgePointsList.push_back(std::pair(r, c)); + float float_minRad = static_cast(m_algoParams.m_minRadius); + float float_maxRad = static_cast(m_algoParams.m_maxRadius); + for (int k1 = 0; k1 < nbDirections; ++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; + int x_high_prev = (y_low_prev = (y_high_prev = x_low_prev)); - float rstart = m_algoParams.m_minRadius, rstop = m_algoParams.m_maxRadius; + float rstart = float_minRad, rstop = float_maxRad; float min_minus_c = minimumXpositionFloat - static_cast(c); float min_minus_r = minimumYpositionFloat - static_cast(r); float max_minus_c = maximumXpositionFloat - static_cast(c); float max_minus_r = maximumYpositionFloat - static_cast(r); if (sx > 0) { float rmin = min_minus_c / sx; - rstart = std::max(rmin, m_algoParams.m_minRadius); + rstart = std::max(rmin, float_minRad); float rmax = max_minus_c / sx; - rstop = std::min(rmax, m_algoParams.m_maxRadius); + rstop = std::min(rmax, float_maxRad); } else if (sx < 0) { float rmin = max_minus_c / sx; - rstart = std::max(rmin, m_algoParams.m_minRadius); + rstart = std::max(rmin, float_minRad); float rmax = min_minus_c / sx; - rstop = std::min(rmax, m_algoParams.m_maxRadius); + rstop = std::min(rmax, float_maxRad); } if (sy > 0) { @@ -467,12 +546,14 @@ vpCircleHoughTransform::computeCenterCandidates() 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 = static_cast(c) + rad * sx; - float y1 = static_cast(r) + rad * sy; + float rad = rstart; + while ((rad <= rstop) && (!hasToStopLoop)) { + float x1 = static_cast(c) + (rad * sx); + float y1 = static_cast(r) + (rad * sy); + rad += deltar; // Update rad that is not used below not to forget it if ((x1 < minimumXpositionFloat) || (y1 < minimumYpositionFloat) - ||(x1 > maximumXpositionFloat) || (y1 > maximumYpositionFloat)) { + || (x1 > maximumXpositionFloat) || (y1 > maximumYpositionFloat)) { continue; // It means that the center is outside the search region. } @@ -498,7 +579,7 @@ vpCircleHoughTransform::computeCenterCandidates() } if ((x_low_prev == x_low) && (x_high_prev == x_high) - && (y_low_prev == y_low) && (y_high_prev == y_high)) { + && (y_low_prev == y_low) && (y_high_prev == y_high)) { // Avoid duplicated votes to the same center candidate continue; } @@ -511,22 +592,22 @@ vpCircleHoughTransform::computeCenterCandidates() auto updateAccumulator = [](const float &x_orig, const float &y_orig, - const int &x, const int &y, - const int &offsetX, const int &offsetY, - const int &nbCols, const int &nbRows, - vpImage &accum, bool &hasToStop) { - if ((x - offsetX < 0) || - (x - offsetX >= nbCols) || - (y - offsetY < 0) || - (y - offsetY >= nbRows) - ) { - hasToStop = true; - } - else { - float dx = (x_orig - static_cast(x)); - float dy = (y_orig - static_cast(y)); - accum[y - offsetY][x - offsetX] += std::abs(dx) + std::abs(dy); - } + const int &x, const int &y, + const int &offsetX, const int &offsetY, + const int &nbCols, const int &nbRows, + vpImage &accum, bool &hasToStop) { + if (((x - offsetX) < 0) || + ((x - offsetX) >= nbCols) || + ((y - offsetY) < 0) || + ((y - offsetY) >= nbRows) + ) { + hasToStop = true; + } + else { + float dx = (x_orig - static_cast(x)); + float dy = (y_orig - static_cast(y)); + accum[y - offsetY][x - offsetX] += std::abs(dx) + std::abs(dy); + } }; updateAccumulator(x1, y1, x_low, y_low, @@ -562,20 +643,21 @@ vpCircleHoughTransform::computeCenterCandidates() int nbRowsAccum = centersAccum.getRows(); int nbVotes = -1; std::vector, float>> peak_positions_votes; - for (int y = 0; y < nbRowsAccum; ++y) { + + for (int y = 0; y < nbRowsAccum; y++) { int left = -1; - for (int x = 0; x < nbColsAccum; ++x) { + for (int x = 0; x < nbColsAccum; x++) { if ((centersAccum[y][x] >= m_algoParams.m_centerMinThresh) - && (std::fabs(centersAccum[y][x] - centerCandidatesMaxima[y][x]) < std::numeric_limits::epsilon()) - && (centersAccum[y][x] > centersAccum[y][x + 1]) - ) { + && (centersAccum[y][x] == centerCandidatesMaxima[y][x]) + && (centersAccum[y][x] > centersAccum[y][x + 1]) + ) { if (left < 0) { left = x; } - nbVotes = std::max(nbVotes, static_cast(centersAccum[y][x])); + nbVotes = std::max(nbVotes, static_cast(centersAccum[y][x])); } else if (left >= 0) { - int cx = static_cast((left + x - 1) * 0.5f); + int cx = static_cast(((left + x) - 1) * 0.5f); float sumVotes = 0.; float x_avg = 0., y_avg = 0.; int averagingWindowHalfSize = m_algoParams.m_averagingWindowSize / 2; @@ -583,8 +665,8 @@ vpCircleHoughTransform::computeCenterCandidates() int startingCol = std::max(0, cx - averagingWindowHalfSize); int endRow = std::min(accumulatorHeight, y + averagingWindowHalfSize + 1); int endCol = std::min(accumulatorWidth, cx + averagingWindowHalfSize + 1); - for (int r = startingRow; r < endRow; r++) { - for (int c = startingCol; c < endCol; c++) { + for (int r = startingRow; r < endRow; ++r) { + for (int c = startingCol; c < endCol; ++c) { sumVotes += centersAccum[r][c]; x_avg += centersAccum[r][c] * c; y_avg += centersAccum[r][c] * r; @@ -686,10 +768,11 @@ void vpCircleHoughTransform::computeCircleCandidates() { size_t nbCenterCandidates = m_centerCandidatesList.size(); - int nbBins = static_cast((m_algoParams.m_maxRadius - m_algoParams.m_minRadius + 1)/ m_algoParams.m_mergingRadiusDiffThresh); - nbBins = std::max((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.*/ + int nbBins = static_cast(((m_algoParams.m_maxRadius - m_algoParams.m_minRadius) + 1) / m_algoParams.m_mergingRadiusDiffThresh); + nbBins = std::max(static_cast(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. + std::vector>> votingPoints(nbBins); // Vectors that contain the points voting for each radius bin float rmin2 = m_algoParams.m_minRadius * m_algoParams.m_minRadius; float rmax2 = m_algoParams.m_maxRadius * m_algoParams.m_maxRadius; @@ -705,33 +788,36 @@ 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) - float rx = edgePoint.second - centerCandidate.second; + float rx = edgePoint.second - centerCandidate.second; float ry = edgePoint.first - centerCandidate.first; - float r2 = rx * rx + ry * ry; + 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]; - float grad2 = gx * gx + gy * gy; + float grad2 = (gx * gx) + (gy * gy); - float scalProd = rx * gx + ry * gy; + float scalProd = (rx * gx) + (ry * gy); 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 additional vote + // 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)); int r_bin = static_cast(std::floor((r - m_algoParams.m_minRadius) / m_algoParams.m_mergingRadiusDiffThresh)); r_bin = std::min(r_bin, nbBins - 1); - if ((r < (m_algoParams.m_minRadius + m_algoParams.m_mergingRadiusDiffThresh * 0.5f)) - || (r >(m_algoParams.m_minRadius + m_algoParams.m_mergingRadiusDiffThresh * (nbBins - 1 + 0.5f)))) { + if ((r < (m_algoParams.m_minRadius + (m_algoParams.m_mergingRadiusDiffThresh * 0.5f))) + || (r >(m_algoParams.m_minRadius + (m_algoParams.m_mergingRadiusDiffThresh * (static_cast(nbBins - 1) + 0.5f))))) { // If the radius is at the very beginning of the allowed radii or at the very end, we do not span the vote radiusAccumList[r_bin] += 1.f; radiusActualValueList[r_bin] += r; + if (m_algoParams.m_recordVotingPoints) { + votingPoints[r_bin].push_back(edgePoint); + } } else { - float midRadiusPrevBin = m_algoParams.m_minRadius + m_algoParams.m_mergingRadiusDiffThresh * (r_bin - 1.f + 0.5f); - float midRadiusCurBin = m_algoParams.m_minRadius + m_algoParams.m_mergingRadiusDiffThresh * (r_bin + 0.5f); - float midRadiusNextBin = m_algoParams.m_minRadius + m_algoParams.m_mergingRadiusDiffThresh * (r_bin + 1.f + 0.5f); + float midRadiusPrevBin = m_algoParams.m_minRadius + (m_algoParams.m_mergingRadiusDiffThresh * ((r_bin - 1.f) + 0.5f)); + float midRadiusCurBin = m_algoParams.m_minRadius + (m_algoParams.m_mergingRadiusDiffThresh * (r_bin + 0.5f)); + float midRadiusNextBin = m_algoParams.m_minRadius + (m_algoParams.m_mergingRadiusDiffThresh * (r_bin + 1.f + 0.5f)); - if (r >= midRadiusCurBin && r <= midRadiusNextBin) { + if ((r >= midRadiusCurBin) && (r <= midRadiusNextBin)) { // The radius is at the end of the current bin or beginning of the next, we span the vote with the next bin float voteCurBin = (midRadiusNextBin - r) / m_algoParams.m_mergingRadiusDiffThresh; // If the difference is big, it means that we are closer to the current bin float voteNextBin = 1.f - voteCurBin; @@ -739,6 +825,10 @@ vpCircleHoughTransform::computeCircleCandidates() radiusActualValueList[r_bin] += r * voteCurBin; radiusAccumList[r_bin + 1] += voteNextBin; radiusActualValueList[r_bin + 1] += r * voteNextBin; + if (m_algoParams.m_recordVotingPoints) { + votingPoints[r_bin].push_back(edgePoint); + votingPoints[r_bin + 1].push_back(edgePoint); + } } else { // The radius is at the end of the previous bin or beginning of the current, we span the vote with the previous bin @@ -748,12 +838,17 @@ vpCircleHoughTransform::computeCircleCandidates() radiusActualValueList[r_bin] += r * voteCurBin; radiusAccumList[r_bin - 1] += votePrevBin; radiusActualValueList[r_bin - 1] += r * votePrevBin; + if (m_algoParams.m_recordVotingPoints) { + votingPoints[r_bin].push_back(edgePoint); + votingPoints[r_bin - 1].push_back(edgePoint); + } } } } } } + // Lambda to compute the effective radius (i.e. barycenter) of each radius bin auto computeEffectiveRadius = [](const float &votes, const float &weigthedSumRadius) { float r_effective = -1.f; if (votes > std::numeric_limits::epsilon()) { @@ -762,23 +857,38 @@ vpCircleHoughTransform::computeCircleCandidates() return r_effective; }; - std::vector v_r_effective; - std::vector v_votes_effective; + // Merging similar candidates + std::vector v_r_effective; // Vector of radius of each candidate after the merge step + std::vector v_votes_effective; // Vector of number of votes of each candidate after the merge step + std::vector>> v_votingPoints_effective; // Vector of voting points after the merge step + std::vector v_hasMerged_effective; // Vector indicating if merge has been performed for the different candidates for (int idBin = 0; idBin < nbBins; ++idBin) { float r_effective = computeEffectiveRadius(radiusAccumList[idBin], radiusActualValueList[idBin]); - float effective_votes = radiusAccumList[idBin]; + float votes_effective = radiusAccumList[idBin]; + std::vector> votingPoints_effective = votingPoints[idBin]; bool is_r_effective_similar = (r_effective > 0.f); // Looking for potential similar radii in the following bins // If so, compute the barycenter radius between them int idCandidate = idBin + 1; + bool hasMerged = false; while ((idCandidate < nbBins) && is_r_effective_similar) { float r_effective_candidate = computeEffectiveRadius(radiusAccumList[idCandidate], radiusActualValueList[idCandidate]); if (std::abs(r_effective_candidate - r_effective) < m_algoParams.m_mergingRadiusDiffThresh) { - r_effective = (r_effective * effective_votes + r_effective_candidate * radiusAccumList[idCandidate]) / (effective_votes + radiusAccumList[idCandidate]); - effective_votes += radiusAccumList[idCandidate]; + r_effective = ((r_effective * votes_effective) + (r_effective_candidate * radiusAccumList[idCandidate])) / (votes_effective + radiusAccumList[idCandidate]); + votes_effective += radiusAccumList[idCandidate]; radiusAccumList[idCandidate] = -.1f; radiusActualValueList[idCandidate] = -1.f; is_r_effective_similar = true; + if (m_algoParams.m_recordVotingPoints) { + // Move elements from votingPoints[idCandidate] to votingPoints_effective. + // votingPoints[idCandidate] is left in undefined but safe-to-destruct state. + votingPoints_effective.insert( + votingPoints_effective.end(), + std::make_move_iterator(votingPoints[idCandidate].begin()), + std::make_move_iterator(votingPoints[idCandidate].end()) + ); + hasMerged = true; + } } else { is_r_effective_similar = false; @@ -786,14 +896,18 @@ vpCircleHoughTransform::computeCircleCandidates() ++idCandidate; } - if (effective_votes > m_algoParams.m_centerMinThresh) { - // Only the circles having enough votes are considered + if ((votes_effective > m_algoParams.m_centerMinThresh) && (votes_effective >= m_algoParams.m_circleVisibilityRatioThresh * 2.f * M_PIf * r_effective)) { + // Only the circles having enough votes and being visible enough are considered v_r_effective.push_back(r_effective); - v_votes_effective.push_back(effective_votes); + v_votes_effective.push_back(votes_effective); + if (m_algoParams.m_recordVotingPoints) { + v_votingPoints_effective.push_back(votingPoints_effective); + v_hasMerged_effective.push_back(hasMerged); + } } } - unsigned int nbCandidates = static_cast(v_r_effective.size()); + unsigned int nbCandidates = v_r_effective.size(); for (unsigned int idBin = 0; idBin < nbCandidates; ++idBin) { // If the circle of center CeC_i and radius RCB_k has enough votes, it is added to the list // of Circle Candidates @@ -804,6 +918,15 @@ vpCircleHoughTransform::computeCircleCandidates() m_circleCandidates.push_back(candidateCircle); m_circleCandidatesProbabilities.push_back(proba); m_circleCandidatesVotes.push_back(v_votes_effective[idBin]); + if (m_algoParams.m_recordVotingPoints) { + if (v_hasMerged_effective[idBin]) { + // Remove potential duplicated points + std::sort(v_votingPoints_effective[idBin].begin(), v_votingPoints_effective[idBin].end()); + v_votingPoints_effective[idBin].erase(std::unique(v_votingPoints_effective[idBin].begin(), v_votingPoints_effective[idBin].end()), v_votingPoints_effective[idBin].end()); + } + // Save the points + m_circleCandidatesVotingPoints.push_back(v_votingPoints_effective[idBin]); + } } } } @@ -813,13 +936,19 @@ float vpCircleHoughTransform::computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes) { float proba(0.f); - float visibleArc((float)nbVotes); - float theoreticalLenght = circle.computeArcLengthInRoI(vpRect(vpImagePoint(0, 0), m_edgeMap.getWidth(), m_edgeMap.getHeight())); + float visibleArc(static_cast(nbVotes)); + float theoreticalLenght; + if (mp_mask != nullptr) { + theoreticalLenght = circle.computePixelsInMask(*mp_mask); + } + else { + theoreticalLenght = circle.computeArcLengthInRoI(vpRect(vpImagePoint(0, 0), m_edgeMap.getWidth(), m_edgeMap.getHeight())); + } if (theoreticalLenght < std::numeric_limits::epsilon()) { proba = 0.f; } else { - proba = std::min(visibleArc / theoreticalLenght, 1.f); + proba = std::min(visibleArc / theoreticalLenght, 1.f); } return proba; } @@ -830,57 +959,81 @@ vpCircleHoughTransform::mergeCircleCandidates() std::vector circleCandidates = m_circleCandidates; std::vector circleCandidatesVotes = m_circleCandidatesVotes; std::vector circleCandidatesProba = m_circleCandidatesProbabilities; + std::vector>> circleCandidatesVotingPoints = m_circleCandidatesVotingPoints; // First iteration of merge - mergeCandidates(circleCandidates, circleCandidatesVotes, circleCandidatesProba); + mergeCandidates(circleCandidates, circleCandidatesVotes, circleCandidatesProba, circleCandidatesVotingPoints); // Second iteration of merge - mergeCandidates(circleCandidates, circleCandidatesVotes, circleCandidatesProba); + mergeCandidates(circleCandidates, circleCandidatesVotes, circleCandidatesProba, circleCandidatesVotingPoints); // Saving the results m_finalCircles = circleCandidates; m_finalCircleVotes = circleCandidatesVotes; m_finalCirclesProbabilities = circleCandidatesProba; + m_finalCirclesVotingPoints = circleCandidatesVotingPoints; } void vpCircleHoughTransform::mergeCandidates(std::vector &circleCandidates, std::vector &circleCandidatesVotes, - std::vector &circleCandidatesProba) + std::vector &circleCandidatesProba, std::vector>> &votingPoints) { size_t nbCandidates = circleCandidates.size(); - for (size_t i = 0; i < nbCandidates; ++i) { + size_t i = 0; + while (i < nbCandidates) { vpImageCircle cic_i = circleCandidates[i]; + bool hasPerformedMerge = false; // // For each other circle candidate CiC_j do: - for (size_t j = i + 1; j < nbCandidates; ++j) { + size_t j = i + 1; + while (j < nbCandidates) { vpImageCircle cic_j = circleCandidates[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) - ); + && (radiusDifference < m_algoParams.m_mergingRadiusDiffThresh) + ); if (areCirclesSimilar) { + hasPerformedMerge = true; // // // 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; + 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); circleCandidates[j] = circleCandidates[nbCandidates - 1]; - circleCandidatesVotes[i] = totalVotes / 2; + circleCandidatesVotes[i] = totalVotes / 2; // Compute the mean vote circleCandidatesVotes[j] = circleCandidatesVotes[nbCandidates - 1]; circleCandidatesProba[i] = newProba; circleCandidatesProba[j] = circleCandidatesProba[nbCandidates - 1]; + if (m_algoParams.m_recordVotingPoints) { + votingPoints[i].insert( + votingPoints[i].end(), + std::make_move_iterator(votingPoints[j].begin()), + std::make_move_iterator(votingPoints[j].end()) + ); + votingPoints.pop_back(); + } circleCandidates.pop_back(); circleCandidatesVotes.pop_back(); circleCandidatesProba.pop_back(); --nbCandidates; - --j; + // We do not update j because the new j-th candidate has not been evaluated yet + } + else { + // We will evaluate the next candidate + ++j; } } // // Add the circle candidate CiC_i, potentially merged with other circle candidates, to the final list of detected circles circleCandidates[i] = cic_i; + if (hasPerformedMerge && m_algoParams.m_recordVotingPoints) { + // Remove duplicated points + std::sort(votingPoints[i].begin(), votingPoints[i].end()); + votingPoints[i].erase(std::unique(votingPoints[i].begin(), votingPoints[i].end()), votingPoints[i].end()); + } + ++i; } } @@ -890,7 +1043,8 @@ vpCircleHoughTransform::toString() const return m_algoParams.toString(); } -std::ostream &operator<<(std::ostream &os, const vpCircleHoughTransform &detector) +std::ostream & +operator<<(std::ostream &os, const vpCircleHoughTransform &detector) { os << detector.toString(); return os; diff --git a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp index ea5368e622..53e9f20737 100644 --- a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp +++ b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp @@ -79,6 +79,8 @@ int main(int argc, char **argv) const float def_lowerCannyThreshRatio = 0.6f; const float def_upperCannyThreshRatio = 0.9f; const int def_expectedNbCenters = -1; + const bool def_recordVotingPoints = false; + const float def_visibilityRatioThresh = 0.1f; std::string opt_input(def_input); std::string opt_jsonFilePath = def_jsonFilePath; @@ -105,6 +107,8 @@ int main(int argc, char **argv) float opt_lowerCannyThreshRatio = def_lowerCannyThreshRatio; float opt_upperCannyThreshRatio = def_upperCannyThreshRatio; int opt_expectedNbCenters = def_expectedNbCenters; + bool opt_recordVotingPoints = def_recordVotingPoints; + float opt_visibilityRatioThresh = def_visibilityRatioThresh; bool opt_displayCanny = false; for (int i = 1; i < argc; i++) { @@ -375,17 +379,19 @@ int main(int argc, char **argv) , opt_minRadius , opt_maxRadius , opt_dilatationKerneSize + , opt_averagingWindowSize , opt_centerThresh , opt_circleProbaThresh , opt_circlePerfectness , opt_centerDistanceThresh , opt_radiusDifferenceThresh - , opt_averagingWindowSize , opt_filteringAndGradientType , opt_cannyBackendType , opt_lowerCannyThreshRatio , opt_upperCannyThreshRatio , opt_expectedNbCenters + , opt_recordVotingPoints + , opt_visibilityRatioThresh ); //! [Algo params] From 30339d36b80fe8697c26ba199beacb342fb7fc62 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 17 Jan 2024 16:35:01 +0100 Subject: [PATCH 02/10] [FIX] Fixed a segfault due to incorrect pointer arithmetic --- .../core/src/tools/histogram/vpHistogram.cpp | 6 +-- .../histogram-with-dataset/testHistogram.cpp | 49 +++++++++++-------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/modules/core/src/tools/histogram/vpHistogram.cpp b/modules/core/src/tools/histogram/vpHistogram.cpp index f7004a43bf..b3fffca360 100644 --- a/modules/core/src/tools/histogram/vpHistogram.cpp +++ b/modules/core/src/tools/histogram/vpHistogram.cpp @@ -94,7 +94,7 @@ vpThread::Return computeHistogramThread(vpThread::Args args) ptrMaskCurrent = (const bool *)histogram_param->m_mask->bitmap + start_index; } - if (end_index - start_index >= 8) { + if (end_index >= 8 + start_index) { // Unroll loop version for (; ptrCurrent <= ptrEnd - 8;) { if (*ptrMaskCurrent) { @@ -167,12 +167,10 @@ vpThread::Return computeHistogramThread(vpThread::Args args) if (*ptrMaskCurrent) { histogram_param->m_histogram[histogram_param->m_lut[*ptrCurrent]]++; } - ++ptrCurrent; if (histogram_param->m_mask != nullptr) { ++ptrMaskCurrent; } } - return 0; } } // namespace @@ -359,7 +357,6 @@ void vpHistogram::calculate(const vpImage &I, unsigned int nbins, else { #if defined(VISP_HAVE_PTHREAD) || (defined(_WIN32) && !defined(WINRT_8_0)) // Multi-threads - std::vector threadpool; std::vector histogramParams; @@ -377,6 +374,7 @@ void vpHistogram::calculate(const vpImage &I, unsigned int nbins, vpHistogram_Param_t *histogram_param = new vpHistogram_Param_t(start_index, end_index, &I, mp_mask); histogram_param->m_histogram = new unsigned int[m_size]; + histogram_param->m_mask = mp_mask; memset(histogram_param->m_histogram, 0, m_size * sizeof(unsigned int)); memcpy(histogram_param->m_lut, lut, 256 * sizeof(unsigned int)); diff --git a/modules/core/test/tools/histogram-with-dataset/testHistogram.cpp b/modules/core/test/tools/histogram-with-dataset/testHistogram.cpp index e3e2ec5100..7974ed449e 100644 --- a/modules/core/test/tools/histogram-with-dataset/testHistogram.cpp +++ b/modules/core/test/tools/histogram-with-dataset/testHistogram.cpp @@ -71,7 +71,7 @@ SYNOPSIS\n\ %s [-i ] [-t ]\n\ [-h]\n \ ", - name); +name); fprintf(stdout, "\n\ OPTIONS: Default\n\ @@ -183,7 +183,7 @@ bool compareHistogram(const vpImage &I, unsigned int nbBins) for (unsigned int cpt = 0; cpt < nbBins; cpt++) { if (histogram_single_threaded[cpt] != histogram_multi_threaded[cpt]) { std::cerr << "histogram_single_threaded[" << cpt << "]=" << histogram_single_threaded[cpt] - << " ; histogram_multi_threaded[" << cpt << "]=" << histogram_multi_threaded[cpt] << std::endl; + << " ; histogram_multi_threaded[" << cpt << "]=" << histogram_multi_threaded[cpt] << std::endl; return false; } @@ -231,8 +231,8 @@ int main(int argc, const char **argv) if (ipath != env_ipath) { std::cout << std::endl << "WARNING: " << std::endl; std::cout << " Since -i " - << " is different from VISP_IMAGE_PATH=" << env_ipath << std::endl - << " we skip the environment variable." << std::endl; + << " is different from VISP_IMAGE_PATH=" << env_ipath << std::endl + << " we skip the environment variable." << std::endl; } } @@ -241,9 +241,9 @@ int main(int argc, const char **argv) usage(argv[0], nullptr, ipath); std::cerr << std::endl << "ERROR:" << std::endl; std::cerr << " Use -i option or set VISP_INPUT_IMAGE_PATH " << std::endl - << " environment variable to specify the location of the " << std::endl - << " image path where test images are located." << std::endl - << std::endl; + << " environment variable to specify the location of the " << std::endl + << " image path where test images are located." << std::endl + << std::endl; return EXIT_FAILURE; } @@ -279,9 +279,9 @@ int main(int argc, const char **argv) t_multithread = vpTime::measureTimeMs() - t_multithread; std::cout << "sum_single_thread=" << sum_single_thread << " ; t_single_thread=" << t_single_thread - << " ms ; mean=" << t_single_thread / (double)nbIterations << " ms" << std::endl; + << " ms ; mean=" << t_single_thread / (double)nbIterations << " ms" << std::endl; std::cout << "sum_single_multithread=" << sum_single_multithread << " ; t_multithread=" << t_multithread - << " ms ; mean=" << t_multithread / (double)nbIterations << " ms" << std::endl; + << " ms ; mean=" << t_multithread / (double)nbIterations << " ms" << std::endl; std::cout << "Speed-up=" << t_single_thread / (double)t_multithread << "X" << std::endl; if (sum_single_thread != I.getSize() || sum_single_multithread != I.getSize()) { @@ -296,6 +296,7 @@ int main(int argc, const char **argv) } // Test histogram computation on empty image + std::cout << "Test histogram computation on empty image" << std::endl << std::flush; vpHistogram histogram; vpImage I_test(0, 0); histogram.calculate(I_test, 256, 4); @@ -303,15 +304,17 @@ int main(int argc, const char **argv) for (unsigned int cpt = 0; cpt < 256; cpt++) { if (histogram[cpt] != 0) { std::cerr << "Problem with histogram computation: histogram[" << cpt << "]=" << histogram[cpt] - << " but should be zero!" << std::endl; + << " but should be zero!" << std::endl; } } - } else { + } + else { std::cerr << "Bad histogram size!" << std::endl; return EXIT_FAILURE; } // Test histogram computation on image size < nbThreads + std::cout << "Test histogram computation on image size < nbThreads" << std::endl << std::flush; I_test.init(3, 1); I_test = 100; histogram.calculate(I_test, 256, 4); @@ -320,22 +323,25 @@ int main(int argc, const char **argv) if (cpt == 100) { if (histogram[cpt] != I_test.getSize()) { std::cerr << "Problem with histogram computation: histogram[" << cpt << "]=" << histogram[cpt] - << " but should be: " << I_test.getSize() << std::endl; + << " but should be: " << I_test.getSize() << std::endl; return EXIT_FAILURE; } - } else { + } + else { if (histogram[cpt] != 0) { std::cerr << "Problem with histogram computation: histogram[" << cpt << "]=" << histogram[cpt] - << " but should be zero!" << std::endl; + << " but should be zero!" << std::endl; } } } - } else { + } + else { std::cerr << "Bad histogram size!" << std::endl; return EXIT_FAILURE; } // Test histogram computation on small image size + std::cout << "Test histogram computation on small image size" << std::endl << std::flush; I_test.init(7, 1); I_test = 50; histogram.calculate(I_test, 256, 4); @@ -344,24 +350,27 @@ int main(int argc, const char **argv) if (cpt == 50) { if (histogram[cpt] != I_test.getSize()) { std::cerr << "Problem with histogram computation: histogram[" << cpt << "]=" << histogram[cpt] - << " but should be: " << I_test.getSize() << std::endl; + << " but should be: " << I_test.getSize() << std::endl; return EXIT_FAILURE; } - } else { + } + else { if (histogram[cpt] != 0) { std::cerr << "Problem with histogram computation: histogram[" << cpt << "]=" << histogram[cpt] - << " but should be zero!" << std::endl; + << " but should be zero!" << std::endl; } } } - } else { + } + else { std::cerr << "Bad histogram size!" << std::endl; return EXIT_FAILURE; } std::cout << "testHistogram is OK!" << std::endl; return EXIT_SUCCESS; - } catch (const vpException &e) { + } + catch (const vpException &e) { std::cerr << "Catch an exception: " << e.what() << std::endl; return EXIT_FAILURE; } From e62c7ddbf35d5eb461b524f51df2e812db9d7d0b Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 17 Jan 2024 16:40:03 +0100 Subject: [PATCH 03/10] [CORPS] Added C++-standard safeguards around optional --- .../visp3/imgproc/vpCircleHoughTransform.h | 13 +++++-- .../imgproc/src/vpCircleHoughTransform.cpp | 37 ++++++++++++++++--- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index eb4a83e5aa..7886fa53d7 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -716,11 +716,18 @@ class VISP_EXPORT vpCircleHoughTransform * \param[out] mask Optional mask where pixels to exclude have a value set to false. * \param[out] opt_votingPoints Optional vector of pairs of pixel coordinates that voted for the \b detections. */ +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) + void computeVotingMask(const vpImage &I, const std::vector &detections, + std::optional< vpImage > &mask, + std::optional>>> &opt_votingPoints) const; +#else void computeVotingMask(const vpImage &I, const std::vector &detections, - std::optional< vpImage > &mask, std::optional>> &opt_votingPoints) const; + vpImage *mask, + std::vector>> *opt_votingPoints) const; +#endif - /** @name Configuration from files */ - //@{ +/** @name Configuration from files */ +//@{ #ifdef VISP_HAVE_NLOHMANN_JSON /** * \brief Construct a new vpCircleHoughTransform object configured according to diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 8d10017d0d..a8e8cc40e1 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -303,17 +303,40 @@ operator==(const vpImageCircle &a, const vpImageCircle &b) return (haveSameCenter && haveSameRadius); } -void -vpCircleHoughTransform::computeVotingMask(const vpImage &I, const std::vector &detections, std::optional< vpImage > &mask, std::optional>> &opt_votingPoints) const +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) +void vpCircleHoughTransform::computeVotingMask(const vpImage &I, const std::vector &detections, + std::optional< vpImage > &mask, std::optional>>> &opt_votingPoints) const +#else +void vpCircleHoughTransform::computeVotingMask(const vpImage &I, const std::vector &detections, + vpImage *mask, std::vector>> *opt_votingPoints) const +#endif { if (!m_algoParams.m_recordVotingPoints) { // We weren't asked to remember the voting points +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) mask = std::nullopt; opt_votingPoints = std::nullopt; +#elif (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + mask = nullptr; + opt_votingPoints = nullptr; +#else + mask = NULL; + opt_votingPoints = NULL; +#endif return; } + +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) mask = vpImage(I.getHeight(), I.getWidth(), false); - for (const auto &detection : detections) { + opt_votingPoints = std::vector>>(); +#else + mask = new vpImage(I.getHeight(), I.getWidth(), false); + opt_votingPoints = new std::vector>>(); +#endif + + const unsigned int nbDetections = detections.size(); + for (unsigned int i = 0; i < nbDetections; ++i) { + const vpImageCircle &detection = detections[i]; bool hasFoundSimilarCircle = false; unsigned int nbPreviouslyDetected = m_finalCircles.size(); unsigned int id = 0; @@ -323,10 +346,12 @@ vpCircleHoughTransform::computeVotingMask(const vpImage &I, const if (previouslyDetected == detection) { hasFoundSimilarCircle = true; // We found a circle that is similar to the one given to the function => updating the mask - for (const auto &votingPoint : m_finalCirclesVotingPoints[id]) { - mask.value()[votingPoint.first][votingPoint.second] = true; + const unsigned int nbVotingPoints = m_finalCirclesVotingPoints[id].size(); + for (unsigned int idPoint = 0; idPoint < nbVotingPoints; ++idPoint) { + const std::pair &votingPoint = m_finalCirclesVotingPoints[id][idPoint]; + (*mask)[votingPoint.first][votingPoint.second] = true; } - opt_votingPoints = m_finalCirclesVotingPoints[id]; + opt_votingPoints->push_back(m_finalCirclesVotingPoints[id]); } ++id; } From d396f73bfdb610456e627bae7c32da2974ac407d Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 17 Jan 2024 17:23:32 +0100 Subject: [PATCH 04/10] [TUTO] Show how to get the voting points of each detected circles --- .../hough-transform/tutorial-circle-hough.cpp | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp index 53e9f20737..a8e6a3b43d 100644 --- a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp +++ b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp @@ -33,7 +33,9 @@ bool run_detection(const vpImage &I_src, vpCircleHoughTransform & std::vector v_colors = { vpColor::red, vpColor::purple, vpColor::orange, vpColor::yellow, vpColor::blue }; unsigned int idColor = 0; //! [Iterate detections] - for (auto circleCandidate : detectedCircles) { + const unsigned int nbCircle = detectedCircles.size(); + for (unsigned int idCircle = 0; idCircle < nbCircle; ++idCircle) { + const vpImageCircle &circleCandidate = detectedCircles[idCircle]; vpImageDraw::drawCircle(I_disp, circleCandidate, v_colors[idColor], 2); std::cout << "Circle #" << id << ":" << std::endl; std::cout << "\tCenter: (" << circleCandidate.getCenter() << ")" << std::endl; @@ -43,7 +45,58 @@ bool run_detection(const vpImage &I_src, vpCircleHoughTransform & id++; idColor = (idColor + 1) % v_colors.size(); } - //! [Iterate detections] +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) + std::optional> opt_mask = std::nullopt; + std::optional>>> opt_votingPoints = std::nullopt; +#elif (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + vpImage *opt_mask = nullptr; + std::vector>> *opt_votingPoints = nullptr; +#else + vpImage *opt_mask = NULL; + std::vector>> *opt_votingPoints = NULL; +#endif + detector.computeVotingMask(I_src, detectedCircles, opt_mask, opt_votingPoints); // Get, if available, the voting points +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) + if (opt_votingPoints) +#elif (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + if (opt_votingPoints != nullptr) +#else + if (opt_votingPoints != NULL) +#endif + { + const unsigned int crossSize = 3; + const unsigned int crossThickness = 1; + unsigned int nbVotedCircles = opt_votingPoints->size(); + for (unsigned int idCircle = 0; idCircle < nbVotedCircles; ++idCircle) { + // Get the voting points of a detected circle + const std::vector> &votingPoints = (*opt_votingPoints)[idCircle]; + unsigned int nbVotingPoints = votingPoints.size(); + for (unsigned int idPoint = 0; idPoint < nbVotingPoints; ++idPoint) { + // Draw the voting points + const std::pair &pt = votingPoints[idPoint]; + vpImageDraw::drawCross(I_disp, vpImagePoint(pt.first, pt.second), crossSize, vpColor::red, crossThickness); + } + } + } +#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_17) +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + if (opt_mask != nullptr) +#else + if (opt_mask != NULL) +#endif + { + delete opt_mask; + } +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + if (opt_votingPoints != nullptr) +#else + if (opt_votingPoints != NULL) +#endif + { + delete opt_votingPoints; + } +#endif +//! [Iterate detections] if (displayCanny) { vpImage edgeMap = detector.getEdgeMap(); @@ -206,6 +259,13 @@ int main(int argc, char **argv) opt_expectedNbCenters = atoi(argv[i + 1]); i++; } + else if (argName == "--visibility-ratio-thresh" && i + 1 < argc) { + opt_visibilityRatioThresh = atof(argv[i + 1]); + i++; + } + else if (argName == "--record-voting-points") { + opt_recordVotingPoints = true; + } else if (argName == "--display-edge-map") { opt_displayCanny = true; } @@ -244,6 +304,9 @@ int main(int argc, char **argv) << " (default: " << def_upperCannyThreshRatio << ")" << std::endl << "\t [--expected-nb-centers ]" << " (default: " << (def_expectedNbCenters < 0 ? "no limits" : std::to_string(def_expectedNbCenters)) << ")" << std::endl + << "\t [--visibility-ratio-thresh ]" + << " (default: " << def_visibilityRatioThresh << ")" << std::endl + << "\t [--record-voting-points]" << std::endl << "\t [--display-edge-map]" << std::endl << "\t [--help, -h]" << std::endl << std::endl; @@ -358,6 +421,15 @@ int main(int argc, char **argv) << "\t\tA negative value makes that all the centers having more votes than the threshold are kept." << std::endl << "\t\tDefault: " << (def_expectedNbCenters < 0 ? "no limits" : std::to_string(def_expectedNbCenters)) << std::endl << std::endl + << "\t--expected-nb-centers" << std::endl + << "\t\tPermit to choose the maximum number of centers having more votes than the threshold that are kept." << std::endl + << "\t\tA negative value makes that all the centers having more votes than the threshold are kept." << std::endl + << "\t\tDefault: " << (def_expectedNbCenters < 0 ? "no limits" : std::to_string(def_expectedNbCenters)) << std::endl + << std::endl + << "\t--record-voting-points" << std::endl + << "\t\tPermit to display the edge map used to detect the circles" << std::endl + << "\t\tDefault: off" << std::endl + << std::endl << "\t--display-edge-map" << std::endl << "\t\tPermit to display the edge map used to detect the circles" << std::endl << "\t\tDefault: off" << std::endl From c2028e1a42c3d1f3e701f1583177d17eb8b7a43d Mon Sep 17 00:00:00 2001 From: rlagneau Date: Fri, 19 Jan 2024 10:59:50 +0100 Subject: [PATCH 05/10] [FIX] Fix wrong memory allocation when c++17 is not available --- .../visp3/imgproc/vpCircleHoughTransform.h | 4 +- .../imgproc/src/vpCircleHoughTransform.cpp | 39 +++++++++++++------ .../hough-transform/tutorial-circle-hough.cpp | 4 +- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index 7886fa53d7..0a82b9615e 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -722,8 +722,8 @@ class VISP_EXPORT vpCircleHoughTransform std::optional>>> &opt_votingPoints) const; #else void computeVotingMask(const vpImage &I, const std::vector &detections, - vpImage *mask, - std::vector>> *opt_votingPoints) const; + vpImage **mask, + std::vector>> **opt_votingPoints) const; #endif /** @name Configuration from files */ diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index a8e8cc40e1..1d86ea8f35 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -308,7 +308,7 @@ void vpCircleHoughTransform::computeVotingMask(const vpImage &I, std::optional< vpImage > &mask, std::optional>>> &opt_votingPoints) const #else void vpCircleHoughTransform::computeVotingMask(const vpImage &I, const std::vector &detections, - vpImage *mask, std::vector>> *opt_votingPoints) const + vpImage **mask, std::vector > > **opt_votingPoints) const #endif { if (!m_algoParams.m_recordVotingPoints) { @@ -317,11 +317,11 @@ void vpCircleHoughTransform::computeVotingMask(const vpImage &I, mask = std::nullopt; opt_votingPoints = std::nullopt; #elif (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) - mask = nullptr; - opt_votingPoints = nullptr; + *mask = nullptr; + *opt_votingPoints = nullptr; #else - mask = NULL; - opt_votingPoints = NULL; + *mask = NULL; + *opt_votingPoints = NULL; #endif return; } @@ -330,28 +330,45 @@ void vpCircleHoughTransform::computeVotingMask(const vpImage &I, mask = vpImage(I.getHeight(), I.getWidth(), false); opt_votingPoints = std::vector>>(); #else - mask = new vpImage(I.getHeight(), I.getWidth(), false); - opt_votingPoints = new std::vector>>(); + *mask = new vpImage(I.getHeight(), I.getWidth(), false); + *opt_votingPoints = new std::vector > >(); #endif - const unsigned int nbDetections = detections.size(); - for (unsigned int i = 0; i < nbDetections; ++i) { - const vpImageCircle &detection = detections[i]; +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + for (const auto &detection : detections) +#else + const size_t nbDetections = detections.size(); + for (size_t i = 0; i < nbDetections; ++i) +#endif + { bool hasFoundSimilarCircle = false; unsigned int nbPreviouslyDetected = m_finalCircles.size(); unsigned int id = 0; // Looking for a circle that was detected and is similar to the one given to the function while ((id < nbPreviouslyDetected) && (!hasFoundSimilarCircle)) { vpImageCircle previouslyDetected = m_finalCircles[id]; - if (previouslyDetected == detection) { +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + if (previouslyDetected == detection) +#else + if (previouslyDetected == detections[i]) +#endif + { hasFoundSimilarCircle = true; // We found a circle that is similar to the one given to the function => updating the mask const unsigned int nbVotingPoints = m_finalCirclesVotingPoints[id].size(); for (unsigned int idPoint = 0; idPoint < nbVotingPoints; ++idPoint) { const std::pair &votingPoint = m_finalCirclesVotingPoints[id][idPoint]; +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) (*mask)[votingPoint.first][votingPoint.second] = true; +#else + (**mask)[votingPoint.first][votingPoint.second] = true; +#endif } +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) opt_votingPoints->push_back(m_finalCirclesVotingPoints[id]); +#else + (**opt_votingPoints).push_back(m_finalCirclesVotingPoints[id]); +#endif } ++id; } diff --git a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp index a8e6a3b43d..0b64f3c3d0 100644 --- a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp +++ b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp @@ -51,11 +51,13 @@ bool run_detection(const vpImage &I_src, vpCircleHoughTransform & #elif (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) vpImage *opt_mask = nullptr; std::vector>> *opt_votingPoints = nullptr; + detector.computeVotingMask(I_src, detectedCircles, opt_mask, opt_votingPoints); // Get, if available, the voting points #else vpImage *opt_mask = NULL; std::vector>> *opt_votingPoints = NULL; + detector.computeVotingMask(I_src, detectedCircles, &opt_mask, &opt_votingPoints); // Get, if available, the voting points #endif - detector.computeVotingMask(I_src, detectedCircles, opt_mask, opt_votingPoints); // Get, if available, the voting points + #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) if (opt_votingPoints) #elif (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) From 41c2656a1d2d5762fb3a47caec54f7acb9f946c1 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Mon, 22 Jan 2024 14:15:21 +0100 Subject: [PATCH 06/10] [CORPS] Protected lambda by preprocessor check of VISP_CXX_STANDARD --- modules/core/src/image/vpImageCircle.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/modules/core/src/image/vpImageCircle.cpp b/modules/core/src/image/vpImageCircle.cpp index 469fe32a1e..02dfad2e66 100644 --- a/modules/core/src/image/vpImageCircle.cpp +++ b/modules/core/src/image/vpImageCircle.cpp @@ -1022,6 +1022,25 @@ float vpImageCircle::computeArcLengthInRoI(const vpRect &roi, const float &round return delta_theta * m_radius; } +#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98) +namespace +{ +// Increment the counter if the considered pixel (x, y) is in the mask image +void incrementIfIsInMask(const vpImage &mask, const int &width, const int &height, const int &x, const int &y, + unsigned int &count) +{ + if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) { + // The pixel is outside the limit of the mask + return; + } + if (mask[y][x]) { + // Increment only if the pixel value of the mask is true + count++; + } +} +}; +#endif + unsigned int vpImageCircle::computePixelsInMask(const vpImage &mask) const { const int xm = m_center.get_u(), ym = m_center.get_v(); @@ -1029,6 +1048,7 @@ unsigned int vpImageCircle::computePixelsInMask(const vpImage &mask) const const int width = mask.getWidth(); const int height = mask.getHeight(); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) // Increment the counter if the considered pixel (x, y) is in the mask image auto incrementIfIsInMask = [](const vpImage &mask, const int &width, const int &height, const int &x, const int &y, unsigned int &count) { @@ -1041,6 +1061,7 @@ unsigned int vpImageCircle::computePixelsInMask(const vpImage &mask) const count++; } }; +#endif unsigned int count = 0; // Count the number of pixels of the circle whose value in the mask is true const float thetaStop = M_PI_2f; From 5cb953f9bf2ed82800aac4361c20fbf33dd99492 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Mon, 22 Jan 2024 14:31:14 +0100 Subject: [PATCH 07/10] [FIX] Fixed c++98 flag and detection of std::round --- cmake/VISPDetectCXXStandard.cmake | 6 +++++- cmake/VISPFindUtils.cmake | 4 +++- cmake/templates/vpConfig.h.in | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cmake/VISPDetectCXXStandard.cmake b/cmake/VISPDetectCXXStandard.cmake index 3057f0dc6e..5a32ea01b4 100644 --- a/cmake/VISPDetectCXXStandard.cmake +++ b/cmake/VISPDetectCXXStandard.cmake @@ -5,7 +5,11 @@ set(VISP_CXX_STANDARD_17 201703L) if(DEFINED USE_CXX_STANDARD) - if(USE_CXX_STANDARD STREQUAL "11") + if(USE_CXX_STANDARD STREQUAL "98") + set(CMAKE_CXX_STANDARD 98) + set(VISP_CXX_STANDARD ${VISP_CXX_STANDARD_98}) + set(CXX98_CXX_FLAGS "-std=c++98" CACHE STRING "C++ compiler flags for C++98 support") + elseif(USE_CXX_STANDARD STREQUAL "11") set(CMAKE_CXX_STANDARD 11) set(VISP_CXX_STANDARD ${VISP_CXX_STANDARD_11}) vp_check_compiler_flag(CXX "-std=c++11" HAVE_STD_CXX11_FLAG "${PROJECT_SOURCE_DIR}/cmake/checks/cxx11.cpp") diff --git a/cmake/VISPFindUtils.cmake b/cmake/VISPFindUtils.cmake index 3b6123a642..9cfbdbdf24 100644 --- a/cmake/VISPFindUtils.cmake +++ b/cmake/VISPFindUtils.cmake @@ -38,7 +38,9 @@ include(CheckCXXSourceCompiles) macro(check_math_expr _expr _header _var) unset(${_var} CACHE) # Since check_cxx_source_compiles() doesn't consider CXX_STANDARD we add the corresponding flag manually - if((VISP_CXX_STANDARD EQUAL VISP_CXX_STANDARD_11) AND CXX11_CXX_FLAGS) + if((VISP_CXX_STANDARD EQUAL VISP_CXX_STANDARD_98) AND CXX98_CXX_FLAGS) + set(CMAKE_REQUIRED_FLAGS ${CXX98_CXX_FLAGS}) + elseif((VISP_CXX_STANDARD EQUAL VISP_CXX_STANDARD_11) AND CXX11_CXX_FLAGS) set(CMAKE_REQUIRED_FLAGS ${CXX11_CXX_FLAGS}) elseif((VISP_CXX_STANDARD EQUAL VISP_CXX_STANDARD_14) AND CXX14_CXX_FLAGS) set(CMAKE_REQUIRED_FLAGS ${CXX14_CXX_FLAGS}) diff --git a/cmake/templates/vpConfig.h.in b/cmake/templates/vpConfig.h.in index 78f4df6082..b54501a9bb 100644 --- a/cmake/templates/vpConfig.h.in +++ b/cmake/templates/vpConfig.h.in @@ -497,7 +497,7 @@ #cmakedefine VISP_HAVE_NLOHMANN_JSON // Define c++ standard values also available in __cplusplus when gcc is used -#define VISP_CXX_STANDARD_98 ${VISP_CXX_STANDARD_98} # Keep for compat with previous releases +#define VISP_CXX_STANDARD_98 ${VISP_CXX_STANDARD_98} #define VISP_CXX_STANDARD_11 ${VISP_CXX_STANDARD_11} #define VISP_CXX_STANDARD_14 ${VISP_CXX_STANDARD_14} #define VISP_CXX_STANDARD_17 ${VISP_CXX_STANDARD_17} From 9fb1743b0625b4e98fc0d42ad176b2b94c1bf2b9 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Mon, 22 Jan 2024 15:11:47 +0100 Subject: [PATCH 08/10] [CLEAN] Made c++98-complinat some files --- .../core/include/visp3/core/vpImageFilter.h | 177 ++++++++++++++---- modules/core/src/image/vpImageCircle.cpp | 8 +- modules/core/src/image/vpImageFilter.cpp | 82 ++++---- 3 files changed, 183 insertions(+), 84 deletions(-) diff --git a/modules/core/include/visp3/core/vpImageFilter.h b/modules/core/include/visp3/core/vpImageFilter.h index ca87b4a1c9..c1b46ed5da 100644 --- a/modules/core/include/visp3/core/vpImageFilter.h +++ b/modules/core/include/visp3/core/vpImageFilter.h @@ -44,6 +44,7 @@ #include #include +#include #include #include #include @@ -58,13 +59,31 @@ #include #endif - /*! - * \class vpImageFilter - * - * \ingroup group_core_image - * - * \brief Various image filter, convolution, etc... - */ +#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98) +namespace +{ +// Helper to apply the scale to the raw values of the filters +template +void scaleFilter(vpArray2D &filter, const float &scale) +{ + const unsigned int nbRows = filter.getRows(); + const unsigned int nbCols = filter.getCols(); + for (unsigned int r = 0; r < nbRows; ++r) { + for (unsigned int c = 0; c < nbCols; ++c) { + filter[r][c] = filter[r][c] * scale; + } + } +} +}; +#endif + +/*! + * \class vpImageFilter + * + * \ingroup group_core_image + * + * \brief Various image filter, convolution, etc... + */ class VISP_EXPORT vpImageFilter { private: @@ -91,6 +110,29 @@ class VISP_EXPORT vpImageFilter } } +/** + * \brief Indicates if the boolean mask is true at the desired coordinates. + * + * \param[in] p_mask Pointer towards the boolean mask if any or nullptr. + * \param[in] r The row index in the boolean mask. + * \param[in] c The column index in the boolean mask. + * \return true If the boolean mask is true at the desired coordinates or if \b p_mask is equal to \b nullptr. + * \return false False otherwise. + */ + static bool checkBooleanMask(const vpImage *p_mask, const unsigned int &r, const unsigned int &c) + { + bool computeVal = true; +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + if (p_mask != nullptr) +#else + if (p_mask != NULL) +#endif + { + computeVal = (*p_mask)[r][c]; + } + return computeVal; + } + public: //! Canny filter backends for the edge detection operations typedef enum vpCannyBackendType @@ -209,6 +251,7 @@ class VISP_EXPORT vpImageFilter vpArray2D gradientFilterX(apertureGradient, apertureGradient); // Gradient filter along the X-axis vpArray2D gradientFilterY(apertureGradient, apertureGradient); // Gradient filter along the Y-axis +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) // Helper to apply the scale to the raw values of the filters auto scaleFilter = [](vpArray2D &filter, const float &scale) { const unsigned int nbRows = filter.getRows(); @@ -218,6 +261,7 @@ class VISP_EXPORT vpImageFilter filter[r][c] = filter[r][c] * scale; } }}; +#endif // Scales to apply to the filters to get a normalized gradient filter that gives a gradient // between 0 and 255 for an vpImage @@ -269,6 +313,7 @@ class VISP_EXPORT vpImageFilter } } +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) template inline static void computePartialDerivatives(const vpImage &I, vpImage &dIx, vpImage &dIy, @@ -295,6 +340,34 @@ class VISP_EXPORT vpImageFilter const unsigned int apertureGradient = 3, const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr) = delete; +#else + template + inline static void computePartialDerivatives(const vpImage &I, + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const FilterType &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr); + + template + inline static void computePartialDerivatives(const vpImage &I, + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const unsigned char &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr); + + template + inline static void computePartialDerivatives(const vpImage &I, + vpImage &dIx, vpImage &dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int gaussianKernelSize = 5, const vpRGBa gaussianStdev = vpRGBa(), + const unsigned int apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, + const vpCannyBackendType &backend = CANNY_VISP_BACKEND, const vpImage *p_mask = nullptr); +#endif /** * \brief Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel or + Scharr operators to compute @@ -323,8 +396,7 @@ class VISP_EXPORT vpImageFilter const OutType &gaussianStdev = 2.f, const unsigned int &apertureGradient = 3, const float &lowerThresholdRatio = 0.6, const float &upperThresholdRatio = 0.8, const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING, - const vpImage *p_mask = nullptr - ) + const vpImage *p_mask = nullptr) { const unsigned int w = I.getWidth(); const unsigned int h = I.getHeight(); @@ -345,12 +417,13 @@ class VISP_EXPORT vpImageFilter for (unsigned int c = 0; c < w; ++c) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[r][c]); + bool computeVal = checkBooleanMask(p_mask, r, c); + if (computeVal) { float dx = static_cast(dIx[r][c]); float dy = static_cast(dIy[r][c]); float gradient = std::abs(dx) + std::abs(dy); - float gradientClamped = std::min(gradient, static_cast(std::numeric_limits::max())); + float gradientClamped = std::min(gradient, static_cast(std::numeric_limits::max())); dI[r][c] = static_cast(gradientClamped); } } @@ -366,14 +439,14 @@ class VISP_EXPORT vpImageFilter float t = upperThresholdRatio * totalNbPixels; float bon = 0; for (unsigned int i = 0; i < nbBins; ++i) { - float tf = hist[i]; + float tf = static_cast(hist[i]); accu = accu + tf; if (accu > t) { bon = (float)i; break; } } - float upperThresh = std::max(bon, 1.f); + float upperThresh = std::max(bon, 1.f); lowerThresh = lowerThresholdRatio * bon; return upperThresh; } @@ -476,16 +549,16 @@ class VISP_EXPORT vpImageFilter \sum_{x=0}^{\textbf{kernel\_w}} \textbf{M} \left( x,y \right ) \times \textbf{I} \left( - u-\frac{\textbf{kernel\_w}}{2}+x,v-\frac{\textbf{kernel\_h}}{2}+y \right) + u-\frac{\textbf{kernel\_w}}{2}+x,v-\frac{\textbf{kernel\_h}}{2}+y \right) \f] The convolution is almost the same operation: \f[ \textbf{I\_filtered} \left( u,v \right) = \sum_{y=0}^{\textbf{kernel\_h}} \sum_{x=0}^{\textbf{kernel\_w}} - \textbf{M} \left( x,y \right ) \times + \textbf{M} \left( x,y \right ) \times \textbf{I} \left( - u+\frac{\textbf{kernel\_w}}{2}-x,v+\frac{\textbf{kernel\_h}}{2}-y \right) + u+\frac{\textbf{kernel\_w}}{2}-x,v+\frac{\textbf{kernel\_h}}{2}-y \right) \f] Only pixels in the input image fully covered by the kernel are considered. */ @@ -506,7 +579,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = half_size_x; j < stopWidth; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { FilterType conv = 0; @@ -528,7 +601,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = half_size_x; j < stopWidth; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { FilterType corr = 0; @@ -545,8 +618,13 @@ class VISP_EXPORT vpImageFilter } } +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) template static void filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve = false) = delete; +#else + template + static void filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve = false); +#endif /*! * Apply a filter to an image: @@ -579,7 +657,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int u = half_size; u < stopU; ++u) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[v][u]); + bool computeVal = checkBooleanMask(p_mask, v, u); if (computeVal) { FilterType conv_u = 0; FilterType conv_v = 0; @@ -602,7 +680,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int u = half_size; u < stopU; ++u) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[v][u]); + bool computeVal = checkBooleanMask(p_mask, v, u); if (computeVal) { FilterType conv_u = 0; @@ -623,11 +701,19 @@ class VISP_EXPORT vpImageFilter } } +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) template static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve) = delete; template static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve) = delete; +#else + template + static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve); + + template + static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve); +#endif static void sepFilter(const vpImage &I, vpImage &If, const vpColVector &kernelH, const vpColVector &kernelV); @@ -672,7 +758,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = 0; j < stop1J; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIx[i][j] = vpImageFilter::filterXLeftBorder(I, i, j, filter, size); } @@ -680,7 +766,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = stop1J; j < stop2J; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIx[i][j] = vpImageFilter::filterX(I, i, j, filter, size); } @@ -688,7 +774,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = stop2J; j < width; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIx[i][j] = vpImageFilter::filterXRightBorder(I, i, j, filter, size); } @@ -697,9 +783,12 @@ class VISP_EXPORT vpImageFilter } static void filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size, const vpImage *p_mask = nullptr); + +#ifdef DOXYGEN_SHOULD_SKIP_THIS static void filterXR(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); static void filterXG(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); static void filterXB(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); +#endif template static inline FilterType filterX(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) @@ -713,6 +802,7 @@ class VISP_EXPORT vpImageFilter return result + filter[0] * static_cast(I[r][c]); } +#ifndef DOXYGEN_SHOULD_SKIP_THIS static inline double filterXR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { const unsigned int stop = (size - 1) / 2; @@ -886,11 +976,16 @@ class VISP_EXPORT vpImageFilter } return result + filter[0] * static_cast(I[r][c].B); } +#endif + static void filterY(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size, const vpImage *p_mask = nullptr); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS static void filterYR(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); static void filterYG(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); static void filterYB(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); +#endif template static void filterY(const vpImage &I, vpImage &dIy, const FilterType *filter, unsigned int size, @@ -905,7 +1000,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = 0; j < width; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j] = vpImageFilter::filterYTopBorder(I, i, j, filter, size); } @@ -915,7 +1010,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = 0; j < width; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j] = vpImageFilter::filterY(I, i, j, filter, size); } @@ -925,7 +1020,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = 0; j < width; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j] = vpImageFilter::filterYBottomBorder(I, i, j, filter, size); } @@ -944,7 +1039,7 @@ class VISP_EXPORT vpImageFilter } return result + filter[0] * static_cast(I[r][c]); } - +#ifndef DOXYGEN_SHOULD_SKIP_THIS static inline double filterYR(const vpImage &I, unsigned int r, unsigned int c, const double *filter, unsigned int size) { const unsigned int stop = (size - 1) / 2; @@ -1115,6 +1210,8 @@ class VISP_EXPORT vpImageFilter } return result + filter[0] * static_cast(I[r][c].B); } +#endif + /*! * Apply a Gaussian blur to an image. @@ -1282,7 +1379,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = 3; j < stopJ; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIx[i][j] = static_cast(vpImageFilter::derivativeFilterX(I, i, j)); } @@ -1316,7 +1413,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = stop1J; j < stop2J; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIx[i][j] = vpImageFilter::derivativeFilterX(I, i, j, filter, size); } @@ -1363,7 +1460,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = 0; j < width; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j] = static_cast(0); } @@ -1373,7 +1470,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = 0; j < width; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j] = static_cast(vpImageFilter::derivativeFilterY(I, i, j)); } @@ -1383,7 +1480,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = 0; j < width; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j] = static_cast(0); } @@ -1403,7 +1500,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = 0; j < width; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j] = static_cast(0); } @@ -1413,7 +1510,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = 0; j < width; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j] = vpImageFilter::derivativeFilterY(I, i, j, filter, size); } @@ -1423,7 +1520,7 @@ class VISP_EXPORT vpImageFilter for (unsigned int j = 0; j < width; ++j) { // We have to compute the value for each pixel if we don't have a mask or for // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j] = static_cast(0); } @@ -1463,8 +1560,9 @@ class VISP_EXPORT vpImageFilter { if (size != 1) { // Size = 1 => kernel_size = 2*1 + 1 = 3 - std::string errMsg = "Cannot get Scharr kernel of size " + std::to_string(size * 2 + 1) + " != 3"; - throw vpException(vpException::dimensionError, errMsg); + std::stringstream errMsg; + errMsg << "Cannot get Scharr kernel of size " << size * 2 + 1 << " != 3"; + throw vpException(vpException::dimensionError, errMsg.str()); } vpArray2D ScharrY(size * 2 + 1, size * 2 + 1); @@ -1488,8 +1586,9 @@ class VISP_EXPORT vpImageFilter if (size != 1) { // Size = 1 => kernel_size = 2*1 + 1 = 3 - std::string errMsg = "Cannot get Scharr kernel of size " + std::to_string(size * 2 + 1) + " != 3"; - throw vpException(vpException::dimensionError, errMsg); + std::stringstream errMsg; + errMsg << "Cannot get Scharr kernel of size " << size * 2 + 1 << " != 3"; + throw vpException(vpException::dimensionError, errMsg.str()); } const unsigned int kernel_size = size * 2 + 1; diff --git a/modules/core/src/image/vpImageCircle.cpp b/modules/core/src/image/vpImageCircle.cpp index 02dfad2e66..90304b93eb 100644 --- a/modules/core/src/image/vpImageCircle.cpp +++ b/modules/core/src/image/vpImageCircle.cpp @@ -1097,19 +1097,19 @@ unsigned int vpImageCircle::computePixelsInMask(const vpImage &mask) const float dthetaPos = 0.f; if ((sin_theta < 0.f) && (cos_theta > 0.f)) { // dTheta <= -1/r sin(theta) && dTheta <= 1/r cos(theta) - dthetaPos = std::min(dthetaCosPos, dthetaSinNeg); + dthetaPos = std::min(dthetaCosPos, dthetaSinNeg); } else if ((sin_theta > 0.f) && (cos_theta < 0.f)) { // dTheta <= 1/r sin(theta) && dTheta <= -1/r cos(theta) - dthetaPos = std::min(dthetaCosNeg, dthetaSinPos); + dthetaPos = std::min(dthetaCosNeg, dthetaSinPos); } else if ((sin_theta < 0.f) && (cos_theta < 0.f)) { // dTheta <= -1/r sin(theta) && dTheta <= -1/r cos(theta) - dthetaPos = std::min(dthetaCosNeg, dthetaSinNeg); + dthetaPos = std::min(dthetaCosNeg, dthetaSinNeg); } else if ((sin_theta > 0.f) && (cos_theta > 0.f)) { // dTheta <= 1/r sin(theta) && dTheta <= 1/r cos(theta) - dthetaPos = std::min(dthetaCosPos, dthetaSinPos); + dthetaPos = std::min(dthetaCosPos, dthetaSinPos); } else if (sin_theta == 0.f && cos_theta !=0.f) { // dTheta = -1 / r cos(theta) || dTheta = 1 / r cos(theta) diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index c44bca6b64..fffa9016d0 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -48,12 +48,12 @@ std::string vpImageFilter::vpCannyBackendTypeList(const std::string &pref, const const std::string &suf) { std::string list(pref); - for (unsigned int i = 0; i < (vpCannyBackendType::CANNY_COUNT_BACKEND - 1); ++i) { + for (unsigned int i = 0; i < (CANNY_COUNT_BACKEND - 1); ++i) { vpCannyBackendType type = static_cast(i); list += vpCannyBackendTypeToString(type); list += sep; } - vpCannyBackendType type = static_cast(vpCannyBackendType::CANNY_COUNT_BACKEND - 1); + vpCannyBackendType type = static_cast(CANNY_COUNT_BACKEND - 1); list += vpCannyBackendTypeToString(type); list += suf; return list; @@ -69,13 +69,13 @@ std::string vpImageFilter::vpCannyBackendTypeToString(const vpImageFilter::vpCan { std::string name; switch (type) { - case vpCannyBackendType::CANNY_OPENCV_BACKEND: + case CANNY_OPENCV_BACKEND: name = "opencv-backend"; break; - case vpCannyBackendType::CANNY_VISP_BACKEND: + case CANNY_VISP_BACKEND: name = "visp-backend"; break; - case vpCannyBackendType::CANNY_COUNT_BACKEND: + case CANNY_COUNT_BACKEND: default: return "unknown-backend"; } @@ -90,9 +90,9 @@ std::string vpImageFilter::vpCannyBackendTypeToString(const vpImageFilter::vpCan */ vpImageFilter::vpCannyBackendType vpImageFilter::vpCannyBackendTypeFromString(const std::string &name) { - vpCannyBackendType type(vpCannyBackendType::CANNY_COUNT_BACKEND); + vpCannyBackendType type(CANNY_COUNT_BACKEND); std::string nameLowerCase = vpIoTools::toLowerCase(name); - unsigned int count = static_cast(vpCannyBackendType::CANNY_COUNT_BACKEND); + unsigned int count = static_cast(CANNY_COUNT_BACKEND); bool notFound = true; unsigned int i = 0; while ((i < count) && notFound) { @@ -118,7 +118,7 @@ std::string vpImageFilter::vpCannyFilteringAndGradientTypeList(const std::string const std::string &suf) { std::string list(pref); - for (unsigned int i = 0; i < (vpCannyFilteringAndGradientType::CANNY_COUNT_FILTERING - 1); ++i) { + for (unsigned int i = 0; i < (CANNY_COUNT_FILTERING - 1); ++i) { vpCannyFilteringAndGradientType type = static_cast(i); list += vpCannyFilteringAndGradientTypeToString(type); list += sep; @@ -139,13 +139,13 @@ std::string vpImageFilter::vpCannyFilteringAndGradientTypeToString(const vpImage { std::string name; switch (type) { - case vpCannyFilteringAndGradientType::CANNY_GBLUR_SOBEL_FILTERING: + case CANNY_GBLUR_SOBEL_FILTERING: name = "gaussianblur+sobel-filtering"; break; - case vpCannyFilteringAndGradientType::CANNY_GBLUR_SCHARR_FILTERING: + case CANNY_GBLUR_SCHARR_FILTERING: name = "gaussianblur+scharr-filtering"; break; - case vpCannyFilteringAndGradientType::CANNY_COUNT_FILTERING: + case CANNY_COUNT_FILTERING: default: return "unknown-filtering"; } @@ -160,9 +160,9 @@ std::string vpImageFilter::vpCannyFilteringAndGradientTypeToString(const vpImage */ vpImageFilter::vpCannyFilteringAndGradientType vpImageFilter::vpCannyFilteringAndGradientTypeFromString(const std::string &name) { - vpCannyFilteringAndGradientType type(vpCannyFilteringAndGradientType::CANNY_COUNT_FILTERING); + vpCannyFilteringAndGradientType type(CANNY_COUNT_FILTERING); std::string nameLowerCase = vpIoTools::toLowerCase(name); - unsigned int count = static_cast(vpCannyFilteringAndGradientType::CANNY_COUNT_FILTERING); + unsigned int count = static_cast(CANNY_COUNT_FILTERING); bool notFound = true; unsigned int i = 0; while ((i < count) && notFound) { @@ -325,8 +325,8 @@ void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, cons for (unsigned int i = 0; i < heightI; i++) { for (unsigned int j = 0; j < stop1J; ++j) { // We have to compute the value for each pixel if we don't have a mask or for - // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIx[i][j].R = static_cast(vpImageFilter::filterXLeftBorderR(I, i, j, filter, size)); dIx[i][j].G = static_cast(vpImageFilter::filterXLeftBorderG(I, i, j, filter, size)); @@ -335,8 +335,8 @@ void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, cons } for (unsigned int j = stop1J; j < stop2J; ++j) { // We have to compute the value for each pixel if we don't have a mask or for - // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIx[i][j].R = static_cast(vpImageFilter::filterXR(I, i, j, filter, size)); dIx[i][j].G = static_cast(vpImageFilter::filterXG(I, i, j, filter, size)); @@ -345,8 +345,8 @@ void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, cons } for (unsigned int j = stop2J; j < widthI; ++j) { // We have to compute the value for each pixel if we don't have a mask or for - // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIx[i][j].R = static_cast(vpImageFilter::filterXRightBorderR(I, i, j, filter, size)); dIx[i][j].G = static_cast(vpImageFilter::filterXRightBorderG(I, i, j, filter, size)); @@ -387,8 +387,8 @@ void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, cons for (unsigned int i = 0; i < stop1I; ++i) { for (unsigned int j = 0; j < widthI; ++j) { // We have to compute the value for each pixel if we don't have a mask or for - // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j].R = static_cast(vpImageFilter::filterYTopBorderR(I, i, j, filter, size)); dIy[i][j].G = static_cast(vpImageFilter::filterYTopBorderG(I, i, j, filter, size)); @@ -399,8 +399,8 @@ void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, cons for (unsigned int i = stop1I; i < stop2I; ++i) { for (unsigned int j = 0; j < widthI; ++j) { // We have to compute the value for each pixel if we don't have a mask or for - // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j].R = static_cast(vpImageFilter::filterYR(I, i, j, filter, size)); dIy[i][j].G = static_cast(vpImageFilter::filterYG(I, i, j, filter, size)); @@ -411,8 +411,8 @@ void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, cons for (unsigned int i = stop2I; i < heightI; ++i) { for (unsigned int j = 0; j < widthI; ++j) { // We have to compute the value for each pixel if we don't have a mask or for - // pixels for which the mask is true otherwise - bool computeVal = ((p_mask == nullptr) ? true : (*p_mask)[i][j]); + // pixels for which the mask is true otherwise + bool computeVal = checkBooleanMask(p_mask, i, j); if (computeVal) { dIy[i][j].R = static_cast(vpImageFilter::filterYBottomBorderR(I, i, j, filter, size)); dIy[i][j].G = static_cast(vpImageFilter::filterYBottomBorderG(I, i, j, filter, size)); @@ -438,20 +438,20 @@ template void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize, const vpImage *p_mask); /** * \endcond -*/ - -/*! - Apply a Gaussian blur to RGB color image. - \param[in] I : Input image. - \param[out] GI : Filtered image. - \param[in] size : Filter size. This value should be odd. - \param[in] sigma : Gaussian standard deviation. If it is equal to zero or - negative, it is computed from filter size as sigma = (size-1)/6. - \param[in] normalize : Flag indicating whether to normalize the filter coefficients or not. - \param[in] p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). - - \sa getGaussianKernel() to know which kernel is used. */ + + /*! + Apply a Gaussian blur to RGB color image. + \param[in] I : Input image. + \param[out] GI : Filtered image. + \param[in] size : Filter size. This value should be odd. + \param[in] sigma : Gaussian standard deviation. If it is equal to zero or + negative, it is computed from filter size as sigma = (size-1)/6. + \param[in] normalize : Flag indicating whether to normalize the filter coefficients or not. + \param[in] p_mask : If different from nullptr, mask indicating which points to consider (true) or to ignore(false). + + \sa getGaussianKernel() to know which kernel is used. + */ void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize, const vpImage *p_mask) { @@ -545,9 +545,9 @@ void vpImageFilter::getGradYGauss2D(const vpImage &I, vp const double *gaussianDerivativeKernel, unsigned int size, const vpImage *p_mask); /** * \endcond -*/ + */ -// Operation for Gaussian pyramid + // Operation for Gaussian pyramid void vpImageFilter::getGaussPyramidal(const vpImage &I, vpImage &GI) { vpImage GIx; @@ -752,7 +752,7 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p break; } } - float upperThresh = std::max(bon, 1.f); + float upperThresh = std::max(bon, 1.f); lowerThresh = lowerThresholdRatio * bon; return upperThresh; } From 3da777d58e787fb6278fcf132540caa32439022e Mon Sep 17 00:00:00 2001 From: rlagneau Date: Mon, 22 Jan 2024 15:37:07 +0100 Subject: [PATCH 09/10] [CORPS] Made vpCircleHoughTransform c++98-compliant --- .../visp3/imgproc/vpCircleHoughTransform.h | 62 ++++---- .../imgproc/src/vpCircleHoughTransform.cpp | 137 +++++++++++++++--- 2 files changed, 153 insertions(+), 46 deletions(-) diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index 0a82b9615e..587ab4ea39 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -232,8 +232,8 @@ class VISP_EXPORT vpCircleHoughTransform , m_upperCannyThreshRatio(upperCannyThreshRatio) , m_centerXlimits(centerXlimits) , m_centerYlimits(centerYlimits) - , m_minRadius(std::min(minRadius, maxRadius)) - , m_maxRadius(std::max(minRadius, maxRadius)) + , m_minRadius(std::min(minRadius, maxRadius)) + , m_maxRadius(std::max(minRadius, maxRadius)) , m_dilatationKernelSize(dilatationKernelSize) , m_averagingWindowSize(averagingWindowSize) , m_centerMinThresh(centerThresh) @@ -458,29 +458,37 @@ class VISP_EXPORT vpCircleHoughTransform */ std::string toString() const { - std::string txt("Hough Circle Transform Configuration:\n"); - txt += "\tFiltering + gradient operators = " + vpImageFilter::vpCannyFilteringAndGradientTypeToString(m_filteringAndGradientType) + "\n"; - txt += "\tGaussian filter kernel size = " + std::to_string(m_gaussianKernelSize) + "\n"; - txt += "\tGaussian filter standard deviation = " + std::to_string(m_gaussianStdev) + "\n"; - txt += "\tGradient filter kernel size = " + std::to_string(m_gradientFilterKernelSize) + "\n"; - txt += "\tCanny backend = " + vpImageFilter::vpCannyBackendTypeToString(m_cannyBackendType) + "\n"; - txt += "\tCanny edge filter thresholds = [" + std::to_string(m_lowerCannyThresh) + " ; " + std::to_string(m_upperCannyThresh) + "]\n"; - txt += "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" + std::to_string(m_lowerCannyThreshRatio) + " ; " + std::to_string(m_upperCannyThreshRatio) + "]\n"; - txt += "\tEdge map 8-neighbor connectivity filtering number of iterations = " + std::to_string(m_edgeMapFilteringNbIter) + "\n"; - 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 += "\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 visibility ratio threshold = " + std::to_string(m_circleVisibilityRatioThresh) + "\n"; - txt += "\tCircle perfectness threshold = " + std::to_string(m_circlePerfectness) + "\n"; - txt += "\tRecord voting points = " + (m_recordVotingPoints ? std::string("true") : std::string("false")) + "\n"; - txt += "\tCenters minimum distance = " + std::to_string(m_centerMinDist) + "\n"; - txt += "\tRadius difference merging threshold = " + std::to_string(m_mergingRadiusDiffThresh) + "\n"; - return txt; + std::stringstream txt; + txt << "Hough Circle Transform Configuration:\n"; + txt << "\tFiltering + gradient operators = " << vpImageFilter::vpCannyFilteringAndGradientTypeToString(m_filteringAndGradientType) << "\n"; + txt << "\tGaussian filter kernel size = " << m_gaussianKernelSize << "\n"; + txt << "\tGaussian filter standard deviation = " << m_gaussianStdev << "\n"; + txt << "\tGradient filter kernel size = " << m_gradientFilterKernelSize << "\n"; + txt << "\tCanny backend = " << vpImageFilter::vpCannyBackendTypeToString(m_cannyBackendType) << "\n"; + txt << "\tCanny edge filter thresholds = [" << m_lowerCannyThresh << " ; " << m_upperCannyThresh << "]\n"; + txt << "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" << m_lowerCannyThreshRatio << " ; " << m_upperCannyThreshRatio << "]\n"; + txt << "\tEdge map 8-neighbor connectivity filtering number of iterations = " << m_edgeMapFilteringNbIter << "\n"; + txt << "\tCenter horizontal position limits: min = " << m_centerXlimits.first << "\tmax = " << m_centerXlimits.second << "\n"; + txt << "\tCenter vertical position limits: min = " << m_centerYlimits.first << "\tmax = " << m_centerYlimits.second << "\n"; + txt << "\tRadius limits: min = " << m_minRadius << "\tmax = " << m_maxRadius << "\n"; + txt << "\tKernel size of the dilatation filter = " << m_dilatationKernelSize << "\n"; + txt << "\tAveraging window size for center detection = " << m_averagingWindowSize << "\n"; + txt << "\tCenters votes threshold = " << m_centerMinThresh << "\n"; + txt << "\tExpected number of centers = "; + if (m_expectedNbCenters > 0) { + txt << m_expectedNbCenters; + } + else { + txt << "no limits"; + } + txt << "\n"; + txt << "\tCircle probability threshold = " << m_circleProbaThresh << "\n"; + txt << "\tCircle visibility ratio threshold = " << m_circleVisibilityRatioThresh << "\n"; + txt << "\tCircle perfectness threshold = " << m_circlePerfectness << "\n"; + txt << "\tRecord voting points = " + (m_recordVotingPoints ? std::string("true") : std::string("false")) << "\n"; + txt << "\tCenters minimum distance = " << m_centerMinDist << "\n"; + txt << "\tRadius difference merging threshold = " << m_mergingRadiusDiffThresh << "\n"; + return txt.str(); } // // Configuration from files @@ -571,8 +579,8 @@ 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)); - params.m_minRadius = std::min(radiusLimits.first, radiusLimits.second); - params.m_maxRadius = std::max(radiusLimits.first, radiusLimits.second); + params.m_minRadius = std::min(radiusLimits.first, radiusLimits.second); + params.m_maxRadius = std::max(radiusLimits.first, radiusLimits.second); params.m_dilatationKernelSize = j.value("dilatationKernelSize", params.m_dilatationKernelSize); params.m_averagingWindowSize = j.value("averagingWindowSize", params.m_averagingWindowSize); diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 1d86ea8f35..018292328c 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -35,6 +35,52 @@ #include +#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11) +namespace +{ +// Sorting by decreasing probabilities +bool hasBetterProba(std::pair a, std::pair b) +{ + return (a.second > b.second); +} + +void updateAccumulator(const float &x_orig, const float &y_orig, + const int &x, const int &y, + const int &offsetX, const int &offsetY, + const int &nbCols, const int &nbRows, + vpImage &accum, bool &hasToStop) +{ + if (((x - offsetX) < 0) || + ((x - offsetX) >= nbCols) || + ((y - offsetY) < 0) || + ((y - offsetY) >= nbRows) + ) { + hasToStop = true; + } + else { + float dx = (x_orig - static_cast(x)); + float dy = (y_orig - static_cast(y)); + accum[y - offsetY][x - offsetX] += std::abs(dx) + std::abs(dy); + } +} + +bool sortingCenters(const std::pair, float> &position_vote_a, + const std::pair, float> &position_vote_b) +{ + return position_vote_a.second > position_vote_b.second; +} + +float computeEffectiveRadius(const float &votes, const float &weigthedSumRadius) +{ + float r_effective = -1.f; + if (votes > std::numeric_limits::epsilon()) { + r_effective = weigthedSumRadius / votes; + } + return r_effective; +} +}; +#endif + vpCircleHoughTransform::vpCircleHoughTransform() : m_algoParams() , mp_mask(nullptr) @@ -182,17 +228,19 @@ vpCircleHoughTransform::detect(const vpImage &I, const int &nbCir size_t nbDetections = detections.size(); // Prepare vector of tuple to sort by decreasing probabilities - std::vector> v_id_proba; + std::vector > v_id_proba; for (size_t i = 0; i < nbDetections; ++i) { std::pair id_proba(i, m_finalCirclesProbabilities[i]); v_id_proba.push_back(id_proba); } - // Sorting by decreasing probabilities +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +// Sorting by decreasing probabilities auto hasBetterProba = [](std::pair a, std::pair b) { return (a.second > b.second); }; +#endif std::sort(v_id_proba.begin(), v_id_proba.end(), hasBetterProba); // Clearing the storages containing the detection results @@ -206,10 +254,10 @@ vpCircleHoughTransform::detect(const vpImage &I, const int &nbCir } std::vector bestCircles; - auto copyFinalCircles = m_finalCircles; - auto copyFinalCirclesVotes = m_finalCircleVotes; - auto copyFinalCirclesProbas = m_finalCirclesProbabilities; - auto copyFinalCirclesVotingPoints = m_finalCirclesVotingPoints; + std::vector copyFinalCircles = m_finalCircles; + std::vector copyFinalCirclesVotes = m_finalCircleVotes; + std::vector copyFinalCirclesProbas = m_finalCirclesProbabilities; + std::vector > > copyFinalCirclesVotingPoints = m_finalCirclesVotingPoints; for (size_t i = 0; i < nbDetections; ++i) { size_t id = v_id_proba[i].first; m_finalCircles[i] = copyFinalCircles[id]; @@ -632,6 +680,7 @@ vpCircleHoughTransform::computeCenterCandidates() y_high_prev = y_high; } +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) auto updateAccumulator = [](const float &x_orig, const float &y_orig, const int &x, const int &y, @@ -651,6 +700,7 @@ vpCircleHoughTransform::computeCenterCandidates() accum[y - offsetY][x - offsetX] += std::abs(dx) + std::abs(dy); } }; +#endif updateAccumulator(x1, y1, x_low, y_low, offsetX, offsetY, @@ -684,7 +734,7 @@ vpCircleHoughTransform::computeCenterCandidates() int nbColsAccum = centersAccum.getCols(); int nbRowsAccum = centersAccum.getRows(); int nbVotes = -1; - std::vector, float>> peak_positions_votes; + std::vector, float> > peak_positions_votes; for (int y = 0; y < nbRowsAccum; y++) { int left = -1; @@ -696,7 +746,7 @@ vpCircleHoughTransform::computeCenterCandidates() if (left < 0) { left = x; } - nbVotes = std::max(nbVotes, static_cast(centersAccum[y][x])); + nbVotes = std::max(nbVotes, static_cast(centersAccum[y][x])); } else if (left >= 0) { int cx = static_cast(((left + x) - 1) * 0.5f); @@ -723,7 +773,9 @@ vpCircleHoughTransform::computeCenterCandidates() 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))); + std::stringstream errMsg; + errMsg << "nbVotes (" << nbVotes << ") < 0, thresh = " << m_algoParams.m_centerMinThresh; + throw(vpException(vpException::badValue, errMsg.str())); } left = -1; nbVotes = -1; @@ -734,7 +786,7 @@ vpCircleHoughTransform::computeCenterCandidates() unsigned int nbPeaks = static_cast(peak_positions_votes.size()); if (nbPeaks > 0) { std::vector has_been_merged(nbPeaks, false); - std::vector, float>> merged_peaks_position_votes; + 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; @@ -789,10 +841,12 @@ vpCircleHoughTransform::computeCenterCandidates() } } +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) 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; + const std::pair, float> &position_vote_b) { + return position_vote_a.second > position_vote_b.second; }; +#endif std::sort(merged_peaks_position_votes.begin(), merged_peaks_position_votes.end(), sortingCenters); @@ -811,10 +865,10 @@ vpCircleHoughTransform::computeCircleCandidates() { size_t nbCenterCandidates = m_centerCandidatesList.size(); int nbBins = static_cast(((m_algoParams.m_maxRadius - m_algoParams.m_minRadius) + 1) / m_algoParams.m_mergingRadiusDiffThresh); - nbBins = std::max(static_cast(1), nbBins); // Avoid having 0 bins, which causes segfault + nbBins = std::max(static_cast(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. - std::vector>> votingPoints(nbBins); // Vectors that contain the points voting for each radius bin + std::vector > > votingPoints(nbBins); // Vectors that contain the points voting for each radius bin float rmin2 = m_algoParams.m_minRadius * m_algoParams.m_minRadius; float rmax2 = m_algoParams.m_maxRadius * m_algoParams.m_maxRadius; @@ -828,14 +882,29 @@ vpCircleHoughTransform::computeCircleCandidates() radiusActualValueList.clear(); radiusActualValueList.resize(nbBins, 0.); - for (auto edgePoint : m_edgePointsList) { +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) + for (auto edgePoint : m_edgePointsList) +#else + for (size_t e = 0; e < m_edgePointsList.size(); ++e) +#endif + { // For each center candidate CeC_i, compute the distance with each edge point EP_j d_ij = dist(CeC_i; EP_j) +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) float rx = edgePoint.second - centerCandidate.second; float ry = edgePoint.first - centerCandidate.first; +#else + float rx = m_edgePointsList[e].second - centerCandidate.second; + float ry = m_edgePointsList[e].first - centerCandidate.first; +#endif float r2 = (rx * rx) + (ry * ry); if ((r2 > rmin2) && (r2 < rmax2)) { +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) float gx = m_dIx[edgePoint.first][edgePoint.second]; float gy = m_dIy[edgePoint.first][edgePoint.second]; +#else + float gx = m_dIx[m_edgePointsList[e].first][m_edgePointsList[e].second]; + float gy = m_dIy[m_edgePointsList[e].first][m_edgePointsList[e].second]; +#endif float grad2 = (gx * gx) + (gy * gy); float scalProd = (rx * gx) + (ry * gy); @@ -851,7 +920,11 @@ vpCircleHoughTransform::computeCircleCandidates() radiusAccumList[r_bin] += 1.f; radiusActualValueList[r_bin] += r; if (m_algoParams.m_recordVotingPoints) { +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) votingPoints[r_bin].push_back(edgePoint); +#else + votingPoints[r_bin].push_back(m_edgePointsList[e]); +#endif } } else { @@ -868,8 +941,13 @@ vpCircleHoughTransform::computeCircleCandidates() radiusAccumList[r_bin + 1] += voteNextBin; radiusActualValueList[r_bin + 1] += r * voteNextBin; if (m_algoParams.m_recordVotingPoints) { +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) votingPoints[r_bin].push_back(edgePoint); votingPoints[r_bin + 1].push_back(edgePoint); +#else + votingPoints[r_bin].push_back(m_edgePointsList[e]); + votingPoints[r_bin + 1].push_back(m_edgePointsList[e]); +#endif } } else { @@ -881,8 +959,13 @@ vpCircleHoughTransform::computeCircleCandidates() radiusAccumList[r_bin - 1] += votePrevBin; radiusActualValueList[r_bin - 1] += r * votePrevBin; if (m_algoParams.m_recordVotingPoints) { +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) votingPoints[r_bin].push_back(edgePoint); votingPoints[r_bin - 1].push_back(edgePoint); +#else + votingPoints[r_bin].push_back(m_edgePointsList[e]); + votingPoints[r_bin - 1].push_back(m_edgePointsList[e]); +#endif } } } @@ -902,12 +985,12 @@ vpCircleHoughTransform::computeCircleCandidates() // Merging similar candidates std::vector v_r_effective; // Vector of radius of each candidate after the merge step std::vector v_votes_effective; // Vector of number of votes of each candidate after the merge step - std::vector>> v_votingPoints_effective; // Vector of voting points after the merge step + std::vector > > v_votingPoints_effective; // Vector of voting points after the merge step std::vector v_hasMerged_effective; // Vector indicating if merge has been performed for the different candidates for (int idBin = 0; idBin < nbBins; ++idBin) { float r_effective = computeEffectiveRadius(radiusAccumList[idBin], radiusActualValueList[idBin]); float votes_effective = radiusAccumList[idBin]; - std::vector> votingPoints_effective = votingPoints[idBin]; + std::vector > votingPoints_effective = votingPoints[idBin]; bool is_r_effective_similar = (r_effective > 0.f); // Looking for potential similar radii in the following bins // If so, compute the barycenter radius between them @@ -924,11 +1007,19 @@ vpCircleHoughTransform::computeCircleCandidates() if (m_algoParams.m_recordVotingPoints) { // Move elements from votingPoints[idCandidate] to votingPoints_effective. // votingPoints[idCandidate] is left in undefined but safe-to-destruct state. +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) votingPoints_effective.insert( votingPoints_effective.end(), std::make_move_iterator(votingPoints[idCandidate].begin()), std::make_move_iterator(votingPoints[idCandidate].end()) ); +#else + votingPoints_effective.insert( + votingPoints_effective.end(), + votingPoints[idCandidate].begin(), + votingPoints[idCandidate].end() + ); +#endif hasMerged = true; } } @@ -1001,7 +1092,7 @@ vpCircleHoughTransform::mergeCircleCandidates() std::vector circleCandidates = m_circleCandidates; std::vector circleCandidatesVotes = m_circleCandidatesVotes; std::vector circleCandidatesProba = m_circleCandidatesProbabilities; - std::vector>> circleCandidatesVotingPoints = m_circleCandidatesVotingPoints; + std::vector > > circleCandidatesVotingPoints = m_circleCandidatesVotingPoints; // First iteration of merge mergeCandidates(circleCandidates, circleCandidatesVotes, circleCandidatesProba, circleCandidatesVotingPoints); @@ -1017,7 +1108,7 @@ vpCircleHoughTransform::mergeCircleCandidates() void vpCircleHoughTransform::mergeCandidates(std::vector &circleCandidates, std::vector &circleCandidatesVotes, - std::vector &circleCandidatesProba, std::vector>> &votingPoints) + std::vector &circleCandidatesProba, std::vector > > &votingPoints) { size_t nbCandidates = circleCandidates.size(); size_t i = 0; @@ -1050,11 +1141,19 @@ vpCircleHoughTransform::mergeCandidates(std::vector &circleCandid circleCandidatesProba[i] = newProba; circleCandidatesProba[j] = circleCandidatesProba[nbCandidates - 1]; if (m_algoParams.m_recordVotingPoints) { +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) votingPoints[i].insert( votingPoints[i].end(), std::make_move_iterator(votingPoints[j].begin()), std::make_move_iterator(votingPoints[j].end()) ); +#else + votingPoints[i].insert( + votingPoints[i].end(), + votingPoints[j].begin(), + votingPoints[j].end() + ); +#endif votingPoints.pop_back(); } circleCandidates.pop_back(); From ed5d19a7e5e4e6ac76afb1f133076956e2d1b182 Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Wed, 24 Jan 2024 08:42:40 +0100 Subject: [PATCH 10/10] Code indentation with vscode settings and changes in cxx standard detection --- modules/core/include/visp3/core/vpHistogram.h | 6 -- .../core/include/visp3/core/vpImageFilter.h | 43 ++++----- .../core/src/image/vpCannyEdgeDetection.cpp | 60 ++++++++---- modules/core/src/image/vpImageCircle.cpp | 94 +++++++++---------- .../visp3/imgproc/vpCircleHoughTransform.h | 26 ++--- .../imgproc/src/vpCircleHoughTransform.cpp | 49 ++++++---- 6 files changed, 144 insertions(+), 134 deletions(-) diff --git a/modules/core/include/visp3/core/vpHistogram.h b/modules/core/include/visp3/core/vpHistogram.h index 9672ef90a7..419b855cc2 100644 --- a/modules/core/include/visp3/core/vpHistogram.h +++ b/modules/core/include/visp3/core/vpHistogram.h @@ -319,9 +319,3 @@ class VISP_EXPORT vpHistogram }; #endif - -/* - * Local variables: - * c-basic-offset: 2 - * End: - */ diff --git a/modules/core/include/visp3/core/vpImageFilter.h b/modules/core/include/visp3/core/vpImageFilter.h index c1b46ed5da..9e38d76dfa 100644 --- a/modules/core/include/visp3/core/vpImageFilter.h +++ b/modules/core/include/visp3/core/vpImageFilter.h @@ -59,24 +59,6 @@ #include #endif -#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98) -namespace -{ -// Helper to apply the scale to the raw values of the filters -template -void scaleFilter(vpArray2D &filter, const float &scale) -{ - const unsigned int nbRows = filter.getRows(); - const unsigned int nbCols = filter.getCols(); - for (unsigned int r = 0; r < nbRows; ++r) { - for (unsigned int c = 0; c < nbCols; ++c) { - filter[r][c] = filter[r][c] * scale; - } - } -} -}; -#endif - /*! * \class vpImageFilter * @@ -122,7 +104,7 @@ class VISP_EXPORT vpImageFilter static bool checkBooleanMask(const vpImage *p_mask, const unsigned int &r, const unsigned int &c) { bool computeVal = true; -#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher if (p_mask != nullptr) #else if (p_mask != NULL) @@ -133,6 +115,21 @@ class VISP_EXPORT vpImageFilter return computeVal; } +#if ((__cplusplus == 199711L) || (defined(_MSVC_LANG) && (_MSVC_LANG == 199711L))) // Check if cxx98 + // Helper to apply the scale to the raw values of the filters + template + static void scaleFilter(vpArray2D &filter, const float &scale) + { + const unsigned int nbRows = filter.getRows(); + const unsigned int nbCols = filter.getCols(); + for (unsigned int r = 0; r < nbRows; ++r) { + for (unsigned int c = 0; c < nbCols; ++c) { + filter[r][c] = filter[r][c] * scale; + } + } + } +#endif + public: //! Canny filter backends for the edge detection operations typedef enum vpCannyBackendType @@ -251,7 +248,7 @@ class VISP_EXPORT vpImageFilter vpArray2D gradientFilterX(apertureGradient, apertureGradient); // Gradient filter along the X-axis vpArray2D gradientFilterY(apertureGradient, apertureGradient); // Gradient filter along the Y-axis -#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher // Helper to apply the scale to the raw values of the filters auto scaleFilter = [](vpArray2D &filter, const float &scale) { const unsigned int nbRows = filter.getRows(); @@ -313,7 +310,7 @@ class VISP_EXPORT vpImageFilter } } -#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher template inline static void computePartialDerivatives(const vpImage &I, vpImage &dIx, vpImage &dIy, @@ -618,7 +615,7 @@ class VISP_EXPORT vpImageFilter } } -#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher template static void filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve = false) = delete; #else @@ -701,7 +698,7 @@ class VISP_EXPORT vpImageFilter } } -#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#if ((__cplusplus >= 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))) // Check if cxx11 or higher template static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve) = delete; diff --git a/modules/core/src/image/vpCannyEdgeDetection.cpp b/modules/core/src/image/vpCannyEdgeDetection.cpp index 3ffbea60a7..3241678ad2 100644 --- a/modules/core/src/image/vpCannyEdgeDetection.cpp +++ b/modules/core/src/image/vpCannyEdgeDetection.cpp @@ -34,6 +34,24 @@ #include +#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98) // Check if cxx98 +namespace +{ +// Helper to apply the scale to the raw values of the filters +template +static void scaleFilter(vpArray2D &filter, const float &scale) +{ + const unsigned int nbRows = filter.getRows(); + const unsigned int nbCols = filter.getCols(); + for (unsigned int r = 0; r < nbRows; ++r) { + for (unsigned int c = 0; c < nbCols; ++c) { + filter[r][c] = filter[r][c] * scale; + } + } +} +}; +#endif + // // Initialization methods vpCannyEdgeDetection::vpCannyEdgeDetection() @@ -53,9 +71,9 @@ vpCannyEdgeDetection::vpCannyEdgeDetection() } vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev - , const unsigned int &sobelAperture, const float &lowerThreshold, const float &upperThreshold - , const float &lowerThresholdRatio, const float &upperThresholdRatio - , const vpImageFilter::vpCannyFilteringAndGradientType &filteringType + , const unsigned int &sobelAperture, const float &lowerThreshold, const float &upperThreshold + , const float &lowerThresholdRatio, const float &upperThresholdRatio + , const vpImageFilter::vpCannyFilteringAndGradientType &filteringType ) : m_filteringAndGradientType(filteringType) , m_gaussianKernelSize(gaussianKernelSize) @@ -111,7 +129,7 @@ vpCannyEdgeDetection::initGaussianFilters() if ((m_gaussianKernelSize % 2) == 0) { throw(vpException(vpException::badValue, "The Gaussian kernel size should be odd")); } - m_fg.resize(1, (m_gaussianKernelSize + 1)/2); + m_fg.resize(1, (m_gaussianKernelSize + 1) / 2); vpImageFilter::getGaussianKernel(m_fg.data, m_gaussianKernelSize, m_gaussianStdev, true); } @@ -124,24 +142,26 @@ vpCannyEdgeDetection::initGradientFilters() m_gradientFilterX.resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize); m_gradientFilterY.resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize); +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) auto scaleFilter = [](vpArray2D &filter, const float &scale) { for (unsigned int r = 0; r < filter.getRows(); r++) { for (unsigned int c = 0; c < filter.getCols(); c++) { filter[r][c] = filter[r][c] * scale; } }}; +#endif float scaleX = 1.f; float scaleY = 1.f; if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { - scaleX = vpImageFilter::getSobelKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1)/2); - scaleY = vpImageFilter::getSobelKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1)/2); + scaleX = vpImageFilter::getSobelKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1) / 2); + scaleY = vpImageFilter::getSobelKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1) / 2); } else if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { // Compute the Scharr filters - scaleX = vpImageFilter::getScharrKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1)/2); - scaleY = vpImageFilter::getScharrKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1)/2); + scaleX = vpImageFilter::getScharrKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1) / 2); + scaleY = vpImageFilter::getScharrKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1) / 2); } else { std::string errMsg = "[vpCannyEdgeDetection::initGradientFilters] Error: gradient filtering method \""; @@ -215,7 +235,7 @@ void vpCannyEdgeDetection::performFilteringAndGradientComputation(const vpImage &I) { if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING - || m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { + || m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { // Computing the Gaussian blur vpImage Iblur; vpImage GIx; @@ -248,9 +268,9 @@ vpCannyEdgeDetection::performFilteringAndGradientComputation(const vpImage &dIx, const vpImage &dIy, const float grad = 0.; int nbRows = dIx.getRows(); int nbCols = dIx.getCols(); - if (row >= 0 - && row < nbRows - && col >= 0 - && col < nbCols - ) { + if (row >= 0 + && row < nbRows + && col >= 0 + && col < nbCols + ) { float dx = dIx[row][col]; float dy = dIy[row][col]; grad = std::abs(dx) + std::abs(dy); @@ -442,9 +462,9 @@ vpCannyEdgeDetection::recursiveSearchForStrongEdge(const std::pair= nbRows) - || (idCol < 0 || idCol >= nbCols) - || (dr == 0 && dc == 0) - ) { + || (idCol < 0 || idCol >= nbCols) + || (dr == 0 && dc == 0) + ) { continue; } diff --git a/modules/core/src/image/vpImageCircle.cpp b/modules/core/src/image/vpImageCircle.cpp index 90304b93eb..6d9ee96677 100644 --- a/modules/core/src/image/vpImageCircle.cpp +++ b/modules/core/src/image/vpImageCircle.cpp @@ -71,7 +71,7 @@ void computeIntersectionsLeftBorderOnly(const float &u_c, const float &umin_roi, { // umin_roi = u_c + r cos(theta) // theta = acos((umin_roi - u_c) / r) - float theta1 = std::acos((umin_roi - u_c)/ radius); + float theta1 = std::acos((umin_roi - u_c) / radius); theta1 = vpMath::getAngleBetweenMinPiAndPi(theta1); float theta2 = -1.f * theta1; float theta_min = std::min(theta1, theta2); @@ -206,14 +206,14 @@ void computeIntersectionsBottomBorderOnly(const float &v_c, const float &vmax_ro * \param[out] theta_v_cross_max The pair angle /v-coordinate for which the circle intersects the v-axis with the highest v-coordinate. */ void computePerpendicularAxesIntersections(const float &u_c, const float &v_c, const float &radius, - const float &crossing_u, const float &crossing_v, - std::pair &theta_u_cross_min, std::pair &theta_u_cross_max, - std::pair &theta_v_cross_min, std::pair &theta_v_cross_max) + const float &crossing_u, const float &crossing_v, + std::pair &theta_u_cross_min, std::pair &theta_u_cross_max, + std::pair &theta_v_cross_min, std::pair &theta_v_cross_max) { // Computing the two angles for which the u-axis is crossed // v = vc - r sin(theta) because the v-axis goes down // theta = asin((vc - v)/r) - float theta_u_cross = std::asin((v_c - crossing_u)/radius); + float theta_u_cross = std::asin((v_c - crossing_u) / radius); theta_u_cross = vpMath::getAngleBetweenMinPiAndPi(theta_u_cross); float theta_u_cross_2 = 0.f; if (theta_u_cross > 0) { @@ -242,7 +242,7 @@ void computePerpendicularAxesIntersections(const float &u_c, const float &v_c, c // Computing the two angles for which the v-axis is crossed // u = u_c + r cos(theta) // theta = acos((u - u_c) / r) - float theta_v_cross = std::acos((crossing_v - u_c)/radius); + float theta_v_cross = std::acos((crossing_v - u_c) / radius); theta_v_cross = vpMath::getAngleBetweenMinPiAndPi(theta_v_cross); float theta_v_cross_2 = -theta_v_cross; // Computing the corresponding v-coordinates at which the v-axis is crossed @@ -327,7 +327,7 @@ void computeIntersectionsTopLeft(const float &u_c, const float &v_c, const float * \param[out] delta_theta The length of the angular interval that is in the RoI. */ void computeIntersectionsTopRight(const float &u_c, const float &v_c, const float &vmin_roi, const float &umax_roi, const float &radius, - float &delta_theta) + float &delta_theta) { std::pair crossing_theta_u_min, crossing_theta_u_max; std::pair crossing_theta_v_min, crossing_theta_v_max; @@ -352,7 +352,7 @@ void computeIntersectionsTopRight(const float &u_c, const float &v_c, const floa else if (u_umin <= umax_roi && v_vmin >= vmin_roi && u_umax <= umax_roi && v_vmax >= vmin_roi) { // The circle crosses twice each axis //Case crossing twice - delta_theta = 2 * M_PIf - ((theta_u_min - theta_u_max)+(theta_v_min - theta_v_max)); + delta_theta = 2 * M_PIf - ((theta_u_min - theta_u_max) + (theta_v_min - theta_v_max)); } else if (u_umin >= umax_roi && v_vmin >= vmin_roi && u_umax >= umax_roi && v_vmax >= vmin_roi) { // The circle crosses the u-axis outside the roi @@ -380,7 +380,7 @@ void computeIntersectionsTopRight(const float &u_c, const float &v_c, const floa * \param[out] delta_theta The length of the angular interval that is in the RoI. */ void computeIntersectionsBottomLeft(const float &u_c, const float &v_c, const float &umin_roi, const float &vmax_roi, const float &radius, - float &delta_theta) + float &delta_theta) { std::pair crossing_theta_u_min, crossing_theta_u_max; std::pair crossing_theta_v_min, crossing_theta_v_max; @@ -431,7 +431,7 @@ void computeIntersectionsBottomLeft(const float &u_c, const float &v_c, const fl * \param[out] delta_theta The length of the angular interval that is in the RoI. */ void computeIntersectionsBottomRight(const float &u_c, const float &v_c, const float &vmax_roi, const float &umax_roi, const float &radius, - float &delta_theta) + float &delta_theta) { std::pair crossing_theta_u_min, crossing_theta_u_max; std::pair crossing_theta_v_min, crossing_theta_v_max; @@ -575,23 +575,23 @@ void computeIntersectionsTopRightBottom(const float &u_c, const float &v_c, cons float u_umin_bottom = crossing_theta_u_min.second; float u_umax_bottom = crossing_theta_u_max.second; if (u_umax_top <= umax_roi && u_umax_bottom <= umax_roi && v_vmin >= vmin_roi && v_vmax <= vmax_roi) { - // case intersection top + right + bottom twice + // case intersection top + right + bottom twice delta_theta = 2.f * M_PIf - ((theta_u_min_top - theta_u_max_top) + (theta_v_min - theta_v_max) + (theta_u_max_bottom - theta_u_min_bottom)); } else if (u_umin_top <= umax_roi && u_umax_top > umax_roi && v_vmin <= vmin_roi && u_umin_bottom <= umax_roi && u_umax_bottom > umax_roi && v_vmax >= vmax_roi) { - // case intersection top and bottom + // case intersection top and bottom delta_theta = (theta_u_max_top - theta_u_max_bottom); } else if (u_umin_top >= umax_roi && u_umin_bottom >= umax_roi && v_vmin >= vmin_roi && v_vmax <= vmax_roi) { - // case right only + // case right only computeIntersectionsRightBorderOnly(u_c, umax_roi, radius, delta_theta); } else if (u_umin_bottom <= umax_roi && v_vmin >= vmin_roi) { - // case bottom/right corner + // case bottom/right corner computeIntersectionsBottomRight(u_c, v_c, vmax_roi, umax_roi, radius, delta_theta); } else if (u_umin_top <= umax_roi && v_vmax <= vmax_roi) { - // case top/right corner + // case top/right corner computeIntersectionsTopRight(u_c, v_c, vmin_roi, umax_roi, radius, delta_theta); } } @@ -608,12 +608,12 @@ void computeIntersectionsTopRightBottom(const float &u_c, const float &v_c, cons * \param[out] delta_theta The length of the angular interval that is in the RoI. */ void computeIntersectionsTopBottomOnly(const float &u_c, const float &v_c, const float &vmin_roi, const float &vmax_roi, const float &radius, - float &delta_theta) + float &delta_theta) { // Computing the two angles for which the u-axis is crossed at the top of the RoI // v = vc - r sin(theta) because the v-axis goes down // theta = asin((vc - vmin_roi)/r) - float theta_u_cross_top = std::asin((v_c - vmin_roi)/radius); + float theta_u_cross_top = std::asin((v_c - vmin_roi) / radius); theta_u_cross_top = vpMath::getAngleBetweenMinPiAndPi(theta_u_cross_top); float theta_u_cross_top_2 = 0.f; if (theta_u_cross_top > 0) { @@ -640,7 +640,7 @@ void computeIntersectionsTopBottomOnly(const float &u_c, const float &v_c, const // Computing the two angles for which the u-axis is crossed at the bottom of the RoI // v = vc - r sin(theta) because the v-axis goes down // theta = asin((vc - vmax_roi)/r) - float theta_u_cross_bottom = std::asin((v_c - vmax_roi)/radius); + float theta_u_cross_bottom = std::asin((v_c - vmax_roi) / radius); theta_u_cross_bottom = vpMath::getAngleBetweenMinPiAndPi(theta_u_cross_bottom); float theta_u_cross_bottom_2 = 0.f; if (theta_u_cross_bottom > 0) { @@ -713,23 +713,23 @@ void computeIntersectionsLeftRightTop(const float &u_c, const float &v_c, const float v_vmax_right = crossing_theta_v_max.second; if (u_umin >= umin_roi && u_umax <= umax_roi && v_vmin_left >= vmin_roi && v_vmin_right >= vmin_roi) { - // case intersection left + right + top twice + // case intersection left + right + top twice delta_theta = (theta_v_min_left - theta_u_min) + (theta_u_max - theta_v_min_right) + (theta_v_max_right - theta_v_max_left); } else if (u_umin <= umin_roi && u_umax >= umax_roi && v_vmax_left >= vmin_roi && v_vmax_right >= vmin_roi) { - // case intersection left + right + // case intersection left + right delta_theta = (theta_v_max_right - theta_v_max_left); } else if (v_vmax_left <= vmin_roi && v_vmax_right <= vmin_roi && u_umin >= umin_roi && u_umax <= umax_roi) { - // case top only + // case top only computeIntersectionsTopBorderOnly(v_c, vmin_roi, radius, delta_theta); } else if (u_umax >= umin_roi && v_vmax_left >= vmin_roi) { - // case top/left corner + // case top/left corner computeIntersectionsTopLeft(u_c, v_c, umin_roi, vmin_roi, radius, delta_theta); } else if (u_umin <= umax_roi && v_vmax_right >= vmin_roi) { - // case top/right corner + // case top/right corner computeIntersectionsTopRight(u_c, v_c, vmin_roi, umax_roi, radius, delta_theta); } } @@ -747,7 +747,7 @@ void computeIntersectionsLeftRightTop(const float &u_c, const float &v_c, const * \param[out] delta_theta The length of the angular interval that is in the RoI. */ void computeIntersectionsLeftRightBottom(const float &u_c, const float &v_c, const float &umin_roi, const float &umax_roi, - const float &vmax_roi, const float &radius, float &delta_theta) + const float &vmax_roi, const float &radius, float &delta_theta) { // Computing the intersections with the bottom and left axes std::pair crossing_theta_u_min, crossing_theta_u_max; @@ -777,23 +777,23 @@ void computeIntersectionsLeftRightBottom(const float &u_c, const float &v_c, con // float v_vmax_right = crossing_theta_v_max.second; if (u_umin >= umin_roi && u_umax <= umax_roi && v_vmin_left <= vmax_roi && v_vmin_right <= vmax_roi) { - // case intersection left + right + bottom twice + // case intersection left + right + bottom twice delta_theta = (theta_v_min_left - theta_v_min_right) + (theta_v_max_right - theta_u_max) + (theta_u_min - theta_v_max_left); } else if (u_umin <= umin_roi && u_umax >= umax_roi && v_vmin_left <= vmax_roi && v_vmin_right <= vmax_roi) { - // case intersection left + right + // case intersection left + right delta_theta = (theta_v_min_left - theta_v_min_right); } else if (v_vmin_left >= vmax_roi && v_vmin_right >= vmax_roi && u_umin >= umin_roi && u_umax <= umax_roi) { - // case bottom only + // case bottom only computeIntersectionsBottomBorderOnly(v_c, vmax_roi, radius, delta_theta); } else if (u_umax >= umin_roi && v_vmin_right >= vmax_roi) { - // case bottom/left corner + // case bottom/left corner computeIntersectionsBottomLeft(u_c, v_c, umin_roi, vmax_roi, radius, delta_theta); } else if (u_umin <= umax_roi && v_vmin_right <= vmax_roi) { - // case bottom/right corner + // case bottom/right corner computeIntersectionsBottomRight(u_c, v_c, vmax_roi, umax_roi, radius, delta_theta); } } @@ -810,13 +810,13 @@ void computeIntersectionsLeftRightBottom(const float &u_c, const float &v_c, con * \param[out] delta_theta The length of the angular interval that is in the RoI. */ void computeIntersectionsLeftRightOnly(const float &u_c, const float &v_c, const float &umin_roi, const float &umax_roi, const float &radius, - float &delta_theta) + float &delta_theta) { // Computing the two angles for which the v-axis is crossed at the left of the RoI // umin_roi = u_c + r cos(theta) // theta = acos((umin_roi - u_c)/r) // theta_min = -theta_max - float theta_v_cross_left = std::acos((umin_roi - u_c)/radius); + float theta_v_cross_left = std::acos((umin_roi - u_c) / radius); theta_v_cross_left = vpMath::getAngleBetweenMinPiAndPi(theta_v_cross_left); float theta_v_cross_left_2 = -theta_v_cross_left; @@ -838,7 +838,7 @@ void computeIntersectionsLeftRightOnly(const float &u_c, const float &v_c, const // umax_roi = u_c + r cos(theta) // theta = acos((umin_roi - u_c)/r) // theta_min = -theta_max - float theta_v_cross_right = std::acos((umax_roi - u_c)/radius); + float theta_v_cross_right = std::acos((umax_roi - u_c) / radius); theta_v_cross_right = vpMath::getAngleBetweenMinPiAndPi(theta_v_cross_right); float theta_v_cross_right_2 = -theta_v_cross_right; @@ -902,7 +902,7 @@ void computeIntersectionsAllAxes(const float &u_c, const float &v_c, const float float theta_u_max_bottom = crossing_theta_u_max.first; float theta_v_min_right = crossing_theta_v_min.first; float theta_v_max_right = crossing_theta_v_max.first; - delta_theta = (theta_v_min_left - theta_u_min_top) + (theta_u_max_top -theta_v_min_right); + delta_theta = (theta_v_min_left - theta_u_min_top) + (theta_u_max_top - theta_v_min_right); delta_theta += (theta_v_max_right - theta_u_max_bottom) + (theta_u_min_bottom - theta_v_max_left); } @@ -964,15 +964,15 @@ float vpImageCircle::computeAngularCoverageInRoI(const vpRect &roi, const float // Touches/intersects the top and right borders of the RoI computeIntersectionsTopRight(u_c, v_c, vmin_roi, umax_roi, radius, delta_theta); } - else if (touchBottomBorder && touchTopBorder && touchLeftBorder && !touchRightBorder) { + else if (touchBottomBorder && touchTopBorder && touchLeftBorder && !touchRightBorder) { // 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); } - else if (touchBottomBorder && touchTopBorder && !touchLeftBorder && touchRightBorder) { + else if (touchBottomBorder && touchTopBorder && !touchLeftBorder && touchRightBorder) { // Touches/intersects the top, right and bottom borders of the RoI computeIntersectionsTopRightBottom(u_c, v_c, umax_roi, vmin_roi, vmax_roi, radius, delta_theta); } - else if (touchBottomBorder && touchTopBorder && !touchLeftBorder && !touchRightBorder) { + else if (touchBottomBorder && touchTopBorder && !touchLeftBorder && !touchRightBorder) { // Touches/intersects the top and bottom borders of the RoI computeIntersectionsTopBottomOnly(u_c, v_c, vmin_roi, vmax_roi, radius, delta_theta); } @@ -1051,15 +1051,15 @@ unsigned int vpImageCircle::computePixelsInMask(const vpImage &mask) const #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) // Increment the counter if the considered pixel (x, y) is in the mask image auto incrementIfIsInMask = [](const vpImage &mask, const int &width, const int &height, const int &x, const int &y, - unsigned int &count) { - if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) { - // The pixel is outside the limit of the mask - return; - } - if (mask[y][x]) { - // Increment only if the pixel value of the mask is true - count++; - } + unsigned int &count) { + if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) { + // The pixel is outside the limit of the mask + return; + } + if (mask[y][x]) { + // Increment only if the pixel value of the mask is true + count++; + } }; #endif unsigned int count = 0; // Count the number of pixels of the circle whose value in the mask is true @@ -1111,7 +1111,7 @@ unsigned int vpImageCircle::computePixelsInMask(const vpImage &mask) const // dTheta <= 1/r sin(theta) && dTheta <= 1/r cos(theta) dthetaPos = std::min(dthetaCosPos, dthetaSinPos); } - else if (sin_theta == 0.f && cos_theta !=0.f) { + else if (sin_theta == 0.f && cos_theta != 0.f) { // dTheta = -1 / r cos(theta) || dTheta = 1 / r cos(theta) if (cos_theta > 0.f) { dthetaPos = dthetaCosNeg; @@ -1120,7 +1120,7 @@ unsigned int vpImageCircle::computePixelsInMask(const vpImage &mask) const dthetaPos = dthetaCosPos; } } - else if (sin_theta != 0.f && cos_theta ==0.f) { + else if (sin_theta != 0.f && cos_theta == 0.f) { // dTheta = -1 / r sin(theta) || dTheta = 1 / r sin(theta) if (sin_theta > 0.f) { dthetaPos = dthetaSinNeg; diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index 587ab4ea39..5580948782 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -31,7 +31,7 @@ #ifndef _vpCircleHoughTransform_h_ #define _vpCircleHoughTransform_h_ - // System includes +// System includes #include #include @@ -40,16 +40,11 @@ #include #include #include -#include -#include -#include #include -#include // 3rd parties inclue #ifdef VISP_HAVE_NLOHMANN_JSON #include -//! json namespace shortcut using json = nlohmann::json; #endif @@ -115,7 +110,7 @@ class VISP_EXPORT vpCircleHoughTransform float m_circlePerfectness; /*!< The threshold for the colinearity between the gradient of a point and the radius it would form with a center candidate to be able to vote. The formula to get the equivalent angle is: `angle = acos(circle_perfectness)`. */ - float m_circleVisibilityRatioThresh; /*!< Visibility ratio threshold: minimun ratio of the circle must be visible in order to keep a circle candidate.*/ + float m_circleVisibilityRatioThresh; /*!< Visibility ratio threshold: minimum ratio of the circle must be visible in order to keep a circle candidate.*/ bool m_recordVotingPoints; /*!< If true, the edge-map points having voted for each circle will be stored.*/ // // Circle candidates merging attributes @@ -192,7 +187,7 @@ class VISP_EXPORT vpCircleHoughTransform * \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. * \param[in] recordVotingPoints If true, the edge-map points having voted for each circle will be stored. - * \param[in] visibilityRatioThresh Visibility threshold: which minimun ratio of the circle must be visible in order to keep a circle candidate. + * \param[in] visibilityRatioThresh Visibility threshold: which minimum ratio of the circle must be visible in order to keep a circle candidate. */ vpCircleHoughTransformParameters( const int &gaussianKernelSize @@ -702,7 +697,6 @@ class VISP_EXPORT vpCircleHoughTransform */ virtual std::vector detect(const vpImage &I); -#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) /** * \brief Perform Circle Hough Transform to detect the circles in in a gray-scale image. * Get only the \b nbCircles circles having the greatest number of votes. @@ -714,8 +708,6 @@ class VISP_EXPORT vpCircleHoughTransform * of votes detected in the image. */ virtual std::vector detect(const vpImage &I, const int &nbCircles); -#endif -//@} /*! * \brief Compute the mask containing pixels that voted for the \b detections. @@ -726,12 +718,10 @@ class VISP_EXPORT vpCircleHoughTransform */ #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) void computeVotingMask(const vpImage &I, const std::vector &detections, - std::optional< vpImage > &mask, - std::optional>>> &opt_votingPoints) const; + std::optional< vpImage > &mask, std::optional>>> &opt_votingPoints) const; #else void computeVotingMask(const vpImage &I, const std::vector &detections, - vpImage **mask, - std::vector>> **opt_votingPoints) const; + vpImage **mask, std::vector > > **opt_votingPoints) const; #endif /** @name Configuration from files */ @@ -1253,7 +1243,7 @@ class VISP_EXPORT vpCircleHoughTransform * \param[out] votingPoints List of edge-map points having voted of the circle candidates. */ virtual void mergeCandidates(std::vector &circleCandidates, std::vector &circleCandidatesVotes, - std::vector &circleCandidatesProba, std::vector>> &votingPoints); + std::vector &circleCandidatesProba, std::vector > > &votingPoints); vpCircleHoughTransformParameters m_algoParams; /*!< Attributes containing all the algorithm parameters.*/ // // Gaussian smoothing attributes @@ -1279,12 +1269,12 @@ class VISP_EXPORT vpCircleHoughTransform std::vector m_circleCandidates; /*!< List of the candidate circles.*/ std::vector m_circleCandidatesProbabilities; /*!< Probabilities of each candidate circle that is kept.*/ std::vector m_circleCandidatesVotes; /*!< Number of pixels voting for each candidate circle that is kept.*/ - std::vector>> m_circleCandidatesVotingPoints; /*!< Points that voted for each circle candidate.*/ + std::vector > > m_circleCandidatesVotingPoints; /*!< Points that voted for each circle candidate.*/ // // Circle candidates merging attributes std::vector m_finalCircles; /*!< List of the final circles, i.e. the ones resulting from the merge of the circle candidates.*/ std::vector m_finalCirclesProbabilities; /*!< Probabilities of each final circle, i.e. resulting from the merge of the circle candidates.*/ std::vector m_finalCircleVotes; /*!< Number of votes for the final circles.*/ - std::vector>> m_finalCirclesVotingPoints; /*!< Points that voted for each final circle.*/ + std::vector > > m_finalCirclesVotingPoints; /*!< Points that voted for each final circle.*/ }; #endif diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 018292328c..02a6464af7 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -28,14 +28,12 @@ * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#include - #include #include #include -#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11) +#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98) namespace { // Sorting by decreasing probabilities @@ -78,6 +76,18 @@ float computeEffectiveRadius(const float &votes, const float &weigthedSumRadius) } return r_effective; } + +void +scaleFilter(vpArray2D &filter, const float &scale) +{ + unsigned int nbRows = filter.getRows(); + unsigned int nbCols = filter.getCols(); + for (unsigned int r = 0; r < nbRows; ++r) { + for (unsigned int c = 0; c < nbCols; ++c) { + filter[r][c] = filter[r][c] * scale; + } + } +} }; #endif @@ -157,21 +167,21 @@ vpCircleHoughTransform::initGaussianFilters() m_cannyVisp.setGaussianFilterParameters(m_algoParams.m_gaussianKernelSize, m_algoParams.m_gaussianStdev); } -void -scaleFilter(vpArray2D &filter, const float &scale) -{ - unsigned int nbRows = filter.getRows(); - unsigned int nbCols = filter.getCols(); - for (unsigned int r = 0; r < nbRows; ++r) { - for (unsigned int c = 0; c < nbCols; ++c) { - filter[r][c] = filter[r][c] * scale; - } - } -} - void vpCircleHoughTransform::initGradientFilters() { +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) // Check if cxx11 or higher + // Helper to apply the scale to the raw values of the filters + auto scaleFilter = [](vpArray2D &filter, const float &scale) { + const unsigned int nbRows = filter.getRows(); + const unsigned int nbCols = filter.getCols(); + for (unsigned int r = 0; r < nbRows; ++r) { + for (unsigned int c = 0; c < nbCols; ++c) { + filter[r][c] = filter[r][c] * scale; + } + }}; +#endif + if ((m_algoParams.m_gradientFilterKernelSize % 2) != 1) { throw vpException(vpException::badValue, "Gradient filters Kernel size should be odd."); } @@ -235,7 +245,7 @@ vpCircleHoughTransform::detect(const vpImage &I, const int &nbCir } #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) -// Sorting by decreasing probabilities + // Sorting by decreasing probabilities auto hasBetterProba = [](std::pair a, std::pair b) { return (a.second > b.second); @@ -364,12 +374,9 @@ void vpCircleHoughTransform::computeVotingMask(const vpImage &I, #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17) mask = std::nullopt; opt_votingPoints = std::nullopt; -#elif (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) +#else *mask = nullptr; *opt_votingPoints = nullptr; -#else - *mask = NULL; - *opt_votingPoints = NULL; #endif return; } @@ -973,6 +980,7 @@ vpCircleHoughTransform::computeCircleCandidates() } } +#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11) // Lambda to compute the effective radius (i.e. barycenter) of each radius bin auto computeEffectiveRadius = [](const float &votes, const float &weigthedSumRadius) { float r_effective = -1.f; @@ -981,6 +989,7 @@ vpCircleHoughTransform::computeCircleCandidates() } return r_effective; }; +#endif // Merging similar candidates std::vector v_r_effective; // Vector of radius of each candidate after the merge step