diff --git a/cmake/templates/vpConfig.h.in b/cmake/templates/vpConfig.h.in index 8190f0c319..26c9ef50d4 100644 --- a/cmake/templates/vpConfig.h.in +++ b/cmake/templates/vpConfig.h.in @@ -167,7 +167,7 @@ // OpenCV version in hexadecimal (for example 2.1.0 gives 0x020100). #ifdef VISP_HAVE_OPENCV # define VISP_HAVE_OPENCV_VERSION ${VISP_HAVE_OPENCV_VERSION} -# include +# include #endif // For compat with previous releases diff --git a/modules/core/include/visp3/core/vpCannyEdgeDetection.h b/modules/core/include/visp3/core/vpCannyEdgeDetection.h index 0ca547ddac..18f4920c5b 100644 --- a/modules/core/include/visp3/core/vpCannyEdgeDetection.h +++ b/modules/core/include/visp3/core/vpCannyEdgeDetection.h @@ -56,14 +56,20 @@ 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.*/ + 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_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.*/ @@ -71,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, @@ -82,10 +93,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 gradient filters (Sobel or Scharr) used to compute the input image gradients. + */ + void initGradientFilters(); //@} /** @name Different steps methods */ @@ -112,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. @@ -147,15 +162,28 @@ 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] 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] 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 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. + * \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 float &lowerThreshold = -1., const float &upperThreshold = -1.); + 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 vpImageFilter::vpCannyFilteringAndGradientType &filteringType = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING); // // Configuration from files #ifdef VISP_HAVE_NLOHMANN_JSON @@ -179,30 +207,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_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); } /** * \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}, - {"upperThreshold", detector.m_upperThreshold} }; + {"lowerThresholdRatio", detector.m_lowerThresholdRatio}, + {"gradientFilterKernelSize", detector.m_gradientFilterKernelSize}, + {"upperThreshold", detector.m_upperThreshold}, + {"upperThresholdRatio", detector.m_upperThresholdRatio} + }; } #endif //@} @@ -240,6 +280,17 @@ 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; + initGradientFilters(); + } + /** * \brief Set the Gradients of the image that will be processed. * @@ -271,7 +322,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. @@ -284,6 +353,17 @@ class VISP_EXPORT vpCannyEdgeDetection m_gaussianStdev = stdev; initGaussianFilters(); } + + /** + * \brief Set the parameters of the gradient filter (Sobel or Scharr) kernel size filters. + * + * \param[in] apertureSize The size of the gradient filters kernel. Must be an odd value. + */ + inline void setGradientFilterAperture(const unsigned int &apertureSize) + { + m_gradientFilterKernelSize = apertureSize; + initGradientFilters(); + } //@} }; #endif 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/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 054fcba05b..a977d73509 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 @@ -67,11 +70,48 @@ 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 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); + + //! 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_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 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); - static void canny(const vpImage &I, vpImage &Ic, unsigned int gaussianFilterSize, - float lowerThresholdCanny, float higherThresholdCanny, unsigned int apertureSobel); + 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 bool &normalizeGradients, + const vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps); /*! Apply a 1x3 derivative filter to an image pixel. @@ -80,7 +120,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 +133,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 +152,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 +179,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 +221,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 +261,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 +276,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 +324,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 +340,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 +358,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 +380,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 +429,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 +494,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 +559,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 +633,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 +695,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 +760,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 +772,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 +951,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,16 +1013,66 @@ 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); } + /*! + 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 to normalize the Scharr kernel. + */ + 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 to normalize the Scharr kernel. + */ + 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. \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) @@ -1169,7 +1093,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) @@ -1198,33 +1122,262 @@ 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; + } + +#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 &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 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..274ba7f6b6 100644 --- a/modules/core/src/image/vpCannyEdgeDetection.cpp +++ b/modules/core/src/image/vpCannyEdgeDetection.cpp @@ -37,24 +37,37 @@ // // 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_lowerThreshold(-1.) - , m_upperThreshold(-1.) + , m_gradientFilterKernelSize(3) + , m_lowerThreshold(-1.f) + , m_lowerThresholdRatio(0.6f) + , m_upperThreshold(-1.f) + , m_upperThresholdRatio(0.8f) { initGaussianFilters(); + initGradientFilters(); } vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev - , const float &lowerThreshold, const float &upperThreshold) - : m_gaussianKernelSize(gaussianKernelSize) + , const unsigned int &sobelAperture, const float &lowerThreshold, const float &upperThreshold + , const float &lowerThresholdRatio, const float &upperThresholdRatio + , const vpImageFilter::vpCannyFilteringAndGradientType &filteringType +) + : m_filteringAndGradientType(filteringType) + , m_gaussianKernelSize(gaussianKernelSize) , m_gaussianStdev(gaussianStdev) , m_areGradientAvailable(false) + , m_gradientFilterKernelSize(sobelAperture) , m_lowerThreshold(lowerThreshold) + , m_lowerThresholdRatio(lowerThresholdRatio) , m_upperThreshold(upperThreshold) + , m_upperThresholdRatio(upperThresholdRatio) { initGaussianFilters(); + initGradientFilters(); } #ifdef VISP_HAVE_NLOHMANN_JSON @@ -83,9 +96,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(); + initGradientFilters(); } #endif @@ -96,9 +110,46 @@ 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); - m_fgDg.resize(1, (m_gaussianKernelSize + 1)/2); - vpImageFilter::getGaussianDerivativeKernel(m_fgDg.data, m_gaussianKernelSize, m_gaussianStdev, false); + vpImageFilter::getGaussianKernel(m_fg.data, m_gaussianKernelSize, m_gaussianStdev, true); +} + +void +vpCannyEdgeDetection::initGradientFilters() +{ + 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); + + 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) { + scaleX = vpImageFilter::getSobelKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1)/2); + scaleY = vpImageFilter::getSobelKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1)/2); + } + else if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { + // Compute the Scharr filters + scaleX = vpImageFilter::getScharrKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1)/2); + scaleY = vpImageFilter::getScharrKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1)/2); + } + 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 @@ -139,9 +190,13 @@ 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, &m_dIx, &m_dIy, m_gaussianKernelSize, + 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. lowerThreshold = m_upperThreshold / 3.f; } @@ -156,18 +211,22 @@ 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 - ); + if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING + || m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_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_gradientFilterX); + vpImageFilter::filter(Iblur, m_dIy, m_gradientFilterY); + } + else { + std::string errmsg("Currently, the only filtering and gradient operators are Gaussian blur + Sobel"); + throw(vpException(vpException::notImplementedError, errmsg)); + } } /** @@ -273,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() { @@ -299,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; @@ -376,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; + } } } } 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 f7a0119d16..94d1111019 100644 --- a/modules/core/src/image/vpImageFilter.cpp +++ b/modules/core/src/image/vpImageFilter.cpp @@ -32,27 +32,176 @@ */ #include -#include -#include #include +#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. + * + * \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 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. + * + * \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_GBLUR_SCHARR_FILTERING: + name = "gaussianblur+scharr-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 */ -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<> -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 <> -void vpImageFilter::filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, +template +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, +template +void vpImageFilter::filter(const vpImage &I, vpImage &Iu, vpImage &Iv, const vpArray2D &M, bool convolve); + +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::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); /** * \endcond */ @@ -102,10 +251,10 @@ void vpImageFilter::filter(const vpImage &I, vpImage &Iu Using two separable kernels reduce the number of operations and can be faster for large kernels. - \param I : Image to filter - \param If : Filtered image. - \param kernelH : Separable kernel (performed first). - \param kernelV : Separable kernel (performed last). + \param[in] I : Image to filter + \param[out] If : Filtered image. + \param[in] kernelH : Separable kernel (performed first). + \param[in] kernelV : Separable kernel (performed last). \note Only pixels in the input image fully covered by the kernel are considered. */ void vpImageFilter::sepFilter(const vpImage &I, vpImage &If, const vpColVector &kernelH, @@ -140,266 +289,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.1f, lowerCannyThresh, upperCannyThresh); - Ires = edgeDetector.detect(Isrc); -#endif -} - /** * \cond DO_NOT_DOCUMENT */ -template<> -void vpImageFilter::filter(const vpImage &I, vpImage &GI, const float *filter, +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 +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<> -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::filterX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); /** * \endcond */ @@ -429,19 +334,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 +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 +void vpImageFilter::filterY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); + +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, unsigned int size); /** * \endcond */ @@ -475,47 +380,29 @@ 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 +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 +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 +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 +void vpImageFilter::gaussianBlur(const vpImage &I, vpImage &GI, unsigned int size, double sigma, bool normalize); /** * \endcond */ /*! 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. */ @@ -533,86 +420,80 @@ 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 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 void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx); -template<> +template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx); -template<> +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy); -template<> +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy); -template<> +template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); -template<> +template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -template<> +template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const float *filter, unsigned int size); -template<> +template void vpImageFilter::getGradX(const vpImage &I, vpImage &dIx, const double *filter, unsigned int size); -template<> +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); -template<> +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); -template<> +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const float *filter, unsigned int size); -template<> +template void vpImageFilter::getGradY(const vpImage &I, vpImage &dIy, const double *filter, unsigned int size); -template<> +template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -template<> +template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); -template<> +template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -template<> +template void vpImageFilter::getGradXGauss2D(const vpImage &I, vpImage &dIx, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); -template<> +template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -template<> +template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); -template<> +template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const float *gaussianKernel, const float *gaussianDerivativeKernel, unsigned int size); -template<> +template void vpImageFilter::getGradYGauss2D(const vpImage &I, vpImage &dIy, const double *gaussianKernel, const double *gaussianDerivativeKernel, unsigned int size); /** @@ -672,17 +553,513 @@ void vpImageFilter::getGaussYPyramidal(const vpImage &I, vpImage< /** * \cond DO_NOT_DOCUMENT */ -template<> +template double vpImageFilter::getSobelKernelX(double *filter, unsigned int size); -template<> +template float vpImageFilter::getSobelKernelX(float *filter, unsigned int size); -template<> +template double vpImageFilter::getSobelKernelY(double *filter, unsigned int size); -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, using Gaussian blur + Sobel or + Scharr operators to compute + * the gradient of the image. + * + * \param[in] cv_I : The image, in cv format. + * \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] 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 &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 dI, dIx, dIy, dIx_abs, dIy_abs; + + if (p_cv_dIx == nullptr || p_cv_dIy == nullptr) { + computePartialDerivatives(cv_I, dIx, dIy, true, true, true, gaussianKernelSize, gaussianStdev, apertureGradient, + filteringType); + } + else { + dIx = *p_cv_dIx; + dIy = *p_cv_dIy; + } + + // Compute the absolute gradient of the blurred image G = |dIx| + |dIy| + 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; + 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(&dI, 1, channels, cv::Mat(), hist, dims, histSize, ranges, uniform, accumulate); + float accu = 0; + float t = (float)(upperThresholdRatio * 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 = lowerThresholdRatio * bon; + return upperThresh; +} + +/** + * \brief Compute the partial derivatives (i.e. horizontal and vertical gradients) of the input image. + * + * \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. + */ +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) +{ + 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); + + // 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); + } + } + 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); + } + } + } +} +#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 + 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[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] 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[in] apertureSobel : Size of the mask for the Sobel operator (odd number). +*/ +void vpImageFilter::canny(const vpImage &Isrc, vpImage &Ires, + const unsigned int &gaussianFilterSize, const float &thresholdCanny, + const 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[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). +*/ +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 = 2.f; + const float upperThresholdRatio = 0.8f; + const float lowerThresholdRatio = 0.6f; +#if defined(HAVE_OPENCV_IMGPROC) + const vpCannyBackendType cannyBackend = CANNY_OPENCV_BACKEND; +#else + const vpCannyBackendType cannyBackend = CANNY_VISP_BACKEND; +#endif + const vpCannyFilteringAndGradientType cannyFilteringSteps = CANNY_GBLUR_SOBEL_FILTERING; + canny(Isrc, Ires, gaussianFilterSize, lowerThreshold, upperThreshold, apertureSobel, + gaussianStdev, lowerThresholdRatio, upperThresholdRatio, false, 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 bool normalizeGradient = true; + const vpCannyBackendType cannyBackend = CANNY_OPENCV_BACKEND; // or CANNY_VISP_BACKEND; + const vpCannyFilteringAndGradientType filteringType = CANNY_GBLUR_SOBEL_FILTERING; // or CANNY_GBLUR_SCHARR_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, normalizeGradient, + cannyBackend, filteringType); + 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 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 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); + computePartialDerivatives(img_cvmat, cv_dx, cv_dy, true, true, normalizeGradients, gaussianFilterSize, + gaussianStdev, apertureGradient, cannyFilteringSteps); + float upperCannyThresh = upperThreshold; + float lowerCannyThresh = lowerThreshold; + if (upperCannyThresh < 0) { + upperCannyThresh = computeCannyThreshold(img_cvmat, &cv_dx, &cv_dy, lowerCannyThresh, gaussianFilterSize, + gaussianStdev, apertureGradient, lowerThresholdRatio, upperThresholdRatio, + cannyFilteringSteps); + } + 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 (cannyBackend == CANNY_VISP_BACKEND) { + float upperCannyThresh = upperThreshold; + float lowerCannyThresh = lowerThreshold; + + vpImage dIx, dIy; + computePartialDerivatives(Isrc, dIx, dIy, true, true, normalizeGradients, gaussianFilterSize, + gaussianStdev, apertureGradient, cannyFilteringSteps, cannyBackend); + + if (upperCannyThresh < 0) { + upperCannyThresh = computeCannyThreshold(Isrc, lowerCannyThresh, &dIx, &dIy, gaussianFilterSize, gaussianStdev, + apertureGradient, lowerThresholdRatio, upperThresholdRatio, + cannyFilteringSteps); + } + else if (lowerCannyThresh < 0) { + lowerCannyThresh = upperCannyThresh / 3.; + } + vpCannyEdgeDetection edgeDetector(gaussianFilterSize, gaussianStdev, apertureGradient, lowerCannyThresh, upperCannyThresh, + lowerThresholdRatio, upperThresholdRatio, cannyFilteringSteps); + edgeDetector.setGradients(dIx, dIy); + Ires = edgeDetector.detect(Isrc); + } +} 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/core/test/image-with-dataset/perfColorConversion.cpp b/modules/core/test/image-with-dataset/perfColorConversion.cpp index f4333b6e87..b8b26970cc 100644 --- a/modules/core/test/image-with-dataset/perfColorConversion.cpp +++ b/modules/core/test/image-with-dataset/perfColorConversion.cpp @@ -45,6 +45,11 @@ #include #include +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGCODECS) && defined(HAVE_OPENCV_IMGPROC) +#include +#include +#endif + static std::string ipath = vpIoTools::getViSPImagesDataPath(); static std::string imagePathColor = vpIoTools::createFilePath(ipath, "Klimt/Klimt.ppm"); static std::string imagePathGray = vpIoTools::createFilePath(ipath, "Klimt/Klimt.pgm"); @@ -312,14 +317,14 @@ int main(int argc, char *argv[]) // Build a new parser on top of Catch's using namespace Catch::clara; auto cli = session.cli() // Get Catch's composite command line parser - | Opt(runBenchmark) // bind variable to a new option, with a hint string - ["--benchmark"] // the option names it will respond to - ("run benchmark?") // description string for the help output - | Opt(imagePathColor, "imagePathColor")["--imagePathColor"]("Path to color image") | - Opt(imagePathGray, "imagePathColor")["--imagePathGray"]("Path to gray image") | - Opt(nThreads, "nThreads")["--nThreads"]("Number of threads"); - - // Now pass the new composite back to Catch so it uses that + | Opt(runBenchmark) // bind variable to a new option, with a hint string + ["--benchmark"] // the option names it will respond to + ("run benchmark?") // description string for the help output + | Opt(imagePathColor, "imagePathColor")["--imagePathColor"]("Path to color image") | + Opt(imagePathGray, "imagePathColor")["--imagePathGray"]("Path to gray image") | + Opt(nThreads, "nThreads")["--nThreads"]("Number of threads"); + +// Now pass the new composite back to Catch so it uses that session.cli(cli); // Let Catch (using Clara) parse the command line @@ -329,14 +334,14 @@ int main(int argc, char *argv[]) vpImage I_color; vpImageIo::read(I_color, imagePathColor); std::cout << "imagePathColor:\n\t" << imagePathColor << "\n\t" << I_color.getWidth() << "x" << I_color.getHeight() - << std::endl; + << std::endl; vpImage I_gray; vpImageIo::read(I_gray, imagePathGray); std::cout << "imagePathGray:\n\t" << imagePathGray << "\n\t" << I_gray.getWidth() << "x" << I_gray.getHeight() - << std::endl; + << std::endl; std::cout << "nThreads: " << nThreads << " / available threads: " << std::thread::hardware_concurrency() - << std::endl; + << std::endl; int numFailed = session.run(); diff --git a/modules/core/test/image-with-dataset/perfGaussianFilter.cpp b/modules/core/test/image-with-dataset/perfGaussianFilter.cpp index 11059e3bda..570ca6340d 100644 --- a/modules/core/test/image-with-dataset/perfGaussianFilter.cpp +++ b/modules/core/test/image-with-dataset/perfGaussianFilter.cpp @@ -45,6 +45,11 @@ #include #include +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGCODECS) && defined(HAVE_OPENCV_IMGPROC) +#include +#include +#endif + static const std::string ipath = vpIoTools::getViSPImagesDataPath(); static std::string imagePath = vpIoTools::createFilePath(ipath, "faces/1280px-Solvay_conference_1927.png"); @@ -166,12 +171,12 @@ int main(int argc, char *argv[]) // Build a new parser on top of Catch's using namespace Catch::clara; auto cli = session.cli() // Get Catch's composite command line parser - | Opt(runBenchmark) // bind variable to a new option, with a hint string - ["--benchmark"] // the option names it will respond to - ("run benchmark?") // description string for the help output - ; + | Opt(runBenchmark) // bind variable to a new option, with a hint string + ["--benchmark"] // the option names it will respond to + ("run benchmark?") // description string for the help output + ; - // Now pass the new composite back to Catch so it uses that +// Now pass the new composite back to Catch so it uses that session.cli(cli); // Let Catch (using Clara) parse the command line diff --git a/modules/core/test/image-with-dataset/perfImageResize.cpp b/modules/core/test/image-with-dataset/perfImageResize.cpp index 28ccd8d048..06e60928bb 100644 --- a/modules/core/test/image-with-dataset/perfImageResize.cpp +++ b/modules/core/test/image-with-dataset/perfImageResize.cpp @@ -46,6 +46,11 @@ #include #include +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGCODECS) && defined(HAVE_OPENCV_IMGPROC) +#include +#include +#endif + static const std::string ipath = vpIoTools::getViSPImagesDataPath(); static std::string imagePathColor = vpIoTools::createFilePath(ipath, "Klimt/Klimt.ppm"); static std::string imagePathGray = vpIoTools::createFilePath(ipath, "Klimt/Klimt.pgm"); @@ -368,15 +373,15 @@ int main(int argc, char *argv[]) // Build a new parser on top of Catch's using namespace Catch::clara; auto cli = session.cli() // Get Catch's composite command line parser - | Opt(runBenchmark) // bind variable to a new option, with a hint string - ["--benchmark"] // the option names it will respond to - ("run benchmark?") // description string for the help output - | Opt(imagePathColor, "imagePathColor")["--imagePathColor"]("Path to color image") | - Opt(imagePathGray, "imagePathColor")["--imagePathGray"] | - Opt(g_resize_width, "g_resize_width")["--width"]("Resize width") | - Opt(g_resize_height, "g_resize_height")["--height"]("Resize height"); - - // Now pass the new composite back to Catch so it uses that + | Opt(runBenchmark) // bind variable to a new option, with a hint string + ["--benchmark"] // the option names it will respond to + ("run benchmark?") // description string for the help output + | Opt(imagePathColor, "imagePathColor")["--imagePathColor"]("Path to color image") | + Opt(imagePathGray, "imagePathColor")["--imagePathGray"] | + Opt(g_resize_width, "g_resize_width")["--width"]("Resize width") | + Opt(g_resize_height, "g_resize_height")["--height"]("Resize height"); + +// Now pass the new composite back to Catch so it uses that session.cli(cli); // Let Catch (using Clara) parse the command line @@ -386,12 +391,12 @@ int main(int argc, char *argv[]) vpImage I_color; vpImageIo::read(I_color, imagePathColor); std::cout << "imagePathColor:\n\t" << imagePathColor << "\n\t" << I_color.getWidth() << "x" << I_color.getHeight() - << std::endl; + << std::endl; vpImage I_gray; vpImageIo::read(I_gray, imagePathGray); std::cout << "imagePathGray:\n\t" << imagePathGray << "\n\t" << I_gray.getWidth() << "x" << I_gray.getHeight() - << std::endl; + << std::endl; std::cout << "Resize to: " << g_resize_width << "x" << g_resize_height << std::endl; int numFailed = session.run(); diff --git a/modules/core/test/image-with-dataset/testConversion.cpp b/modules/core/test/image-with-dataset/testConversion.cpp index a8fee2ac6b..a61f87d6f8 100644 --- a/modules/core/test/image-with-dataset/testConversion.cpp +++ b/modules/core/test/image-with-dataset/testConversion.cpp @@ -45,6 +45,11 @@ #include #include +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGCODECS) && defined(HAVE_OPENCV_IMGPROC) +#include +#include +#endif + /*! \example testConversion.cpp @@ -74,7 +79,7 @@ SYNOPSIS\n\ %s [-i ] [-o ] [-n ]\n\ [-h]\n\ ", - name); +name); fprintf(stdout, "\n\ OPTIONS: Default\n\ @@ -208,7 +213,8 @@ int main(int argc, const char **argv) try { // Create the dirname vpIoTools::makeDirectory(opath); - } catch (...) { + } + catch (...) { usage(argv[0], NULL, ipath, opt_opath, username, nbIterations); std::cerr << std::endl << "ERROR:" << std::endl; std::cerr << " Cannot create " << opath << std::endl; @@ -223,8 +229,8 @@ int main(int argc, const char **argv) if (ipath != env_ipath) { std::cout << std::endl << "WARNING: " << std::endl; std::cout << " Since -i " - << " is different from VISP_IMAGE_PATH=" << env_ipath << std::endl - << " we skip the environment variable." << std::endl; + << " is different from VISP_IMAGE_PATH=" << env_ipath << std::endl + << " we skip the environment variable." << std::endl; } } @@ -233,9 +239,9 @@ int main(int argc, const char **argv) usage(argv[0], NULL, ipath, opt_opath, username, nbIterations); std::cerr << std::endl << "ERROR:" << std::endl; std::cerr << " Use -i option or set VISP_INPUT_IMAGE_PATH " << std::endl - << " environment variable to specify the location of the " << std::endl - << " image path where test images are located." << std::endl - << std::endl; + << " environment variable to specify the location of the " << std::endl + << " image path where test images are located." << std::endl + << std::endl; return EXIT_FAILURE; } @@ -278,7 +284,7 @@ int main(int argc, const char **argv) // Convert a YUV pixel value to a RGB value vpImageConvert::YUVToRGB(y, u, v, r, g, b); std::cout << " y(" << (int)y << ") u(" << (int)u << ") v(" << (int)v << ") = r(" << (int)r << ") g(" << (int)g - << ") b(" << (int)b << ")" << std::endl; + << ") b(" << (int)b << ")" << std::endl; vpChrono chrono; @@ -475,8 +481,8 @@ int main(int argc, const char **argv) // HSV color //////////////////////////////////// std::cout << "** Convert a vpImage in RGB color space to a " - "vpImage in HSV color" - << std::endl; + "vpImage in HSV color" + << std::endl; unsigned int size = Ic.getSize(); unsigned int w = Ic.getWidth(), h = Ic.getHeight(); std::vector hue(size); @@ -512,11 +518,11 @@ int main(int argc, const char **argv) for (unsigned int j = 0; j < Ic.getWidth(); j++) { if (Ic[i][j].R != I_HSV2RGBa[i][j].R || Ic[i][j].G != I_HSV2RGBa[i][j].G || Ic[i][j].B != I_HSV2RGBa[i][j].B) { std::cerr << "Ic[i][j].R=" << static_cast(Ic[i][j].R) - << " ; I_HSV2RGBa[i][j].R=" << static_cast(I_HSV2RGBa[i][j].R) << std::endl; + << " ; I_HSV2RGBa[i][j].R=" << static_cast(I_HSV2RGBa[i][j].R) << std::endl; std::cerr << "Ic[i][j].G=" << static_cast(Ic[i][j].G) - << " ; I_HSV2RGBa[i][j].G=" << static_cast(I_HSV2RGBa[i][j].G) << std::endl; + << " ; I_HSV2RGBa[i][j].G=" << static_cast(I_HSV2RGBa[i][j].G) << std::endl; std::cerr << "Ic[i][j].B=" << static_cast(Ic[i][j].B) - << " ; I_HSV2RGBa[i][j].B=" << static_cast(I_HSV2RGBa[i][j].B) << std::endl; + << " ; I_HSV2RGBa[i][j].B=" << static_cast(I_HSV2RGBa[i][j].B) << std::endl; throw vpException(vpException::fatalError, "Problem with conversion between RGB <==> HSV"); } } @@ -586,9 +592,9 @@ int main(int argc, const char **argv) cv::Mat colorMat_crop = colorMat(rect_roi); cv::Mat colorMat_crop_continuous = colorMat(rect_roi).clone(); std::cout << " colorMat_crop: " << colorMat_crop.cols << "x" << colorMat_crop.rows << " is continuous? " - << colorMat_crop.isContinuous() << std::endl; + << colorMat_crop.isContinuous() << std::endl; std::cout << " colorMat_crop_continuous: " << colorMat_crop_continuous.cols << "x" << colorMat_crop_continuous.rows - << " is continuous? " << colorMat_crop_continuous.isContinuous() << std::endl; + << " is continuous? " << colorMat_crop_continuous.isContinuous() << std::endl; vpImage I_color_crop((unsigned int)(rect_roi.height - rect_roi.y), (unsigned int)(rect_roi.width - rect_roi.x)); @@ -636,7 +642,8 @@ int main(int argc, const char **argv) } return EXIT_SUCCESS; - } catch (const vpException &e) { + } + catch (const vpException &e) { std::cout << "Catch an exception: " << e.getMessage() << std::endl; return EXIT_FAILURE; } diff --git a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h index 9c503700c4..953fbc920b 100644 --- a/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h +++ b/modules/imgproc/include/visp3/imgproc/vpCircleHoughTransform.h @@ -72,12 +72,17 @@ 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 - int m_sobelKernelSize; /*!< Size of the Sobel kernels used to compute the gradients. Must be an odd number.*/ + int m_gradientFilterKernelSize; /*!< Size of the Sobel or Scharr kernels used to compute the gradients. Must be an odd number.*/ // // Edge detection attributes float m_lowerCannyThresh; /*!< The lower threshold for the Canny operator. Values lower than this value are rejected. @@ -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_gradientFilterKernelSize(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) @@ -133,7 +147,7 @@ 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] gradientFilterKernelSize Size of the Sobel or Scharr kernels used to compute the gradients. Must be an odd number. * \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. @@ -149,11 +163,19 @@ 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 , const float &gaussianStdev - , const int &sobelKernelSize + , const int &gradientFilterKernelSize , const float &lowerCannyThresh , const float &upperCannyThresh , const int &edgeMapFilterNbIter @@ -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_gradientFilterKernelSize(gradientFilterKernelSize) , 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 += "\tGradient filter kernel size = " + std::to_string(m_gradientFilterKernelSize) + "\n"; + txt += "\tCanny backend = " + vpImageFilter::vpCannyBackendTypeToString(m_cannyBackendType) + "\n"; txt += "\tCanny edge filter thresholds = [" + std::to_string(m_lowerCannyThresh) + " ; " + std::to_string(m_upperCannyThresh) + "]\n"; + txt += "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" + std::to_string(m_lowerCannyThreshRatio) + " ; " + std::to_string(m_upperCannyThreshRatio) + "]\n"; txt += "\tEdge map 8-neighbor connectivity filtering number of iterations = " + std::to_string(m_edgeMapFilteringNbIter) + "\n"; txt += "\tCenter horizontal position limits: min = " + std::to_string(m_centerXlimits.first) + "\tmax = " + std::to_string(m_centerXlimits.second) +"\n"; txt += "\tCenter vertical position limits: min = " + std::to_string(m_centerYlimits.first) + "\tmax = " + std::to_string(m_centerYlimits.second) +"\n"; @@ -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."); @@ -276,13 +313,18 @@ class VISP_EXPORT vpCircleHoughTransform throw vpException(vpException::badValue, "Standard deviation should be > 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); + 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}, + {"gradientFilterKernelSize", params.m_gradientFilterKernelSize}, + {"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. * @@ -468,7 +518,19 @@ class VISP_EXPORT vpCircleHoughTransform void init(const vpCircleHoughTransformParameters &algoParams); /** - * \brief Set the parameters of the Gaussian filter, that computes the + * \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); + initGradientFilters(); + } + + /** + * \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 +552,32 @@ class VISP_EXPORT vpCircleHoughTransform initGaussianFilters(); } + /** + * \brief Set the parameters of the gradient filter (Sobel or Scharr) kernel size filters. + * + * \param[in] apertureSize The size of the gradient filters kernel. Must be an odd value. + */ + inline void setGradientFilterAperture(const unsigned int &apertureSize) + { + m_algoParams.m_gradientFilterKernelSize = apertureSize; + + if ((m_algoParams.m_gradientFilterKernelSize % 2) != 1) { + throw vpException(vpException::badValue, "Gradient filter (Sobel or Scharr) Kernel size should be odd."); + } + + initGradientFilters(); + } + + /** + * \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. @@ -505,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. @@ -615,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 * @@ -737,6 +842,7 @@ class VISP_EXPORT vpCircleHoughTransform { return m_finalCircleVotes; } + //@} /*! * Create a string with all Hough transform parameters. @@ -750,11 +856,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 gradient filters used to compute the gradient images. + */ + void initGradientFilters(); + /** * \brief Perform Gaussian smoothing on the input image to reduce the noise * that would perturbate the edge detection. @@ -790,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); @@ -819,9 +929,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_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 c39f9aacb6..20e4f40667 100644 --- a/modules/imgproc/src/vpCircleHoughTransform.cpp +++ b/modules/imgproc/src/vpCircleHoughTransform.cpp @@ -39,12 +39,14 @@ vpCircleHoughTransform::vpCircleHoughTransform() : m_algoParams() { initGaussianFilters(); + initGradientFilters(); } vpCircleHoughTransform::vpCircleHoughTransform(const vpCircleHoughTransformParameters &algoParams) : m_algoParams(algoParams) { initGaussianFilters(); + initGradientFilters(); } void @@ -52,6 +54,7 @@ vpCircleHoughTransform::init(const vpCircleHoughTransformParameters &algoParams) { m_algoParams = algoParams; initGaussianFilters(); + initGradientFilters(); } vpCircleHoughTransform::~vpCircleHoughTransform() @@ -84,9 +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(); + initGradientFilters(); } void @@ -100,12 +104,50 @@ void 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); + vpImageFilter::getGaussianKernel(m_fg.data, m_algoParams.m_gaussianKernelSize, m_algoParams.m_gaussianStdev, true); m_cannyVisp.setGaussianFilterParameters(m_algoParams.m_gaussianKernelSize, m_algoParams.m_gaussianStdev); } +void +vpCircleHoughTransform::initGradientFilters() +{ + 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); + + 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) { + // 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 + 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 vpCircleHoughTransform::detect(const vpImage &I) { @@ -210,40 +252,45 @@ 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 - ); + 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_gradientFilterX); + vpImageFilter::filter(Iblur, m_dIy, m_gradientFilterY); + } + 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_gradientFilterKernelSize, m_algoParams.m_gaussianStdev, + m_algoParams.m_lowerCannyThreshRatio, m_algoParams.m_upperCannyThreshRatio, true, + 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/modules/imgproc/src/vpImgproc.cpp b/modules/imgproc/src/vpImgproc.cpp index 14cd8dee4f..d7d48eb2bb 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) diff --git a/modules/tracker/mbt/test/generic-with-dataset/testGenericTracker.cpp b/modules/tracker/mbt/test/generic-with-dataset/testGenericTracker.cpp index 9c0677ef5e..709128e746 100644 --- a/modules/tracker/mbt/test/generic-with-dataset/testGenericTracker.cpp +++ b/modules/tracker/mbt/test/generic-with-dataset/testGenericTracker.cpp @@ -497,9 +497,9 @@ bool run(const std::string &input_directory, bool opt_click_allowed, bool opt_di std::map > > mapOfModels; std::map mapOfW; mapOfW["Camera1"] = I.getWidth(); - mapOfW["Camera2"] = I.getHeight(); + mapOfW["Camera2"] = I_depth.getWidth(); std::map mapOfH; - mapOfH["Camera1"] = I_depth.getWidth(); + mapOfH["Camera1"] = I.getHeight(); mapOfH["Camera2"] = I_depth.getHeight(); std::map mapOfcMos; mapOfcMos["Camera1"] = cMo; diff --git a/modules/vision/test/keypoint-with-dataset/testKeyPoint-5.cpp b/modules/vision/test/keypoint-with-dataset/testKeyPoint-5.cpp index ebe751d8c2..2a8297ce4a 100644 --- a/modules/vision/test/keypoint-with-dataset/testKeyPoint-5.cpp +++ b/modules/vision/test/keypoint-with-dataset/testKeyPoint-5.cpp @@ -48,6 +48,10 @@ #include #include + + + + // List of allowed command line options #define GETOPTARGS "cdh" @@ -165,9 +169,9 @@ void run_test(const std::string &env_ipath, bool opt_click_allowed, bool opt_dis // features that are scale invariant to detect potential problem in ViSP. std::cout << "INFORMATION: " << std::endl; std::cout << "Here, we want to test feature detection on a pyramid of images even for features " - "that are scale invariant to detect potential problem in ViSP." - << std::endl - << std::endl; + "that are scale invariant to detect potential problem in ViSP." + << std::endl + << std::endl; vpKeyPoint keyPoints; // Will test the different types of keypoints detection to see if there is @@ -274,11 +278,11 @@ void run_test(const std::string &env_ipath, bool opt_click_allowed, bool opt_dis keyPoints.detect(I, kpts); std::cout << "Nb keypoints detected: " << kpts.size() << " for " - << mapOfDetectorNames[(vpKeyPoint::vpFeatureDetectorType)i] << " method." << std::endl; + << mapOfDetectorNames[(vpKeyPoint::vpFeatureDetectorType)i] << " method." << std::endl; if (kpts.empty()) { std::stringstream ss; ss << "No keypoints detected with " << mapOfDetectorNames[(vpKeyPoint::vpFeatureDetectorType)i] - << " method and image: " << filename << "." << std::endl; + << " method and image: " << filename << "." << std::endl; throw(vpException(vpException::fatalError, ss.str())); } @@ -325,8 +329,8 @@ int main(int argc, const char **argv) if (env_ipath.empty()) { std::cerr << "Please set the VISP_INPUT_IMAGE_PATH environment " - "variable value." - << std::endl; + "variable value." + << std::endl; return EXIT_FAILURE; } @@ -344,7 +348,8 @@ int main(int argc, const char **argv) run_test(env_ipath, opt_click_allowed, opt_display, Iinput, I); } - } catch (const vpException &e) { + } + catch (const vpException &e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } 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..2e74f9ac36 --- /dev/null +++ b/tutorial/image/tutorial-canny.cpp @@ -0,0 +1,260 @@ +/**************************************************************************** + * + * 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) +{ + // Computing the gradients + vpImage dIx, dIy; + vpImageFilter::computePartialDerivatives(I, dIx, dIy, true, true, true, gaussianKernelSize, gaussianStdev, + apertureSize, filteringType); + + // 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, 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; + 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::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 + << "\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." << 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." << 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." << 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." << 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." << 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." << 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." << std::endl + << 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]), opt_gaussianKernelSize, opt_gaussianStdev, opt_lowerThresh, opt_upperThresh, + opt_apertureSize, opt_filteringType, opt_lowerThreshRatio, opt_upperThreshRatio, opt_backend); + 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, true, + opt_backend, opt_filteringType); + drawingHelpers::display(I_canny, "Canny results with \"" + vpImageFilter::vpCannyBackendTypeToString(opt_backend) + "\" backend", true); + } + + return EXIT_SUCCESS; +} diff --git a/tutorial/image/tutorial-image-converter.cpp b/tutorial/image/tutorial-image-converter.cpp index 4bfcdc68e2..15c96d7037 100644 --- a/tutorial/image/tutorial-image-converter.cpp +++ b/tutorial/image/tutorial-image-converter.cpp @@ -2,6 +2,11 @@ #include #include +#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGCODECS) && defined(HAVE_OPENCV_IMGPROC) +#include +#include +#endif + int main() { #if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_HIGHGUI) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_IMGCODECS) @@ -23,7 +28,8 @@ int main() #ifdef VISP_HAVE_PNG vpImageIo::write(I, "monkey.png"); // Gray #endif - } catch (const vpException &e) { + } + catch (const vpException &e) { std::cout << "Catch an exception: " << e << std::endl; } #endif 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": [