Merge pull request #29740 from varun-jaiswal17:imgproc_cleanup

Imgproc test cleanup - #29740

co-authored by: @Prasadayus

  ### Re-enabled as-is (stale disable reasons)
  - `FillPolyFully.fillpoly_fully` (`test_drawing.cpp:1142`)
  - `Resize_Bitexact` (`test_resize_bitexact.cpp:188`, 4 instantiations): `INTER_NEAREST` and
  `INTER_NEAREST_EXACT` agree exactly at integer upscale factors; measured 0.0 diff on all 4.

### Add assertions

- **imgproc** — new `Imgproc_Watershed.regression`: `cv::watershed` had no working coverage at all.

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Varun Jaiswal
2026-08-26 13:20:42 +05:30
committed by GitHub
parent 9b60166420
commit 1bc76bc0fe
5 changed files with 102 additions and 36 deletions

View File

@@ -1139,7 +1139,7 @@ PARAM_TEST_CASE(FillPolyFully, unsigned, unsigned, int, int, Point, cv::LineType
}
};
TEST_P(FillPolyFully, DISABLED_fillpoly_fully)
TEST_P(FillPolyFully, fillpoly_fully)
{
int imageSizeOffset = 15;

View File

@@ -44,41 +44,30 @@
namespace opencv_test { namespace {
class CV_ImgprocUMatTest : public cvtest::BaseTest
TEST(Imgproc_UMat, regression)
{
public:
CV_ImgprocUMatTest() {}
~CV_ImgprocUMatTest() {}
protected:
void run(int)
{
string imgpath = string(ts->get_data_path()) + "shared/lena.png";
Mat img = imread(imgpath, IMREAD_COLOR), gray, smallimg, result;
UMat uimg = img.getUMat(ACCESS_READ), ugray, usmallimg, uresult;
const string imgpath = cvtest::findDataFile("shared/lena.png");
Mat img = imread(imgpath, IMREAD_COLOR);
ASSERT_FALSE(img.empty());
cvtColor(img, gray, COLOR_BGR2GRAY);
resize(gray, smallimg, Size(), 0.75, 0.75, INTER_LINEAR_EXACT);
equalizeHist(smallimg, result);
Mat gray, smallimg, result;
cvtColor(img, gray, COLOR_BGR2GRAY);
resize(gray, smallimg, Size(), 0.75, 0.75, INTER_LINEAR_EXACT);
equalizeHist(smallimg, result);
cvtColor(uimg, ugray, COLOR_BGR2GRAY);
resize(ugray, usmallimg, Size(), 0.75, 0.75, INTER_LINEAR_EXACT);
equalizeHist(usmallimg, uresult);
// Deliberately getUMat() rather than copyTo(): the ocl/ tests always upload
// by copy (UMAT_UPLOAD_INPUT_PARAMETER), so this is the only coverage of an
// imgproc chain fed by a UMat view over a Mat that stays alive alongside it.
UMat uimg = img.getUMat(ACCESS_READ), ugray, usmallimg, uresult;
cvtColor(uimg, ugray, COLOR_BGR2GRAY);
resize(ugray, usmallimg, Size(), 0.75, 0.75, INTER_LINEAR_EXACT);
equalizeHist(usmallimg, uresult);
#if 0
imshow("orig", uimg);
imshow("small", usmallimg);
imshow("equalized gray", uresult);
waitKey();
destroyWindow("orig");
destroyWindow("small");
destroyWindow("equalized gray");
#endif
ts->set_failed_test_info(cvtest::TS::OK);
(void)uresult.getMat(ACCESS_READ);
}
};
TEST(Imgproc_UMat, regression) { CV_ImgprocUMatTest test; test.safe_run(); }
// Measured identical here; the tolerance of 1 matches OCL_TEST_P(EqualizeHist, Mat),
// since bit-exactness was only confirmed on one device.
Mat uresult_host = uresult.getMat(ACCESS_READ);
EXPECT_LE(cv::norm(result, uresult_host, NORM_INF), 1)
<< "Mat and UMat imgproc pipelines disagree";
}
}} // namespace

View File

