Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpCannyEdgeDetection.h
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2025 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#ifndef VP_CANNY_EDGE_DETECTION_H
32#define VP_CANNY_EDGE_DETECTION_H
33
34// System includes
35#include <map>
36#include <vector>
37#include <iostream>
38#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
39#include <sys/resource.h> // To dynamically change the stack size
40#endif
41
42// ViSP include
43#include <visp3/core/vpConfig.h>
44#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
45#include <visp3/core/vpHSV.h>
46#endif
47#include <visp3/core/vpImage.h>
48#include <visp3/core/vpImageFilter.h>
49#include <visp3/core/vpRGBa.h>
50
51// 3rd parties include
52#ifdef VISP_HAVE_NLOHMANN_JSON
53#include VISP_NLOHMANN_JSON(json.hpp)
54#endif
55
66class VISP_EXPORT vpCannyEdgeDetection
67{
68
69public:
72
77
100 vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev, const unsigned int &sobelAperture,
101 const float &lowerThreshold = -1.f, const float &upperThreshold = -1.f,
102 const float &lowerThresholdRatio = 0.6f, const float &upperThresholdRatio = 0.8f,
104 const bool &storeEdgePoints = false, const int &nbThread = -1);
105
115 void reinit();
116
117 // // Configuration from files
118#ifdef VISP_HAVE_NLOHMANN_JSON
124 vpCannyEdgeDetection(const std::string &jsonPath);
125
133 void initFromJSON(const std::string &jsonPath);
134
142 friend void from_json(const nlohmann::json &j, vpCannyEdgeDetection &detector);
143
150 friend void to_json(nlohmann::json &j, const vpCannyEdgeDetection &detector);
151#endif
153
156#ifdef HAVE_OPENCV_CORE
166 vpImage<unsigned char> detect(const cv::Mat &cv_I);
167#endif
168
179
180#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
181 template <typename ArithmeticType, bool useFullScale>
183 {
184 // // Step 1 and 2: filter the image and compute the gradient, if not given by the user
185 if (!m_areGradientAvailable) {
187 vpImageFilter::gaussianBlur(Ihsv, Iblur, m_gaussianKernelSize, m_gaussianStdev, true, mp_mask);
188 vpImageFilter::gradientFilter(Iblur, m_dIx, m_dIy, m_nbThread, mp_mask, m_filteringAndGradientType);
189 }
190 m_areGradientAvailable = false; // Reset for next call
191
192 // // Step 3: edge thining
193 float upperThreshold = m_upperThreshold;
194 float lowerThreshold = m_lowerThreshold;
195 if (upperThreshold < 0) {
196 upperThreshold = vpImageFilter::computeCannyThreshold(Ihsv, lowerThreshold, &m_dIx, &m_dIy, m_gaussianKernelSize,
197 m_gaussianStdev, m_lowerThresholdRatio,
198 m_upperThresholdRatio, m_filteringAndGradientType, mp_mask);
199 }
200 else if (m_lowerThreshold < 0) {
201 // Applying Canny recommendation to have the upper threshold 3 times greater than the lower threshold.
202 lowerThreshold = m_upperThreshold / 3.f;
203 }
204 // To ensure that if lowerThreshold = 0, we reject null gradient points
205 lowerThreshold = std::max<float>(lowerThreshold, std::numeric_limits<float>::epsilon());
206
207 step3to5(Ihsv.getHeight(), Ihsv.getWidth(), lowerThreshold, upperThreshold);
208 return m_edgeMap;
209 }
210#endif
211
222
225
231 {
232 m_filteringAndGradientType = type;
233 initGradientFilters();
234 }
235
242 inline void setGradients(const vpImage<float> &dIx, const vpImage<float> &dIy)
243 {
244 m_dIx = dIx;
245 m_dIy = dIy;
246 m_areGradientAvailable = true;
247 }
248
260 inline void setCannyThresholds(const float &lowerThresh, const float &upperThresh)
261 {
262 m_lowerThreshold = lowerThresh;
263 m_upperThreshold = upperThresh;
264 }
265
278 inline void setCannyThresholdsRatio(const float &lowerThreshRatio, const float &upperThreshRatio)
279 {
280 m_lowerThresholdRatio = lowerThreshRatio;
281 m_upperThresholdRatio = upperThreshRatio;
282 }
283
292 inline void setGaussianFilterParameters(const int &kernelSize, const float &stdev)
293 {
294 m_gaussianKernelSize = kernelSize;
295 m_gaussianStdev = stdev;
296 initGaussianFilters();
297 }
298
304 inline void setGradientFilterAperture(const unsigned int &apertureSize)
305 {
306 m_gradientFilterKernelSize = apertureSize;
307 initGradientFilters();
308 }
309
320 inline void setMask(const vpImage<bool> *p_mask)
321 {
322 mp_mask = p_mask;
323 }
324
340#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
341 inline void setMinimumStackSize(const rlim_t &requiredStackSize)
342 {
343 m_minStackSize = requiredStackSize;
344 }
345#else
346 inline void setMinimumStackSize(const unsigned int &requiredStackSize)
347 {
348 (void)requiredStackSize;
349 static bool hasNotBeenDisplayed = true;
350 if (hasNotBeenDisplayed) {
351 std::cerr << "vpCannyEdgeDetection::setStackSize() has no effect on non-POSIX systems. The stack size is defined during compilation." << std::endl;
352 hasNotBeenDisplayed = false;
353 }
354 }
355#endif
356
357 inline void setNbThread(const int &maxNbThread)
358 {
359#ifdef VISP_HAVE_OPENMP
360 int nbThread = maxNbThread;
361 if (nbThread < 0) {
362 nbThread = omp_get_max_threads();
363 }
364 m_nbThread = nbThread;
365#else
366 (void)maxNbThread;
367 m_nbThread = 1;
368 std::cout << "[WARNING] OpenMP is not available, setting the number of threads is ignored." << std::endl;
369#endif
370 }
371
377 inline void setStoreEdgePoints(const bool &storeEdgePoints)
378 {
379 m_storeListEdgePoints = storeEdgePoints;
380 }
381
382
385
390 inline const std::vector<vpImagePoint> &getEdgePointsList() const
391 {
392 if (!m_storeListEdgePoints) {
393 throw(vpException(vpException::fatalError, "Asking for the edge-points list while not asking to store it"));
394 }
395 return m_edgePointsList;
396 }
397
406#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
407 inline rlim_t getMinimumStackSize() const
408 {
409 return m_minStackSize;
410 }
411#else
412 inline unsigned int getMinimumStackSize() const
413 {
414 const unsigned int limit = 65532000;
415 return limit;
416 }
417#endif
418
424 const vpImage<float> &getGIx() const
425 {
426 return m_dIx;
427 }
428
434 const vpImage<float> &getGIy() const
435 {
436 return m_dIy;
437 }
438
445 {
446 return m_edgeMap;
447 }
448
450private:
451 typedef enum EdgeType
452 {
453 STRONG_EDGE,
454 WEAK_EDGE,
455 ON_CHECK,
456 NOT_EDGE
457 } EdgeType;
458
459 // Filtering + gradient methods choice
460 vpImageFilter::vpCannyFilteringAndGradientType m_filteringAndGradientType;
462
463 int m_nbThread;
464
465 // // Gaussian smoothing attributes
466 int m_gaussianKernelSize;
467 float m_gaussianStdev;
468 vpArray2D<float> m_fg;
469
470 // // Gradient computation attributes
471 bool m_areGradientAvailable;
472 unsigned int m_gradientFilterKernelSize;
473 vpArray2D<float> m_gradientFilterX;
474 vpArray2D<float> m_gradientFilterY;
475 vpImage<float> m_dIx;
476 vpImage<float> m_dIy;
477
478 // // Edge thining attributes
479 std::vector<std::pair<unsigned int, float> > m_edgeCandidateAndGradient;
480
481 // // Hysteresis thresholding attributes
482 float m_lowerThreshold;
484 float m_lowerThresholdRatio;
486 float m_upperThreshold;
487 float m_upperThresholdRatio;
489
490 // // Edge tracking attributes
491#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
492 rlim_t m_minStackSize;
493#endif
494 bool m_storeListEdgePoints;
495 std::vector<unsigned int> m_activeEdgeCandidates;
496 vpImage<EdgeType> m_edgePointsCandidates;
498 vpImage<unsigned char> m_edgeMap;
499 std::vector<vpImagePoint> m_edgePointsList;
500 const vpImage<bool> *mp_mask;
501
502 float getGradientOrientation(const vpImage<float> &dIx, const vpImage<float> &dIy, const int &iter);
503
504 void getInterpolWeightsAndOffsets(const float &gradientOrientation, float &alpha, float &beta, const int &nbCols,
505 int &dRowGradAlpha, int &dRowGradBeta, int &dColGradAlpha, int &dColGradBeta);
506
507 float getManhattanGradient(const vpImage<float> &dIx, const vpImage<float> &dIy, const int &iter);
508
511
514 void initGaussianFilters();
515
519 void initGradientFilters();
521
529 void computeFilteringAndGradient(const vpImage<unsigned char> &I);
530
540 void step3to5(const unsigned int &height, const unsigned int &width, const float &lowerThreshold, const float &upperThreshold);
541
549 void performEdgeThinning(const float &lowerThreshold);
550
564 void performHysteresisThresholding(const float &lowerThreshold, const float &upperThreshold);
565
573 bool recursiveSearchForStrongEdge(const unsigned int &coordinates);
574
581 void performEdgeTracking();
583};
584
585#ifdef VISP_HAVE_NLOHMANN_JSON
586inline void from_json(const nlohmann::json &j, vpCannyEdgeDetection &detector)
587{
588 std::string filteringAndGradientName = vpImageFilter::vpCannyFiltAndGradTypeToStr(detector.m_filteringAndGradientType);
589 filteringAndGradientName = j.value("filteringAndGradientType", filteringAndGradientName);
590 detector.m_filteringAndGradientType = vpImageFilter::vpCannyFiltAndGradTypeFromStr(filteringAndGradientName);
591 detector.m_gaussianKernelSize = j.value("gaussianSize", detector.m_gaussianKernelSize);
592 detector.m_gaussianStdev = j.value("gaussianStdev", detector.m_gaussianStdev);
593 detector.m_lowerThreshold = j.value("lowerThreshold", detector.m_lowerThreshold);
594 detector.m_lowerThresholdRatio = j.value("lowerThresholdRatio", detector.m_lowerThresholdRatio);
595 detector.m_gradientFilterKernelSize = j.value("gradientFilterKernelSize", detector.m_gradientFilterKernelSize);
596 detector.m_upperThreshold = j.value("upperThreshold", detector.m_upperThreshold);
597 detector.m_upperThresholdRatio = j.value("upperThresholdRatio", detector.m_upperThresholdRatio);
598 detector.m_nbThread = j.value("nbThread", detector.m_nbThread);
599 detector.reinit();
600}
601
602inline void to_json(nlohmann::json &j, const vpCannyEdgeDetection &detector)
603{
604 std::string filteringAndGradientName = vpImageFilter::vpCannyFiltAndGradTypeToStr(detector.m_filteringAndGradientType);
605 j = nlohmann::json {
606 {"filteringAndGradientType", filteringAndGradientName},
607 {"gaussianSize", detector.m_gaussianKernelSize},
608 {"gaussianStdev", detector.m_gaussianStdev},
609 {"lowerThreshold", detector.m_lowerThreshold},
610 {"lowerThresholdRatio", detector.m_lowerThresholdRatio},
611 {"gradientFilterKernelSize", detector.m_gradientFilterKernelSize},
612 {"upperThreshold", detector.m_upperThreshold},
613 {"upperThresholdRatio", detector.m_upperThresholdRatio},
614 {"nbThread", detector.m_nbThread}
615 };
616}
617#endif
618
619END_VISP_NAMESPACE
620#endif
vpCannyEdgeDetection()
Default constructor of the vpCannyEdgeDetection class. The thresholds used during the hysteresis thre...
vpImage< unsigned char > detect(const vpImage< vpRGBa > &I_color)
Detect the edges in an image. Convert the color image into a gray-scale image.
rlim_t getMinimumStackSize() const
Get the minimum stack size used by the algorithm.
void setMinimumStackSize(const rlim_t &requiredStackSize)
Set the minimum stack size, expressed in bytes, due to the recursive algorithm. If not called,...
void setStoreEdgePoints(const bool &storeEdgePoints)
If set to true, the list of the detected edge-points will be available calling the method vpCannyEdge...
void setFilteringAndGradientType(const vpImageFilter::vpCannyFilteringAndGradientType &type)
Set the Filtering And Gradient operators to apply to the image before the edge detection operation.
const std::vector< vpImagePoint > & getEdgePointsList() const
Get the list of edge-points that have been detected.
void reinit()
Reinitialize the detector:
void setMask(const vpImage< bool > *p_mask)
Set a mask to ignore pixels for which the mask is false.
void setCannyThresholdsRatio(const float &lowerThreshRatio, const float &upperThreshRatio)
Set the lower and upper Canny Thresholds ratio that are used to compute them automatically....
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,...
vpImage< unsigned char > detect(const vpImage< vpHSV< ArithmeticType, useFullScale > > &Ihsv)
void initFromJSON(const std::string &jsonPath)
Initialize all the algorithm parameters using the JSON file whose path is jsonPath....
void setNbThread(const int &maxNbThread)
const vpImage< float > & getGIx() const
Get the horizontal gradient.
const vpImage< float > & getGIy() const
Get the vertical gradient.
void setGradients(const vpImage< float > &dIx, const vpImage< float > &dIy)
Set the Gradients of the image that will be processed.
friend void to_json(nlohmann::json &j, const vpCannyEdgeDetection &detector)
Parse a vpCannyEdgeDetection object into JSON format.
const vpImage< unsigned char > & getEdgeMap() const
Get the final edge-map.
void setCannyThresholds(const float &lowerThresh, const float &upperThresh)
Set the lower and upper Canny Thresholds used to qualify the edge point candidates....
void setGradientFilterAperture(const unsigned int &apertureSize)
Set the parameters of the gradient filter (Sobel or Scharr) kernel size filters.
void setGaussianFilterParameters(const int &kernelSize, const float &stdev)
Set the Gaussian Filters kernel size and standard deviation and initialize the aforementioned filters...
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ fatalError
Fatal error.
Definition vpException.h:72
Class implementing the HSV pixel format.
Definition vpHSV.h:135
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.6f, const float &upperThresholdRatio=0.8f, const vpCannyFilteringAndGradientType &filteringType=CANNY_GBLUR_SOBEL_FILTERING)
Compute the upper Canny edge filter threshold, using Gaussian blur + Sobel or + Scharr operators to c...
static std::string vpCannyFiltAndGradTypeToStr(const vpCannyFilteringAndGradientType &type)
Cast a vpImageFilter::vpCannyFilteringAndGradientType into a string, to know its name.
static void gradientFilter(const vpImage< vpHSV< ArithmeticType, useFullScale > > &I, vpImage< FilterType > &GIx, vpImage< FilterType > &GIy, const int &nbThread=-1, const vpImage< bool > *p_mask=nullptr, const vpImageFilter::vpCannyFilteringAndGradientType &type=CANNY_GBLUR_SCHARR_FILTERING)
Compute the horizontal and vertical gradients for HSV images.
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 void gaussianBlur(const vpImage< ImageType > &I, vpImage< OutputType > &GI, unsigned int size=7, FilterType sigma=0., bool normalize=true, const vpImage< bool > *p_mask=nullptr)
static vpCannyFilteringAndGradientType vpCannyFiltAndGradTypeFromStr(const std::string &name)
Cast a string into a vpImageFilter::vpCannyFilteringAndGradientType.
Definition of the vpImage class member functions.
Definition vpImage.h:131