From dde4641ec82729c651c16764b255010ef81f96e3 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Tue, 12 Sep 2023 13:59:15 +0200 Subject: [PATCH 1/2] [CORPS] Added a vpImageFilter::canny that takes both the lower and upper Canny thresholds as parameters + apply Gaussian blur and Sobel filters before calling cv::Canny --- .../core/include/visp3/core/vpImageFilter.h | 3 + modules/core/src/image/vpImageFilter.cpp | 71 +++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/modules/core/include/visp3/core/vpImageFilter.h b/modules/core/include/visp3/core/vpImageFilter.h index f1f4aff6bf..ec03c45e55 100644 --- a/modules/core/include/visp3/core/vpImageFilter.h +++ b/modules/core/include/visp3/core/vpImageFilter.h @@ -72,6 +72,9 @@ class VISP_EXPORT vpImageFilter static void canny(const vpImage &I, vpImage &Ic, unsigned int gaussianFilterSize, float thresholdCanny, unsigned int apertureSobel); + static void canny(const vpImage &I, vpImage &Ic, unsigned int gaussianFilterSize, + float lowerThresholdCanny, float higherThresholdCanny, unsigned int apertureSobel); + /*! Apply a 1x3 derivative filter to an image pixel. diff --git a/modules/core/src/image/vpImageFilter.cpp b/modules/core/src/image/vpImageFilter.cpp index 52538ace0e..63da6cc2e2 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -297,24 +297,83 @@ int main() */ 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, edges_cvmat; + 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); - float upperCannyThresh = thresholdCanny; - float lowerCannyThresh = thresholdCanny / 3.f; + 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); } - cv::Canny(cv_I_blur, edges_cvmat, lowerCannyThresh, upperCannyThresh, (int)apertureSobel); + 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; - if (thresholdCanny < 0) { + 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")); } - vpCannyEdgeDetection edgeDetector(gaussianFilterSize, 0.1, thresholdCanny * 0.5, thresholdCanny); + else if (lowerCannyThresh < 0) { + lowerCannyThresh = upperCannyThresh / 3.; + } + vpCannyEdgeDetection edgeDetector(gaussianFilterSize, 0.1, lowerCannyThresh, upperCannyThresh); Ires = edgeDetector.detect(Isrc); #endif } From f91582b4ebb8847965e32cb334a1e5fb4ecea5ca Mon Sep 17 00:00:00 2001 From: rlagneau Date: Tue, 12 Sep 2023 13:59:48 +0200 Subject: [PATCH 2/2] [CORPS] Allow the configuration of the lower Canny threshold --- .../visp3/imgproc/vpCircleHoughTransform.h | 40 ++++++++++++------- .../imgproc/src/vpCircleHoughTransform.cpp | 15 ++++--- .../hough-transform/config/detector_full.json | 3 +- .../hough-transform/config/detector_half.json | 3 +- .../hough-transform/config/detector_img.json | 3 +- .../config/detector_quarter.json | 3 +- .../hough-transform/tutorial-circle-hough.cpp | 27 ++++++++----- 7 files changed, 59 insertions(+), 35 deletions(-) diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index b81a7a20db..b18a82b276 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -81,8 +81,10 @@ class VISP_EXPORT vpCircleHoughTransform int m_sobelKernelSize; /*!< Size of the Sobel kernels used to compute the gradients. Must be an odd number.*/ // // Edge detection attributes - float m_cannyThresh; /*!< The threshold for the Canny operator. Only value greater than this value are marked as an edge. - A negative value makes the algorithm compute this threshold automatically.*/ + float m_lowerCannyThresh; /*!< The lower threshold for the Canny operator. Values lower than this value are rejected. + A negative value makes the algorithm compute the lower threshold automatically.*/ + 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*/ // // Center candidates computation attributes @@ -107,7 +109,8 @@ class VISP_EXPORT vpCircleHoughTransform : m_gaussianKernelSize(5) , m_gaussianStdev(1.f) , m_sobelKernelSize(3) - , m_cannyThresh(-1.f) + , m_lowerCannyThresh(-1.f) + , m_upperCannyThresh(-1.f) , m_edgeMapFilteringNbIter(1) , m_centerXlimits(std::pair(std::numeric_limits::min(), std::numeric_limits::max())) , m_centerYlimits(std::pair(std::numeric_limits::min(), std::numeric_limits::max())) @@ -129,8 +132,10 @@ class VISP_EXPORT vpCircleHoughTransform * \param[in] gaussianKernelSize Size of the Gaussian filter kernel used to smooth the input image. Must be an odd number. * \param[in] gaussianStdev Standard deviation of the Gaussian filter. * \param[in] sobelKernelSize Size of the Sobel kernels used to compute the gradients. Must be an odd number. - * \param[in] cannyThresh The threshold for the Canny operator. Only value greater than this value are marked as an edge. - A negative value makes the algorithm compute this threshold automatically. + * \param[in] lowerCannyThresh The lower threshold for the Canny operator. Values lower than this value are rejected. + A negative value makes the algorithm compute this threshold and the lower one automatically. + * \param[in] 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 this threshold and the lower one automatically. * \param[in] edgeMapFilterNbIter Number of 8-neighbor connectivity filtering iterations to apply to the edge map. * \param[in] centerXlimits Minimum and maximum position on the horizontal axis of the center of the circle we want to detect. * \param[in] centerYlimits Minimum and maximum position on the vertical axis of the center of the circle we want to detect. @@ -147,7 +152,8 @@ class VISP_EXPORT vpCircleHoughTransform const int &gaussianKernelSize , const float &gaussianStdev , const int &sobelKernelSize - , const float &cannyThresh + , const float &lowerCannyThresh + , const float &upperCannyThresh , const int &edgeMapFilterNbIter , const std::pair ¢erXlimits , const std::pair ¢erYlimits @@ -163,7 +169,8 @@ class VISP_EXPORT vpCircleHoughTransform : m_gaussianKernelSize(gaussianKernelSize) , m_gaussianStdev(gaussianStdev) , m_sobelKernelSize(sobelKernelSize) - , m_cannyThresh(cannyThresh) + , m_lowerCannyThresh(lowerCannyThresh) + , m_upperCannyThresh(upperCannyThresh) , m_edgeMapFilteringNbIter(edgeMapFilterNbIter) , m_centerXlimits(centerXlimits) , m_centerYlimits(centerYlimits) @@ -185,7 +192,7 @@ class VISP_EXPORT vpCircleHoughTransform 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 edge filter threshold = " + std::to_string(m_cannyThresh) + "\n"; + txt += "\tCanny edge filter thresholds = [" + std::to_string(m_lowerCannyThresh) + " ; " + std::to_string(m_upperCannyThresh) + "]\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"; @@ -271,7 +278,8 @@ class VISP_EXPORT vpCircleHoughTransform throw vpException(vpException::badValue, "Sobel Kernel size should be odd."); } - params.m_cannyThresh = j.value("cannyThresh", params.m_cannyThresh); + params.m_lowerCannyThresh = j.value("lowerCannyThresh", params.m_lowerCannyThresh); + params.m_upperCannyThresh = j.value("upperCannyThresh", params.m_upperCannyThresh); params.m_edgeMapFilteringNbIter = j.value("edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter); params.m_centerXlimits = j.value("centerXlimits", params.m_centerXlimits); @@ -320,7 +328,8 @@ class VISP_EXPORT vpCircleHoughTransform {"gaussianKernelSize", params.m_gaussianKernelSize}, {"gaussianStdev", params.m_gaussianStdev}, {"sobelKernelSize", params.m_sobelKernelSize}, - {"cannyThresh", params.m_cannyThresh}, + {"lowerCannyThresh", params.m_lowerCannyThresh}, + {"upperCannyThresh", params.m_upperCannyThresh}, {"edgeMapFilteringNbIter", params.m_edgeMapFilteringNbIter}, {"centerXlimits", params.m_centerXlimits}, {"centerYlimits", params.m_centerYlimits}, @@ -482,12 +491,15 @@ class VISP_EXPORT vpCircleHoughTransform * Set the threshold for the Canny operator. * Only value greater than this value are marked as an edge. * If negative, the threshold is automatically computed. - * \param[in] canny_threshold : Canny filter upper threshold. When set to -1 (default), compute + * \param[in] lowerCannyThreshold : Canny filter lower threshold. When set to -1 (default), compute + * automatically this threshold. + * \param[in] upperCannyThreshold : Canny filter upper threshold. When set to -1 (default), compute * automatically this threshold. */ - inline void setCannyThreshold(const float &canny_threshold) + inline void setCannyThreshold(const float &lowerCannyThreshold, const float &upperCannyThreshold) { - m_algoParams.m_cannyThresh = canny_threshold; + m_algoParams.m_lowerCannyThresh = lowerCannyThreshold; + m_algoParams.m_upperCannyThresh = upperCannyThreshold; } /*! @@ -680,7 +692,7 @@ class VISP_EXPORT vpCircleHoughTransform */ inline float getCannyThreshold() const { - return m_algoParams.m_cannyThresh; + return m_algoParams.m_upperCannyThresh; } /*! diff --git a/modules/imgproc/src/vpCircleHoughTransform.cpp b/modules/imgproc/src/vpCircleHoughTransform.cpp index c7291ca8d7..a457731d1b 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -220,16 +220,19 @@ void vpCircleHoughTransform::edgeDetection(const vpImage &I) { #if defined(HAVE_OPENCV_IMGPROC) - float cannyThresh = m_algoParams.m_cannyThresh; - float lowerThresh; + 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_cannyThresh < 0.) { - cannyThresh = vpImageFilter::computeCannyThreshold(I, lowerThresh); + if (m_algoParams.m_upperCannyThresh < 0.) { + upperCannyThresh = vpImageFilter::computeCannyThreshold(I, lowerCannyThresh); } - vpImageFilter::canny(I, m_edgeMap, m_algoParams.m_gaussianKernelSize, cannyThresh, m_algoParams.m_sobelKernelSize); + else if (m_algoParams.m_lowerCannyThresh < 0) { + lowerCannyThresh = upperCannyThresh / 3.; + } + vpImageFilter::canny(I, m_edgeMap, m_algoParams.m_gaussianKernelSize, lowerCannyThresh, upperCannyThresh, m_algoParams.m_sobelKernelSize); #else - m_cannyVisp.setCannyThresholds(-1, m_algoParams.m_cannyThresh); + 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 diff --git a/tutorial/imgproc/hough-transform/config/detector_full.json b/tutorial/imgproc/hough-transform/config/detector_full.json index dda67b8b24..7ca91550e7 100644 --- a/tutorial/imgproc/hough-transform/config/detector_full.json +++ b/tutorial/imgproc/hough-transform/config/detector_full.json @@ -1,5 +1,6 @@ { - "cannyThresh": 150.0, + "lowerCannyThresh": 50.0, + "upperCannyThresh": 150.0, "centerMinDistance": 15.0, "centerThresh": 100.0, "centerXlimits": [ diff --git a/tutorial/imgproc/hough-transform/config/detector_half.json b/tutorial/imgproc/hough-transform/config/detector_half.json index 4b5f9a4e9f..376798b6f3 100644 --- a/tutorial/imgproc/hough-transform/config/detector_half.json +++ b/tutorial/imgproc/hough-transform/config/detector_half.json @@ -1,5 +1,6 @@ { - "cannyThresh": 150.0, + "lowerCannyThresh": 50.0, + "upperCannyThresh": 150.0, "centerMinDistance": 15.0, "centerThresh": 50.0, "centerXlimits": [ diff --git a/tutorial/imgproc/hough-transform/config/detector_img.json b/tutorial/imgproc/hough-transform/config/detector_img.json index f9537a1d2c..f736c97e38 100644 --- a/tutorial/imgproc/hough-transform/config/detector_img.json +++ b/tutorial/imgproc/hough-transform/config/detector_img.json @@ -1,5 +1,6 @@ { - "cannyThresh": -1.0, + "lowerCannyThresh": -1.0, + "upperCannyThresh": -1.0, "centerMinDistance": 5.0, "centerThresh": 100.0, "centerXlimits": [ diff --git a/tutorial/imgproc/hough-transform/config/detector_quarter.json b/tutorial/imgproc/hough-transform/config/detector_quarter.json index a59fcc6263..06b14c3ea8 100644 --- a/tutorial/imgproc/hough-transform/config/detector_quarter.json +++ b/tutorial/imgproc/hough-transform/config/detector_quarter.json @@ -1,5 +1,6 @@ { - "cannyThresh": 150.0, + "lowerCannyThresh": 50.0, + "upperCannyThresh": 150.0, "centerMinDistance": 15.0, "centerThresh": 25.0, "centerXlimits": [ diff --git a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp index 94691b70f3..398051a3e0 100644 --- a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp +++ b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp @@ -199,9 +199,11 @@ int main(int argc, char **argv) const float def_gaussianSigma = 1.f; const int def_sobelKernelSize = 3; #ifdef HAVE_OPENCV_IMGPROC - const float def_cannyThresh = 150.f; + const float def_lowerCannyThresh = 50.f; + const float def_upperCannyThresh = 150.f; #else - const float def_cannyThresh = 25.f; + const float def_lowerCannyThresh = 8.f; + const float def_upperCannyThresh = 25.f; #endif const int def_nbEdgeFilteringIter = 2; const std::pair def_centerXlimits = std::pair(0, 640); @@ -221,7 +223,8 @@ int main(int argc, char **argv) int opt_gaussianKernelSize = def_gaussianKernelSize; float opt_gaussianSigma = def_gaussianSigma; int opt_sobelKernelSize = def_sobelKernelSize; - float opt_cannyThresh = def_cannyThresh; + float opt_lowerCannyThresh = def_lowerCannyThresh; + float opt_upperCannyThresh = def_upperCannyThresh; int opt_nbEdgeFilteringIter = def_nbEdgeFilteringIter; std::pair opt_centerXlimits = def_centerXlimits; std::pair opt_centerYlimits = def_centerYlimits; @@ -263,9 +266,10 @@ int main(int argc, char **argv) opt_sobelKernelSize = atoi(argv[i + 1]); i++; } - else if (argName == "--canny-thresh" && i + 1 < argc) { - opt_cannyThresh = static_cast(atof(argv[i + 1])); - i++; + else if (argName == "--canny-thresh" && i + 2 < argc) { + opt_lowerCannyThresh = static_cast(atof(argv[i + 1])); + opt_upperCannyThresh = static_cast(atof(argv[i + 2])); + i += 2; } else if (argName == "--edge-filter" && i + 1 < argc) { opt_nbEdgeFilteringIter = atoi(argv[i + 1]); @@ -322,7 +326,7 @@ int main(int argc, char **argv) << "\t [--gaussian-kernel ] (default: " << def_gaussianKernelSize << ")" << std::endl << "\t [--gaussian-sigma ] (default: " << def_gaussianSigma << ")" << std::endl << "\t [--sobel-kernel ] (default: " << def_sobelKernelSize << ")" << std::endl - << "\t [--canny-thresh ] (default: " << def_cannyThresh << ")" << std::endl + << "\t [--canny-thresh ] (default: " << def_lowerCannyThresh << " ; " << def_upperCannyThresh << ")" << std::endl << "\t [--edge-filter ] (default: " << def_nbEdgeFilteringIter << ")" << std::endl << "\t [--radius-limits ] (default: min = " << def_minRadius << ", max = " << def_maxRadius << ")" << std::endl << "\t [--dilatation-repet ] (default: " << def_dilatationRepet << ")" << std::endl @@ -363,9 +367,9 @@ int main(int argc, char **argv) << "\t\tDefault: " << def_gaussianSigma << std::endl << std::endl << "\t--canny-thresh" << std::endl - << "\t\tPermit to set the upper threshold of the Canny edge detector." << std::endl - << "\t\tMust be a positive value." << std::endl - << "\t\tDefault: " << def_cannyThresh << std::endl + << "\t\tPermit to set the lower and upper thresholds of the Canny edge detector." << std::endl + << "\t\tIf a value is negative, it will be automatically computed." << std::endl + << "\t\tDefault: " << def_upperCannyThresh << std::endl << std::endl << "\t--edge-filter" << std::endl << "\t\tPermit to set the number of iteration of 8-neighbor filter iterations of the result of the Canny edge detector." << std::endl @@ -478,7 +482,8 @@ int main(int argc, char **argv) algoParams(opt_gaussianKernelSize , opt_gaussianSigma , opt_sobelKernelSize - , opt_cannyThresh + , opt_lowerCannyThresh + , opt_upperCannyThresh , opt_nbEdgeFilteringIter , opt_centerXlimits , opt_centerYlimits