From 10af8feb9ca8e0ed592d349557b3fad34a78c819 Mon Sep 17 00:00:00 2001 From: Varun Jaiswal <96684656+varun-jaiswal17@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:48:27 +0530 Subject: [PATCH] Merge pull request #29742 from varun-jaiswal17:test_cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ptcloud , photo test suit cleanup - #29742 ## Test suite cleanup ### Given real assertions - **ptcloud** — `HugeSceneGrowthTest`: zero assertions, including a `// Reset check` comment followed by no check. - **ptcloud** — `PointCloud.SaveBadExtension`: passed an empty vertex set, so it exited at the empty-input guard and never reached the extension code it is named for. - **ptcloud** — new `PointCloud.SaveEmptyVertices`: covers the early-return branch the above was hitting by accident. ### Moved - **photo** — `Photo_Denoising.speed` → `perf/perf_denoising.cpp`: a `getTickCount` + `printf` stopwatch in the accuracy suite, asserting nothing, costing 393 ms per run. ### Library fixes found while doing the above - **ptcloud** — `findPlanes` now converts 3-channel input instead of reshaping it: `Mat_::operator=` reshapes when depths match, so a 320×240 `CV_32FC3` input silently became 240×240. - **ptcloud** — new `RGBD_Plane.regression_3channel_matches_4channel`: nothing covered the documented 3-channel path, since all 40 `RgbdPlaneGenerate` cases feed `CV_32FC4`. ### 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 --- modules/photo/perf/perf_denoising.cpp | 34 ++++++++++++++++++++ modules/photo/test/test_denoising.cpp | 11 ------- modules/ptcloud/src/plane.cpp | 35 +++++++++++++++------ modules/ptcloud/test/test_normal.cpp | 32 +++++++++++++++++++ modules/ptcloud/test/test_pointcloud_io.cpp | 27 ++++++++++++++-- modules/ptcloud/test/test_tsdf.cpp | 17 ++++++++++ 6 files changed, 133 insertions(+), 23 deletions(-) create mode 100644 modules/photo/perf/perf_denoising.cpp diff --git a/modules/photo/perf/perf_denoising.cpp b/modules/photo/perf/perf_denoising.cpp new file mode 100644 index 0000000000..3e7b3b88e1 --- /dev/null +++ b/modules/photo/perf/perf_denoising.cpp @@ -0,0 +1,34 @@ +// 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 "perf_precomp.hpp" + +namespace opencv_test +{ + +typedef perf::TestBaseWithParam Size_Denoising; + +PERF_TEST_P(Size_Denoising, fastNlMeansDenoising, + testing::Values(::perf::szVGA, ::perf::sz720p, Size(2592, 1944))) +{ + const Size size = GetParam(); + + Mat original = imread(getDataPath("cv/denoising/lena_noised_gaussian_sigma=10.png"), + IMREAD_GRAYSCALE); + ASSERT_FALSE(original.empty()) << "Could not load input image"; + + Mat tiled; + repeat(original, (size.height + original.rows - 1) / original.rows, + (size.width + original.cols - 1) / original.cols, tiled); + Mat src = tiled(Rect(0, 0, size.width, size.height)).clone(); + Mat dst(size, CV_8UC1); + + declare.in(src).out(dst).time(120); + + TEST_CYCLE() cv::fastNlMeansDenoising(src, dst, 10, 7, 21); + + SANITY_CHECK_NOTHING(); +} + +} // namespace diff --git a/modules/photo/test/test_denoising.cpp b/modules/photo/test/test_denoising.cpp index 2e57369171..2c3159cf2d 100644 --- a/modules/photo/test/test_denoising.cpp +++ b/modules/photo/test/test_denoising.cpp @@ -154,17 +154,6 @@ TEST(Photo_White, issue_2646) ASSERT_EQ(0, nonWhitePixelsCount); } -TEST(Photo_Denoising, speed) -{ - string imgname = string(cvtest::TS::ptr()->get_data_path()) + "shared/5MP.png"; - Mat src = imread(imgname, IMREAD_GRAYSCALE), dst; - - double t = (double)getTickCount(); - fastNlMeansDenoising(src, dst, 5, 7, 21); - t = (double)getTickCount() - t; - printf("execution time: %gms\n", t*1000./getTickFrequency()); -} - // Related issue : https://github.com/opencv/opencv/issues/26582 TEST(Photo_DenoisingGrayscaleMulti16bitL1, regression) { diff --git a/modules/ptcloud/src/plane.cpp b/modules/ptcloud/src/plane.cpp index 70d449d374..510c69439d 100644 --- a/modules/ptcloud/src/plane.cpp +++ b/modules/ptcloud/src/plane.cpp @@ -475,6 +475,29 @@ private: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static void toPaddedVec4f(const Mat& src, Mat_& dst) +{ + Mat src32; + if (src.depth() == CV_32F) + src32 = src; + else + src.convertTo(src32, CV_32F); + + CV_Assert(src32.channels() == 3 || src32.channels() == 4); + + if (src32.channels() == 4) + { + dst = src32; + return; + } + + dst.create(src32.rows, src32.cols); + dst.setTo(Scalar::all(0)); + int from_to[] = { 0, 0, 1, 1, 2, 2 }; + Mat dstMat = dst; + mixChannels(&src32, 1, &dstMat, 1, from_to, 3); +} + void findPlanes(InputArray points3d_in, InputArray normals_in, OutputArray mask_out, OutputArray plane_coefficients_out, int block_size, int min_size, double threshold, double sensor_error_a, double sensor_error_b, double sensor_error_c, RgbdPlaneMethod method) @@ -482,17 +505,9 @@ void findPlanes(InputArray points3d_in, InputArray normals_in, OutputArray mask_ CV_Assert(method == RGBD_PLANE_METHOD_DEFAULT); Mat_ points3d, normals; - if (points3d_in.depth() == CV_32F) - points3d = points3d_in.getMat(); - else - points3d_in.getMat().convertTo(points3d, CV_32F); + toPaddedVec4f(points3d_in.getMat(), points3d); if (!normals_in.empty()) - { - if (normals_in.depth() == CV_32F) - normals = normals_in.getMat(); - else - normals_in.getMat().convertTo(normals, CV_32F); - } + toPaddedVec4f(normals_in.getMat(), normals); // Pre-computations mask_out.create(points3d.size(), CV_8U); diff --git a/modules/ptcloud/test/test_normal.cpp b/modules/ptcloud/test/test_normal.cpp index 582f131367..e0eca51700 100644 --- a/modules/ptcloud/test/test_normal.cpp +++ b/modules/ptcloud/test/test_normal.cpp @@ -722,4 +722,36 @@ TEST(RGBD_Plane, regression2309ValgrindCheck) findPlanes(points, noArray(), mask, planes, blockSize); } +TEST(RGBD_Plane, regression_3channel_matches_4channel) +{ + const int rows = 240, cols = 320; + + Mat points3(rows, cols, CV_32FC3); + Mat points4(rows, cols, CV_32FC4); + for (int v = 0; v < rows; v++) + { + for (int u = 0; u < cols; u++) + { + const float z = 2.f + 0.001f * u + 0.002f * v; + const Vec3f p((float)u * 0.01f, (float)v * 0.01f, z); + points3.at(v, u) = p; + points4.at(v, u) = Vec4f(p[0], p[1], p[2], 0.f); + } + } + + Mat mask3, mask4; + std::vector planes3, planes4; + findPlanes(points3, noArray(), mask3, planes3); + findPlanes(points4, noArray(), mask4, planes4); + + EXPECT_EQ(points3.size(), mask3.size()); + EXPECT_EQ(CV_8U, mask3.type()); + + ASSERT_EQ(mask4.size(), mask3.size()); + EXPECT_EQ(0, cv::countNonZero(mask3 != mask4)); + ASSERT_EQ(planes4.size(), planes3.size()); + for (size_t i = 0; i < planes3.size(); i++) + EXPECT_LE(cv::norm(planes3[i], planes4[i], NORM_INF), 1e-6) << "plane " << i; +} + }} // namespace diff --git a/modules/ptcloud/test/test_pointcloud_io.cpp b/modules/ptcloud/test/test_pointcloud_io.cpp index 4dbf2e3eb2..847de4ad3a 100644 --- a/modules/ptcloud/test/test_pointcloud_io.cpp +++ b/modules/ptcloud/test/test_pointcloud_io.cpp @@ -289,12 +289,35 @@ TEST(PointCloud, LoadBadExtension) } TEST(PointCloud, SaveBadExtension) +{ + std::vector points { cv::Point3f(1.f, 2.f, 3.f) }; + std::vector normals; + + std::string new_path = tempfile("new.fake"); + + cv::savePointCloud(new_path, points, normals); + + std::ifstream f(new_path.c_str()); + EXPECT_FALSE(f.good()) + << "savePointCloud() created a file for an unsupported extension: " << new_path; + f.close(); + std::remove(new_path.c_str()); +} + +TEST(PointCloud, SaveEmptyVertices) { std::vector points; std::vector normals; - auto folder = cvtest::TS::ptr()->get_data_path(); - cv::savePointCloud(folder + "pointcloudio/fake.fake", points, normals); + std::string new_path = tempfile("new_empty.ply"); + + cv::savePointCloud(new_path, points, normals); + + std::ifstream f(new_path.c_str()); + EXPECT_FALSE(f.good()) + << "savePointCloud() created a file for an empty vertex set: " << new_path; + f.close(); + std::remove(new_path.c_str()); } TEST(PointCloud, LoadPlyEmptyFormatLine) diff --git a/modules/ptcloud/test/test_tsdf.cpp b/modules/ptcloud/test/test_tsdf.cpp index 36b91dc928..5a69369598 100644 --- a/modules/ptcloud/test/test_tsdf.cpp +++ b/modules/ptcloud/test/test_tsdf.cpp @@ -732,9 +732,26 @@ void hugeSceneGrowthTest(VolumeType volumeType) debugVolumeDraw(volume, poses[0], depth, depthFactor, "pts.obj"); } + if (volumeType == VolumeType::ColorHashTSDF) + { + EXPECT_GT(volume.getTotalVolumeUnits(), size_t(8192)) + << "the scene no longer exceeds the initial volume unit capacity, so this " + "test no longer covers hash volume growth"; + } + + Vec6f bb; + volume.getBoundingBox(bb, Volume::BoundingBoxPrecision::VOLUME_UNIT); + EXPECT_GT(bb[3], bb[0]) << "bounding box = " << bb; + EXPECT_GT(bb[4], bb[1]) << "bounding box = " << bb; + EXPECT_GT(bb[5], bb[2]) << "bounding box = " << bb; + // Reset check volume.reset(); + Vec6f bbReset; + volume.getBoundingBox(bbReset, Volume::BoundingBoxPrecision::VOLUME_UNIT); + EXPECT_LE(std::sqrt(bbReset.ddot(bbReset)), std::numeric_limits::epsilon()) + << "bounding box after reset() = " << bbReset; }