Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpCannyEdgeDetection.cpp
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#include <visp3/core/vpCannyEdgeDetection.h>
32
33#include <visp3/core/vpImageConvert.h>
34
35#ifdef VISP_HAVE_OPENMP
36#include <omp.h>
37#endif
38
39#ifdef VISP_USE_MSVC
40#pragma comment(linker, "/STACK:65532000") // Increase max recursion depth
41#endif
42
43#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98) // Check if cxx98
44namespace
45{
46// Helper to apply the scale to the raw values of the filters
47template <typename FilterType>
48static void scaleFilter(
49#ifdef ENABLE_VISP_NAMESPACE
50 visp::
51#endif
52 vpArray2D<FilterType> &filter, const float &scale)
53{
54 const unsigned int nbRows = filter.getRows();
55 const unsigned int nbCols = filter.getCols();
56 for (unsigned int r = 0; r < nbRows; ++r) {
57 for (unsigned int c = 0; c < nbCols; ++c) {
58 filter[r][c] = filter[r][c] * scale;
59 }
60 }
61}
62}
63#endif
64
66// // Initialization methods
67
69 : m_filteringAndGradientType(vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
70 , m_nbThread(-1)
71 , m_gaussianKernelSize(3)
72 , m_gaussianStdev(1.f)
73 , m_areGradientAvailable(false)
74 , m_gradientFilterKernelSize(3)
75 , m_lowerThreshold(-1.f)
76 , m_lowerThresholdRatio(0.6f)
77 , m_upperThreshold(-1.f)
78 , m_upperThresholdRatio(0.8f)
79#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
80 , m_minStackSize(0) // Deactivated by default
81#endif
82 , mp_mask(nullptr)
83{
84 reinit();
85}
86
87vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev,
88 const unsigned int &sobelAperture, const float &lowerThreshold,
89 const float &upperThreshold, const float &lowerThresholdRatio,
90 const float &upperThresholdRatio,
92 const bool &storeEdgePoints, const int &nbThread
93)
94 : m_filteringAndGradientType(filteringType)
95 , m_nbThread(nbThread)
96 , m_gaussianKernelSize(gaussianKernelSize)
97 , m_gaussianStdev(gaussianStdev)
98 , m_areGradientAvailable(false)
99 , m_gradientFilterKernelSize(sobelAperture)
100 , m_lowerThreshold(lowerThreshold)
101 , m_lowerThresholdRatio(lowerThresholdRatio)
102 , m_upperThreshold(upperThreshold)
103 , m_upperThresholdRatio(upperThresholdRatio)
104#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
105 , m_minStackSize(0) // Deactivated by default
106#endif
107 , m_storeListEdgePoints(storeEdgePoints)
108 , mp_mask(nullptr)
109{
110 reinit();
111}
112
113void
115{
116 setNbThread(m_nbThread);
117 initGaussianFilters();
118 initGradientFilters();
119 mp_mask = nullptr;
120 m_areGradientAvailable = false;
121
122 // // Clearing the previous results
123 m_edgeCandidateAndGradient.clear();
124 m_activeEdgeCandidates.clear();
125 m_edgePointsList.clear();
126 if (m_edgeMap.getSize() != 0) {
127 m_edgeMap.resize(m_edgeMap.getRows(), m_edgeMap.getCols(), 0);
128 m_edgePointsCandidates.resize(m_edgeMap.getRows(), m_edgeMap.getCols(), NOT_EDGE);
129 }
130}
131
132#ifdef VISP_HAVE_NLOHMANN_JSON
133
134using json = nlohmann::json;
135
137#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
138 : m_minStackSize(0) // Deactivated by default
139#endif
140{
141 initFromJSON(jsonPath);
142}
143
144void
145vpCannyEdgeDetection::initFromJSON(const std::string &jsonPath)
146{
147 std::ifstream file(jsonPath);
148 if (!file.good()) {
149 std::stringstream ss;
150 ss << "Problem opening file " << jsonPath << ". Make sure it exists and is readable" << std::endl;
151 throw vpException(vpException::ioError, ss.str());
152 }
153 json j;
154 try {
155 j = json::parse(file);
156 }
157 catch (json::parse_error &e) {
158 std::stringstream msg;
159 msg << "Could not parse JSON file : \n";
160 msg << e.what() << std::endl;
161 msg << "Byte position of error: " << e.byte;
162 throw vpException(vpException::ioError, msg.str());
163 }
164 from_json(j, *this);
165 file.close();
166 reinit();
167}
168#endif
169
170void
171vpCannyEdgeDetection::initGaussianFilters()
172{
173 const int val_2 = 2;
174 if ((m_gaussianKernelSize % val_2) == 0) {
175 throw(vpException(vpException::badValue, "The Gaussian kernel size should be odd"));
176 }
177 m_fg.resize(1, static_cast<unsigned int>((m_gaussianKernelSize + 1) / val_2));
178 vpImageFilter::getGaussianKernel(m_fg.data, static_cast<unsigned int>(m_gaussianKernelSize), m_gaussianStdev, true);
179}
180
181void
182vpCannyEdgeDetection::initGradientFilters()
183{
184 const int val_2 = 2;
185 if ((m_gradientFilterKernelSize % val_2) != 1) {
186 throw vpException(vpException::badValue, "Gradient filters kernel size should be odd.");
187 }
188 m_gradientFilterX.resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
189 m_gradientFilterY.resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
190
191#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
192 auto scaleFilter = [](vpArray2D<float> &filter, const float &scale) {
193 unsigned int filter_rows = filter.getRows();
194 unsigned int filter_col = filter.getCols();
195 for (unsigned int r = 0; r < filter_rows; ++r) {
196 for (unsigned int c = 0; c < filter_col; ++c) {
197 filter[r][c] = filter[r][c] * scale;
198 }
199 }
200 };
201#endif
202
203 float scaleX = 1.f;
204 float scaleY = 1.f;
205
206 if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING) {
207 scaleX = vpImageFilter::getSobelKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1) / val_2);
208 scaleY = vpImageFilter::getSobelKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1) / val_2);
209 }
210 else if (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING) {
211 // Compute the Scharr filters
212 scaleX = vpImageFilter::getScharrKernelX(m_gradientFilterX.data, (m_gradientFilterKernelSize - 1) / val_2);
213 scaleY = vpImageFilter::getScharrKernelY(m_gradientFilterY.data, (m_gradientFilterKernelSize - 1) / val_2);
214 }
215 else {
216 std::string errMsg = "[vpCannyEdgeDetection::initGradientFilters] Error: gradient filtering method \"";
217 errMsg += vpImageFilter::vpCannyFiltAndGradTypeToStr(m_filteringAndGradientType);
218 errMsg += "\" has not been implemented yet\n";
219 throw vpException(vpException::notImplementedError, errMsg);
220 }
221
222 scaleFilter(m_gradientFilterX, scaleX);
223 scaleFilter(m_gradientFilterY, scaleY);
224}
225
226// // Detection methods
227#ifdef HAVE_OPENCV_CORE
229vpCannyEdgeDetection::detect(const cv::Mat &cv_I)
230{
231 vpImage<unsigned char> I_gray;
232 vpImageConvert::convert(cv_I, I_gray);
233 return detect(I_gray);
234}
235#endif
236
239{
241 vpImageConvert::convert(I_color, I_gray);
242 return detect(I_gray);
243}
244
247{
248 // // Step 1 and 2: filter the image and compute the gradient, if not given by the user
249 if (!m_areGradientAvailable) {
250 computeFilteringAndGradient(I);
251 }
252 m_areGradientAvailable = false; // Reset for next call
253
254 // // Step 3: edge thining
255 float upperThreshold = m_upperThreshold;
256 float lowerThreshold = m_lowerThreshold;
257 if (upperThreshold < 0) {
258 upperThreshold = vpImageFilter::computeCannyThreshold(I, lowerThreshold, &m_dIx, &m_dIy, m_gaussianKernelSize,
259 m_gaussianStdev, m_gradientFilterKernelSize, m_lowerThresholdRatio,
260 m_upperThresholdRatio, m_filteringAndGradientType, mp_mask);
261 }
262 else if (m_lowerThreshold < 0) {
263 // Applying Canny recommendation to have the upper threshold 3 times greater than the lower threshold.
264 lowerThreshold = m_upperThreshold / 3.f;
265 }
266 // To ensure that if lowerThreshold = 0, we reject null gradient points
267 lowerThreshold = std::max<float>(lowerThreshold, std::numeric_limits<float>::epsilon());
268
269 step3to5(I.getHeight(), I.getWidth(), lowerThreshold, upperThreshold);
270
271 return m_edgeMap;
272}
273
274void
275vpCannyEdgeDetection::step3to5(const unsigned int &height, const unsigned int &width, const float &lowerThreshold, const float &upperThreshold)
276{
277#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
278 rlim_t initialStackSize = 0;
279 struct rlimit rl;
280 int result;
281 if (m_minStackSize > 0) {
282 // Check the current stack size
283 result = getrlimit(RLIMIT_STACK, &rl);
284 if (result == 0) {
285 initialStackSize = rl.rlim_cur;
286 if (rl.rlim_cur < m_minStackSize) {
287 // Increase stack size due to the recursive algorithm
288 rl.rlim_cur = m_minStackSize;
289 result = setrlimit(RLIMIT_STACK, &rl);
290 if (result != 0) {
291 throw(vpException(vpException::fatalError, "setrlimit returned result = %d\n", result));
292 }
293 }
294 }
295 else {
296 throw(vpException(vpException::fatalError, "getrlimit returned result = %d\n", result));
297 }
298 }
299#endif
300 // // Clearing the previous results
301 m_edgeMap.resize(height, width, 0);
302 m_edgeCandidateAndGradient.clear();
303 m_activeEdgeCandidates.clear();
304 m_edgePointsCandidates.resize(m_dIx.getRows(), m_dIx.getCols(), NOT_EDGE);
305 m_edgePointsList.clear();
306 if (m_storeListEdgePoints) {
307 m_edgePointsList.reserve(m_dIx.getSize() / 8);
308 }
309
310 performEdgeThinning(lowerThreshold);
311
312 // // Step 4: hysteresis thresholding
313 performHysteresisThresholding(lowerThreshold, upperThreshold);
314
315 // // Step 5: edge tracking
316 performEdgeTracking();
317
318#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) // UNIX
319 if (m_minStackSize > 0) {
320 if (rl.rlim_cur > initialStackSize) {
321 // Reset stack size to its original value
322 rl.rlim_cur = initialStackSize;
323 result = setrlimit(RLIMIT_STACK, &rl);
324 if (result != 0) {
325 throw(vpException(vpException::fatalError, "setrlimit returned result = %d\n", result));
326
327 }
328 }
329 }
330#endif
331}
332
333void
334vpCannyEdgeDetection::computeFilteringAndGradient(const vpImage<unsigned char> &I)
335{
336 if ((m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
337 || (m_filteringAndGradientType == vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING)) {
338 // Computing the Gaussian blur
339 vpImage<float> Iblur;
340 vpImage<float> GIx;
341 vpImageFilter::filterX<unsigned char, float>(I, GIx, m_fg.data, static_cast<unsigned int>(m_gaussianKernelSize), mp_mask);
342 vpImageFilter::filterY<float, float>(GIx, Iblur, m_fg.data, static_cast<unsigned int>(m_gaussianKernelSize), mp_mask);
343
344 // Computing the gradients
345 vpImageFilter::filter(Iblur, m_dIx, m_gradientFilterX, true, mp_mask);
346 vpImageFilter::filter(Iblur, m_dIy, m_gradientFilterY, true, mp_mask);
347 }
348 else {
349 std::string errmsg("Currently, the filtering operation \"");
350 errmsg += vpImageFilter::vpCannyFiltAndGradTypeToStr(m_filteringAndGradientType);
351 errmsg += "\" is not handled.";
352 throw(vpException(vpException::notImplementedError, errmsg));
353 }
354}
355
368void
369vpCannyEdgeDetection::getInterpolWeightsAndOffsets(const float &gradientOrientation,
370 float &alpha, float &beta, const int &nbCols,
371 int &dRowGradAlpha, int &dRowGradBeta,
372 int &dColGradAlpha, int &dColGradBeta
373)
374{
375 float thetaMin = 0.f;
376 if (gradientOrientation < M_PI_4_FLOAT) {
377 // Angles between 0 and 45 deg rely on the horizontal and diagonal points
378 dColGradAlpha = 1;
379 dColGradBeta = 1;
380 dRowGradAlpha = 0;
381 dRowGradBeta = -nbCols;
382 }
383 else if ((gradientOrientation >= M_PI_4_FLOAT) && (gradientOrientation < M_PI_2_FLOAT)) {
384 // Angles between 45 and 90 deg rely on the diagonal and vertical points
385 thetaMin = M_PI_4_FLOAT;
386 dColGradAlpha = 1;
387 dColGradBeta = 0;
388 dRowGradAlpha = -nbCols;
389 dRowGradBeta = -nbCols;
390 }
391 else if ((gradientOrientation >= M_PI_2_FLOAT) && (gradientOrientation < (3.f * M_PI_4_FLOAT))) {
392 // Angles between 90 and 135 deg rely on the vertical and diagonal points
393 thetaMin = M_PI_2_FLOAT;
394 dColGradAlpha = 0;
395 dColGradBeta = -1;
396 dRowGradAlpha = -nbCols;
397 dRowGradBeta = -nbCols;
398 }
399 else if ((gradientOrientation >= (3.f * M_PI_4_FLOAT)) && (gradientOrientation < M_PI_FLOAT)) {
400 // Angles between 135 and 180 deg rely on the vertical and diagonal points
401 thetaMin = 3.f * M_PI_4_FLOAT;
402 dColGradAlpha = -1;
403 dColGradBeta = -1;
404 dRowGradAlpha = -nbCols;
405 dRowGradBeta = 0;
406 }
407 beta = (gradientOrientation - thetaMin) / M_PI_4_FLOAT;
408 alpha = 1.f - beta;
409}
410
419float
420vpCannyEdgeDetection::getManhattanGradient(const vpImage<float> &dIx, const vpImage<float> &dIy, const int &iter)
421{
422 float grad = 0.;
423 float dx = dIx.bitmap[iter];
424 float dy = dIy.bitmap[iter];
425 grad = std::abs(dx) + std::abs(dy);
426
427 return grad;
428}
429
440float
441vpCannyEdgeDetection::getGradientOrientation(const vpImage<float> &dIx, const vpImage<float> &dIy, const int &iter)
442{
443 float gradientOrientation = 0.f;
444 float dx = dIx.bitmap[iter];
445 float dy = dIy.bitmap[iter];
446
447 if (std::abs(dx) < std::numeric_limits<float>::epsilon()) {
448 gradientOrientation = M_PI_2_FLOAT;
449 }
450 else {
451 // -dy because the y-axis of the image is oriented towards the bottom of the screen
452 // while we later work with a y-axis oriented towards the top when getting the theta quadrant.
453 gradientOrientation = static_cast<float>(std::atan2(-dy, dx));
454 if (gradientOrientation < 0.f) {
455 gradientOrientation += M_PI_FLOAT; // + M_PI in order to be between 0 and M_PI_FLOAT
456 }
457 }
458 return gradientOrientation;
459}
460
461void
462vpCannyEdgeDetection::performEdgeThinning(const float &lowerThreshold)
463{
464 const int nbCols = static_cast<int>(m_dIx.getCols());
465 const int size = static_cast<int>(m_dIx.getSize());
466
467 int istart = 0, istop = size;
468#ifdef VISP_HAVE_OPENMP
469 int iam, nt, ipoints, npoints(size);
470#pragma omp parallel default(shared) private(iam, nt, ipoints, istart, istop) num_threads(m_nbThread)
471 {
472 iam = omp_get_thread_num();
473 nt = omp_get_num_threads();
474 ipoints = npoints / nt;
475 // size of partition
476 istart = iam * ipoints; // starting array index
477 if (iam == nt-1) {
478 // last thread may do more
479 ipoints = npoints - istart;
480 }
481 istop = istart + ipoints;
482 std::vector<std::pair<unsigned int, float> > localMemoryEdgeCandidates;
483#endif
484 bool ignore_current_pixel = false;
485 bool grad_lower_threshold = false;
486 for (int iter = istart; iter < istop; ++iter) {
487 // reset the checks
488 ignore_current_pixel = false;
489 grad_lower_threshold = false;
490
491 if (mp_mask != nullptr) {
492 if (!mp_mask->bitmap[iter]) {
493 // The mask tells us to ignore the current pixel
494 ignore_current_pixel = true;
495 // continue
496 }
497 }
498 // continue if the mask does not tell us to ignore the current pixel
499 if (ignore_current_pixel == false) {
500
501 // Computing the gradient orientation and magnitude
502 float grad = getManhattanGradient(m_dIx, m_dIy, iter);
503
504 if (grad < lowerThreshold) {
505 // The gradient is lower than minimum threshold => ignoring the point
506 grad_lower_threshold = true;
507 // continue
508 }
509 if (grad_lower_threshold == false) {
510 //
511 // Getting the offset along the horizontal and vertical axes
512 // depending on the gradient orientation
513 int dRowAlphaPlus = 0, dRowBetaPlus = 0;
514 int dColAphaPlus = 0, dColBetaPlus = 0;
515 float gradientOrientation = getGradientOrientation(m_dIx, m_dIy, iter);
516 float alpha = 0.f, beta = 0.f;
517 getInterpolWeightsAndOffsets(gradientOrientation, alpha, beta, nbCols, dRowAlphaPlus, dRowBetaPlus, dColAphaPlus, dColBetaPlus);
518 int dRowAlphaMinus = -dRowAlphaPlus, dRowBetaMinus = -dRowBetaPlus;
519 int dColAphaMinus = -dColAphaPlus, dColBetaMinus = -dColBetaPlus;
520 float gradAlphaPlus = getManhattanGradient(m_dIx, m_dIy, iter + dRowAlphaPlus + dColAphaPlus);
521 float gradBetaPlus = getManhattanGradient(m_dIx, m_dIy, iter + dRowBetaPlus + dColBetaPlus);
522 float gradAlphaMinus = getManhattanGradient(m_dIx, m_dIy, iter + dRowAlphaMinus + dColAphaMinus);
523 float gradBetaMinus = getManhattanGradient(m_dIx, m_dIy, iter + dRowBetaMinus + dColBetaMinus);
524 float gradPlus = (alpha * gradAlphaPlus) + (beta * gradBetaPlus);
525 float gradMinus = (alpha * gradAlphaMinus) + (beta * gradBetaMinus);
526
527 if ((grad >= gradPlus) && (grad >= gradMinus)) {
528 // Keeping the edge point that has the highest gradient
529#ifdef VISP_HAVE_OPENMP
530 localMemoryEdgeCandidates.push_back(std::pair<unsigned int, float>(iter, grad));
531#else
532 m_edgeCandidateAndGradient.push_back(std::pair<unsigned int, float>(iter, grad));
533#endif
534 }
535 }
536 }
537 }
538#ifdef VISP_HAVE_OPENMP
539#pragma omp critical
540 {
541#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
542 m_edgeCandidateAndGradient.insert(
543 m_edgeCandidateAndGradient.end(),
544 std::make_move_iterator(localMemoryEdgeCandidates.begin()),
545 std::make_move_iterator(localMemoryEdgeCandidates.end())
546 );
547#else
548 m_edgeCandidateAndGradient.insert(
549 m_edgeCandidateAndGradient.end(),
550 localMemoryEdgeCandidates.begin(),
551 localMemoryEdgeCandidates.end()
552 );
553#endif
554 }
555#endif
556#ifdef VISP_HAVE_OPENMP
557 }
558#endif
559}
560
561void
562vpCannyEdgeDetection::performHysteresisThresholding(const float &lowerThreshold, const float &upperThreshold)
563{
564 const int size = static_cast<int>(m_edgeCandidateAndGradient.size());
565 int istart = 0;
566 int istop = size;
567
568#ifdef VISP_HAVE_OPENMP
569 int iam, nt, ipoints, npoints(size);
570#pragma omp parallel default(shared) private(iam, nt, ipoints, istart, istop) num_threads(m_nbThread)
571 {
572 iam = omp_get_thread_num();
573 nt = omp_get_num_threads();
574 ipoints = npoints / nt;
575 // size of partition
576 istart = iam * ipoints; // starting array index
577 if (iam == nt-1) {
578 // last thread may do more
579 ipoints = npoints - istart;
580 }
581 istop = istart + ipoints;
582 std::vector<unsigned int> localMemoryEdgeCandidates;
583#endif
584 for (int id = istart; id < istop; ++id) {
585 const std::pair<unsigned int, float> &candidate = m_edgeCandidateAndGradient[id];
586 if (candidate.second >= upperThreshold) {
587#ifdef VISP_HAVE_OPENMP
588 localMemoryEdgeCandidates.push_back(candidate.first);
589#else
590 m_activeEdgeCandidates.push_back(candidate.first);
591#endif
592 m_edgePointsCandidates.bitmap[candidate.first] = STRONG_EDGE;
593 }
594 else if ((candidate.second >= lowerThreshold) && (candidate.second < upperThreshold)) {
595#ifdef VISP_HAVE_OPENMP
596 localMemoryEdgeCandidates.push_back(candidate.first);
597#else
598 m_activeEdgeCandidates.push_back(candidate.first);
599#endif
600 m_edgePointsCandidates.bitmap[candidate.first] = WEAK_EDGE;
601 }
602 }
603
604#ifdef VISP_HAVE_OPENMP
605#pragma omp critical
606 {
607#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
608 m_activeEdgeCandidates.insert(
609 m_activeEdgeCandidates.end(),
610 std::make_move_iterator(localMemoryEdgeCandidates.begin()),
611 std::make_move_iterator(localMemoryEdgeCandidates.end())
612 );
613#else
614 m_activeEdgeCandidates.insert(
615 m_activeEdgeCandidates.end(),
616 localMemoryEdgeCandidates.begin(),
617 localMemoryEdgeCandidates.end()
618 );
619#endif
620 }
621 }
622#endif
623}
624
625void
626vpCannyEdgeDetection::performEdgeTracking()
627{
628 const unsigned char var_uc_255 = 255;
629 const unsigned int nbCols = m_edgeMap.getCols();
630
631 std::vector<unsigned int>::iterator it;
632 std::vector<unsigned int>::iterator m_edgePointsCandidates_end = m_activeEdgeCandidates.end();
633 for (it = m_activeEdgeCandidates.begin(); it != m_edgePointsCandidates_end; ++it) {
634 if (m_edgePointsCandidates.bitmap[*it] == STRONG_EDGE) {
635 if (m_storeListEdgePoints) {
636 if (m_edgeMap.bitmap[*it] != var_uc_255) {
637 // Edge point not added yet to the edge list
638 unsigned int row = *it / nbCols;
639 unsigned int col = *it % nbCols;
640 m_edgePointsList.push_back(vpImagePoint(row, col));
641 }
642 }
643 m_edgeMap.bitmap[*it] = var_uc_255;
644 }
645 else if (m_edgePointsCandidates.bitmap[*it] == WEAK_EDGE) {
646 recursiveSearchForStrongEdge(*it);
647 }
648 }
649}
650
651bool
652vpCannyEdgeDetection::recursiveSearchForStrongEdge(const unsigned int &coordinates)
653{
654 const int nbCols = static_cast<int>(m_dIx.getCols());
655 const int size = static_cast<int>(m_dIx.getSize());
656 const int coordAsInt = static_cast<int>(coordinates);
657 bool hasFoundStrongEdge = false;
658 m_edgePointsCandidates.bitmap[coordinates] = ON_CHECK;
659 bool test_row = false;
660 bool test_col = false;
661 bool test_drdc = false;
662 bool edge_in_image_limit = false;
663 int dr = -1;
664 while ((dr <= 1) && (!hasFoundStrongEdge)) {
665 int dc = -1;
666 while ((dc <= 1) && (!hasFoundStrongEdge)) {
667 // reset the check for the edge on image limit
668 edge_in_image_limit = false;
669
670 int iterTest = coordAsInt + dr * nbCols + dc;
671
672 // Checking if we are still looking for an edge in the limit of the image
673 test_row = (iterTest < 0) || (iterTest >= size);
674 test_col = ((iterTest - dc) / nbCols) != (iterTest / nbCols);
675 test_drdc = (dr == 0) && (dc == 0);
676 if (test_row || test_col || test_drdc) {
677 edge_in_image_limit = true;
678 // the continue is replaced by the test
679 }
680 if (edge_in_image_limit == false) {
681
682 try {
683 // Checking if the 8-neighbor point is in the list of edge candidates
684 EdgeType type_candidate = m_edgePointsCandidates.bitmap[iterTest];
685 if (type_candidate == STRONG_EDGE) {
686 // The 8-neighbor point is a strong edge => the weak edge becomes a strong edge
687 hasFoundStrongEdge = true;
688 }
689 else if (type_candidate == WEAK_EDGE) {
690 // Checking if the WEAK_EDGE neighbor has a STRONG_EDGE neighbor
691 hasFoundStrongEdge = recursiveSearchForStrongEdge(iterTest);
692 }
693 }
694 catch (...) {
695 // continue - nothing to do
696 }
697 }
698 ++dc;
699 }
700 ++dr;
701 }
702 const unsigned char var_uc_255 = 255;
703 if (hasFoundStrongEdge) {
704 if (m_storeListEdgePoints) {
705 if (m_edgeMap.bitmap[coordinates] != var_uc_255) {
706 // Edge point not added yet to the edge list
707 unsigned int row = coordinates / nbCols;
708 unsigned int col = coordinates % nbCols;
709 m_edgePointsList.push_back(vpImagePoint(row, col));
710 }
711 }
712 m_edgePointsCandidates.bitmap[coordinates] = STRONG_EDGE;
713 m_edgeMap.bitmap[coordinates] = var_uc_255;
714 }
715 return hasFoundStrongEdge;
716}
717END_VISP_NAMESPACE
Implementation of a generic 2D array used as base class for matrices and vectors.
Definition vpArray2D.h:146
unsigned int getCols() const
Definition vpArray2D.h:423
unsigned int getRows() const
Definition vpArray2D.h:433
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.
void reinit()
Reinitialize the detector:
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....
void setNbThread(const int &maxNbThread)
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ ioError
I/O error.
Definition vpException.h:67
@ badValue
Used to indicate that a value is not in the allowed range.
Definition vpException.h:73
@ notImplementedError
Not implemented.
Definition vpException.h:69
@ fatalError
Fatal error.
Definition vpException.h:72
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
Various image filter, convolution, etc...
static void filterY(const vpImage< ImageType > &I, vpImage< OutputType > &dIy, const FilterType *filter, unsigned int size, const vpImage< bool > *p_mask=nullptr)
Filter along the vertical direction.
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 FilterType getSobelKernelX(FilterType *filter, unsigned int size)
static void filterX(const vpImage< ImageType > &I, vpImage< OutputType > &dIx, const FilterType *filter, unsigned int size, const vpImage< bool > *p_mask=nullptr)
Filter along the horizontal direction.
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 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)
Definition of the vpImage class member functions.
Definition vpImage.h:131
Type * bitmap
points toward the bitmap
Definition vpImage.h:135