Visual Servoing Platform version 3.6.0
Loading...
Searching...
No Matches
vpCannyEdgeDetection.cpp
1/****************************************************************************
2 *
3 * ViSP, open source Visual Servoing Platform software.
4 * Copyright (C) 2005 - 2023 by Inria. All rights reserved.
5 *
6 * This software is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 * See the file LICENSE.txt at the root directory of this source
11 * distribution for additional information about the GNU GPL.
12 *
13 * For using ViSP with software that can not be combined with the GNU
14 * GPL, please contact Inria about acquiring a ViSP Professional
15 * Edition License.
16 *
17 * See https://visp.inria.fr for more information.
18 *
19 * This software was developed at:
20 * Inria Rennes - Bretagne Atlantique
21 * Campus Universitaire de Beaulieu
22 * 35042 Rennes Cedex
23 * France
24 *
25 * If you have questions regarding the use of this file, please contact
26 * Inria at visp@inria.fr
27 *
28 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30 *
31*****************************************************************************/
32
33#include <visp3/core/vpCannyEdgeDetection.h>
34
35#include <visp3/core/vpImageConvert.h>
36
37// // Initialization methods
38
40 : m_gaussianKernelSize(3)
41 , m_gaussianStdev(1.)
42 , m_areGradientAvailable(false)
43 , m_lowerThreshold(-1.)
44 , m_upperThreshold(-1.)
45{
46 initGaussianFilters();
47}
48
49vpCannyEdgeDetection::vpCannyEdgeDetection(const int &gaussianKernelSize, const float &gaussianStdev
50 , const float &lowerThreshold, const float &upperThreshold)
51 : m_gaussianKernelSize(gaussianKernelSize)
52 , m_gaussianStdev(gaussianStdev)
53 , m_areGradientAvailable(false)
54 , m_lowerThreshold(lowerThreshold)
55 , m_upperThreshold(upperThreshold)
56{
57 initGaussianFilters();
58}
59
60#ifdef VISP_HAVE_NLOHMANN_JSON
62{
63 initFromJSON(jsonPath);
64}
65
66void
67vpCannyEdgeDetection::initFromJSON(const std::string &jsonPath)
68{
69 std::ifstream file(jsonPath);
70 if (!file.good()) {
71 std::stringstream ss;
72 ss << "Problem opening file " << jsonPath << ". Make sure it exists and is readable" << std::endl;
73 throw vpException(vpException::ioError, ss.str());
74 }
75 json j;
76 try {
77 j = json::parse(file);
78 }
79 catch (json::parse_error &e) {
80 std::stringstream msg;
81 msg << "Could not parse JSON file : \n";
82 msg << e.what() << std::endl;
83 msg << "Byte position of error: " << e.byte;
84 throw vpException(vpException::ioError, msg.str());
85 }
86 *this = j; // Call from_json(const json& j, vpDetectionCircle2D& *this) to read json
87 file.close();
88 initGaussianFilters();
89}
90#endif
91
92void
93vpCannyEdgeDetection::initGaussianFilters()
94{
95 if ((m_gaussianKernelSize % 2) == 0) {
96 throw(vpException(vpException::badValue, "The Gaussian kernel size should be odd"));
97 }
98 m_fg.resize(1, (m_gaussianKernelSize + 1)/2);
99 vpImageFilter::getGaussianKernel(m_fg.data, m_gaussianKernelSize, m_gaussianStdev, false);
100 m_fgDg.resize(1, (m_gaussianKernelSize + 1)/2);
101 vpImageFilter::getGaussianDerivativeKernel(m_fgDg.data, m_gaussianKernelSize, m_gaussianStdev, false);
102}
103
104// // Detection methods
105#ifdef HAVE_OPENCV_CORE
107vpCannyEdgeDetection::detect(const cv::Mat &cv_I)
108{
110 vpImageConvert::convert(cv_I, I_gray);
111 return detect(I_gray);
112}
113#endif
114
117{
119 vpImageConvert::convert(I_color, I_gray);
120 return detect(I_gray);
121}
122
125{
126 // // Clearing the previous results
127 m_edgeMap.resize(I.getHeight(), I.getWidth(), 0);
128 m_edgeCandidateAndGradient.clear();
129 m_edgePointsCandidates.clear();
130
131 // // Step 1 and 2: filter the image and compute the gradient, if not given by the user
132 if (!m_areGradientAvailable) {
133 performFilteringAndGradientComputation(I);
134 }
135 m_areGradientAvailable = false; // Reset for next call
136
137 // // Step 3: edge thining
138 performEdgeThining();
139
140 // // Step 4: hysteresis thresholding
141 float upperThreshold = m_upperThreshold;
142
143 float lowerThreshold = m_lowerThreshold;
144 if (m_lowerThreshold < 0) {
145 // Applying Canny recommendation to have the upper threshold 3 times greater than the lower threshold.
146 lowerThreshold = m_upperThreshold / 3.f;
147 }
148
149 performHysteresisThresholding(lowerThreshold, upperThreshold);
150
151 // // Step 5: edge tracking
152 performEdgeTracking();
153 return m_edgeMap;
154}
155
156void
157vpCannyEdgeDetection::performFilteringAndGradientComputation(const vpImage<unsigned char> &I)
158{
160 m_dIx,
161 m_fg.data,
162 m_fgDg.data,
163 m_gaussianKernelSize
164 );
166 m_dIy,
167 m_fg.data,
168 m_fgDg.data,
169 m_gaussianKernelSize
170 );
171}
172
184int
185getThetaQuadrant(const float &absoluteTheta, int &dRowGradPlus, int &dRowGradMinus, int &dColGradPlus, int &dColGradMinus)
186{
187 if (absoluteTheta < 22.5) {
188 // Angles between -22.5 and 22.5 are mapped to be horizontal axis
189 dColGradMinus = -1;
190 dColGradPlus = 1;
191 dRowGradPlus = dRowGradMinus = 0;
192 return 0;
193 }
194 else if (absoluteTheta >= 22.5 && absoluteTheta < 67.5) {
195 // Angles between 22.5 and 67.5 are mapped to the diagonal 45degree
196 dRowGradMinus = dColGradMinus = -1;
197 dRowGradPlus = dColGradPlus = 1;
198 return 45;
199 }
200 else if (absoluteTheta >= 67.5 && absoluteTheta < 112.5) {
201 // Angles between 67.5 and 112.5 are mapped to the vertical axis
202 dColGradMinus = dColGradPlus = 0;
203 dRowGradMinus = -1;
204 dRowGradPlus = 1;
205 return 90;
206 }
207 else if (absoluteTheta >= 112.5 && absoluteTheta < 157.5) {
208 // Angles between 112.5 and 157.5 are mapped to the diagonal -45degree
209 dRowGradMinus = -1;
210 dColGradMinus = 1;
211 dRowGradPlus = 1;
212 dColGradPlus = -1;
213 return 135;
214 }
215 else {
216 // Angles greater than 157.5 are mapped to be horizontal axis
217 dColGradMinus = 1;
218 dColGradPlus = -1;
219 dRowGradMinus = dRowGradPlus = 0;
220 return 180;
221 }
222 return -1; // Should not reach this point
223}
224
234float
235getManhattanGradient(const vpImage<float> &dIx, const vpImage<float> &dIy, const int &row, const int &col)
236{
237 float grad = 0.;
238 int nbRows = dIx.getRows();
239 int nbCols = dIx.getCols();
240 if (row >= 0
241 && row < nbRows
242 && col >= 0
243 && col < nbCols
244 ) {
245 float dx = dIx[row][col];
246 float dy = dIy[row][col];
247 grad = std::abs(dx) + std::abs(dy);
248 }
249 return grad;
250}
251
261float
262getAbsoluteTheta(const vpImage<float> &dIx, const vpImage<float> &dIy, const int &row, const int &col)
263{
264 float absoluteTheta;
265 float dx = dIx[row][col];
266 float dy = dIy[row][col];
267
268 if (std::abs(dx) < std::numeric_limits<float>::epsilon()) {
269 absoluteTheta = 90.;
270 }
271 else {
272 absoluteTheta = static_cast<float>(vpMath::deg(std::abs(std::atan(dy / dx))));
273 }
274 return absoluteTheta;
275}
276void
277vpCannyEdgeDetection::performEdgeThining()
278{
279 vpImage<float> dIx = m_dIx;
280 vpImage<float> dIy = m_dIy;
281 int nbRows = m_dIx.getRows();
282 int nbCols = m_dIx.getCols();
283
284 for (int row = 0; row < nbRows; row++) {
285 for (int col = 0; col < nbCols; col++) {
286 // Computing the gradient orientation and magnitude
287 float grad = getManhattanGradient(dIx, dIy, row, col);
288
289 if (grad < std::numeric_limits<float>::epsilon()) {
290 // The gradient is almost null => ignoring the point
291 continue;
292 }
293
294 float absoluteTheta = getAbsoluteTheta(dIx, dIy, row, col);
295
296 // Getting the offset along the horizontal and vertical axes
297 // depending on the gradient orientation
298 int dRowGradPlus = 0, dRowGradMinus = 0;
299 int dColGradPlus = 0, dColGradMinus = 0;
300 int thetaQuadrant = getThetaQuadrant(absoluteTheta, dRowGradPlus, dRowGradMinus, dColGradPlus, dColGradMinus);
301
302 bool isGradientInTheSameDirection = true;
303 std::vector<std::pair<int, int> > pixelsSeen;
304 std::pair<int, int> bestPixel(row, col);
305 float bestGrad = grad;
306 int rowCandidate = row + dRowGradPlus;
307 int colCandidate = col + dColGradPlus;
308
309 while (isGradientInTheSameDirection) {
310 // Getting the gradients around the edge point
311 float gradPlus = getManhattanGradient(dIx, dIy, rowCandidate, colCandidate);
312 if (std::abs(gradPlus) < std::numeric_limits<float>::epsilon()) {
313 // The gradient is almost null => ignoring the point
314 isGradientInTheSameDirection = false;
315 break;
316 }
317 int dRowGradPlusCandidate = 0, dRowGradMinusCandidate = 0;
318 int dColGradPlusCandidate = 0, dColGradMinusCandidate = 0;
319 float absThetaPlus = getAbsoluteTheta(dIx, dIy, rowCandidate, colCandidate);
320 int thetaQuadrantCandidate = getThetaQuadrant(absThetaPlus, dRowGradPlusCandidate, dRowGradMinusCandidate, dColGradPlusCandidate, dColGradMinusCandidate);
321 if (thetaQuadrantCandidate != thetaQuadrant) {
322 isGradientInTheSameDirection = false;
323 break;
324 }
325
326 std::pair<int, int> pixelCandidate(rowCandidate, colCandidate);
327 if (gradPlus > bestGrad) {
328 // The gradient is higher with the nex pixel candidate
329 // Saving it
330 bestGrad = gradPlus;
331 pixelsSeen.push_back(bestPixel);
332 bestPixel = pixelCandidate;
333 }
334 else {
335 // Best pixel is still the best
336 pixelsSeen.push_back(pixelCandidate);
337 }
338 rowCandidate += dRowGradPlus;
339 colCandidate += dColGradPlus;
340 }
341
342 // Keeping the edge point that has the highest gradient
343 m_edgeCandidateAndGradient[bestPixel] = bestGrad;
344
345 // Suppressing non-maximum gradient
346 for (std::vector<std::pair<int, int> >::iterator it = pixelsSeen.begin(); it != pixelsSeen.end(); it++) {
347 // Suppressing non-maximum gradient
348 int row_temp = it->first;
349 int col_temp = it->second;
350 dIx[row_temp][col_temp] = 0.;
351 dIy[row_temp][col_temp] = 0.;
352 }
353 }
354 }
355}
356
357void
358vpCannyEdgeDetection::performHysteresisThresholding(const float &lowerThreshold, const float &upperThreshold)
359{
360 std::map<std::pair<unsigned int, unsigned int>, float>::iterator it;
361 for (it = m_edgeCandidateAndGradient.begin(); it != m_edgeCandidateAndGradient.end(); it++) {
362 if (it->second >= upperThreshold) {
363 m_edgePointsCandidates[it->first] = STRONG_EDGE;
364 }
365 else if (it->second >= lowerThreshold && it->second < upperThreshold) {
366 m_edgePointsCandidates[it->first] = WEAK_EDGE;
367 }
368 }
369}
370
371void
372vpCannyEdgeDetection::performEdgeTracking()
373{
374 std::map<std::pair<unsigned int, unsigned int>, EdgeType>::iterator it;
375 for (it = m_edgePointsCandidates.begin(); it != m_edgePointsCandidates.end(); it++) {
376 if (it->second == STRONG_EDGE) {
377 m_edgeMap[it->first.first][it->first.second] = 255;
378 }
379 else if (recursiveSearchForStrongEdge(it->first)) {
380 m_edgeMap[it->first.first][it->first.second] = 255;
381 }
382 }
383}
384
385bool
386vpCannyEdgeDetection::recursiveSearchForStrongEdge(const std::pair<unsigned int, unsigned int> &coordinates)
387{
388 bool hasFoundStrongEdge = false;
389 int nbRows = m_dIx.getRows();
390 int nbCols = m_dIx.getCols();
391 m_edgePointsCandidates[coordinates] = ON_CHECK;
392 for (int dr = -1; dr <= 1 && !hasFoundStrongEdge; dr++) {
393 for (int dc = -1; dc <= 1 && !hasFoundStrongEdge; dc++) {
394 int idRow = dr + (int)coordinates.first;
395 int idCol = dc + (int)coordinates.second;
396
397 // Checking if we are still looking for an edge in the limit of the image
398 if ((idRow < 0 || idRow >= nbRows)
399 || (idCol < 0 || idCol >= nbCols)
400 || (dr == 0 && dc == 0)
401 ) {
402 continue;
403 }
404
405 try {
406 std::pair<unsigned int, unsigned int> key_candidate(idRow, idCol);
407 // Checking if the 8-neighbor point is in the list of edge candidates
408 EdgeType type_candidate = m_edgePointsCandidates.at(key_candidate);
409 if (type_candidate == STRONG_EDGE) {
410 // The 8-neighbor point is a strong edge => the weak edge becomes a strong edge
411 hasFoundStrongEdge = true;
412 }
413 else if (type_candidate == WEAK_EDGE) {
414 hasFoundStrongEdge = recursiveSearchForStrongEdge(key_candidate);
415 }
416 }
417 catch (...) {
418 continue;
419 }
420 }
421 }
422 if (hasFoundStrongEdge) {
423 m_edgePointsCandidates[coordinates] = STRONG_EDGE;
424 m_edgeMap[coordinates.first][coordinates.second] = 255;
425 }
426 return hasFoundStrongEdge;
427}
Type * data
Address of the first element of the data array.
Definition vpArray2D.h:144
void resize(unsigned int nrows, unsigned int ncols, bool flagNullify=true, bool recopy_=true)
Definition vpArray2D.h:305
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 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.
Definition vpException.h:59
@ ioError
I/O error.
Definition vpException.h:79
@ badValue
Used to indicate that a value is not in the allowed range.
Definition vpException.h:85
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
static void getGradYGauss2D(const vpImage< ImageType > &I, vpImage< FilterType > &dIy, const FilterType *gaussianKernel, const FilterType *gaussianDerivativeKernel, unsigned int size)
static void getGradXGauss2D(const vpImage< ImageType > &I, vpImage< FilterType > &dIx, const FilterType *gaussianKernel, const FilterType *gaussianDerivativeKernel, unsigned int size)
static void getGaussianDerivativeKernel(FilterType *filter, unsigned int size, FilterType sigma=0., bool normalize=true)
static void getGaussianKernel(FilterType *filter, unsigned int size, FilterType sigma=0., bool normalize=true)
Definition of the vpImage class member functions.
Definition vpImage.h:135
unsigned int getWidth() const
Definition vpImage.h:242
void resize(unsigned int h, unsigned int w)
resize the image : Image initialization
Definition vpImage.h:795
unsigned int getCols() const
Definition vpImage.h:175
unsigned int getHeight() const
Definition vpImage.h:184
unsigned int getRows() const
Definition vpImage.h:214
static double deg(double rad)
Definition vpMath.h:106