| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- // This file is part of OpenCV project.
- // It is subject to the license terms in the LICENSE file found in the top-level directory
- // of this distribution and at http://opencv.org/license.html
- #include "test_precomp.hpp"
- #include "opencv2/ts/ocl_test.hpp"
- #include "opencv2/imgproc/detail/legacy.hpp"
- #define CHECK_OLD 1
- namespace opencv_test { namespace {
- // debug function
- template <typename T>
- inline static void print_pts(const T& c)
- {
- for (const auto& one_pt : c)
- {
- cout << one_pt << " ";
- }
- cout << endl;
- }
- // debug function
- template <typename T>
- inline static void print_pts_2(vector<T>& cs)
- {
- int cnt = 0;
- cout << "Contours:" << endl;
- for (const auto& one_c : cs)
- {
- cout << cnt++ << " : ";
- print_pts(one_c);
- }
- };
- // draw 1-2 px blob with orientation defined by 'kind'
- template <typename T>
- inline static void drawSmallContour(Mat& img, Point pt, int kind, int color_)
- {
- const T color = static_cast<T>(color_);
- img.at<T>(pt) = color;
- switch (kind)
- {
- case 1: img.at<T>(pt + Point(1, 0)) = color; break;
- case 2: img.at<T>(pt + Point(1, -1)) = color; break;
- case 3: img.at<T>(pt + Point(0, -1)) = color; break;
- case 4: img.at<T>(pt + Point(-1, -1)) = color; break;
- case 5: img.at<T>(pt + Point(-1, 0)) = color; break;
- case 6: img.at<T>(pt + Point(-1, 1)) = color; break;
- case 7: img.at<T>(pt + Point(0, 1)) = color; break;
- case 8: img.at<T>(pt + Point(1, 1)) = color; break;
- default: break;
- }
- }
- inline static void drawContours(Mat& img,
- const vector<vector<Point>>& contours,
- const Scalar& color = Scalar::all(255))
- {
- for (const auto& contour : contours)
- {
- for (size_t n = 0, end = contour.size(); n < end; ++n)
- {
- size_t m = n + 1;
- if (n == end - 1)
- m = 0;
- line(img, contour[m], contour[n], color, 1, LINE_8);
- }
- }
- }
- //==================================================================================================
- // Test parameters - mode + method
- typedef testing::TestWithParam<tuple<int, int>> Imgproc_FindContours_Modes1;
- // Draw random rectangle and find contours
- //
- TEST_P(Imgproc_FindContours_Modes1, rectangle)
- {
- const int mode = get<0>(GetParam());
- const int method = get<1>(GetParam());
- const size_t ITER = 100;
- RNG rng = TS::ptr()->get_rng();
- for (size_t i = 0; i < ITER; ++i)
- {
- SCOPED_TRACE(cv::format("i=%zu", i));
- const Size sz(rng.uniform(640, 1920), rng.uniform(480, 1080));
- Mat img(sz, CV_8UC1, Scalar::all(0));
- Mat img32s(sz, CV_32SC1, Scalar::all(0));
- const Rect r(Point(rng.uniform(1, sz.width / 2 - 1), rng.uniform(1, sz.height / 2)),
- Point(rng.uniform(sz.width / 2 - 1, sz.width - 1),
- rng.uniform(sz.height / 2 - 1, sz.height - 1)));
- rectangle(img, r, Scalar::all(255));
- rectangle(img32s, r, Scalar::all(255), FILLED);
- const vector<Point> ext_ref {r.tl(),
- r.tl() + Point(0, r.height - 1),
- r.br() + Point(-1, -1),
- r.tl() + Point(r.width - 1, 0)};
- const vector<Point> int_ref {ext_ref[0] + Point(0, 1),
- ext_ref[0] + Point(1, 0),
- ext_ref[3] + Point(-1, 0),
- ext_ref[3] + Point(0, 1),
- ext_ref[2] + Point(0, -1),
- ext_ref[2] + Point(-1, 0),
- ext_ref[1] + Point(1, 0),
- ext_ref[1] + Point(0, -1)};
- const size_t ext_perimeter = r.width * 2 + r.height * 2;
- const size_t int_perimeter = ext_perimeter - 4;
- vector<vector<Point>> contours;
- vector<vector<schar>> chains;
- vector<Vec4i> hierarchy;
- // run functionn
- if (mode == RETR_FLOODFILL)
- if (method == 0)
- findContours(img32s, chains, hierarchy, mode, method);
- else
- findContours(img32s, contours, hierarchy, mode, method);
- else if (method == 0)
- findContours(img, chains, hierarchy, mode, method);
- else
- findContours(img, contours, hierarchy, mode, method);
- // verify results
- if (mode == RETR_EXTERNAL)
- {
- if (method == 0)
- {
- ASSERT_EQ(1U, chains.size());
- }
- else
- {
- ASSERT_EQ(1U, contours.size());
- if (method == CHAIN_APPROX_NONE)
- {
- EXPECT_EQ(int_perimeter, contours[0].size());
- }
- else if (method == CHAIN_APPROX_SIMPLE)
- {
- EXPECT_MAT_NEAR(Mat(ext_ref), Mat(contours[0]), 0);
- }
- }
- }
- else
- {
- if (method == 0)
- {
- ASSERT_EQ(2U, chains.size());
- }
- else
- {
- ASSERT_EQ(2U, contours.size());
- if (mode == RETR_LIST)
- {
- if (method == CHAIN_APPROX_NONE)
- {
- EXPECT_EQ(int_perimeter - 4, contours[0].size());
- EXPECT_EQ(int_perimeter, contours[1].size());
- }
- else if (method == CHAIN_APPROX_SIMPLE)
- {
- EXPECT_MAT_NEAR(Mat(int_ref), Mat(contours[0]), 0);
- EXPECT_MAT_NEAR(Mat(ext_ref), Mat(contours[1]), 0);
- }
- }
- else if (mode == RETR_CCOMP || mode == RETR_TREE)
- {
- if (method == CHAIN_APPROX_NONE)
- {
- EXPECT_EQ(int_perimeter, contours[0].size());
- EXPECT_EQ(int_perimeter - 4, contours[1].size());
- }
- else if (method == CHAIN_APPROX_SIMPLE)
- {
- EXPECT_MAT_NEAR(Mat(ext_ref), Mat(contours[0]), 0);
- EXPECT_MAT_NEAR(Mat(int_ref), Mat(contours[1]), 0);
- }
- }
- else if (mode == RETR_FLOODFILL)
- {
- if (method == CHAIN_APPROX_NONE)
- {
- EXPECT_EQ(int_perimeter + 4, contours[0].size());
- }
- else if (method == CHAIN_APPROX_SIMPLE)
- {
- EXPECT_EQ(int_ref.size(), contours[0].size());
- EXPECT_MAT_NEAR(Mat(ext_ref), Mat(contours[1]), 0);
- }
- }
- }
- }
- #if CHECK_OLD
- if (method != 0) // old doesn't support chain codes
- {
- if (mode != RETR_FLOODFILL)
- {
- vector<vector<Point>> contours_o;
- vector<Vec4i> hierarchy_o;
- findContours_legacy(img, contours_o, hierarchy_o, mode, method);
- ASSERT_EQ(contours.size(), contours_o.size());
- for (size_t j = 0; j < contours.size(); ++j)
- {
- SCOPED_TRACE(format("contour %zu", j));
- EXPECT_MAT_NEAR(Mat(contours[j]), Mat(contours_o[j]), 0);
- }
- EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
- }
- else
- {
- vector<vector<Point>> contours_o;
- vector<Vec4i> hierarchy_o;
- findContours_legacy(img32s, contours_o, hierarchy_o, mode, method);
- ASSERT_EQ(contours.size(), contours_o.size());
- for (size_t j = 0; j < contours.size(); ++j)
- {
- SCOPED_TRACE(format("contour %zu", j));
- EXPECT_MAT_NEAR(Mat(contours[j]), Mat(contours_o[j]), 0);
- }
- EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
- }
- }
- #endif
- }
- }
- // Draw many small 1-2px blobs and find contours
- //
- TEST_P(Imgproc_FindContours_Modes1, small)
- {
- const int mode = get<0>(GetParam());
- const int method = get<1>(GetParam());
- const size_t DIM = 1000;
- const Size sz(DIM, DIM);
- const int num = (DIM / 10) * (DIM / 10); // number of 10x10 squares
- Mat img(sz, CV_8UC1, Scalar::all(0));
- Mat img32s(sz, CV_32SC1, Scalar::all(0));
- vector<Point> pts;
- int extra_contours_32s = 0;
- for (int j = 0; j < num; ++j)
- {
- const int kind = j % 9;
- Point pt {(j % 100) * 10 + 4, (j / 100) * 10 + 4};
- drawSmallContour<uchar>(img, pt, kind, 255);
- drawSmallContour<int>(img32s, pt, kind, j + 1);
- pts.push_back(pt);
- // NOTE: for some reason these small diagonal contours (NW, SE)
- // result in 2 external contours for FLOODFILL mode
- if (kind == 8 || kind == 4)
- ++extra_contours_32s;
- }
- {
- vector<vector<Point>> contours;
- vector<vector<schar>> chains;
- vector<Vec4i> hierarchy;
- if (mode == RETR_FLOODFILL)
- {
- if (method == 0)
- {
- findContours(img32s, chains, hierarchy, mode, method);
- ASSERT_EQ(pts.size() * 2 + extra_contours_32s, chains.size());
- }
- else
- {
- findContours(img32s, contours, hierarchy, mode, method);
- ASSERT_EQ(pts.size() * 2 + extra_contours_32s, contours.size());
- #if CHECK_OLD
- vector<vector<Point>> contours_o;
- vector<Vec4i> hierarchy_o;
- findContours_legacy(img32s, contours_o, hierarchy_o, mode, method);
- ASSERT_EQ(contours.size(), contours_o.size());
- for (size_t i = 0; i < contours.size(); ++i)
- {
- SCOPED_TRACE(format("contour %zu", i));
- EXPECT_MAT_NEAR(Mat(contours[i]), Mat(contours_o[i]), 0);
- }
- EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
- #endif
- }
- }
- else
- {
- if (method == 0)
- {
- findContours(img, chains, hierarchy, mode, method);
- ASSERT_EQ(pts.size(), chains.size());
- }
- else
- {
- findContours(img, contours, hierarchy, mode, method);
- ASSERT_EQ(pts.size(), contours.size());
- #if CHECK_OLD
- vector<vector<Point>> contours_o;
- vector<Vec4i> hierarchy_o;
- findContours_legacy(img, contours_o, hierarchy_o, mode, method);
- ASSERT_EQ(contours.size(), contours_o.size());
- for (size_t i = 0; i < contours.size(); ++i)
- {
- SCOPED_TRACE(format("contour %zu", i));
- EXPECT_MAT_NEAR(Mat(contours[i]), Mat(contours_o[i]), 0);
- }
- EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
- #endif
- }
- }
- }
- }
- // Draw many nested rectangles and find contours
- //
- TEST_P(Imgproc_FindContours_Modes1, deep)
- {
- const int mode = get<0>(GetParam());
- const int method = get<1>(GetParam());
- const size_t DIM = 1000;
- const Size sz(DIM, DIM);
- const size_t NUM = 249U;
- Mat img(sz, CV_8UC1, Scalar::all(0));
- Mat img32s(sz, CV_32SC1, Scalar::all(0));
- Rect rect(1, 1, 998, 998);
- for (size_t i = 0; i < NUM; ++i)
- {
- rectangle(img, rect, Scalar::all(255));
- rectangle(img32s, rect, Scalar::all((double)i + 1), FILLED);
- rect.x += 2;
- rect.y += 2;
- rect.width -= 4;
- rect.height -= 4;
- }
- {
- vector<vector<Point>> contours {{{0, 0}, {1, 1}}};
- vector<vector<schar>> chains {{1, 2, 3}};
- vector<Vec4i> hierarchy;
- if (mode == RETR_FLOODFILL)
- {
- if (method == 0)
- {
- findContours(img32s, chains, hierarchy, mode, method);
- ASSERT_EQ(2 * NUM, chains.size());
- }
- else
- {
- findContours(img32s, contours, hierarchy, mode, method);
- ASSERT_EQ(2 * NUM, contours.size());
- #if CHECK_OLD
- vector<vector<Point>> contours_o;
- vector<Vec4i> hierarchy_o;
- findContours_legacy(img32s, contours_o, hierarchy_o, mode, method);
- ASSERT_EQ(contours.size(), contours_o.size());
- for (size_t i = 0; i < contours.size(); ++i)
- {
- SCOPED_TRACE(format("contour %zu", i));
- EXPECT_MAT_NEAR(Mat(contours[i]), Mat(contours_o[i]), 0);
- }
- EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
- #endif
- }
- }
- else
- {
- const size_t expected_count = (mode == RETR_EXTERNAL) ? 1U : 2 * NUM;
- if (method == 0)
- {
- findContours(img, chains, hierarchy, mode, method);
- ASSERT_EQ(expected_count, chains.size());
- }
- else
- {
- findContours(img, contours, hierarchy, mode, method);
- ASSERT_EQ(expected_count, contours.size());
- #if CHECK_OLD
- vector<vector<Point>> contours_o;
- vector<Vec4i> hierarchy_o;
- findContours_legacy(img, contours_o, hierarchy_o, mode, method);
- ASSERT_EQ(contours.size(), contours_o.size());
- for (size_t i = 0; i < contours.size(); ++i)
- {
- SCOPED_TRACE(format("contour %zu", i));
- EXPECT_MAT_NEAR(Mat(contours[i]), Mat(contours_o[i]), 0);
- }
- EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
- #endif
- }
- }
- }
- }
- INSTANTIATE_TEST_CASE_P(
- ,
- Imgproc_FindContours_Modes1,
- testing::Combine(
- testing::Values(RETR_EXTERNAL, RETR_LIST, RETR_CCOMP, RETR_TREE, RETR_FLOODFILL),
- testing::Values(0,
- CHAIN_APPROX_NONE,
- CHAIN_APPROX_SIMPLE,
- CHAIN_APPROX_TC89_L1,
- CHAIN_APPROX_TC89_KCOS)));
- //==================================================================================================
- typedef testing::TestWithParam<tuple<int, int>> Imgproc_FindContours_Modes2;
- // Very approximate backport of an old accuracy test
- //
- TEST_P(Imgproc_FindContours_Modes2, new_accuracy)
- {
- const int mode = get<0>(GetParam());
- const int method = get<1>(GetParam());
- RNG& rng = TS::ptr()->get_rng();
- const int blob_count = rng.uniform(1, 10);
- const Size sz(rng.uniform(640, 1920), rng.uniform(480, 1080));
- const int blob_sz = 50;
- // prepare image
- Mat img(sz, CV_8UC1, Scalar::all(0));
- vector<RotatedRect> rects;
- for (int i = 0; i < blob_count; ++i)
- {
- const Point2f center((float)rng.uniform(blob_sz, sz.width - blob_sz),
- (float)rng.uniform(blob_sz, sz.height - blob_sz));
- const Size2f rsize((float)rng.uniform(1, blob_sz), (float)rng.uniform(1, blob_sz));
- RotatedRect rect(center, rsize, rng.uniform(0.f, 180.f));
- rects.push_back(rect);
- ellipse(img, rect, Scalar::all(100), FILLED);
- }
- // draw contours manually
- Mat cont_img(sz, CV_8UC1, Scalar::all(0));
- for (int y = 1; y < sz.height - 1; ++y)
- {
- for (int x = 1; x < sz.width - 1; ++x)
- {
- if (img.at<uchar>(y, x) != 0 &&
- ((img.at<uchar>(y - 1, x) == 0) || (img.at<uchar>(y + 1, x) == 0) ||
- (img.at<uchar>(y, x + 1) == 0) || (img.at<uchar>(y, x - 1) == 0)))
- {
- cont_img.at<uchar>(y, x) = 255;
- }
- }
- }
- // find contours
- vector<vector<Point>> contours;
- vector<Vec4i> hierarchy;
- findContours(img, contours, hierarchy, mode, method);
- // 0 < contours <= rects
- EXPECT_GT(contours.size(), 0U);
- EXPECT_GE(rects.size(), contours.size());
- // draw contours
- Mat res_img(sz, CV_8UC1, Scalar::all(0));
- drawContours(res_img, contours);
- // compare resulting drawn contours with manually drawn contours
- const double diff1 = cvtest::norm(cont_img, res_img, NORM_L1) / 255;
- if (method == CHAIN_APPROX_NONE || method == CHAIN_APPROX_SIMPLE)
- {
- EXPECT_EQ(0., diff1);
- }
- #if CHECK_OLD
- vector<vector<Point>> contours_o;
- vector<Vec4i> hierarchy_o;
- findContours(img, contours_o, hierarchy_o, mode, method);
- ASSERT_EQ(contours_o.size(), contours.size());
- for (size_t i = 0; i < contours_o.size(); ++i)
- {
- SCOPED_TRACE(format("contour = %zu", i));
- EXPECT_MAT_NEAR(Mat(contours_o[i]), Mat(contours[i]), 0);
- }
- EXPECT_MAT_NEAR(Mat(hierarchy_o), Mat(hierarchy), 0);
- #endif
- }
- TEST_P(Imgproc_FindContours_Modes2, approx)
- {
- const int mode = get<0>(GetParam());
- const int method = get<1>(GetParam());
- const Size sz {500, 500};
- Mat img = Mat::zeros(sz, CV_8UC1);
- for (int c = 0; c < 4; ++c)
- {
- if (c != 0)
- {
- // noise + filter + threshold
- RNG& rng = TS::ptr()->get_rng();
- cvtest::randUni(rng, img, 0, 255);
- Mat fimg;
- boxFilter(img, fimg, CV_8U, Size(5, 5));
- Mat timg;
- const int level = 44 + c * 42;
- // 'level' goes through:
- // 86 - some black speckles on white
- // 128 - 50/50 black/white
- // 170 - some white speckles on black
- cv::threshold(fimg, timg, level, 255, THRESH_BINARY);
- }
- else
- {
- // circle with cut
- const Point center {250, 250};
- const int r {20};
- const Point cut {r, r};
- circle(img, center, r, Scalar(255), FILLED);
- rectangle(img, center, center + cut, Scalar(0), FILLED);
- }
- vector<vector<Point>> contours;
- vector<Vec4i> hierarchy;
- findContours(img, contours, hierarchy, mode, method);
- #if CHECK_OLD
- // NOTE: old and new function results might not match when approximation mode is TC89.
- // Currently this test passes, but might fail for other random data.
- // See https://github.com/opencv/opencv/issues/25663 for details.
- vector<vector<Point>> contours_o;
- vector<Vec4i> hierarchy_o;
- findContours_legacy(img, contours_o, hierarchy_o, mode, method);
- ASSERT_EQ(contours_o.size(), contours.size());
- for (size_t i = 0; i < contours_o.size(); ++i)
- {
- SCOPED_TRACE(format("c = %d, contour = %zu", c, i));
- EXPECT_MAT_NEAR(Mat(contours_o[i]), Mat(contours[i]), 0);
- }
- EXPECT_MAT_NEAR(Mat(hierarchy_o), Mat(hierarchy), 0);
- #endif
- // TODO: check something
- }
- }
- // TODO: offset test
- // no RETR_FLOODFILL - no CV_32S input images
- INSTANTIATE_TEST_CASE_P(
- ,
- Imgproc_FindContours_Modes2,
- testing::Combine(testing::Values(RETR_EXTERNAL, RETR_LIST, RETR_CCOMP, RETR_TREE),
- testing::Values(CHAIN_APPROX_NONE,
- CHAIN_APPROX_SIMPLE,
- CHAIN_APPROX_TC89_L1,
- CHAIN_APPROX_TC89_KCOS)));
- TEST(Imgproc_FindContours, link_runs)
- {
- const Size sz {500, 500};
- Mat img = Mat::zeros(sz, CV_8UC1);
- // noise + filter + threshold
- RNG& rng = TS::ptr()->get_rng();
- cvtest::randUni(rng, img, 0, 255);
- Mat fimg;
- boxFilter(img, fimg, CV_8U, Size(5, 5));
- const int level = 135;
- cv::threshold(fimg, img, level, 255, THRESH_BINARY);
- vector<vector<Point>> contours;
- vector<Vec4i> hierarchy;
- findContoursLinkRuns(img, contours, hierarchy);
- if (cvtest::debugLevel >= 10)
- {
- print_pts_2(contours);
- Mat res = Mat::zeros(sz, CV_8UC1);
- drawContours(res, contours);
- imshow("res", res);
- imshow("img", img);
- waitKey(0);
- }
- #if CHECK_OLD
- vector<vector<Point>> contours_o;
- vector<Vec4i> hierarchy_o;
- findContours_legacy(img, contours_o, hierarchy_o, 0, 5); // CV_LINK_RUNS method
- ASSERT_EQ(contours_o.size(), contours.size());
- for (size_t i = 0; i < contours_o.size(); ++i)
- {
- SCOPED_TRACE(format("contour = %zu", i));
- EXPECT_MAT_NEAR(Mat(contours_o[i]), Mat(contours[i]), 0);
- }
- EXPECT_MAT_NEAR(Mat(hierarchy_o), Mat(hierarchy), 0);
- #endif
- }
- }} // namespace opencv_test
|