@@ -183,8 +183,9 @@ TEST_P(Resize_Bitexact, Nearest8U_vsNonExact)
EXPECT_EQ(CountDiff(mat_gray), 0) << "gray, type: " << depth;
}
// Now INTER_NEAREST's convention and INTER_NEAREST_EXACT's one are different.
INSTANTIATE_TEST_CASE_P(DISABLED_Imgproc, Resize_Bitexact,
// At an integer upscale INTER_NEAREST and INTER_NEAREST_EXACT both map dst i to src i/k, so they
// agree here. They diverge on downscale and fractional factors, where EXACT follows Pillow.
INSTANTIATE_TEST_CASE_P(Imgproc, Resize_Bitexact,
testing::Values(CV_8U, CV_16U, CV_32F, CV_64F)
);

View File

@@ -41,3 +41,70 @@
//M*/
#include "test_precomp.hpp"
namespace opencv_test { namespace {
// Seeds are eroded copies of the stored reference regions, so watershed has to re-grow the
// boundaries. The neighbouring watershed/comp.xml is not used: it is an
// "opencv-sequence-tree" holding CvSeq contours and needs the removed C API to read.
TEST(Imgproc_Watershed, regression)
{
const string folder = string(cvtest::TS::ptr()->get_data_path());
const string expPath = folder + "watershed/wshed_exp.png";
Mat image = imread(folder + "inpaint/orig.png", IMREAD_COLOR);
Mat expLabels8 = imread(expPath, IMREAD_GRAYSCALE);
ASSERT_FALSE(image.empty()) << "Could not read " << folder << "inpaint/orig.png";
ASSERT_FALSE(expLabels8.empty()) << "Could not read " << expPath;
ASSERT_EQ(image.size(), expLabels8.size());
// wshed_exp.png stores the expected labels offset by +1, so that the -1 used for
// watershed boundaries survives being written to an 8-bit PNG.
Mat expected;
expLabels8.convertTo(expected, CV_32S);
expected -= 1; // -1 = boundary, 1..nLabels = regions
double maxLabel = 0;
minMaxLoc(expected, 0, &maxLabel);
const int nLabels = cvRound(maxLabel);
ASSERT_GT(nLabels, 1) << "reference image does not contain several regions";
Mat markers = Mat::zeros(expected.size(), CV_32S);
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(15, 15));
for (int label = 1; label <= nLabels; label++)
{
Mat seed;
erode(expected == label, seed, kernel);
ASSERT_GT(countNonZero(seed), 0) << "region " << label << " vanished when eroded";
markers.setTo(label, seed);
}
Mat result = markers.clone();
watershed(image, result);
ASSERT_EQ(expected.size(), result.size());
ASSERT_EQ(CV_32S, result.type());
EXPECT_EQ(0, countNonZero(result == 0)) << "watershed left pixels unlabelled";
double minVal = 0, maxVal = 0;
minMaxLoc(result, &minVal, &maxVal);
EXPECT_GE(minVal, -1);
EXPECT_LE(maxVal, nLabels) << "watershed produced a label that was never seeded";
// Measured: ~99.6% for a correct implementation, ~78.7% if watershed does nothing.
Mat interior = expected > 0;
const int totalPx = countNonZero(interior);
ASSERT_GT(totalPx, 0);
Mat agreeMask;
bitwise_and(result == expected, interior, agreeMask);
const double agreement = (double)countNonZero(agreeMask) / totalPx;
EXPECT_GT(agreement, 0.95)
<< "only " << agreement * 100 << "% of interior pixels match " << expPath;
Mat again = markers.clone();
watershed(image, again);
EXPECT_EQ(0, countNonZero(again != result)) << "watershed is not deterministic";
}
}} // namespace

View File

@@ -22,8 +22,17 @@ class watershed_test(NewOpenCVTests):
if img is None or markers is None:
self.assertEqual(0, 1, 'Missing test data')
# cv.watershed() writes into markers in place, so the CV_32S array must be bound
# to a name: np.int32(markers) inline would hand over a temporary.
markers = np.int32(markers)
before = markers.copy()
colors = np.int32( list(np.ndindex(3, 3, 3)) ) * 122
cv.watershed(img, np.int32(markers))
cv.watershed(img, markers)
self.assertFalse(np.array_equal(before, markers),
'cv.watershed() did not modify the markers in place')
segments = colors[np.maximum(markers, 0)]
if refSegments is None: