Visual Servoing Platform  version 3.6.1 under development (2024-12-03)
vpCannyEdgeDetection.cpp
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2024 by Inria. All rights reserved.
4  *
5  * This software is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * See the file LICENSE.txt at the root directory of this source
10  * distribution for additional information about the GNU GPL.
11  *
12  * For using ViSP with software that can not be combined with the GNU
13  * GPL, please contact Inria about acquiring a ViSP Professional
14  * Edition License.
15  *
16  * See https://visp.inria.fr for more information.
17  *
18  * This software was developed at:
19  * Inria Rennes - Bretagne Atlantique
20  * Campus Universitaire de Beaulieu
21  * 35042 Rennes Cedex
22  * France
23  *
24  * If you have questions regarding the use of this file, please contact
25  * Inria at visp@inria.fr
26  *
27  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29  */
30 
31 #include <visp3/core/vpCannyEdgeDetection.h>
32 
33 #include <visp3/core/vpImageConvert.h>
34 
35 #ifdef VISP_USE_MSVC
36 #pragma comment(linker, "/STACK:65532000") // Increase max recursion depth
37 #endif
38 
39 #if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98) // Check if cxx98
40 namespace
41 {
42 // Helper to apply the scale to the raw values of the filters
43 template <typename FilterType>
44 static void scaleFilter(
45 #ifdef ENABLE_VISP_NAMESPACE
46  visp::
47 #endif
48  vpArray2D<FilterType> &filter, const float &scale)
49 {
50  const unsigned int nbRows = filter.getRows();
51  const unsigned int nbCols = filter.getCols();
52  for (unsigned int r = 0; r < nbRows; ++r) {
53  for (unsigned int c = 0; c < nbCols; ++c) {
54  filter[r][c] = filter[r][c] * scale;
55  }
56  }
57 }
58 }
59 #endif
60 
61 BEGIN_VISP_NAMESPACE
62 #ifdef VISP_HAVE_NLOHMANN_JSON
63 void from_json(const nlohmann::json &j, vpCannyEdgeDetection &detector)
64 {
65  std::string filteringAndGradientName = vpImageFilter::vpCannyFiltAndGradTypeToStr(detector.m_filteringAndGradientType);
66  filteringAndGradientName = j.value("filteringAndGradientType", filteringAndGradientName);
67  detector.m_filteringAndGradientType = vpImageFilter::vpCannyFiltAndGradTypeFromStr(filteringAndGradientName);
68  detector.m_gaussianKernelSize = j.value("gaussianSize", detector.m_gaussianKernelSize);
69  detector.m_gaussianStdev = j.value("gaussianStdev", detector.m_gaussianStdev);
70  detector.m_lowerThreshold = j.value("lowerThreshold", detector.m_lowerThreshold);
71  detector.m_lowerThresholdRatio = j.value("lowerThresholdRatio", detector.m_lowerThresholdRatio);
72  detector.m_gradientFilterKernelSize = j.value("gradientFilterKernelSize", detector.m_gradientFilterKernelSize);
73  detector.m_upperThreshold = j.value("upperThreshold", detector.m_upperThreshold);
74  detector.m_upperThresholdRatio = j.value("upperThresholdRatio", detector.m_upperThresholdRatio);
75 }
76 
77 void to_json(nlohmann::json &j, const vpCannyEdgeDetection &detector)
78 {
79  std::string filteringAndGradientName = vpImageFilter::vpCannyFiltAndGradTypeToStr(detector.m_filteringAndGradientType);
80  j = nlohmann::json {
81  {"filteringAndGradientType", filteringAndGradientName},
82  {"gaussianSize", detector.m_gaussianKernelSize},
83  {"gaussianStdev", detector.m_gaussianStdev},
84  {"lowerThreshold", detector.m_lowerThreshold},
85  {"lowerThresholdRatio", detector.m_lowerThresholdRatio},
86  {"gradientFilterKernelSize", detector.m_gradientFilterKernelSize},
87  {"upperThreshold", detector.m_upperThreshold},
88  {"upperThresholdRatio", detector.m_upperThresholdRatio}
89  };
90 }
91 #endif
92 
93 // // Initialization methods
94 
96  : m_filteringAndGradientType(vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
97  , m_gaussianKernelSize(3)
98  , m_gaussianStdev(1.f)
99  , m_areGradientAvailable(false)
100  , m_gradientFilterKernelSize(3)
101  , m_lowerThreshold(-1.f)
102  , m_lowerThresholdRatio(0.6f)
103  , m_upperThreshold(-1.f)
104  , m_upperThresholdRatio(0.8f)
105 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
106  , m_minStackSize(0) // Deactivated by default
107 #endif
108  , mp_mask(nullptr)
109 {
110  initGaussianFilters();
111  initGradientFilters();
112 }
113 
114 vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev,
115  const unsigned int &sobelAperture, const float &lowerThreshold,
116  const float &upperThreshold, const float &lowerThresholdRatio,
117  const float &upperThresholdRatio,
119  const bool &storeEdgePoints
120 )
121  : m_filteringAndGradientType(filteringType)
122  , m_gaussianKernelSize(gaussianKernelSize)
123  , m_gaussianStdev(gaussianStdev)
124  , m_areGradientAvailable(false)
125  , m_gradientFilterKernelSize(sobelAperture)
126  , m_lowerThreshold(lowerThreshold)
127  , m_lowerThresholdRatio(lowerThresholdRatio)
128  , m_upperThreshold(upperThreshold)
129  , m_upperThresholdRatio(upperThresholdRatio)
130 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
131  , m_minStackSize(0) // Deactivated by default
132 #endif
133  , m_storeListEdgePoints(storeEdgePoints)
134  , mp_mask(nullptr)
135 {
136  initGaussianFilters();
137  initGradientFilters();
138 }
139 
140 #ifdef VISP_HAVE_NLOHMANN_JSON
141 
142 using json = nlohmann::json;
143 
144 vpCannyEdgeDetection::vpCannyEdgeDetection(const std::string &jsonPath) :
145 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
146  m_minStackSize(0) // Deactivated by default
147 #endif
148 {
149  initFromJSON(jsonPath);
150 }
151 
152 void
153 vpCannyEdgeDetection::initFromJSON(const std::string &jsonPath)
154 {
155  std::ifstream file(jsonPath);
156  if (!file.good()) {
157  std::stringstream ss;
158  ss << "Problem opening file " << jsonPath << ". Make sure it exists and is readable" << std::endl;
159  throw vpException(vpException::ioError, ss.str());
160  }
161  json j;
162  try {
163  j = json::parse(file);
164  }
165  catch (json::parse_error &e) {
166  std::stringstream msg;
167  msg << "Could not parse JSON file : \n";
168  msg << e.what() << std::endl;
169  msg << "Byte position of error: " << e.byte;
170  throw vpException(vpException::ioError, msg.str());
171  }
172  from_json(j, *this);
173  file.close();
174  initGaussianFilters();
175  initGradientFilters();
176 }
177 #endif
178 
179 void
180 vpCannyEdgeDetection::initGaussianFilters()
181 {
182  const int val_2 = 2;
183  if ((m_gaussianKernelSize % val_2) == 0) {
184  throw(vpException(vpException::badValue, "The Gaussian kernel size should be odd"));
185  }
186  m_fg.resize(1, (m_gaussianKernelSize + 1) / 2);
187  vpImageFilter::getGaussianKernel(m_fg.data, m_gaussianKernelSize, m_gaussianStdev, true);
188 }
189 
190 void
191 vpCannyEdgeDetection::initGradientFilters()
192 {
193  const int val_2 = 2;
194  if ((m_gradientFilterKernelSize % val_2) != 1) {
195  throw vpException(vpException::badValue, "Gradient filters kernel size should be odd.");
196  }
197  m_gradientFilterX.resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
198  m_gradientFilterY.resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
199 
200 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
201  auto scaleFilter = [](vpArray2D<float> &filter, const float &scale) {
202  unsigned int filter_rows = filter.getRows();
203  unsigned int filter_col = filter.getCols();
204  for (unsigned int r = 0; r < filter_rows; ++r) {
205  for (unsigned int c = 0; c < filter_col; ++c) {
206  filter[r][c] = filter[r][c] * scale;
207  }
208  }
209  };
210 #endif
211 
212  float scaleX = 1.f;
213  float scaleY = 1.f;
214 
215  if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) {
216  scaleX = vpImageFilter::getSobelKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1) / val_2);
217  scaleY = vpImageFilter::getSobelKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1) / val_2);
218  }
219  else if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) {
220  // Compute the Scharr filters
221  scaleX = vpImageFilter::getScharrKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1) / val_2);
222  scaleY = vpImageFilter::getScharrKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1) / val_2);
223  }
224  else {
225  std::string errMsg = "[vpCannyEdgeDetection::initGradientFilters] Error: gradient filtering method \"";
226  errMsg += vpImageFilter::vpCannyFiltAndGradTypeToStr(m_filteringAndGradientType);
227  errMsg += "\" has not been implemented yet\n";
229  }
230 
231  scaleFilter(m_gradientFilterX, scaleX);
232  scaleFilter(m_gradientFilterY, scaleY);
233 }
234 
235 // // Detection methods
236 #ifdef HAVE_OPENCV_CORE
238 vpCannyEdgeDetection::detect(const cv::Mat &cv_I)
239 {
240  vpImage<unsigned char> I_gray;
241  vpImageConvert::convert(cv_I, I_gray);
242  return detect(I_gray);
243 }
244 #endif
245 
248 {
249  vpImage<unsigned char> I_gray;
250  vpImageConvert::convert(I_color, I_gray);
251  return detect(I_gray);
252 }
253 
256 {
257 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
258  rlim_t initialStackSize = 0;
259  struct rlimit rl;
260  int result;
261  if (m_minStackSize > 0) {
262  // Check the current stack size
263  result = getrlimit(RLIMIT_STACK, &rl);
264  if (result == 0) {
265  initialStackSize = rl.rlim_cur;
266  if (rl.rlim_cur < m_minStackSize) {
267  // Increase stack size due to the recursive algorithm
268  rl.rlim_cur = m_minStackSize;
269  result = setrlimit(RLIMIT_STACK, &rl);
270  if (result != 0) {
271  throw(vpException(vpException::fatalError, "setrlimit returned result = %d\n", result));
272  }
273  }
274  }
275  else {
276  throw(vpException(vpException::fatalError, "getrlimit returned result = %d\n", result));
277  }
278  }
279 #endif
280  // // Clearing the previous results
281  m_edgeMap.resize(I.getHeight(), I.getWidth(), 0);
282  m_edgeCandidateAndGradient.clear();
283  m_edgePointsCandidates.clear();
284  m_edgePointsList.clear();
285 
286  // // Step 1 and 2: filter the image and compute the gradient, if not given by the user
287  if (!m_areGradientAvailable) {
288  computeFilteringAndGradient(I);
289  }
290  m_areGradientAvailable = false; // Reset for next call
291 
292  // // Step 3: edge thining
293  float upperThreshold = m_upperThreshold;
294  float lowerThreshold = m_lowerThreshold;
295  if (upperThreshold < 0) {
296  upperThreshold = vpImageFilter::computeCannyThreshold(I, lowerThreshold, &m_dIx, &m_dIy, m_gaussianKernelSize,
297  m_gaussianStdev, m_gradientFilterKernelSize, m_lowerThresholdRatio,
298  m_upperThresholdRatio, m_filteringAndGradientType, mp_mask);
299  }
300  else if (m_lowerThreshold < 0) {
301  // Applying Canny recommendation to have the upper threshold 3 times greater than the lower threshold.
302  lowerThreshold = m_upperThreshold / 3.f;
303  }
304  // To ensure that if lowerThreshold = 0, we reject null gradient points
305  lowerThreshold = std::max<float>(lowerThreshold, std::numeric_limits<float>::epsilon());
306  performEdgeThinning(lowerThreshold);
307 
308  // // Step 4: hysteresis thresholding
309  performHysteresisThresholding(lowerThreshold, upperThreshold);
310 
311  // // Step 5: edge tracking
312  performEdgeTracking();
313 
314 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
315  if (m_minStackSize > 0) {
316  if (rl.rlim_cur > initialStackSize) {
317  // Reset stack size to its original value
318  rl.rlim_cur = initialStackSize;
319  result = setrlimit(RLIMIT_STACK, &rl);
320  if (result != 0) {
321  throw(vpException(vpException::fatalError, "setrlimit returned result = %d\n", result));
322 
323  }
324  }
325  }
326 #endif
327  return m_edgeMap;
328 }
329 
330 void
331 vpCannyEdgeDetection::computeFilteringAndGradient(const vpImage<unsigned char> &I)
332 {
333  if ((m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
334  || (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING)) {
335  // Computing the Gaussian blur
336  vpImage<float> Iblur;
337  vpImage<float> GIx;
338  vpImageFilter::filterX<unsigned char, float>(I, GIx, m_fg.data, m_gaussianKernelSize, mp_mask);
339  vpImageFilter::filterY<float, float>(GIx, Iblur, m_fg.data, m_gaussianKernelSize, mp_mask);
340 
341  // Computing the gradients
342  vpImageFilter::filter(Iblur, m_dIx, m_gradientFilterX, true, mp_mask);
343  vpImageFilter::filter(Iblur, m_dIy, m_gradientFilterY, true, mp_mask);
344  }
345  else {
346  std::string errmsg("Currently, the filtering operation \"");
347  errmsg += vpImageFilter::vpCannyFiltAndGradTypeToStr(m_filteringAndGradientType);
348  errmsg += "\" is not handled.";
350  }
351 }
352 
365 void
366 getInterpolWeightsAndOffsets(const float &gradientOrientation,
367  float &alpha, float &beta,
368  int &dRowGradAlpha, int &dRowGradBeta,
369  int &dColGradAlpha, int &dColGradBeta
370 )
371 {
372  float thetaMin = 0.f;
373  if (gradientOrientation < M_PI_4_FLOAT) {
374  // Angles between 0 and 45 deg rely on the horizontal and diagonal points
375  dColGradAlpha = 1;
376  dColGradBeta = 1;
377  dRowGradAlpha = 0;
378  dRowGradBeta = -1;
379  }
380  else if ((gradientOrientation >= M_PI_4_FLOAT) && (gradientOrientation < M_PI_2_FLOAT)) {
381  // Angles between 45 and 90 deg rely on the diagonal and vertical points
382  thetaMin = M_PI_4_FLOAT;
383  dColGradAlpha = 1;
384  dColGradBeta = 0;
385  dRowGradAlpha = -1;
386  dRowGradBeta = -1;
387  }
388  else if ((gradientOrientation >= M_PI_2_FLOAT) && (gradientOrientation < (3.f * M_PI_4_FLOAT))) {
389  // Angles between 90 and 135 deg rely on the vertical and diagonal points
390  thetaMin = M_PI_2_FLOAT;
391  dColGradAlpha = 0;
392  dColGradBeta = -1;
393  dRowGradAlpha = -1;
394  dRowGradBeta = -1;
395  }
396  else if ((gradientOrientation >= (3.f * M_PI_4_FLOAT)) && (gradientOrientation < M_PI_FLOAT)) {
397  // Angles between 135 and 180 deg rely on the vertical and diagonal points
398  thetaMin = 3.f * M_PI_4_FLOAT;
399  dColGradAlpha = -1;
400  dColGradBeta = -1;
401  dRowGradAlpha = -1;
402  dRowGradBeta = 0;
403  }
404  beta = (gradientOrientation - thetaMin) / M_PI_4_FLOAT;
405  alpha = 1.f - beta;
406 }
407 
417 float
418 getManhattanGradient(const vpImage<float> &dIx, const vpImage<float> &dIy, const int &row, const int &col)
419 {
420  float grad = 0.;
421  int nbRows = dIx.getRows();
422  int nbCols = dIx.getCols();
423  if ((row >= 0)
424  && (row < nbRows)
425  && (col >= 0)
426  && (col < nbCols)
427  ) {
428  float dx = dIx[row][col];
429  float dy = dIy[row][col];
430  grad = std::abs(dx) + std::abs(dy);
431  }
432  return grad;
433 }
434 
446 float
447 getGradientOrientation(const vpImage<float> &dIx, const vpImage<float> &dIy, const int &row, const int &col)
448 {
449  float gradientOrientation = 0.f;
450  float dx = dIx[row][col];
451  float dy = dIy[row][col];
452 
453  if (std::abs(dx) < std::numeric_limits<float>::epsilon()) {
454  gradientOrientation = M_PI_2_FLOAT;
455  }
456  else {
457  // -dy because the y-axis of the image is oriented towards the bottom of the screen
458  // while we later work with a y-axis oriented towards the top when getting the theta quadrant.
459  gradientOrientation = static_cast<float>(std::atan2(-dy, dx));
460  if (gradientOrientation < 0.f) {
461  gradientOrientation += M_PI_FLOAT; // + M_PI in order to be between 0 and M_PI_FLOAT
462  }
463  }
464  return gradientOrientation;
465 }
466 
467 void
468 vpCannyEdgeDetection::performEdgeThinning(const float &lowerThreshold)
469 {
470  int nbRows = m_dIx.getRows();
471  int nbCols = m_dIx.getCols();
472 
473  bool ignore_current_pixel = false;
474  bool grad_lower_threshold = false;
475  for (int row = 0; row < nbRows; ++row) {
476  for (int col = 0; col < nbCols; ++col) {
477  // reset the checks
478  ignore_current_pixel = false;
479  grad_lower_threshold = false;
480 
481  if (mp_mask != nullptr) {
482  if (!(*mp_mask)[row][col]) {
483  // The mask tells us to ignore the current pixel
484  ignore_current_pixel = true;
485  // continue
486  }
487  }
488  // continue if the mask does not tell us to ignore the current pixel
489  if (ignore_current_pixel == false) {
490 
491  // Computing the gradient orientation and magnitude
492  float grad = getManhattanGradient(m_dIx, m_dIy, row, col);
493 
494  if (grad < lowerThreshold) {
495  // The gradient is lower than minimum threshold => ignoring the point
496  grad_lower_threshold = true;
497  // continue
498  }
499  if (grad_lower_threshold == false) {
500  //
501  // Getting the offset along the horizontal and vertical axes
502  // depending on the gradient orientation
503  int dRowAlphaPlus = 0, dRowBetaPlus = 0;
504  int dColAphaPlus = 0, dColBetaPlus = 0;
505  float gradientOrientation = getGradientOrientation(m_dIx, m_dIy, row, col);
506  float alpha = 0.f, beta = 0.f;
507  getInterpolWeightsAndOffsets(gradientOrientation, alpha, beta, dRowAlphaPlus, dRowBetaPlus, dColAphaPlus, dColBetaPlus);
508  int dRowAlphaMinus = -dRowAlphaPlus, dRowBetaMinus = -dRowBetaPlus;
509  int dColAphaMinus = -dColAphaPlus, dColBetaMinus = -dColBetaPlus;
510  float gradAlphaPlus = getManhattanGradient(m_dIx, m_dIy, row + dRowAlphaPlus, col + dColAphaPlus);
511  float gradBetaPlus = getManhattanGradient(m_dIx, m_dIy, row + dRowBetaPlus, col + dColBetaPlus);
512  float gradAlphaMinus = getManhattanGradient(m_dIx, m_dIy, row + dRowAlphaMinus, col + dColAphaMinus);
513  float gradBetaMinus = getManhattanGradient(m_dIx, m_dIy, row + dRowBetaMinus, col + dColBetaMinus);
514  float gradPlus = (alpha * gradAlphaPlus) + (beta * gradBetaPlus);
515  float gradMinus = (alpha * gradAlphaMinus) + (beta * gradBetaMinus);
516 
517  if ((grad >= gradPlus) && (grad >= gradMinus)) {
518  // Keeping the edge point that has the highest gradient
519  std::pair<unsigned int, unsigned int> bestPixel(row, col);
520  m_edgeCandidateAndGradient[bestPixel] = grad;
521  }
522  }
523  }
524  }
525  }
526 }
527 
528 void
529 vpCannyEdgeDetection::performHysteresisThresholding(const float &lowerThreshold, const float &upperThreshold)
530 {
531  std::map<std::pair<unsigned int, unsigned int>, float>::iterator it;
532  std::map<std::pair<unsigned int, unsigned int>, float>::iterator m_edgeCandidateAndGradient_end = m_edgeCandidateAndGradient.end();
533  for (it = m_edgeCandidateAndGradient.begin(); it != m_edgeCandidateAndGradient_end; ++it) {
534  if (it->second >= upperThreshold) {
535  m_edgePointsCandidates[it->first] = STRONG_EDGE;
536  }
537  else if ((it->second >= lowerThreshold) && (it->second < upperThreshold)) {
538  m_edgePointsCandidates[it->first] = WEAK_EDGE;
539  }
540  }
541 }
542 
543 void
544 vpCannyEdgeDetection::performEdgeTracking()
545 {
546  const unsigned char var_uc_255 = 255;
547  std::map<std::pair<unsigned int, unsigned int>, EdgeType>::iterator it;
548  std::map<std::pair<unsigned int, unsigned int>, EdgeType>::iterator m_edgePointsCandidates_end = m_edgePointsCandidates.end();
549  for (it = m_edgePointsCandidates.begin(); it != m_edgePointsCandidates_end; ++it) {
550  if (it->second == STRONG_EDGE) {
551  m_edgeMap[it->first.first][it->first.second] = var_uc_255;
552  if (m_storeListEdgePoints) {
553  m_edgePointsList.push_back(vpImagePoint(it->first.first, it->first.second));
554  }
555  }
556  else if (it->second == WEAK_EDGE) {
557  recursiveSearchForStrongEdge(it->first);
558  }
559  }
560 }
561 
562 bool
563 vpCannyEdgeDetection::recursiveSearchForStrongEdge(const std::pair<unsigned int, unsigned int> &coordinates)
564 {
565  bool hasFoundStrongEdge = false;
566  int nbRows = m_dIx.getRows();
567  int nbCols = m_dIx.getCols();
568  m_edgePointsCandidates[coordinates] = ON_CHECK;
569  bool test_row = false;
570  bool test_col = false;
571  bool test_drdc = false;
572  bool edge_in_image_limit = false;
573  int dr = -1;
574  while ((dr <= 1) && (!hasFoundStrongEdge)) {
575  int dc = -1;
576  while ((dc <= 1) && (!hasFoundStrongEdge)) {
577  // reset the check for the edge on image limit
578  edge_in_image_limit = false;
579 
580  int idRow = dr + static_cast<int>(coordinates.first);
581  idRow = std::max<int>(idRow, 0); // Avoid getting negative pixel ID
582  int idCol = dc + static_cast<int>(coordinates.second);
583  idCol = std::max<int>(idCol, 0); // Avoid getting negative pixel ID
584 
585  // Checking if we are still looking for an edge in the limit of the image
586  test_row = (idRow < 0) || (idRow >= nbRows);
587  test_col = (idCol < 0) || (idCol >= nbCols);
588  test_drdc = (dr == 0) && (dc == 0);
589  if (test_row || test_col || test_drdc) {
590  edge_in_image_limit = true;
591  // the continue is replaced by the test
592  }
593  if (edge_in_image_limit == false) {
594 
595  try {
596  std::pair<unsigned int, unsigned int> key_candidate(idRow, idCol);
597  // Checking if the 8-neighbor point is in the list of edge candidates
598  EdgeType type_candidate = m_edgePointsCandidates.at(key_candidate);
599  if (type_candidate == STRONG_EDGE) {
600  // The 8-neighbor point is a strong edge => the weak edge becomes a strong edge
601  hasFoundStrongEdge = true;
602  }
603  else if (type_candidate == WEAK_EDGE) {
604  // Checking if the WEAK_EDGE neighbor has a STRONG_EDGE neighbor
605  hasFoundStrongEdge = recursiveSearchForStrongEdge(key_candidate);
606  }
607  }
608  catch (...) {
609  // continue - nothing to do
610  }
611  }
612  ++dc;
613  }
614  ++dr;
615  }
616  const unsigned char var_uc_255 = 255;
617  if (hasFoundStrongEdge) {
618  m_edgePointsCandidates[coordinates] = STRONG_EDGE;
619  m_edgeMap[coordinates.first][coordinates.second] = var_uc_255;
620  if (m_storeListEdgePoints) {
621  m_edgePointsList.push_back(vpImagePoint(coordinates.first, coordinates.second));
622  }
623  }
624  return hasFoundStrongEdge;
625 }
626 END_VISP_NAMESPACE
Implementation of a generic 2D array used as base class for matrices and vectors.
Definition: vpArray2D.h:145
unsigned int getCols() const
Definition: vpArray2D.h:337
Type * data
Address of the first element of the data array.
Definition: vpArray2D.h:148
void resize(unsigned int nrows, unsigned int ncols, bool flagNullify=true, bool recopy_=true)
Definition: vpArray2D.h:362
unsigned int getRows() const
Definition: vpArray2D.h:347
Class that implements the Canny's edge detector. It is possible to use a boolean mask to ignore some ...
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.
Definition: vpException.h:60
@ ioError
I/O error.
Definition: vpException.h:67
@ badValue
Used to indicate that a value is not in the allowed range.
Definition: vpException.h:73
@ notImplementedError
Not implemented.
Definition: vpException.h:69
@ fatalError
Fatal error.
Definition: vpException.h:72
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
Various image filter, convolution, etc...
Definition: vpImageFilter.h:71
static FilterType getSobelKernelX(FilterType *filter, unsigned int size)
static std::string vpCannyFiltAndGradTypeToStr(const vpCannyFilteringAndGradientType &type)
Cast a vpImageFilter::vpCannyFilteringAndGradientType into a string, to know its name.
vpCannyFilteringAndGradientType
Canny filter and gradient operators to apply on the image before the edge detection stage.
Definition: vpImageFilter.h:90
@ CANNY_GBLUR_SOBEL_FILTERING
Apply Gaussian blur + Sobel operator on the input image.
Definition: vpImageFilter.h:91
@ CANNY_GBLUR_SCHARR_FILTERING
Apply Gaussian blur + Scharr operator on the input image.
Definition: vpImageFilter.h:92
static void filter(const vpImage< ImageType > &I, vpImage< FilterType > &If, const vpArray2D< FilterType > &M, bool convolve=false, const vpImage< bool > *p_mask=nullptr)
static vpCannyFilteringAndGradientType vpCannyFiltAndGradTypeFromStr(const std::string &name)
Cast a string into a vpImageFilter::vpCannyFilteringAndGradientType.
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)
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
Definition: vpImagePoint.h:82
unsigned int getWidth() const
Definition: vpImage.h:242
void resize(unsigned int h, unsigned int w)
resize the image : Image initialization
Definition: vpImage.h:544
unsigned int getCols() const
Definition: vpImage.h:171
unsigned int getHeight() const
Definition: vpImage.h:181
unsigned int getRows() const
Definition: vpImage.h:212
Definition: vpIoTools.h:61