diff --git a/doc/opencv.bib b/doc/opencv.bib index be05ecb8d2..79883296d1 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -1553,3 +1553,12 @@ year = {2014}, url = {http://www.marcozuliani.com/docs/RANSAC4Dummies.pdf} } +@article{Aggarwal1985, + author = {Aggarwal, A. and Chang, J. and Yap, Chee K.}, + title = {Minimum area circumscribing Polygons}, + year = {1985}, + pages = {112--117}, + journal = {The Visual Computer}, + volume = {7}, + url = {https://doi.org/10.1007/BF01898354} +} diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 8b4eab43cd..395b6c4904 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -3070,10 +3070,9 @@ Also, the special values #THRESH_OTSU or #THRESH_TRIANGLE may be combined with o above values. In these cases, the function determines the optimal threshold value using the Otsu's or Triangle algorithm and uses it instead of the specified thresh. -@note Currently, the Otsu's method is implemented only for CV_8UC1 and CV_16UC1 images, -and the Triangle's method is implemented only for CV_8UC1 images. +@note Currently, the Otsu's and Triangle methods are implemented only for 8-bit single-channel images. -@param src input array (multiple-channel, CV_8U, CV_16S, CV_16U, CV_32F or CV_64F). +@param src input array (multiple-channel, 8-bit or 32-bit floating point). @param dst output array of the same size and type and the same number of channels as src. @param thresh threshold value. @param maxval maximum value to use with the #THRESH_BINARY and #THRESH_BINARY_INV thresholding @@ -3081,30 +3080,11 @@ types. @param type thresholding type (see #ThresholdTypes). @return the computed threshold value if Otsu's or Triangle methods used. -@sa thresholdWithMask, adaptiveThreshold, findContours, compare, min, max +@sa adaptiveThreshold, findContours, compare, min, max */ CV_EXPORTS_W double threshold( InputArray src, OutputArray dst, double thresh, double maxval, int type ); -/** @brief Same as #threshold, but with an optional mask - -@note If the mask is empty, #thresholdWithMask is equivalent to #threshold. -If the mask is not empty, dst *must* be of the same size and type as src, so that -outliers pixels are left as-is - -@param src input array (multiple-channel, 8-bit or 32-bit floating point). -@param dst output array of the same size and type and the same number of channels as src. -@param mask optional mask (same size as src, 8-bit). -@param thresh threshold value. -@param maxval maximum value to use with the #THRESH_BINARY and #THRESH_BINARY_INV thresholding -types. -@param type thresholding type (see #ThresholdTypes). -@return the computed threshold value if Otsu's or Triangle methods used. - -@sa threshold, adaptiveThreshold, findContours, compare, min, max -*/ -CV_EXPORTS_W double thresholdWithMask( InputArray src, InputOutputArray dst, InputArray mask, - double thresh, double maxval, int type ); /** @brief Applies an adaptive threshold to an array. @@ -4160,11 +4140,7 @@ CV_EXPORTS_W double contourArea( InputArray contour, bool oriented = false ); /** @brief Finds a rotated rectangle of the minimum area enclosing the input 2D point set. The function calculates and returns the minimum-area bounding rectangle (possibly rotated) for a -specified point set. The angle of rotation represents the angle between the line connecting the starting -and ending points (based on the clockwise order with greatest index for the corner with greatest \f$y\f$) -and the horizontal axis. This angle always falls between \f$[-90, 0)\f$ because, if the object -rotates more than a rect angle, the next edge is used to measure the angle. The starting and ending points change -as the object rotates.Developer should keep in mind that the returned RotatedRect can contain negative +specified point set. Developer should keep in mind that the returned RotatedRect can contain negative indices when data is close to the containing Mat element boundary. @param points Input vector of 2D points, stored in std::vector\<\> or Mat @@ -4173,9 +4149,7 @@ CV_EXPORTS_W RotatedRect minAreaRect( InputArray points ); /** @brief Finds the four vertices of a rotated rect. Useful to draw the rotated rectangle. -The function finds the four vertices of a rotated rectangle. The four vertices are returned -in clockwise order starting from the point with greatest \f$y\f$. If two points have the -same \f$y\f$ coordinate the rightmost is the starting point. This function is useful to draw the +The function finds the four vertices of a rotated rectangle. This function is useful to draw the rectangle. In C++, instead of using this function, you can directly use RotatedRect::points method. Please visit the @ref tutorial_bounding_rotated_ellipses "tutorial on Creating Bounding rotated boxes and ellipses for contours" for more information. @@ -4219,6 +4193,7 @@ of the OutputArray must be CV_32F. */ CV_EXPORTS_W double minEnclosingTriangle( InputArray points, CV_OUT OutputArray triangle ); + /** @brief Finds a convex polygon of minimum area enclosing a 2D point set and returns its area. @@ -4226,7 +4201,7 @@ This function takes a given set of 2D points and finds the enclosing polygon wit area. It takes the set of points and the parameter k as input and returns the area of the minimal enclosing polygon. -The Implementation is based on a paper by Aggarwal, Chang and Yap @cite AggarwalChangYap85. They +The Implementation is based on a paper by Aggarwal, Chang and Yap @cite Aggarwal1985. They provide a \f$\theta(n²log(n)log(k))\f$ algorighm for finding the minimal convex polygon with k vertices enclosing a 2D convex polygon with n vertices (k < n). Since the #minEnclosingConvexPolygon function takes a 2D point set as input, an additional preprocessing step of computing the convex hull @@ -4241,6 +4216,7 @@ is lower than \f$\theta(n²log(n)log(k))\f$. Thus the overall complexity of the CV_EXPORTS_W double minEnclosingConvexPolygon ( InputArray points, OutputArray polygon, int k ); + /** @brief Compares two shapes. The function compares two shapes. All three implemented methods use the Hu invariants (see #HuMoments) diff --git a/modules/imgproc/src/min_enclosing_convex_polygon.cpp b/modules/imgproc/src/min_enclosing_convex_polygon.cpp index 288ec0cc0e..a447c9afec 100644 --- a/modules/imgproc/src/min_enclosing_convex_polygon.cpp +++ b/modules/imgproc/src/min_enclosing_convex_polygon.cpp @@ -1,12 +1,3 @@ -/* - -All the functionality must be put into cv:: namespace, or nested namespace, e.g. cv::vslam:: - -*/ - - - - /*M/////////////////////////////////////////////////////////////////////////////////////// // // This file is part of OpenCV project. @@ -139,7 +130,7 @@ struct IntersectionPoint struct FlushIntersect { IntersectionPoint intersection = {}; - float extra_area = std::numeric_limits::max(); + double extra_area = std::numeric_limits::max(); bool done = false; }; @@ -156,7 +147,7 @@ struct BalancedIntersect { cv::Point2f pi = {-1, -1}; cv::Point2f pj = {-1, -1}; - float extra_area = std::numeric_limits::max(); + double extra_area = std::numeric_limits::max(); int flush = -1; bool position = false; bool done = false; @@ -172,7 +163,7 @@ struct BalancedIntersect */ struct Segment { - float extra_area = std::numeric_limits::max(); + double extra_area = std::numeric_limits::max(); int side = -1; bool flush = false; bool exists = false; @@ -187,7 +178,7 @@ struct Segment */ struct Minimum { - float area = std::numeric_limits::max(); + double area = std::numeric_limits::max(); int i = -1; int j = -1; }; @@ -217,7 +208,7 @@ struct Kgon { std::vector sides; std::vector vertices; - float extra_area = std::numeric_limits::max(); + double extra_area = std::numeric_limits::max(); int i = -1; int j = -1; }; @@ -253,8 +244,7 @@ private: std::vector> balanced_intersections; std::vector area_edges; - float extraArea(int first, int last, const cv::Point2f& extra1, - const cv::Point2f& extra2); + double extraArea ( int first, int last, const cv::Point2f& extra1, const cv::Point2f& extra2 ); BalancedIntersect flush(int i, int j, int e); @@ -415,7 +405,7 @@ const FlushIntersect& FlushIntersections::lineIntersect(int i, int j) if(itr.done) return itr; - const int n = ngon.size(); + const size_t n = ngon.size(); if((i + 1) % n == j) { itr.intersection.point = ngon[j]; @@ -438,7 +428,7 @@ const FlushIntersect& FlushIntersections::lineIntersect(int i, int j) } else { - itr.extra_area = std::numeric_limits::max(); + itr.extra_area = std::numeric_limits::max(); itr.intersection.position = false; itr.done = true; } @@ -452,7 +442,7 @@ const FlushIntersect& FlushIntersections::lineIntersect(int i, int j) * @param extra1 Last point of the sequence * @param extra2 Intersection point */ -float BalancedIntersections::extraArea(int first, int last, +double BalancedIntersections::extraArea(int first, int last, const cv::Point2f& extra1, const cv::Point2f& extra2) { @@ -480,7 +470,7 @@ BalancedIntersect BalancedIntersections::flush(int i, int j, int e) if(j == e) std::logic_error(""); - const int n = ngon.size(); + const size_t n = ngon.size(); const int before = (e - 1 + n) % n; BalancedIntersect bi = balanced_intersections[i][j]; @@ -541,7 +531,7 @@ BalancedIntersect BalancedIntersections::balancedIntersect(int i, int j, int e) if(balanced_intersections[i][j].done) return balanced_intersections[i][j]; - const int n = ngon.size(); + const size_t n = ngon.size(); if((i + 2) % n == j) { BalancedIntersect& bi = balanced_intersections[i][j]; @@ -646,12 +636,12 @@ const std::vector& BalancedIntersections::operator[]( */ void Chains::findSingleE(int i, int j, int l, int r) { - const int n = ngon.size(); + const size_t n = ngon.size(); Segment& one = single_sides[i][j]; if (one.done) return; - float min_area = std::numeric_limits::max(); + double min_area = std::numeric_limits::max(); for (int e = l; e != r + 1 && e != j; e = (e + 1) %n) { BalancedIntersect candidate = balanced_inters.balancedIntersect(i, j, e); @@ -676,7 +666,7 @@ void Chains::findSingleE(int i, int j, int l, int r) */ void Chains::singleSideImpl(int i, int j1, int j2) { - const int n = ngon.size(); + const size_t n = ngon.size(); if((j1 + 1) %n == j2) { return; @@ -702,12 +692,12 @@ void Chains::singleSideImpl(int i, int j1, int j2) */ void Chains::findMiddleE1(int i, int j, int l, int r) { - const int n = ngon.size(); + const size_t n = ngon.size(); Segment& one = middle_sides[1][i][j]; if (one.done) return; - float min_area = std::numeric_limits::max(); + double min_area = std::numeric_limits::max(); for (int e = l; e != r + 1 && e != j; e = (e + 1) %n) { const FlushIntersect& before = intersections.lineIntersect(i, e); @@ -717,7 +707,7 @@ void Chains::findMiddleE1(int i, int j, int l, int r) if(!after.intersection.position) continue; - float tmp_area = before.extra_area + after.extra_area; + double tmp_area = before.extra_area + after.extra_area; if(tmp_area < min_area) { min_area = tmp_area; @@ -738,7 +728,7 @@ void Chains::findMiddleE1(int i, int j, int l, int r) */ void Chains::middleSideImpl1(int i, int j1, int j2) { - const int n = ngon.size(); + const size_t n = ngon.size(); if((j1 + 1) %n == j2) { return; @@ -765,7 +755,7 @@ void Chains::middleSideImpl1(int i, int j1, int j2) */ void Chains::findMiddleE(int h, int i, int j, int l, int r) { - const int n = ngon.size(); + const size_t n = ngon.size(); Segment& one = middle_sides[h][i][j]; if (one.done) return; @@ -791,17 +781,17 @@ void Chains::findMiddleE(int h, int i, int j, int l, int r) return; } - float min_area = std::numeric_limits::max(); + double min_area = std::numeric_limits::max(); for (int e = l; e != r + 1 && e != j; e = (e + 1) %n) { const Segment& before = middle_sides[h_floor][i][e]; - if (before.extra_area == std::numeric_limits::max()) + if (before.extra_area == std::numeric_limits::max()) continue; const Segment& after = middle_sides[h_ceil][e][j]; - if(after.extra_area == std::numeric_limits::max()) + if(after.extra_area == std::numeric_limits::max()) continue; - float tmp_area = before.extra_area + after.extra_area; + double tmp_area = before.extra_area + after.extra_area; if(tmp_area < min_area) { min_area = tmp_area; @@ -823,7 +813,7 @@ void Chains::findMiddleE(int h, int i, int j, int l, int r) */ void Chains::middleSideImpl(int h, int i, int j1, int j2) { - const int n = ngon.size(); + const size_t n = ngon.size(); if((j1 + 1) %n == j2) { return; @@ -869,8 +859,8 @@ std::set Chains::relevantChainLengths(int h) */ void Chains::calcOneSidedChains() { - const int n = ngon.size(); - for(int i = 0; i < n; i++) + const size_t n = ngon.size(); + for(size_t i = 0; i < n; i++) { int j1 = (i + 2) %n, j2 = (i - 2 + n) %n; @@ -886,12 +876,12 @@ void Chains::calcOneSidedChains() */ void Chains::calcMiddleChains(int h) { - const int n = ngon.size(); + const size_t n = ngon.size(); if (h == 0) { - for (int i = 0; i < n; i++) + for (size_t i = 0; i < n; i++) { - for (int j = 0; j < n; j++) + for (size_t j = 0; j < n; j++) { Segment& one = middle_sides[h][i][j]; const FlushIntersect itrs = intersections.lineIntersect(i, j); @@ -907,7 +897,7 @@ void Chains::calcMiddleChains(int h) } if (h == 1) { - for (int i = 0; i < n; i++) + for (size_t i = 0; i < n; i++) { int j1 = (i + 2) %n, j2 = (i - 2 + n) %n; @@ -918,7 +908,7 @@ void Chains::calcMiddleChains(int h) return; } - for (int i = 0; i < n; i++) + for (size_t i = 0; i < n; i++) { int j1 = (i + 2) %n, j2 = (i - 2 + n) %n; @@ -943,7 +933,7 @@ Minimum Chains::minimumArea(int n, int k) if(!single_sides[i][j].exists || !middle_sides[k - 3][j][i].exists) continue; - float tmp_area = + double tmp_area = single_sides[i][j].extra_area + middle_sides[k - 3][j][i].extra_area; if(tmp_area < min.area) { @@ -1096,22 +1086,22 @@ static void findMinEnclosingPolygon(const std::vector &ngon, { throw std::invalid_argument( "k must be 3 or higher" ); } - const int n = ngon.size(); - if (n == k) + const size_t n = ngon.size(); + if ((const int)n == k) { throw std::runtime_error ("(n = k)"); } - if (n < k) + if ((const int)n < k) { throw std::runtime_error ("(n < k)"); } } - catch (std::invalid_argument message) + catch (std::invalid_argument &message) { std::cout << "invalid argument: " << message.what() << std::endl; return; } - catch (std::runtime_error message) + catch (std::runtime_error &message) { std::cout << "Warning: no minimum area polygon calculated " << message.what() << std::endl; cv::Mat(ngon).copyTo(minPolygon); @@ -1149,7 +1139,7 @@ static void findMinAreaPolygon(const std::vector &ngon, double &area, int k) { - const int n = ngon.size(); + const size_t n = ngon.size(); Chains chains(ngon, k); chains.calcOneSidedChains(); diff --git a/modules/imgproc/test/test_convhull.cpp b/modules/imgproc/test/test_convhull.cpp index 2e461f461f..d4f9b7a0f6 100644 --- a/modules/imgproc/test/test_convhull.cpp +++ b/modules/imgproc/test/test_convhull.cpp @@ -1077,32 +1077,43 @@ TEST(minEnclosingPolygon, input_errors) std::vector kgon; std::vector ngon; - std::cout << "Four lines of \'invalid argument: ...\' expected:" << std::endl; + std::cout << "Four lines of \'invalid argument: ...\' are expected:" << std::endl; + ngon = {{0.0, 0.0}, {1.0, 1.0}}; EXPECT_NO_THROW(minEnclosingConvexPolygon(ngon, kgon, 3)) << "unexpected exception: not enough points in input ngon (n < 3)"; + ngon = {{0.0, 0.0}, {0.0, 0.0}, {1.0, 1.0}, {1.0, 1.0}}; EXPECT_NO_THROW(minEnclosingConvexPolygon(ngon, kgon, 3)) << "unexpected exception: not enough different points in input ngon (double points)"; + ngon = {{0.0, 0.0}, {1.0, 1.0}, {2.0, 2.0}, {3.0, 3.0}, {4.0, 4.0}}; EXPECT_NO_THROW(minEnclosingConvexPolygon(ngon, kgon, 3)) << "unexpected exception: all points on line"; + ngon = {{0.0, 0.0}, {0.0, 1.0}, {1.0, 0.0}, {1.0, 1.0}}; EXPECT_NO_THROW(minEnclosingConvexPolygon(ngon, kgon, 2)) << "unexpected exception: k < 3"; +} +TEST(minEnclosingPolygon, input_warnings) +{ double area = -1.0; + std::vector kgon; + std::vector ngon; + + std::cout << "Two lines of \'Warning: ...\' are expected:" << std::endl; - std::cout << "Two lines of \'Warning: ...\' expected:" << std::endl; ngon = {{0.0, 0.0}, {0.0, 1.0}, {1.0, 0.0}, {1.0, 1.0}}; EXPECT_NO_THROW({ area = minEnclosingConvexPolygon(ngon, kgon, 4); }) << "unexpected exception: n = k failed"; - EXPECT_NEAR(area, 1, 1e-4) << "n = k: area not equal " << ngon; + EXPECT_NEAR(area, 1.0, 1e-4) << "n = k: area not equal " << ngon; + ngon = {{0.0, 0.0}, {0.0, 1.0}, {1.0, 0.0}, {1.0, 1.0}}; EXPECT_NO_THROW({ area = minEnclosingConvexPolygon(ngon, kgon, 5); }) << "unexpected exception: n < k failed"; - EXPECT_NEAR(area, 1, 1e-4) << "n < k: area not equal " << ngon; + EXPECT_NEAR(area, 1.0, 1e-4) << "n < k: area not equal " << ngon; } TEST(minEnclosingPolygon, unit_circle) @@ -1182,6 +1193,6 @@ TEST(minEnclosingPolygon, pentagon) } } - }} // namespace + /* End of file. */