From 99fb0d384e549d5cced97a69b1b5d7f6c7698591 Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Sat, 13 Jul 2024 23:33:55 +0300 Subject: [PATCH] Update edge_drawing.py Create edge_drawing_demo.cpp Update ximgproc.bib Update test_fld.cpp add CV_WRAP to read & write functions of Params a bug fixed --- modules/ximgproc/doc/ximgproc.bib | 12 +- .../include/opencv2/ximgproc/edge_drawing.hpp | 6 +- modules/ximgproc/samples/edge_drawing.py | 141 +++++++++---- .../ximgproc/samples/edge_drawing_demo.cpp | 185 ++++++++++++++++++ modules/ximgproc/src/edge_drawing.cpp | 5 +- modules/ximgproc/test/test_fld.cpp | 26 ++- 6 files changed, 326 insertions(+), 49 deletions(-) create mode 100644 modules/ximgproc/samples/edge_drawing_demo.cpp diff --git a/modules/ximgproc/doc/ximgproc.bib b/modules/ximgproc/doc/ximgproc.bib index 279bac1d11..3a0f82f09f 100644 --- a/modules/ximgproc/doc/ximgproc.bib +++ b/modules/ximgproc/doc/ximgproc.bib @@ -406,4 +406,14 @@ @article{jia2017fast pages={3665--3679}, year={2017}, publisher={IEEE} -} \ No newline at end of file +} + +@article{akinlar201782, + title = {ColorED: Color edge and segment detection by Edge Drawing (ED)}, + author = {Cuneyt Akinlar and Cihan Topal}, + journal = {Journal of Visual Communication and Image Representation}, + volume = {44}, + pages = {82-94}, + year = {2017}, + publisher={Academic Press} +} diff --git a/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp b/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp index ed583ea764..c401b8dd5b 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp @@ -15,7 +15,7 @@ namespace ximgproc //! @addtogroup ximgproc_edge_drawing //! @{ -/** @brief Class implementing the ED (EdgeDrawing) @cite topal2012edge, EDLines @cite akinlar2011edlines, EDPF @cite akinlar2012edpf and EDCircles @cite akinlar2013edcircles algorithms +/** @brief Class implementing the ED (EdgeDrawing) @cite topal2012edge, EDLines @cite akinlar2011edlines, EDPF @cite akinlar2012edpf, EDCircles @cite akinlar2013edcircles and ColorED @cite akinlar201782 algorithms. */ class CV_EXPORTS_W EdgeDrawing : public Algorithm @@ -66,8 +66,8 @@ class CV_EXPORTS_W EdgeDrawing : public Algorithm //! Default value is 1.3 CV_PROP_RW double MaxErrorThreshold; - void read(const FileNode& fn); - void write(FileStorage& fs) const; + CV_WRAP void read(const FileNode& fn); + CV_WRAP void write(FileStorage& fs) const; }; /** @brief Detects edges in a grayscale or color image and prepares them to detect lines and ellipses. diff --git a/modules/ximgproc/samples/edge_drawing.py b/modules/ximgproc/samples/edge_drawing.py index e5af20aa95..d379291b74 100644 --- a/modules/ximgproc/samples/edge_drawing.py +++ b/modules/ximgproc/samples/edge_drawing.py @@ -1,7 +1,24 @@ #!/usr/bin/python ''' -This example illustrates how to use cv.ximgproc.EdgeDrawing class. +This example script illustrates how to use cv.ximgproc.EdgeDrawing class. + +It uses the OpenCV library to load an image, and then use the EdgeDrawing class +to detect edges, lines, and ellipses. The detected features are then drawn and displayed. + +The main loop allows the user changing parameters of EdgeDrawing by pressing following keys: + +to toggle the grayscale conversion press 'space' key +to increase MinPathLength value press '/' key +to decrease MinPathLength value press '*' key +to increase MinLineLength value press '+' key +to decrease MinLineLength value press '-' key +to toggle NFAValidation value press 'n' key +to toggle PFmode value press 'p' key +to save parameters to file press 's' key +to load parameters from file press 'l' key + +The program exits when the Esc key is pressed. Usage: ed.py [] @@ -16,30 +33,25 @@ import random as rng import sys -def EdgeDrawingDemo(src, convert_to_gray): - +def EdgeDrawingDemo(src, ed, EDParams, convert_to_gray): rng.seed(12345) - ssrc = src.copy()*0 + ssrc = np.zeros_like(src) lsrc = src.copy() esrc = src.copy() - ed = cv.ximgproc.createEdgeDrawing() + img_to_detect = cv.cvtColor(src, cv.COLOR_BGR2GRAY) if convert_to_gray else src - # you can change parameters (refer the documentation to see all parameters) - EDParams = cv.ximgproc_EdgeDrawing_Params() - EDParams.MinPathLength = 50 # try changing this value between 5 to 1000 - EDParams.PFmode = False # defaut value try to swich it to True - EDParams.MinLineLength = 10 # try changing this value between 5 to 100 - EDParams.NFAValidation = True # defaut value try to swich it to False - - ed.setParams(EDParams) + cv.imshow("source image", img_to_detect) - if convert_to_gray: - img_to_detect = cv.cvtColor(src, cv.COLOR_BGR2GRAY) - else: - img_to_detect = src + print("") + print("convert_to_gray:", convert_to_gray) + print("MinPathLength:", EDParams.MinPathLength) + print("MinLineLength:", EDParams.MinLineLength) + print("PFmode:", EDParams.PFmode) + print("NFAValidation:", EDParams.NFAValidation) - cv.imshow("source image", img_to_detect) + tm = cv.TickMeter() + tm.start() # Detect edges # you should call this before detectLines() and detectEllipses() @@ -49,48 +61,91 @@ def EdgeDrawingDemo(src, convert_to_gray): lines = ed.detectLines() ellipses = ed.detectEllipses() - #Draw detected edge segments - for i in range(len(segments)): - color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256)) - cv.polylines(ssrc, [segments[i]], False, color, 1, cv.LINE_8) + tm.stop() + + print("Detection time : {:.2f} ms. using the parameters above".format(tm.getTimeMilli())) + + # Draw detected edge segments + for segment in segments: + color = (rng.randint(0, 256), rng.randint(0, 256), rng.randint(0, 256)) + cv.polylines(ssrc, [segment], False, color, 1, cv.LINE_8) cv.imshow("detected edge segments", ssrc) - #Draw detected lines - if lines is not None: # Check if the lines have been found and only then iterate over these and add them to the image + # Draw detected lines + if lines is not None: # Check if the lines have been found and only then iterate over these and add them to the image lines = np.uint16(np.around(lines)) - for i in range(len(lines)): - cv.line(lsrc, (lines[i][0][0], lines[i][0][1]), (lines[i][0][2], lines[i][0][3]), (0, 0, 255), 1, cv.LINE_AA) + for line in lines: + cv.line(lsrc, (line[0][0], line[0][1]), (line[0][2], line[0][3]), (0, 0, 255), 1, cv.LINE_AA) cv.imshow("detected lines", lsrc) - #Draw detected circles and ellipses - if ellipses is not None: # Check if circles and ellipses have been found and only then iterate over these and add them to the image - for i in range(len(ellipses)): - center = (int(ellipses[i][0][0]), int(ellipses[i][0][1])) - axes = (int(ellipses[i][0][2])+int(ellipses[i][0][3]),int(ellipses[i][0][2])+int(ellipses[i][0][4])) - angle = ellipses[i][0][5] - color = (0, 0, 255) - if ellipses[i][0][2] == 0: - color = (0, 255, 0) - cv.ellipse(esrc, center, axes, angle,0, 360, color, 2, cv.LINE_AA) + # Draw detected circles and ellipses + if ellipses is not None: # Check if circles and ellipses have been found and only then iterate over these and add them to the image + for ellipse in ellipses: + center = (int(ellipse[0][0]), int(ellipse[0][1])) + axes = (int(ellipse[0][2] + ellipse[0][3]), int(ellipse[0][2] + ellipse[0][4])) + angle = ellipse[0][5] - cv.imshow("detected circles and ellipses", esrc) - print('Press any key to swich color conversion code. Press Esc to exit.') + color = (0, 255, 0) if ellipse[0][2] == 0 else (0, 0, 255) + cv.ellipse(esrc, center, axes, angle, 0, 360, color, 2, cv.LINE_AA) -if __name__ == '__main__': - print(__doc__) + cv.imshow("detected circles and ellipses", esrc) + +def main(): try: fn = sys.argv[1] except IndexError: fn = 'board.jpg' src = cv.imread(cv.samples.findFile(fn)) + if src is None: + print("Error loading image") + return + + ed = cv.ximgproc.createEdgeDrawing() + + # Set parameters (refer to the documentation for all parameters) + EDParams = cv.ximgproc_EdgeDrawing_Params() + EDParams.MinPathLength = 10 # try changing this value by pressing '/' and '*' keys + EDParams.MinLineLength = 10 # try changing this value by pressing '+' and '-' keys + EDParams.PFmode = False # default value is False, try switching by pressing 'p' key + EDParams.NFAValidation = True # default value is True, try switching by pressing 'n' key convert_to_gray = True key = 0 - while key is not 27: - EdgeDrawingDemo(src, convert_to_gray) + + while key != 27: + ed.setParams(EDParams) + EdgeDrawingDemo(src, ed, EDParams, convert_to_gray) key = cv.waitKey() - convert_to_gray = not convert_to_gray + if key == 32: # space key + convert_to_gray = not convert_to_gray + if key == 112: # 'p' key + EDParams.PFmode = not EDParams.PFmode + if key == 110: # 'n' key + EDParams.NFAValidation = not EDParams.NFAValidation + if key == 43: # '+' key + EDParams.MinLineLength = EDParams.MinLineLength + 5 + if key == 45: # '-' key + EDParams.MinLineLength = max(0, EDParams.MinLineLength - 5) + if key == 47: # '/' key + EDParams.MinPathLength = EDParams.MinPathLength + 20 + if key == 42: # '*' key + EDParams.MinPathLength = max(0, EDParams.MinPathLength - 20) + if key == 115: # 's' key + fs = cv.FileStorage("ed-params.xml",cv.FileStorage_WRITE) + EDParams.write(fs) + fs.release() + print("parameters saved to ed-params.xml") + if key == 108: # 'l' key + fs = cv.FileStorage("ed-params.xml",cv.FileStorage_READ) + if fs.isOpened(): + EDParams.read(fs.root()) + fs.release() + print("parameters loaded from ed-params.xml") + +if __name__ == '__main__': + print(__doc__) + main() cv.destroyAllWindows() diff --git a/modules/ximgproc/samples/edge_drawing_demo.cpp b/modules/ximgproc/samples/edge_drawing_demo.cpp new file mode 100644 index 0000000000..6a815183d8 --- /dev/null +++ b/modules/ximgproc/samples/edge_drawing_demo.cpp @@ -0,0 +1,185 @@ +/* edge_drawing.cpp + +This example illustrates how to use cv.ximgproc.EdgeDrawing class. + +It uses the OpenCV library to load an image, and then use the EdgeDrawing class +to detect edges, lines, and ellipses. The detected features are then drawn and displayed. + +The main loop allows the user changing parameters of EdgeDrawing by pressing following keys: + +to toggle the grayscale conversion press 'space' key +to increase MinPathLength value press '/' key +to decrease MinPathLength value press '*' key +to increase MinLineLength value press '+' key +to decrease MinLineLength value press '-' key +to toggle NFAValidation value press 'n' key +to toggle PFmode value press 'p' key +to save parameters to file press 's' key +to load parameters from file press 'l' key + +The program exits when the Esc key is pressed. +*/ + +#include +#include +#include +#include + +void EdgeDrawingDemo(const cv::Mat src, cv::Ptr ed, bool convert_to_gray); + +void EdgeDrawingDemo(const cv::Mat src, cv::Ptr ed, bool convert_to_gray) +{ + cv::Mat ssrc = cv::Mat::zeros(src.size(), src.type()); + cv::Mat lsrc = src.clone(); + cv::Mat esrc = src.clone(); + + std::cout << std::endl << "convert_to_gray: " << convert_to_gray << std::endl; + std::cout << "MinPathLength: " << ed->params.MinPathLength << std::endl; + std::cout << "MinLineLength: " << ed->params.MinLineLength << std::endl; + std::cout << "PFmode: " << ed->params.PFmode << std::endl; + std::cout << "NFAValidation: " << ed->params.NFAValidation << std::endl; + + cv::TickMeter tm; + tm.start(); + + cv::Mat img_to_detect; + + if (convert_to_gray) + { + cv::cvtColor(src, img_to_detect, cv::COLOR_BGR2GRAY); + } + else + { + img_to_detect = src; + } + + cv::imshow("source image", img_to_detect); + + tm.start(); + + // Detect edges + ed->detectEdges(img_to_detect); + + std::vector> segments = ed->getSegments(); + std::vector lines; + ed->detectLines(lines); + std::vector ellipses; + ed->detectEllipses(ellipses); + + tm.stop(); + + cv::RNG& rng = cv::theRNG(); + cv::setRNGSeed(0); + + // Draw detected edge segments + for (const auto& segment : segments) + { + cv::Scalar color(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256)); + cv::polylines(ssrc, segment, false, color, 1, cv::LINE_8); + } + + cv::imshow("detected edge segments", ssrc); + + // Draw detected lines + if (!lines.empty()) // Check if the lines have been found and only then iterate over these and add them to the image + { + for (size_t i = 0; i < lines.size(); i++) + { + cv::line(lsrc, cv::Point2d(lines[i][0], lines[i][1]), cv::Point2d(lines[i][2], lines[i][3]), cv::Scalar(0, 0, 255), 1, cv::LINE_AA); + } + } + + cv::imshow("detected lines", lsrc); + + // Draw detected circles and ellipses + if (!ellipses.empty()) // Check if circles and ellipses have been found and only then iterate over these and add them to the image + { + for (const auto& ellipse : ellipses) + { + cv::Point center((int)ellipse[0], (int)ellipse[1]); + cv::Size axes((int)ellipse[2] + (int)ellipse[3], (int)ellipse[2] + (int)ellipse[4]); + double angle(ellipse[5]); + cv::Scalar color = (ellipse[2] == 0) ? cv::Scalar(0, 255, 0) : cv::Scalar(0, 0, 255); + cv::ellipse(esrc, center, axes, angle, 0, 360, color, 1, cv::LINE_AA); + } + } + + cv::imshow("detected circles and ellipses", esrc); + std::cout << "Total Detection Time : " << tm.getTimeMilli() << "ms." << std::endl; +} + +int main(int argc, char** argv) +{ + std::string filename = (argc > 1) ? argv[1] : "board.jpg"; + cv::Mat src = cv::imread(cv::samples::findFile(filename)); + + if (src.empty()) + { + std::cerr << "Error: Could not open or find the image!" << std::endl; + return -1; + } + + cv::Ptr ed = cv::ximgproc::createEdgeDrawing(); + + // Set parameters (refer to the documentation for all parameters) + ed->params.MinPathLength = 10; // try changing this value by pressing '/' and '*' keys + ed->params.MinLineLength = 10; // try changing this value by pressing '+' and '-' keys + ed->params.PFmode = false; // default value is false, try switching by pressing 'p' key + ed->params.NFAValidation = true; // default value is true, try switching by pressing 'n' key + + bool convert_to_gray = true; + int key = 0; + + while (key != 27) + { + EdgeDrawingDemo(src, ed, convert_to_gray); + key = cv::waitKey(0); + + switch (key) + { + case 32: // space key + convert_to_gray = !convert_to_gray; + break; + case 'p': // 'p' key + ed->params.PFmode = !ed->params.PFmode; + break; + case 'n': // 'n' key + ed->params.NFAValidation = !ed->params.NFAValidation; + break; + case '+': // '+' key + ed->params.MinLineLength = std::max(0, ed->params.MinLineLength + 5); + break; + case '-': // '-' key + ed->params.MinLineLength = std::max(0, ed->params.MinLineLength - 5); + break; + case '/': // '/' key + ed->params.MinPathLength += 20; + break; + case '*': // '*' key + ed->params.MinPathLength = std::max(0, ed->params.MinPathLength - 20); + break; + case 's': // 's' key + { + cv::FileStorage fs("ed-params.xml", cv::FileStorage::WRITE); + ed->params.write(fs); + fs.release(); + std::cout << "Parameters saved to ed-params.xml" << std::endl; + } + break; + case 'l': // 'l' key + { + cv::FileStorage fs("ed-params.xml", cv::FileStorage::READ); + if (fs.isOpened()) + { + ed->params.read(fs.root()); + fs.release(); + std::cout << "Parameters loaded from ed-params.xml" << std::endl; + } + } + break; + default: + break; + } + } + return 0; +} diff --git a/modules/ximgproc/src/edge_drawing.cpp b/modules/ximgproc/src/edge_drawing.cpp index 47ec59b256..4771e5a467 100644 --- a/modules/ximgproc/src/edge_drawing.cpp +++ b/modules/ximgproc/src/edge_drawing.cpp @@ -384,6 +384,9 @@ void EdgeDrawingImpl::detectEdges(InputArray src) if (anchorThresh < 0) anchorThresh = 0; + if (params.MinPathLength < 3) + params.MinPathLength = 3; + segmentNos = 0; anchorNos = 0; anchorPoints.clear(); @@ -1390,7 +1393,7 @@ void EdgeDrawingImpl::detectLines(OutputArray _lines) max_distance_between_two_lines = params.MaxDistanceBetweenTwoLines; max_error = params.MaxErrorThreshold; - if (min_line_len == -1) // If no initial value given, compute it + if (min_line_len < 0) // If no initial value given, compute it min_line_len = ComputeMinLineLength(); if (min_line_len < 9) // avoids small line segments in the result. Might be deleted! diff --git a/modules/ximgproc/test/test_fld.cpp b/modules/ximgproc/test/test_fld.cpp index b9acac8c5d..c2fcf15014 100644 --- a/modules/ximgproc/test/test_fld.cpp +++ b/modules/ximgproc/test/test_fld.cpp @@ -297,6 +297,29 @@ TEST_F(ximgproc_ED, ManySmallCircles) EXPECT_EQ(ellipses.size(), ellipses_size); } +TEST_F(ximgproc_ED, ManySmallCirclesPF) +{ + string picture_name = "cv/imgproc/beads.jpg"; + + string filename = cvtest::TS::ptr()->get_data_path() + picture_name; + test_image = imread(filename, IMREAD_GRAYSCALE); + EXPECT_FALSE(test_image.empty()) << "Invalid test image: " << filename; + + detector->params.PFmode = true; + vector ellipses; + detector->detectEdges(test_image); + detector->detectEllipses(ellipses); + detector->detectLines(lines); + + size_t segments_size = 2717; + size_t lines_size = 6060; + size_t ellipses_size = 2447; + EXPECT_EQ(detector->getSegments().size(), segments_size); + EXPECT_GE(lines.size(), lines_size); + EXPECT_LE(lines.size(), lines_size + 2); + EXPECT_EQ(ellipses.size(), ellipses_size); +} + TEST_F(ximgproc_ED, ManySmallCirclesColor) { string picture_name = "cv/imgproc/beads.jpg"; @@ -305,13 +328,14 @@ TEST_F(ximgproc_ED, ManySmallCirclesColor) test_image = imread(filename, IMREAD_COLOR); EXPECT_FALSE(test_image.empty()) << "Invalid test image: " << filename; + detector->params.MinLineLength = 10; vector ellipses; detector->detectEdges(test_image); detector->detectEllipses(ellipses); detector->detectLines(lines); detector->detectEllipses(ellipses); size_t segments_size = 6230; - size_t lines_size = 13715; + size_t lines_size = 11133; size_t ellipses_size = 2431; EXPECT_EQ(detector->getSegments().size(), segments_size); EXPECT_GE(lines.size(), lines_size);