-
Notifications
You must be signed in to change notification settings - Fork 287
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CORPS] Added a ViSP implementation of the Canny edge detector, that …
…does not rely on OpenCV imgproc
- Loading branch information
rlagneau
committed
Aug 11, 2023
1 parent
6794698
commit c3eb4a1
Showing
6 changed files
with
784 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,287 @@ | ||
/**************************************************************************** | ||
* | ||
* 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 [email protected] | ||
* | ||
* 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 _vpCannyEdgeDetection_h_ | ||
#define _vpCannyEdgeDetection_h_ | ||
|
||
// System includes | ||
#include <map> | ||
#include <vector> | ||
|
||
// ViSP include | ||
#include <visp3/core/vpConfig.h> | ||
#include <visp3/core/vpImage.h> | ||
#include <visp3/core/vpImageFilter.h> | ||
|
||
// 3rd parties include | ||
#ifdef VISP_HAVE_NLOHMANN_JSON | ||
#include <nlohmann/json.hpp> | ||
using json = nlohmann::json; | ||
#endif | ||
|
||
class vpCannyEdgeDetection | ||
{ | ||
private: | ||
typedef enum EdgeType | ||
{ | ||
STRONG_EDGE, | ||
WEAK_EDGE, | ||
ON_CHECK | ||
} EdgeType; | ||
|
||
// // 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.*/ | ||
|
||
// // Gradient computation attributes | ||
bool m_areGradientAvailable; /*!< Set to true if the user gave the gradient images, false otherwise. In the later case, the class will compute the gradients.*/ | ||
vpArray2D<float> m_fg; /*!< Array that contains the Gaussian kernel.*/ | ||
vpArray2D<float> m_fgDg; /*!< Array that contains the derivative of the Gaussian kernel.*/ | ||
vpImage<float> m_dIx; /*!< X-axis gradient.*/ | ||
vpImage<float> m_dIy; /*!< Y-axis gradient.*/ | ||
|
||
// // Edge thining attributes | ||
std::map<std::pair<unsigned int, unsigned int>, float> m_edgeCandidateAndGradient; /*!< Map that contains point image coordinates and corresponding gradient value.*/ | ||
|
||
// // Histeresis thresholding attributes | ||
float m_lowerThreshold; /*!< Lower threshold for the histeresis step. If negative, it will be deduced as from m_upperThreshold. */ | ||
float m_upperThreshold; /*!< Upper threshold for the histeresis step.*/ | ||
|
||
// // Edge tracking attributes | ||
std::map<std::pair<unsigned int, unsigned int>, EdgeType> m_edgePointsCandidates; /*!< Map that contains the strong edge points, i.e. the points for which we know for sure they are edge points, | ||
and the weak edge points, i.e. the points for which we still must determine if they are actual edge points.*/ | ||
vpImage<unsigned char> m_edgeMap; /*!< Final edge map that results from the whole Canny algorithm.*/ | ||
|
||
/** @name Constructors and initialization */ | ||
//@{ | ||
/** | ||
* \brief Initialize the Gaussian filters used to filter the input image and | ||
* to compute its gradients. | ||
*/ | ||
void initGaussianFilters(); | ||
//@} | ||
|
||
/** @name Different steps methods */ | ||
/** | ||
* \brief Step 1: filtering + Step 2: gradient computation | ||
* \details First, perform Gaussian blur to the input image. | ||
* Then, compute the x-axis and y-axis gradients of the image. | ||
* \param[in] I The image we want to compute the gradients. | ||
*/ | ||
void performFilteringAndGradientComputation(const vpImage<unsigned char> &I); | ||
|
||
/** | ||
* \brief Step 3: edge thining | ||
* \details Perform the edge thining step. | ||
* Perform a non-maximum suppresion to keep only local maxima as edge candidates. | ||
*/ | ||
void performEdgeThining(); | ||
|
||
/** | ||
* \brief Perform hysteresis thresholding. | ||
* \details Edge candidates that are greater than \b m_upperThreshold are saved in \b m_strongEdgePoints | ||
* and will be kept in the final edge map. | ||
* Edge candidates that are bewteen \b m_lowerThreshold and \b m_upperThreshold are saved in | ||
* \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 | ||
* edges. | ||
* \param 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. | ||
* | ||
* \param[in] coordinates The coordinates we are checking. | ||
* \return true We found a strong edge point in its 8-connected neighborhood. | ||
* \return false We did not found a strong edge point in its 8-connected neighborhood. | ||
*/ | ||
bool recursiveSearchForStrongEdge(const std::pair<unsigned int, unsigned int> &coordinates); | ||
|
||
/** | ||
* \brief Perform edge tracking. | ||
* \details For each weak edge, we will recursively check if they are 8-connected to a strong edge point. | ||
* If so, the weak edge will be saved in \b m_strongEdgePoints and will be kept in the final edge map. | ||
* Otherwise, the edge point will be discarded. | ||
*/ | ||
void performEdgeTracking(); | ||
//@} | ||
|
||
public: | ||
/** @name Constructors and initialization */ | ||
//@{ | ||
/** | ||
* \brief Default constructor of the vpCannyEdgeDetection class. | ||
* The thresholds used during the hysteresis thresholding step are set to be automatically computed. | ||
*/ | ||
vpCannyEdgeDetection(); | ||
|
||
/** | ||
* \brief Construct a new vpCannyEdgeDetection object. | ||
* | ||
* \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. | ||
*/ | ||
vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev | ||
, const float &lowerThreshold = -1., const float &upperThreshold = -1.); | ||
|
||
// // Configuration from files | ||
#ifdef VISP_HAVE_NLOHMANN_JSON | ||
/** | ||
* \brief Construct a new vpCannyEdgeDetection object. | ||
* | ||
* \param[in] jsonPath The path towards the JSON file to use to initialize the vpCannyEdgeDetection object. | ||
*/ | ||
vpCannyEdgeDetection(const std::string &jsonPath); | ||
|
||
/** | ||
* \brief Initialize all the algorithm parameters using the JSON file | ||
* whose path is \b jsonPath. Throw a \b vpException error if the file | ||
* does not exist. | ||
* | ||
* \param[in] jsonPath The path towards the JSON configuration file. | ||
*/ | ||
void initFromJSON(const std::string &jsonPath); | ||
|
||
/** | ||
* \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. | ||
*/ | ||
inline friend void from_json(const json &j, vpCannyEdgeDetection &detector) | ||
{ | ||
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_upperThreshold = j.value("upperThreshold", detector.m_upperThreshold); | ||
} | ||
|
||
/** | ||
* \brief Parse a vpCircleHoughTransform into JSON format. | ||
* | ||
* \param j A JSON parser object. | ||
* \param config The vpCircleHoughTransform that must be parsed into JSON format. | ||
*/ | ||
inline friend void to_json(json &j, const vpCannyEdgeDetection &detector) | ||
{ | ||
j = json { | ||
{"gaussianSize", detector.m_gaussianKernelSize}, | ||
{"gaussianStdev", detector.m_gaussianStdev}, | ||
{"lowerThreshold", detector.m_lowerThreshold}, | ||
{"upperThreshold", detector.m_upperThreshold} }; | ||
} | ||
#endif | ||
//@} | ||
|
||
/** @name Detection methods */ | ||
//@{ | ||
/** | ||
* \brief Detect the edges in an image. | ||
* Convert the color image into a ViSP gray-scale image. | ||
* | ||
* \param[in] cv_I A color image, in OpenCV format. | ||
* \return vpImage<unsigned char> 255 means an edge, 0 means not an adge. | ||
*/ | ||
vpImage<unsigned char> detect(const cv::Mat &cv_I); | ||
|
||
/** | ||
* \brief Detect the edges in an image. | ||
* Convert the color image into a gray-scale image. | ||
* | ||
* \param[in] I_color An RGB image, in ViSP format. | ||
* \return vpImage<unsigned char> 255 means an edge, 0 means not an adge. | ||
*/ | ||
vpImage<unsigned char> detect(const vpImage<vpRGBa> &I_color); | ||
|
||
/** | ||
* \brief Detect the edges in a gray-scale image. | ||
* | ||
* \param[in] I A gray-scale image, in ViSP format. | ||
* \return vpImage<unsigned char> 255 means an edge, 0 means not an adge. | ||
*/ | ||
vpImage<unsigned char> detect(const vpImage<unsigned char> &I); | ||
//@} | ||
|
||
/** @name Setters */ | ||
//@{ | ||
/** | ||
* \brief Set the Gradients of the image that will be processed. | ||
* | ||
* \param[in] dIx Gradient along the horizontal axis of the image. | ||
* \param[in] dIy Gradient along the vertical axis of the image. | ||
*/ | ||
inline void setGradients(const vpImage<float> &dIx, const vpImage<float> &dIy) | ||
{ | ||
m_dIx = dIx; | ||
m_dIy = dIy; | ||
m_areGradientAvailable = true; | ||
} | ||
|
||
/** | ||
* \brief Set the lower and upper Canny Thresholds used to qualify the edge point candidates. | ||
* Edge point candidates whose gradient is between these two values is kept only if it | ||
* linked somehow to a strong edge point. | ||
* | ||
* \param[in] lowerThresh The lower threshold: each point whose gradient is below this threshold is discarded. | ||
* \param[in] upperThresh The upper threshold: each point whose gradient is greater than this threshold is | ||
* said to be a strong edge point and is kept. | ||
*/ | ||
inline void setCannyThresholds(const float &lowerThresh, const float &upperThresh) | ||
{ | ||
m_lowerThreshold = lowerThresh; | ||
m_upperThreshold = upperThresh; | ||
} | ||
|
||
/** | ||
* @brief Set the Gaussian Filters kernel size and standard deviation | ||
* and initialize the aforementioned filters. | ||
* | ||
* \param[in] kernelSize The size of the Gaussian filters kernel. | ||
* \param[in] stdev The standard deviation of the Gaussian filters used to blur and | ||
* compute the gradient of the image. | ||
*/ | ||
inline void setGaussianFilterParameters(const int &kernelSize, const float &stdev) | ||
{ | ||
m_gaussianKernelSize = kernelSize; | ||
m_gaussianStdev = stdev; | ||
initGaussianFilters(); | ||
} | ||
//@} | ||
}; | ||
#endif |
Oops, something went wrong.