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