31 #include <visp3/core/vpCannyEdgeDetection.h>
33 #include <visp3/core/vpImageConvert.h>
36 #pragma comment(linker, "/STACK:65532000")
39 #if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98)
43 template <
typename FilterType>
44 static void scaleFilter(
45 #ifdef ENABLE_VISP_NAMESPACE
50 const unsigned int nbRows = filter.getRows();
51 const unsigned int nbCols = filter.getCols();
52 for (
unsigned int r = 0; r < nbRows; ++r) {
53 for (
unsigned int c = 0; c < nbCols; ++c) {
54 filter[r][c] = filter[r][c] * scale;
62 #ifdef VISP_HAVE_NLOHMANN_JSON
66 filteringAndGradientName = j.value(
"filteringAndGradientType", filteringAndGradientName);
68 detector.m_gaussianKernelSize = j.value(
"gaussianSize", detector.m_gaussianKernelSize);
69 detector.m_gaussianStdev = j.value(
"gaussianStdev", detector.m_gaussianStdev);
70 detector.m_lowerThreshold = j.value(
"lowerThreshold", detector.m_lowerThreshold);
71 detector.m_lowerThresholdRatio = j.value(
"lowerThresholdRatio", detector.m_lowerThresholdRatio);
72 detector.m_gradientFilterKernelSize = j.value(
"gradientFilterKernelSize", detector.m_gradientFilterKernelSize);
73 detector.m_upperThreshold = j.value(
"upperThreshold", detector.m_upperThreshold);
74 detector.m_upperThresholdRatio = j.value(
"upperThresholdRatio", detector.m_upperThresholdRatio);
81 {
"filteringAndGradientType", filteringAndGradientName},
82 {
"gaussianSize", detector.m_gaussianKernelSize},
83 {
"gaussianStdev", detector.m_gaussianStdev},
84 {
"lowerThreshold", detector.m_lowerThreshold},
85 {
"lowerThresholdRatio", detector.m_lowerThresholdRatio},
86 {
"gradientFilterKernelSize", detector.m_gradientFilterKernelSize},
87 {
"upperThreshold", detector.m_upperThreshold},
88 {
"upperThresholdRatio", detector.m_upperThresholdRatio}
96 : m_filteringAndGradientType(
vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
97 , m_gaussianKernelSize(3)
98 , m_gaussianStdev(1.f)
99 , m_areGradientAvailable(false)
100 , m_gradientFilterKernelSize(3)
101 , m_lowerThreshold(-1.f)
102 , m_lowerThresholdRatio(0.6f)
103 , m_upperThreshold(-1.f)
104 , m_upperThresholdRatio(0.8f)
105 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
110 initGaussianFilters();
111 initGradientFilters();
115 const unsigned int &sobelAperture,
const float &lowerThreshold,
116 const float &upperThreshold,
const float &lowerThresholdRatio,
117 const float &upperThresholdRatio,
119 const bool &storeEdgePoints
121 : m_filteringAndGradientType(filteringType)
122 , m_gaussianKernelSize(gaussianKernelSize)
123 , m_gaussianStdev(gaussianStdev)
124 , m_areGradientAvailable(false)
125 , m_gradientFilterKernelSize(sobelAperture)
126 , m_lowerThreshold(lowerThreshold)
127 , m_lowerThresholdRatio(lowerThresholdRatio)
128 , m_upperThreshold(upperThreshold)
129 , m_upperThresholdRatio(upperThresholdRatio)
130 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
133 , m_storeListEdgePoints(storeEdgePoints)
136 initGaussianFilters();
137 initGradientFilters();
140 #ifdef VISP_HAVE_NLOHMANN_JSON
142 using json = nlohmann::json;
145 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
155 std::ifstream file(jsonPath);
157 std::stringstream ss;
158 ss <<
"Problem opening file " << jsonPath <<
". Make sure it exists and is readable" << std::endl;
163 j = json::parse(file);
165 catch (json::parse_error &e) {
166 std::stringstream msg;
167 msg <<
"Could not parse JSON file : \n";
168 msg << e.what() << std::endl;
169 msg <<
"Byte position of error: " << e.byte;
174 initGaussianFilters();
175 initGradientFilters();
180 vpCannyEdgeDetection::initGaussianFilters()
183 if ((m_gaussianKernelSize % val_2) == 0) {
186 m_fg.
resize(1, (m_gaussianKernelSize + 1) / 2);
191 vpCannyEdgeDetection::initGradientFilters()
194 if ((m_gradientFilterKernelSize % val_2) != 1) {
197 m_gradientFilterX.
resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
198 m_gradientFilterY.
resize(m_gradientFilterKernelSize, m_gradientFilterKernelSize);
200 #if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
202 unsigned int filter_rows = filter.
getRows();
203 unsigned int filter_col = filter.
getCols();
204 for (
unsigned int r = 0; r < filter_rows; ++r) {
205 for (
unsigned int c = 0; c < filter_col; ++c) {
206 filter[r][c] = filter[r][c] * scale;
225 std::string errMsg =
"[vpCannyEdgeDetection::initGradientFilters] Error: gradient filtering method \"";
227 errMsg +=
"\" has not been implemented yet\n";
231 scaleFilter(m_gradientFilterX, scaleX);
232 scaleFilter(m_gradientFilterY, scaleY);
236 #ifdef HAVE_OPENCV_CORE
257 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
258 rlim_t initialStackSize = 0;
261 if (m_minStackSize > 0) {
263 result = getrlimit(RLIMIT_STACK, &rl);
265 initialStackSize = rl.rlim_cur;
266 if (rl.rlim_cur < m_minStackSize) {
268 rl.rlim_cur = m_minStackSize;
269 result = setrlimit(RLIMIT_STACK, &rl);
282 m_edgeCandidateAndGradient.clear();
283 m_edgePointsCandidates.clear();
284 m_edgePointsList.clear();
287 if (!m_areGradientAvailable) {
288 computeFilteringAndGradient(I);
290 m_areGradientAvailable =
false;
293 float upperThreshold = m_upperThreshold;
294 float lowerThreshold = m_lowerThreshold;
295 if (upperThreshold < 0) {
297 m_gaussianStdev, m_gradientFilterKernelSize, m_lowerThresholdRatio,
298 m_upperThresholdRatio, m_filteringAndGradientType, mp_mask);
300 else if (m_lowerThreshold < 0) {
302 lowerThreshold = m_upperThreshold / 3.f;
305 lowerThreshold = std::max<float>(lowerThreshold, std::numeric_limits<float>::epsilon());
306 performEdgeThinning(lowerThreshold);
309 performHysteresisThresholding(lowerThreshold, upperThreshold);
312 performEdgeTracking();
314 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
315 if (m_minStackSize > 0) {
316 if (rl.rlim_cur > initialStackSize) {
318 rl.rlim_cur = initialStackSize;
319 result = setrlimit(RLIMIT_STACK, &rl);
338 vpImageFilter::filterX<unsigned char, float>(I, GIx, m_fg.
data, m_gaussianKernelSize, mp_mask);
339 vpImageFilter::filterY<float, float>(GIx, Iblur, m_fg.
data, m_gaussianKernelSize, mp_mask);
346 std::string errmsg(
"Currently, the filtering operation \"");
348 errmsg +=
"\" is not handled.";
366 getInterpolWeightsAndOffsets(
const float &gradientOrientation,
367 float &alpha,
float &beta,
368 int &dRowGradAlpha,
int &dRowGradBeta,
369 int &dColGradAlpha,
int &dColGradBeta
372 float thetaMin = 0.f;
373 if (gradientOrientation < M_PI_4_FLOAT) {
380 else if ((gradientOrientation >= M_PI_4_FLOAT) && (gradientOrientation < M_PI_2_FLOAT)) {
382 thetaMin = M_PI_4_FLOAT;
388 else if ((gradientOrientation >= M_PI_2_FLOAT) && (gradientOrientation < (3.f * M_PI_4_FLOAT))) {
390 thetaMin = M_PI_2_FLOAT;
396 else if ((gradientOrientation >= (3.f * M_PI_4_FLOAT)) && (gradientOrientation < M_PI_FLOAT)) {
398 thetaMin = 3.f * M_PI_4_FLOAT;
404 beta = (gradientOrientation - thetaMin) / M_PI_4_FLOAT;
428 float dx = dIx[row][col];
429 float dy = dIy[row][col];
430 grad = std::abs(dx) + std::abs(dy);
449 float gradientOrientation = 0.f;
450 float dx = dIx[row][col];
451 float dy = dIy[row][col];
453 if (std::abs(dx) < std::numeric_limits<float>::epsilon()) {
454 gradientOrientation = M_PI_2_FLOAT;
459 gradientOrientation =
static_cast<float>(std::atan2(-dy, dx));
460 if (gradientOrientation < 0.f) {
461 gradientOrientation += M_PI_FLOAT;
464 return gradientOrientation;
468 vpCannyEdgeDetection::performEdgeThinning(
const float &lowerThreshold)
473 bool ignore_current_pixel =
false;
474 bool grad_lower_threshold =
false;
475 for (
int row = 0; row < nbRows; ++row) {
476 for (
int col = 0; col < nbCols; ++col) {
478 ignore_current_pixel =
false;
479 grad_lower_threshold =
false;
481 if (mp_mask !=
nullptr) {
482 if (!(*mp_mask)[row][col]) {
484 ignore_current_pixel =
true;
489 if (ignore_current_pixel ==
false) {
492 float grad = getManhattanGradient(m_dIx, m_dIy, row, col);
494 if (grad < lowerThreshold) {
496 grad_lower_threshold =
true;
499 if (grad_lower_threshold ==
false) {
503 int dRowAlphaPlus = 0, dRowBetaPlus = 0;
504 int dColAphaPlus = 0, dColBetaPlus = 0;
505 float gradientOrientation = getGradientOrientation(m_dIx, m_dIy, row, col);
506 float alpha = 0.f, beta = 0.f;
507 getInterpolWeightsAndOffsets(gradientOrientation, alpha, beta, dRowAlphaPlus, dRowBetaPlus, dColAphaPlus, dColBetaPlus);
508 int dRowAlphaMinus = -dRowAlphaPlus, dRowBetaMinus = -dRowBetaPlus;
509 int dColAphaMinus = -dColAphaPlus, dColBetaMinus = -dColBetaPlus;
510 float gradAlphaPlus = getManhattanGradient(m_dIx, m_dIy, row + dRowAlphaPlus, col + dColAphaPlus);
511 float gradBetaPlus = getManhattanGradient(m_dIx, m_dIy, row + dRowBetaPlus, col + dColBetaPlus);
512 float gradAlphaMinus = getManhattanGradient(m_dIx, m_dIy, row + dRowAlphaMinus, col + dColAphaMinus);
513 float gradBetaMinus = getManhattanGradient(m_dIx, m_dIy, row + dRowBetaMinus, col + dColBetaMinus);
514 float gradPlus = (alpha * gradAlphaPlus) + (beta * gradBetaPlus);
515 float gradMinus = (alpha * gradAlphaMinus) + (beta * gradBetaMinus);
517 if ((grad >= gradPlus) && (grad >= gradMinus)) {
519 std::pair<unsigned int, unsigned int> bestPixel(row, col);
520 m_edgeCandidateAndGradient[bestPixel] = grad;
529 vpCannyEdgeDetection::performHysteresisThresholding(
const float &lowerThreshold,
const float &upperThreshold)
531 std::map<std::pair<unsigned int, unsigned int>,
float>::iterator it;
532 std::map<std::pair<unsigned int, unsigned int>,
float>::iterator m_edgeCandidateAndGradient_end = m_edgeCandidateAndGradient.end();
533 for (it = m_edgeCandidateAndGradient.begin(); it != m_edgeCandidateAndGradient_end; ++it) {
534 if (it->second >= upperThreshold) {
535 m_edgePointsCandidates[it->first] = STRONG_EDGE;
537 else if ((it->second >= lowerThreshold) && (it->second < upperThreshold)) {
538 m_edgePointsCandidates[it->first] = WEAK_EDGE;
544 vpCannyEdgeDetection::performEdgeTracking()
546 const unsigned char var_uc_255 = 255;
547 std::map<std::pair<unsigned int, unsigned int>, EdgeType>::iterator it;
548 std::map<std::pair<unsigned int, unsigned int>, EdgeType>::iterator m_edgePointsCandidates_end = m_edgePointsCandidates.end();
549 for (it = m_edgePointsCandidates.begin(); it != m_edgePointsCandidates_end; ++it) {
550 if (it->second == STRONG_EDGE) {
551 m_edgeMap[it->first.first][it->first.second] = var_uc_255;
552 if (m_storeListEdgePoints) {
553 m_edgePointsList.push_back(
vpImagePoint(it->first.first, it->first.second));
556 else if (it->second == WEAK_EDGE) {
557 recursiveSearchForStrongEdge(it->first);
563 vpCannyEdgeDetection::recursiveSearchForStrongEdge(
const std::pair<unsigned int, unsigned int> &coordinates)
565 bool hasFoundStrongEdge =
false;
568 m_edgePointsCandidates[coordinates] = ON_CHECK;
569 bool test_row =
false;
570 bool test_col =
false;
571 bool test_drdc =
false;
572 bool edge_in_image_limit =
false;
574 while ((dr <= 1) && (!hasFoundStrongEdge)) {
576 while ((dc <= 1) && (!hasFoundStrongEdge)) {
578 edge_in_image_limit =
false;
580 int idRow = dr +
static_cast<int>(coordinates.first);
581 idRow = std::max<int>(idRow, 0);
582 int idCol = dc +
static_cast<int>(coordinates.second);
583 idCol = std::max<int>(idCol, 0);
586 test_row = (idRow < 0) || (idRow >= nbRows);
587 test_col = (idCol < 0) || (idCol >= nbCols);
588 test_drdc = (dr == 0) && (dc == 0);
589 if (test_row || test_col || test_drdc) {
590 edge_in_image_limit =
true;
593 if (edge_in_image_limit ==
false) {
596 std::pair<unsigned int, unsigned int> key_candidate(idRow, idCol);
598 EdgeType type_candidate = m_edgePointsCandidates.at(key_candidate);
599 if (type_candidate == STRONG_EDGE) {
601 hasFoundStrongEdge =
true;
603 else if (type_candidate == WEAK_EDGE) {
605 hasFoundStrongEdge = recursiveSearchForStrongEdge(key_candidate);
616 const unsigned char var_uc_255 = 255;
617 if (hasFoundStrongEdge) {
618 m_edgePointsCandidates[coordinates] = STRONG_EDGE;
619 m_edgeMap[coordinates.first][coordinates.second] = var_uc_255;
620 if (m_storeListEdgePoints) {
621 m_edgePointsList.push_back(
vpImagePoint(coordinates.first, coordinates.second));
624 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)
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
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