diff --git a/tutorial/image/CMakeLists.txt b/tutorial/image/CMakeLists.txt index 370c300e55..7fd6ad0b04 100644 --- a/tutorial/image/CMakeLists.txt +++ b/tutorial/image/CMakeLists.txt @@ -43,6 +43,11 @@ foreach(cpp ${tutorial_cpp}) endif() endforeach() +visp_add_target(tutorial-canny.cpp drawingHelpers.cpp) +if(COMMAND visp_add_dependency) + visp_add_dependency(tutorial-canny.cpp "tutorials") +endif() + # Copy the data files to the same location than the target foreach(data ${tutorial_data}) visp_copy_data(tutorial-viewer.cpp ${data}) diff --git a/tutorial/image/drawingHelpers.cpp b/tutorial/image/drawingHelpers.cpp new file mode 100644 index 0000000000..9345b1b6aa --- /dev/null +++ b/tutorial/image/drawingHelpers.cpp @@ -0,0 +1,90 @@ +/* + * 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 visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "drawingHelpers.h" + +#include + +#if defined(VISP_HAVE_X11) + vpDisplayX drawingHelpers::d; +#elif defined(HAVE_OPENCV_HIGHGUI) + vpDisplayOpenCV drawingHelpers::d; +#elif defined(VISP_HAVE_GTK) + vpDisplayGTK drawingHelpers::d; +#elif defined(VISP_HAVE_GDI) + vpDisplayGDI drawingHelpers::d; +#elif defined(VISP_HAVE_D3D9) + vpDisplayD3D drawingHelpers::d; +#endif + +vpImage drawingHelpers::I_disp; + +bool drawingHelpers::display(vpImage &I, const std::string &title, const bool &blockingMode) +{ + I_disp = I; + d.init(I_disp); + vpDisplay::setTitle(I_disp, title.c_str()); + + vpDisplay::display(I_disp); + vpDisplay::displayText(I_disp, 15, 15, "Left click to continue...", vpColor::red); + vpDisplay::displayText(I_disp, 35, 15, "Right click to stop...", vpColor::red); + vpDisplay::flush(I_disp); + vpMouseButton::vpMouseButtonType button; + vpDisplay::getClick(I_disp, button, blockingMode); + bool hasToContinue = true; + if (button == vpMouseButton::button3) + { + // Right click => stop the program + hasToContinue = false; + } + + return hasToContinue; +} + +bool drawingHelpers::display(vpImage &D, const std::string &title, const bool &blockingMode) +{ + vpImage I; // Image to display + vpImageConvert::convert(D, I); + return display(I, title, blockingMode); +} + +bool drawingHelpers::display(vpImage &D, const std::string &title, const bool &blockingMode) +{ + vpImage I; // Image to display + vpImageConvert::convert(D, I); + return display(I, title, blockingMode); +} + +bool drawingHelpers::display(vpImage &F, const std::string &title, const bool &blockingMode) +{ + vpImage I; // Image to display + vpImageConvert::convert(F, I); + return display(I, title, blockingMode); +} diff --git a/tutorial/image/drawingHelpers.h b/tutorial/image/drawingHelpers.h new file mode 100644 index 0000000000..ad3b4e57aa --- /dev/null +++ b/tutorial/image/drawingHelpers.h @@ -0,0 +1,103 @@ +/* + * 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 visp@inria.fr + * + * 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 _drawingHelpers_h_ +#define _drawingHelpers_h_ + +#include +#include +#include +#include + +namespace drawingHelpers +{ + #if defined(VISP_HAVE_X11) + extern vpDisplayX d; + #elif defined(HAVE_OPENCV_HIGHGUI) + extern vpDisplayOpenCV d; + #elif defined(VISP_HAVE_GTK) + extern vpDisplayGTK d; + #elif defined(VISP_HAVE_GDI) + extern vpDisplayGDI d; + #elif defined(VISP_HAVE_D3D9) + extern vpDisplayD3D d; + #endif + + extern vpImage I_disp; /*!< Displayed image.*/ + + /** + * \brief Display a RGB image and catch the user clicks to know if + * the user wants to stop the program. + * + * \param[out] I The RGB image to display. + * \param[in] title The title of the window. + * \param[in] blockingMode If true, wait for a click to switch to the next image. + * \return true The user wants to continue the application. + * \return false The user wants to stop the application. + */ + bool display(vpImage &I, const std::string &title, const bool &blockingMode); + + /** + * \brief Display a gray-scale image and catch the user clicks to know if + * the user wants to stop the program. + * + * \param[out] I The gray-scale image to display. + * \param[in] title The title of the window. + * \param[in] blockingMode If true, wait for a click to switch to the next image. + * \return true The user wants to continue the application. + * \return false The user wants to stop the application. + */ + bool display(vpImage &I, const std::string &title, const bool &blockingMode); + + /** + * \brief Display a double precision image and catch the user clicks to know if + * the user wants to stop the program. + * + * \param[out] I The double precision image to display. + * \param[in] title The title of the window. + * \param[in] blockingMode If true, wait for a click to switch to the next image. + * \return true The user wants to continue the application. + * \return false The user wants to stop the application. + */ + bool display(vpImage &D, const std::string &title, const bool &blockingMode); + + /** + * \brief Display a floating-point precision image and catch the user clicks to know if + * the user wants to stop the program. + * + * \param[out] I The floating-point precision image to display. + * \param[in] title The title of the window. + * \param[in] blockingMode If true, wait for a click to switch to the next image. + * \return true The user wants to continue the application. + * \return false The user wants to stop the application. + */ + bool display(vpImage &F, const std::string &title, const bool &blockingMode); +} + +#endif diff --git a/tutorial/image/tutorial-canny.cpp b/tutorial/image/tutorial-canny.cpp new file mode 100644 index 0000000000..10aabe4f85 --- /dev/null +++ b/tutorial/image/tutorial-canny.cpp @@ -0,0 +1,296 @@ +/**************************************************************************** + * + * 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 visp@inria.fr + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE + * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * +*****************************************************************************/ +#include + +#include +#include +#include + +#ifdef HAVE_OPENCV_IMGPROC +#include +#endif + +#include "drawingHelpers.h" + +template +void computeMeanMaxStdev(const vpImage &I, float &mean, float &max, float &stdev) +{ + max = std::numeric_limits::epsilon(); + mean = 0.; + stdev = 0.; + unsigned int nbRows = I.getRows(); + unsigned int nbCols = I.getCols(); + float scale = 1. / ((float)nbRows * (float)nbCols); + for (unsigned int r = 0; r < nbRows; r++) { + for (unsigned int c = 0; c < nbCols; c++) { + mean += I[r][c]; + max = std::max(max, static_cast(I[r][c])); + } + } + mean *= scale; + for (unsigned int r = 0; r < nbRows; r++) { + for (unsigned int c = 0; c < nbCols; c++) { + stdev += (I[r][c] - mean) * (I[r][c] - mean); + } + } + stdev *= scale; + stdev = std::sqrt(stdev); +} + +void setGradientOutsideClass(const vpImage &I, const int &gaussianKernelSize, const float &gaussianStdev, vpCannyEdgeDetection &cannyDetector, + const unsigned int apertureSize, const vpImageFilter::vpCannyFilteringAndGradientType &filteringType) +{ + // Get the Gaussian blur kernel + if ((gaussianKernelSize % 2) == 0) { + throw(vpException(vpException::badValue, "The Gaussian kernel size should be odd")); + } + vpArray2D fg(1, (gaussianKernelSize + 1)/2); + vpImageFilter::getGaussianKernel(fg.data, gaussianKernelSize, gaussianStdev, true); + + // Get the gradient filters kernel + if ((apertureSize % 2) != 1) { + throw vpException(vpException::badValue, "Gradient filters kernel size should be odd."); + } + vpArray2D gradientFilterX(apertureSize, apertureSize); + vpArray2D gradientFilterY(apertureSize, apertureSize); + + auto scaleFilter = [](vpArray2D &filter, const float &scale) { + for (unsigned int r = 0; r < filter.getRows(); r++) { + for (unsigned int c = 0; c < filter.getCols(); c++) { + filter[r][c] = filter[r][c] * scale; + } + }}; + + float scaleX = 1.f; + float scaleY = 1.f; + + if (filteringType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) { + scaleX = vpImageFilter::getSobelKernelX(gradientFilterX.data, (apertureSize - 1)/2); + scaleY = vpImageFilter::getSobelKernelY(gradientFilterY.data, (apertureSize - 1)/2); + } + else if (filteringType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) { + // Compute the Scharr filters + scaleX = vpImageFilter::getScharrKernelX(gradientFilterX.data, (apertureSize - 1)/2); + scaleY = vpImageFilter::getScharrKernelY(gradientFilterY.data, (apertureSize - 1)/2); + } + else { + std::string errMsg = "Error: gradient filtering method \""; + errMsg += vpImageFilter::vpCannyFilteringAndGradientTypeToString(filteringType); + errMsg += "\" has not been implemented yet\n"; + throw vpException(vpException::notImplementedError, errMsg); + } + + scaleFilter(gradientFilterX, scaleX); + scaleFilter(gradientFilterY, scaleY); + + // Perform Gaussian blur + vpImage Iblur; + vpImage GIx; + vpImageFilter::filterX(I, GIx, fg.data, gaussianKernelSize); + vpImageFilter::filterY(GIx, Iblur, fg.data, gaussianKernelSize); + + // Computing the gradients + vpImage dIx, dIy; + vpImageFilter::filter(Iblur, dIx, gradientFilterX); + vpImageFilter::filter(Iblur, dIy, gradientFilterY); + + // Set the gradients of the vpCannyEdgeDetection + cannyDetector.setGradients(dIx, dIy); + + // Display the gradients + float mean, max, stdev; + computeMeanMaxStdev(dIx, mean, max, stdev); + std::string title = "Gradient along the horizontal axis. Mean = " + std::to_string(mean) + + "+/-" + std::to_string(stdev) + " Max = " + std::to_string(max); + drawingHelpers::display(dIx, title, true); + computeMeanMaxStdev(dIy, mean, max, stdev); + title = "Gradient along the horizontal axis. Mean = " + std::to_string(mean) + + "+/-" + std::to_string(stdev) + " Max = " + std::to_string(max); + drawingHelpers::display(dIy, title, true); +} + +void usage(const std::string &softName) +{ + std::cout << "NAME" << std::endl; + std::cout << softName << ": software to test the vpCannyEdgeComputation class and vpImageFilter::canny method" << std::endl; + std::cout << "SYNOPSIS" << std::endl; + std::cout << "\t" << softName + << " [-i, --image ]" + << " [-g, --gradient ]" + << " [-t, --thresh ]" + << " [-a, --aperture ]" + << " [-f, --filter ]" + << " [-r, --ratio ]" + << " [-b, --backend ]" + << " [-h, --help]" + << std::endl; + std::cout << "DESCRIPTION" << std::endl; + std::cout << "\t-i, --image" << std::endl + << "\t\tPermits to load an image on which will be tested the vpCanny class." + << std::endl; + std::cout << "\t-g, --gradient" << std::endl + << "\t\tPermits to compute the gradients of the image outside the vpCanny class." + << "\t\tFirst parameter is the size of the Gaussian kernel used to compute the gradients." + << "\t\tSecond parameter is the standard deviation of the Gaussian kernel used to compute the gradients." + << std::endl; + std::cout << "\t-t, --thresh" << std::endl + << "\t\tPermits to set the lower and upper thresholds of the vpCanny class." + << "\t\tFirst parameter is the lower threshold." + << "\t\tSecond parameter is the upper threshold." + << std::endl; + std::cout << "\t-a, --aperture" << std::endl + << "\t\tPermits to set the size of the gradient filter kernel." + << "\t\tParameter must be odd and positive." + << std::endl; + std::cout << "\t-f, --filter" << std::endl + << "\t\tPermits to choose the type of filter to apply to compute the gradient." + << std::endl; + std::cout << "\t-r, --ratio" << std::endl + << "\t\tPermits to set the lower and upper thresholds ratio of the vpCanny class." + << "\t\tFirst parameter is the lower threshold ratio." + << "\t\tSecond parameter is the upper threshold ratio." + << std::endl; + std::cout << "\t-b, --backend" << std::endl + << "\t\tPermits to use the vpImageFilter::canny method for comparison." + << std::endl; + std::cout << "\t-h, --help" << std::endl + << "\t\tPermits to display the different arguments this software handles." + << std::endl; +} + +int main(int argc, const char *argv[]) +{ + std::string opt_img; + bool opt_gradientOutsideClass = false; + bool opt_useVpImageFilterCanny = false; + int opt_gaussianKernelSize = 3; + int opt_apertureSize = 3; + vpImageFilter::vpCannyFilteringAndGradientType opt_filteringType = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING; + float opt_gaussianStdev = 1.; + float opt_lowerThresh = -1.; + float opt_upperThresh = -1.; + float opt_lowerThreshRatio = 0.6f; + float opt_upperThreshRatio = 0.8f; + vpImageFilter::vpCannyBackendType opt_backend = vpImageFilter::CANNY_VISP_BACKEND; + for (int i = 1; i < argc; i++) { + std::string argv_str = std::string(argv[i]); + if ((argv_str == "-i" || argv_str == "--image") && i + 1 < argc) { + opt_img = std::string(argv[i + 1]); + i++; + } + else if ((argv_str == "-g" || argv_str == "--gradient") && i + 2 < argc) { + opt_gradientOutsideClass = true; + opt_gaussianKernelSize = atoi(argv[i + 1]); + opt_gaussianStdev = atoi(argv[i + 2]); + i += 2; + } + else if ((argv_str == "-t" || argv_str == "--thresh") && i + 2 < argc) { + opt_lowerThresh = atof(argv[i + 1]); + opt_upperThresh = atof(argv[i + 2]); + i += 2; + } + else if ((argv_str == "-a" || argv_str == "--aperture") && i + 1 < argc) { + opt_apertureSize = std::atoi(argv[i + 1]); + i++; + } + else if ((argv_str == "-f" || argv_str == "--filter") && i + 1 < argc) { + opt_filteringType = vpImageFilter::vpCannyFilteringAndGradientTypeFromString(std::string(argv[i + 1])); + i++; + } + else if ((argv_str == "-r" || argv_str == "--ratio") && i + 2 < argc) { + opt_lowerThreshRatio = std::atof(argv[i + 1]); + opt_upperThreshRatio = std::atof(argv[i + 2]); + i += 2; + } + else if ((argv_str == "-b" || argv_str == "--backend") && i + 1 < argc) { + opt_useVpImageFilterCanny = true; + opt_backend = vpImageFilter::vpCannyBackendTypeFromString(std::string(argv[i+1])); + i++; + } + else if (argv_str == "-h" || argv_str == "--help") { + usage(std::string(argv[0])); + return EXIT_SUCCESS; + } + else { + std::cerr << "Argument \"" << argv_str << "\" is unknown." << std::endl; + return EXIT_FAILURE; + } + } + + std::string configAsTxt("Canny Configuration:\n"); + configAsTxt += "\tFiltering + gradient operators = " + vpImageFilter::vpCannyFilteringAndGradientTypeToString(opt_filteringType) + "\n"; + configAsTxt += "\tGaussian filter kernel size = " + std::to_string(opt_gaussianKernelSize) + "\n"; + configAsTxt += "\tGaussian filter standard deviation = " + std::to_string(opt_gaussianStdev) + "\n"; + configAsTxt += "\tGradient filter kernel size = " + std::to_string(opt_apertureSize) + "\n"; + configAsTxt += "\tCanny edge filter thresholds = [" + std::to_string(opt_lowerThresh) + " ; " + std::to_string(opt_upperThresh) + "]\n"; + configAsTxt += "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" + std::to_string(opt_lowerThreshRatio) + " ; " + std::to_string(opt_upperThreshRatio) + "]\n"; + std::cout << configAsTxt << std::endl; + + vpCannyEdgeDetection cannyDetector(opt_gaussianKernelSize, opt_gaussianStdev, opt_apertureSize, + opt_lowerThresh, opt_upperThresh, opt_lowerThreshRatio, opt_upperThreshRatio, + opt_filteringType); + vpImage I_canny_input; + if (!opt_img.empty()) { + // Detection on the user image + vpImageIo::read(I_canny_input, opt_img); + } + else { + // Detection on a fake image of a square + I_canny_input.resize(500, 500, 0); + for (unsigned int r = 150; r < 350; r++) { + for (unsigned int c = 150; c < 350; c++) { + I_canny_input[r][c] = 125; + } + } + } + + if (opt_gradientOutsideClass) { + setGradientOutsideClass(I_canny_input, opt_gaussianKernelSize, opt_gaussianStdev, cannyDetector, opt_apertureSize, opt_filteringType); + } + vpImage I_canny = cannyDetector.detect(I_canny_input); + float mean, max, stdev; + computeMeanMaxStdev(I_canny_input, mean, max, stdev); + std::string title("Input of the Canny edge detector. Mean = " + std::to_string(mean) + "+/-" + std::to_string(stdev) + " Max = " + std::to_string(max)); + drawingHelpers::display(I_canny_input, title, true); + drawingHelpers::display(I_canny, "Canny results on image " + opt_img, true); + + if (opt_useVpImageFilterCanny) { + float cannyThresh = opt_upperThresh; + float lowerThresh(opt_lowerThresh); + vpImageFilter::canny(I_canny_input, I_canny, opt_gaussianKernelSize, lowerThresh, cannyThresh, + opt_apertureSize, opt_gaussianStdev, opt_lowerThreshRatio, opt_upperThreshRatio, + opt_backend, opt_filteringType); + drawingHelpers::display(I_canny, "Canny results with \"" + vpImageFilter::vpCannyBackendTypeToString(opt_backend) + "\" backend", true); + } + + return EXIT_SUCCESS; +}