31#include <visp3/core/vpCannyEdgeDetection.h>
33#include <visp3/core/vpImageConvert.h>
35#ifdef VISP_HAVE_OPENMP
40#pragma comment(linker, "/STACK:65532000")
43#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98)
47template <
typename FilterType>
48static void scaleFilter(
49#ifdef ENABLE_VISP_NAMESPACE
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;
69 : m_filteringAndGradientType(
vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
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__)))
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
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__)))
107 , m_storeListEdgePoints(storeEdgePoints)
117 initGaussianFilters();
118 initGradientFilters();
120 m_areGradientAvailable =
false;
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);
132#ifdef VISP_HAVE_NLOHMANN_JSON
134using json = nlohmann::json;
137#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
147 std::ifstream file(jsonPath);
149 std::stringstream ss;
150 ss <<
"Problem opening file " << jsonPath <<
". Make sure it exists and is readable" << std::endl;
155 j = json::parse(file);
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;
171vpCannyEdgeDetection::initGaussianFilters()
174 if ((m_gaussianKernelSize % val_2) == 0) {
177 m_fg.resize(1,
static_cast<unsigned int>((m_gaussianKernelSize + 1) / val_2));
182vpCannyEdgeDetection::initGradientFilters()
185 if ((m_gradientFilterKernelSize % val_2) != 1) {
188 m_gradientFilterX.resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
189 m_gradientFilterY.resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
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;
216 std::string errMsg =
"[vpCannyEdgeDetection::initGradientFilters] Error: gradient filtering method \"";
218 errMsg +=
"\" has not been implemented yet\n";
222 scaleFilter(m_gradientFilterX, scaleX);
223 scaleFilter(m_gradientFilterY, scaleY);
227#ifdef HAVE_OPENCV_CORE
231 vpImage<unsigned char> I_gray;
249 if (!m_areGradientAvailable) {
250 computeFilteringAndGradient(I);
252 m_areGradientAvailable =
false;
255 float upperThreshold = m_upperThreshold;
256 float lowerThreshold = m_lowerThreshold;
257 if (upperThreshold < 0) {
259 m_gaussianStdev, m_gradientFilterKernelSize, m_lowerThresholdRatio,
260 m_upperThresholdRatio, m_filteringAndGradientType, mp_mask);
262 else if (m_lowerThreshold < 0) {
264 lowerThreshold = m_upperThreshold / 3.f;
267 lowerThreshold = std::max<float>(lowerThreshold, std::numeric_limits<float>::epsilon());
269 step3to5(I.getHeight(), I.getWidth(), lowerThreshold, upperThreshold);
275vpCannyEdgeDetection::step3to5(
const unsigned int &height,
const unsigned int &width,
const float &lowerThreshold,
const float &upperThreshold)
277#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
278 rlim_t initialStackSize = 0;
281 if (m_minStackSize > 0) {
283 result = getrlimit(RLIMIT_STACK, &rl);
285 initialStackSize = rl.rlim_cur;
286 if (rl.rlim_cur < m_minStackSize) {
288 rl.rlim_cur = m_minStackSize;
289 result = setrlimit(RLIMIT_STACK, &rl);
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);
310 performEdgeThinning(lowerThreshold);
313 performHysteresisThresholding(lowerThreshold, upperThreshold);
316 performEdgeTracking();
318#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
319 if (m_minStackSize > 0) {
320 if (rl.rlim_cur > initialStackSize) {
322 rl.rlim_cur = initialStackSize;
323 result = setrlimit(RLIMIT_STACK, &rl);
339 vpImage<float> Iblur;
349 std::string errmsg(
"Currently, the filtering operation \"");
351 errmsg +=
"\" is not handled.";
369vpCannyEdgeDetection::getInterpolWeightsAndOffsets(
const float &gradientOrientation,
370 float &alpha,
float &beta,
const int &nbCols,
371 int &dRowGradAlpha,
int &dRowGradBeta,
372 int &dColGradAlpha,
int &dColGradBeta
375 float thetaMin = 0.f;
376 if (gradientOrientation < M_PI_4_FLOAT) {
381 dRowGradBeta = -nbCols;
383 else if ((gradientOrientation >= M_PI_4_FLOAT) && (gradientOrientation < M_PI_2_FLOAT)) {
385 thetaMin = M_PI_4_FLOAT;
388 dRowGradAlpha = -nbCols;
389 dRowGradBeta = -nbCols;
391 else if ((gradientOrientation >= M_PI_2_FLOAT) && (gradientOrientation < (3.f * M_PI_4_FLOAT))) {
393 thetaMin = M_PI_2_FLOAT;
396 dRowGradAlpha = -nbCols;
397 dRowGradBeta = -nbCols;
399 else if ((gradientOrientation >= (3.f * M_PI_4_FLOAT)) && (gradientOrientation < M_PI_FLOAT)) {
401 thetaMin = 3.f * M_PI_4_FLOAT;
404 dRowGradAlpha = -nbCols;
407 beta = (gradientOrientation - thetaMin) / M_PI_4_FLOAT;
425 grad = std::abs(dx) + std::abs(dy);
443 float gradientOrientation = 0.f;
447 if (std::abs(dx) < std::numeric_limits<float>::epsilon()) {
448 gradientOrientation = M_PI_2_FLOAT;
453 gradientOrientation =
static_cast<float>(std::atan2(-dy, dx));
454 if (gradientOrientation < 0.f) {
455 gradientOrientation += M_PI_FLOAT;
458 return gradientOrientation;
462vpCannyEdgeDetection::performEdgeThinning(
const float &lowerThreshold)
464 const int nbCols =
static_cast<int>(m_dIx.getCols());
465 const int size =
static_cast<int>(m_dIx.getSize());
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)
472 iam = omp_get_thread_num();
473 nt = omp_get_num_threads();
474 ipoints = npoints / nt;
476 istart = iam * ipoints;
479 ipoints = npoints - istart;
481 istop = istart + ipoints;
482 std::vector<std::pair<unsigned int, float> > localMemoryEdgeCandidates;
484 bool ignore_current_pixel =
false;
485 bool grad_lower_threshold =
false;
486 for (
int iter = istart;
iter < istop; ++
iter) {
488 ignore_current_pixel =
false;
489 grad_lower_threshold =
false;
491 if (mp_mask !=
nullptr) {
492 if (!mp_mask->bitmap[iter]) {
494 ignore_current_pixel =
true;
499 if (ignore_current_pixel ==
false) {
502 float grad = getManhattanGradient(m_dIx, m_dIy, iter);
504 if (grad < lowerThreshold) {
506 grad_lower_threshold =
true;
509 if (grad_lower_threshold ==
false) {
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);
527 if ((grad >= gradPlus) && (grad >= gradMinus)) {
529#ifdef VISP_HAVE_OPENMP
530 localMemoryEdgeCandidates.push_back(std::pair<unsigned int, float>(iter, grad));
532 m_edgeCandidateAndGradient.push_back(std::pair<unsigned int, float>(iter, grad));
538#ifdef VISP_HAVE_OPENMP
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())
548 m_edgeCandidateAndGradient.insert(
549 m_edgeCandidateAndGradient.end(),
550 localMemoryEdgeCandidates.begin(),
551 localMemoryEdgeCandidates.end()
556#ifdef VISP_HAVE_OPENMP
562vpCannyEdgeDetection::performHysteresisThresholding(
const float &lowerThreshold,
const float &upperThreshold)
564 const int size =
static_cast<int>(m_edgeCandidateAndGradient.size());
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)
572 iam = omp_get_thread_num();
573 nt = omp_get_num_threads();
574 ipoints = npoints / nt;
576 istart = iam * ipoints;
579 ipoints = npoints - istart;
581 istop = istart + ipoints;
582 std::vector<unsigned int> localMemoryEdgeCandidates;
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);
590 m_activeEdgeCandidates.push_back(candidate.first);
592 m_edgePointsCandidates.bitmap[candidate.first] = STRONG_EDGE;
594 else if ((candidate.second >= lowerThreshold) && (candidate.second < upperThreshold)) {
595#ifdef VISP_HAVE_OPENMP
596 localMemoryEdgeCandidates.push_back(candidate.first);
598 m_activeEdgeCandidates.push_back(candidate.first);
600 m_edgePointsCandidates.bitmap[candidate.first] = WEAK_EDGE;
604#ifdef VISP_HAVE_OPENMP
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())
614 m_activeEdgeCandidates.insert(
615 m_activeEdgeCandidates.end(),
616 localMemoryEdgeCandidates.begin(),
617 localMemoryEdgeCandidates.end()
626vpCannyEdgeDetection::performEdgeTracking()
628 const unsigned char var_uc_255 = 255;
629 const unsigned int nbCols = m_edgeMap.getCols();
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) {
638 unsigned int row = *it / nbCols;
639 unsigned int col = *it % nbCols;
640 m_edgePointsList.push_back(vpImagePoint(row, col));
643 m_edgeMap.bitmap[*it] = var_uc_255;
645 else if (m_edgePointsCandidates.bitmap[*it] == WEAK_EDGE) {
646 recursiveSearchForStrongEdge(*it);
652vpCannyEdgeDetection::recursiveSearchForStrongEdge(
const unsigned int &coordinates)
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;
664 while ((dr <= 1) && (!hasFoundStrongEdge)) {
666 while ((dc <= 1) && (!hasFoundStrongEdge)) {
668 edge_in_image_limit =
false;
670 int iterTest = coordAsInt + dr * nbCols + dc;
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;
680 if (edge_in_image_limit ==
false) {
684 EdgeType type_candidate = m_edgePointsCandidates.bitmap[iterTest];
685 if (type_candidate == STRONG_EDGE) {
687 hasFoundStrongEdge =
true;
689 else if (type_candidate == WEAK_EDGE) {
691 hasFoundStrongEdge = recursiveSearchForStrongEdge(iterTest);
702 const unsigned char var_uc_255 = 255;
703 if (hasFoundStrongEdge) {
704 if (m_storeListEdgePoints) {
705 if (m_edgeMap.bitmap[coordinates] != var_uc_255) {
707 unsigned int row = coordinates / nbCols;
708 unsigned int col = coordinates % nbCols;
709 m_edgePointsList.push_back(vpImagePoint(row, col));
712 m_edgePointsCandidates.bitmap[coordinates] = STRONG_EDGE;
713 m_edgeMap.bitmap[coordinates] = var_uc_255;
715 return hasFoundStrongEdge;
Implementation of a generic 2D array used as base class for matrices and vectors.
unsigned int getCols() const
unsigned int getRows() const
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.
@ 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 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.
Type * bitmap
points toward the bitmap