Visual Servoing Platform  version 3.6.1 under development (2024-05-07)
tutorial-canny.cpp
1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2023 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See https://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  *
31 *****************************************************************************/
32 #include <visp3/core/vpConfig.h>
33 
34 #include <visp3/core/vpCannyEdgeDetection.h>
35 #include <visp3/core/vpImageFilter.h>
36 #include <visp3/io/vpImageIo.h>
37 
38 #ifdef HAVE_OPENCV_IMGPROC
39 #include <opencv2/imgproc/imgproc.hpp>
40 #endif
41 
42 #include "drawingHelpers.h"
43 
44 template <class T>
45 void computeMeanMaxStdev(const vpImage<T> &I, float &mean, float &max, float &stdev)
46 {
47  max = std::numeric_limits<float>::epsilon();
48  mean = 0.;
49  stdev = 0.;
50  unsigned int nbRows = I.getRows();
51  unsigned int nbCols = I.getCols();
52  float scale = 1.f / (static_cast<float>(nbRows) * static_cast<float>(nbCols));
53  for (unsigned int r = 0; r < nbRows; r++) {
54  for (unsigned int c = 0; c < nbCols; c++) {
55  mean += I[r][c];
56  max = std::max<float>(max, static_cast<float>(I[r][c]));
57  }
58  }
59  mean *= scale;
60  for (unsigned int r = 0; r < nbRows; r++) {
61  for (unsigned int c = 0; c < nbCols; c++) {
62  stdev += (I[r][c] - mean) * (I[r][c] - mean);
63  }
64  }
65  stdev *= scale;
66  stdev = std::sqrt(stdev);
67 }
68 
69 void setGradientOutsideClass(const vpImage<unsigned char> &I, const int &gaussianKernelSize, const float &gaussianStdev,
70  vpCannyEdgeDetection &cannyDetector, const unsigned int apertureSize,
72  vpImage<unsigned char> &dIx_uchar, vpImage<unsigned char> &dIy_uchar
73 )
74 {
75  // Computing the gradients
76  vpImage<float> dIx, dIy;
77  vpImageFilter::computePartialDerivatives(I, dIx, dIy, true, true, true, gaussianKernelSize, gaussianStdev,
78  apertureSize, filteringType);
79 
80  // Set the gradients of the vpCannyEdgeDetection
81  cannyDetector.setGradients(dIx, dIy);
82 
83  // Display the gradients
84  float mean, max, stdev;
85  computeMeanMaxStdev(dIx, mean, max, stdev);
86 
87 #if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
88  std::string title = "Gradient along the horizontal axis. Mean = " + std::to_string(mean)
89  + "+/-" + std::to_string(stdev) + " Max = " + std::to_string(max);
90 #else
91  std::string title;
92  {
93  std::stringstream ss;
94  ss << "Gradient along the horizontal axis. Mean = " << mean<< "+/-" << stdev<< " Max = " << max;
95  title = ss.str();
96  }
97 #endif
98  vpImageConvert::convert(dIx, dIx_uchar);
99  drawingHelpers::display(dIx_uchar, title);
100  computeMeanMaxStdev(dIy, mean, max, stdev);
101 #if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
102  title = "Gradient along the horizontal axis. Mean = " + std::to_string(mean)
103  + "+/-" + std::to_string(stdev) + " Max = " + std::to_string(max);
104 #else
105  {
106  std::stringstream ss;
107  ss << "Gradient along the horizontal axis. Mean = " << mean<< "+/-" << stdev<< " Max = " << max;
108  title = ss.str();
109  }
110 #endif
111  vpImageConvert::convert(dIy, dIy_uchar);
112  drawingHelpers::display(dIy_uchar, title);
113 }
114 
115 void usage(const std::string &softName, int gaussianKernelSize, float gaussianStdev, float lowerThresh, float upperThresh,
116  int apertureSize, vpImageFilter::vpCannyFilteringAndGradientType filteringType,
117  float lowerThreshRatio, float upperThreshRatio, vpImageFilter::vpCannyBackendType backend)
118 {
119  std::cout << "NAME" << std::endl;
120  std::cout << softName << ": software to test the vpCannyEdgeComputation class and vpImageFilter::canny method" << std::endl;
121  std::cout << "SYNOPSIS" << std::endl;
122  std::cout << "\t" << softName
123  << " [-i, --image <pathToImg>]"
124  << " [-g, --gradient <kernelSize stdev>]"
125  << " [-t, --thresh <lowerThresh upperThresh>]"
126  << " [-a, --aperture <apertureSize>]"
127  << " [-f, --filter <filterName>]"
128  << " [-r, --ratio <lowerThreshRatio upperThreshRatio>]"
129  << " [-b, --backend <backendName>]"
130  << " [-h, --help]" << std::endl
131  << std::endl;
132  std::cout << "DESCRIPTION" << std::endl;
133  std::cout << "\t-i, --image <pathToImg>" << std::endl
134  << "\t\tPermits to load an image on which will be tested the vpCanny class." << std::endl
135  << "\t\tWhen empty uses a simulated image." << std::endl
136  << std::endl;
137  std::cout << "\t-g, --gradient <kernelSize stdev>" << std::endl
138  << "\t\tPermits to compute the gradients of the image outside the vpCanny class." << std::endl
139  << "\t\tFirst parameter is the size of the Gaussian kernel used to compute the gradients." << std::endl
140  << "\t\tSecond parameter is the standard deviation of the Gaussian kernel used to compute the gradients." << std::endl
141  << "\t\tDefault: " << gaussianKernelSize << " " << gaussianStdev << std::endl
142  << std::endl;
143  std::cout << "\t-t, --thresh <lowerThresh upperThresh>" << std::endl
144  << "\t\tPermits to set the lower and upper thresholds of the vpCanny class." << std::endl
145  << "\t\tFirst parameter is the lower threshold." << std::endl
146  << "\t\tSecond parameter is the upper threshold." << std::endl
147  << "\t\tWhen set to -1 thresholds are computed automatically." << std::endl
148  << "\t\tDefault: " << lowerThresh << " " << upperThresh << std::endl
149  << std::endl;
150  std::cout << "\t-a, --aperture <apertureSize>" << std::endl
151  << "\t\tPermits to set the size of the gradient filter kernel." << std::endl
152  << "\t\tParameter must be odd and positive." << std::endl
153  << "\t\tDefault: " << apertureSize << std::endl
154  << std::endl;
155  std::cout << "\t-f, --filter <filterName>" << std::endl
156  << "\t\tPermits to choose the type of filter to apply to compute the gradient." << std::endl
157  << "\t\tAvailable values: " << vpImageFilter::vpCannyFilteringAndGradientTypeList("<", " | ", ">") << std::endl
158  << "\t\tDefault: " << vpImageFilter::vpCannyFilteringAndGradientTypeToString(filteringType) << std::endl
159  << std::endl;
160  std::cout << "\t-r, --ratio <lowerThreshRatio upperThreshRatio>" << std::endl
161  << "\t\tPermits to set the lower and upper thresholds ratio of the vpCanny class." << std::endl
162  << "\t\tFirst parameter is the lower threshold ratio." << std::endl
163  << "\t\tSecond parameter is the upper threshold ratio." << std::endl
164  << "\t\tDefault: " << lowerThreshRatio << " " << upperThreshRatio << std::endl
165  << std::endl;
166  std::cout << "\t-b, --backend <backendName>" << std::endl
167  << "\t\tPermits to use the vpImageFilter::canny method for comparison." << std::endl
168  << "\t\tAvailable values: " << vpImageFilter::vpCannyBackendTypeList("<", " | ", ">") << std::endl
169  << "\t\tDefault: " << vpImageFilter::vpCannyBackendTypeToString(backend) << std::endl
170  << std::endl;
171  std::cout << "\t-h, --help" << std::endl
172  << "\t\tPermits to display the different arguments this software handles." << std::endl
173  << std::endl;
174 }
175 
176 int main(int argc, const char *argv[])
177 {
178  std::string opt_img;
179  bool opt_gradientOutsideClass = false;
180  bool opt_useVpImageFilterCanny = false;
181  int opt_gaussianKernelSize = 3;
182  int opt_apertureSize = 3;
184  float opt_gaussianStdev = 1.;
185  float opt_lowerThresh = -1.;
186  float opt_upperThresh = -1.;
187  float opt_lowerThreshRatio = 0.6f;
188  float opt_upperThreshRatio = 0.8f;
190  for (int i = 1; i < argc; i++) {
191  std::string argv_str = std::string(argv[i]);
192  if ((argv_str == "-i" || argv_str == "--image") && i + 1 < argc) {
193  opt_img = std::string(argv[i + 1]);
194  i++;
195  }
196  else if ((argv_str == "-g" || argv_str == "--gradient") && i + 2 < argc) {
197  opt_gradientOutsideClass = true;
198  opt_gaussianKernelSize = atoi(argv[i + 1]);
199  opt_gaussianStdev = static_cast<float>(atof(argv[i + 2]));
200  i += 2;
201  }
202  else if ((argv_str == "-t" || argv_str == "--thresh") && i + 2 < argc) {
203  opt_lowerThresh = static_cast<float>(atof(argv[i + 1]));
204  opt_upperThresh = static_cast<float>(atof(argv[i + 2]));
205  i += 2;
206  }
207  else if ((argv_str == "-a" || argv_str == "--aperture") && i + 1 < argc) {
208  opt_apertureSize = std::atoi(argv[i + 1]);
209  i++;
210  }
211  else if ((argv_str == "-f" || argv_str == "--filter") && i + 1 < argc) {
212  opt_filteringType = vpImageFilter::vpCannyFilteringAndGradientTypeFromString(std::string(argv[i + 1]));
213  i++;
214  }
215  else if ((argv_str == "-r" || argv_str == "--ratio") && i + 2 < argc) {
216  opt_lowerThreshRatio = static_cast<float>(std::atof(argv[i + 1]));
217  opt_upperThreshRatio = static_cast<float>(std::atof(argv[i + 2]));
218  i += 2;
219  }
220  else if ((argv_str == "-b" || argv_str == "--backend") && i + 1 < argc) {
221  opt_useVpImageFilterCanny = true;
222  opt_backend = vpImageFilter::vpCannyBackendTypeFromString(std::string(argv[i+1]));
223  i++;
224  }
225  else if (argv_str == "-h" || argv_str == "--help") {
226  usage(std::string(argv[0]), opt_gaussianKernelSize, opt_gaussianStdev, opt_lowerThresh, opt_upperThresh,
227  opt_apertureSize, opt_filteringType, opt_lowerThreshRatio, opt_upperThreshRatio, opt_backend);
228  return EXIT_SUCCESS;
229  }
230  else {
231  std::cerr << "Argument \"" << argv_str << "\" is unknown." << std::endl;
232  return EXIT_FAILURE;
233  }
234  }
235 
236  std::string configAsTxt("Canny Configuration:\n");
237  configAsTxt += "\tFiltering + gradient operators = " + vpImageFilter::vpCannyFilteringAndGradientTypeToString(opt_filteringType) + "\n";
238 #if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
239  configAsTxt += "\tGaussian filter kernel size = " + std::to_string(opt_gaussianKernelSize) + "\n";
240  configAsTxt += "\tGaussian filter standard deviation = " + std::to_string(opt_gaussianStdev) + "\n";
241  configAsTxt += "\tGradient filter kernel size = " + std::to_string(opt_apertureSize) + "\n";
242  configAsTxt += "\tCanny edge filter thresholds = [" + std::to_string(opt_lowerThresh) + " ; " + std::to_string(opt_upperThresh) + "]\n";
243  configAsTxt += "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" + std::to_string(opt_lowerThreshRatio) + " ; " + std::to_string(opt_upperThreshRatio) + "]\n";
244 #else
245  {
246  std::stringstream ss;
247  ss << "\tGaussian filter kernel size = " << opt_gaussianKernelSize << "\n";
248  ss << "\tGaussian filter standard deviation = " << opt_gaussianStdev << "\n";
249  ss << "\tGradient filter kernel size = " << opt_apertureSize << "\n";
250  ss << "\tCanny edge filter thresholds = [" << opt_lowerThresh << " ; " << opt_upperThresh << "]\n";
251  ss << "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" << opt_lowerThreshRatio << " ; " << opt_upperThreshRatio << "]\n";
252  configAsTxt += ss.str();
253  }
254 #endif
255  std::cout << configAsTxt << std::endl;
256 
257  vpCannyEdgeDetection cannyDetector(opt_gaussianKernelSize, opt_gaussianStdev, opt_apertureSize,
258  opt_lowerThresh, opt_upperThresh, opt_lowerThreshRatio, opt_upperThreshRatio,
259  opt_filteringType);
260  vpImage<unsigned char> I_canny_input, I_canny_visp, dIx_uchar, dIy_uchar, I_canny_imgFilter;
261  if (!opt_img.empty()) {
262  // Detection on the user image
263  vpImageIo::read(I_canny_input, opt_img);
264  }
265  else {
266  // Detection on a fake image of a square
267  I_canny_input.resize(500, 500, 0);
268  for (unsigned int r = 150; r < 350; r++) {
269  for (unsigned int c = 150; c < 350; c++) {
270  I_canny_input[r][c] = 125;
271  }
272  }
273  }
274 
275  // Initialization of the displays
276  I_canny_visp = I_canny_imgFilter = dIx_uchar = dIy_uchar = I_canny_input;
277  vpImage<unsigned char> *p_dIx = nullptr, *p_dIy = nullptr, *p_IcannyImgFilter = nullptr;
278 
279  if (opt_gradientOutsideClass) {
280  p_dIx = &dIx_uchar;
281  p_dIy = &dIy_uchar;
282  }
283 
284  if (opt_useVpImageFilterCanny) {
285  p_IcannyImgFilter = &I_canny_imgFilter;
286  }
287  drawingHelpers::init(I_canny_input, I_canny_visp, p_dIx, p_dIy, p_IcannyImgFilter);
288 
289  // Computing the gradient outside the vpCannyEdgeDetection class if asked
290  if (opt_gradientOutsideClass) {
291  setGradientOutsideClass(I_canny_input, opt_gaussianKernelSize, opt_gaussianStdev, cannyDetector, opt_apertureSize,
292  opt_filteringType, dIx_uchar, dIy_uchar);
293  }
294  I_canny_visp = cannyDetector.detect(I_canny_input);
295  float mean, max, stdev;
296  computeMeanMaxStdev(I_canny_input, mean, max, stdev);
297 #if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
298  std::string title("Input of the Canny edge detector. Mean = " + std::to_string(mean) + "+/-" + std::to_string(stdev) + " Max = " + std::to_string(max));
299 #else
300  std::string title;
301  {
302  std::stringstream ss;
303  ss << "Input of the Canny edge detector. Mean = " << mean << "+/-" << stdev << " Max = " << max;
304  title = ss.str();
305  }
306 #endif
307  drawingHelpers::display(I_canny_input, title);
308  drawingHelpers::display(I_canny_visp, "Canny results on image " + opt_img);
309 
310  if (opt_useVpImageFilterCanny) {
311  float cannyThresh = opt_upperThresh;
312  float lowerThresh(opt_lowerThresh);
313  vpImageFilter::canny(I_canny_input, I_canny_imgFilter, opt_gaussianKernelSize, lowerThresh, cannyThresh,
314  opt_apertureSize, opt_gaussianStdev, opt_lowerThreshRatio, opt_upperThreshRatio, true,
315  opt_backend, opt_filteringType);
316  drawingHelpers::display(I_canny_imgFilter, "Canny results with \"" + vpImageFilter::vpCannyBackendTypeToString(opt_backend) + "\" backend");
317  }
318 
319  drawingHelpers::waitForClick(I_canny_input, true);
320  return EXIT_SUCCESS;
321 }
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 setGradients(const vpImage< float > &dIx, const vpImage< float > &dIy)
Set the Gradients of the image that will be processed.
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)
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.
static vpCannyFilteringAndGradientType vpCannyFilteringAndGradientTypeFromString(const std::string &name)
Cast a string into a vpImageFilter::vpCannyFilteringAndGradientType.
static std::string vpCannyFilteringAndGradientTypeToString(const vpCannyFilteringAndGradientType &type)
Cast a vpImageFilter::vpCannyFilteringAndGradientType into a string, to know its name.
vpCannyBackendType
Canny filter backends for the edge detection operations.
@ CANNY_VISP_BACKEND
Use ViSP.
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 vpCannyFilteringAndGradientTypeList(const std::string &pref="<", const std::string &sep=" , ", const std::string &suf=">")
Get the list of available vpCannyFilteringAndGradientType.
static vpCannyBackendType vpCannyBackendTypeFromString(const std::string &name)
Cast a string into a vpImageFilter::vpCannyBackendTypeToString.
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 void read(vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
Definition: vpImageIo.cpp:143
Definition of the vpImage class member functions.
Definition: vpImage.h:69
void resize(unsigned int h, unsigned int w)
resize the image : Image initialization
Definition: vpImage.h:783
unsigned int getCols() const
Definition: vpImage.h:175
unsigned int getRows() const
Definition: vpImage.h:215
bool waitForClick(const vpImage< unsigned char > &I, const bool &blockingMode)
Catch the user clicks to know if the user wants to stop the program.
void display(vpImage< unsigned char > &I, const std::string &title)
Display a gray-scale image.
void init(vpImage< unsigned char > &Iinput, vpImage< unsigned char > &IcannyVisp, vpImage< unsigned char > *p_dIx, vpImage< unsigned char > *p_dIy, vpImage< unsigned char > *p_IcannyimgFilter)
Initialize the different displays.