32 #include <visp3/core/vpCannyEdgeDetection.h>
34 #include <visp3/core/vpImageConvert.h>
36 #if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98)
40 template <
typename FilterType>
41 static void scaleFilter(
42 #ifdef ENABLE_VISP_NAMESPACE
47 const unsigned int nbRows = filter.getRows();
48 const unsigned int nbCols = filter.getCols();
49 for (
unsigned int r = 0; r < nbRows; ++r) {
50 for (
unsigned int c = 0; c < nbCols; ++c) {
51 filter[r][c] = filter[r][c] * scale;
59 #ifdef VISP_HAVE_NLOHMANN_JSON
63 filteringAndGradientName = j.value(
"filteringAndGradientType", filteringAndGradientName);
65 detector.m_gaussianKernelSize = j.value(
"gaussianSize", detector.m_gaussianKernelSize);
66 detector.m_gaussianStdev = j.value(
"gaussianStdev", detector.m_gaussianStdev);
67 detector.m_lowerThreshold = j.value(
"lowerThreshold", detector.m_lowerThreshold);
68 detector.m_lowerThresholdRatio = j.value(
"lowerThresholdRatio", detector.m_lowerThresholdRatio);
69 detector.m_gradientFilterKernelSize = j.value(
"gradientFilterKernelSize", detector.m_gradientFilterKernelSize);
70 detector.m_upperThreshold = j.value(
"upperThreshold", detector.m_upperThreshold);
71 detector.m_upperThresholdRatio = j.value(
"upperThresholdRatio", detector.m_upperThresholdRatio);
78 {
"filteringAndGradientType", filteringAndGradientName},
79 {
"gaussianSize", detector.m_gaussianKernelSize},
80 {
"gaussianStdev", detector.m_gaussianStdev},
81 {
"lowerThreshold", detector.m_lowerThreshold},
82 {
"lowerThresholdRatio", detector.m_lowerThresholdRatio},
83 {
"gradientFilterKernelSize", detector.m_gradientFilterKernelSize},
84 {
"upperThreshold", detector.m_upperThreshold},
85 {
"upperThresholdRatio", detector.m_upperThresholdRatio}
93 : m_filteringAndGradientType(
vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
94 , m_gaussianKernelSize(3)
95 , m_gaussianStdev(1.f)
96 , m_areGradientAvailable(false)
97 , m_gradientFilterKernelSize(3)
98 , m_lowerThreshold(-1.f)
99 , m_lowerThresholdRatio(0.6f)
100 , m_upperThreshold(-1.f)
101 , m_upperThresholdRatio(0.8f)
104 initGaussianFilters();
105 initGradientFilters();
109 ,
const unsigned int &sobelAperture,
const float &lowerThreshold,
const float &upperThreshold
110 ,
const float &lowerThresholdRatio,
const float &upperThresholdRatio
113 : m_filteringAndGradientType(filteringType)
114 , m_gaussianKernelSize(gaussianKernelSize)
115 , m_gaussianStdev(gaussianStdev)
116 , m_areGradientAvailable(false)
117 , m_gradientFilterKernelSize(sobelAperture)
118 , m_lowerThreshold(lowerThreshold)
119 , m_lowerThresholdRatio(lowerThresholdRatio)
120 , m_upperThreshold(upperThreshold)
121 , m_upperThresholdRatio(upperThresholdRatio)
124 initGaussianFilters();
125 initGradientFilters();
128 #ifdef VISP_HAVE_NLOHMANN_JSON
130 using json = nlohmann::json;
140 std::ifstream file(jsonPath);
142 std::stringstream ss;
143 ss <<
"Problem opening file " << jsonPath <<
". Make sure it exists and is readable" << std::endl;
148 j = json::parse(file);
150 catch (json::parse_error &e) {
151 std::stringstream msg;
152 msg <<
"Could not parse JSON file : \n";
153 msg << e.what() << std::endl;
154 msg <<
"Byte position of error: " << e.byte;
159 initGaussianFilters();
160 initGradientFilters();
165 vpCannyEdgeDetection::initGaussianFilters()
167 if ((m_gaussianKernelSize % 2) == 0) {
170 m_fg.
resize(1, (m_gaussianKernelSize + 1) / 2);
175 vpCannyEdgeDetection::initGradientFilters()
177 if ((m_gradientFilterKernelSize % 2) != 1) {
180 m_gradientFilterX.
resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
181 m_gradientFilterY.
resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
183 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
185 unsigned int filter_rows = filter.
getRows();
186 unsigned int filter_col = filter.
getCols();
187 for (
unsigned int r = 0; r < filter_rows; ++r) {
188 for (
unsigned int c = 0; c < filter_col; ++c) {
189 filter[r][c] = filter[r][c] * scale;
208 std::string errMsg =
"[vpCannyEdgeDetection::initGradientFilters] Error: gradient filtering method \"";
210 errMsg +=
"\" has not been implemented yet\n";
214 scaleFilter(m_gradientFilterX, scaleX);
215 scaleFilter(m_gradientFilterY, scaleY);
219 #ifdef HAVE_OPENCV_CORE
242 m_edgeCandidateAndGradient.clear();
243 m_edgePointsCandidates.clear();
246 if (!m_areGradientAvailable) {
247 computeFilteringAndGradient(I);
249 m_areGradientAvailable =
false;
252 float upperThreshold = m_upperThreshold;
253 float lowerThreshold = m_lowerThreshold;
254 if (upperThreshold < 0) {
256 m_gaussianStdev, m_gradientFilterKernelSize, m_lowerThresholdRatio,
257 m_upperThresholdRatio, m_filteringAndGradientType, mp_mask);
259 else if (m_lowerThreshold < 0) {
261 lowerThreshold = m_upperThreshold / 3.f;
264 lowerThreshold = std::max<float>(lowerThreshold, std::numeric_limits<float>::epsilon());
265 performEdgeThinning(lowerThreshold);
268 performHysteresisThresholding(lowerThreshold, upperThreshold);
271 performEdgeTracking();
283 vpImageFilter::filterX<unsigned char, float>(I, GIx, m_fg.
data, m_gaussianKernelSize, mp_mask);
284 vpImageFilter::filterY<float, float>(GIx, Iblur, m_fg.
data, m_gaussianKernelSize, mp_mask);
291 std::string errmsg(
"Currently, the filtering operation \"");
293 errmsg +=
"\" is not handled.";
311 getInterpolWeightsAndOffsets(
const float &gradientOrientation,
312 float &alpha,
float &beta,
313 int &dRowGradAlpha,
int &dRowGradBeta,
314 int &dColGradAlpha,
int &dColGradBeta
317 float thetaMin = 0.f;
318 if (gradientOrientation < M_PI_4_FLOAT) {
325 else if ((gradientOrientation >= M_PI_4_FLOAT) && (gradientOrientation < M_PI_2_FLOAT)) {
327 thetaMin = M_PI_4_FLOAT;
333 else if ((gradientOrientation >= M_PI_2_FLOAT) && (gradientOrientation < (3.f * M_PI_4_FLOAT))) {
335 thetaMin = M_PI_2_FLOAT;
341 else if ((gradientOrientation >= (3.f * M_PI_4_FLOAT)) && (gradientOrientation < M_PI_FLOAT)) {
343 thetaMin = 3.f * M_PI_4_FLOAT;
349 beta = (gradientOrientation - thetaMin) / M_PI_4_FLOAT;
373 float dx = dIx[row][col];
374 float dy = dIy[row][col];
375 grad = std::abs(dx) + std::abs(dy);
394 float gradientOrientation = 0.f;
395 float dx = dIx[row][col];
396 float dy = dIy[row][col];
398 if (std::abs(dx) < std::numeric_limits<float>::epsilon()) {
399 gradientOrientation = M_PI_2_FLOAT;
404 gradientOrientation =
static_cast<float>(std::atan2(-dy, dx));
405 if (gradientOrientation < 0.f) {
406 gradientOrientation += M_PI_FLOAT;
409 return gradientOrientation;
413 vpCannyEdgeDetection::performEdgeThinning(
const float &lowerThreshold)
418 bool ignore_current_pixel =
false;
419 bool grad_lower_threshold =
false;
420 for (
int row = 0; row < nbRows; ++row) {
421 for (
int col = 0; col < nbCols; ++col) {
423 ignore_current_pixel =
false;
424 grad_lower_threshold =
false;
426 if (mp_mask !=
nullptr) {
427 if (!(*mp_mask)[row][col]) {
429 ignore_current_pixel =
true;
434 if (ignore_current_pixel ==
false) {
437 float grad = getManhattanGradient(m_dIx, m_dIy, row, col);
439 if (grad < lowerThreshold) {
441 grad_lower_threshold =
true;
444 if (grad_lower_threshold ==
false) {
448 int dRowAlphaPlus = 0, dRowBetaPlus = 0;
449 int dColAphaPlus = 0, dColBetaPlus = 0;
450 float gradientOrientation = getGradientOrientation(m_dIx, m_dIy, row, col);
451 float alpha = 0.f, beta = 0.f;
452 getInterpolWeightsAndOffsets(gradientOrientation, alpha, beta, dRowAlphaPlus, dRowBetaPlus, dColAphaPlus, dColBetaPlus);
453 int dRowAlphaMinus = -dRowAlphaPlus, dRowBetaMinus = -dRowBetaPlus;
454 int dColAphaMinus = -dColAphaPlus, dColBetaMinus = -dColBetaPlus;
455 float gradAlphaPlus = getManhattanGradient(m_dIx, m_dIy, row + dRowAlphaPlus, col + dColAphaPlus);
456 float gradBetaPlus = getManhattanGradient(m_dIx, m_dIy, row + dRowBetaPlus, col + dColBetaPlus);
457 float gradAlphaMinus = getManhattanGradient(m_dIx, m_dIy, row + dRowAlphaMinus, col + dColAphaMinus);
458 float gradBetaMinus = getManhattanGradient(m_dIx, m_dIy, row + dRowBetaMinus, col + dColBetaMinus);
459 float gradPlus = (alpha * gradAlphaPlus) + (beta * gradBetaPlus);
460 float gradMinus = (alpha * gradAlphaMinus) + (beta * gradBetaMinus);
462 if ((grad >= gradPlus) && (grad >= gradMinus)) {
464 std::pair<unsigned int, unsigned int> bestPixel(row, col);
465 m_edgeCandidateAndGradient[bestPixel] = grad;
474 vpCannyEdgeDetection::performHysteresisThresholding(
const float &lowerThreshold,
const float &upperThreshold)
476 std::map<std::pair<unsigned int, unsigned int>,
float>::iterator it;
477 std::map<std::pair<unsigned int, unsigned int>,
float>::iterator m_edgeCandidateAndGradient_end = m_edgeCandidateAndGradient.end();
478 for (it = m_edgeCandidateAndGradient.begin(); it != m_edgeCandidateAndGradient_end; ++it) {
479 if (it->second >= upperThreshold) {
480 m_edgePointsCandidates[it->first] = STRONG_EDGE;
482 else if ((it->second >= lowerThreshold) && (it->second < upperThreshold)) {
483 m_edgePointsCandidates[it->first] = WEAK_EDGE;
489 vpCannyEdgeDetection::performEdgeTracking()
491 std::map<std::pair<unsigned int, unsigned int>, EdgeType>::iterator it;
492 std::map<std::pair<unsigned int, unsigned int>, EdgeType>::iterator m_edgePointsCandidates_end = m_edgePointsCandidates.end();
493 for (it = m_edgePointsCandidates.begin(); it != m_edgePointsCandidates_end; ++it) {
494 if (it->second == STRONG_EDGE) {
495 m_edgeMap[it->first.first][it->first.second] = 255;
497 else if (it->second == WEAK_EDGE) {
498 if (recursiveSearchForStrongEdge(it->first)) {
499 m_edgeMap[it->first.first][it->first.second] = 255;
506 vpCannyEdgeDetection::recursiveSearchForStrongEdge(
const std::pair<unsigned int, unsigned int> &coordinates)
508 bool hasFoundStrongEdge =
false;
511 m_edgePointsCandidates[coordinates] = ON_CHECK;
512 bool test_row =
false;
513 bool test_col =
false;
514 bool test_drdc =
false;
515 bool edge_in_image_limit =
false;
517 while ((dr <= 1) && (!hasFoundStrongEdge)) {
519 while ((dc <= 1) && (!hasFoundStrongEdge)) {
521 edge_in_image_limit =
false;
523 int idRow = dr +
static_cast<int>(coordinates.first);
524 idRow = std::max<int>(idRow, 0);
525 int idCol = dc +
static_cast<int>(coordinates.second);
526 idCol = std::max<int>(idCol, 0);
529 test_row = (idRow < 0) || (idRow >= nbRows);
530 test_col = (idCol < 0) || (idCol >= nbCols);
531 test_drdc = (dr == 0) && (dc == 0);
532 if (test_row || test_col || test_drdc) {
533 edge_in_image_limit =
true;
536 if (edge_in_image_limit ==
false) {
539 std::pair<unsigned int, unsigned int> key_candidate(idRow, idCol);
541 EdgeType type_candidate = m_edgePointsCandidates.at(key_candidate);
542 if (type_candidate == STRONG_EDGE) {
544 hasFoundStrongEdge =
true;
546 else if (type_candidate == WEAK_EDGE) {
548 hasFoundStrongEdge = recursiveSearchForStrongEdge(key_candidate);
559 if (hasFoundStrongEdge) {
560 m_edgePointsCandidates[coordinates] = STRONG_EDGE;
561 m_edgeMap[coordinates.first][coordinates.second] = 255;
563 return hasFoundStrongEdge;
Implementation of a generic 2D array used as base class for matrices and vectors.
unsigned int getCols() const
Type * data
Address of the first element of the data array.
void resize(unsigned int nrows, unsigned int ncols, bool flagNullify=true, bool recopy_=true)
unsigned int getRows() const
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.
@ badValue
Used to indicate that a value is not in the allowed range.
@ notImplementedError
Not implemented.
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
Various image filter, convolution, etc...
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.
@ CANNY_GBLUR_SOBEL_FILTERING
Apply Gaussian blur + Sobel operator on the input image.
@ CANNY_GBLUR_SCHARR_FILTERING
Apply Gaussian blur + Scharr operator on the input image.
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)
unsigned int getWidth() const
void resize(unsigned int h, unsigned int w)
resize the image : Image initialization
unsigned int getCols() const
unsigned int getHeight() const
unsigned int getRows() const