From 73be3ddbae88099081d45819bfec0872271a7145 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Fri, 6 Oct 2023 14:16:20 +0200 Subject: [PATCH 01/14] [CORPS] Moved the vp::equalizeHistogram implementation in the vpHistogram class --- modules/core/include/visp3/core/vpHistogram.h | 1 + .../core/src/tools/histogram/vpHistogram.cpp | 46 +++++++++++++++ modules/imgproc/src/vpImgproc.cpp | 57 +++---------------- 3 files changed, 55 insertions(+), 49 deletions(-) diff --git a/modules/core/include/visp3/core/vpHistogram.h b/modules/core/include/visp3/core/vpHistogram.h index 36afaeaec2..20dabd6a1d 100644 --- a/modules/core/include/visp3/core/vpHistogram.h +++ b/modules/core/include/visp3/core/vpHistogram.h @@ -234,6 +234,7 @@ class VISP_EXPORT vpHistogram }; void calculate(const vpImage &I, unsigned int nbins = 256, unsigned int nbThreads = 1); + void equalize(const vpImage &I, vpImage &Iout); void display(const vpImage &I, const vpColor &color = vpColor::white, unsigned int thickness = 2, unsigned int maxValue_ = 0); diff --git a/modules/core/src/tools/histogram/vpHistogram.cpp b/modules/core/src/tools/histogram/vpHistogram.cpp index 9d34be9dd7..de74e105c6 100644 --- a/modules/core/src/tools/histogram/vpHistogram.cpp +++ b/modules/core/src/tools/histogram/vpHistogram.cpp @@ -330,6 +330,52 @@ void vpHistogram::calculate(const vpImage &I, unsigned int nbins, } } +void vpHistogram::equalize(const vpImage &I, vpImage &Iout) +{ + // Compute the histogram + calculate(I); + + // Calculate the cumulative distribution function + unsigned int cdf[256]; + unsigned int cdfMin = std::numeric_limits::max(), cdfMax = 0; + unsigned int minValue = std::numeric_limits::max(), maxValue = 0; + cdf[0] = histogram[0]; + + if (cdf[0] < cdfMin && cdf[0] > 0) { + cdfMin = cdf[0]; + minValue = 0; + } + + for (unsigned int i = 1; i < 256; i++) { + cdf[i] = cdf[i - 1] + histogram[i]; + + if (cdf[i] < cdfMin && cdf[i] > 0) { + cdfMin = cdf[i]; + minValue = i; + } + + if (cdf[i] > cdfMax) { + cdfMax = cdf[i]; + maxValue = i; + } + } + + unsigned int nbPixels = I.getWidth() * I.getHeight(); + if (nbPixels == cdfMin) { + // Only one brightness value in the image + return; + } + + // Construct the look-up table + unsigned char lut[256]; + for (unsigned int x = minValue; x <= maxValue; x++) { + lut[x] = vpMath::round((cdf[x] - cdfMin) / (double)(nbPixels - cdfMin) * 255.0); + } + + Iout = I; + Iout.performLut(lut); +} + /*! Display the histogram distribution in an image, the minimal image size is 36x36 px. diff --git a/modules/imgproc/src/vpImgproc.cpp b/modules/imgproc/src/vpImgproc.cpp index d607689620..3e7fa52d62 100644 --- a/modules/imgproc/src/vpImgproc.cpp +++ b/modules/imgproc/src/vpImgproc.cpp @@ -110,60 +110,19 @@ void adjust(const vpImage &I1, vpImage &I2, double alpha, double void equalizeHistogram(vpImage &I) { - if (I.getWidth() * I.getHeight() == 0) { + vpImage Icpy = I; + vp::equalizeHistogram(Icpy, I); +} + +void equalizeHistogram(const vpImage &I1, vpImage &I2) +{ + if (I1.getWidth() * I1.getHeight() == 0) { return; } // Calculate the histogram vpHistogram hist; - hist.calculate(I); - - // Calculate the cumulative distribution function - unsigned int cdf[256]; - unsigned int cdfMin = /*std::numeric_limits::max()*/ UINT_MAX, cdfMax = 0; - unsigned int minValue = - /*std::numeric_limits::max()*/ UINT_MAX, - maxValue = 0; - cdf[0] = hist[0]; - - if (cdf[0] < cdfMin && cdf[0] > 0) { - cdfMin = cdf[0]; - minValue = 0; - } - - for (unsigned int i = 1; i < 256; i++) { - cdf[i] = cdf[i - 1] + hist[i]; - - if (cdf[i] < cdfMin && cdf[i] > 0) { - cdfMin = cdf[i]; - minValue = i; - } - - if (cdf[i] > cdfMax) { - cdfMax = cdf[i]; - maxValue = i; - } - } - - unsigned int nbPixels = I.getWidth() * I.getHeight(); - if (nbPixels == cdfMin) { - // Only one brightness value in the image - return; - } - - // Construct the look-up table - unsigned char lut[256]; - for (unsigned int x = minValue; x <= maxValue; x++) { - lut[x] = vpMath::round((cdf[x] - cdfMin) / (double)(nbPixels - cdfMin) * 255.0); - } - - I.performLut(lut); -} - -void equalizeHistogram(const vpImage &I1, vpImage &I2) -{ - I2 = I1; - vp::equalizeHistogram(I2); + hist.equalize(I1, I2); } void equalizeHistogram(vpImage &I, bool useHSV) From 3fe2f212d74421efa63c55674920b7ea35a8ad52 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Fri, 6 Oct 2023 14:20:32 +0200 Subject: [PATCH 02/14] [CORPS] The vpImageFilter::filter functions are now templated both in terms of image type and filter type. Adapted calls to these methods in some classes. [WIP] Added a first draft of Canny auto-threshold [CORPS] Changed the way the gradients are computed in vpCannyEdgeDetection and vpCircleHoughTransform classes --- .../include/visp3/core/vpCannyEdgeDetection.h | 33 +- .../core/include/visp3/core/vpImageFilter.h | 271 ++----- .../core/src/image/vpCannyEdgeDetection.cpp | 49 +- modules/core/src/image/vpImageFilter.cpp | 753 ++++++++++-------- .../visp3/imgproc/vpCircleHoughTransform.h | 30 +- .../imgproc/src/vpCircleHoughTransform.cpp | 39 +- 6 files changed, 592 insertions(+), 583 deletions(-) diff --git a/modules/core/include/visp3/core/vpCannyEdgeDetection.h b/modules/core/include/visp3/core/vpCannyEdgeDetection.h index 0ca547ddac..d6ee6a6f46 100644 --- a/modules/core/include/visp3/core/vpCannyEdgeDetection.h +++ b/modules/core/include/visp3/core/vpCannyEdgeDetection.h @@ -59,11 +59,13 @@ class VISP_EXPORT vpCannyEdgeDetection // // Gaussian smoothing attributes int m_gaussianKernelSize; /*!< Size of the Gaussian filter kernel used to smooth the input image. Must be an odd number.*/ float m_gaussianStdev; /*!< Standard deviation of the Gaussian filter.*/ + vpArray2D m_fg; /*!< Array that contains the Gaussian kernel.*/ // // Gradient computation attributes bool m_areGradientAvailable; /*!< Set to true if the user provides the gradient images, false otherwise. In the latter case, the class will compute the gradients.*/ - vpArray2D m_fg; /*!< Array that contains the Gaussian kernel.*/ - vpArray2D m_fgDg; /*!< Array that contains the derivative of the Gaussian kernel.*/ + unsigned int m_sobelAperture; /*!< The size of the Sobel kernels used to compute the gradients of the image.*/ + vpArray2D m_sobelX; /*!< Array that contains the Sobel kernel along the X-axis.*/ + vpArray2D m_sobelY; /*!< Array that contains the Sobel kernel along the Y-axis.*/ vpImage m_dIx; /*!< X-axis gradient.*/ vpImage m_dIy; /*!< Y-axis gradient.*/ @@ -82,10 +84,14 @@ class VISP_EXPORT vpCannyEdgeDetection /** @name Constructors and initialization */ //@{ /** - * \brief Initialize the Gaussian filters used to filter the input image and - * to compute its gradients. + * \brief Initialize the Gaussian filters used to filter the input image. */ void initGaussianFilters(); + + /** + * \brief Initialize the Sobel filters used to compute the input image gradients. + */ + void initSobelFilters(); //@} /** @name Different steps methods */ @@ -151,10 +157,11 @@ class VISP_EXPORT vpCannyEdgeDetection * * \param[in] gaussianKernelSize : The size of the Gaussian filter kernel. Must be odd. * \param[in] gaussianStdev : The standard deviation of the Gaussian filter. + * \param[in] sobelAperture : The size of the Sobel filters kernel. Must be odd. * \param[in] lowerThreshold : The lower threshold of the hysteresis thresholding step. If negative, will be computed from the upper threshold. * \param[in] upperThreshold : The upper threshold of the hysteresis thresholding step. If negative, will be computed from the median of the gray values of the image. */ - vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev, + vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev, const unsigned int &sobelAperture, const float &lowerThreshold = -1., const float &upperThreshold = -1.); // // Configuration from files @@ -187,6 +194,7 @@ class VISP_EXPORT vpCannyEdgeDetection detector.m_gaussianKernelSize = j.value("gaussianSize", detector.m_gaussianKernelSize); detector.m_gaussianStdev = j.value("gaussianStdev", detector.m_gaussianStdev); detector.m_lowerThreshold = j.value("lowerThreshold", detector.m_lowerThreshold); + detector.m_sobelAperture = j.value("sobelAperture", detector.m_sobelAperture); detector.m_upperThreshold = j.value("upperThreshold", detector.m_upperThreshold); } @@ -202,6 +210,7 @@ class VISP_EXPORT vpCannyEdgeDetection {"gaussianSize", detector.m_gaussianKernelSize}, {"gaussianStdev", detector.m_gaussianStdev}, {"lowerThreshold", detector.m_lowerThreshold}, + {"sobelAperture", detector.m_sobelAperture}, {"upperThreshold", detector.m_upperThreshold} }; } #endif @@ -284,6 +293,20 @@ class VISP_EXPORT vpCannyEdgeDetection m_gaussianStdev = stdev; initGaussianFilters(); } + + /** + * @brief Set the Gaussian Filters kernel size and standard deviation + * and initialize the aforementioned filters. + * + * \param[in] kernelSize : The size of the Gaussian filters kernel. + * \param[in] stdev : The standard deviation of the Gaussian filters used to blur and + * compute the gradient of the image. + */ + inline void setSobelAperture(const unsigned int &sobelAperture) + { + m_sobelAperture = sobelAperture; + initGaussianFilters(); + } //@} }; #endif diff --git a/modules/core/include/visp3/core/vpImageFilter.h b/modules/core/include/visp3/core/vpImageFilter.h index 054fcba05b..4f0e572be7 100644 --- a/modules/core/include/visp3/core/vpImageFilter.h +++ b/modules/core/include/visp3/core/vpImageFilter.h @@ -80,7 +80,7 @@ class VISP_EXPORT vpImageFilter \param r : coordinates (row) of the pixel \param c : coordinates (column) of the pixel */ - template static double derivativeFilterX(const vpImage &I, unsigned int r, unsigned int c) + template static double derivativeFilterX(const vpImage &I, unsigned int r, unsigned int c) { return (2047.0 * (I[r][c + 1] - I[r][c - 1]) + 913.0 * (I[r][c + 2] - I[r][c - 2]) + 112.0 * (I[r][c + 3] - I[r][c - 3])) / 8418.0; @@ -93,7 +93,7 @@ class VISP_EXPORT vpImageFilter \param r : coordinates (row) of the pixel \param c : coordinates (column) of the pixel */ - template static double derivativeFilterY(const vpImage &I, unsigned int r, unsigned int c) + template static double derivativeFilterY(const vpImage &I, unsigned int r, unsigned int c) { return (2047.0 * (I[r + 1][c] - I[r - 1][c]) + 913.0 * (I[r + 2][c] - I[r - 2][c]) + 112.0 * (I[r + 3][c] - I[r - 3][c])) / 8418.0; @@ -112,8 +112,8 @@ class VISP_EXPORT vpImageFilter \sa vpImageFilter::getGaussianDerivativeKernel() */ - template - static FilterType derivativeFilterX(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + template + static FilterType derivativeFilterX(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { unsigned int i; FilterType result; @@ -139,8 +139,8 @@ class VISP_EXPORT vpImageFilter \sa vpImageFilter::getGaussianDerivativeKernel() */ - template - static FilterType derivativeFilterY(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + template + static FilterType derivativeFilterY(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { unsigned int i; FilterType result; @@ -181,8 +181,8 @@ class VISP_EXPORT vpImageFilter \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) + template + static void filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve = false) { unsigned int size_y = M.getRows(), size_x = M.getCols(); unsigned int half_size_y = size_y / 2, half_size_x = size_x / 2; @@ -221,6 +221,9 @@ class VISP_EXPORT vpImageFilter } } + template + static void filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve = false) = delete; + /*! Apply a filter to an image: \f[ @@ -233,8 +236,8 @@ class VISP_EXPORT vpImageFilter \param M : Filter kernel. \param convolve : If true, perform a convolution otherwise a correlation. */ - template - static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, + template + static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve = false) { unsigned int size = M.getRows(); @@ -281,24 +284,13 @@ class VISP_EXPORT vpImageFilter } } - static void sepFilter(const vpImage &I, vpImage &If, const vpColVector &kernelH, const vpColVector &kernelV); + template + static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve) = delete; - /*! - Apply a separable filter. - \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. - \param I : The original image. - \param GI : The filtered image. - \param filter : The separable filter. - \param size : The size of the filter. - */ - template - static void filter(const vpImage &I, vpImage &GI, const FilterType *filter, unsigned int size) - { - vpImage GIx; - filterX(I, GIx, filter, size); - filterY(GIx, GI, filter, size); - GIx.destroy(); - } + template + static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve) = delete; + + static void sepFilter(const vpImage &I, vpImage &If, const vpColVector &kernelH, const vpColVector &kernelV); /*! Apply a separable filter. @@ -308,12 +300,12 @@ class VISP_EXPORT vpImageFilter \param filter: The separable filter. \param size: The size of the filter. */ - template - static void filter(const vpImage &I, vpImage &GI, const FilterType *filter, unsigned int size) + template + static void filter(const vpImage &I, vpImage &GI, const FilterType *filter, unsigned int size) { vpImage GIx; - filterX(I, GIx, filter, size); - filterY(GIx, GI, filter, size); + filterX(I, GIx, filter, size); + filterY(GIx, GI, filter, size); GIx.destroy(); } @@ -326,36 +318,19 @@ class VISP_EXPORT vpImageFilter return (unsigned char)((1. * I[i - 2][j] + 4. * I[i - 1][j] + 6. * I[i][j] + 4. * I[i + 1][j] + 1. * I[i + 2][j]) / 16.); } - template - static void filterX(const vpImage &I, vpImage &dIx, const FilterType *filter, unsigned int size) - { - 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); - } - 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 = I.getWidth() - (size - 1) / 2; j < I.getWidth(); j++) { - dIx[i][j] = vpImageFilter::filterXRightBorder(I, i, j, filter, size); - } - } - } - - template - static void filterX(const vpImage &I, vpImage &dIx, const FilterType *filter, unsigned int size) + template + static void filterX(const vpImage &I, vpImage &dIx, const FilterType *filter, unsigned int size) { 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); + 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); + 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); + dIx[i][j] = vpImageFilter::filterXRightBorder(I, i, j, filter, size); } } } @@ -365,8 +340,8 @@ class VISP_EXPORT vpImageFilter 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); - template - static inline FilterType filterX(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + template + static inline FilterType filterX(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { FilterType result; @@ -414,8 +389,8 @@ class VISP_EXPORT vpImageFilter return result + filter[0] * I[r][c].B; } - template - static inline FilterType filterXLeftBorder(const vpImage &I, unsigned int r, unsigned int c, + template + static inline FilterType filterXLeftBorder(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { FilterType result; @@ -479,8 +454,8 @@ class VISP_EXPORT vpImageFilter return result + filter[0] * I[r][c].B; } - template - static inline FilterType filterXRightBorder(const vpImage &I, unsigned int r, unsigned int c, + template + static inline FilterType filterXRightBorder(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { FilterType result; @@ -544,103 +519,34 @@ class VISP_EXPORT vpImageFilter return result + filter[0] * I[r][c].B; } - template - static inline FilterType filterX(const vpImage &I, unsigned int r, unsigned int c, - const FilterType *filter, unsigned int size) - { - FilterType result; - - result = 0; - - for (unsigned int i = 1; i <= (size - 1) / 2; i++) { - result += filter[i] * (I[r][c + i] + I[r][c - i]); - } - return result + filter[0] * I[r][c]; - } - - template - static inline FilterType filterXLeftBorder(const vpImage &I, unsigned int r, unsigned int c, - const FilterType *filter, unsigned int size) - { - FilterType result; - - result = 0; - - for (unsigned int i = 1; i <= (size - 1) / 2; i++) { - if (c > i) - result += filter[i] * (I[r][c + i] + I[r][c - i]); - else - result += filter[i] * (I[r][c + i] + I[r][i - c]); - } - return result + filter[0] * I[r][c]; - } - - template - static inline FilterType filterXRightBorder(const vpImage &I, unsigned int r, unsigned int c, - const FilterType *filter, unsigned int size) - { - FilterType result; - - result = 0; - - for (unsigned int i = 1; i <= (size - 1) / 2; i++) { - if (c + i < I.getWidth()) - result += filter[i] * (I[r][c + i] + I[r][c - i]); - else - result += filter[i] * (I[r][2 * I.getWidth() - c - i - 1] + I[r][c - i]); - } - return result + filter[0] * I[r][c]; - } - - template - static void filterY(const vpImage &I, vpImage &dIy, const FilterType *filter, unsigned int size) - { - 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); - } - } - 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 = 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); - } - } - } - static void filterY(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); 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); - template - static void filterY(const vpImage &I, vpImage &dIy, const FilterType *filter, unsigned int size) + template + static void filterY(const vpImage &I, vpImage &dIy, const FilterType *filter, unsigned int size) { 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); + 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); + 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); + dIy[i][j] = vpImageFilter::filterYBottomBorder(I, i, j, filter, size); } } } - template - static inline FilterType filterY(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) + template + static inline FilterType filterY(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { FilterType result; @@ -687,8 +593,8 @@ class VISP_EXPORT vpImageFilter return result + filter[0] * I[r][c].B; } - template - static inline FilterType filterYTopBorder(const vpImage &I, unsigned int r, unsigned int c, + template + static inline FilterType filterYTopBorder(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { FilterType result; @@ -749,8 +655,8 @@ class VISP_EXPORT vpImageFilter return result + filter[0] * I[r][c].B; } - template - static inline FilterType filterYBottomBorder(const vpImage &I, unsigned int r, unsigned int c, + template + static inline FilterType filterYBottomBorder(const vpImage &I, unsigned int r, unsigned int c, const FilterType *filter, unsigned int size) { FilterType result; @@ -814,54 +720,6 @@ class VISP_EXPORT vpImageFilter return result + filter[0] * I[r][c].B; } - template - static inline FilterType filterYTopBorder(const vpImage &I, unsigned int r, unsigned int c, - const FilterType *filter, unsigned int size) - { - FilterType result; - - result = 0; - - for (unsigned int i = 1; i <= (size - 1) / 2; i++) { - if (r > i) - result += filter[i] * (I[r + i][c] + I[r - i][c]); - else - result += filter[i] * (I[r + i][c] + I[i - r][c]); - } - return result + filter[0] * I[r][c]; - } - - template - static inline FilterType filterYBottomBorder(const vpImage &I, unsigned int r, unsigned int c, - const FilterType *filter, unsigned int size) - { - FilterType result; - - result = 0; - - for (unsigned int i = 1; i <= (size - 1) / 2; i++) { - if (r + i < I.getHeight()) - result += filter[i] * (I[r + i][c] + I[r - i][c]); - else - result += filter[i] * (I[2 * I.getHeight() - r - i - 1][c] + I[r - i][c]); - } - return result + filter[0] * I[r][c]; - } - - template - static inline FilterType filterY(const vpImage &I, unsigned int r, unsigned int c, - const FilterType *filter, unsigned int size) - { - FilterType result; - - result = 0; - - for (unsigned int i = 1; i <= (size - 1) / 2; i++) { - result += filter[i] * (I[r + i][c] + I[r - i][c]); - } - return result + filter[0] * I[r][c]; - } - /*! Apply a Gaussian blur to an image. \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. @@ -874,44 +732,20 @@ class VISP_EXPORT vpImageFilter \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) + template + static void gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size = 7, FilterType sigma = 0., bool normalize = true) { 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); + vpImageFilter::filterY(GIx, GI, fg, size); GIx.destroy(); delete[] fg; } static void gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size = 7, double sigma = 0., bool normalize = true); - /*! - Apply a Gaussian blur to a double image. - \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. - \param I : Input double 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. - */ - template - static void gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size = 7, FilterType sigma = 0., bool normalize = true) - { - 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); - GIx.destroy(); - delete[] fg; - } - /*! Apply a 5x5 Gaussian filter to an image pixel. @@ -1077,7 +911,7 @@ class VISP_EXPORT vpImageFilter const FilterType *gaussianDerivativeKernel, unsigned int size) { vpImage GIy; - vpImageFilter::filterY(I, GIy, gaussianKernel, size); + vpImageFilter::filterY(I, GIy, gaussianKernel, size); vpImageFilter::getGradX(GIy, dIx, gaussianDerivativeKernel, size); } @@ -1139,7 +973,7 @@ class VISP_EXPORT vpImageFilter const FilterType *gaussianDerivativeKernel, unsigned int size) { vpImage GIx; - vpImageFilter::filterX(I, GIx, gaussianKernel, size); + vpImageFilter::filterX(I, GIx, gaussianKernel, size); vpImageFilter::getGradY(GIx, dIy, gaussianDerivativeKernel, size); } @@ -1222,9 +1056,10 @@ class VISP_EXPORT vpImageFilter return 1 / 16.0; } +static float computeCannyThreshold(const vpImage &I, float &lowerThresh); + #if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) static float computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_blur, float &lowerThresh); - static float computeCannyThreshold(const vpImage &I, float &lowerThresh); static float median(const cv::Mat &cv_I); static float median(const vpImage &Isrc); static std::vector median(const vpImage &Isrc); diff --git a/modules/core/src/image/vpCannyEdgeDetection.cpp b/modules/core/src/image/vpCannyEdgeDetection.cpp index a9a050cd36..716f164a8c 100644 --- a/modules/core/src/image/vpCannyEdgeDetection.cpp +++ b/modules/core/src/image/vpCannyEdgeDetection.cpp @@ -40,21 +40,25 @@ vpCannyEdgeDetection::vpCannyEdgeDetection() : m_gaussianKernelSize(3) , m_gaussianStdev(1.) , m_areGradientAvailable(false) + , m_sobelAperture(3) , m_lowerThreshold(-1.) , m_upperThreshold(-1.) { initGaussianFilters(); + initSobelFilters(); } -vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev +vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev, const unsigned int &sobelAperture , const float &lowerThreshold, const float &upperThreshold) : m_gaussianKernelSize(gaussianKernelSize) , m_gaussianStdev(gaussianStdev) , m_areGradientAvailable(false) + , m_sobelAperture(sobelAperture) , m_lowerThreshold(lowerThreshold) , m_upperThreshold(upperThreshold) { initGaussianFilters(); + initSobelFilters(); } #ifdef VISP_HAVE_NLOHMANN_JSON @@ -86,6 +90,7 @@ vpCannyEdgeDetection::initFromJSON(const std::string &jsonPath) *this = j; // Call from_json(const json& j, vpDetectionCircle2D& *this) to read json file.close(); initGaussianFilters(); + initSobelFilters(); } #endif @@ -97,8 +102,18 @@ vpCannyEdgeDetection::initGaussianFilters() } m_fg.resize(1, (m_gaussianKernelSize + 1)/2); vpImageFilter::getGaussianKernel(m_fg.data, m_gaussianKernelSize, m_gaussianStdev, false); - m_fgDg.resize(1, (m_gaussianKernelSize + 1)/2); - vpImageFilter::getGaussianDerivativeKernel(m_fgDg.data, m_gaussianKernelSize, m_gaussianStdev, false); +} + +void +vpCannyEdgeDetection::initSobelFilters() +{ + if ((m_sobelAperture % 2) == 0) { + throw(vpException(vpException::badValue, "The Sobel kernel size should be odd")); + } + m_sobelX.resize(m_sobelAperture, m_sobelAperture); + vpImageFilter::getSobelKernelX(m_sobelX.data, (m_sobelAperture - 1)/2); + m_sobelY.resize(m_sobelAperture, m_sobelAperture); + vpImageFilter::getSobelKernelY(m_sobelY.data, (m_sobelAperture - 1)/2); } // // Detection methods @@ -139,12 +154,15 @@ vpCannyEdgeDetection::detect(const vpImage &I) // // Step 4: hysteresis thresholding float upperThreshold = m_upperThreshold; - float lowerThreshold = m_lowerThreshold; - if (m_lowerThreshold < 0) { + if(upperThreshold < 0) { + upperThreshold = vpImageFilter::computeCannyThreshold(I, lowerThreshold); + } + else if (m_lowerThreshold < 0) { // Applying Canny recommendation to have the upper threshold 3 times greater than the lower threshold. lowerThreshold = m_upperThreshold / 3.f; } + std::cout << "lt = " << lowerThreshold << " ; ut = " << upperThreshold << std::endl; performHysteresisThresholding(lowerThreshold, upperThreshold); @@ -156,18 +174,15 @@ vpCannyEdgeDetection::detect(const vpImage &I) void vpCannyEdgeDetection::performFilteringAndGradientComputation(const vpImage &I) { - vpImageFilter::getGradXGauss2D(I, - m_dIx, - m_fg.data, - m_fgDg.data, - m_gaussianKernelSize - ); - vpImageFilter::getGradYGauss2D(I, - m_dIy, - m_fg.data, - m_fgDg.data, - m_gaussianKernelSize - ); + // Computing the Gaussian blurr + vpImage Iblur; + vpImage GIx; + vpImageFilter::filterX(I, GIx, m_fg.data, m_gaussianKernelSize); + vpImageFilter::filterY(GIx, Iblur, m_fg.data, m_gaussianKernelSize); + + // Computing the gradients + vpImageFilter::filter(Iblur,m_dIx,m_sobelX); + vpImageFilter::filter(Iblur,m_dIy,m_sobelY); } /** diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index 63da6cc2e2..239f31d2b0 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -42,19 +43,33 @@ /** * \cond DO_NOT_DOCUMENT */ -template<> -void vpImageFilter::filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve); +/*template<>*/template +void vpImageFilter::filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve); -template<> -void vpImageFilter::filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve); +/*template<>*/template +void vpImageFilter::filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve); template <> -void vpImageFilter::filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, +void vpImageFilter::filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve); template <> -void vpImageFilter::filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, +void vpImageFilter::filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve); + +/*template<>*/template +void vpImageFilter::filter(const vpImage &I, vpImage &GI, const float *filter, + unsigned int size); + +/*template<>*/template +void vpImageFilter::filter(const vpImage &I, vpImage &GI, const double *filter, + unsigned int size); + +/*template<>*/template +void vpImageFilter::filter(const vpImage &I, vpImage &GI, const float *filter, unsigned int size); + +/*template<>*/template +void vpImageFilter::filter(const vpImage &I, vpImage &GI, const double *filter, unsigned int size); /** * \endcond */ @@ -142,266 +157,22 @@ void vpImageFilter::sepFilter(const vpImage &I, vpImage & } } -#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. - */ -float vpImageFilter::median(const cv::Mat &channel) -{ - float m = (channel.rows * channel.cols) / 2.f; - int bin = 0; - float med = -1.0f; - - int histSize = 256; - float range[] = { 0, 256 }; - const float *histRange = { range }; - bool uniform = true; - bool accumulate = false; - cv::Mat hist; - cv::calcHist(&channel, 1, 0, cv::Mat(), hist, 1, &histSize, &histRange, uniform, accumulate); - - for (int i = 0; i < histSize && med < 0.0; ++i) { - bin += cvRound(hist.at(i)); - if (bin > m && med < 0.0) - med = static_cast(i); - } - - return med; -} - -/** - * \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] Isrc : Gray-level image in ViSP format. - * \return Gray level image median value. - * \sa \ref vpImageFilter::median() "vpImageFilter::median(const cv::Mat)" - */ -float vpImageFilter::median(const vpImage &Isrc) -{ - cv::Mat cv_I; - vpImageConvert::convert(Isrc, cv_I); - return median(cv_I); -} - -/** - * \brief Calculates the median value of a vpRGBa image. - * The result is ordered in RGB format. - * \param[in] Isrc : RGB image in ViSP format. Alpha channel is ignored. - * \return std::vector meds such as meds[0] = red-channel-median, meds[1] = green-channel-median - * and meds[2] = blue-channel-median. - * \sa \ref vpImageFilter::median() "vpImageFilter::median(const cv::Mat)" - */ -std::vector vpImageFilter::median(const vpImage &Isrc) -{ - cv::Mat cv_I_bgr; - vpImageConvert::convert(Isrc, cv_I_bgr); - std::vector channels; - cv::split(cv_I_bgr, channels); - std::vector meds(3); - const int orderMeds[] = { 2, 1, 0 }; // To keep the order R, G, B - const int orderCvChannels[] = { 0, 1, 2 }; // Because the order of the cv::Mat is B, G, R - for (unsigned int i = 0; i < 3; i++) { - meds[orderMeds[i]] = median(channels[orderCvChannels[i]]); - } - return meds; -} - -/** - * \brief Compute the upper Canny edge filter threshold. - * - * \param[in] cv_I : The image, in cv format. - * \param[in] p_cv_blur : If different from nullptr, must contain a blurred version of cv_I. - * \param[out] lowerThresh : The lower threshold for the Canny edge filter. - * \return The upper Canny edge filter threshold. - */ -float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_blur, float &lowerThresh) -{ - cv::Mat cv_I_blur; - if (p_cv_blur != nullptr) { - cv_I_blur = *p_cv_blur; - } - else { - cv::GaussianBlur(cv_I, cv_I_blur, cv::Size(9, 9), 2, 2); - } - - // Subsample image to reach a 256 x 256 size - int req_size = 256; - int orig_size = std::min(static_cast(cv_I.rows), static_cast(cv_I.cols)); - int scale_down = std::max(1, static_cast(orig_size / req_size)); - cv::Mat cv_I_scaled_down; - resize(cv_I_blur, cv_I_scaled_down, cv::Size(), scale_down, scale_down, cv::INTER_NEAREST); - - float median_pix = vpImageFilter::median(cv_I_scaled_down); - float lower = std::max(0.f, 0.7f * median_pix); - float upper = std::min(255.f, 1.3f * median_pix); - upper = std::max(1.f, upper); - lowerThresh = lower; - return upper; -} - -/** - * \brief Compute the upper Canny edge filter threshold. - * - * \param[in] I : The gray-scale image, in ViSP format. - * \param[in] lowerThresh : Canny lower threshold. - * \return The upper Canny edge filter threshold. - */ -float vpImageFilter::computeCannyThreshold(const vpImage &I, float &lowerThresh) -{ - cv::Mat cv_I; - vpImageConvert::convert(I, cv_I); - return computeCannyThreshold(cv_I, nullptr, lowerThresh); -} -#endif - -/*! - Apply the Canny edge operator on the image \e Isrc and return the resulting - image \e Ires. - - The following example shows how to use the method: - - \code -#include -#include - -int main() -{ - // Constants for the Canny operator. - const unsigned int gaussianFilterSize = 5; - const double thresholdCanny = 15; - const unsigned int apertureSobel = 3; - - // Image for the Canny edge operator - vpImage Isrc; - vpImage Icanny; - - // First grab the source image Isrc. - - // Apply the Canny edge operator and set the Icanny image. - vpImageFilter::canny(Isrc, Icanny, gaussianFilterSize, thresholdCanny, apertureSobel); - return (0); -} - \endcode - - \param Isrc : Image to apply the Canny edge detector to. - \param Ires : Filtered image (255 means an edge, 0 otherwise). - \param gaussianFilterSize : The size of the mask of the Gaussian filter to - apply (an odd number). - \param thresholdCanny : The upper threshold for the Canny operator. Only value - greater than this value are marked as an edge. If negative, it will be automatically - computed, along with the lower threshold. Otherwise, the lower threshold will be set to one third - of the thresholdCanny . - \param apertureSobel : Size of the mask for the Sobel operator (odd number). -*/ -void vpImageFilter::canny(const vpImage &Isrc, vpImage &Ires, - unsigned int gaussianFilterSize, float thresholdCanny, unsigned int apertureSobel) -{ - vpImageFilter::canny(Isrc, Ires, gaussianFilterSize, thresholdCanny / 3.f, thresholdCanny, apertureSobel); -} - -/*! - Apply the Canny edge operator on the image \e Isrc and return the resulting - image \e Ires. - - The following example shows how to use the method: - - \code -#include -#include - -int main() -{ - // Constants for the Canny operator. - const unsigned int gaussianFilterSize = 5; - const float upperThresholdCanny = 15; - const float lowerThresholdCanny = 5; - const unsigned int apertureSobel = 3; - - // Image for the Canny edge operator - vpImage Isrc; - vpImage Icanny; - - // First grab the source image Isrc. - - // Apply the Canny edge operator and set the Icanny image. - vpImageFilter::canny(Isrc, Icanny, gaussianFilterSize, lowerThresholdCanny, upperThresholdCanny, apertureSobel); - return (0); -} - \endcode - - \param Isrc : Image to apply the Canny edge detector to. - \param Ires : Filtered image (255 means an edge, 0 otherwise). - \param gaussianFilterSize : The size of the mask of the Gaussian filter to - apply (an odd number). - \param lowerThreshold : The lower threshold for the Canny operator. Values lower - than this value are rejected. If negative, it will be set to one third - of the thresholdCanny . - \param upperThreshold : The upper threshold for the Canny operator. Only value - greater than this value are marked as an edge. If negative, it will be automatically - computed, along with the lower threshold. Otherwise, the lower threshold will be set to one third - of the thresholdCanny . - \param apertureSobel : Size of the mask for the Sobel operator (odd number). -*/ -void vpImageFilter::canny(const vpImage &Isrc, vpImage &Ires, - unsigned int gaussianFilterSize, float lowerThreshold, float upperThreshold, unsigned int apertureSobel) -{ -#if defined(HAVE_OPENCV_IMGPROC) - cv::Mat img_cvmat, cv_I_blur, cv_dx, cv_dy, edges_cvmat; - vpImageConvert::convert(Isrc, img_cvmat); - cv::GaussianBlur(img_cvmat, cv_I_blur, cv::Size((int)gaussianFilterSize, (int)gaussianFilterSize), 0, 0); - cv::Sobel(cv_I_blur, cv_dx, CV_16S, 1, 0, apertureSobel); - cv::Sobel(cv_I_blur, cv_dy, CV_16S, 0, 1, apertureSobel); - float upperCannyThresh = upperThreshold; - float lowerCannyThresh = lowerThreshold; - if (upperCannyThresh < 0) { - upperCannyThresh = computeCannyThreshold(img_cvmat, &cv_I_blur, lowerCannyThresh); - } - else if (lowerCannyThresh < 0) { - lowerCannyThresh = upperCannyThresh / 3.f; - } - cv::Canny(cv_dx, cv_dy, edges_cvmat, lowerCannyThresh, upperCannyThresh, false); - vpImageConvert::convert(edges_cvmat, Ires); -#else - (void)apertureSobel; - float upperCannyThresh = upperThreshold; - float lowerCannyThresh = lowerThreshold; - if (upperCannyThresh < 0) { - throw(vpException(vpException::badValue, "OpenCV imgproc module missing to be able to compute automatically the Canny thresholds")); - } - else if (lowerCannyThresh < 0) { - lowerCannyThresh = upperCannyThresh / 3.; - } - vpCannyEdgeDetection edgeDetector(gaussianFilterSize, 0.1, lowerCannyThresh, upperCannyThresh); - Ires = edgeDetector.detect(Isrc); -#endif -} - /** * \cond DO_NOT_DOCUMENT */ -template<> -void vpImageFilter::filter(const vpImage &I, vpImage &GI, const float *filter, +/*template<>*/template +void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); -template<> -void vpImageFilter::filter(const vpImage &I, vpImage &GI, const double *filter, +/*template<>*/template +void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -template<> -void vpImageFilter::filter(const vpImage &I, vpImage &GI, const float *filter, unsigned int size); - -template<> -void vpImageFilter::filter(const vpImage &I, vpImage &GI, const double *filter, unsigned int size); - -template<> -void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const float *filter, - unsigned int size); +/*template<>*/template +void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); -template<> -void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const double *filter, - unsigned int size); +/*template<>*/template +void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); /** * \endcond */ @@ -431,19 +202,19 @@ void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, cons /** * \cond DO_NOT_DOCUMENT */ -template<> -void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); - -template<> -void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); - -template<> -void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const float *filter, +/*template<>*/template +void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); -template<> -void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, +/*template<>*/template +void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); + +/*template<>*/template +void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); + +/*template<>*/template +void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); /** * \endcond */ @@ -477,35 +248,17 @@ void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, cons /** * \cond DO_NOT_DOCUMENT */ -template<> -void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); +/*template<>*/template +void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, float sigma, bool normalize); -template<> -void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size) -{ - 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); - } - } - 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 = 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); - } - } -} +/*template<>*/template +void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize); -template<> -void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, float sigma, bool normalize); +/*template<>*/template +void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, float sigma, bool normalize); -template<> -void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize); +/*template<>*/template +void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize); /** * \endcond */ @@ -535,13 +288,7 @@ void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, /** * \cond DO_NOT_DOCUMENT */ -template<> -void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, float sigma, bool normalize); - -template<> -void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize); - -template<> +/*template<>*/template void vpImageFilter::getGaussianKernel(float *filter, unsigned int size, float sigma, bool normalize); template <> @@ -550,71 +297,71 @@ void vpImageFilter::getGaussianDerivativeKernel(float *filter, unsigned i template <> void vpImageFilter::getGaussianDerivativeKernel(double *filter, unsigned int size, double sigma, bool normalize); -template<> +/*template<>*/template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx); -template<> +/*template<>*/template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx); -template<> +/*template<>*/template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy); -template<> +/*template<>*/template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy); -template<> +/*template<>*/template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -template<> +/*template<>*/template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); /** @@ -674,17 +421,373 @@ void vpImageFilter::getGaussYPyramidal(const vpImage &I, vpImage< /** * \cond DO_NOT_DOCUMENT */ -template<> +/*template<>*/template double vpImageFilter::getSobelKernelX(double *filter, unsigned int size); -template<> +/*template<>*/template float vpImageFilter::getSobelKernelX(float *filter, unsigned int size); -template<> +/*template<>*/template double vpImageFilter::getSobelKernelY(double *filter, unsigned int size); -template<> +/*template<>*/template float vpImageFilter::getSobelKernelY(float *filter, unsigned int size); /** * \endcond */ + + +#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. + */ +float vpImageFilter::median(const cv::Mat &channel) +{ + float m = (channel.rows * channel.cols) / 2.f; + int bin = 0; + float med = -1.0f; + + int histSize = 256; + float range[] = { 0, 256 }; + const float *histRange = { range }; + bool uniform = true; + bool accumulate = false; + cv::Mat hist; + cv::calcHist(&channel, 1, 0, cv::Mat(), hist, 1, &histSize, &histRange, uniform, accumulate); + + for (int i = 0; i < histSize && med < 0.0; ++i) { + bin += cvRound(hist.at(i)); + if (bin > m && med < 0.0) + med = static_cast(i); + } + + return med; +} + +/** + * \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] Isrc : Gray-level image in ViSP format. + * \return Gray level image median value. + * \sa \ref vpImageFilter::median() "vpImageFilter::median(const cv::Mat)" + */ +float vpImageFilter::median(const vpImage &Isrc) +{ + cv::Mat cv_I; + vpImageConvert::convert(Isrc, cv_I); + return median(cv_I); +} + +/** + * \brief Calculates the median value of a vpRGBa image. + * The result is ordered in RGB format. + * \param[in] Isrc : RGB image in ViSP format. Alpha channel is ignored. + * \return std::vector meds such as meds[0] = red-channel-median, meds[1] = green-channel-median + * and meds[2] = blue-channel-median. + * \sa \ref vpImageFilter::median() "vpImageFilter::median(const cv::Mat)" + */ +std::vector vpImageFilter::median(const vpImage &Isrc) +{ + cv::Mat cv_I_bgr; + vpImageConvert::convert(Isrc, cv_I_bgr); + std::vector channels; + cv::split(cv_I_bgr, channels); + std::vector meds(3); + const int orderMeds[] = { 2, 1, 0 }; // To keep the order R, G, B + const int orderCvChannels[] = { 0, 1, 2 }; // Because the order of the cv::Mat is B, G, R + for (unsigned int i = 0; i < 3; i++) { + meds[orderMeds[i]] = median(channels[orderCvChannels[i]]); + } + return meds; +} + +/** + * \brief Compute the upper Canny edge filter threshold. + * + * \param[in] cv_I : The image, in cv format. + * \param[in] p_cv_blur : If different from nullptr, must contain a blurred version of cv_I. + * \param[out] lowerThresh : The lower threshold for the Canny edge filter. + * \return The upper Canny edge filter threshold. + */ +float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_blur, float &lowerThresh) +{ + const unsigned int gaussianKernelSize = 5; + const float gaussianStdev = 2.f; + const unsigned int sobelAperture = 3; + const float nonEdgeRate = 0.6; + const float thresholdRate = 0.6; + double w = cv_I.cols; + double h = cv_I.rows; + int bins = 256; + cv::Mat sobel, sobelx, sobely, sobelxabs, sobelyabs, img_blur; + + if(p_cv_blur == nullptr) { + // Apply Gaussian blur to the image + cv::Size gsz(gaussianKernelSize, gaussianKernelSize); + cv::GaussianBlur(cv_I, img_blur,gsz, gaussianStdev); + } + else { + img_blur = *p_cv_blur; + } + + // Compute the gradient of the blurred image + cv::Sobel(img_blur, sobelx, CV_16S, 1, 0, sobelAperture, 1, 0); + cv::convertScaleAbs(sobelx, sobelxabs); + cv::Sobel(img_blur, sobely, CV_16S, 0, 1, sobelAperture, 1, 0); + cv::convertScaleAbs(sobely, sobelyabs); + cv::addWeighted(sobelxabs, 1, sobelyabs, 1, 0, sobel); + sobel.convertTo(sobel, CV_8U); + + // Equalize the histogram + cv::Mat equalized; + cv::equalizeHist(sobel, equalized); + + // Compute the upper threshold from the equalized histogram + cv::Mat hist; + const float range[] = {0.f, 256.f}; // The upper boundary is exclusive + const float* ranges[] = { range }; + int channels[] = {0}; + bool dims = 1; // The number of dimensions of the histogram + int histSize[] = {bins}; + bool uniform = true; + bool accumulate = false; // Clear the histogram at the beginning of calcHist if false, does not clear it otherwise + cv::calcHist(&equalized, 1, channels, cv::Mat(), hist, dims, histSize, ranges, uniform, accumulate); + float accu = 0; + float t = (float)(nonEdgeRate * w * h); + float bon = 0; + for (int i = 0; i < bins; i++) { + float tf = hist.at(i); + accu = accu + tf; + if (accu > t) { + bon = (float)i; + break; + } + } + float upperThresh = std::max(bon, 1.f); + lowerThresh = thresholdRate * bon; + std::cout << "[computeCannyThreshold::cv] ut = " << upperThresh << " ; lt = " << lowerThresh << std::endl; + return upperThresh; +} +#endif + +template +void computeMeanMaxStdev(const vpImage &I, float &mean, float &max, float &stdev) +{ + max = std::numeric_limits::epsilon(); + mean = 0.; + stdev = 0.; + unsigned int nbRows = I.getRows(); + unsigned int nbCols = I.getCols(); + float scale = 1. / ((float)nbRows * (float)nbCols); + for (unsigned int r = 0; r < nbRows; r++) { + for (unsigned int c = 0; c < nbCols; c++) { + mean += I[r][c]; + max = std::max(max, static_cast(I[r][c])); + } + } + mean *= scale; + for (unsigned int r = 0; r < nbRows; r++) { + for (unsigned int c = 0; c < nbCols; c++) { + stdev += (I[r][c] - mean) * (I[r][c] - mean); + } + } + stdev *= scale; + stdev = std::sqrt(stdev); +} + + +/** + * \brief Compute the upper Canny edge filter threshold. + * + * \param[in] I : The gray-scale image, in ViSP format. + * \param[in] lowerThresh : Canny lower threshold. + * \return The upper Canny edge filter threshold. + */ +float vpImageFilter::computeCannyThreshold(const vpImage &I, float &lowerThresh) +{ + const unsigned int gaussianKernelSize = 5; + const float gaussianStdev = 2.; + const unsigned int apertureSize = 3; + const float nonEdgeRate = 0.6; + const float thresholdRate = 0.6; + + double w = I.getWidth(); + double h = I.getHeight(); + + // Computing the Gaussian blurr + gradients of the image + vpImage Iblur; + vpImageFilter::gaussianBlur(I, Iblur, gaussianKernelSize, gaussianStdev); + + vpArray2D sobelX(apertureSize, apertureSize); // Sobel kernel along x + vpImageFilter::getSobelKernelX(sobelX.data, (apertureSize - 1)/2); + vpArray2D sobelY(apertureSize, apertureSize); // Sobel kernel along x + vpImageFilter::getSobelKernelY(sobelY.data, (apertureSize - 1)/2); + vpImage dIx, dIy; + vpImageFilter::filter(Iblur,dIx,sobelX); + vpImageFilter::filter(Iblur,dIy,sobelY); + + // Computing the gradient of the image + vpImage dI(h, w); + for (unsigned int r = 0; r < h; r++) { + for (unsigned int c = 0; c < w; c++) { + float dx = (float)dIx[r][c]; + float dy = (float)dIy[r][c]; + float gradient = std::sqrt(dx * dx + dy *dy); + float gradientClamped = std::min(gradient, (float)std::numeric_limits::max()); + dI[r][c] = gradientClamped; + } + } + float mean = 0.f, max = 0.f, stdev = 0.f; + computeMeanMaxStdev(dI, mean, max, stdev); + std::stringstream title; + title << "[computeCannyThreshold::ViSP] Gradient, mean = " << mean << " +/- " << stdev << "; max = " << max; + std::cout << title.str() << std::endl; + + // Equalize the histogram + vpImage dIabs_equalized; + vpHistogram hist; + hist.equalize(dI, dIabs_equalized); + + // Compute the histogram + const unsigned int nbBins = 256; + hist.calculate(dIabs_equalized, nbBins); + float accu = 0; + float t = (float)(nonEdgeRate * w * h); + float bon = 0; + for (unsigned int i = 0; i < nbBins; i++) { + float tf = hist[i]; + accu = accu + tf; + if (accu > t) { + bon = (float)i; + break; + } + } + float upperThresh = std::max(bon, 1.f); + lowerThresh = thresholdRate * bon; + std::cout << "[computeCannyThreshold::ViSP] ut = " << upperThresh << " ; lt = " << lowerThresh << std::endl; + return upperThresh; +} + +/*! + Apply the Canny edge operator on the image \e Isrc and return the resulting + image \e Ires. + + The following example shows how to use the method: + + \code +#include +#include + +int main() +{ + // Constants for the Canny operator. + const unsigned int gaussianFilterSize = 5; + const double thresholdCanny = 15; + const unsigned int apertureSobel = 3; + + // Image for the Canny edge operator + vpImage Isrc; + vpImage Icanny; + + // First grab the source image Isrc. + + // Apply the Canny edge operator and set the Icanny image. + vpImageFilter::canny(Isrc, Icanny, gaussianFilterSize, thresholdCanny, apertureSobel); + return (0); +} + \endcode + + \param Isrc : Image to apply the Canny edge detector to. + \param Ires : Filtered image (255 means an edge, 0 otherwise). + \param gaussianFilterSize : The size of the mask of the Gaussian filter to + apply (an odd number). + \param thresholdCanny : The upper threshold for the Canny operator. Only value + greater than this value are marked as an edge. If negative, it will be automatically + computed, along with the lower threshold. Otherwise, the lower threshold will be set to one third + of the thresholdCanny . + \param apertureSobel : Size of the mask for the Sobel operator (odd number). +*/ +void vpImageFilter::canny(const vpImage &Isrc, vpImage &Ires, + unsigned int gaussianFilterSize, float thresholdCanny, unsigned int apertureSobel) +{ + vpImageFilter::canny(Isrc, Ires, gaussianFilterSize, thresholdCanny / 3.f, thresholdCanny, apertureSobel); +} + +/*! + Apply the Canny edge operator on the image \e Isrc and return the resulting + image \e Ires. + + The following example shows how to use the method: + + \code +#include +#include + +int main() +{ + // Constants for the Canny operator. + const unsigned int gaussianFilterSize = 5; + const float upperThresholdCanny = 15; + const float lowerThresholdCanny = 5; + const unsigned int apertureSobel = 3; + + // Image for the Canny edge operator + vpImage Isrc; + vpImage Icanny; + + // First grab the source image Isrc. + + // Apply the Canny edge operator and set the Icanny image. + vpImageFilter::canny(Isrc, Icanny, gaussianFilterSize, lowerThresholdCanny, upperThresholdCanny, apertureSobel); + return (0); +} + \endcode + + \param Isrc : Image to apply the Canny edge detector to. + \param Ires : Filtered image (255 means an edge, 0 otherwise). + \param gaussianFilterSize : The size of the mask of the Gaussian filter to + apply (an odd number). + \param lowerThreshold : The lower threshold for the Canny operator. Values lower + than this value are rejected. If negative, it will be set to one third + of the thresholdCanny . + \param upperThreshold : The upper threshold for the Canny operator. Only value + greater than this value are marked as an edge. If negative, it will be automatically + computed, along with the lower threshold. Otherwise, the lower threshold will be set to one third + of the thresholdCanny . + \param apertureSobel : Size of the mask for the Sobel operator (odd number). +*/ +void vpImageFilter::canny(const vpImage &Isrc, vpImage &Ires, + unsigned int gaussianFilterSize, float lowerThreshold, float upperThreshold, unsigned int apertureSobel) +{ +#if defined(HAVE_OPENCV_IMGPROC) + cv::Mat img_cvmat, cv_I_blur, cv_dx, cv_dy, edges_cvmat; + vpImageConvert::convert(Isrc, img_cvmat); + cv::GaussianBlur(img_cvmat, cv_I_blur, cv::Size((int)gaussianFilterSize, (int)gaussianFilterSize), 0, 0); + cv::Sobel(cv_I_blur, cv_dx, CV_16S, 1, 0, apertureSobel); + cv::Sobel(cv_I_blur, cv_dy, CV_16S, 0, 1, apertureSobel); + float upperCannyThresh = upperThreshold; + float lowerCannyThresh = lowerThreshold; + if (upperCannyThresh < 0) { + upperCannyThresh = computeCannyThreshold(img_cvmat, &cv_I_blur, lowerCannyThresh); + } + else if (lowerCannyThresh < 0) { + lowerCannyThresh = upperCannyThresh / 3.f; + } + cv::Canny(cv_dx, cv_dy, edges_cvmat, lowerCannyThresh, upperCannyThresh, false); + vpImageConvert::convert(edges_cvmat, Ires); +#else + (void)apertureSobel; + float upperCannyThresh = upperThreshold; + float lowerCannyThresh = lowerThreshold; + if (upperCannyThresh < 0) { + upperCannyThresh = computeCannyThreshold(Isrc, lowerCannyThresh); + } + else if (lowerCannyThresh < 0) { + lowerCannyThresh = upperCannyThresh / 3.; + } + vpCannyEdgeDetection edgeDetector(gaussianFilterSize, 0.1, lowerCannyThresh, upperCannyThresh); + Ires = edgeDetector.detect(Isrc); +#endif +} \ No newline at end of file diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index 9c503700c4..071fe036b4 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -468,7 +468,7 @@ class VISP_EXPORT vpCircleHoughTransform void init(const vpCircleHoughTransformParameters &algoParams); /** - * \brief Set the parameters of the Gaussian filter, that computes the + * \brief Set the parameters of the Gaussian filter, that permits to blur the * gradients of the image. * * \param[in] kernelSize The size of the Gaussian kernel. Must be an odd value. @@ -490,6 +490,23 @@ class VISP_EXPORT vpCircleHoughTransform initGaussianFilters(); } + /** + * \brief Set the parameters of the Sobel filters, that computes the + * gradients of the image. + * + * \param[in] apertureSize The size of the Sobel filters kernel. Must be an odd value. + */ + inline void setSobelAperture(const unsigned int &apertureSize) + { + m_algoParams.m_sobelKernelSize = apertureSize; + + if ((m_algoParams.m_sobelKernelSize % 2) != 1) { + throw vpException(vpException::badValue, "Sobel Kernel size should be odd."); + } + + initSobelFilters(); + } + /*! * Set the threshold for the Canny operator. * Only value greater than this value are marked as an edge. @@ -750,11 +767,15 @@ class VISP_EXPORT vpCircleHoughTransform private: /** - * \brief Initialize the Gaussian filters used to blur the image and - * compute the gradient images. + * \brief Initialize the Gaussian filters used to blur the image. */ void initGaussianFilters(); + /** + * \brief Initialize the Gaussian filters used to blur the image compute the gradient images. + */ + void initSobelFilters(); + /** * \brief Perform Gaussian smoothing on the input image to reduce the noise * that would perturbate the edge detection. @@ -819,9 +840,10 @@ class VISP_EXPORT vpCircleHoughTransform vpCircleHoughTransformParameters m_algoParams; /*!< Attributes containing all the algorithm parameters.*/ // // Gaussian smoothing attributes vpArray2D m_fg; - vpArray2D m_fgDg; // // Gradient computation attributes + vpArray2D m_sobelX; + vpArray2D m_sobelY; vpImage m_dIx; /*!< Gradient along the x-axis of the input image.*/ vpImage m_dIy; /*!< Gradient along the y-axis of the input image.*/ diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index a5e3d5b198..21c9a05fd9 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -39,12 +39,14 @@ vpCircleHoughTransform::vpCircleHoughTransform() : m_algoParams() { initGaussianFilters(); + initSobelFilters(); } vpCircleHoughTransform::vpCircleHoughTransform(const vpCircleHoughTransformParameters &algoParams) : m_algoParams(algoParams) { initGaussianFilters(); + initSobelFilters(); } void @@ -52,6 +54,7 @@ vpCircleHoughTransform::init(const vpCircleHoughTransformParameters &algoParams) { m_algoParams = algoParams; initGaussianFilters(); + initSobelFilters(); } vpCircleHoughTransform::~vpCircleHoughTransform() @@ -87,6 +90,7 @@ vpCircleHoughTransform::initFromJSON(const std::string &jsonPath) m_algoParams = j; // Call from_json(const json& j, vpDetectorDNN& *this) to read json file.close(); initGaussianFilters(); + initSobelFilters(); } void @@ -101,11 +105,22 @@ vpCircleHoughTransform::initGaussianFilters() { m_fg.resize(1, (m_algoParams.m_gaussianKernelSize + 1)/2); vpImageFilter::getGaussianKernel(m_fg.data, m_algoParams.m_gaussianKernelSize, m_algoParams.m_gaussianStdev, false); - m_fgDg.resize(1, (m_algoParams.m_gaussianKernelSize + 1)/2); - vpImageFilter::getGaussianDerivativeKernel(m_fgDg.data, m_algoParams.m_gaussianKernelSize, m_algoParams.m_gaussianStdev, false); m_cannyVisp.setGaussianFilterParameters(m_algoParams.m_gaussianKernelSize, m_algoParams.m_gaussianStdev); } +void +vpCircleHoughTransform::initSobelFilters() +{ + if ((m_algoParams.m_sobelKernelSize % 2) != 1) { + throw vpException(vpException::badValue, "Sobel Kernel size should be odd."); + } + m_sobelX.resize(m_algoParams.m_sobelKernelSize , m_algoParams.m_sobelKernelSize ); + vpImageFilter::getSobelKernelX(m_sobelX.data, (m_algoParams.m_sobelKernelSize - 1)/2); + m_sobelY.resize(m_algoParams.m_sobelKernelSize , m_algoParams.m_sobelKernelSize ); + vpImageFilter::getSobelKernelY(m_sobelY.data, (m_algoParams.m_sobelKernelSize - 1)/2); + m_cannyVisp.setSobelAperture(m_algoParams.m_sobelKernelSize); +} + std::vector vpCircleHoughTransform::detect(const vpImage &I) { @@ -210,18 +225,14 @@ vpCircleHoughTransform::detect(const vpImage &I) void vpCircleHoughTransform::computeGradientsAfterGaussianSmoothing(const vpImage &I) { - vpImageFilter::getGradXGauss2D(I, - m_dIx, - m_fg.data, - m_fgDg.data, - m_algoParams.m_gaussianKernelSize - ); - vpImageFilter::getGradYGauss2D(I, - m_dIy, - m_fg.data, - m_fgDg.data, - m_algoParams.m_gaussianKernelSize - ); + // 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); + + // Computing the gradients + vpImageFilter::filter(Iblur,m_dIx,m_sobelX); + vpImageFilter::filter(Iblur,m_dIy,m_sobelY); } void From 5fd94451c3ef511e9bdb4c30db406497c6b5177a Mon Sep 17 00:00:00 2001 From: rlagneau Date: Fri, 6 Oct 2023 16:08:17 +0200 Subject: [PATCH 03/14] [WIP] Working on the non-equalized gradient image works better in most cases --- modules/core/src/image/vpImageFilter.cpp | 44 +++--------------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index 239f31d2b0..172dbea53e 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -572,32 +572,6 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p } #endif -template -void computeMeanMaxStdev(const vpImage &I, float &mean, float &max, float &stdev) -{ - max = std::numeric_limits::epsilon(); - mean = 0.; - stdev = 0.; - unsigned int nbRows = I.getRows(); - unsigned int nbCols = I.getCols(); - float scale = 1. / ((float)nbRows * (float)nbCols); - for (unsigned int r = 0; r < nbRows; r++) { - for (unsigned int c = 0; c < nbCols; c++) { - mean += I[r][c]; - max = std::max(max, static_cast(I[r][c])); - } - } - mean *= scale; - for (unsigned int r = 0; r < nbRows; r++) { - for (unsigned int c = 0; c < nbCols; c++) { - stdev += (I[r][c] - mean) * (I[r][c] - mean); - } - } - stdev *= scale; - stdev = std::sqrt(stdev); -} - - /** * \brief Compute the upper Canny edge filter threshold. * @@ -610,7 +584,7 @@ float vpImageFilter::computeCannyThreshold(const vpImage &I, floa const unsigned int gaussianKernelSize = 5; const float gaussianStdev = 2.; const unsigned int apertureSize = 3; - const float nonEdgeRate = 0.6; + const float nonEdgeRate = 0.8; const float thresholdRate = 0.6; double w = I.getWidth(); @@ -634,25 +608,16 @@ float vpImageFilter::computeCannyThreshold(const vpImage &I, floa for (unsigned int c = 0; c < w; c++) { float dx = (float)dIx[r][c]; float dy = (float)dIy[r][c]; - float gradient = std::sqrt(dx * dx + dy *dy); + float gradient = std::abs(dx) + std::abs(dy); float gradientClamped = std::min(gradient, (float)std::numeric_limits::max()); dI[r][c] = gradientClamped; } } - float mean = 0.f, max = 0.f, stdev = 0.f; - computeMeanMaxStdev(dI, mean, max, stdev); - std::stringstream title; - title << "[computeCannyThreshold::ViSP] Gradient, mean = " << mean << " +/- " << stdev << "; max = " << max; - std::cout << title.str() << std::endl; - - // Equalize the histogram - vpImage dIabs_equalized; - vpHistogram hist; - hist.equalize(dI, dIabs_equalized); // Compute the histogram + vpHistogram hist; const unsigned int nbBins = 256; - hist.calculate(dIabs_equalized, nbBins); + hist.calculate(dI, nbBins); float accu = 0; float t = (float)(nonEdgeRate * w * h); float bon = 0; @@ -666,7 +631,6 @@ float vpImageFilter::computeCannyThreshold(const vpImage &I, floa } float upperThresh = std::max(bon, 1.f); lowerThresh = thresholdRate * bon; - std::cout << "[computeCannyThreshold::ViSP] ut = " << upperThresh << " ; lt = " << lowerThresh << std::endl; return upperThresh; } From eee29b9e99057d46f343847fa7f787567ddfe71e Mon Sep 17 00:00:00 2001 From: rlagneau Date: Mon, 9 Oct 2023 16:58:36 +0200 Subject: [PATCH 04/14] [CORPS] Added choice of the backend (ViSP/OpenCV) and of the filtering + gradient operator (only Gaussian blur + Sobel at the moment) and upper threshold ratio + lower threshold ratio are now parameters --- .../include/visp3/core/vpCannyEdgeDetection.h | 88 +++- .../core/include/visp3/core/vpImageFilter.h | 51 ++- .../core/src/image/vpCannyEdgeDetection.cpp | 56 ++- modules/core/src/image/vpImageFilter.cpp | 415 ++++++++++++++---- .../visp3/imgproc/vpCircleHoughTransform.h | 113 ++++- .../imgproc/src/vpCircleHoughTransform.cpp | 60 +-- .../hough-transform/config/detector_img.json | 4 + 7 files changed, 613 insertions(+), 174 deletions(-) diff --git a/modules/core/include/visp3/core/vpCannyEdgeDetection.h b/modules/core/include/visp3/core/vpCannyEdgeDetection.h index d6ee6a6f46..cc22dd3e02 100644 --- a/modules/core/include/visp3/core/vpCannyEdgeDetection.h +++ b/modules/core/include/visp3/core/vpCannyEdgeDetection.h @@ -56,6 +56,10 @@ class VISP_EXPORT vpCannyEdgeDetection ON_CHECK /*!< This pixel is currently tested to know if it is linked to a strong edge point.*/ } EdgeType; + // Filtering + gradient methods choice + vpImageFilter::vpCannyFilteringAndGradientType m_filteringAndGradientType; /*!< Choice of the filter and + gradient operator to apply before the edge detection step*/ + // // Gaussian smoothing attributes int m_gaussianKernelSize; /*!< Size of the Gaussian filter kernel used to smooth the input image. Must be an odd number.*/ float m_gaussianStdev; /*!< Standard deviation of the Gaussian filter.*/ @@ -73,8 +77,13 @@ class VISP_EXPORT vpCannyEdgeDetection std::map, float> m_edgeCandidateAndGradient; /*!< Map that contains point image coordinates and corresponding gradient value.*/ // // Hysteresis thresholding attributes - float m_lowerThreshold; /*!< Lower threshold for the hysteresis step. If negative, it will be deduced as from m_upperThreshold. */ + float m_lowerThreshold; /*!< Lower threshold for the hysteresis step. If negative, it will be deduced + as from m_upperThreshold. */ + float m_lowerThresholdRatio; /*!< If the thresholds must be computed, the ratio of the upper threshold the lower + threshold is equal: m_lowerThreshold = m_lowerThresholdRatio * m_upperThreshold. */ float m_upperThreshold; /*!< Upper threshold for the hysteresis step.*/ + float m_upperThresholdRatio; /*!< If the thresholds must be computed, the ratio of pixels of the gradient image that + must be lower than the upper threshold \b m_upperThreshold.*/ // // Edge tracking attributes 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, @@ -118,15 +127,15 @@ class VISP_EXPORT vpCannyEdgeDetection * \b m_weakEdgePoints and will be kept in the final edge map only if they are connected * to a strong edge point. * Edge candidates that are below \b m_lowerThreshold are discarded. - * \param lowerThreshold Edge candidates that are below this threshold are definitely not + * \param[in] lowerThreshold Edge candidates that are below this threshold are definitely not * edges. - * \param upperThreshold Edge candidates that are greater than this threshold are classified + * \param[in] upperThreshold Edge candidates that are greater than this threshold are classified * as strong edges. */ void performHysteresisThresholding(const float &lowerThreshold, const float &upperThreshold); /** - * @brief Search recursively for a strong edge in the neighborhood of a weak edge. + * \brief Search recursively for a strong edge in the neighborhood of a weak edge. * * \param[in] coordinates : The coordinates we are checking. * \return true We found a strong edge point in its 8-connected neighborhood. @@ -153,16 +162,25 @@ class VISP_EXPORT vpCannyEdgeDetection vpCannyEdgeDetection(); /** - * \brief Construct a new vpCannyEdgeDetection object. + * \brief Construct a new vpCannyEdgeDetection object that uses Gaussian blur + Sobel operators to compute + * the edge map. * * \param[in] gaussianKernelSize : The size of the Gaussian filter kernel. Must be odd. * \param[in] gaussianStdev : The standard deviation of the Gaussian filter. * \param[in] sobelAperture : The size of the Sobel filters kernel. Must be odd. - * \param[in] lowerThreshold : The lower threshold of the hysteresis thresholding step. If negative, will be computed from the upper threshold. - * \param[in] upperThreshold : The upper threshold of the hysteresis thresholding step. If negative, will be computed from the median of the gray values of the image. + * \param[in] lowerThreshold : The lower threshold of the hysteresis thresholding step. If negative, will be computed + * from the upper threshold. + * \param[in] upperThreshold : The upper threshold of the hysteresis thresholding step. If negative, will be computed + * from the histogram of the absolute gradient. + * \param[in] lowerThresholdRatio : If the thresholds must be computed,the lower threshold will be equal to the upper + * threshold times \b lowerThresholdRatio . + * \param[in] upperThresholdRatio : If the thresholds must be computed,the upper threshold will be equal to the value + * such as the number of pixels of the image times \b upperThresholdRatio have an absolute gradient lower than the + * upper threshold. */ vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev, const unsigned int &sobelAperture, - const float &lowerThreshold = -1., const float &upperThreshold = -1.); + const float &lowerThreshold = -1.f, const float &upperThreshold = -1.f, + const float &lowerThresholdRatio = 0.6f, const float &upperThresholdRatio = 0.8f); // // Configuration from files #ifdef VISP_HAVE_NLOHMANN_JSON @@ -186,32 +204,42 @@ class VISP_EXPORT vpCannyEdgeDetection * \brief Read the detector configuration from JSON. All values are optional and if an argument is not present, * the default value defined in the constructor is kept * - * \param j : The JSON object, resulting from the parsing of a JSON file. - * \param detector : The detector that will be initialized from the JSON data. + * \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. */ inline friend void from_json(const json &j, vpCannyEdgeDetection &detector) { + std::string filteringAndGradientName = vpImageFilter::vpCannyFilteringAndGradientTypeToString(detector.m_filteringAndGradientType); + filteringAndGradientName = j.value("filteringAndGradientType", filteringAndGradientName); + detector.m_filteringAndGradientType = vpImageFilter::vpCannyFilteringAndGradientTypeFromString(filteringAndGradientName); detector.m_gaussianKernelSize = j.value("gaussianSize", detector.m_gaussianKernelSize); detector.m_gaussianStdev = j.value("gaussianStdev", detector.m_gaussianStdev); detector.m_lowerThreshold = j.value("lowerThreshold", detector.m_lowerThreshold); + detector.m_lowerThresholdRatio = j.value("lowerThresholdRatio", detector.m_lowerThresholdRatio); detector.m_sobelAperture = j.value("sobelAperture", detector.m_sobelAperture); detector.m_upperThreshold = j.value("upperThreshold", detector.m_upperThreshold); + detector.m_upperThresholdRatio = j.value("upperThresholdRatio", detector.m_upperThresholdRatio); } /** * \brief Parse a vpCannyEdgeDetection object into JSON format. * - * \param j : A JSON parser object. - * \param detector : The vpCannyEdgeDetection object that must be parsed into JSON format. + * \param[out] j : A JSON parser object. + * \param[in] detector : The vpCannyEdgeDetection object that must be parsed into JSON format. */ inline friend void to_json(json &j, const vpCannyEdgeDetection &detector) { + std::string filteringAndGradientName = vpImageFilter::vpCannyFilteringAndGradientTypeToString(detector.m_filteringAndGradientType); j = json { + {"filteringAndGradientType", filteringAndGradientName}, {"gaussianSize", detector.m_gaussianKernelSize}, {"gaussianStdev", detector.m_gaussianStdev}, {"lowerThreshold", detector.m_lowerThreshold}, + {"lowerThresholdRatio", detector.m_lowerThresholdRatio}, {"sobelAperture", detector.m_sobelAperture}, - {"upperThreshold", detector.m_upperThreshold} }; + {"upperThreshold", detector.m_upperThreshold}, + {"upperThresholdRatio", detector.m_upperThresholdRatio} + }; } #endif //@} @@ -249,6 +277,16 @@ class VISP_EXPORT vpCannyEdgeDetection /** @name Setters */ //@{ + /** + * \brief Set the Filtering And Gradient operators to apply to the image before the edge detection operation. + * + * \param[in] type The operators to apply. + */ + inline void setFilteringAndGradientType(const vpImageFilter::vpCannyFilteringAndGradientType &type) + { + m_filteringAndGradientType = type; + } + /** * \brief Set the Gradients of the image that will be processed. * @@ -280,7 +318,25 @@ class VISP_EXPORT vpCannyEdgeDetection } /** - * @brief Set the Gaussian Filters kernel size and standard deviation + * \brief Set the lower and upper Canny Thresholds ratio that are used to compute them automatically. To ask to + * compute automatically the thresholds, you must set the lower and upper thresholds with negative values using the + * appropriate setter. + * + * \sa \ref vpCannyEdgeDetection::setCannyThresholds() "vpCannyEdgeDetection::setCannyThresholds(const float&, const float&)" + * \param[in] lowerThreshRatio : The lower threshold ratio: if the thresholds are computed automatically, the lower + * threshold will be equal to the upper threshold multiplied by \b lowerThreshRatio. + * \param[in] upperThreshRatio : The upper threshold ratio: if the thresholds are computed automatically, the upper + * threshold will be set such as \b upperThreshRatio times the number of pixels of the image have their absolute + * gradient lower then the upper threshold. + */ + inline void setCannyThresholdsRatio(const float &lowerThreshRatio, const float &upperThreshRatio) + { + m_lowerThresholdRatio = lowerThreshRatio; + m_upperThresholdRatio = upperThreshRatio; + } + + /** + * \brief Set the Gaussian Filters kernel size and standard deviation * and initialize the aforementioned filters. * * \param[in] kernelSize : The size of the Gaussian filters kernel. @@ -295,7 +351,7 @@ class VISP_EXPORT vpCannyEdgeDetection } /** - * @brief Set the Gaussian Filters kernel size and standard deviation + * \brief Set the Gaussian Filters kernel size and standard deviation * and initialize the aforementioned filters. * * \param[in] kernelSize : The size of the Gaussian filters kernel. @@ -305,7 +361,7 @@ class VISP_EXPORT vpCannyEdgeDetection inline void setSobelAperture(const unsigned int &sobelAperture) { m_sobelAperture = sobelAperture; - initGaussianFilters(); + initSobelFilters(); } //@} }; diff --git a/modules/core/include/visp3/core/vpImageFilter.h b/modules/core/include/visp3/core/vpImageFilter.h index 4f0e572be7..0b14f7b4af 100644 --- a/modules/core/include/visp3/core/vpImageFilter.h +++ b/modules/core/include/visp3/core/vpImageFilter.h @@ -67,11 +67,41 @@ class VISP_EXPORT vpImageFilter { public: - static void canny(const vpImage &I, vpImage &Ic, unsigned int gaussianFilterSize, - float thresholdCanny, unsigned int apertureSobel); + //! Canny filter backends for the edge detection operations + typedef enum vpCannyBackendType + { + CANNY_OPENCV_BACKEND = 0, //!< Use OpenCV + CANNY_VISP_BACKEND = 1, //!< Use ViSP + CANNY_COUNT_BACKEND = 2 //! Number of supported backends + } vpCannyBackendType; + + static std::string vpCannyBackendTypeToString(const vpCannyBackendType &type); + + static vpCannyBackendType vpCannyBackendTypeFromString(const std::string &name); + + //! Canny filter and gradient operators to apply on the image before the edge detection stage + typedef enum vpCannyFilteringAndGradientType + { + CANNY_GBLUR_SOBEL_FILTERING = 0, //!< Apply Gaussian blur + Sobel operator on the input image + CANNY_COUNT_FILTERING = 1 //! Number of supported backends + } vpCannyFilteringAndGradientType; - static void canny(const vpImage &I, vpImage &Ic, unsigned int gaussianFilterSize, - float lowerThresholdCanny, float higherThresholdCanny, unsigned int apertureSobel); + static std::string vpCannyFilteringAndGradientTypeToString(const vpCannyFilteringAndGradientType &type); + + static vpCannyFilteringAndGradientType vpCannyFilteringAndGradientTypeFromString(const std::string &name); + + static void canny(const vpImage &I, vpImage &Ic, const unsigned int &gaussianFilterSize, + const float &thresholdCanny, const unsigned int &apertureSobel); + + static void canny(const vpImage &I, vpImage &Ic, const unsigned int &gaussianFilterSize, + const float &lowerThresholdCanny, const float &higherThresholdCanny, + const unsigned int &apertureSobel); + + static void canny(const vpImage &I, vpImage &Ic, const unsigned int &gaussianFilterSize, + const float &lowerThresholdCanny, const float &higherThresholdCanny, + const unsigned int &apertureSobel, const float &gaussianStdev, const float &lowerThresholdRatio, + const float &upperThresholdRatio, const vpCannyBackendType &cannyBackend, + const vpCannyFilteringAndGradientType &cannyFilteringSteps); /*! Apply a 1x3 derivative filter to an image pixel. @@ -289,7 +319,7 @@ class VISP_EXPORT vpImageFilter template static void filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve) = delete; - + static void sepFilter(const vpImage &I, vpImage &If, const vpColVector &kernelH, const vpColVector &kernelV); /*! @@ -1056,10 +1086,17 @@ class VISP_EXPORT vpImageFilter return 1 / 16.0; } -static float computeCannyThreshold(const vpImage &I, float &lowerThresh); + static float computeCannyThreshold(const vpImage &I, float &lowerThresh, + const vpImage *p_dIx = nullptr, const vpImage *p_dIy = nullptr, + const unsigned int gaussianKernelSize = 5, + const float gaussianStdev = 2.f, const unsigned int apertureSobel = 3, + const float lowerThresholdRatio = 0.6, const float upperThresholdRatio = 0.8); #if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) - static float computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_blur, float &lowerThresh); + static float computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_dIx, const cv::Mat *p_cv_dIy, + float &lowerThresh, const unsigned int gaussianKernelSize = 5, + const float gaussianStdev = 2.f, const unsigned int apertureSobel = 3, + const float lowerThresholdRatio = 0.6, const float upperThresholdRatio = 0.8); static float median(const cv::Mat &cv_I); static float median(const vpImage &Isrc); static std::vector median(const vpImage &Isrc); diff --git a/modules/core/src/image/vpCannyEdgeDetection.cpp b/modules/core/src/image/vpCannyEdgeDetection.cpp index 716f164a8c..a76bde2a29 100644 --- a/modules/core/src/image/vpCannyEdgeDetection.cpp +++ b/modules/core/src/image/vpCannyEdgeDetection.cpp @@ -37,25 +37,33 @@ // // Initialization methods vpCannyEdgeDetection::vpCannyEdgeDetection() - : m_gaussianKernelSize(3) - , m_gaussianStdev(1.) + : m_filteringAndGradientType(vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) + , m_gaussianKernelSize(3) + , m_gaussianStdev(1.f) , m_areGradientAvailable(false) , m_sobelAperture(3) - , m_lowerThreshold(-1.) - , m_upperThreshold(-1.) + , m_lowerThreshold(-1.f) + , m_lowerThresholdRatio(0.6f) + , m_upperThreshold(-1.f) + , m_upperThresholdRatio(0.8f) { initGaussianFilters(); initSobelFilters(); } -vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev, const unsigned int &sobelAperture - , const float &lowerThreshold, const float &upperThreshold) - : m_gaussianKernelSize(gaussianKernelSize) +vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev + , const unsigned int &sobelAperture, const float &lowerThreshold, const float &upperThreshold + , const float &lowerThresholdRatio, const float &upperThresholdRatio +) + : m_filteringAndGradientType(vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) + , m_gaussianKernelSize(gaussianKernelSize) , m_gaussianStdev(gaussianStdev) , m_areGradientAvailable(false) , m_sobelAperture(sobelAperture) , m_lowerThreshold(lowerThreshold) + , m_lowerThresholdRatio(lowerThresholdRatio) , m_upperThreshold(upperThreshold) + , m_upperThresholdRatio(upperThresholdRatio) { initGaussianFilters(); initSobelFilters(); @@ -108,7 +116,8 @@ void vpCannyEdgeDetection::initSobelFilters() { if ((m_sobelAperture % 2) == 0) { - throw(vpException(vpException::badValue, "The Sobel kernel size should be odd")); + std::string errMsg("The Sobel kernel (" + std::to_string(m_sobelAperture) + ") should be odd"); + throw(vpException(vpException::badValue, errMsg)); } m_sobelX.resize(m_sobelAperture, m_sobelAperture); vpImageFilter::getSobelKernelX(m_sobelX.data, (m_sobelAperture - 1)/2); @@ -155,14 +164,15 @@ vpCannyEdgeDetection::detect(const vpImage &I) // // Step 4: hysteresis thresholding float upperThreshold = m_upperThreshold; float lowerThreshold = m_lowerThreshold; - if(upperThreshold < 0) { - upperThreshold = vpImageFilter::computeCannyThreshold(I, lowerThreshold); + if (upperThreshold < 0) { + upperThreshold = vpImageFilter::computeCannyThreshold(I, lowerThreshold, &m_dIx, &m_dIy, m_gaussianKernelSize, + m_gaussianStdev, m_sobelAperture, m_lowerThresholdRatio, + m_upperThresholdRatio); } else if (m_lowerThreshold < 0) { // Applying Canny recommendation to have the upper threshold 3 times greater than the lower threshold. lowerThreshold = m_upperThreshold / 3.f; } - std::cout << "lt = " << lowerThreshold << " ; ut = " << upperThreshold << std::endl; performHysteresisThresholding(lowerThreshold, upperThreshold); @@ -174,15 +184,21 @@ vpCannyEdgeDetection::detect(const vpImage &I) void vpCannyEdgeDetection::performFilteringAndGradientComputation(const vpImage &I) { - // Computing the Gaussian blurr - vpImage Iblur; - vpImage GIx; - vpImageFilter::filterX(I, GIx, m_fg.data, m_gaussianKernelSize); - vpImageFilter::filterY(GIx, Iblur, m_fg.data, m_gaussianKernelSize); - - // Computing the gradients - vpImageFilter::filter(Iblur,m_dIx,m_sobelX); - vpImageFilter::filter(Iblur,m_dIy,m_sobelY); + if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { + // Computing the Gaussian blur + vpImage Iblur; + vpImage GIx; + vpImageFilter::filterX(I, GIx, m_fg.data, m_gaussianKernelSize); + vpImageFilter::filterY(GIx, Iblur, m_fg.data, m_gaussianKernelSize); + + // Computing the gradients + vpImageFilter::filter(Iblur, m_dIx, m_sobelX); + vpImageFilter::filter(Iblur, m_dIy, m_sobelY); + } + else { + std::string errmsg("Currently, the only filtering and gradient operators are Gaussian blur + Sobel"); + throw(vpException(vpException::notImplementedError, errmsg)); + } } /** diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index 172dbea53e..98dae6772e 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -38,8 +38,96 @@ #include #include #include +#include #include +/** + * \brief Cast a \b vpImageFilter::vpCannyBackendTypeToString into a string, to know its name. + * + * \param[in] type The type that must be casted into a string. + * \return std::string The corresponding name. + */ +std::string vpImageFilter::vpCannyBackendTypeToString(const vpImageFilter::vpCannyBackendType &type) +{ + std::string name; + switch (type) { + case vpCannyBackendType::CANNY_OPENCV_BACKEND: + name = "opencv-backend"; + break; + case vpCannyBackendType::CANNY_VISP_BACKEND: + name = "visp-backend"; + break; + case vpCannyBackendType::CANNY_COUNT_BACKEND: + default: + return "unknown-backend"; + } + return name; +} + +/** + * \brief Cast a string into a \b vpImageFilter::vpCannyBackendTypeToString. + * + * \param[in] name The name of the backend. + * \return vpImageFilter::vpCannyBackendTypeToString The corresponding enumeration value. + */ +vpImageFilter::vpCannyBackendType vpImageFilter::vpCannyBackendTypeFromString(const std::string &name) +{ + vpCannyBackendType type(vpCannyBackendType::CANNY_COUNT_BACKEND); + std::string nameLowerCase = vpIoTools::toLowerCase(name); + unsigned int count = (unsigned int)vpCannyBackendType::CANNY_COUNT_BACKEND; + bool found = false; + for (unsigned int i = 0; i < count && !found; i++) { + vpCannyBackendType temp = (vpCannyBackendType)i; + if (nameLowerCase == vpCannyBackendTypeToString(temp)) { + type = temp; + found = true; + } + } + return type; +} + +/** + * \brief Cast a \b vpImageFilter::vpCannyFilteringAndGradientType into a string, to know its name. + * + * \param[in] type The type that must be casted into a string. + * \return std::string The corresponding name. + */ +std::string vpImageFilter::vpCannyFilteringAndGradientTypeToString(const vpImageFilter::vpCannyFilteringAndGradientType &type) +{ + std::string name; + switch (type) { + case vpCannyFilteringAndGradientType::CANNY_GBLUR_SOBEL_FILTERING: + name = "gaussianblur+sobel-filtering"; + break; + case vpCannyFilteringAndGradientType::CANNY_COUNT_FILTERING: + default: + return "unknown-filtering"; + } + return name; +} + +/** + * \brief Cast a string into a \b vpImageFilter::vpCannyFilteringAndGradientType. + * + * \param[in] name The name of the backend. + * \return vpImageFilter::vpCannyFilteringAndGradientType The corresponding enumeration value. + */ +vpImageFilter::vpCannyFilteringAndGradientType vpImageFilter::vpCannyFilteringAndGradientTypeFromString(const std::string &name) +{ + vpCannyFilteringAndGradientType type(vpCannyFilteringAndGradientType::CANNY_COUNT_FILTERING); + std::string nameLowerCase = vpIoTools::toLowerCase(name); + unsigned int count = (unsigned int)vpCannyFilteringAndGradientType::CANNY_COUNT_FILTERING; + bool found = false; + for (unsigned int i = 0; i < count && !found; i++) { + vpCannyFilteringAndGradientType temp = (vpCannyFilteringAndGradientType)i; + if (nameLowerCase == vpCannyFilteringAndGradientTypeToString(temp)) { + type = temp; + found = true; + } + } + return type; +} + /** * \cond DO_NOT_DOCUMENT */ @@ -119,10 +207,10 @@ void vpImageFilter::filter(const vpImage &I, vpImage &I, vpImage &If, const vpColVector &kernelH, @@ -207,7 +295,7 @@ void vpImageFilter::filterY(const vpImage & unsigned int size); /*template<>*/template -void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, +void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); /*template<>*/template @@ -265,12 +353,12 @@ void vpImageFilter::gaussianBlur(const vpImage &I, vpIma /*! Apply a Gaussian blur to RGB color image. - \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 + \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 normalize : Flag indicating whether to normalize the filter coefficients or not. + \param[in] normalize : Flag indicating whether to normalize the filter coefficients or not. \sa getGaussianKernel() to know which kernel is used. */ @@ -504,38 +592,48 @@ std::vector vpImageFilter::median(const vpImage &Isrc) } /** - * \brief Compute the upper Canny edge filter threshold. + * \brief Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel operators to compute + * the gradient of the image. * * \param[in] cv_I : The image, in cv format. - * \param[in] p_cv_blur : If different from nullptr, must contain a blurred version of cv_I. + * \param[in] p_cv_dIx : If different from nullptr, the gradient of cv_I with regard to the horizontal axis. + * \param[in] p_cv_dIy : If different from nullptr, the gradient of cv_I with regard to the vertical axis. * \param[out] lowerThresh : The lower threshold for the Canny edge filter. + * \param[in] gaussianFilterSize : The size of the mask of the Gaussian filter to apply (an odd number). + * \param[in] gaussianStdev : The standard deviation of the Gaussian filter to apply. + * \param[in] apertureSobel : Size of the mask for the Sobel operator (odd number). + * \param[in] lowerThresholdRatio : The ratio of the upper threshold the lower threshold must be equal to. + * \param[in] upperThresholdRatio : The ratio of pixels whose absolute gradient Gabs is lower or equal to to define + * the upper threshold. * \return The upper Canny edge filter threshold. */ -float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_blur, float &lowerThresh) +float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_dIx, const cv::Mat *p_cv_dIy, + float &lowerThresh, const unsigned int gaussianKernelSize, + const float gaussianStdev, const unsigned int apertureSobel, + const float lowerThresholdRatio, const float upperThresholdRatio) { - const unsigned int gaussianKernelSize = 5; - const float gaussianStdev = 2.f; - const unsigned int sobelAperture = 3; - const float nonEdgeRate = 0.6; - const float thresholdRate = 0.6; double w = cv_I.cols; double h = cv_I.rows; int bins = 256; - cv::Mat sobel, sobelx, sobely, sobelxabs, sobelyabs, img_blur; + cv::Mat sobel, sobelx, sobely, sobelxabs, sobelyabs; - if(p_cv_blur == nullptr) { + if (p_cv_dIx == nullptr || p_cv_dIy == nullptr) { + cv::Mat img_blur; // Apply Gaussian blur to the image cv::Size gsz(gaussianKernelSize, gaussianKernelSize); - cv::GaussianBlur(cv_I, img_blur,gsz, gaussianStdev); + cv::GaussianBlur(cv_I, img_blur, gsz, gaussianStdev); + + // Compute the gradient of the blurred image + cv::Sobel(img_blur, sobelx, CV_16S, 1, 0, apertureSobel, 1, 0); + cv::Sobel(img_blur, sobely, CV_16S, 0, 1, apertureSobel, 1, 0); } else { - img_blur = *p_cv_blur; + sobelx = *p_cv_dIx; + sobely = *p_cv_dIy; } - // Compute the gradient of the blurred image - cv::Sobel(img_blur, sobelx, CV_16S, 1, 0, sobelAperture, 1, 0); + // Compute the absolute gradient of the blurred image G = |dIx| + |dIy| cv::convertScaleAbs(sobelx, sobelxabs); - cv::Sobel(img_blur, sobely, CV_16S, 0, 1, sobelAperture, 1, 0); cv::convertScaleAbs(sobely, sobelyabs); cv::addWeighted(sobelxabs, 1, sobelyabs, 1, 0, sobel); sobel.convertTo(sobel, CV_8U); @@ -546,16 +644,16 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p // Compute the upper threshold from the equalized histogram cv::Mat hist; - const float range[] = {0.f, 256.f}; // The upper boundary is exclusive - const float* ranges[] = { range }; - int channels[] = {0}; + const float range[] = { 0.f, 256.f }; // The upper boundary is exclusive + const float *ranges[] = { range }; + int channels[] = { 0 }; bool dims = 1; // The number of dimensions of the histogram - int histSize[] = {bins}; + int histSize[] = { bins }; bool uniform = true; bool accumulate = false; // Clear the histogram at the beginning of calcHist if false, does not clear it otherwise cv::calcHist(&equalized, 1, channels, cv::Mat(), hist, dims, histSize, ranges, uniform, accumulate); float accu = 0; - float t = (float)(nonEdgeRate * w * h); + float t = (float)(upperThresholdRatio * w * h); float bon = 0; for (int i = 0; i < bins; i++) { float tf = hist.at(i); @@ -566,44 +664,57 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p } } float upperThresh = std::max(bon, 1.f); - lowerThresh = thresholdRate * bon; - std::cout << "[computeCannyThreshold::cv] ut = " << upperThresh << " ; lt = " << lowerThresh << std::endl; + lowerThresh = lowerThresholdRatio * bon; return upperThresh; } #endif /** - * \brief Compute the upper Canny edge filter threshold. + * \brief Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel operators to compute + * the gradient of the image. * * \param[in] I : The gray-scale image, in ViSP format. + * \param[in] p_dIx : If different from nullptr, must contain the gradient of the image with regard to the horizontal axis. + * \param[in] p_dIy : If different from nullptr, must contain the gradient of the image with regard to the vertical axis. * \param[in] lowerThresh : Canny lower threshold. + * \param[in] gaussianFilterSize : The size of the mask of the Gaussian filter to apply (an odd number). + * \param[in] gaussianStdev : The standard deviation of the Gaussian filter to apply. + * \param[in] apertureSobel : Size of the mask for the Sobel operator (odd number). + * \param[in] lowerThresholdRatio : The ratio of the upper threshold the lower threshold must be equal to. + * \param[in] upperThresholdRatio : The ratio of pixels whose absolute gradient Gabs is lower or equal to define + * the upper threshold. * \return The upper Canny edge filter threshold. */ -float vpImageFilter::computeCannyThreshold(const vpImage &I, float &lowerThresh) +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 apertureSobel, + const float lowerThresholdRatio, const float upperThresholdRatio) { - const unsigned int gaussianKernelSize = 5; - const float gaussianStdev = 2.; - const unsigned int apertureSize = 3; - const float nonEdgeRate = 0.8; - const float thresholdRate = 0.6; - double w = I.getWidth(); double h = I.getHeight(); - // Computing the Gaussian blurr + gradients of the image - vpImage Iblur; - vpImageFilter::gaussianBlur(I, Iblur, gaussianKernelSize, gaussianStdev); + vpImage dI(h, w); + vpImage dIx(h, w), dIy(h, w); + if (p_dIx != nullptr && p_dIy != nullptr) { + dIx = *p_dIx; + dIy = *p_dIy; + } + else { + // Computing the Gaussian blur + gradients of the image + vpImage Iblur; + vpImageFilter::gaussianBlur(I, Iblur, gaussianKernelSize, gaussianStdev); - vpArray2D sobelX(apertureSize, apertureSize); // Sobel kernel along x - vpImageFilter::getSobelKernelX(sobelX.data, (apertureSize - 1)/2); - vpArray2D sobelY(apertureSize, apertureSize); // Sobel kernel along x - vpImageFilter::getSobelKernelY(sobelY.data, (apertureSize - 1)/2); - vpImage dIx, dIy; - vpImageFilter::filter(Iblur,dIx,sobelX); - vpImageFilter::filter(Iblur,dIy,sobelY); + vpArray2D sobelX(apertureSobel, apertureSobel); // Sobel kernel along x + vpImageFilter::getSobelKernelX(sobelX.data, (apertureSobel - 1)/2); + vpArray2D sobelY(apertureSobel, apertureSobel); // Sobel kernel along y + vpImageFilter::getSobelKernelY(sobelY.data, (apertureSobel - 1)/2); - // Computing the gradient of the image - vpImage dI(h, w); + vpImageFilter::filter(Iblur, dIx, sobelX); + vpImageFilter::filter(Iblur, dIy, sobelY); + } + + // 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 = (float)dIx[r][c]; @@ -619,7 +730,7 @@ float vpImageFilter::computeCannyThreshold(const vpImage &I, floa const unsigned int nbBins = 256; hist.calculate(dI, nbBins); float accu = 0; - float t = (float)(nonEdgeRate * w * h); + float t = (float)(upperThresholdRatio * w * h); float bon = 0; for (unsigned int i = 0; i < nbBins; i++) { float tf = hist[i]; @@ -630,7 +741,7 @@ float vpImageFilter::computeCannyThreshold(const vpImage &I, floa } } float upperThresh = std::max(bon, 1.f); - lowerThresh = thresholdRate * bon; + lowerThresh = lowerThresholdRatio * bon; return upperThresh; } @@ -663,18 +774,19 @@ int main() } \endcode - \param Isrc : Image to apply the Canny edge detector to. - \param Ires : Filtered image (255 means an edge, 0 otherwise). - \param gaussianFilterSize : The size of the mask of the Gaussian filter to + \param[in] Isrc : Image to apply the Canny edge detector to. + \param[out] Ires : Filtered image (255 means an edge, 0 otherwise). + \param[in] gaussianFilterSize : The size of the mask of the Gaussian filter to apply (an odd number). - \param thresholdCanny : The upper threshold for the Canny operator. Only value + \param[in] thresholdCanny : The upper threshold for the Canny operator. Only value greater than this value are marked as an edge. If negative, it will be automatically computed, along with the lower threshold. Otherwise, the lower threshold will be set to one third of the thresholdCanny . - \param apertureSobel : Size of the mask for the Sobel operator (odd number). + \param[in] apertureSobel : Size of the mask for the Sobel operator (odd number). */ void vpImageFilter::canny(const vpImage &Isrc, vpImage &Ires, - unsigned int gaussianFilterSize, float thresholdCanny, unsigned int apertureSobel) + const unsigned int &gaussianFilterSize, const float &thresholdCanny, + const unsigned int &apertureSobel) { vpImageFilter::canny(Isrc, Ires, gaussianFilterSize, thresholdCanny / 3.f, thresholdCanny, apertureSobel); } @@ -709,49 +821,166 @@ int main() } \endcode - \param Isrc : Image to apply the Canny edge detector to. - \param Ires : Filtered image (255 means an edge, 0 otherwise). - \param gaussianFilterSize : The size of the mask of the Gaussian filter to + \param[in] Isrc : Image to apply the Canny edge detector to. + \param[out] Ires : Filtered image (255 means an edge, 0 otherwise). + \param[in] gaussianFilterSize : The size of the mask of the Gaussian filter to apply (an odd number). - \param lowerThreshold : The lower threshold for the Canny operator. Values lower + \param[in] lowerThreshold : The lower threshold for the Canny operator. Values lower than this value are rejected. If negative, it will be set to one third of the thresholdCanny . - \param upperThreshold : The upper threshold for the Canny operator. Only value + \param[in] upperThreshold : The upper threshold for the Canny operator. Only value greater than this value are marked as an edge. If negative, it will be automatically computed, along with the lower threshold. Otherwise, the lower threshold will be set to one third of the thresholdCanny . - \param apertureSobel : Size of the mask for the Sobel operator (odd number). + \param[in] apertureSobel : Size of the mask for the Sobel operator (odd number). */ void vpImageFilter::canny(const vpImage &Isrc, vpImage &Ires, - unsigned int gaussianFilterSize, float lowerThreshold, float upperThreshold, unsigned int apertureSobel) + const unsigned int &gaussianFilterSize, + const float &lowerThreshold, const float &upperThreshold, + const unsigned int &apertureSobel) { + const float gaussianStdev = 2.f; + const float upperThresholdRatio = 0.8f; + const float lowerThresholdRatio = 0.6f; #if defined(HAVE_OPENCV_IMGPROC) - cv::Mat img_cvmat, cv_I_blur, cv_dx, cv_dy, edges_cvmat; - vpImageConvert::convert(Isrc, img_cvmat); - cv::GaussianBlur(img_cvmat, cv_I_blur, cv::Size((int)gaussianFilterSize, (int)gaussianFilterSize), 0, 0); - cv::Sobel(cv_I_blur, cv_dx, CV_16S, 1, 0, apertureSobel); - cv::Sobel(cv_I_blur, cv_dy, CV_16S, 0, 1, apertureSobel); - float upperCannyThresh = upperThreshold; - float lowerCannyThresh = lowerThreshold; - if (upperCannyThresh < 0) { - upperCannyThresh = computeCannyThreshold(img_cvmat, &cv_I_blur, lowerCannyThresh); - } - else if (lowerCannyThresh < 0) { - lowerCannyThresh = upperCannyThresh / 3.f; - } - cv::Canny(cv_dx, cv_dy, edges_cvmat, lowerCannyThresh, upperCannyThresh, false); - vpImageConvert::convert(edges_cvmat, Ires); + const vpCannyBackendType cannyBackend = CANNY_OPENCV_BACKEND; #else - (void)apertureSobel; - float upperCannyThresh = upperThreshold; - float lowerCannyThresh = lowerThreshold; - if (upperCannyThresh < 0) { - upperCannyThresh = computeCannyThreshold(Isrc, lowerCannyThresh); + const vpCannyBackendType cannyBackend = CANNY_VISP_BACKEND; +#endif + const vpCannyFilteringAndGradientType cannyFilteringSteps = CANNY_GBLUR_SOBEL_FILTERING; + canny(Isrc, Ires, gaussianFilterSize, lowerThreshold, upperThreshold, apertureSobel, + gaussianStdev, lowerThresholdRatio, upperThresholdRatio, cannyBackend, cannyFilteringSteps); +} + +/*! + Apply the Canny edge operator on the image \e Isrc and return the resulting + image \e Ires. + + The following example shows how to use the method: + + \code +#include +#include + +int main() +{ + // Constants for the Canny operator. + const unsigned int gaussianFilterSize = 5; + const float gaussianStdev = 2.0f; + const float upperThresholdCanny = 15.f; + const float lowerThresholdCanny = 5.f; + const float upperThresholdRatio = 0.8f; + const float lowerThresholdRatio = 0.6f; + const unsigned int apertureSobel = 3; + const vpCannyBackendType cannyBackend = CANNY_OPENCV_BACKEND; // or CANNY_VISP_BACKEND; + const vpCannyFilteringAndGradientType cannyFilteringSteps = CANNY_GBLUR_SOBEL_FILTERING; + + // Image for the Canny edge operator + vpImage Isrc; + vpImage Icanny; + + // First grab the source image Isrc. + + // Apply the Canny edge operator and set the Icanny image. + vpImageFilter::canny(Isrc, Icanny, gaussianFilterSize, lowerThresholdCanny, upperThresholdCanny, apertureSobel, + gaussianStdev, lowerThresholdRatio, upperThresholdRatio, cannyBackend, cannyFilteringSteps); + return (0); +} + \endcode + + \param[in] Isrc : Image to apply the Canny edge detector to. + \param[out] Ires : Filtered image (255 means an edge, 0 otherwise). + \param[in] gaussianFilterSize : The size of the mask of the Gaussian filter to + apply (an odd number). + \param[in] lowerThreshold : The lower threshold for the Canny operator. Values lower + than this value are rejected. If negative, it will be set to one third + of the thresholdCanny . + \param[in] upperThreshold : The upper threshold for the Canny operator. Only value + greater than this value are marked as an edge. If negative, it will be automatically + computed, along with the lower threshold. Otherwise, the lower threshold will be set to one third + of the thresholdCanny . + \param[in] apertureSobel : Size of the mask for the Sobel operator (odd number). + \param[in] gaussianStdev : The standard deviation of the Gaussian filter to apply. + \param[in] lowerThresholdRatio : The ratio of the upper threshold the lower threshold must be equal to. + It is used only if the user asks to compute the Canny thresholds. + \param[in] upperThresholdRatio : The ratio of pixels whose absolute gradient Gabs is lower or equal to define + the upper threshold. It is used only if the user asks to compute the Canny thresholds. + \param[in] cannyBackend : The backend to use to perform the Canny edge filtering. + \param[in] cannyFilteringSteps : The filtering + gradient operators to apply to compute the gradient in the early + stage of the Canny algoritgm. + +*/ +void vpImageFilter::canny(const vpImage &Isrc, vpImage &Ires, + const unsigned int &gaussianFilterSize, + const float &lowerThreshold, const float &upperThreshold, const unsigned int &apertureSobel, + const float &gaussianStdev, const float &lowerThresholdRatio, const float &upperThresholdRatio, + const vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps) +{ + if (cannyBackend == CANNY_OPENCV_BACKEND) { +#if defined(HAVE_OPENCV_IMGPROC) + cv::Mat img_cvmat, cv_dx, cv_dy, edges_cvmat; + vpImageConvert::convert(Isrc, img_cvmat); + if (cannyFilteringSteps == CANNY_GBLUR_SOBEL_FILTERING) { + cv::Mat cv_I_blur; + cv::GaussianBlur(img_cvmat, cv_I_blur, cv::Size((int)gaussianFilterSize, (int)gaussianFilterSize), 0, 0); + cv::Sobel(cv_I_blur, cv_dx, CV_16S, 1, 0, apertureSobel); + cv::Sobel(cv_I_blur, cv_dy, CV_16S, 0, 1, apertureSobel); + } + else { + std::string errMsg("[vpImageFilter::canny]Other types of Canny filtering steps have not been implemented"); + throw(vpException(vpException::functionNotImplementedError, errMsg)); + } + float upperCannyThresh = upperThreshold; + float lowerCannyThresh = lowerThreshold; + if (upperCannyThresh < 0) { + upperCannyThresh = computeCannyThreshold(img_cvmat, &cv_dx, &cv_dy, lowerCannyThresh, gaussianFilterSize, + gaussianStdev, apertureSobel, lowerThresholdRatio, upperThresholdRatio); + } + else if (lowerCannyThresh < 0) { + lowerCannyThresh = upperCannyThresh / 3.f; + } + cv::Canny(cv_dx, cv_dy, edges_cvmat, lowerCannyThresh, upperCannyThresh, false); + vpImageConvert::convert(edges_cvmat, Ires); +#else + std::string errMsg("[vpImageFilter::canny]You asked for CANNY_OPENCV_BACKEND but ViSP has not been compiled with OpenCV"); + throw(vpException(vpException::badValue, errMsg)); +#endif } - else if (lowerCannyThresh < 0) { - lowerCannyThresh = upperCannyThresh / 3.; + else if (cannyBackend == CANNY_VISP_BACKEND) { + float upperCannyThresh = upperThreshold; + float lowerCannyThresh = lowerThreshold; + + vpImage dIx, dIy; + if (cannyFilteringSteps == CANNY_GBLUR_SOBEL_FILTERING) { + // Computing the Gaussian blur + gradients of the image + vpImage Iblur; + vpImageFilter::gaussianBlur(Isrc, Iblur, gaussianFilterSize, gaussianStdev); + + // Compute the Sobel filters + vpArray2D sobelX(apertureSobel, apertureSobel); // Sobel kernel along x + vpImageFilter::getSobelKernelX(sobelX.data, (apertureSobel - 1)/2); + vpArray2D sobelY(apertureSobel, apertureSobel); // Sobel kernel along y + vpImageFilter::getSobelKernelY(sobelY.data, (apertureSobel - 1)/2); + + // Apply the Sobel filters to get the gradients + vpImageFilter::filter(Iblur, dIx, sobelX); + vpImageFilter::filter(Iblur, dIy, sobelY); + } + else { + std::string errMsg("[vpImageFilter::canny]Other types of Canny filtering steps have not been implemented"); + throw(vpException(vpException::functionNotImplementedError, errMsg)); + } + + if (upperCannyThresh < 0) { + upperCannyThresh = computeCannyThreshold(Isrc, lowerCannyThresh, &dIx, &dIy, gaussianFilterSize, gaussianStdev, + apertureSobel, lowerThresholdRatio, upperThresholdRatio); + } + else if (lowerCannyThresh < 0) { + lowerCannyThresh = upperCannyThresh / 3.; + } + vpCannyEdgeDetection edgeDetector(gaussianFilterSize, gaussianStdev, apertureSobel, lowerCannyThresh, upperCannyThresh, + lowerThresholdRatio, upperThresholdRatio); + edgeDetector.setGradients(dIx, dIy); + Ires = edgeDetector.detect(Isrc); } - vpCannyEdgeDetection edgeDetector(gaussianFilterSize, 0.1, lowerCannyThresh, upperCannyThresh); - Ires = edgeDetector.detect(Isrc); -#endif -} \ No newline at end of file +} diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index 071fe036b4..fb90e688d8 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -72,8 +72,13 @@ class VISP_EXPORT vpCircleHoughTransform class vpCircleHoughTransformParameters { private: + // // Filtering + gradient operators to use + vpImageFilter::vpCannyFilteringAndGradientType m_filteringAndGradientType; /*!< Permits to choose the filtering + + gradient operators to use.*/ + // // Gaussian smoothing attributes - int m_gaussianKernelSize; /*!< Size of the Gaussian filter kernel used to smooth the input image. Must be an odd number.*/ + int m_gaussianKernelSize; /*!< Size of the Gaussian filter kernel used to smooth the input image. + Must be an odd number.*/ float m_gaussianStdev; /*!< Standard deviation of the Gaussian filter.*/ // // Gradient computation attributes @@ -85,6 +90,11 @@ class VISP_EXPORT vpCircleHoughTransform float m_upperCannyThresh; /*!< The upper threshold for the Canny operator. Only values greater than this value are marked as an edge. A negative value makes the algorithm compute the upper and lower thresholds automatically.*/ int m_edgeMapFilteringNbIter; /*!< Number of iterations of 8-neighbor connectivity filtering to apply to the edge map*/ + vpImageFilter::vpCannyBackendType m_cannyBackendType; /*!< Permits to choose the backend used to compute the edge map.*/ + float m_lowerCannyThreshRatio; /*!< The ratio of the upper threshold the lower threshold must be equal to. + It is used only if the user asks to compute the Canny thresholds.*/ + float m_upperCannyThreshRatio; /*!< The ratio of pixels whose absolute gradient Gabs is lower or equal to define + the upper threshold. It is used only if the user asks to compute the Canny thresholds.*/ // // 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.*/ @@ -108,12 +118,16 @@ class VISP_EXPORT vpCircleHoughTransform * \brief Construct a new vpCircleHoughTransformParameters object with default parameters. */ vpCircleHoughTransformParameters() - : m_gaussianKernelSize(5) + : m_filteringAndGradientType(vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) + , m_gaussianKernelSize(5) , m_gaussianStdev(1.f) , m_sobelKernelSize(3) , m_lowerCannyThresh(-1.f) , m_upperCannyThresh(-1.f) , m_edgeMapFilteringNbIter(1) + , m_cannyBackendType(vpImageFilter::CANNY_OPENCV_BACKEND) + , m_lowerCannyThreshRatio(0.6f) + , 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) @@ -149,6 +163,14 @@ class VISP_EXPORT vpCircleHoughTransform * \param[in] circlePerfectness The scalar product radius RC_ij . gradient(Ep_j) >= m_circlePerfectness * || RC_ij || * || gradient(Ep_j) || to add a vote for the radius RC_ij. * \param[in] centerMinDistThresh Two circle candidates whose centers are closer than this threshold are considered for merging. * \param[in] mergingRadiusDiffThresh Maximum radius difference between two circle candidates to consider merging them. + * \param[in] 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. + * \param[in] lowerCannyThreshRatio If the thresholds must be computed,the lower threshold will be equal to the upper + * threshold times \b lowerThresholdRatio . + * \param[in] upperCannyThreshRatio If the thresholds must be computed,the upper threshold will be equal to the value + * such as the number of pixels of the image times \b upperThresholdRatio have an absolute gradient lower than the + * upper threshold. */ vpCircleHoughTransformParameters( const int &gaussianKernelSize @@ -167,13 +189,21 @@ class VISP_EXPORT vpCircleHoughTransform , const float &circlePerfectness , const float ¢erMinDistThresh , const float &mergingRadiusDiffThresh + , 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 ) - : m_gaussianKernelSize(gaussianKernelSize) + : m_filteringAndGradientType(filteringAndGradientMethod) + , m_gaussianKernelSize(gaussianKernelSize) , m_gaussianStdev(gaussianStdev) , m_sobelKernelSize(sobelKernelSize) , m_lowerCannyThresh(lowerCannyThresh) , m_upperCannyThresh(upperCannyThresh) , m_edgeMapFilteringNbIter(edgeMapFilterNbIter) + , m_cannyBackendType(backendType) + , m_lowerCannyThreshRatio(lowerCannyThreshRatio) + , m_upperCannyThreshRatio(upperCannyThreshRatio) , m_centerXlimits(centerXlimits) , m_centerYlimits(centerYlimits) , m_minRadius(std::min(minRadius, maxRadius)) @@ -192,10 +222,13 @@ 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 += "\tSobel filter kernel size = " + std::to_string(m_sobelKernelSize) + "\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"; @@ -266,6 +299,10 @@ class VISP_EXPORT vpCircleHoughTransform */ inline friend void from_json(const json &j, vpCircleHoughTransformParameters ¶ms) { + std::string filteringAndGradientName = vpImageFilter::vpCannyFilteringAndGradientTypeToString(params.m_filteringAndGradientType); + filteringAndGradientName = j.value("filteringAndGradientType", filteringAndGradientName); + params.m_filteringAndGradientType = vpImageFilter::vpCannyFilteringAndGradientTypeFromString(filteringAndGradientName); + params.m_gaussianKernelSize = j.value("gaussianKernelSize", params.m_gaussianKernelSize); if ((params.m_gaussianKernelSize % 2) != 1) { throw vpException(vpException::badValue, "Gaussian Kernel size should be odd."); @@ -281,8 +318,13 @@ class VISP_EXPORT vpCircleHoughTransform throw vpException(vpException::badValue, "Sobel Kernel size should be odd."); } + std::string cannyBackendName = vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType); + cannyBackendName = j.value("cannyBackendType", cannyBackendName); + params.m_cannyBackendType = vpImageFilter::vpCannyBackendTypeFromString(cannyBackendName); params.m_lowerCannyThresh = j.value("lowerCannyThresh", params.m_lowerCannyThresh); + params.m_lowerCannyThreshRatio = j.value("lowerThresholdRatio", params.m_lowerCannyThreshRatio); params.m_upperCannyThresh = j.value("upperCannyThresh", params.m_upperCannyThresh); + params.m_upperCannyThreshRatio = j.value("upperThresholdRatio", params.m_upperCannyThreshRatio); params.m_edgeMapFilteringNbIter = j.value("edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter); params.m_centerXlimits = j.value("centerXlimits", params.m_centerXlimits); @@ -328,11 +370,15 @@ class VISP_EXPORT vpCircleHoughTransform std::pair radiusLimits = { params.m_minRadius, params.m_maxRadius }; j = json { + {"filteringAndGradientType", vpImageFilter::vpCannyFilteringAndGradientTypeToString(params.m_filteringAndGradientType)}, {"gaussianKernelSize", params.m_gaussianKernelSize}, {"gaussianStdev", params.m_gaussianStdev}, {"sobelKernelSize", params.m_sobelKernelSize}, + {"cannyBackendType", vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType)}, {"lowerCannyThresh", params.m_lowerCannyThresh}, + {"lowerThresholdRatio", params.m_lowerCannyThreshRatio}, {"upperCannyThresh", params.m_upperCannyThresh}, + {"upperThresholdRatio", params.m_upperCannyThreshRatio}, {"edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter}, {"centerXlimits", params.m_centerXlimits}, {"centerYlimits", params.m_centerYlimits}, @@ -364,8 +410,8 @@ class VISP_EXPORT vpCircleHoughTransform */ virtual ~vpCircleHoughTransform(); - // // Detection methods - + /** @name Detection methods */ + //@{ #ifdef HAVE_OPENCV_CORE /** * \brief Perform Circle Hough Transform to detect the circles in an OpenCV image. @@ -406,8 +452,10 @@ class VISP_EXPORT vpCircleHoughTransform */ std::vector detect(const vpImage &I, const int &nbCircles); #endif + //@} - // // Configuration from files + /** @name Configuration from files */ + //@{ #ifdef VISP_HAVE_NLOHMANN_JSON /** * \brief Construct a new vpCircleHoughTransform object configured according to @@ -458,8 +506,10 @@ class VISP_EXPORT vpCircleHoughTransform j = detector.m_algoParams; } #endif + //@} - // // Setters + /** @name Setters */ + //@{ /** * \brief Initialize all the algorithm parameters. * @@ -467,6 +517,17 @@ class VISP_EXPORT vpCircleHoughTransform */ void init(const vpCircleHoughTransformParameters &algoParams); + /** + * \brief Permits to choose the filtering + gradient operators to use. + * + * \param[in] type The type of filtering + gradient operators to use. + */ + inline void setFilteringAndGradientType(const vpImageFilter::vpCannyFilteringAndGradientType &type) + { + m_algoParams.m_filteringAndGradientType = type; + m_cannyVisp.setFilteringAndGradientType(type); + } + /** * \brief Set the parameters of the Gaussian filter, that permits to blur the * gradients of the image. @@ -507,6 +568,16 @@ class VISP_EXPORT vpCircleHoughTransform initSobelFilters(); } + /** + * \brief Set the backend to use to perform the Canny edge detection. + * + * \param[in] type The backend that must be used. + */ + inline void setCannyBackend(const vpImageFilter::vpCannyBackendType &type) + { + m_algoParams.m_cannyBackendType = type; + } + /*! * Set the threshold for the Canny operator. * Only value greater than this value are marked as an edge. @@ -522,6 +593,22 @@ class VISP_EXPORT vpCircleHoughTransform m_algoParams.m_upperCannyThresh = upperCannyThreshold; } + /** + * \brief Set the Canny thresholds ratio that are used to automatically compute the Canny thresholds + * in case the user asks to. + * + * \sa \ref vpCircleHoughTransform::setCannyThreshold "vpCircleHoughTransform::setCannyThreshold(const float&, const float&)" + * + * \param[in] lowerThreshRatio The ratio of the upper threshold the lower threshold will be equal to. + * \param[in] upperThreshRatio The ratio of pixels that must have a gradient lower than the upper threshold. + */ + inline void setCannyThresholdRatio(const float &lowerThreshRatio, const float &upperThreshRatio) + { + m_algoParams.m_lowerCannyThreshRatio = lowerThreshRatio; + m_algoParams.m_upperCannyThreshRatio = upperThreshRatio; + m_cannyVisp.setCannyThresholdsRatio(lowerThreshRatio, upperThreshRatio); + } + /*! * Set circles center min distance. * Change this value to detect circles with different distances to each other. @@ -632,9 +719,10 @@ class VISP_EXPORT vpCircleHoughTransform throw vpException(vpException::badValue, "Radius difference merging threshold must be positive."); } } + //@} - // // Getters - + /** @name Getters */ + //@{ /** * \brief Get the list of Center Candidates, stored as pair * @@ -754,6 +842,7 @@ class VISP_EXPORT vpCircleHoughTransform { return m_finalCircleVotes; } + //@} /*! * Create a string with all Hough transform parameters. @@ -811,9 +900,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 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. + * \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. */ float computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes); diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 21c9a05fd9..4df497cbbd 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -112,11 +112,11 @@ void vpCircleHoughTransform::initSobelFilters() { if ((m_algoParams.m_sobelKernelSize % 2) != 1) { - throw vpException(vpException::badValue, "Sobel Kernel size should be odd."); + throw vpException(vpException::badValue, "Sobel Kernel size should be odd."); } - m_sobelX.resize(m_algoParams.m_sobelKernelSize , m_algoParams.m_sobelKernelSize ); + m_sobelX.resize(m_algoParams.m_sobelKernelSize, m_algoParams.m_sobelKernelSize); vpImageFilter::getSobelKernelX(m_sobelX.data, (m_algoParams.m_sobelKernelSize - 1)/2); - m_sobelY.resize(m_algoParams.m_sobelKernelSize , m_algoParams.m_sobelKernelSize ); + m_sobelY.resize(m_algoParams.m_sobelKernelSize, m_algoParams.m_sobelKernelSize); vpImageFilter::getSobelKernelY(m_sobelY.data, (m_algoParams.m_sobelKernelSize - 1)/2); m_cannyVisp.setSobelAperture(m_algoParams.m_sobelKernelSize); } @@ -225,36 +225,44 @@ vpCircleHoughTransform::detect(const vpImage &I) void vpCircleHoughTransform::computeGradientsAfterGaussianSmoothing(const vpImage &I) { - // 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); - - // Computing the gradients - vpImageFilter::filter(Iblur,m_dIx,m_sobelX); - vpImageFilter::filter(Iblur,m_dIy,m_sobelY); + if (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_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); + + // Computing the gradients + vpImageFilter::filter(Iblur, m_dIx, m_sobelX); + vpImageFilter::filter(Iblur, m_dIy, m_sobelY); + } + else { + std::string errMsg("[computeGradientsAfterGaussianSmoothing] The filtering + gradient operators \""); + errMsg += vpImageFilter::vpCannyFilteringAndGradientTypeToString(m_algoParams.m_filteringAndGradientType); + errMsg += "\" is not implemented (yet)."; + throw(vpException(vpException::notImplementedError, errMsg)); + } } void vpCircleHoughTransform::edgeDetection(const vpImage &I) { -#if defined(HAVE_OPENCV_IMGPROC) - float upperCannyThresh = m_algoParams.m_upperCannyThresh; - float lowerCannyThresh = m_algoParams.m_lowerCannyThresh; - // Apply the Canny edge operator to compute the edge map - // The canny method performs Gaussian blur and gradient computation - if (m_algoParams.m_upperCannyThresh < 0.) { - upperCannyThresh = vpImageFilter::computeCannyThreshold(I, lowerCannyThresh); + if (m_algoParams.m_cannyBackendType == vpImageFilter::CANNY_VISP_BACKEND) { + // This is done to increase the time performances, because it avoids to + // recompute the gradient in the vpImageFilter::canny method + m_cannyVisp.setFilteringAndGradientType(m_algoParams.m_filteringAndGradientType); + 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_edgeMap = m_cannyVisp.detect(I); } - else if (m_algoParams.m_lowerCannyThresh < 0) { - lowerCannyThresh = upperCannyThresh / 3.f; + 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_sobelKernelSize, m_algoParams.m_gaussianStdev, + m_algoParams.m_lowerCannyThreshRatio, m_algoParams.m_upperCannyThreshRatio, + m_algoParams.m_cannyBackendType, m_algoParams.m_filteringAndGradientType); } - vpImageFilter::canny(I, m_edgeMap, m_algoParams.m_gaussianKernelSize, lowerCannyThresh, upperCannyThresh, m_algoParams.m_sobelKernelSize); -#else - m_cannyVisp.setCannyThresholds(m_algoParams.m_lowerCannyThresh, m_algoParams.m_upperCannyThresh); - m_cannyVisp.setGradients(m_dIx, m_dIy); - m_edgeMap = m_cannyVisp.detect(I); -#endif for (int i = 0; i < m_algoParams.m_edgeMapFilteringNbIter; i++) { filterEdgeMap(); diff --git a/tutorial/imgproc/hough-transform/config/detector_img.json b/tutorial/imgproc/hough-transform/config/detector_img.json index febadcdcbe..3b0c490eb7 100644 --- a/tutorial/imgproc/hough-transform/config/detector_img.json +++ b/tutorial/imgproc/hough-transform/config/detector_img.json @@ -1,6 +1,10 @@ { + "cannyBackendType": "opencv-backend", + "filteringAndGradientType": "gaussianblur+sobel-filtering", "lowerCannyThresh": 100.0, + "lowerThresholdRatio": 0.6, "upperCannyThresh": 200.0, + "upperThresholdRatio": 0.8, "centerMinDistance": 5.0, "centerThresh": 100.0, "centerXlimits": [ From d9160532b1ff990f25ec589430e00c3bc2b91c37 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 11 Oct 2023 10:37:23 +0200 Subject: [PATCH 05/14] Compute the threshold on the gradient image instead of the equalized histogram version of the gradient with OpenCV --- modules/core/src/image/vpImageFilter.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index 98dae6772e..3771003959 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -638,10 +638,6 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p cv::addWeighted(sobelxabs, 1, sobelyabs, 1, 0, sobel); sobel.convertTo(sobel, CV_8U); - // Equalize the histogram - cv::Mat equalized; - cv::equalizeHist(sobel, equalized); - // Compute the upper threshold from the equalized histogram cv::Mat hist; const float range[] = { 0.f, 256.f }; // The upper boundary is exclusive @@ -651,7 +647,7 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p int histSize[] = { bins }; bool uniform = true; bool accumulate = false; // Clear the histogram at the beginning of calcHist if false, does not clear it otherwise - cv::calcHist(&equalized, 1, channels, cv::Mat(), hist, dims, histSize, ranges, uniform, accumulate); + cv::calcHist(&sobel, 1, channels, cv::Mat(), hist, dims, histSize, ranges, uniform, accumulate); float accu = 0; float t = (float)(upperThresholdRatio * w * h); float bon = 0; @@ -922,7 +918,7 @@ void vpImageFilter::canny(const vpImage &Isrc, vpImage Date: Wed, 18 Oct 2023 09:58:56 +0200 Subject: [PATCH 06/14] [CORPS] Added Scharr 3x3 filter and CANNY_GBLUR_SCHARR_FILTERING option --- .../include/visp3/core/vpCannyEdgeDetection.h | 28 ++--- .../core/include/visp3/core/vpImageFilter.h | 59 ++++++++- .../core/src/image/vpCannyEdgeDetection.cpp | 46 ++++--- modules/core/src/image/vpImageFilter.cpp | 116 ++++++++++++------ .../visp3/imgproc/vpCircleHoughTransform.h | 44 +++---- .../imgproc/src/vpCircleHoughTransform.cpp | 43 ++++--- 6 files changed, 220 insertions(+), 116 deletions(-) diff --git a/modules/core/include/visp3/core/vpCannyEdgeDetection.h b/modules/core/include/visp3/core/vpCannyEdgeDetection.h index cc22dd3e02..3cab0823ed 100644 --- a/modules/core/include/visp3/core/vpCannyEdgeDetection.h +++ b/modules/core/include/visp3/core/vpCannyEdgeDetection.h @@ -67,9 +67,9 @@ class VISP_EXPORT vpCannyEdgeDetection // // Gradient computation attributes bool m_areGradientAvailable; /*!< Set to true if the user provides the gradient images, false otherwise. In the latter case, the class will compute the gradients.*/ - unsigned int m_sobelAperture; /*!< The size of the Sobel kernels used to compute the gradients of the image.*/ - vpArray2D m_sobelX; /*!< Array that contains the Sobel kernel along the X-axis.*/ - vpArray2D m_sobelY; /*!< Array that contains the Sobel kernel along the Y-axis.*/ + unsigned int m_gradientFilterKernelSize; /*!< The size of the Sobel kernels used to compute the gradients of the image.*/ + vpArray2D m_gradientFilterX; /*!< Array that contains the gradient filter kernel (Sobel or Scharr) along the X-axis.*/ + vpArray2D m_gradientFilterY; /*!< Array that contains the gradient filter kernel (Sobel or Scharr) along the Y-axis.*/ vpImage m_dIx; /*!< X-axis gradient.*/ vpImage m_dIy; /*!< Y-axis gradient.*/ @@ -98,9 +98,9 @@ class VISP_EXPORT vpCannyEdgeDetection void initGaussianFilters(); /** - * \brief Initialize the Sobel filters used to compute the input image gradients. + * \brief Initialize the gradient filters (Sobel or Scharr) used to compute the input image gradients. */ - void initSobelFilters(); + void initGradientFilters(); //@} /** @name Different steps methods */ @@ -216,7 +216,7 @@ class VISP_EXPORT vpCannyEdgeDetection detector.m_gaussianStdev = j.value("gaussianStdev", detector.m_gaussianStdev); detector.m_lowerThreshold = j.value("lowerThreshold", detector.m_lowerThreshold); detector.m_lowerThresholdRatio = j.value("lowerThresholdRatio", detector.m_lowerThresholdRatio); - detector.m_sobelAperture = j.value("sobelAperture", detector.m_sobelAperture); + detector.m_gradientFilterKernelSize = j.value("gradientFilterKernelSize", detector.m_gradientFilterKernelSize); detector.m_upperThreshold = j.value("upperThreshold", detector.m_upperThreshold); detector.m_upperThresholdRatio = j.value("upperThresholdRatio", detector.m_upperThresholdRatio); } @@ -236,7 +236,7 @@ class VISP_EXPORT vpCannyEdgeDetection {"gaussianStdev", detector.m_gaussianStdev}, {"lowerThreshold", detector.m_lowerThreshold}, {"lowerThresholdRatio", detector.m_lowerThresholdRatio}, - {"sobelAperture", detector.m_sobelAperture}, + {"gradientFilterKernelSize", detector.m_gradientFilterKernelSize}, {"upperThreshold", detector.m_upperThreshold}, {"upperThresholdRatio", detector.m_upperThresholdRatio} }; @@ -285,6 +285,7 @@ class VISP_EXPORT vpCannyEdgeDetection inline void setFilteringAndGradientType(const vpImageFilter::vpCannyFilteringAndGradientType &type) { m_filteringAndGradientType = type; + initGradientFilters(); } /** @@ -351,17 +352,14 @@ class VISP_EXPORT vpCannyEdgeDetection } /** - * \brief Set the Gaussian Filters kernel size and standard deviation - * and initialize the aforementioned filters. + * \brief Set the parameters of the gradient filter (Sobel or Scharr) kernel size filters. * - * \param[in] kernelSize : The size of the Gaussian filters kernel. - * \param[in] stdev : The standard deviation of the Gaussian filters used to blur and - * compute the gradient of the image. + * \param[in] apertureSize The size of the gradient filters kernel. Must be an odd value. */ - inline void setSobelAperture(const unsigned int &sobelAperture) + inline void setGradientFilterAperture(const unsigned int &apertureSize) { - m_sobelAperture = sobelAperture; - initSobelFilters(); + m_gradientFilterKernelSize = apertureSize; + initGradientFilters(); } //@} }; diff --git a/modules/core/include/visp3/core/vpImageFilter.h b/modules/core/include/visp3/core/vpImageFilter.h index 0b14f7b4af..3309f2c30d 100644 --- a/modules/core/include/visp3/core/vpImageFilter.h +++ b/modules/core/include/visp3/core/vpImageFilter.h @@ -83,7 +83,8 @@ class VISP_EXPORT vpImageFilter typedef enum vpCannyFilteringAndGradientType { CANNY_GBLUR_SOBEL_FILTERING = 0, //!< Apply Gaussian blur + Sobel operator on the input image - CANNY_COUNT_FILTERING = 1 //! Number of supported backends + CANNY_GBLUR_SCHARR_FILTERING = 1, //!< Apply Gaussian blur + Scharr operator on the input image + CANNY_COUNT_FILTERING = 2 //! Number of supported backends } vpCannyFilteringAndGradientType; static std::string vpCannyFilteringAndGradientTypeToString(const vpCannyFilteringAndGradientType &type); @@ -1007,6 +1008,56 @@ class VISP_EXPORT vpImageFilter vpImageFilter::getGradY(GIx, dIy, gaussianDerivativeKernel, size); } + /*! + Get Scharr kernel for X-direction. + \tparam FilterType: Either float, to accelerate the computation time, or double, to have greater precision. + \param filter : Pointer to a double array already allocated. + \param size : Kernel size computed as: kernel_size = size*2 + 1 (max size is 20). + \return Scaling factor. + */ + template + inline static FilterType getScharrKernelX(FilterType *filter, unsigned int size) + { + 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); + } + + vpArray2D ScharrY(size * 2 + 1, size * 2 + 1); + FilterType norm = getScharrKernelY(ScharrY.data, size); + memcpy(filter, ScharrY.t().data, ScharrY.getRows() * ScharrY.getCols() * sizeof(FilterType)); + return norm; + } + + /*! + Get Scharr kernel for Y-direction. + \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. + \param filter : Pointer to a double array already allocated. + \param size : Kernel size computed as: kernel_size = size*2 + 1 (max size is 20). + \return Scaling factor. + */ + template + inline static FilterType getScharrKernelY(FilterType *filter, unsigned int size) + { + // Scharr kernel pre-computed for the usual size + static const FilterType ScharrY3x3[9] = { -3.0, -10.0, -3.0, 0.0, 0.0, 0.0, 3.0, 10.0, 3.0 }; + + 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); + } + + const unsigned int kernel_size = size * 2 + 1; + if (kernel_size == 3) { + memcpy(filter, ScharrY3x3, kernel_size * kernel_size * sizeof(FilterType)); + return 1 / 32.0; + } + + return 0.; + } + /*! Get Sobel kernel for X-direction. \tparam FilterType: Either float, to accelerate the computation time, or double, to have greater precision. @@ -1090,13 +1141,15 @@ class VISP_EXPORT vpImageFilter const vpImage *p_dIx = nullptr, const vpImage *p_dIy = nullptr, const unsigned int gaussianKernelSize = 5, const float gaussianStdev = 2.f, const unsigned int apertureSobel = 3, - const float lowerThresholdRatio = 0.6, const float upperThresholdRatio = 0.8); + const float lowerThresholdRatio = 0.6, const float upperThresholdRatio = 0.8, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING); #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, float &lowerThresh, const unsigned int gaussianKernelSize = 5, const float gaussianStdev = 2.f, const unsigned int apertureSobel = 3, - const float lowerThresholdRatio = 0.6, const float upperThresholdRatio = 0.8); + const float lowerThresholdRatio = 0.6, const float upperThresholdRatio = 0.8, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING); static float median(const cv::Mat &cv_I); static float median(const vpImage &Isrc); static std::vector median(const vpImage &Isrc); diff --git a/modules/core/src/image/vpCannyEdgeDetection.cpp b/modules/core/src/image/vpCannyEdgeDetection.cpp index a76bde2a29..5f0779d5ec 100644 --- a/modules/core/src/image/vpCannyEdgeDetection.cpp +++ b/modules/core/src/image/vpCannyEdgeDetection.cpp @@ -41,14 +41,14 @@ vpCannyEdgeDetection::vpCannyEdgeDetection() , m_gaussianKernelSize(3) , m_gaussianStdev(1.f) , m_areGradientAvailable(false) - , m_sobelAperture(3) + , m_gradientFilterKernelSize(3) , m_lowerThreshold(-1.f) , m_lowerThresholdRatio(0.6f) , m_upperThreshold(-1.f) , m_upperThresholdRatio(0.8f) { initGaussianFilters(); - initSobelFilters(); + initGradientFilters(); } vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev @@ -59,14 +59,14 @@ vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const , m_gaussianKernelSize(gaussianKernelSize) , m_gaussianStdev(gaussianStdev) , m_areGradientAvailable(false) - , m_sobelAperture(sobelAperture) + , m_gradientFilterKernelSize(sobelAperture) , m_lowerThreshold(lowerThreshold) , m_lowerThresholdRatio(lowerThresholdRatio) , m_upperThreshold(upperThreshold) , m_upperThresholdRatio(upperThresholdRatio) { initGaussianFilters(); - initSobelFilters(); + initGradientFilters(); } #ifdef VISP_HAVE_NLOHMANN_JSON @@ -95,10 +95,10 @@ vpCannyEdgeDetection::initFromJSON(const std::string &jsonPath) msg << "Byte position of error: " << e.byte; throw vpException(vpException::ioError, msg.str()); } - *this = j; // Call from_json(const json& j, vpDetectionCircle2D& *this) to read json + from_json(j, *this); file.close(); initGaussianFilters(); - initSobelFilters(); + initGradientFilters(); } #endif @@ -113,16 +113,23 @@ vpCannyEdgeDetection::initGaussianFilters() } void -vpCannyEdgeDetection::initSobelFilters() +vpCannyEdgeDetection::initGradientFilters() { - if ((m_sobelAperture % 2) == 0) { - std::string errMsg("The Sobel kernel (" + std::to_string(m_sobelAperture) + ") should be odd"); - throw(vpException(vpException::badValue, errMsg)); + if ((m_gradientFilterKernelSize % 2) != 1) { + throw vpException(vpException::badValue, "Gradient filters kernel size should be odd."); + } + m_gradientFilterX.resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize); + m_gradientFilterY.resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize); + + if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { + vpImageFilter::getSobelKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1)/2); + vpImageFilter::getSobelKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1)/2); + } + else if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { + // Compute the Scharr filters + vpImageFilter::getScharrKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1)/2); + vpImageFilter::getScharrKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1)/2); } - m_sobelX.resize(m_sobelAperture, m_sobelAperture); - vpImageFilter::getSobelKernelX(m_sobelX.data, (m_sobelAperture - 1)/2); - m_sobelY.resize(m_sobelAperture, m_sobelAperture); - vpImageFilter::getSobelKernelY(m_sobelY.data, (m_sobelAperture - 1)/2); } // // Detection methods @@ -166,8 +173,8 @@ vpCannyEdgeDetection::detect(const vpImage &I) float lowerThreshold = m_lowerThreshold; if (upperThreshold < 0) { upperThreshold = vpImageFilter::computeCannyThreshold(I, lowerThreshold, &m_dIx, &m_dIy, m_gaussianKernelSize, - m_gaussianStdev, m_sobelAperture, m_lowerThresholdRatio, - m_upperThresholdRatio); + m_gaussianStdev, m_gradientFilterKernelSize, m_lowerThresholdRatio, + m_upperThresholdRatio, m_filteringAndGradientType); } else if (m_lowerThreshold < 0) { // Applying Canny recommendation to have the upper threshold 3 times greater than the lower threshold. @@ -184,7 +191,8 @@ vpCannyEdgeDetection::detect(const vpImage &I) void vpCannyEdgeDetection::performFilteringAndGradientComputation(const vpImage &I) { - if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { + if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING + || m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { // Computing the Gaussian blur vpImage Iblur; vpImage GIx; @@ -192,8 +200,8 @@ vpCannyEdgeDetection::performFilteringAndGradientComputation(const vpImage(GIx, Iblur, m_fg.data, m_gaussianKernelSize); // Computing the gradients - vpImageFilter::filter(Iblur, m_dIx, m_sobelX); - vpImageFilter::filter(Iblur, m_dIy, m_sobelY); + vpImageFilter::filter(Iblur, m_dIx, m_gradientFilterX); + vpImageFilter::filter(Iblur, m_dIy, m_gradientFilterY); } else { std::string errmsg("Currently, the only filtering and gradient operators are Gaussian blur + Sobel"); diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index 3771003959..69aa9642b8 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -99,6 +99,9 @@ std::string vpImageFilter::vpCannyFilteringAndGradientTypeToString(const vpImage case vpCannyFilteringAndGradientType::CANNY_GBLUR_SOBEL_FILTERING: name = "gaussianblur+sobel-filtering"; break; + case vpCannyFilteringAndGradientType::CANNY_GBLUR_SCHARR_FILTERING: + name = "gaussianblur+scharr-filtering"; + break; case vpCannyFilteringAndGradientType::CANNY_COUNT_FILTERING: default: return "unknown-filtering"; @@ -592,7 +595,7 @@ std::vector vpImageFilter::median(const vpImage &Isrc) } /** - * \brief Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel operators to compute + * \brief Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel or + Scharr operators to compute * the gradient of the image. * * \param[in] cv_I : The image, in cv format. @@ -601,21 +604,24 @@ std::vector vpImageFilter::median(const vpImage &Isrc) * \param[out] lowerThresh : The lower threshold for the Canny edge filter. * \param[in] gaussianFilterSize : The size of the mask of the Gaussian filter to apply (an odd number). * \param[in] gaussianStdev : The standard deviation of the Gaussian filter to apply. - * \param[in] apertureSobel : Size of the mask for the Sobel operator (odd number). + * \param[in] apertureGradient : Size of the mask for the Sobel operator (odd number). * \param[in] lowerThresholdRatio : The ratio of the upper threshold the lower threshold must be equal to. * \param[in] upperThresholdRatio : The ratio of pixels whose absolute gradient Gabs is lower or equal to to define + * \param[in] filteringType : The gradient filter to apply to compute the gradient, if \b p_cv_dIx and \b p_cv_dIy are + * nullptr. * the upper threshold. * \return The upper Canny edge filter threshold. */ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_dIx, const cv::Mat *p_cv_dIy, float &lowerThresh, const unsigned int gaussianKernelSize, - const float gaussianStdev, const unsigned int apertureSobel, - const float lowerThresholdRatio, const float upperThresholdRatio) + const float gaussianStdev, const unsigned int apertureGradient, + const float lowerThresholdRatio, const float upperThresholdRatio, + const vpImageFilter::vpCannyFilteringAndGradientType &filteringType) { double w = cv_I.cols; double h = cv_I.rows; int bins = 256; - cv::Mat sobel, sobelx, sobely, sobelxabs, sobelyabs; + cv::Mat dI, dIx, dIy, dIx_abs, dIy_abs; if (p_cv_dIx == nullptr || p_cv_dIy == nullptr) { cv::Mat img_blur; @@ -624,19 +630,25 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p cv::GaussianBlur(cv_I, img_blur, gsz, gaussianStdev); // Compute the gradient of the blurred image - cv::Sobel(img_blur, sobelx, CV_16S, 1, 0, apertureSobel, 1, 0); - cv::Sobel(img_blur, sobely, CV_16S, 0, 1, apertureSobel, 1, 0); + if (filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { + cv::Sobel(img_blur, dIx, CV_16S, 1, 0, apertureGradient, 1, 0); + cv::Sobel(img_blur, dIy, CV_16S, 0, 1, apertureGradient, 1, 0); + } + else if (filteringType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { + cv::Scharr(img_blur, dIx, CV_16S, 1, 0); + cv::Scharr(img_blur, dIy, CV_16S, 0, 1); + } } else { - sobelx = *p_cv_dIx; - sobely = *p_cv_dIy; + dIx = *p_cv_dIx; + dIy = *p_cv_dIy; } // Compute the absolute gradient of the blurred image G = |dIx| + |dIy| - cv::convertScaleAbs(sobelx, sobelxabs); - cv::convertScaleAbs(sobely, sobelyabs); - cv::addWeighted(sobelxabs, 1, sobelyabs, 1, 0, sobel); - sobel.convertTo(sobel, CV_8U); + cv::convertScaleAbs(dIx, dIx_abs); + cv::convertScaleAbs(dIy, dIy_abs); + cv::addWeighted(dIx_abs, 1, dIy_abs, 1, 0, dI); + dI.convertTo(dI, CV_8U); // Compute the upper threshold from the equalized histogram cv::Mat hist; @@ -647,7 +659,7 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p int histSize[] = { bins }; bool uniform = true; bool accumulate = false; // Clear the histogram at the beginning of calcHist if false, does not clear it otherwise - cv::calcHist(&sobel, 1, channels, cv::Mat(), hist, dims, histSize, ranges, uniform, accumulate); + cv::calcHist(&dI, 1, channels, cv::Mat(), hist, dims, histSize, ranges, uniform, accumulate); float accu = 0; float t = (float)(upperThresholdRatio * w * h); float bon = 0; @@ -666,7 +678,7 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p #endif /** - * \brief Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel operators to compute + * \brief Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel or + Scharr operators to compute * the gradient of the image. * * \param[in] I : The gray-scale image, in ViSP format. @@ -675,17 +687,20 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p * \param[in] lowerThresh : Canny lower threshold. * \param[in] gaussianFilterSize : The size of the mask of the Gaussian filter to apply (an odd number). * \param[in] gaussianStdev : The standard deviation of the Gaussian filter to apply. - * \param[in] apertureSobel : Size of the mask for the Sobel operator (odd number). + * \param[in] apertureGradient : Size of the mask for the Sobel operator (odd number). * \param[in] lowerThresholdRatio : The ratio of the upper threshold the lower threshold must be equal to. * \param[in] upperThresholdRatio : The ratio of pixels whose absolute gradient Gabs is lower or equal to define * 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. * \return The upper Canny edge filter threshold. */ 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 apertureSobel, - const float lowerThresholdRatio, const float upperThresholdRatio) + const float gaussianStdev, const unsigned int apertureGradient, + const float lowerThresholdRatio, const float upperThresholdRatio, + const vpImageFilter::vpCannyFilteringAndGradientType &filteringType) { double w = I.getWidth(); double h = I.getHeight(); @@ -701,13 +716,20 @@ float vpImageFilter::computeCannyThreshold(const vpImage &I, floa vpImage Iblur; vpImageFilter::gaussianBlur(I, Iblur, gaussianKernelSize, gaussianStdev); - vpArray2D sobelX(apertureSobel, apertureSobel); // Sobel kernel along x - vpImageFilter::getSobelKernelX(sobelX.data, (apertureSobel - 1)/2); - vpArray2D sobelY(apertureSobel, apertureSobel); // Sobel kernel along y - vpImageFilter::getSobelKernelY(sobelY.data, (apertureSobel - 1)/2); + vpArray2D gradientFilterX(apertureGradient, apertureGradient); // Gradient filter along the X-axis + vpArray2D gradientFilterY(apertureGradient, apertureGradient); // Gradient filter along the Y-axis - vpImageFilter::filter(Iblur, dIx, sobelX); - vpImageFilter::filter(Iblur, dIy, sobelY); + if (filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { + vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Sobel kernel along x + vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Sobel kernel along y + } + else if (filteringType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { + vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Scharr kernel along x + vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Scharr kernel along y + } + + vpImageFilter::filter(Iblur, dIx, gradientFilterX); + vpImageFilter::filter(Iblur, dIy, gradientFilterY); } // Computing the absolute gradient of the image G = |dIx| + |dIy| @@ -895,7 +917,7 @@ int main() greater than this value are marked as an edge. If negative, it will be automatically computed, along with the lower threshold. Otherwise, the lower threshold will be set to one third of the thresholdCanny . - \param[in] apertureSobel : Size of the mask for the Sobel operator (odd number). + \param[in] apertureGradient : Size of the mask for the gardient (Sobel or Scharr) operator (odd number). \param[in] gaussianStdev : The standard deviation of the Gaussian filter to apply. \param[in] lowerThresholdRatio : The ratio of the upper threshold the lower threshold must be equal to. It is used only if the user asks to compute the Canny thresholds. @@ -908,7 +930,7 @@ int main() */ void vpImageFilter::canny(const vpImage &Isrc, vpImage &Ires, const unsigned int &gaussianFilterSize, - const float &lowerThreshold, const float &upperThreshold, const unsigned int &apertureSobel, + const float &lowerThreshold, const float &upperThreshold, const unsigned int &apertureGradient, const float &gaussianStdev, const float &lowerThresholdRatio, const float &upperThresholdRatio, const vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps) { @@ -919,8 +941,14 @@ void vpImageFilter::canny(const vpImage &Isrc, vpImage &Isrc, vpImage &Isrc, vpImage dIx, dIy; - if (cannyFilteringSteps == CANNY_GBLUR_SOBEL_FILTERING) { + if (cannyFilteringSteps == CANNY_GBLUR_SOBEL_FILTERING + || cannyFilteringSteps == CANNY_GBLUR_SCHARR_FILTERING) { // Computing the Gaussian blur + gradients of the image vpImage Iblur; vpImageFilter::gaussianBlur(Isrc, Iblur, gaussianFilterSize, gaussianStdev); - // Compute the Sobel filters - vpArray2D sobelX(apertureSobel, apertureSobel); // Sobel kernel along x - vpImageFilter::getSobelKernelX(sobelX.data, (apertureSobel - 1)/2); - vpArray2D sobelY(apertureSobel, apertureSobel); // Sobel kernel along y - vpImageFilter::getSobelKernelY(sobelY.data, (apertureSobel - 1)/2); + // Compute the gradient filters + vpArray2D gradientFilterX(apertureGradient, apertureGradient); // Gradient filter along the X-axis + vpArray2D gradientFilterY(apertureGradient, apertureGradient); // Gradient filter along the Y-axis + + if (cannyFilteringSteps == CANNY_GBLUR_SOBEL_FILTERING) { + vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Sobel kernel along X + vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Sobel kernel along Y + } + else if (cannyFilteringSteps == CANNY_GBLUR_SCHARR_FILTERING) { + vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1)/2); + vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1)/2); + } - // Apply the Sobel filters to get the gradients - vpImageFilter::filter(Iblur, dIx, sobelX); - vpImageFilter::filter(Iblur, dIy, sobelY); + // Apply the gradient filters to get the gradients + vpImageFilter::filter(Iblur, dIx, gradientFilterX); + vpImageFilter::filter(Iblur, dIy, gradientFilterY); } else { std::string errMsg("[vpImageFilter::canny]Other types of Canny filtering steps have not been implemented"); @@ -969,12 +1005,12 @@ void vpImageFilter::canny(const vpImage &Isrc, vpImage 0"); } - params.m_sobelKernelSize = j.value("sobelKernelSize", params.m_sobelKernelSize); - if ((params.m_sobelKernelSize % 2) != 1) { - throw vpException(vpException::badValue, "Sobel Kernel size should be odd."); + params.m_gradientFilterKernelSize = j.value("gradientFilterKernelSize", params.m_gradientFilterKernelSize); + if ((params.m_gradientFilterKernelSize % 2) != 1) { + throw vpException(vpException::badValue, "Gradient filter kernel (Sobel or Scharr) size should be odd."); } std::string cannyBackendName = vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType); @@ -373,7 +373,7 @@ class VISP_EXPORT vpCircleHoughTransform {"filteringAndGradientType", vpImageFilter::vpCannyFilteringAndGradientTypeToString(params.m_filteringAndGradientType)}, {"gaussianKernelSize", params.m_gaussianKernelSize}, {"gaussianStdev", params.m_gaussianStdev}, - {"sobelKernelSize", params.m_sobelKernelSize}, + {"gradientFilterKernelSize", params.m_gradientFilterKernelSize}, {"cannyBackendType", vpImageFilter::vpCannyBackendTypeToString(params.m_cannyBackendType)}, {"lowerCannyThresh", params.m_lowerCannyThresh}, {"lowerThresholdRatio", params.m_lowerCannyThreshRatio}, @@ -526,6 +526,7 @@ class VISP_EXPORT vpCircleHoughTransform { m_algoParams.m_filteringAndGradientType = type; m_cannyVisp.setFilteringAndGradientType(type); + initGradientFilters(); } /** @@ -552,20 +553,19 @@ class VISP_EXPORT vpCircleHoughTransform } /** - * \brief Set the parameters of the Sobel filters, that computes the - * gradients of the image. + * \brief Set the parameters of the gradient filter (Sobel or Scharr) kernel size filters. * - * \param[in] apertureSize The size of the Sobel filters kernel. Must be an odd value. + * \param[in] apertureSize The size of the gradient filters kernel. Must be an odd value. */ - inline void setSobelAperture(const unsigned int &apertureSize) + inline void setGradientFilterAperture(const unsigned int &apertureSize) { - m_algoParams.m_sobelKernelSize = apertureSize; + m_algoParams.m_gradientFilterKernelSize = apertureSize; - if ((m_algoParams.m_sobelKernelSize % 2) != 1) { - throw vpException(vpException::badValue, "Sobel Kernel size should be odd."); + if ((m_algoParams.m_gradientFilterKernelSize % 2) != 1) { + throw vpException(vpException::badValue, "Gradient filter (Sobel or Scharr) Kernel size should be odd."); } - initSobelFilters(); + initGradientFilters(); } /** @@ -861,9 +861,9 @@ class VISP_EXPORT vpCircleHoughTransform void initGaussianFilters(); /** - * \brief Initialize the Gaussian filters used to blur the image compute the gradient images. + * \brief Initialize the gradient filters used to compute the gradient images. */ - void initSobelFilters(); + void initGradientFilters(); /** * \brief Perform Gaussian smoothing on the input image to reduce the noise @@ -931,8 +931,8 @@ class VISP_EXPORT vpCircleHoughTransform vpArray2D m_fg; // // Gradient computation attributes - vpArray2D m_sobelX; - vpArray2D m_sobelY; + 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.*/ vpImage m_dIy; /*!< Gradient along the y-axis of the input image.*/ diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 4df497cbbd..e28769f67f 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -39,14 +39,14 @@ vpCircleHoughTransform::vpCircleHoughTransform() : m_algoParams() { initGaussianFilters(); - initSobelFilters(); + initGradientFilters(); } vpCircleHoughTransform::vpCircleHoughTransform(const vpCircleHoughTransformParameters &algoParams) : m_algoParams(algoParams) { initGaussianFilters(); - initSobelFilters(); + initGradientFilters(); } void @@ -54,7 +54,7 @@ vpCircleHoughTransform::init(const vpCircleHoughTransformParameters &algoParams) { m_algoParams = algoParams; initGaussianFilters(); - initSobelFilters(); + initGradientFilters(); } vpCircleHoughTransform::~vpCircleHoughTransform() @@ -87,10 +87,10 @@ 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, vpDetectorDNN& *this) to read json + m_algoParams = j; // Call from_json(const json& j, vpCircleHoughTransformParameters&) to read json file.close(); initGaussianFilters(); - initSobelFilters(); + initGradientFilters(); } void @@ -109,16 +109,24 @@ vpCircleHoughTransform::initGaussianFilters() } void -vpCircleHoughTransform::initSobelFilters() +vpCircleHoughTransform::initGradientFilters() { - if ((m_algoParams.m_sobelKernelSize % 2) != 1) { - throw vpException(vpException::badValue, "Sobel Kernel size should be odd."); + if ((m_algoParams.m_gradientFilterKernelSize % 2) != 1) { + throw vpException(vpException::badValue, "Gradient filters Kernel size should be odd."); + } + m_gradientFilterX.resize(m_algoParams.m_gradientFilterKernelSize, m_algoParams.m_gradientFilterKernelSize); + m_gradientFilterY.resize(m_algoParams.m_gradientFilterKernelSize, m_algoParams.m_gradientFilterKernelSize); + m_cannyVisp.setGradientFilterAperture(m_algoParams.m_gradientFilterKernelSize); + + if (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { + vpImageFilter::getSobelKernelX(m_gradientFilterX.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); + vpImageFilter::getSobelKernelY(m_gradientFilterY.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); + } + else if (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { + // Compute the Scharr filters + vpImageFilter::getScharrKernelX(m_gradientFilterX.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); + vpImageFilter::getScharrKernelY(m_gradientFilterY.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); } - m_sobelX.resize(m_algoParams.m_sobelKernelSize, m_algoParams.m_sobelKernelSize); - vpImageFilter::getSobelKernelX(m_sobelX.data, (m_algoParams.m_sobelKernelSize - 1)/2); - m_sobelY.resize(m_algoParams.m_sobelKernelSize, m_algoParams.m_sobelKernelSize); - vpImageFilter::getSobelKernelY(m_sobelY.data, (m_algoParams.m_sobelKernelSize - 1)/2); - m_cannyVisp.setSobelAperture(m_algoParams.m_sobelKernelSize); } std::vector @@ -225,15 +233,16 @@ vpCircleHoughTransform::detect(const vpImage &I) void vpCircleHoughTransform::computeGradientsAfterGaussianSmoothing(const vpImage &I) { - if (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { + 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); // Computing the gradients - vpImageFilter::filter(Iblur, m_dIx, m_sobelX); - vpImageFilter::filter(Iblur, m_dIy, m_sobelY); + vpImageFilter::filter(Iblur, m_dIx, m_gradientFilterX); + vpImageFilter::filter(Iblur, m_dIy, m_gradientFilterY); } else { std::string errMsg("[computeGradientsAfterGaussianSmoothing] The filtering + gradient operators \""); @@ -259,7 +268,7 @@ vpCircleHoughTransform::edgeDetection(const vpImage &I) // 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_sobelKernelSize, m_algoParams.m_gaussianStdev, + m_algoParams.m_upperCannyThresh, m_algoParams.m_gradientFilterKernelSize, m_algoParams.m_gaussianStdev, m_algoParams.m_lowerCannyThreshRatio, m_algoParams.m_upperCannyThreshRatio, m_algoParams.m_cannyBackendType, m_algoParams.m_filteringAndGradientType); } From eeeb9925ea2cf997c84fcd4d27126a3852690c2c Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 18 Oct 2023 14:13:07 +0200 Subject: [PATCH 07/14] [CORPS] When using visp-backend, work with normalized gradient filters, with which the computeCannyThreshold method works better. --- .../core/src/image/vpCannyEdgeDetection.cpp | 27 +++++++-- modules/core/src/image/vpImageFilter.cpp | 55 +++++++++++++++---- .../imgproc/src/vpCircleHoughTransform.cpp | 27 +++++++-- 3 files changed, 91 insertions(+), 18 deletions(-) diff --git a/modules/core/src/image/vpCannyEdgeDetection.cpp b/modules/core/src/image/vpCannyEdgeDetection.cpp index 5f0779d5ec..4329dfcdd0 100644 --- a/modules/core/src/image/vpCannyEdgeDetection.cpp +++ b/modules/core/src/image/vpCannyEdgeDetection.cpp @@ -121,15 +121,34 @@ vpCannyEdgeDetection::initGradientFilters() m_gradientFilterX.resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize); m_gradientFilterY.resize(m_gradientFilterKernelSize, 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; + if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { - vpImageFilter::getSobelKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1)/2); - 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 - vpImageFilter::getScharrKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1)/2); - 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 \""; + errMsg += vpImageFilter::vpCannyFilteringAndGradientTypeToString(m_filteringAndGradientType); + errMsg += "\" has not been implemented yet\n"; + throw vpException(vpException::notImplementedError, errMsg); + } + + scaleFilter(m_gradientFilterX, scaleX); + scaleFilter(m_gradientFilterY, scaleY); } // // Detection methods diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index 69aa9642b8..554db5e55a 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -719,15 +719,33 @@ float vpImageFilter::computeCannyThreshold(const vpImage &I, floa vpArray2D gradientFilterX(apertureGradient, apertureGradient); // Gradient filter along the X-axis vpArray2D gradientFilterY(apertureGradient, apertureGradient); // Gradient filter along the Y-axis - if (filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { - vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Sobel kernel along x - vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Sobel kernel along y + // 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++) { + filter[r][c] = filter[r][c] * scale; + } + }}; + + // Scales to apply to the filters to get a normalized gradient filter that gives a gradient + // between 0 and 255 for an vpImage + float scaleX = 1.f; + float scaleY = 1.f; + + if (filteringType == CANNY_GBLUR_SOBEL_FILTERING) { + scaleX = vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Sobel kernel along X + scaleY = vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Sobel kernel along Y } - else if (filteringType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { - vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Scharr kernel along x - vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Scharr kernel along y + else if (filteringType == CANNY_GBLUR_SCHARR_FILTERING) { + scaleX = vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Scharr kernel along X + scaleY = vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Scharr kernel along Y } + // Scale the gradient filters to have a normalized gradient filter + scaleFilter(gradientFilterX, scaleX); + scaleFilter(gradientFilterY, scaleY); + + // Apply the gradient filters to get the gradients vpImageFilter::filter(Iblur, dIx, gradientFilterX); vpImageFilter::filter(Iblur, dIy, gradientFilterY); } @@ -985,15 +1003,32 @@ void vpImageFilter::canny(const vpImage &Isrc, vpImage 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++) { + filter[r][c] = filter[r][c] * scale; + } + }}; + + // Scales to apply to the filters to get a normalized gradient filter that gives a gradient + // between 0 and 255 for an vpImage + float scaleX = 1.f; + float scaleY = 1.f; + if (cannyFilteringSteps == CANNY_GBLUR_SOBEL_FILTERING) { - vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Sobel kernel along X - vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Sobel kernel along Y + scaleX = vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Sobel kernel along X + scaleY = vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Sobel kernel along Y } else if (cannyFilteringSteps == CANNY_GBLUR_SCHARR_FILTERING) { - vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1)/2); - vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1)/2); + scaleX = vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1)/2); + scaleY = vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1)/2); } + // Scale the gradient filters to have a normalized gradient filter + scaleFilter(gradientFilterX, scaleX); + scaleFilter(gradientFilterY, scaleY); + // Apply the gradient filters to get the gradients vpImageFilter::filter(Iblur, dIx, gradientFilterX); vpImageFilter::filter(Iblur, dIy, gradientFilterY); diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 2e0ec58ca6..5d01152493 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -118,15 +118,34 @@ 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; + if (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { - vpImageFilter::getSobelKernelX(m_gradientFilterX.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); - vpImageFilter::getSobelKernelY(m_gradientFilterY.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); + // 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); } else if (m_algoParams.m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { // Compute the Scharr filters - vpImageFilter::getScharrKernelX(m_gradientFilterX.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); - vpImageFilter::getScharrKernelY(m_gradientFilterY.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); + scaleX = vpImageFilter::getScharrKernelX(m_gradientFilterX.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); + scaleY = vpImageFilter::getScharrKernelY(m_gradientFilterY.data, (m_algoParams.m_gradientFilterKernelSize - 1)/2); + } + else { + std::string errMsg = "[vpCircleHoughTransform::initGradientFilters] Error: gradient filtering method \""; + errMsg += vpImageFilter::vpCannyFilteringAndGradientTypeToString(m_algoParams.m_filteringAndGradientType); + errMsg += "\" has not been implemented yet\n"; + throw vpException(vpException::notImplementedError, errMsg); } + scaleFilter(m_gradientFilterX, scaleX); + scaleFilter(m_gradientFilterY, scaleY); } std::vector From 40de54c78efb79b736f40e4808faec4aab4b4094 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 18 Oct 2023 16:34:51 +0200 Subject: [PATCH 08/14] [CORPS] Working with normalized Gaussian kernel + corrected Sobel scale + normalize OpenCV gradients to make the computeCannyThresh works --- .../include/visp3/core/vpCannyEdgeDetection.h | 5 ++- .../core/include/visp3/core/vpImageFilter.h | 21 ++++++---- .../core/src/image/vpCannyEdgeDetection.cpp | 5 ++- modules/core/src/image/vpImageFilter.cpp | 40 +++++++++++++------ .../imgproc/src/vpCircleHoughTransform.cpp | 2 +- 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/modules/core/include/visp3/core/vpCannyEdgeDetection.h b/modules/core/include/visp3/core/vpCannyEdgeDetection.h index 3cab0823ed..18f4920c5b 100644 --- a/modules/core/include/visp3/core/vpCannyEdgeDetection.h +++ b/modules/core/include/visp3/core/vpCannyEdgeDetection.h @@ -177,10 +177,13 @@ class VISP_EXPORT vpCannyEdgeDetection * \param[in] upperThresholdRatio : If the thresholds must be computed,the upper threshold will be equal to the value * such as the number of pixels of the image times \b upperThresholdRatio have an absolute gradient lower than the * upper threshold. + * \param[in] filteringType : The filtering and gradient operators to apply to the image before the edge detection + * operation. */ vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev, const unsigned int &sobelAperture, const float &lowerThreshold = -1.f, const float &upperThreshold = -1.f, - const float &lowerThresholdRatio = 0.6f, const float &upperThresholdRatio = 0.8f); + const float &lowerThresholdRatio = 0.6f, const float &upperThresholdRatio = 0.8f, + const vpImageFilter::vpCannyFilteringAndGradientType &filteringType = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING); // // Configuration from files #ifdef VISP_HAVE_NLOHMANN_JSON diff --git a/modules/core/include/visp3/core/vpImageFilter.h b/modules/core/include/visp3/core/vpImageFilter.h index 3309f2c30d..98aab64da1 100644 --- a/modules/core/include/visp3/core/vpImageFilter.h +++ b/modules/core/include/visp3/core/vpImageFilter.h @@ -1013,7 +1013,7 @@ class VISP_EXPORT vpImageFilter \tparam FilterType: Either float, to accelerate the computation time, or double, to have greater precision. \param filter : Pointer to a double array already allocated. \param size : Kernel size computed as: kernel_size = size*2 + 1 (max size is 20). - \return Scaling factor. + \return Scaling factor to normalize the Scharr kernel. */ template inline static FilterType getScharrKernelX(FilterType *filter, unsigned int size) @@ -1035,7 +1035,7 @@ class VISP_EXPORT vpImageFilter \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. \param filter : Pointer to a double array already allocated. \param size : Kernel size computed as: kernel_size = size*2 + 1 (max size is 20). - \return Scaling factor. + \return Scaling factor to normalize the Scharr kernel. */ template inline static FilterType getScharrKernelY(FilterType *filter, unsigned int size) @@ -1063,7 +1063,7 @@ class VISP_EXPORT vpImageFilter \tparam FilterType: Either float, to accelerate the computation time, or double, to have greater precision. \param filter : Pointer to a double array already allocated. \param size : Kernel size computed as: kernel_size = size*2 + 1 (max size is 20). - \return Scaling factor. + \return Scaling factor to normalize the Sobel kernel. */ template inline static FilterType getSobelKernelX(FilterType *filter, unsigned int size) @@ -1084,7 +1084,7 @@ class VISP_EXPORT vpImageFilter \tparam FilterType : Either float, to accelerate the computation time, or double, to have greater precision. \param filter : Pointer to a double array already allocated. \param size : Kernel size computed as: kernel_size = size*2 + 1 (max size is 20). - \return Scaling factor. + \return Scaling factor to normalize the Sobel kernel. */ template inline static FilterType getSobelKernelY(FilterType *filter, unsigned int size) @@ -1113,28 +1113,33 @@ class VISP_EXPORT vpImageFilter throw vpException(vpException::dimensionError, "Cannot get Sobel kernel of size > 20!"); const unsigned int kernel_size = size * 2 + 1; + double scale = (1. / 8.); // Scale to normalize Sobel3x3 if (kernel_size == 3) { memcpy(filter, SobelY3x3, kernel_size * kernel_size * sizeof(FilterType)); - return 1 / 8.0; + return scale; } + scale *= 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 1 / 16.0; + return scale; } + scale *= 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 1 / 16.0; + return scale; } vpArray2D sobelY(7, 7); memcpy(sobelY.data, SobelY7x7, sobelY.getRows() * sobelY.getCols() * sizeof(FilterType)); 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 *= 1./ 16.; } memcpy(filter, sobelY.data, sobelY.getRows() * sobelY.getCols() * sizeof(FilterType)); - return 1 / 16.0; + return scale; } static float computeCannyThreshold(const vpImage &I, float &lowerThresh, diff --git a/modules/core/src/image/vpCannyEdgeDetection.cpp b/modules/core/src/image/vpCannyEdgeDetection.cpp index 4329dfcdd0..2f3c3fec6e 100644 --- a/modules/core/src/image/vpCannyEdgeDetection.cpp +++ b/modules/core/src/image/vpCannyEdgeDetection.cpp @@ -54,8 +54,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 ) - : m_filteringAndGradientType(vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) + : m_filteringAndGradientType(filteringType) , m_gaussianKernelSize(gaussianKernelSize) , m_gaussianStdev(gaussianStdev) , m_areGradientAvailable(false) @@ -109,7 +110,7 @@ vpCannyEdgeDetection::initGaussianFilters() throw(vpException(vpException::badValue, "The Gaussian kernel size should be odd")); } m_fg.resize(1, (m_gaussianKernelSize + 1)/2); - vpImageFilter::getGaussianKernel(m_fg.data, m_gaussianKernelSize, m_gaussianStdev, false); + vpImageFilter::getGaussianKernel(m_fg.data, m_gaussianKernelSize, m_gaussianStdev, true); } void diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index 554db5e55a..2fcb5402e9 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -631,12 +631,16 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p // Compute the gradient of the blurred image if (filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { - cv::Sobel(img_blur, dIx, CV_16S, 1, 0, apertureGradient, 1, 0); - cv::Sobel(img_blur, dIy, CV_16S, 0, 1, apertureGradient, 1, 0); + double scale = 1. / 8.; + if (apertureGradient > 3) { + scale *= std::pow(1./16., ((apertureGradient -1.)/2.) - 1.); + } + cv::Sobel(img_blur, dIx, CV_16S, 1, 0, apertureGradient, 1, 0, scale); + cv::Sobel(img_blur, dIy, CV_16S, 0, 1, apertureGradient, 1, 0, scale); } else if (filteringType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { - cv::Scharr(img_blur, dIx, CV_16S, 1, 0); - cv::Scharr(img_blur, dIy, CV_16S, 0, 1); + cv::Scharr(img_blur, dIx, CV_16S, 1, 0, 1.f/32.f); + cv::Scharr(img_blur, dIy, CV_16S, 0, 1, 1.f/32.f); } } else { @@ -959,14 +963,18 @@ void vpImageFilter::canny(const vpImage &Isrc, vpImage 3) { + scale *= std::pow(1./16., ((apertureGradient -1.)/2.) - 1.); + } + cv::Sobel(cv_I_blur, cv_dx, CV_16S, 1, 0, apertureGradient, scale); + cv::Sobel(cv_I_blur, cv_dy, CV_16S, 0, 1, apertureGradient, scale); } else if (cannyFilteringSteps == CANNY_GBLUR_SCHARR_FILTERING) { cv::Mat cv_I_blur; cv::GaussianBlur(img_cvmat, cv_I_blur, cv::Size((int)gaussianFilterSize, (int)gaussianFilterSize), gaussianStdev, 0); - cv::Scharr(cv_I_blur, cv_dx, CV_16S, 1, 0); - cv::Scharr(cv_I_blur, cv_dy, CV_16S, 0, 1); + cv::Scharr(cv_I_blur, cv_dx, CV_16S, 1, 0, 1.f/32.f); + cv::Scharr(cv_I_blur, cv_dy, CV_16S, 0, 1, 1.f/32.f); } else { std::string errMsg("[vpImageFilter::canny]Other types of Canny filtering steps have not been implemented"); @@ -976,7 +984,8 @@ void vpImageFilter::canny(const vpImage &Isrc, vpImage &Isrc, vpImage dIx, dIy; if (cannyFilteringSteps == CANNY_GBLUR_SOBEL_FILTERING || cannyFilteringSteps == CANNY_GBLUR_SCHARR_FILTERING) { - // Computing the Gaussian blur + gradients of the image + // Computing the Gaussian blur vpImage Iblur; - vpImageFilter::gaussianBlur(Isrc, Iblur, gaussianFilterSize, gaussianStdev); + vpArray2D fg(1, (gaussianFilterSize + 1)/2); + vpImageFilter::getGaussianKernel(fg.data, gaussianFilterSize, gaussianStdev, true); + vpImage GIx; + vpImageFilter::filterX(Isrc, GIx, fg.data, gaussianFilterSize); + vpImageFilter::filterY(GIx, Iblur, fg.data, gaussianFilterSize); // Compute the gradient filters vpArray2D gradientFilterX(apertureGradient, apertureGradient); // Gradient filter along the X-axis @@ -1040,13 +1053,14 @@ void vpImageFilter::canny(const vpImage &Isrc, vpImage Date: Wed, 18 Oct 2023 17:40:56 +0200 Subject: [PATCH 09/14] [TUTO] Added a tutorial for vpCannyEdgeDetection --- tutorial/image/CMakeLists.txt | 5 + tutorial/image/drawingHelpers.cpp | 90 +++++++++ tutorial/image/drawingHelpers.h | 103 +++++++++++ tutorial/image/tutorial-canny.cpp | 296 ++++++++++++++++++++++++++++++ 4 files changed, 494 insertions(+) create mode 100644 tutorial/image/drawingHelpers.cpp create mode 100644 tutorial/image/drawingHelpers.h create mode 100644 tutorial/image/tutorial-canny.cpp diff --git a/tutorial/image/CMakeLists.txt b/tutorial/image/CMakeLists.txt index 370c300e55..7fd6ad0b04 100644 --- a/tutorial/image/CMakeLists.txt +++ b/tutorial/image/CMakeLists.txt @@ -43,6 +43,11 @@ foreach(cpp ${tutorial_cpp}) endif() endforeach() +visp_add_target(tutorial-canny.cpp drawingHelpers.cpp) +if(COMMAND visp_add_dependency) + visp_add_dependency(tutorial-canny.cpp "tutorials") +endif() + # Copy the data files to the same location than the target foreach(data ${tutorial_data}) visp_copy_data(tutorial-viewer.cpp ${data}) diff --git a/tutorial/image/drawingHelpers.cpp b/tutorial/image/drawingHelpers.cpp new file mode 100644 index 0000000000..9345b1b6aa --- /dev/null +++ b/tutorial/image/drawingHelpers.cpp @@ -0,0 +1,90 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "drawingHelpers.h" + +#include + +#if defined(VISP_HAVE_X11) + vpDisplayX drawingHelpers::d; +#elif defined(HAVE_OPENCV_HIGHGUI) + vpDisplayOpenCV drawingHelpers::d; +#elif defined(VISP_HAVE_GTK) + vpDisplayGTK drawingHelpers::d; +#elif defined(VISP_HAVE_GDI) + vpDisplayGDI drawingHelpers::d; +#elif defined(VISP_HAVE_D3D9) + vpDisplayD3D drawingHelpers::d; +#endif + +vpImage drawingHelpers::I_disp; + +bool drawingHelpers::display(vpImage &I, const std::string &title, const bool &blockingMode) +{ + I_disp = I; + d.init(I_disp); + vpDisplay::setTitle(I_disp, title.c_str()); + + vpDisplay::display(I_disp); + vpDisplay::displayText(I_disp, 15, 15, "Left click to continue...", vpColor::red); + vpDisplay::displayText(I_disp, 35, 15, "Right click to stop...", vpColor::red); + vpDisplay::flush(I_disp); + vpMouseButton::vpMouseButtonType button; + vpDisplay::getClick(I_disp, button, blockingMode); + bool hasToContinue = true; + if (button == vpMouseButton::button3) + { + // Right click => stop the program + hasToContinue = false; + } + + return hasToContinue; +} + +bool drawingHelpers::display(vpImage &D, const std::string &title, const bool &blockingMode) +{ + vpImage I; // Image to display + vpImageConvert::convert(D, I); + return display(I, title, blockingMode); +} + +bool drawingHelpers::display(vpImage &D, const std::string &title, const bool &blockingMode) +{ + vpImage I; // Image to display + vpImageConvert::convert(D, I); + return display(I, title, blockingMode); +} + +bool drawingHelpers::display(vpImage &F, const std::string &title, const bool &blockingMode) +{ + vpImage I; // Image to display + vpImageConvert::convert(F, I); + return display(I, title, blockingMode); +} diff --git a/tutorial/image/drawingHelpers.h b/tutorial/image/drawingHelpers.h new file mode 100644 index 0000000000..ad3b4e57aa --- /dev/null +++ b/tutorial/image/drawingHelpers.h @@ -0,0 +1,103 @@ +/* + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ +#ifndef _drawingHelpers_h_ +#define _drawingHelpers_h_ + +#include +#include +#include +#include + +namespace drawingHelpers +{ + #if defined(VISP_HAVE_X11) + extern vpDisplayX d; + #elif defined(HAVE_OPENCV_HIGHGUI) + extern vpDisplayOpenCV d; + #elif defined(VISP_HAVE_GTK) + extern vpDisplayGTK d; + #elif defined(VISP_HAVE_GDI) + extern vpDisplayGDI d; + #elif defined(VISP_HAVE_D3D9) + extern vpDisplayD3D d; + #endif + + extern vpImage I_disp; /*!< Displayed image.*/ + + /** + * \brief Display a RGB image and catch the user clicks to know if + * the user wants to stop the program. + * + * \param[out] I The RGB image to display. + * \param[in] title The title of the window. + * \param[in] blockingMode If true, wait for a click to switch to the next image. + * \return true The user wants to continue the application. + * \return false The user wants to stop the application. + */ + bool display(vpImage &I, const std::string &title, const bool &blockingMode); + + /** + * \brief Display a gray-scale image and catch the user clicks to know if + * the user wants to stop the program. + * + * \param[out] I The gray-scale image to display. + * \param[in] title The title of the window. + * \param[in] blockingMode If true, wait for a click to switch to the next image. + * \return true The user wants to continue the application. + * \return false The user wants to stop the application. + */ + bool display(vpImage &I, const std::string &title, const bool &blockingMode); + + /** + * \brief Display a double precision image and catch the user clicks to know if + * the user wants to stop the program. + * + * \param[out] I The double precision image to display. + * \param[in] title The title of the window. + * \param[in] blockingMode If true, wait for a click to switch to the next image. + * \return true The user wants to continue the application. + * \return false The user wants to stop the application. + */ + bool display(vpImage &D, const std::string &title, const bool &blockingMode); + + /** + * \brief Display a floating-point precision image and catch the user clicks to know if + * the user wants to stop the program. + * + * \param[out] I The floating-point precision image to display. + * \param[in] title The title of the window. + * \param[in] blockingMode If true, wait for a click to switch to the next image. + * \return true The user wants to continue the application. + * \return false The user wants to stop the application. + */ + bool display(vpImage &F, const std::string &title, const bool &blockingMode); +} + +#endif diff --git a/tutorial/image/tutorial-canny.cpp b/tutorial/image/tutorial-canny.cpp new file mode 100644 index 0000000000..10aabe4f85 --- /dev/null +++ b/tutorial/image/tutorial-canny.cpp @@ -0,0 +1,296 @@ +/**************************************************************************** + * + * ViSP, open source Visual Servoing Platform software. + * Copyright (C) 2005 - 2023 by Inria. All rights reserved. + * + * This software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file LICENSE.txt at the root directory of this source + * distribution for additional information about the GNU GPL. + * + * For using ViSP with software that can not be combined with the GNU + * GPL, please contact Inria about acquiring a ViSP Professional + * Edition License. + * + * See https://visp.inria.fr for more information. + * + * This software was developed at: + * Inria Rennes - Bretagne Atlantique + * Campus Universitaire de Beaulieu + * 35042 Rennes Cedex + * France + * + * If you have questions regarding the use of this file, please contact + * Inria at visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * +*****************************************************************************/ +#include + +#include +#include +#include + +#ifdef HAVE_OPENCV_IMGPROC +#include +#endif + +#include "drawingHelpers.h" + +template +void computeMeanMaxStdev(const vpImage &I, float &mean, float &max, float &stdev) +{ + max = std::numeric_limits::epsilon(); + mean = 0.; + stdev = 0.; + unsigned int nbRows = I.getRows(); + unsigned int nbCols = I.getCols(); + float scale = 1. / ((float)nbRows * (float)nbCols); + for (unsigned int r = 0; r < nbRows; r++) { + for (unsigned int c = 0; c < nbCols; c++) { + mean += I[r][c]; + max = std::max(max, static_cast(I[r][c])); + } + } + mean *= scale; + for (unsigned int r = 0; r < nbRows; r++) { + for (unsigned int c = 0; c < nbCols; c++) { + stdev += (I[r][c] - mean) * (I[r][c] - mean); + } + } + stdev *= scale; + stdev = std::sqrt(stdev); +} + +void setGradientOutsideClass(const vpImage &I, const int &gaussianKernelSize, const float &gaussianStdev, vpCannyEdgeDetection &cannyDetector, + const unsigned int apertureSize, const vpImageFilter::vpCannyFilteringAndGradientType &filteringType) +{ + // Get the Gaussian blur kernel + if ((gaussianKernelSize % 2) == 0) { + throw(vpException(vpException::badValue, "The Gaussian kernel size should be odd")); + } + vpArray2D fg(1, (gaussianKernelSize + 1)/2); + vpImageFilter::getGaussianKernel(fg.data, gaussianKernelSize, gaussianStdev, true); + + // Get the gradient filters kernel + if ((apertureSize % 2) != 1) { + throw vpException(vpException::badValue, "Gradient filters kernel size should be odd."); + } + vpArray2D gradientFilterX(apertureSize, apertureSize); + vpArray2D gradientFilterY(apertureSize, apertureSize); + + 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; + + if (filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { + scaleX = vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureSize - 1)/2); + scaleY = vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureSize - 1)/2); + } + else if (filteringType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { + // Compute the Scharr filters + scaleX = vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureSize - 1)/2); + scaleY = vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureSize - 1)/2); + } + else { + std::string errMsg = "Error: gradient filtering method \""; + errMsg += vpImageFilter::vpCannyFilteringAndGradientTypeToString(filteringType); + errMsg += "\" has not been implemented yet\n"; + throw vpException(vpException::notImplementedError, errMsg); + } + + scaleFilter(gradientFilterX, scaleX); + scaleFilter(gradientFilterY, scaleY); + + // Perform Gaussian blur + vpImage Iblur; + vpImage GIx; + vpImageFilter::filterX(I, GIx, fg.data, gaussianKernelSize); + vpImageFilter::filterY(GIx, Iblur, fg.data, gaussianKernelSize); + + // Computing the gradients + vpImage dIx, dIy; + vpImageFilter::filter(Iblur, dIx, gradientFilterX); + vpImageFilter::filter(Iblur, dIy, gradientFilterY); + + // Set the gradients of the vpCannyEdgeDetection + cannyDetector.setGradients(dIx, dIy); + + // Display the gradients + float mean, max, stdev; + computeMeanMaxStdev(dIx, mean, max, stdev); + std::string title = "Gradient along the horizontal axis. Mean = " + std::to_string(mean) + + "+/-" + std::to_string(stdev) + " Max = " + std::to_string(max); + drawingHelpers::display(dIx, title, true); + computeMeanMaxStdev(dIy, mean, max, stdev); + title = "Gradient along the horizontal axis. Mean = " + std::to_string(mean) + + "+/-" + std::to_string(stdev) + " Max = " + std::to_string(max); + drawingHelpers::display(dIy, title, true); +} + +void usage(const std::string &softName) +{ + std::cout << "NAME" << std::endl; + std::cout << softName << ": software to test the vpCannyEdgeComputation class and vpImageFilter::canny method" << std::endl; + std::cout << "SYNOPSIS" << std::endl; + std::cout << "\t" << softName + << " [-i, --image ]" + << " [-g, --gradient ]" + << " [-t, --thresh ]" + << " [-a, --aperture ]" + << " [-f, --filter ]" + << " [-r, --ratio ]" + << " [-b, --backend ]" + << " [-h, --help]" + << std::endl; + std::cout << "DESCRIPTION" << std::endl; + std::cout << "\t-i, --image" << std::endl + << "\t\tPermits to load an image on which will be tested the vpCanny class." + << std::endl; + std::cout << "\t-g, --gradient" << std::endl + << "\t\tPermits to compute the gradients of the image outside the vpCanny class." + << "\t\tFirst parameter is the size of the Gaussian kernel used to compute the gradients." + << "\t\tSecond parameter is the standard deviation of the Gaussian kernel used to compute the gradients." + << std::endl; + std::cout << "\t-t, --thresh" << std::endl + << "\t\tPermits to set the lower and upper thresholds of the vpCanny class." + << "\t\tFirst parameter is the lower threshold." + << "\t\tSecond parameter is the upper threshold." + << std::endl; + std::cout << "\t-a, --aperture" << std::endl + << "\t\tPermits to set the size of the gradient filter kernel." + << "\t\tParameter must be odd and positive." + << std::endl; + std::cout << "\t-f, --filter" << std::endl + << "\t\tPermits to choose the type of filter to apply to compute the gradient." + << std::endl; + std::cout << "\t-r, --ratio" << std::endl + << "\t\tPermits to set the lower and upper thresholds ratio of the vpCanny class." + << "\t\tFirst parameter is the lower threshold ratio." + << "\t\tSecond parameter is the upper threshold ratio." + << std::endl; + std::cout << "\t-b, --backend" << std::endl + << "\t\tPermits to use the vpImageFilter::canny method for comparison." + << std::endl; + std::cout << "\t-h, --help" << std::endl + << "\t\tPermits to display the different arguments this software handles." + << std::endl; +} + +int main(int argc, const char *argv[]) +{ + std::string opt_img; + bool opt_gradientOutsideClass = false; + bool opt_useVpImageFilterCanny = false; + int opt_gaussianKernelSize = 3; + int opt_apertureSize = 3; + vpImageFilter::vpCannyFilteringAndGradientType opt_filteringType = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING; + float opt_gaussianStdev = 1.; + float opt_lowerThresh = -1.; + float opt_upperThresh = -1.; + float opt_lowerThreshRatio = 0.6f; + float opt_upperThreshRatio = 0.8f; + vpImageFilter::vpCannyBackendType opt_backend = vpImageFilter::CANNY_VISP_BACKEND; + for (int i = 1; i < argc; i++) { + std::string argv_str = std::string(argv[i]); + if ((argv_str == "-i" || argv_str == "--image") && i + 1 < argc) { + opt_img = std::string(argv[i + 1]); + i++; + } + else if ((argv_str == "-g" || argv_str == "--gradient") && i + 2 < argc) { + opt_gradientOutsideClass = true; + opt_gaussianKernelSize = atoi(argv[i + 1]); + opt_gaussianStdev = atoi(argv[i + 2]); + i += 2; + } + else if ((argv_str == "-t" || argv_str == "--thresh") && i + 2 < argc) { + opt_lowerThresh = atof(argv[i + 1]); + opt_upperThresh = atof(argv[i + 2]); + i += 2; + } + else if ((argv_str == "-a" || argv_str == "--aperture") && i + 1 < argc) { + opt_apertureSize = std::atoi(argv[i + 1]); + i++; + } + else if ((argv_str == "-f" || argv_str == "--filter") && i + 1 < argc) { + opt_filteringType = vpImageFilter::vpCannyFilteringAndGradientTypeFromString(std::string(argv[i + 1])); + i++; + } + else if ((argv_str == "-r" || argv_str == "--ratio") && i + 2 < argc) { + opt_lowerThreshRatio = std::atof(argv[i + 1]); + opt_upperThreshRatio = std::atof(argv[i + 2]); + i += 2; + } + else if ((argv_str == "-b" || argv_str == "--backend") && i + 1 < argc) { + opt_useVpImageFilterCanny = true; + opt_backend = vpImageFilter::vpCannyBackendTypeFromString(std::string(argv[i+1])); + i++; + } + else if (argv_str == "-h" || argv_str == "--help") { + usage(std::string(argv[0])); + return EXIT_SUCCESS; + } + else { + std::cerr << "Argument \"" << argv_str << "\" is unknown." << std::endl; + return EXIT_FAILURE; + } + } + + std::string configAsTxt("Canny Configuration:\n"); + configAsTxt += "\tFiltering + gradient operators = " + vpImageFilter::vpCannyFilteringAndGradientTypeToString(opt_filteringType) + "\n"; + configAsTxt += "\tGaussian filter kernel size = " + std::to_string(opt_gaussianKernelSize) + "\n"; + configAsTxt += "\tGaussian filter standard deviation = " + std::to_string(opt_gaussianStdev) + "\n"; + configAsTxt += "\tGradient filter kernel size = " + std::to_string(opt_apertureSize) + "\n"; + configAsTxt += "\tCanny edge filter thresholds = [" + std::to_string(opt_lowerThresh) + " ; " + std::to_string(opt_upperThresh) + "]\n"; + configAsTxt += "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" + std::to_string(opt_lowerThreshRatio) + " ; " + std::to_string(opt_upperThreshRatio) + "]\n"; + std::cout << configAsTxt << std::endl; + + vpCannyEdgeDetection cannyDetector(opt_gaussianKernelSize, opt_gaussianStdev, opt_apertureSize, + opt_lowerThresh, opt_upperThresh, opt_lowerThreshRatio, opt_upperThreshRatio, + opt_filteringType); + vpImage I_canny_input; + if (!opt_img.empty()) { + // Detection on the user image + vpImageIo::read(I_canny_input, opt_img); + } + else { + // Detection on a fake image of a square + I_canny_input.resize(500, 500, 0); + for (unsigned int r = 150; r < 350; r++) { + for (unsigned int c = 150; c < 350; c++) { + I_canny_input[r][c] = 125; + } + } + } + + if (opt_gradientOutsideClass) { + setGradientOutsideClass(I_canny_input, opt_gaussianKernelSize, opt_gaussianStdev, cannyDetector, opt_apertureSize, opt_filteringType); + } + vpImage I_canny = cannyDetector.detect(I_canny_input); + float mean, max, stdev; + computeMeanMaxStdev(I_canny_input, mean, max, stdev); + std::string title("Input of the Canny edge detector. Mean = " + std::to_string(mean) + "+/-" + std::to_string(stdev) + " Max = " + std::to_string(max)); + drawingHelpers::display(I_canny_input, title, true); + drawingHelpers::display(I_canny, "Canny results on image " + opt_img, true); + + if (opt_useVpImageFilterCanny) { + float cannyThresh = opt_upperThresh; + float lowerThresh(opt_lowerThresh); + vpImageFilter::canny(I_canny_input, I_canny, opt_gaussianKernelSize, lowerThresh, cannyThresh, + opt_apertureSize, opt_gaussianStdev, opt_lowerThreshRatio, opt_upperThreshRatio, + opt_backend, opt_filteringType); + drawingHelpers::display(I_canny, "Canny results with \"" + vpImageFilter::vpCannyBackendTypeToString(opt_backend) + "\" backend", true); + } + + return EXIT_SUCCESS; +} From fc14b3269c9c51920118851df295320581248cc1 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Thu, 19 Oct 2023 10:57:45 +0200 Subject: [PATCH 10/14] [CORPS] Added a method to compute the partial gradients from an image depending on the vpCannyFilteringAndGradientType and vpCannyBackendType --- .../core/include/visp3/core/vpImageConvert.h | 2 + .../core/include/visp3/core/vpImageFilter.h | 241 +++++++++- modules/core/src/image/vpImageConvert.cpp | 34 ++ modules/core/src/image/vpImageFilter.cpp | 434 ++++++++---------- .../imgproc/src/vpCircleHoughTransform.cpp | 2 +- tutorial/image/tutorial-canny.cpp | 55 +-- 6 files changed, 463 insertions(+), 305 deletions(-) diff --git a/modules/core/include/visp3/core/vpImageConvert.h b/modules/core/include/visp3/core/vpImageConvert.h index 2c7c9c57fb..cb045254b3 100644 --- a/modules/core/include/visp3/core/vpImageConvert.h +++ b/modules/core/include/visp3/core/vpImageConvert.h @@ -111,11 +111,13 @@ class VISP_EXPORT vpImageConvert static void convert(const cv::Mat &src, vpImage &dest, bool flip = false); static void convert(const cv::Mat &src, vpImage &dest, bool flip = false, unsigned int nThreads = 0); static void convert(const cv::Mat &src, vpImage &dest, bool flip = false); + static void convert(const cv::Mat &src, vpImage &dest, bool flip = false); static void convert(const cv::Mat &src, vpImage &dest, bool flip = false); static void convert(const cv::Mat &src, vpImage &dest, bool flip = false); static void convert(const vpImage &src, cv::Mat &dest); static void convert(const vpImage &src, cv::Mat &dest, bool copyData = true); static void convert(const vpImage &src, cv::Mat &dest, bool copyData = true); + static void convert(const vpImage &src, cv::Mat &dest, bool copyData = true); static void convert(const vpImage &src, cv::Mat &dest); #endif diff --git a/modules/core/include/visp3/core/vpImageFilter.h b/modules/core/include/visp3/core/vpImageFilter.h index 98aab64da1..2af1b1513c 100644 --- a/modules/core/include/visp3/core/vpImageFilter.h +++ b/modules/core/include/visp3/core/vpImageFilter.h @@ -45,7 +45,10 @@ #include #include +#include +#include #include +#include #include #include #include @@ -101,8 +104,8 @@ class VISP_EXPORT vpImageFilter static void canny(const vpImage &I, vpImage &Ic, const unsigned int &gaussianFilterSize, const float &lowerThresholdCanny, const float &higherThresholdCanny, const unsigned int &apertureSobel, const float &gaussianStdev, const float &lowerThresholdRatio, - const float &upperThresholdRatio, const vpCannyBackendType &cannyBackend, - const vpCannyFilteringAndGradientType &cannyFilteringSteps); + const float &upperThresholdRatio, const bool &normalizeGradients, + const vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps); /*! Apply a 1x3 derivative filter to an image pixel. @@ -1142,19 +1145,233 @@ class VISP_EXPORT vpImageFilter return scale; } - static float computeCannyThreshold(const vpImage &I, float &lowerThresh, - const vpImage *p_dIx = nullptr, const vpImage *p_dIy = nullptr, - const unsigned int gaussianKernelSize = 5, - const float gaussianStdev = 2.f, const unsigned int apertureSobel = 3, - const float lowerThresholdRatio = 0.6, const float upperThresholdRatio = 0.8, - const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING); - #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, - float &lowerThresh, const unsigned int gaussianKernelSize = 5, - const float gaussianStdev = 2.f, const unsigned int apertureSobel = 3, - const float lowerThresholdRatio = 0.6, const float upperThresholdRatio = 0.8, + float &lowerThresh, const unsigned int &gaussianKernelSize = 5, + const float &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); + + static void computePartialDerivatives(const cv::Mat &cv_I, + cv::Mat &cv_dIx, cv::Mat &cv_dIy, + const bool &computeDx = true, const bool &computeDy = true, const bool &normalize = true, + const unsigned int &gaussianKernelSize = 5, const float &gaussianStdev = 2.f, + const unsigned int &apertureGradient = 3, + const vpCannyFilteringAndGradientType &filteringType = CANNY_GBLUR_SOBEL_FILTERING); +#endif + + /** + * \brief Compute the partial derivatives (i.e. horizontal and vertical gradients) of the input image. + * + * \tparam ImageType Either unsigned char, float or double + * \tparam FilterType Either float or double. + * \param[in] I The input image we want the partial derivatives. + * \param[out] dIx The horizontal partial derivative, i.e. horizontal gradient. + * \param[out] dIy The vertical partial derivative, i.e. vertical gradient. + * \param[in] computeDx Idicate if we must compute the horizontal gradient. + * \param[in] computeDy Idicate if we must compute the vertical gradient. + * \param[in] normalize Idicate if we must normalize the gradient filters. + * \param[in] gaussianKernelSize The size of the kernel of the Gaussian filter used to blur the image. + * \param[in] gaussianStdev The standard deviation of the Gaussian filter used to blur the image. + * \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. + */ + 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) + { + 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); + if (computeDx) { + vpImageConvert::convert(cv_dIx, dIx); + } + if (computeDy) { + vpImageConvert::convert(cv_dIy, dIy); + } +#else + throw(vpException(vpException::badValue, "You need to compile ViSP with OpenCV to use CANNY_OPENCV_BACKEND")); +#endif + } + else { + if (filteringType == CANNY_GBLUR_SCHARR_FILTERING || filteringType == CANNY_GBLUR_SOBEL_FILTERING) { + dIx.resize(I.getHeight(), I.getWidth()); + dIy.resize(I.getHeight(), I.getWidth()); + + // Computing the Gaussian blur + gradients of the image + vpImage Iblur; + vpImageFilter::gaussianBlur(I, Iblur, gaussianKernelSize, gaussianStdev); + + 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++) { + filter[r][c] = filter[r][c] * scale; + } + }}; + + // Scales to apply to the filters to get a normalized gradient filter that gives a gradient + // between 0 and 255 for an vpImage + float scaleX = 1.f; + float scaleY = 1.f; + + if (filteringType == CANNY_GBLUR_SOBEL_FILTERING) { + if (computeDx) { + scaleX = vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1)/2); + } + if (computeDy) { + scaleY = vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1)/2); + } + } + else if (filteringType == CANNY_GBLUR_SCHARR_FILTERING) { + if (computeDx) { + scaleX = vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1)/2); + } + if (computeDy) { + scaleY = vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1)/2); + } + } + + // Scale the gradient filters to have a normalized gradient filter + if (normalize) { + if (computeDx) { + scaleFilter(gradientFilterX, scaleX); + } + if (computeDy) { + scaleFilter(gradientFilterY, scaleY); + } + } + + // Apply the gradient filters to get the gradients + if (computeDx) { + vpImageFilter::filter(Iblur, dIx, gradientFilterX); + } + + if (computeDy) { + vpImageFilter::filter(Iblur, dIy, gradientFilterY); + } + } + else { + std::string errMsg = "[vpImageFilter::computePartialDerivatives] Filtering + gradient method \""; + errMsg += vpCannyFilteringAndGradientTypeToString(filteringType); + errMsg += "\" is not implemented yet\n"; + throw(vpException(vpException::notImplementedError, errMsg)); + } + } + } + + 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; + + 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; + + 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; + + /** + * \brief Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel or + Scharr operators to compute + * the gradient of the image. + * + * \tparam OutType : Either float, to accelerate the computation time, or double, to have greater precision. + * \param[in] I : The gray-scale image, in ViSP format. + * \param[in] p_dIx : If different from nullptr, must contain the gradient of the image with regard to the horizontal axis. + * \param[in] p_dIy : If different from nullptr, must contain the gradient of the image with regard to the vertical axis. + * \param[in] lowerThresh : Canny lower threshold. + * \param[in] gaussianFilterSize : The size of the mask of the Gaussian filter to apply (an odd number). + * \param[in] gaussianStdev : The standard deviation of the Gaussian filter to apply. + * \param[in] apertureGradient : Size of the mask for the Sobel operator (odd number). + * \param[in] lowerThresholdRatio : The ratio of the upper threshold the lower threshold must be equal to. + * \param[in] upperThresholdRatio : The ratio of pixels whose absolute gradient Gabs is lower or equal to define + * 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. + * \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) + { + double w = I.getWidth(); + double h = I.getHeight(); + + vpImage dI(h, w); + vpImage dIx(h, w), dIy(h, w); + 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); + } + + // 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 = (float)dIx[r][c]; + float dy = (float)dIy[r][c]; + float gradient = std::abs(dx) + std::abs(dy); + float gradientClamped = std::min(gradient, (float)std::numeric_limits::max()); + dI[r][c] = gradientClamped; + } + } + + // Compute the histogram + vpHistogram hist; + const unsigned int nbBins = 256; + hist.calculate(dI, nbBins); + float accu = 0; + float t = (float)(upperThresholdRatio * w * h); + float bon = 0; + for (unsigned int i = 0; i < nbBins; i++) { + float tf = hist[i]; + accu = accu + tf; + if (accu > t) { + bon = (float)i; + break; + } + } + float upperThresh = std::max(bon, 1.f); + lowerThresh = lowerThresholdRatio * bon; + return upperThresh; + } + +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) static float median(const cv::Mat &cv_I); static float median(const vpImage &Isrc); static std::vector median(const vpImage &Isrc); diff --git a/modules/core/src/image/vpImageConvert.cpp b/modules/core/src/image/vpImageConvert.cpp index f1d6df0bf0..3fe7de60e8 100644 --- a/modules/core/src/image/vpImageConvert.cpp +++ b/modules/core/src/image/vpImageConvert.cpp @@ -497,6 +497,27 @@ void vpImageConvert::convert(const cv::Mat &src, vpImage &dest, bool flip } } +/*! + * Converts cv::Mat CV_32FC1 format to ViSP vpImage. + * + * \param[in] src : OpenCV image in CV_32FC1 format. + * \param[out] dest : ViSP image in double format. + * \param[in] flip : When true during conversion flip image vertically. + */ +void vpImageConvert::convert(const cv::Mat &src, vpImage &dest, bool flip) +{ + vpImage I_float; + convert(src, I_float, flip); + unsigned int nbRows = (unsigned int)src.rows; + unsigned int nbCols = (unsigned int)src.cols; + dest.resize(nbRows, nbCols); + for (unsigned int i = 0; i < nbRows; ++i) { + for (unsigned int j = 0; j < nbCols; ++j) { + dest[i][j] = I_float[i][j]; + } + } +} + /*! * Converts cv::Mat CV_16UC1 format to ViSP vpImage. * @@ -662,6 +683,19 @@ void vpImageConvert::convert(const vpImage &src, cv::Mat &dest, bool copy } } +void vpImageConvert::convert(const vpImage &src, cv::Mat &dest, bool copyData) +{ + unsigned int nbRows = src.getRows(); + unsigned int nbCols = src.getCols(); + vpImage I_float(nbRows, nbCols); + for (unsigned int i = 0; i < nbRows; ++i) { + for (unsigned int j = 0; j < nbCols; ++j) { + I_float[i][j] = src[i][j]; + } + } + convert(I_float, dest, copyData); +} + void vpImageConvert::convert(const vpImage &src, cv::Mat &dest) { cv::Mat vpToMat((int)src.getRows(), (int)src.getCols(), CV_32FC3, (void *)src.bitmap); diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index 2fcb5402e9..f2fc84a2da 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -34,9 +34,6 @@ *****************************************************************************/ #include -#include -#include -#include #include #include #include @@ -134,32 +131,32 @@ vpImageFilter::vpCannyFilteringAndGradientType vpImageFilter::vpCannyFilteringAn /** * \cond DO_NOT_DOCUMENT */ -/*template<>*/template +template void vpImageFilter::filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve); -/*template<>*/template +template void vpImageFilter::filter(const vpImage &I, vpImage &If, const vpArray2D &M, bool convolve); -template <> +template void vpImageFilter::filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve); -template <> +template void vpImageFilter::filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve); -/*template<>*/template +template void vpImageFilter::filter(const vpImage &I, vpImage &GI, const float *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::filter(const vpImage &I, vpImage &GI, const double *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::filter(const vpImage &I, vpImage &GI, const float *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::filter(const vpImage &I, vpImage &GI, const double *filter, unsigned int size); /** * \endcond @@ -251,18 +248,18 @@ void vpImageFilter::sepFilter(const vpImage &I, vpImage & /** * \cond DO_NOT_DOCUMENT */ -/*template<>*/template +template void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); /** * \endcond @@ -293,18 +290,18 @@ void vpImageFilter::filterX(const vpImage &I, vpImage &dIx, cons /** * \cond DO_NOT_DOCUMENT */ -/*template<>*/template +template void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); /** * \endcond @@ -339,16 +336,16 @@ void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, cons /** * \cond DO_NOT_DOCUMENT */ -/*template<>*/template +template void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, float sigma, bool normalize); -/*template<>*/template +template void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize); -/*template<>*/template +template void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, float sigma, bool normalize); -/*template<>*/template +template void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize); /** * \endcond @@ -379,80 +376,80 @@ void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, /** * \cond DO_NOT_DOCUMENT */ -/*template<>*/template +template void vpImageFilter::getGaussianKernel(float *filter, unsigned int size, float sigma, bool normalize); -template <> +template void vpImageFilter::getGaussianDerivativeKernel(float *filter, unsigned int size, float sigma, bool normalize); -template <> +template void vpImageFilter::getGaussianDerivativeKernel(double *filter, unsigned int size, double sigma, bool normalize); -/*template<>*/template +template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx); -/*template<>*/template +template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx); -/*template<>*/template +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy); -/*template<>*/template +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy); -/*template<>*/template +template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -/*template<>*/template +template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); /** @@ -512,16 +509,16 @@ void vpImageFilter::getGaussYPyramidal(const vpImage &I, vpImage< /** * \cond DO_NOT_DOCUMENT */ -/*template<>*/template +template double vpImageFilter::getSobelKernelX(double *filter, unsigned int size); -/*template<>*/template +template float vpImageFilter::getSobelKernelX(float *filter, unsigned int size); -/*template<>*/template +template double vpImageFilter::getSobelKernelY(double *filter, unsigned int size); -/*template<>*/template +template float vpImageFilter::getSobelKernelY(float *filter, unsigned int size); /** * \endcond @@ -613,9 +610,9 @@ std::vector vpImageFilter::median(const vpImage &Isrc) * \return The upper Canny edge filter threshold. */ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_dIx, const cv::Mat *p_cv_dIy, - float &lowerThresh, const unsigned int gaussianKernelSize, - const float gaussianStdev, const unsigned int apertureGradient, - const float lowerThresholdRatio, const float upperThresholdRatio, + float &lowerThresh, const unsigned int &gaussianKernelSize, + const float &gaussianStdev, const unsigned int &apertureGradient, + const float &lowerThresholdRatio, const float &upperThresholdRatio, const vpImageFilter::vpCannyFilteringAndGradientType &filteringType) { double w = cv_I.cols; @@ -624,24 +621,8 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p cv::Mat dI, dIx, dIy, dIx_abs, dIy_abs; if (p_cv_dIx == nullptr || p_cv_dIy == nullptr) { - cv::Mat img_blur; - // Apply Gaussian blur to the image - cv::Size gsz(gaussianKernelSize, gaussianKernelSize); - cv::GaussianBlur(cv_I, img_blur, gsz, gaussianStdev); - - // Compute the gradient of the blurred image - if (filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { - double scale = 1. / 8.; - if (apertureGradient > 3) { - scale *= std::pow(1./16., ((apertureGradient -1.)/2.) - 1.); - } - cv::Sobel(img_blur, dIx, CV_16S, 1, 0, apertureGradient, 1, 0, scale); - cv::Sobel(img_blur, dIy, CV_16S, 0, 1, apertureGradient, 1, 0, scale); - } - else if (filteringType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { - cv::Scharr(img_blur, dIx, CV_16S, 1, 0, 1.f/32.f); - cv::Scharr(img_blur, dIy, CV_16S, 0, 1, 1.f/32.f); - } + computePartialDerivatives(cv_I, dIx, dIy, true, true, true, gaussianKernelSize, gaussianStdev, apertureGradient, + filteringType); } else { dIx = *p_cv_dIx; @@ -679,111 +660,143 @@ float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p lowerThresh = lowerThresholdRatio * bon; return upperThresh; } -#endif /** - * \brief Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel or + Scharr operators to compute - * the gradient of the image. + * \brief Compute the partial derivatives (i.e. horizontal and vertical gradients) of the input image. * - * \param[in] I : The gray-scale image, in ViSP format. - * \param[in] p_dIx : If different from nullptr, must contain the gradient of the image with regard to the horizontal axis. - * \param[in] p_dIy : If different from nullptr, must contain the gradient of the image with regard to the vertical axis. - * \param[in] lowerThresh : Canny lower threshold. - * \param[in] gaussianFilterSize : The size of the mask of the Gaussian filter to apply (an odd number). - * \param[in] gaussianStdev : The standard deviation of the Gaussian filter to apply. - * \param[in] apertureGradient : Size of the mask for the Sobel operator (odd number). - * \param[in] lowerThresholdRatio : The ratio of the upper threshold the lower threshold must be equal to. - * \param[in] upperThresholdRatio : The ratio of pixels whose absolute gradient Gabs is lower or equal to define - * 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. - * \return The upper Canny edge filter threshold. + * \param[in] cv_I The input image we want the partial derivatives. + * \param[out] cv_dIx The horizontal partial derivative, i.e. horizontal gradient. + * \param[out] cv_dIy The vertical partial derivative, i.e. vertical gradient. + * \param[in] computeDx Idicate if we must compute the horizontal gradient. + * \param[in] computeDy Idicate if we must compute the vertical gradient. + * \param[in] normalize Idicate if we must normalize the gradient filters. + * \param[in] gaussianKernelSize The size of the kernel of the Gaussian filter used to blur the image. + * \param[in] gaussianStdev The standard deviation of the Gaussian filter used to blur the image. + * \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. */ -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) +void vpImageFilter::computePartialDerivatives(const cv::Mat &cv_I, + cv::Mat &cv_dIx, cv::Mat &cv_dIy, + const bool &computeDx, const bool &computeDy, const bool &normalize, + const unsigned int &gaussianKernelSize, const float &gaussianStdev, + const unsigned int &apertureGradient, + const vpImageFilter::vpCannyFilteringAndGradientType &filteringType) { - double w = I.getWidth(); - double h = I.getHeight(); - - vpImage dI(h, w); - vpImage dIx(h, w), dIy(h, w); - if (p_dIx != nullptr && p_dIy != nullptr) { - dIx = *p_dIx; - dIy = *p_dIy; - } - else { - // Computing the Gaussian blur + gradients of the image - vpImage Iblur; - vpImageFilter::gaussianBlur(I, Iblur, gaussianKernelSize, gaussianStdev); - - 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++) { - filter[r][c] = filter[r][c] * scale; - } - }}; - - // Scales to apply to the filters to get a normalized gradient filter that gives a gradient - // between 0 and 255 for an vpImage - float scaleX = 1.f; - float scaleY = 1.f; - - if (filteringType == CANNY_GBLUR_SOBEL_FILTERING) { - scaleX = vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Sobel kernel along X - scaleY = vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Sobel kernel along Y - } - else if (filteringType == CANNY_GBLUR_SCHARR_FILTERING) { - scaleX = vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Scharr kernel along X - scaleY = vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Scharr kernel along Y - } - - // Scale the gradient filters to have a normalized gradient filter - scaleFilter(gradientFilterX, scaleX); - scaleFilter(gradientFilterY, scaleY); - - // Apply the gradient filters to get the gradients - vpImageFilter::filter(Iblur, dIx, gradientFilterX); - vpImageFilter::filter(Iblur, dIy, gradientFilterY); - } + if (filteringType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING + || filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { + cv::Mat img_blur; + // Apply Gaussian blur to the image + cv::Size gsz(gaussianKernelSize, gaussianKernelSize); + cv::GaussianBlur(cv_I, img_blur, gsz, gaussianStdev); - // 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 = (float)dIx[r][c]; - float dy = (float)dIy[r][c]; - float gradient = std::abs(dx) + std::abs(dy); - float gradientClamped = std::min(gradient, (float)std::numeric_limits::max()); - dI[r][c] = gradientClamped; + // Compute the gradient of the blurred image + if (filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { + double scale = 1.; + if (normalize) { + scale = 1. / 8.; + if (apertureGradient > 3) { + scale *= std::pow(1./16., ((apertureGradient -1.)/2.) - 1.); + } + } + if (computeDx) { + cv::Sobel(img_blur, cv_dIx, CV_16S, 1, 0, apertureGradient, 1, 0, scale); + } + if (computeDy) { + cv::Sobel(img_blur, cv_dIy, CV_16S, 0, 1, apertureGradient, 1, 0, scale); + } } - } - - // Compute the histogram - vpHistogram hist; - const unsigned int nbBins = 256; - hist.calculate(dI, nbBins); - float accu = 0; - float t = (float)(upperThresholdRatio * w * h); - float bon = 0; - for (unsigned int i = 0; i < nbBins; i++) { - float tf = hist[i]; - accu = accu + tf; - if (accu > t) { - bon = (float)i; - break; + else if (filteringType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { + double scale = 1.; + if (normalize) { + scale = 1. / 32.; + } + if (computeDx) { + cv::Scharr(img_blur, cv_dIx, CV_16S, 1, 0, scale); + } + if (computeDy) { + cv::Scharr(img_blur, cv_dIy, CV_16S, 0, 1, scale); + } } } - float upperThresh = std::max(bon, 1.f); - lowerThresh = lowerThresholdRatio * bon; - return upperThresh; } +#endif + +/** + * \cond DO_NOT_DOCUMENT + */ +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); + +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); + +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); + +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); + +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); + +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); + +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); + +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); +/** + * \endcond +*/ /*! Apply the Canny edge operator on the image \e Isrc and return the resulting @@ -889,7 +902,7 @@ void vpImageFilter::canny(const vpImage &Isrc, vpImage Isrc; @@ -923,7 +937,8 @@ int main() // Apply the Canny edge operator and set the Icanny image. vpImageFilter::canny(Isrc, Icanny, gaussianFilterSize, lowerThresholdCanny, upperThresholdCanny, apertureSobel, - gaussianStdev, lowerThresholdRatio, upperThresholdRatio, cannyBackend, cannyFilteringSteps); + gaussianStdev, lowerThresholdRatio, upperThresholdRatio, normalizeGradient, + cannyBackend, filteringType); return (0); } \endcode @@ -938,48 +953,33 @@ int main() \param[in] upperThreshold : The upper threshold for the Canny operator. Only value greater than this value are marked as an edge. If negative, it will be automatically computed, along with the lower threshold. Otherwise, the lower threshold will be set to one third - of the thresholdCanny . + of the upper threshold . \param[in] apertureGradient : Size of the mask for the gardient (Sobel or Scharr) operator (odd number). \param[in] gaussianStdev : The standard deviation of the Gaussian filter to apply. \param[in] lowerThresholdRatio : The ratio of the upper threshold the lower threshold must be equal to. It is used only if the user asks to compute the Canny thresholds. \param[in] upperThresholdRatio : The ratio of pixels whose absolute gradient Gabs is lower or equal to define the upper threshold. It is used only if the user asks to compute the Canny thresholds. + \param[in] normalizeGradients : Needs to be true if asking to compute the \b upperThreshold , otherwise it depends on + the user application and user-defined thresholds. \param[in] cannyBackend : The backend to use to perform the Canny edge filtering. \param[in] cannyFilteringSteps : The filtering + gradient operators to apply to compute the gradient in the early stage of the Canny algoritgm. - */ void vpImageFilter::canny(const 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 vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps) + const bool &normalizeGradients, + const vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps +) { if (cannyBackend == CANNY_OPENCV_BACKEND) { #if defined(HAVE_OPENCV_IMGPROC) cv::Mat img_cvmat, cv_dx, cv_dy, edges_cvmat; vpImageConvert::convert(Isrc, img_cvmat); - if (cannyFilteringSteps == CANNY_GBLUR_SOBEL_FILTERING) { - cv::Mat cv_I_blur; - cv::GaussianBlur(img_cvmat, cv_I_blur, cv::Size((int)gaussianFilterSize, (int)gaussianFilterSize), gaussianStdev, 0); - double scale = 1. / 8.; - if (apertureGradient > 3) { - scale *= std::pow(1./16., ((apertureGradient -1.)/2.) - 1.); - } - cv::Sobel(cv_I_blur, cv_dx, CV_16S, 1, 0, apertureGradient, scale); - cv::Sobel(cv_I_blur, cv_dy, CV_16S, 0, 1, apertureGradient, scale); - } - else if (cannyFilteringSteps == CANNY_GBLUR_SCHARR_FILTERING) { - cv::Mat cv_I_blur; - cv::GaussianBlur(img_cvmat, cv_I_blur, cv::Size((int)gaussianFilterSize, (int)gaussianFilterSize), gaussianStdev, 0); - cv::Scharr(cv_I_blur, cv_dx, CV_16S, 1, 0, 1.f/32.f); - cv::Scharr(cv_I_blur, cv_dy, CV_16S, 0, 1, 1.f/32.f); - } - else { - std::string errMsg("[vpImageFilter::canny]Other types of Canny filtering steps have not been implemented"); - throw(vpException(vpException::functionNotImplementedError, errMsg)); - } + computePartialDerivatives(img_cvmat, cv_dx, cv_dy, true, true, normalizeGradients, gaussianFilterSize, + gaussianStdev, apertureGradient, cannyFilteringSteps); float upperCannyThresh = upperThreshold; float lowerCannyThresh = lowerThreshold; if (upperCannyThresh < 0) { @@ -1002,54 +1002,8 @@ void vpImageFilter::canny(const vpImage &Isrc, vpImage dIx, dIy; - if (cannyFilteringSteps == CANNY_GBLUR_SOBEL_FILTERING - || cannyFilteringSteps == CANNY_GBLUR_SCHARR_FILTERING) { - // Computing the Gaussian blur - vpImage Iblur; - vpArray2D fg(1, (gaussianFilterSize + 1)/2); - vpImageFilter::getGaussianKernel(fg.data, gaussianFilterSize, gaussianStdev, true); - vpImage GIx; - vpImageFilter::filterX(Isrc, GIx, fg.data, gaussianFilterSize); - vpImageFilter::filterY(GIx, Iblur, fg.data, gaussianFilterSize); - - // Compute the gradient filters - 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++) { - filter[r][c] = filter[r][c] * scale; - } - }}; - - // Scales to apply to the filters to get a normalized gradient filter that gives a gradient - // between 0 and 255 for an vpImage - float scaleX = 1.f; - float scaleY = 1.f; - - if (cannyFilteringSteps == CANNY_GBLUR_SOBEL_FILTERING) { - scaleX = vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureGradient - 1)/2); // Sobel kernel along X - scaleY = vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureGradient - 1)/2); // Sobel kernel along Y - } - else if (cannyFilteringSteps == CANNY_GBLUR_SCHARR_FILTERING) { - scaleX = vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureGradient - 1)/2); - scaleY = vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureGradient - 1)/2); - } - - // Scale the gradient filters to have a normalized gradient filter - scaleFilter(gradientFilterX, scaleX); - scaleFilter(gradientFilterY, scaleY); - - // Apply the gradient filters to get the gradients - vpImageFilter::filter(Iblur, dIx, gradientFilterX); - vpImageFilter::filter(Iblur, dIy, gradientFilterY); - } - else { - std::string errMsg("[vpImageFilter::canny]Other types of Canny filtering steps have not been implemented"); - throw(vpException(vpException::functionNotImplementedError, errMsg)); - } + computePartialDerivatives(Isrc, dIx, dIy, true, true, normalizeGradients, gaussianFilterSize, + gaussianStdev, apertureGradient, cannyFilteringSteps, cannyBackend); if (upperCannyThresh < 0) { upperCannyThresh = computeCannyThreshold(Isrc, lowerCannyThresh, &dIx, &dIy, gaussianFilterSize, gaussianStdev, diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index 17e3a720c2..ea971438eb 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -288,7 +288,7 @@ vpCircleHoughTransform::edgeDetection(const vpImage &I) // 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, + m_algoParams.m_lowerCannyThreshRatio, m_algoParams.m_upperCannyThreshRatio, true, m_algoParams.m_cannyBackendType, m_algoParams.m_filteringAndGradientType); } diff --git a/tutorial/image/tutorial-canny.cpp b/tutorial/image/tutorial-canny.cpp index 10aabe4f85..6e133f5da9 100644 --- a/tutorial/image/tutorial-canny.cpp +++ b/tutorial/image/tutorial-canny.cpp @@ -69,59 +69,10 @@ void computeMeanMaxStdev(const vpImage &I, float &mean, float &max, float &st void setGradientOutsideClass(const vpImage &I, const int &gaussianKernelSize, const float &gaussianStdev, vpCannyEdgeDetection &cannyDetector, const unsigned int apertureSize, const vpImageFilter::vpCannyFilteringAndGradientType &filteringType) { - // Get the Gaussian blur kernel - if ((gaussianKernelSize % 2) == 0) { - throw(vpException(vpException::badValue, "The Gaussian kernel size should be odd")); - } - vpArray2D fg(1, (gaussianKernelSize + 1)/2); - vpImageFilter::getGaussianKernel(fg.data, gaussianKernelSize, gaussianStdev, true); - - // Get the gradient filters kernel - if ((apertureSize % 2) != 1) { - throw vpException(vpException::badValue, "Gradient filters kernel size should be odd."); - } - vpArray2D gradientFilterX(apertureSize, apertureSize); - vpArray2D gradientFilterY(apertureSize, apertureSize); - - 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; - - if (filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { - scaleX = vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureSize - 1)/2); - scaleY = vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureSize - 1)/2); - } - else if (filteringType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { - // Compute the Scharr filters - scaleX = vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureSize - 1)/2); - scaleY = vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureSize - 1)/2); - } - else { - std::string errMsg = "Error: gradient filtering method \""; - errMsg += vpImageFilter::vpCannyFilteringAndGradientTypeToString(filteringType); - errMsg += "\" has not been implemented yet\n"; - throw vpException(vpException::notImplementedError, errMsg); - } - - scaleFilter(gradientFilterX, scaleX); - scaleFilter(gradientFilterY, scaleY); - - // Perform Gaussian blur - vpImage Iblur; - vpImage GIx; - vpImageFilter::filterX(I, GIx, fg.data, gaussianKernelSize); - vpImageFilter::filterY(GIx, Iblur, fg.data, gaussianKernelSize); - // Computing the gradients vpImage dIx, dIy; - vpImageFilter::filter(Iblur, dIx, gradientFilterX); - vpImageFilter::filter(Iblur, dIy, gradientFilterY); + vpImageFilter::computePartialDerivatives(I, dIx, dIy, true, true, true, gaussianKernelSize, gaussianStdev, + apertureSize, filteringType); // Set the gradients of the vpCannyEdgeDetection cannyDetector.setGradients(dIx, dIy); @@ -287,7 +238,7 @@ int main(int argc, const char *argv[]) float cannyThresh = opt_upperThresh; float lowerThresh(opt_lowerThresh); vpImageFilter::canny(I_canny_input, I_canny, opt_gaussianKernelSize, lowerThresh, cannyThresh, - opt_apertureSize, opt_gaussianStdev, opt_lowerThreshRatio, opt_upperThreshRatio, + opt_apertureSize, opt_gaussianStdev, opt_lowerThreshRatio, opt_upperThreshRatio, true, opt_backend, opt_filteringType); drawingHelpers::display(I_canny, "Canny results with \"" + vpImageFilter::vpCannyBackendTypeToString(opt_backend) + "\" backend", true); } From adedf6015294a17059c31953326f093257cbf230 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Thu, 19 Oct 2023 11:22:41 +0200 Subject: [PATCH 11/14] [CORPS] Added a method to know the list of available backend and filtering types for Canny --- .../core/include/visp3/core/vpImageFilter.h | 6 +++ modules/core/src/image/vpImageFilter.cpp | 46 +++++++++++++++++++ tutorial/image/tutorial-canny.cpp | 2 + 3 files changed, 54 insertions(+) diff --git a/modules/core/include/visp3/core/vpImageFilter.h b/modules/core/include/visp3/core/vpImageFilter.h index 2af1b1513c..a977d73509 100644 --- a/modules/core/include/visp3/core/vpImageFilter.h +++ b/modules/core/include/visp3/core/vpImageFilter.h @@ -78,6 +78,9 @@ class VISP_EXPORT vpImageFilter CANNY_COUNT_BACKEND = 2 //! Number of supported backends } vpCannyBackendType; + static std::string vpCannyBackendTypeList(const std::string &pref = "<", const std::string &sep = " , ", + const std::string &suf = ">"); + static std::string vpCannyBackendTypeToString(const vpCannyBackendType &type); static vpCannyBackendType vpCannyBackendTypeFromString(const std::string &name); @@ -90,6 +93,9 @@ class VISP_EXPORT vpImageFilter CANNY_COUNT_FILTERING = 2 //! Number of supported backends } vpCannyFilteringAndGradientType; + static std::string vpCannyFilteringAndGradientTypeList(const std::string &pref = "<", const std::string &sep = " , ", + const std::string &suf = ">"); + static std::string vpCannyFilteringAndGradientTypeToString(const vpCannyFilteringAndGradientType &type); static vpCannyFilteringAndGradientType vpCannyFilteringAndGradientTypeFromString(const std::string &name); diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index f2fc84a2da..ad33a28fd3 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -38,6 +38,29 @@ #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. + */ +std::string vpImageFilter::vpCannyBackendTypeList(const std::string &pref, const std::string &sep, + const std::string &suf) +{ + std::string list(pref); + for (unsigned int i = 0; i < vpCannyBackendType::CANNY_COUNT_BACKEND - 1; i++) { + vpCannyBackendType type = (vpCannyBackendType)i; + list += vpCannyBackendTypeToString(type); + list += sep; + } + vpCannyBackendType type = (vpCannyBackendType)(vpCannyBackendType::CANNY_COUNT_BACKEND - 1); + list += vpCannyBackendTypeToString(type); + list += suf; + return list; +} + /** * \brief Cast a \b vpImageFilter::vpCannyBackendTypeToString into a string, to know its name. * @@ -83,6 +106,29 @@ vpImageFilter::vpCannyBackendType vpImageFilter::vpCannyBackendTypeFromString(co return type; } +/** + * \brief Get the list of available vpCannyFilteringAndGradientType. + * + * \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::vpCannyFilteringAndGradientTypeList(const std::string &pref, const std::string &sep, + const std::string &suf) +{ + std::string list(pref); + for (unsigned int i = 0; i < vpCannyFilteringAndGradientType::CANNY_COUNT_FILTERING - 1; i++) { + vpCannyFilteringAndGradientType type = (vpCannyFilteringAndGradientType)i; + list += vpCannyFilteringAndGradientTypeToString(type); + list += sep; + } + vpCannyFilteringAndGradientType type = (vpCannyFilteringAndGradientType)(CANNY_COUNT_FILTERING - 1); + list += vpCannyFilteringAndGradientTypeToString(type); + list += suf; + return list; +} + /** * \brief Cast a \b vpImageFilter::vpCannyFilteringAndGradientType into a string, to know its name. * diff --git a/tutorial/image/tutorial-canny.cpp b/tutorial/image/tutorial-canny.cpp index 6e133f5da9..e75e6c16cb 100644 --- a/tutorial/image/tutorial-canny.cpp +++ b/tutorial/image/tutorial-canny.cpp @@ -124,6 +124,7 @@ void usage(const std::string &softName) << std::endl; std::cout << "\t-f, --filter" << std::endl << "\t\tPermits to choose the type of filter to apply to compute the gradient." + << "\t\tAvailable values = " << vpImageFilter::vpCannyFilteringAndGradientTypeList() << std::endl << std::endl; std::cout << "\t-r, --ratio" << std::endl << "\t\tPermits to set the lower and upper thresholds ratio of the vpCanny class." @@ -132,6 +133,7 @@ void usage(const std::string &softName) << std::endl; std::cout << "\t-b, --backend" << std::endl << "\t\tPermits to use the vpImageFilter::canny method for comparison." + << "\t\tAvailable values = " << vpImageFilter::vpCannyBackendTypeList() << std::endl << std::endl; std::cout << "\t-h, --help" << std::endl << "\t\tPermits to display the different arguments this software handles." From 643bbb3374ca6c78c457dbcc0e94e6c52b0ee638 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 25 Oct 2023 11:33:54 +0200 Subject: [PATCH 12/14] [CORPS] Iterates both in the positive and negative direction of the gradient to find the peak --- .../core/src/image/vpCannyEdgeDetection.cpp | 99 ++++++++++++------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/modules/core/src/image/vpCannyEdgeDetection.cpp b/modules/core/src/image/vpCannyEdgeDetection.cpp index 2f3c3fec6e..d93b0a636a 100644 --- a/modules/core/src/image/vpCannyEdgeDetection.cpp +++ b/modules/core/src/image/vpCannyEdgeDetection.cpp @@ -332,6 +332,64 @@ getAbsoluteTheta(const vpImage &dIx, const vpImage &dIy, const int } return absoluteTheta; } + +/** + * \brief Search in the direction of the gradient for the highest value of the gradient. + * + * \param[in] dIx The gradient image along the x-axis. + * \param[in] dIy The gradient image along the y-axis. + * \param[in] row The row of the initial point that is considered. + * \param[in] col The column of the initial point that is considered. + * \param[in] thetaQuadrant The gradient orientation quadrant of the initial point. + * \param[in] dRowGrad The direction of the gradient for the vertical direction. + * \param[in] dColGrad The direction of the gradient for the horizontal direction. + * \param[out] pixelsSeen The list of pixels that are of same gradient orientation quadrant. + * \param[out] bestPixel The pixel having the highest absolute value of gradient. + * \param[out] bestGrad The highest absolute value of gradient. + */ +void +searchForBestGradientInGradientDirection(const vpImage &dIx, const vpImage &dIy, +const int &row, const int &col, const int &thetaQuadrant, const int &dRowGrad, const int &dColGrad, +std::vector > &pixelsSeen, std::pair &bestPixel, float &bestGrad) +{ + bool isGradientInTheSameDirection = true; + int rowCandidate = row + dRowGrad; + int colCandidate = col + dColGrad; + + while (isGradientInTheSameDirection) { + // Getting the gradients around the edge point + float gradPlus = getManhattanGradient(dIx, dIy, rowCandidate, colCandidate); + if (std::abs(gradPlus) < std::numeric_limits::epsilon()) { + // The gradient is almost null => ignoring the point + isGradientInTheSameDirection = false; + break; + } + int dRowGradPlusCandidate = 0, dRowGradMinusCandidate = 0; + int dColGradPlusCandidate = 0, dColGradMinusCandidate = 0; + float absThetaPlus = getAbsoluteTheta(dIx, dIy, rowCandidate, colCandidate); + int thetaQuadrantCandidate = getThetaQuadrant(absThetaPlus, dRowGradPlusCandidate, dRowGradMinusCandidate, dColGradPlusCandidate, dColGradMinusCandidate); + if (thetaQuadrantCandidate != thetaQuadrant) { + isGradientInTheSameDirection = false; + break; + } + + std::pair pixelCandidate(rowCandidate, colCandidate); + if (gradPlus > bestGrad) { + // The gradient is higher with the next pixel candidate + // Saving it + bestGrad = gradPlus; + pixelsSeen.push_back(bestPixel); + bestPixel = pixelCandidate; + } + else { + // Best pixel is still the best + pixelsSeen.push_back(pixelCandidate); + } + rowCandidate += dRowGrad; + colCandidate += dColGrad; + } +} + void vpCannyEdgeDetection::performEdgeThining() { @@ -358,45 +416,16 @@ vpCannyEdgeDetection::performEdgeThining() int dColGradPlus = 0, dColGradMinus = 0; int thetaQuadrant = getThetaQuadrant(absoluteTheta, dRowGradPlus, dRowGradMinus, dColGradPlus, dColGradMinus); - bool isGradientInTheSameDirection = true; std::vector > pixelsSeen; std::pair bestPixel(row, col); float bestGrad = grad; - int rowCandidate = row + dRowGradPlus; - int colCandidate = col + dColGradPlus; - - while (isGradientInTheSameDirection) { - // Getting the gradients around the edge point - float gradPlus = getManhattanGradient(dIx, dIy, rowCandidate, colCandidate); - if (std::abs(gradPlus) < std::numeric_limits::epsilon()) { - // The gradient is almost null => ignoring the point - isGradientInTheSameDirection = false; - break; - } - int dRowGradPlusCandidate = 0, dRowGradMinusCandidate = 0; - int dColGradPlusCandidate = 0, dColGradMinusCandidate = 0; - float absThetaPlus = getAbsoluteTheta(dIx, dIy, rowCandidate, colCandidate); - int thetaQuadrantCandidate = getThetaQuadrant(absThetaPlus, dRowGradPlusCandidate, dRowGradMinusCandidate, dColGradPlusCandidate, dColGradMinusCandidate); - if (thetaQuadrantCandidate != thetaQuadrant) { - isGradientInTheSameDirection = false; - break; - } - std::pair pixelCandidate(rowCandidate, colCandidate); - if (gradPlus > bestGrad) { - // The gradient is higher with the nex pixel candidate - // Saving it - bestGrad = gradPlus; - pixelsSeen.push_back(bestPixel); - bestPixel = pixelCandidate; - } - else { - // Best pixel is still the best - pixelsSeen.push_back(pixelCandidate); - } - rowCandidate += dRowGradPlus; - colCandidate += dColGradPlus; - } + // iterate over all the pixels having the same gradient orientation quadrant + searchForBestGradientInGradientDirection(dIx, dIy, row, col, thetaQuadrant, dRowGradPlus, dColGradPlus, + pixelsSeen, bestPixel, bestGrad); + + searchForBestGradientInGradientDirection(dIx, dIy, row, col, thetaQuadrant, dRowGradMinus, dColGradMinus, + pixelsSeen, bestPixel, bestGrad); // Keeping the edge point that has the highest gradient m_edgeCandidateAndGradient[bestPixel] = bestGrad; From 2512b21f47d25f11e0e923114bcfad1e30dbd166 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Wed, 25 Oct 2023 14:12:13 +0200 Subject: [PATCH 13/14] [CORPS] Avoids to look at points that have already been explored and rejected --- modules/core/src/image/vpCannyEdgeDetection.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/core/src/image/vpCannyEdgeDetection.cpp b/modules/core/src/image/vpCannyEdgeDetection.cpp index d93b0a636a..274ba7f6b6 100644 --- a/modules/core/src/image/vpCannyEdgeDetection.cpp +++ b/modules/core/src/image/vpCannyEdgeDetection.cpp @@ -464,8 +464,10 @@ vpCannyEdgeDetection::performEdgeTracking() if (it->second == STRONG_EDGE) { m_edgeMap[it->first.first][it->first.second] = 255; } - else if (recursiveSearchForStrongEdge(it->first)) { - m_edgeMap[it->first.first][it->first.second] = 255; + else if (it->second == WEAK_EDGE) { + if (recursiveSearchForStrongEdge(it->first)) { + m_edgeMap[it->first.first][it->first.second] = 255; + } } } } From ae3f1ad0d369a211b5a865e2329668c48b9b3c43 Mon Sep 17 00:00:00 2001 From: Fabien Spindler Date: Fri, 27 Oct 2023 15:07:50 +0200 Subject: [PATCH 14/14] Improve helper message in tutorial-canny.cpp --- tutorial/image/tutorial-canny.cpp | 67 ++++++++++++++++++------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/tutorial/image/tutorial-canny.cpp b/tutorial/image/tutorial-canny.cpp index e75e6c16cb..2e74f9ac36 100644 --- a/tutorial/image/tutorial-canny.cpp +++ b/tutorial/image/tutorial-canny.cpp @@ -89,7 +89,9 @@ void setGradientOutsideClass(const vpImage &I, const int &gaussia drawingHelpers::display(dIy, title, true); } -void usage(const std::string &softName) +void usage(const std::string &softName, int gaussianKernelSize, float gaussianStdev, float lowerThresh, float upperThresh, + int apertureSize, vpImageFilter::vpCannyFilteringAndGradientType filteringType, + float lowerThreshRatio, float upperThreshRatio, vpImageFilter::vpCannyBackendType backend) { std::cout << "NAME" << std::endl; std::cout << softName << ": software to test the vpCannyEdgeComputation class and vpImageFilter::canny method" << std::endl; @@ -100,43 +102,51 @@ void usage(const std::string &softName) << " [-t, --thresh ]" << " [-a, --aperture ]" << " [-f, --filter ]" - << " [-r, --ratio ]" + << " [-r, --ratio ]" << " [-b, --backend ]" - << " [-h, --help]" + << " [-h, --help]" << std::endl << std::endl; std::cout << "DESCRIPTION" << std::endl; - std::cout << "\t-i, --image" << std::endl - << "\t\tPermits to load an image on which will be tested the vpCanny class." + std::cout << "\t-i, --image " << std::endl + << "\t\tPermits to load an image on which will be tested the vpCanny class." << std::endl + << "\t\tWhen empty uses a simulated image." << std::endl << std::endl; - std::cout << "\t-g, --gradient" << std::endl - << "\t\tPermits to compute the gradients of the image outside the vpCanny class." - << "\t\tFirst parameter is the size of the Gaussian kernel used to compute the gradients." - << "\t\tSecond parameter is the standard deviation of the Gaussian kernel used to compute the gradients." + std::cout << "\t-g, --gradient " << std::endl + << "\t\tPermits to compute the gradients of the image outside the vpCanny class." << std::endl + << "\t\tFirst parameter is the size of the Gaussian kernel used to compute the gradients." << std::endl + << "\t\tSecond parameter is the standard deviation of the Gaussian kernel used to compute the gradients." << std::endl + << "\t\tDefault: " << gaussianKernelSize << " " << gaussianStdev << std::endl << std::endl; - std::cout << "\t-t, --thresh" << std::endl - << "\t\tPermits to set the lower and upper thresholds of the vpCanny class." - << "\t\tFirst parameter is the lower threshold." - << "\t\tSecond parameter is the upper threshold." + std::cout << "\t-t, --thresh " << std::endl + << "\t\tPermits to set the lower and upper thresholds of the vpCanny class." << std::endl + << "\t\tFirst parameter is the lower threshold." << std::endl + << "\t\tSecond parameter is the upper threshold." << std::endl + << "\t\tWhen set to -1 thresholds are computed automatically." << std::endl + << "\t\tDefault: " << lowerThresh << " " << upperThresh << std::endl << std::endl; - std::cout << "\t-a, --aperture" << std::endl - << "\t\tPermits to set the size of the gradient filter kernel." - << "\t\tParameter must be odd and positive." + std::cout << "\t-a, --aperture " << std::endl + << "\t\tPermits to set the size of the gradient filter kernel." << std::endl + << "\t\tParameter must be odd and positive." << std::endl + << "\t\tDefault: " << apertureSize << std::endl << std::endl; - std::cout << "\t-f, --filter" << std::endl - << "\t\tPermits to choose the type of filter to apply to compute the gradient." - << "\t\tAvailable values = " << vpImageFilter::vpCannyFilteringAndGradientTypeList() << std::endl + std::cout << "\t-f, --filter " << std::endl + << "\t\tPermits to choose the type of filter to apply to compute the gradient." << std::endl + << "\t\tAvailable values: " << vpImageFilter::vpCannyFilteringAndGradientTypeList("<", " | ", ">") << std::endl + << "\t\tDefault: " << vpImageFilter::vpCannyFilteringAndGradientTypeToString(filteringType) << std::endl << std::endl; - std::cout << "\t-r, --ratio" << std::endl - << "\t\tPermits to set the lower and upper thresholds ratio of the vpCanny class." - << "\t\tFirst parameter is the lower threshold ratio." - << "\t\tSecond parameter is the upper threshold ratio." + std::cout << "\t-r, --ratio " << std::endl + << "\t\tPermits to set the lower and upper thresholds ratio of the vpCanny class." << std::endl + << "\t\tFirst parameter is the lower threshold ratio." << std::endl + << "\t\tSecond parameter is the upper threshold ratio." << std::endl + << "\t\tDefault: " << lowerThreshRatio << " " << upperThreshRatio << std::endl << std::endl; - std::cout << "\t-b, --backend" << std::endl - << "\t\tPermits to use the vpImageFilter::canny method for comparison." - << "\t\tAvailable values = " << vpImageFilter::vpCannyBackendTypeList() << std::endl + std::cout << "\t-b, --backend " << std::endl + << "\t\tPermits to use the vpImageFilter::canny method for comparison." << std::endl + << "\t\tAvailable values: " << vpImageFilter::vpCannyBackendTypeList("<", " | ", ">") << std::endl + << "\t\tDefault: " << vpImageFilter::vpCannyBackendTypeToString(backend) << std::endl << std::endl; std::cout << "\t-h, --help" << std::endl - << "\t\tPermits to display the different arguments this software handles." + << "\t\tPermits to display the different arguments this software handles." << std::endl << std::endl; } @@ -190,7 +200,8 @@ int main(int argc, const char *argv[]) i++; } else if (argv_str == "-h" || argv_str == "--help") { - usage(std::string(argv[0])); + usage(std::string(argv[0]), opt_gaussianKernelSize, opt_gaussianStdev, opt_lowerThresh, opt_upperThresh, + opt_apertureSize, opt_filteringType, opt_lowerThreshRatio, opt_upperThreshRatio, opt_backend); return EXIT_SUCCESS; } else {