From aa850cd83b43668f08a9aefdc0cfa9fef11d6389 Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Tue, 13 Apr 2021 10:53:28 +0300 Subject: [PATCH] EdgeDrawing Improvements 1 --- modules/ximgproc/include/opencv2/ximgproc.hpp | 11 + .../include/opencv2/ximgproc/edge_drawing.hpp | 11 - modules/ximgproc/src/edge_drawing.cpp | 235 ++++++++++-------- modules/ximgproc/src/edge_drawing_common.hpp | 3 +- 4 files changed, 147 insertions(+), 113 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc.hpp b/modules/ximgproc/include/opencv2/ximgproc.hpp index a1d1becf12d..5f056da7d27 100644 --- a/modules/ximgproc/include/opencv2/ximgproc.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc.hpp @@ -78,6 +78,17 @@ i.e. algorithms which somehow takes into account pixel affinities in natural ima @defgroup ximgproc_edge_drawing EdgeDrawing +EDGE DRAWING LIBRARY FOR GEOMETRIC FEATURE EXTRACTION AND VALIDATION + +Edge Drawing (ED) algorithm is an proactive approach on edge detection problem. In contrast to many other existing edge detection algorithms which follow a subtractive +approach (i.e. after applying gradient filters onto an image eliminating pixels w.r.t. several rules, e.g. non-maximal suppression and hysteresis in Canny), ED algorithm +works via an additive strategy, i.e. it picks edge pixels one by one, hence the name Edge Drawing. Then we process those random shaped edge segments to extract higher level +edge features, i.e. lines, circles, ellipses, etc. The popular method of extraction edge pixels from the thresholded gradient magnitudes is non-maximal supression that tests +every pixel whether it has the maximum gradient response along its gradient direction and eliminates if it does not. However, this method does not check status of the +neighboring pixels, and therefore might result low quality (in terms of edge continuity, smoothness, thinness, localization) edge segments. Instead of non-maximal supression, +ED points a set of edge pixels and join them by maximizing the total gradient response of edge segments. Therefore it can extract high quality edge segments without need for +an additional hysteresis step. + @defgroup ximgproc_fourier Fourier descriptors @} */ diff --git a/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp b/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp index cb0bc735aad..e33b23b0897 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp @@ -16,17 +16,6 @@ namespace ximgproc //! @{ /** @brief Class implementing the ED (EdgeDrawing) @cite topal2012edge, EDLines @cite akinlar2011edlines, EDPF @cite akinlar2012edpf and EDCircles @cite akinlar2013edcircles algorithms - -EDGE DRAWING LIBRARY FOR GEOMETRIC FEATURE EXTRACTION AND VALIDATION - -Edge Drawing (ED) algorithm is an proactive approach on edge detection problem. In contrast to many other existing edge detection algorithms which follow a subtractive -approach (i.e. after applying gradient filters onto an image eliminating pixels w.r.t. several rules, e.g. non-maximal suppression and hysteresis in Canny), ED algorithm -works via an additive strategy, i.e. it picks edge pixels one by one, hence the name Edge Drawing. Then we process those random shaped edge segments to extract higher level -edge features, i.e. lines, circles, ellipses, etc. The popular method of extraction edge pixels from the thresholded gradient magnitudes is non-maximal supressiun that tests -every pixel whether it has the maximum gradient response along its gradient direction and eliminates if it does not. However, this method does not check status of the -neighboring pixels, and therefore might result low quality (in terms of edge continuity, smoothness, thinness, localization) edge segments. Instead of non-maximal supression, -ED points a set of edge pixels and join them by maximizing the total gradient response of edge segments. Therefore it can extract high quality edge segments without need for -an additional hysteresis step. */ class CV_EXPORTS_W EdgeDrawing : public Algorithm diff --git a/modules/ximgproc/src/edge_drawing.cpp b/modules/ximgproc/src/edge_drawing.cpp index bfabf704ce5..8c943a7887c 100644 --- a/modules/ximgproc/src/edge_drawing.cpp +++ b/modules/ximgproc/src/edge_drawing.cpp @@ -12,6 +12,86 @@ namespace cv namespace ximgproc { +struct ComputeGradientBody : ParallelLoopBody +{ +void operator() (const Range& range) const CV_OVERRIDE; + +Mat_ src; +mutable Mat_ gradImage; +mutable Mat_ dirImage; +int gradThresh; +int op; +bool SumFlag; +int* grads; +bool PFmode; +}; + +void ComputeGradientBody::operator() (const Range& range) const +{ + const int last_col = src.cols - 1; + int gx = 0; + int gy = 0; + int sum; + + for (int y = range.start; y < range.end; ++y) + { + const uchar* srcPrevRow = src[y - 1]; + const uchar* srcCurRow = src[y]; + const uchar* srcNextRow = src[y + 1]; + + ushort* gradRow = gradImage[y]; + uchar* dirRow = dirImage[y]; + + for (int x = 1; x < last_col; ++x) + { + int com1 = srcNextRow[x + 1] - srcPrevRow[x - 1]; + int com2 = srcPrevRow[x + 1] - srcNextRow[x - 1]; + + switch (op) + { + case EdgeDrawing::PREWITT: + gx = abs(com1 + com2 + srcCurRow[x + 1] - srcCurRow[x - 1]); + gy = abs(com1 - com2 + srcNextRow[x] - srcPrevRow[x]); + break; + case EdgeDrawing::SOBEL: + gx = abs(com1 + com2 + 2 * (srcCurRow[x + 1] - srcCurRow[x - 1])); + gy = abs(com1 - com2 + 2 * (srcNextRow[x] - srcPrevRow[x])); + break; + case EdgeDrawing::SCHARR: + gx = abs(3 * (com1 + com2) + 10 * (srcCurRow[x + 1] - srcCurRow[x - 1])); + gy = abs(3 * (com1 - com2) + 10 * (srcNextRow[x] - srcPrevRow[x])); + break; + case EdgeDrawing::LSD: + // com1 and com2 differs from previous operators, because LSD has 2x2 kernel + com1 = srcNextRow[x + 1] - srcCurRow[x]; + com2 = srcCurRow[x + 1] - srcNextRow[x]; + + gx = abs(com1 + com2); + gy = abs(com1 - com2); + break; + } + + if (SumFlag) + sum = gx + gy; + else + sum = (int)sqrt((double)gx * gx + gy * gy); + + gradRow[x] = (ushort)sum; + + if (PFmode) + grads[sum]++; + + if (sum >= gradThresh) + { + if (gx >= gy) + dirRow[x] = EDGE_VERTICAL; + else + dirRow[x] = EDGE_HORIZONTAL; + } + } + } +} + class EdgeDrawingImpl : public EdgeDrawing { public: @@ -23,6 +103,7 @@ class EdgeDrawingImpl : public EdgeDrawing }; EdgeDrawingImpl(); + ~EdgeDrawingImpl(); void detectEdges(InputArray src) CV_OVERRIDE; void getEdgeImage(OutputArray dst) CV_OVERRIDE; void getGradientImage(OutputArray dst) CV_OVERRIDE; @@ -47,6 +128,7 @@ class EdgeDrawingImpl : public EdgeDrawing double divForTestSegment; double* dH; + int* grads; int np; private: @@ -64,9 +146,9 @@ class EdgeDrawingImpl : public EdgeDrawing Mat edgeImage; Mat gradImage; - + Mat dirImage; uchar *dirImg; // pointer to direction image data - short *gradImg; // pointer to gradient image data + ushort *gradImg; // pointer to gradient image data int op; // edge detection operator int gradThresh; // gradient threshold @@ -251,6 +333,16 @@ void EdgeDrawingImpl::write(cv::FileStorage& fs) const EdgeDrawingImpl::EdgeDrawingImpl() { params = EdgeDrawing::Params(); + nfa = new NFALUT(1, 1/2, 1, 1); + dH = new double[MAX_GRAD_VALUE]; + grads = new int[MAX_GRAD_VALUE]; +} + +EdgeDrawingImpl::~EdgeDrawingImpl() +{ + delete nfa; + delete[] dH; + delete[] grads; } void EdgeDrawingImpl::detectEdges(InputArray src) @@ -259,13 +351,6 @@ void EdgeDrawingImpl::detectEdges(InputArray src) anchorThresh = params.AnchorThresholdValue; op = params.EdgeDetectionOperator; - if (params.PFmode) - { - op = PREWITT; - gradThresh = 11; - anchorThresh = 3; - } - // Check parameters for sanity if (op < 0 || op > 3) op = 0; @@ -289,7 +374,8 @@ void EdgeDrawingImpl::detectEdges(InputArray src) width = srcImage.cols; edgeImage = Mat(height, width, CV_8UC1, Scalar(0)); // initialize edge Image - gradImage = Mat(height, width, CV_16SC1); // gradImage contains short values + gradImage = Mat(height, width, CV_16UC1); // gradImage contains short values + dirImage = Mat(height, width, CV_8UC1); if (params.Sigma < 1.0) smoothImage = srcImage; @@ -300,11 +386,15 @@ void EdgeDrawingImpl::detectEdges(InputArray src) // Assign Pointers from Mat's data smoothImg = smoothImage.data; - gradImg = (short*)gradImage.data; + gradImg = (ushort*)gradImage.data; edgeImg = edgeImage.data; + dirImg = dirImage.data; - Mat _dirImg = Mat(height, width, CV_8UC1); - dirImg = _dirImg.data; + if (params.PFmode) + { + memset(dH, 0, sizeof(double) * MAX_GRAD_VALUE); + memset(grads, 0, sizeof(int) * MAX_GRAD_VALUE); + } ComputeGradient(); // COMPUTE GRADIENT & EDGE DIRECTION MAPS ComputeAnchorPoints(); // COMPUTE ANCHORS @@ -312,6 +402,15 @@ void EdgeDrawingImpl::detectEdges(InputArray src) if (params.PFmode) { + // Compute probability function H + int size = (width - 2) * (height - 2); + + for (int i = MAX_GRAD_VALUE - 1; i > 0; i--) + grads[i - 1] += grads[i]; + + for (int i = 0; i < MAX_GRAD_VALUE; i++) + dH[i] = (double)grads[i] / ((double)size); + divForTestSegment = 2.25; // Some magic number :-) memset(edgeImg, 0, width * height); // clear edge image np = 0; @@ -335,14 +434,12 @@ void EdgeDrawingImpl::getEdgeImage(OutputArray _dst) edgeImage.copyTo(_dst); } - void EdgeDrawingImpl::getGradientImage(OutputArray _dst) { if (!gradImage.empty()) - convertScaleAbs(gradImage, _dst); + gradImage.copyTo(_dst); } - std::vector > EdgeDrawingImpl::getSegments() { return segmentPoints; @@ -350,90 +447,27 @@ std::vector > EdgeDrawingImpl::getSegments() void EdgeDrawingImpl::ComputeGradient() { -#define MAX_GRAD_VALUE 128*256 - dH = new double[MAX_GRAD_VALUE]; - memset(dH, 0, sizeof(double) * MAX_GRAD_VALUE); - - int* grads = new int[MAX_GRAD_VALUE]; - memset(grads, 0, sizeof(int) * MAX_GRAD_VALUE); - - // Initialize gradient image for row = 0, row = height-1, column=0, column=width-1 for (int j = 0; j < width; j++) { - gradImg[j] = gradImg[(height - 1) * width + j] = (short)gradThresh - 1; + gradImg[j] = gradImg[(height - 1) * width + j] = (ushort)gradThresh - 1; } for (int i = 1; i < height - 1; i++) { - gradImg[i * width] = gradImg[(i + 1) * width - 1] = (short)gradThresh - 1; - } - - for (int i = 1; i < height - 1; i++) - { - for (int j = 1; j < width - 1; j++) - { - int com1 = smoothImg[(i + 1) * width + j + 1] - smoothImg[(i - 1) * width + j - 1]; - int com2 = smoothImg[(i - 1) * width + j + 1] - smoothImg[(i + 1) * width + j - 1]; - - int gx(0); - int gy(0); - - switch (op) - { - case PREWITT: - gx = abs(com1 + com2 + (smoothImg[i * width + j + 1] - smoothImg[i * width + j - 1])); - gy = abs(com1 - com2 + (smoothImg[(i + 1) * width + j] - smoothImg[(i - 1) * width + j])); - break; - case SOBEL: - gx = abs(com1 + com2 + 2 * (smoothImg[i * width + j + 1] - smoothImg[i * width + j - 1])); - gy = abs(com1 - com2 + 2 * (smoothImg[(i + 1) * width + j] - smoothImg[(i - 1) * width + j])); - break; - case SCHARR: - gx = abs(3 * (com1 + com2) + 10 * (smoothImg[i * width + j + 1] - smoothImg[i * width + j - 1])); - gy = abs(3 * (com1 - com2) + 10 * (smoothImg[(i + 1) * width + j] - smoothImg[(i - 1) * width + j])); - break; - case LSD: - // com1 and com2 differs from previous operators, because LSD has 2x2 kernel - com1 = smoothImg[(i + 1) * width + j + 1] - smoothImg[i * width + j]; - com2 = smoothImg[i * width + j + 1] - smoothImg[(i + 1) * width + j]; - - gx = abs(com1 + com2); - gy = abs(com1 - com2); - break; - } - - int sum; - - if (params.SumFlag) - sum = gx + gy; - else - sum = (int)sqrt((double)gx * gx + gy * gy); - - int index = i * width + j; - gradImg[index] = (short)sum; - - grads[(int)sum]++; - - if (sum >= gradThresh) - { - if (gx >= gy) - dirImg[index] = EDGE_VERTICAL; - else - dirImg[index] = EDGE_HORIZONTAL; - } - } + gradImg[i * width] = gradImg[(i + 1) * width - 1] = (ushort)gradThresh - 1; } - // Compute probability function H - int size = (width - 2) * (height - 2); - - for (int i = MAX_GRAD_VALUE - 1; i > 0; i--) - grads[i - 1] += grads[i]; - - for (int i = 0; i < MAX_GRAD_VALUE; i++) - dH[i] = (double)grads[i] / ((double)size); - -#undef MAX_GRAD_VALUE + ComputeGradientBody body; + body.src = smoothImage; + body.gradImage = gradImage; + body.dirImage = dirImage; + body.gradThresh = gradThresh; + body.SumFlag = params.SumFlag; + body.op = op; + body.grads = grads; + body.PFmode = params.PFmode; + + parallel_for_(Range(1, smoothImage.rows - 1), body); } void EdgeDrawingImpl::ComputeAnchorPoints() @@ -526,7 +560,6 @@ void EdgeDrawingImpl::JoinAnchorPointsUsingSortedAnchors() stack[top].c = j; stack[top].dir = UP; stack[top].parent = 0; - } else { @@ -1273,12 +1306,12 @@ void EdgeDrawingImpl::detectLines(OutputArray _lines) #define PRECISON_ANGLE 22.5 precision = (PRECISON_ANGLE / 180) * CV_PI; - double prob = 0.125; #undef PRECISON_ANGLE - if (params.NFAValidation) + if (nfa->LUTSize == 1 && params.NFAValidation) { int lutSize = (width + height) / 8; + double prob = 1.0 / 8; // probability of alignment nfa = new NFALUT(lutSize, prob, width, height); ValidateLineSegments(); } @@ -2787,7 +2820,6 @@ void EdgeDrawingImpl::GenerateCandidateCircles() void EdgeDrawingImpl::DetectArcs() { - double maxLineLengthThreshold = MAX(width, height) / 5; double MIN_ANGLE = CV_PI / 30; // 6 degrees @@ -3155,8 +3187,6 @@ void EdgeDrawingImpl::DetectArcs() void EdgeDrawingImpl::ValidateCircles(bool validate) { precision = CV_PI / 16; // Alignment precision - double prob = 1.0 / 8; // probability of alignment - double max = width; if (height > max) max = height; @@ -3167,8 +3197,12 @@ void EdgeDrawingImpl::ValidateCircles(bool validate) double* px = new double[8 * (width + height)]; double* py = new double[8 * (width + height)]; - int lutSize = (width + height) / 8; - nfa = new NFALUT(lutSize, prob, width, height); // create look up table + if (nfa->LUTSize == 1 && params.NFAValidation) + { + int lutSize = (width + height) / 8; + double prob = 1.0 / 8; // probability of alignment + nfa = new NFALUT(lutSize, prob, width, height); // create look up table + } // Validate circles & ellipses bool validateAgain; @@ -3412,7 +3446,6 @@ void EdgeDrawingImpl::ValidateCircles(bool validate) delete[] px; delete[] py; - delete nfa; } void EdgeDrawingImpl::JoinCircles() diff --git a/modules/ximgproc/src/edge_drawing_common.hpp b/modules/ximgproc/src/edge_drawing_common.hpp index 990c1fec78f..0b6a7555289 100644 --- a/modules/ximgproc/src/edge_drawing_common.hpp +++ b/modules/ximgproc/src/edge_drawing_common.hpp @@ -37,6 +37,7 @@ // Ellipse thresholds #define CANDIDATE_ELLIPSE_RATIO 0.50 // 50% -- If 50% of the ellipse is detected, it may be candidate for validation #define ELLIPSE_ERROR 1.50 // Used for ellipses. (used to be 1.65 for what reason?) +#define MAX_GRAD_VALUE 128*256 using namespace std; using namespace cv; @@ -64,7 +65,7 @@ class NFALUT NFALUT::NFALUT(int size, double _prob, int _w, int _h) { - LUTSize = size; + LUTSize = size > 60 ? 60 : size; LUT = new int[LUTSize]; w = _w; h = _h;