From cfb9d11170c64c9d66c9df22461452e98ee82ba9 Mon Sep 17 00:00:00 2001 From: Oscar Amoros Huguet Date: Thu, 27 Aug 2026 16:13:26 +0200 Subject: [PATCH] Added resize overloads and tests --- include/cvGPUSpeedup.cuh | 32 ++- tests/resize/test_resize_write.cu | 395 +++++++++++++++++++++++++++++- 2 files changed, 421 insertions(+), 6 deletions(-) diff --git a/include/cvGPUSpeedup.cuh b/include/cvGPUSpeedup.cuh index ec4e8f60..043d728b 100644 --- a/include/cvGPUSpeedup.cuh +++ b/include/cvGPUSpeedup.cuh @@ -204,18 +204,40 @@ inline constexpr auto splitT(const fk::RawPtr> {output}; } -template -inline const auto resize(const cv::Size& dsize) { - return fk::Resize(INTER_F)>::build(fk::Size(dsize.width, dsize.height)); +template +inline const auto resize(const cv::Size& dsize, cv::Scalar defaultValue = cv::Scalar(0.f)) { + if constexpr (AR == fk::AspectRatio::IGNORE_AR) { + return fk::Resize(INTER_F), AR>::build(fk::Size(dsize.width, dsize.height)); + } else { + return fk::Resize(INTER_F), AR>::build(fk::Size(dsize.width, dsize.height), cvScalar2CUDAV::get(defaultValue)); + } +} + +template +inline const auto resize(const std::array& dsizes, const std::array& defaultValue = {cv::Scalar(0.f)}) { + std::array fk_dsizes{}; + if constexpr (AR != fk::AspectRatio::IGNORE_AR) { + for (int i = 0; i < BATCH; ++i) { + fk_dsizes[i] = fk::Size(dsizes[i].width, dsizes[i].height); + } + return fk::Resize(INTER_F), AR>::build(fk_dsizes); + } else { + std::array::get(std::declval())), BATCH> fk_defaultValue{}; + for (int i = 0; i < BATCH; ++i) { + fk_dsizes[i] = fk::Size(dsizes[i].width, dsizes[i].height); + fk_defaultValue[i] = cvScalar2CUDAV::get(defaultValue[i]); + } + return fk::Resize(INTER_F), AR>::build(fk_dsizes, fk_defaultValue); + } } -template +template inline const auto resize(const cv::cuda::GpuMat& input, const cv::Size& dsize, double fx, double fy) { static_assert(isSupportedInterpolation, "Interpolation type not supported yet."); const fk::RawPtr fk_input = gpuMat2Ptr2D(input); const fk::Size dSize{ dsize.width, dsize.height }; - return fk::Resize<(fk::InterpolationType)INTER_F>::build(fk_input, dSize, fx, fy); + return fk::Resize<(fk::InterpolationType)INTER_F, AR>::build(fk_input, dSize, fx, fy); } template diff --git a/tests/resize/test_resize_write.cu b/tests/resize/test_resize_write.cu index 278a3db4..e265a4a3 100644 --- a/tests/resize/test_resize_write.cu +++ b/tests/resize/test_resize_write.cu @@ -107,6 +107,391 @@ bool test_resize_write(int NUM_ELEMS_X, int NUM_ELEMS_Y, cv::cuda::Stream& cv_st return passed; } +// Tests the cvGS::resize overload that computes the destination size from the +// fx and fy scale factors, instead of using a cv::Size. +template +bool test_resize_write_scale_factors(int NUM_ELEMS_X, int NUM_ELEMS_Y, cv::cuda::Stream& cv_stream, bool enabled) { + std::stringstream error_s; + bool passed = true; + bool exception = false; + + if (enabled) { + + struct Parameters { + cv::Scalar init; + }; + + std::vector params = { + {{2u}}, + {{2u, 37u}}, + {{2u, 37u, 128u}}, + {{2u, 37u, 128u, 20u}} + }; + + cv::Scalar val_init = params.at(CV_MAT_CN(I)-1).init; + + try { + + cv::cuda::GpuMat d_input(NUM_ELEMS_Y, NUM_ELEMS_X, I, val_init); + + constexpr double fxUp = 1.5; + constexpr double fyUp = 1.5; + constexpr double fxDown = 0.25; + constexpr double fyDown = 0.25; + + const cv::Size up(static_cast(NUM_ELEMS_X * fxUp), static_cast(NUM_ELEMS_Y * fyUp)); + const cv::Size down(static_cast(NUM_ELEMS_X * fxDown), static_cast(NUM_ELEMS_Y * fyDown)); + + cv::cuda::GpuMat d_down(down, I); + cv::cuda::GpuMat d_up(up, I); + + cv::cuda::GpuMat d_down_cvGS(down, I); + cv::cuda::GpuMat d_up_cvGS(up, I); + + // Execute cvGS first to avoid OpenCV exceptions + cvGS::executeOperations(cv_stream, cvGS::resize(d_input, cv::Size(0, 0), fxUp, fyUp), + cvGS::convertTo(), + cvGS::write(d_up_cvGS)); + cvGS::executeOperations(cv_stream, cvGS::resize(d_input, cv::Size(0, 0), fxDown, fyDown), + cvGS::convertTo(), + cvGS::write(d_down_cvGS)); + + cv::cuda::resize(d_input, d_up, cv::Size(), fxUp, fyUp, cv::INTER_LINEAR, cv_stream); + cv::cuda::resize(d_input, d_down, cv::Size(), fxDown, fyDown, cv::INTER_LINEAR, cv_stream); + + cv::Mat h_up, h_up_cvGS; + cv::Mat h_down, h_down_cvGS; + + d_up.download(h_up, cv_stream); + d_up_cvGS.download(h_up_cvGS, cv_stream); + d_down.download(h_down, cv_stream); + d_down_cvGS.download(h_down_cvGS, cv_stream); + + cv_stream.waitForCompletion(); + + passed &= compareAndCheck(up.width, up.height, h_up, h_up_cvGS); + passed &= compareAndCheck(down.width, down.height, h_down, h_down_cvGS); + + } catch (const cv::Exception& e) { + if (e.code != -210) { + error_s << e.what(); + passed = false; + exception = true; + } else { + std::stringstream ss; + ss << "test_resize_write_scale_factors<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> not supported by OpenCV" << std::endl; + } + } catch (const std::exception& e) { + error_s << e.what(); + passed = false; + exception = true; + } + + if (!passed) { + if (!exception) { + std::stringstream ss; + ss << "test_resize_write_scale_factors<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> failed!! RESULT ERROR: Some results do not match baseline." << std::endl; + } else { + std::stringstream ss; + ss << "test_resize_write_scale_factors<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> failed!! EXCEPTION: " << error_s.str() << std::endl; + } + } + } + + return passed; +} + +// Tests the cvGS::resize overload that only receives the destination size, and +// therefore has to be fused with a previous read Operation, by using then(). +template +bool test_resize_write_fused(int NUM_ELEMS_X, int NUM_ELEMS_Y, cv::cuda::Stream& cv_stream, bool enabled) { + std::stringstream error_s; + bool passed = true; + bool exception = false; + + if (enabled) { + + struct Parameters { + cv::Scalar init; + }; + + std::vector params = { + {{2u}}, + {{2u, 37u}}, + {{2u, 37u, 128u}}, + {{2u, 37u, 128u, 20u}} + }; + + cv::Scalar val_init = params.at(CV_MAT_CN(I)-1).init; + + try { + + cv::cuda::GpuMat d_input(NUM_ELEMS_Y, NUM_ELEMS_X, I, val_init); + + const cv::Size up(3870, 2260); // x,y + const cv::Size down(300, 500); // x,y + + cv::cuda::GpuMat d_down(down, I); + cv::cuda::GpuMat d_up(up, I); + + cv::cuda::GpuMat d_down_cvGS(down, I); + cv::cuda::GpuMat d_up_cvGS(up, I); + + const auto readOp = + fk::PerThreadRead::build(cvGS::gpuMat2Ptr2D(d_input).ptr()); + + // Execute cvGS first to avoid OpenCV exceptions + cvGS::executeOperations(cv_stream, readOp.then(cvGS::resize(up)), + cvGS::convertTo(), + cvGS::write(d_up_cvGS)); + cvGS::executeOperations(cv_stream, readOp.then(cvGS::resize(down)), + cvGS::convertTo(), + cvGS::write(d_down_cvGS)); + + cv::cuda::resize(d_input, d_up, up, 0., 0., cv::INTER_LINEAR, cv_stream); + cv::cuda::resize(d_input, d_down, down, 0., 0., cv::INTER_LINEAR, cv_stream); + + cv::Mat h_up, h_up_cvGS; + cv::Mat h_down, h_down_cvGS; + + d_up.download(h_up, cv_stream); + d_up_cvGS.download(h_up_cvGS, cv_stream); + d_down.download(h_down, cv_stream); + d_down_cvGS.download(h_down_cvGS, cv_stream); + + cv_stream.waitForCompletion(); + + passed &= compareAndCheck(up.width, up.height, h_up, h_up_cvGS); + passed &= compareAndCheck(down.width, down.height, h_down, h_down_cvGS); + + } catch (const cv::Exception& e) { + if (e.code != -210) { + error_s << e.what(); + passed = false; + exception = true; + } else { + std::stringstream ss; + ss << "test_resize_write_fused<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> not supported by OpenCV" << std::endl; + } + } catch (const std::exception& e) { + error_s << e.what(); + passed = false; + exception = true; + } + + if (!passed) { + if (!exception) { + std::stringstream ss; + ss << "test_resize_write_fused<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> failed!! RESULT ERROR: Some results do not match baseline." << std::endl; + } else { + std::stringstream ss; + ss << "test_resize_write_fused<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> failed!! EXCEPTION: " << error_s.str() << std::endl; + } + } + } + + return passed; +} + +// Tests the cvGS::resize overload that preserves the aspect ratio, filling the +// rest of the destination image with a background value. +template +bool test_resize_write_aspect_ratio(int NUM_ELEMS_X, int NUM_ELEMS_Y, cv::cuda::Stream& cv_stream, bool enabled) { + std::stringstream error_s; + bool passed = true; + bool exception = false; + + if (enabled) { + + struct Parameters { + cv::Scalar init; + }; + + std::vector params = { + {{2u}}, + {{2u, 37u}}, + {{2u, 37u, 128u}}, + {{2u, 37u, 128u, 20u}} + }; + + cv::Scalar val_init = params.at(CV_MAT_CN(I)-1).init; + const cv::Scalar background = cvGS::cvScalar_set(128.f); + + try { + // We use a source with an aspect ratio different than the destination one + cv::cuda::GpuMat d_input(NUM_ELEMS_Y, NUM_ELEMS_X, I, val_init); + + const cv::Size dstSize(512, 512); // x,y + + // Compute the size that preserves the aspect ratio, the same way cvGS does + float scaleFactor = dstSize.height / static_cast(NUM_ELEMS_Y); + int targetWidth = static_cast(std::round(scaleFactor * NUM_ELEMS_X)); + int targetHeight = dstSize.height; + if (targetWidth > dstSize.width) { + scaleFactor = dstSize.width / static_cast(NUM_ELEMS_X); + targetWidth = dstSize.width; + targetHeight = static_cast(std::round(scaleFactor * NUM_ELEMS_Y)); + } + const cv::Size targetSize(targetWidth, targetHeight); + + cv::cuda::GpuMat d_output(dstSize, I, background); + cv::cuda::GpuMat d_output_cvGS(dstSize, I); + + const auto readOp = + fk::PerThreadRead::build(cvGS::gpuMat2Ptr2D(d_input).ptr()); + constexpr int DEFAULT_TYPE = CV_MAKETYPE(CV_32F, CV_MAT_CN(I)); + + // Execute cvGS first to avoid OpenCV exceptions + cvGS::executeOperations(cv_stream, + readOp.then(cvGS::resize(dstSize, background)), + cvGS::convertTo(), + cvGS::write(d_output_cvGS)); + + // OpenCV version: resize into the centered region of interest + const int xOffset = (dstSize.width - targetSize.width) / 2; + const int yOffset = (dstSize.height - targetSize.height) / 2; + cv::cuda::GpuMat d_roi = d_output(cv::Rect(xOffset, yOffset, targetSize.width, targetSize.height)); + cv::cuda::resize(d_input, d_roi, targetSize, 0., 0., cv::INTER_LINEAR, cv_stream); + + cv::Mat h_output, h_output_cvGS; + d_output.download(h_output, cv_stream); + d_output_cvGS.download(h_output_cvGS, cv_stream); + + cv_stream.waitForCompletion(); + + passed &= compareAndCheck(dstSize.width, dstSize.height, h_output, h_output_cvGS); + + } catch (const cv::Exception& e) { + if (e.code != -210) { + error_s << e.what(); + passed = false; + exception = true; + } else { + std::stringstream ss; + ss << "test_resize_write_aspect_ratio<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> not supported by OpenCV" << std::endl; + } + } catch (const std::exception& e) { + error_s << e.what(); + passed = false; + exception = true; + } + + if (!passed) { + if (!exception) { + std::stringstream ss; + ss << "test_resize_write_aspect_ratio<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> failed!! RESULT ERROR: Some results do not match baseline." << std::endl; + } else { + std::stringstream ss; + ss << "test_resize_write_aspect_ratio<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> failed!! EXCEPTION: " << error_s.str() << std::endl; + } + } + } + + return passed; +} + +// Tests the cvGS::resize overload that resizes a batch of images at once. +template +bool test_resize_write_batch(int NUM_ELEMS_X, int NUM_ELEMS_Y, cv::cuda::Stream& cv_stream, bool enabled) { + std::stringstream error_s; + bool passed = true; + bool exception = false; + + if (enabled) { + + struct Parameters { + cv::Scalar init; + }; + + std::vector params = { + {{2u}}, + {{2u, 37u}}, + {{2u, 37u, 128u}}, + {{2u, 37u, 128u, 20u}} + }; + + cv::Scalar val_init = params.at(CV_MAT_CN(I)-1).init; + + try { + const cv::Size down(300, 500); // x,y + + std::array d_inputs; + std::array d_outputs_cv; + for (size_t i = 0; i < BATCH; i++) { + d_inputs[i] = cv::cuda::GpuMat(NUM_ELEMS_Y, NUM_ELEMS_X, I, val_init); + d_outputs_cv[i] = cv::cuda::GpuMat(down, I); + } + + cv::cuda::GpuMat d_tensor_output(BATCH, down.width * down.height * CV_MAT_CN(I), CV_MAT_DEPTH(I)); + d_tensor_output.step = down.width * down.height * CV_MAT_CN(I) * sizeof(BASE_CUDA_T(I)); + + // Execute cvGS first to avoid OpenCV exceptions + cvGS::executeOperations(cv_stream, + cvGS::resize(d_inputs, down, BATCH), + cvGS::convertTo(), + cvGS::write(d_tensor_output, down)); + + for (size_t i = 0; i < BATCH; i++) { + cv::cuda::resize(d_inputs[i], d_outputs_cv[i], down, 0., 0., cv::INTER_LINEAR, cv_stream); + } + + cv::Mat h_tensor_output(BATCH, down.width * down.height * CV_MAT_CN(I), CV_MAT_DEPTH(I)); + d_tensor_output.download(h_tensor_output, cv_stream); + + std::array h_outputs_cv; + for (size_t i = 0; i < BATCH; i++) { + d_outputs_cv[i].download(h_outputs_cv[i], cv_stream); + } + + cv_stream.waitForCompletion(); + + for (size_t i = 0; i < BATCH; i++) { + cv::Mat row = h_tensor_output.row(static_cast(i)); + cv::Mat h_cvGSResult(down.height, down.width, I, row.data); + passed &= compareAndCheck(down.width, down.height, h_outputs_cv[i], h_cvGSResult); + } + + } catch (const cv::Exception& e) { + if (e.code != -210) { + error_s << e.what(); + passed = false; + exception = true; + } else { + std::stringstream ss; + ss << "test_resize_write_batch<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> not supported by OpenCV" << std::endl; + } + } catch (const std::exception& e) { + error_s << e.what(); + passed = false; + exception = true; + } + + if (!passed) { + if (!exception) { + std::stringstream ss; + ss << "test_resize_write_batch<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> failed!! RESULT ERROR: Some results do not match baseline." << std::endl; + } else { + std::stringstream ss; + ss << "test_resize_write_batch<" << cvTypeToString() << ", " << cvTypeToString(); + std::cout << ss.str() << "> failed!! EXCEPTION: " << error_s.str() << std::endl; + } + } + } + + return passed; +} + int launch() { constexpr size_t NUM_ELEMS_X = 3840; constexpr size_t NUM_ELEMS_Y = 2160; @@ -117,9 +502,17 @@ int launch() { std::unordered_map results; results["test_resize_write"] = true; + results["test_resize_write_scale_factors"] = true; + results["test_resize_write_fused"] = true; + results["test_resize_write_aspect_ratio"] = true; + results["test_resize_write_batch"] = true; #define LAUNCH_TESTS(CV_INPUT, CV_OUTPUT) \ - results["test_resize_write"] &= test_resize_write(NUM_ELEMS_X, NUM_ELEMS_Y, cv_stream, true); + results["test_resize_write"] &= test_resize_write(NUM_ELEMS_X, NUM_ELEMS_Y, cv_stream, true); \ + results["test_resize_write_scale_factors"] &= test_resize_write_scale_factors(NUM_ELEMS_X, NUM_ELEMS_Y, cv_stream, true); \ + results["test_resize_write_fused"] &= test_resize_write_fused(NUM_ELEMS_X, NUM_ELEMS_Y, cv_stream, true); \ + results["test_resize_write_aspect_ratio"] &= test_resize_write_aspect_ratio(NUM_ELEMS_X, NUM_ELEMS_Y, cv_stream, true); \ + results["test_resize_write_batch"] &= test_resize_write_batch(NUM_ELEMS_X, NUM_ELEMS_Y, cv_stream, true); LAUNCH_TESTS(CV_8UC1, CV_32FC1) LAUNCH_TESTS(CV_16UC1, CV_32FC1)