33 #include <visp3/core/vpCannyEdgeDetection.h>
35 #include <visp3/core/vpImageConvert.h>
37 #if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98)
41 template <
typename FilterType>
44 const unsigned int nbRows = filter.
getRows();
45 const unsigned int nbCols = filter.
getCols();
46 for (
unsigned int r = 0; r < nbRows; ++r) {
47 for (
unsigned int c = 0; c < nbCols; ++c) {
48 filter[r][c] = filter[r][c] * scale;
58 : m_filteringAndGradientType(
vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
59 , m_gaussianKernelSize(3)
60 , m_gaussianStdev(1.f)
61 , m_areGradientAvailable(false)
62 , m_gradientFilterKernelSize(3)
63 , m_lowerThreshold(-1.f)
64 , m_lowerThresholdRatio(0.6f)
65 , m_upperThreshold(-1.f)
66 , m_upperThresholdRatio(0.8f)
69 initGaussianFilters();
70 initGradientFilters();
74 ,
const unsigned int &sobelAperture,
const float &lowerThreshold,
const float &upperThreshold
75 ,
const float &lowerThresholdRatio,
const float &upperThresholdRatio
78 : m_filteringAndGradientType(filteringType)
79 , m_gaussianKernelSize(gaussianKernelSize)
80 , m_gaussianStdev(gaussianStdev)
81 , m_areGradientAvailable(false)
82 , m_gradientFilterKernelSize(sobelAperture)
83 , m_lowerThreshold(lowerThreshold)
84 , m_lowerThresholdRatio(lowerThresholdRatio)
85 , m_upperThreshold(upperThreshold)
86 , m_upperThresholdRatio(upperThresholdRatio)
89 initGaussianFilters();
90 initGradientFilters();
93 #ifdef VISP_HAVE_NLOHMANN_JSON
95 using json = nlohmann::json;
105 std::ifstream file(jsonPath);
107 std::stringstream ss;
108 ss <<
"Problem opening file " << jsonPath <<
". Make sure it exists and is readable" << std::endl;
113 j = json::parse(file);
115 catch (json::parse_error &e) {
116 std::stringstream msg;
117 msg <<
"Could not parse JSON file : \n";
118 msg << e.what() << std::endl;
119 msg <<
"Byte position of error: " << e.byte;
124 initGaussianFilters();
125 initGradientFilters();
130 vpCannyEdgeDetection::initGaussianFilters()
132 if ((m_gaussianKernelSize % 2) == 0) {
135 m_fg.
resize(1, (m_gaussianKernelSize + 1) / 2);
140 vpCannyEdgeDetection::initGradientFilters()
142 if ((m_gradientFilterKernelSize % 2) != 1) {
145 m_gradientFilterX.
resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
146 m_gradientFilterY.
resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
148 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
150 unsigned int filter_rows = filter.
getRows();
151 unsigned int filter_col = filter.
getCols();
152 for (
unsigned int r = 0; r < filter_rows; ++r) {
153 for (
unsigned int c = 0; c < filter_col; ++c) {
154 filter[r][c] = filter[r][c] * scale;
173 std::string errMsg =
"[vpCannyEdgeDetection::initGradientFilters] Error: gradient filtering method \"";
175 errMsg +=
"\" has not been implemented yet\n";
179 scaleFilter(m_gradientFilterX, scaleX);
180 scaleFilter(m_gradientFilterY, scaleY);
184 #ifdef HAVE_OPENCV_CORE
207 m_edgeCandidateAndGradient.clear();
208 m_edgePointsCandidates.clear();
211 if (!m_areGradientAvailable) {
212 performFilteringAndGradientComputation(I);
214 m_areGradientAvailable =
false;
217 float upperThreshold = m_upperThreshold;
218 float lowerThreshold = m_lowerThreshold;
219 if (upperThreshold < 0) {
221 m_gaussianStdev, m_gradientFilterKernelSize, m_lowerThresholdRatio,
222 m_upperThresholdRatio, m_filteringAndGradientType, mp_mask);
224 else if (m_lowerThreshold < 0) {
226 lowerThreshold = m_upperThreshold / 3.f;
229 lowerThreshold = std::max<float>(lowerThreshold, std::numeric_limits<float>::epsilon());
230 performEdgeThinning(lowerThreshold);
233 performHysteresisThresholding(lowerThreshold, upperThreshold);
236 performEdgeTracking();
248 vpImageFilter::filterX<unsigned char, float>(I, GIx, m_fg.
data, m_gaussianKernelSize, mp_mask);
249 vpImageFilter::filterY<float, float>(GIx, Iblur, m_fg.
data, m_gaussianKernelSize, mp_mask);
256 std::string errmsg(
"Currently, the filtering operation \"");
258 errmsg +=
"\" is not handled.";
276 getInterpolationWeightsAndOffsets(
const float &gradientOrientation,
277 float &alpha,
float &beta,
278 int &dRowGradAlpha,
int &dRowGradBeta,
279 int &dColGradAlpha,
int &dColGradBeta
282 float thetaMin = 0.f;
283 if (gradientOrientation < M_PI_4f) {
290 else if ((gradientOrientation >= M_PI_4f) && (gradientOrientation < M_PI_2f)) {
298 else if ((gradientOrientation >= M_PI_2f) && (gradientOrientation < (3.f * M_PI_4f))) {
306 else if ((gradientOrientation >= (3.f * M_PI_4f)) && (gradientOrientation < M_PIf)) {
308 thetaMin = 3.f * M_PI_4f;
314 beta = (gradientOrientation - thetaMin) / M_PI_4f;
338 float dx = dIx[row][col];
339 float dy = dIy[row][col];
340 grad = std::abs(dx) + std::abs(dy);
359 float gradientOrientation = 0.f;
360 float dx = dIx[row][col];
361 float dy = dIy[row][col];
363 if (std::abs(dx) < std::numeric_limits<float>::epsilon()) {
364 gradientOrientation = M_PI_2f;
369 gradientOrientation =
static_cast<float>(std::atan2(-dy, dx));
370 if (gradientOrientation < 0.f) {
371 gradientOrientation += M_PIf;
374 return gradientOrientation;
378 vpCannyEdgeDetection::performEdgeThinning(
const float &lowerThreshold)
383 for (
int row = 0; row < nbRows; ++row) {
384 for (
int col = 0; col < nbCols; ++col) {
385 if (mp_mask !=
nullptr) {
386 if (!(*mp_mask)[row][col]) {
393 float grad = getManhattanGradient(m_dIx, m_dIy, row, col);
395 if (grad < lowerThreshold) {
402 int dRowAlphaPlus = 0, dRowBetaPlus = 0;
403 int dColAphaPlus = 0, dColBetaPlus = 0;
404 float gradientOrientation = getGradientOrientation(m_dIx, m_dIy, row, col);
405 float alpha = 0.f, beta = 0.f;
406 getInterpolationWeightsAndOffsets(gradientOrientation, alpha, beta, dRowAlphaPlus, dRowBetaPlus, dColAphaPlus, dColBetaPlus);
407 int dRowAlphaMinus = -dRowAlphaPlus, dRowBetaMinus = -dRowBetaPlus;
408 int dColAphaMinus = -dColAphaPlus, dColBetaMinus = -dColBetaPlus;
409 float gradAlphaPlus = getManhattanGradient(m_dIx, m_dIy, row + dRowAlphaPlus, col + dColAphaPlus);
410 float gradBetaPlus = getManhattanGradient(m_dIx, m_dIy, row + dRowBetaPlus, col + dColBetaPlus);
411 float gradAlphaMinus = getManhattanGradient(m_dIx, m_dIy, row + dRowAlphaMinus, col + dColAphaMinus);
412 float gradBetaMinus = getManhattanGradient(m_dIx, m_dIy, row + dRowBetaMinus, col + dColBetaMinus);
413 float gradPlus = (alpha * gradAlphaPlus) + (beta * gradBetaPlus);
414 float gradMinus = (alpha * gradAlphaMinus) + (beta * gradBetaMinus);
416 if ((grad >= gradPlus) && (grad >= gradMinus)) {
418 std::pair<unsigned int, unsigned int> bestPixel(row, col);
419 m_edgeCandidateAndGradient[bestPixel] = grad;
426 vpCannyEdgeDetection::performHysteresisThresholding(
const float &lowerThreshold,
const float &upperThreshold)
428 std::map<std::pair<unsigned int, unsigned int>,
float>::iterator it;
429 for (it = m_edgeCandidateAndGradient.begin(); it != m_edgeCandidateAndGradient.end(); ++it) {
430 if (it->second >= upperThreshold) {
431 m_edgePointsCandidates[it->first] = STRONG_EDGE;
433 else if ((it->second >= lowerThreshold) && (it->second < upperThreshold)) {
434 m_edgePointsCandidates[it->first] = WEAK_EDGE;
440 vpCannyEdgeDetection::performEdgeTracking()
442 std::map<std::pair<unsigned int, unsigned int>, EdgeType>::iterator it;
443 for (it = m_edgePointsCandidates.begin(); it != m_edgePointsCandidates.end(); ++it) {
444 if (it->second == STRONG_EDGE) {
445 m_edgeMap[it->first.first][it->first.second] = 255;
447 else if (it->second == WEAK_EDGE) {
448 if (recursiveSearchForStrongEdge(it->first)) {
449 m_edgeMap[it->first.first][it->first.second] = 255;
456 vpCannyEdgeDetection::recursiveSearchForStrongEdge(
const std::pair<unsigned int, unsigned int> &coordinates)
458 bool hasFoundStrongEdge =
false;
461 m_edgePointsCandidates[coordinates] = ON_CHECK;
462 for (
int dr = -1; (dr <= 1) && (!hasFoundStrongEdge); ++dr) {
463 for (
int dc = -1; (dc <= 1) && (!hasFoundStrongEdge); ++dc) {
464 int idRow = dr +
static_cast<int>(coordinates.first);
465 idRow = std::max<int>(idRow, 0);
466 int idCol = dc +
static_cast<int>(coordinates.second);
467 idCol = std::max<int>(idCol, 0);
470 if (((idRow < 0) || (idRow >= nbRows))
471 || ((idCol < 0) || (idCol >= nbCols))
472 || ((dr == 0) && (dc == 0))
478 std::pair<unsigned int, unsigned int> key_candidate(idRow, idCol);
480 EdgeType type_candidate = m_edgePointsCandidates.at(key_candidate);
481 if (type_candidate == STRONG_EDGE) {
483 hasFoundStrongEdge =
true;
485 else if (type_candidate == WEAK_EDGE) {
487 hasFoundStrongEdge = recursiveSearchForStrongEdge(key_candidate);
495 if (hasFoundStrongEdge) {
496 m_edgePointsCandidates[coordinates] = STRONG_EDGE;
497 m_edgeMap[coordinates.first][coordinates.second] = 255;
499 return hasFoundStrongEdge;
Implementation of a generic 2D array used as base class for matrices and vectors.
unsigned int getCols() const
Type * data
Address of the first element of the data array.
void resize(unsigned int nrows, unsigned int ncols, bool flagNullify=true, bool recopy_=true)
unsigned int getRows() const
vpImage< unsigned char > detect(const vpImage< vpRGBa > &I_color)
Detect the edges in an image. Convert the color image into a gray-scale image.
friend void from_json(const nlohmann::json &j, vpCannyEdgeDetection &detector)
Read the detector configuration from JSON. All values are optional and if an argument is not present,...
void initFromJSON(const std::string &jsonPath)
Initialize all the algorithm parameters using the JSON file whose path is jsonPath....
vpCannyEdgeDetection()
Default constructor of the vpCannyEdgeDetection class. The thresholds used during the hysteresis thre...
error that can be emitted by ViSP classes.
@ badValue
Used to indicate that a value is not in the allowed range.
@ notImplementedError
Not implemented.
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
Various image filter, convolution, etc...
static FilterType getSobelKernelX(FilterType *filter, unsigned int size)
vpCannyFilteringAndGradientType
Canny filter and gradient operators to apply on the image before the edge detection stage.
@ CANNY_GBLUR_SOBEL_FILTERING
Apply Gaussian blur + Sobel operator on the input image.
@ CANNY_GBLUR_SCHARR_FILTERING
Apply Gaussian blur + Scharr operator on the input image.
static void filter(const vpImage< ImageType > &I, vpImage< FilterType > &If, const vpArray2D< FilterType > &M, bool convolve=false, const vpImage< bool > *p_mask=nullptr)
static std::string vpCannyFilteringAndGradientTypeToString(const vpCannyFilteringAndGradientType &type)
Cast a vpImageFilter::vpCannyFilteringAndGradientType into a string, to know its name.
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)
Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel or + Scharr operators to c...
static void getGaussianKernel(FilterType *filter, unsigned int size, FilterType sigma=0., bool normalize=true)
static FilterType getScharrKernelY(FilterType *filter, unsigned int size)
static FilterType getSobelKernelY(FilterType *filter, unsigned int size)
static FilterType getScharrKernelX(FilterType *filter, unsigned int size)
unsigned int getWidth() const
void resize(unsigned int h, unsigned int w)
resize the image : Image initialization
unsigned int getCols() const
unsigned int getHeight() const
unsigned int getRows() const