Visual Servoing Platform  version 3.6.1 under development (2024-12-07)
vpImageFilter_canny.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  * Description:
31  * Image Canny filtering.
32  */
33 
34 #include <visp3/core/vpConfig.h>
35 #include <visp3/core/vpImageFilter.h>
36 #include <visp3/core/vpIoTools.h>
37 #include <visp3/core/vpCannyEdgeDetection.h>
38 
39 BEGIN_VISP_NAMESPACE
40 
49 std::string vpImageFilter::vpCannyBackendTypeList(const std::string &pref, const std::string &sep,
50  const std::string &suf)
51 {
52  std::string list(pref);
53  for (unsigned int i = 0; i < (CANNY_COUNT_BACKEND - 1); ++i) {
54  vpCannyBackendType type = static_cast<vpCannyBackendType>(i);
55  list += vpCannyBackendTypeToString(type);
56  list += sep;
57  }
59  list += vpCannyBackendTypeToString(type);
60  list += suf;
61  return list;
62 }
63 
71 {
72  std::string name;
73  switch (type) {
75  name = "opencv-backend";
76  break;
77  case CANNY_VISP_BACKEND:
78  name = "visp-backend";
79  break;
81  default:
82  return "unknown-backend";
83  }
84  return name;
85 }
86 
94 {
96  std::string nameLowerCase = vpIoTools::toLowerCase(name);
97  unsigned int count = static_cast<unsigned int>(CANNY_COUNT_BACKEND);
98  bool notFound = true;
99  unsigned int i = 0;
100  while ((i < count) && notFound) {
101  vpCannyBackendType temp = static_cast<vpCannyBackendType>(i);
102  if (nameLowerCase == vpCannyBackendTypeToString(temp)) {
103  type = temp;
104  notFound = false;
105  }
106  ++i;
107  }
108  return type;
109 }
110 
119 std::string vpImageFilter::vpGetCannyFiltAndGradTypes(const std::string &pref, const std::string &sep,
120  const std::string &suf)
121 {
122  std::string list(pref);
123  for (unsigned int i = 0; i < (CANNY_COUNT_FILTERING - 1); ++i) {
125  list += vpCannyFiltAndGradTypeToStr(type);
126  list += sep;
127  }
129  list += vpCannyFiltAndGradTypeToStr(type);
130  list += suf;
131  return list;
132 }
133 
141 {
142  std::string name;
143  switch (type) {
145  name = "gaussianblur+sobel-filtering";
146  break;
148  name = "gaussianblur+scharr-filtering";
149  break;
151  default:
152  return "unknown-filtering";
153  }
154  return name;
155 }
156 
164 {
166  std::string nameLowerCase = vpIoTools::toLowerCase(name);
167  unsigned int count = static_cast<unsigned int>(CANNY_COUNT_FILTERING);
168  bool notFound = true;
169  unsigned int i = 0;
170  while ((i < count) && notFound) {
172  if (nameLowerCase == vpCannyFiltAndGradTypeToStr(temp)) {
173  type = temp;
174  notFound = false;
175  }
176  ++i;
177  }
178  return type;
179 }
180 
181 #if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC)
200 float vpImageFilter::computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_dIx, const cv::Mat *p_cv_dIy,
201  float &lowerThresh, const unsigned int &gaussianKernelSize,
202  const float &gaussianStdev, const unsigned int &apertureGradient,
203  const float &lowerThresholdRatio, const float &upperThresholdRatio,
205 {
206  if ((lowerThresholdRatio <= 0.f) || (lowerThresholdRatio >= 1.f)) {
207  std::stringstream errMsg;
208  errMsg << "Lower ratio (" << lowerThresholdRatio << ") " << (lowerThresholdRatio < 0.f ? "should be greater than 0 !" : "should be lower than 1 !");
209  throw(vpException(vpException::fatalError, errMsg.str()));
210  }
211 
212  if ((upperThresholdRatio <= 0.f) || (upperThresholdRatio >= 1.f)) {
213  std::stringstream errMsg;
214  errMsg << "Upper ratio (" << upperThresholdRatio << ") " << (upperThresholdRatio < 0.f ? "should be greater than 0 !" : "should be lower than 1 !");
215  throw(vpException(vpException::fatalError, errMsg.str()));
216  }
217 
218  if (lowerThresholdRatio >= upperThresholdRatio) {
219  std::stringstream errMsg;
220  errMsg << "Lower ratio (" << lowerThresholdRatio << ") should be lower than the upper ratio (" << upperThresholdRatio << ")";
221  throw(vpException(vpException::fatalError, errMsg.str()));
222  }
223 
224  double w = cv_I.cols;
225  double h = cv_I.rows;
226  int bins = 256;
227  cv::Mat dI, dIx, dIy, dIx_abs, dIy_abs;
228 
229  if ((p_cv_dIx == nullptr) || (p_cv_dIy == nullptr)) {
230  computePartialDerivatives(cv_I, dIx, dIy, true, true, true, gaussianKernelSize, gaussianStdev, apertureGradient,
231  filteringType);
232  }
233  else {
234  dIx = *p_cv_dIx;
235  dIy = *p_cv_dIy;
236  }
237 
238  // Compute the absolute gradient of the blurred image G = |dIx| + |dIy|
239  cv::convertScaleAbs(dIx, dIx_abs);
240  cv::convertScaleAbs(dIy, dIy_abs);
241  cv::addWeighted(dIx_abs, 1, dIy_abs, 1, 0, dI);
242  dI.convertTo(dI, CV_8U);
243 
244  // Compute the upper threshold from the equalized histogram
245  cv::Mat hist;
246  const float range[] = { 0.f, 256.f }; // The upper boundary is exclusive
247  const float *ranges[] = { range };
248  int channels[] = { 0 };
249  int dims = 1; // The number of dimensions of the histogram
250  int histSize[] = { bins };
251  bool uniform = true;
252  bool accumulate = false; // Clear the histogram at the beginning of calcHist if false, does not clear it otherwise
253  cv::calcHist(&dI, 1, channels, cv::Mat(), hist, dims, histSize, ranges, uniform, accumulate);
254  float accu = 0;
255  float t = static_cast<float>(upperThresholdRatio * w * h);
256  float bon = 0;
257  int i = 0;
258  bool notFound = true;
259  while ((i < bins) && notFound) {
260  float tf = hist.at<float>(i);
261  accu = accu + tf;
262  if (accu > t) {
263  bon = static_cast<float>(i);
264  notFound = false;
265  }
266  ++i;
267  }
268  if (notFound) {
269  std::stringstream errMsg;
270  errMsg << "Could not find a bin for which " << upperThresholdRatio * 100.f << " percents of the pixels had a gradient lower than the upper threshold.";
271  throw(vpException(vpException::fatalError, errMsg.str()));
272  }
273  float upperThresh = std::max<float>(bon, 1.f);
274  lowerThresh = lowerThresholdRatio * bon;
275  return upperThresh;
276 }
277 #endif
278 
323  const unsigned int &gaussianFilterSize, const float &thresholdCanny,
324  const unsigned int &apertureSobel)
325 {
326  vpImageFilter::canny(Isrc, Ires, gaussianFilterSize, thresholdCanny / 3.f, thresholdCanny, apertureSobel);
327 }
328 
377  const unsigned int &gaussianFilterSize,
378  const float &lowerThreshold, const float &upperThreshold,
379  const unsigned int &apertureSobel)
380 {
381  const float gaussianStdev = 2.f;
382  const float upperThresholdRatio = 0.8f;
383  const float lowerThresholdRatio = 0.6f;
384 #if defined(HAVE_OPENCV_IMGPROC)
385  const vpCannyBackendType cannyBackend = CANNY_OPENCV_BACKEND;
386 #else
387  const vpCannyBackendType cannyBackend = CANNY_VISP_BACKEND;
388 #endif
390  canny(Isrc, Ires, gaussianFilterSize, lowerThreshold, upperThreshold, apertureSobel,
391  gaussianStdev, lowerThresholdRatio, upperThresholdRatio, false, cannyBackend, cannyFilteringSteps);
392 }
393 
463  const unsigned int &gaussianFilterSize,
464  const float &lowerThreshold, const float &upperThreshold, const unsigned int &apertureGradient,
465  const float &gaussianStdev, const float &lowerThresholdRatio, const float &upperThresholdRatio,
466  const bool &normalizeGradients,
467  const vpCannyBackendType &cannyBackend, const vpCannyFilteringAndGradientType &cannyFilteringSteps,
468  const vpImage<bool> *p_mask)
469 {
470  if (cannyBackend == CANNY_OPENCV_BACKEND) {
471 #if defined(HAVE_OPENCV_IMGPROC)
472  cv::Mat img_cvmat, cv_dx, cv_dy, edges_cvmat;
473  vpImageConvert::convert(Isrc, img_cvmat);
474  computePartialDerivatives(img_cvmat, cv_dx, cv_dy, true, true, normalizeGradients, gaussianFilterSize,
475  gaussianStdev, apertureGradient, cannyFilteringSteps);
476  float upperCannyThresh = upperThreshold;
477  float lowerCannyThresh = lowerThreshold;
478  if (upperCannyThresh < 0.f) {
479  upperCannyThresh = computeCannyThreshold(img_cvmat, &cv_dx, &cv_dy, lowerCannyThresh, gaussianFilterSize,
480  gaussianStdev, apertureGradient, lowerThresholdRatio, upperThresholdRatio,
481  cannyFilteringSteps);
482  }
483  else if (lowerCannyThresh < 0.f) {
484  lowerCannyThresh = upperCannyThresh / 3.f;
485  }
486 #if (VISP_HAVE_OPENCV_VERSION >= 0x030200)
487  cv::Canny(cv_dx, cv_dy, edges_cvmat, lowerCannyThresh, upperCannyThresh, false);
488 #else
489  cv::GaussianBlur(img_cvmat, img_cvmat, cv::Size((int)gaussianFilterSize, (int)gaussianFilterSize),
490  gaussianStdev, gaussianStdev);
491  cv::Canny(img_cvmat, edges_cvmat, lowerCannyThresh, upperCannyThresh);
492 #endif
493  vpImageConvert::convert(edges_cvmat, Ires);
494 #else
495  std::string errMsg("[vpImageFilter::canny]You asked for CANNY_OPENCV_BACKEND but ViSP has not been compiled with OpenCV");
496  throw(vpException(vpException::badValue, errMsg));
497 #endif
498  }
499  else if (cannyBackend == CANNY_VISP_BACKEND) {
500  float upperCannyThresh = upperThreshold;
501  float lowerCannyThresh = lowerThreshold;
502 
503  vpImage<float> dIx, dIy;
504  computePartialDerivatives(Isrc, dIx, dIy, true, true, normalizeGradients, gaussianFilterSize,
505  gaussianStdev, apertureGradient, cannyFilteringSteps, cannyBackend, p_mask);
506 
507  if (upperCannyThresh < 0.f) {
508  upperCannyThresh = computeCannyThreshold(Isrc, lowerCannyThresh, &dIx, &dIy, gaussianFilterSize, gaussianStdev,
509  apertureGradient, lowerThresholdRatio, upperThresholdRatio,
510  cannyFilteringSteps, p_mask);
511  }
512  else if (lowerCannyThresh < 0.f) {
513  lowerCannyThresh = upperCannyThresh / 3.f;
514  }
515  vpCannyEdgeDetection edgeDetector(gaussianFilterSize, gaussianStdev, apertureGradient, lowerCannyThresh, upperCannyThresh,
516  lowerThresholdRatio, upperThresholdRatio, cannyFilteringSteps);
517  edgeDetector.setGradients(dIx, dIy);
518  edgeDetector.setMask(p_mask);
519  Ires = edgeDetector.detect(Isrc);
520  }
521 }
522 
523 
524 END_VISP_NAMESPACE
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.
void setMask(const vpImage< bool > *p_mask)
Set a mask to ignore pixels for which the mask is false.
void setGradients(const vpImage< float > &dIx, const vpImage< float > &dIy)
Set the Gradients of the image that will be processed.
error that can be emitted by ViSP classes.
Definition: vpException.h:60
@ badValue
Used to indicate that a value is not in the allowed range.
Definition: vpException.h:73
@ fatalError
Fatal error.
Definition: vpException.h:72
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
static std::string vpCannyBackendTypeToString(const vpCannyBackendType &type)
Cast a vpImageFilter::vpCannyBackendTypeToString into a string, to know its name.
static void canny(const vpImage< unsigned char > &I, vpImage< unsigned char > &Ic, const unsigned int &gaussianFilterSize, const float &thresholdCanny, const unsigned int &apertureSobel)
static std::string vpCannyBackendTypeList(const std::string &pref="<", const std::string &sep=" , ", const std::string &suf=">")
Get the list of available vpCannyBackendType.
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
vpCannyBackendType
Canny filter backends for the edge detection operations.
Definition: vpImageFilter.h:75
@ CANNY_VISP_BACKEND
Use ViSP.
Definition: vpImageFilter.h:77
@ CANNY_OPENCV_BACKEND
Use OpenCV.
Definition: vpImageFilter.h:76
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 vpCannyBackendType vpCannyBackendTypeFromString(const std::string &name)
Cast a string into a vpImageFilter::vpCannyBackendTypeToString.
static std::string vpGetCannyFiltAndGradTypes(const std::string &pref="<", const std::string &sep=" , ", const std::string &suf=">")
Get the list of available vpCannyFilteringAndGradientType.
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)
Compute the partial derivatives (i.e. horizontal and vertical gradients) of the input image.
static std::string toLowerCase(const std::string &input)
Return a lower-case version of the string input . Numbers and special characters stay the same.
Definition: vpIoTools.cpp:1339