diff --git a/modules/fastcv/include/opencv2/fastcv/scale.hpp b/modules/fastcv/include/opencv2/fastcv/scale.hpp index 276b2304050..7e18ce81edd 100644 --- a/modules/fastcv/include/opencv2/fastcv/scale.hpp +++ b/modules/fastcv/include/opencv2/fastcv/scale.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -15,20 +15,18 @@ namespace fastcv { //! @{ /** - * @brief Down-scale the image by averaging each 2x2 pixel block. - * This function is not bit-exact with cv::resize but provides faster execution time on Qualcomm's processor. - * @param _src The first input image data, type CV_8UC1, src height must be a multiple of 2 - * @param _dst The output image data, type CV_8UC1 -*/ -CV_EXPORTS_W void resizeDownBy2(cv::InputArray _src, cv::OutputArray _dst); - -/** - * @brief Down-scale the image by averaging each 4x4 pixel block. - * This function is not bit-exact with cv::resize but provides faster execution time on Qualcomm's processor. - * @param _src The first input image data, type CV_8UC1, src height must be a multiple of 4 - * @param _dst The output image data, type CV_8UC1 -*/ -CV_EXPORTS_W void resizeDownBy4(cv::InputArray _src, cv::OutputArray _dst); + * @brief Down-scales the image using specified scaling factors or dimensions. + * This function supports both single-channel (CV_8UC1) and two-channel (CV_8UC2) images. + * + * @param _src The input image data, type CV_8UC1 or CV_8UC2. + * @param _dst The output image data, type CV_8UC1 or CV_8UC2. + * @param dsize The desired size of the output image. If empty, it is calculated using inv_scale_x and inv_scale_y. + * @param inv_scale_x The inverse scaling factor for the width. If dsize is provided, this parameter is ignored. + * @param inv_scale_y The inverse scaling factor for the height. If dsize is provided, this parameter is ignored. + * + * @note If dsize is not specified, inv_scale_x and inv_scale_y must be strictly positive. + */ +CV_EXPORTS_W void resizeDown(cv::InputArray _src, cv::OutputArray _dst, Size dsize, double inv_scale_x, double inv_scale_y); //! @} diff --git a/modules/fastcv/include/opencv2/fastcv/warp.hpp b/modules/fastcv/include/opencv2/fastcv/warp.hpp index 2c62b0cb313..dae1a72bcc3 100644 --- a/modules/fastcv/include/opencv2/fastcv/warp.hpp +++ b/modules/fastcv/include/opencv2/fastcv/warp.hpp @@ -44,6 +44,46 @@ CV_EXPORTS_W void warpPerspective(InputArray _src, OutputArray _dst, InputArray CV_EXPORTS_W void warpPerspective2Plane(InputArray _src1, InputArray _src2, OutputArray _dst1, OutputArray _dst2, InputArray _M0, Size dsize); +/** + * @brief Performs an affine transformation on an input image using a provided transformation matrix. + * + * This function performs two types of operations based on the transformation matrix: + * + * 1. Standard Affine Transformation (2x3 matrix): + * - Transforms the entire input image using the affine matrix + * - Supports both CV_8UC1 and CV_8UC3 types + * + * 2. Patch Extraction with Transformation (2x2 matrix): + * - Extracts and transforms a patch from the input image + * - Only supports CV_8UC1 type + * - If input is a ROI: patch is extracted from ROI center in the original image + * - If input is full image: patch is extracted from image center + * + * @param _src Input image. Supported formats: + * - CV_8UC1: 8-bit single-channel + * - CV_8UC3: 8-bit three-channel - only for 2x3 matrix + * @param _dst Output image. Will have the same type as src and size specified by dsize + * @param _M 2x2/2x3 affine transformation matrix (inversed map) + * @param dsize Output size: + * - For 2x3 matrix: Size of the output image + * - For 2x2 matrix: Size of the extracted patch + * @param interpolation Interpolation method. Only applicable for 2x3 transformation with CV_8UC1 input. + * Options: + * - INTER_NEAREST: Nearest-neighbor interpolation + * - INTER_LINEAR: Bilinear interpolation (default) + * - INTER_AREA: Area-based interpolation + * - INTER_CUBIC: Bicubic interpolation + * Note: CV_8UC3 input always use bicubic interpolation internally + * @param borderValue Constant pixel value for border pixels. Only applicable for 2x3 transformations + * with single-channel input. + * + * @note The affine matrix follows the inverse mapping convention, applied to destination coordinates + * to produce corresponding source coordinates. + * @note The function uses 'FASTCV_BORDER_CONSTANT' for border handling, with the specified 'borderValue'. +*/ +CV_EXPORTS_W void warpAffine(InputArray _src, OutputArray _dst, InputArray _M, Size dsize, int interpolation = INTER_LINEAR, + int borderValue = 0); + //! @} } diff --git a/modules/fastcv/perf/perf_scale.cpp b/modules/fastcv/perf/perf_scale.cpp new file mode 100644 index 00000000000..e9975d51a96 --- /dev/null +++ b/modules/fastcv/perf/perf_scale.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 +*/ + +#include "perf_precomp.hpp" + +namespace opencv_test { + +typedef perf::TestBaseWithParam> ResizePerfTest; + +PERF_TEST_P(ResizePerfTest, run, ::testing::Combine( + ::testing::Values(perf::szVGA, perf::sz720p, perf::sz1080p), // image size + ::testing::Values(2, 4) // resize factor +)) +{ + Size size = std::get<0>(GetParam()); + int factor = std::get<1>(GetParam()); + + cv::Mat inputImage(size, CV_8UC1); + cv::randu(inputImage, cv::Scalar::all(0), cv::Scalar::all(255)); + + cv::Mat resized_image; + Size dsize(inputImage.cols / factor, inputImage.rows / factor); + + while (next()) + { + startTimer(); + cv::fastcv::resizeDown(inputImage, resized_image, dsize, 0, 0); + stopTimer(); + } + + SANITY_CHECK_NOTHING(); +} + +typedef perf::TestBaseWithParam> ResizeByMnPerfTest; + +PERF_TEST_P(ResizeByMnPerfTest, run, ::testing::Combine( + ::testing::Values(perf::szVGA, perf::sz720p, perf::sz1080p), // image size + ::testing::Values(0.35, 0.65), // inv_scale_x + ::testing::Values(0.35, 0.65), // inv_scale_y + ::testing::Values(CV_8UC1, CV_8UC2) // data type +)) +{ + Size size = std::get<0>(GetParam()); + double inv_scale_x = std::get<1>(GetParam()); + double inv_scale_y = std::get<2>(GetParam()); + int type = std::get<3>(GetParam()); + + cv::Mat inputImage(size, type); + cv::randu(inputImage, cv::Scalar::all(0), cv::Scalar::all(255)); + + Size dsize; + cv::Mat resized_image; + + while (next()) + { + startTimer(); + cv::fastcv::resizeDown(inputImage, resized_image, dsize, inv_scale_x, inv_scale_y); + stopTimer(); + } + + SANITY_CHECK_NOTHING(); +} + +} // namespace \ No newline at end of file diff --git a/modules/fastcv/perf/perf_warp.cpp b/modules/fastcv/perf/perf_warp.cpp index a2ec2b65cee..008c1e100a7 100644 --- a/modules/fastcv/perf/perf_warp.cpp +++ b/modules/fastcv/perf/perf_warp.cpp @@ -39,6 +39,29 @@ static void getInvertMatrix(Mat& src, Size dstSize, Mat& M) invert(M,M); } +static cv::Mat getInverseAffine(const cv::Mat& affine) +{ + // Extract the 2x2 part + cv::Mat rotationScaling = affine(cv::Rect(0, 0, 2, 2)); + + // Invert the 2x2 part + cv::Mat inverseRotationScaling; + cv::invert(rotationScaling, inverseRotationScaling); + + // Extract the translation part + cv::Mat translation = affine(cv::Rect(2, 0, 1, 2)); + + // Compute the new translation + cv::Mat inverseTranslation = -inverseRotationScaling * translation; + + // Construct the inverse affine matrix + cv::Mat inverseAffine = cv::Mat::zeros(2, 3, CV_32F); + inverseRotationScaling.copyTo(inverseAffine(cv::Rect(0, 0, 2, 2))); + inverseTranslation.copyTo(inverseAffine(cv::Rect(2, 0, 1, 2))); + + return inverseAffine; +} + typedef perf::TestBaseWithParam WarpPerspective2PlanePerfTest; PERF_TEST_P(WarpPerspective2PlanePerfTest, run, @@ -93,4 +116,121 @@ PERF_TEST_P(WarpPerspectivePerfTest, run, SANITY_CHECK_NOTHING(); } +typedef TestBaseWithParam< tuple > WarpAffine3ChannelPerf; + +PERF_TEST_P(WarpAffine3ChannelPerf, run, Combine( + Values(CV_8UC3), + Values( szVGA, sz720p, sz1080p) +)) +{ + Size sz, szSrc(512, 512); + int dataType; + dataType = get<0>(GetParam()); + sz = get<1>(GetParam()); + + cv::Mat src(szSrc, dataType), dst(sz, dataType); + + cvtest::fillGradient(src); + + //Affine matrix + float angle = 30.0; // Rotation angle in degrees + float scale = 2.2; // Scale factor + cv::Mat affine = cv::getRotationMatrix2D(cv::Point2f(100, 100), angle, scale); + + // Compute the inverse affine matrix + cv::Mat inverseAffine = getInverseAffine(affine); + + // Create the dstBorder array + Mat dstBorder; + + declare.in(src).out(dst); + + while (next()) + { + startTimer(); + cv::fastcv::warpAffine(src, dst, inverseAffine, sz); + stopTimer(); + } + + SANITY_CHECK_NOTHING(); +} + +typedef perf::TestBaseWithParam> WarpAffineROIPerfTest; + +PERF_TEST_P(WarpAffineROIPerfTest, run, ::testing::Combine( + ::testing::Values(cv::Size(50, 50), cv::Size(100, 100)), // patch size + ::testing::Values(cv::Point2f(50.0f, 50.0f), cv::Point2f(100.0f, 100.0f)), // position + ::testing::Values((cv::Mat_(2, 2) << 1, 0, 0, 1), // identity matrix + (cv::Mat_(2, 2) << cos(CV_PI), -sin(CV_PI), sin(CV_PI), cos(CV_PI))) // rotation matrix +)) +{ + cv::Size patchSize = std::get<0>(GetParam()); + cv::Point2f position = std::get<1>(GetParam()); + cv::Mat affine = std::get<2>(GetParam()); + + cv::Mat src = cv::imread(cvtest::findDataFile("cv/shared/baboon.png"), cv::IMREAD_GRAYSCALE); + + // Create ROI with top-left at the specified position + cv::Rect roiRect(static_cast(position.x), static_cast(position.y), patchSize.width, patchSize.height); + + // Ensure ROI is within image bounds + roiRect = roiRect & cv::Rect(0, 0, src.cols, src.rows); + cv::Mat roi = src(roiRect); + + cv::Mat patch; + + while (next()) + { + startTimer(); + cv::fastcv::warpAffine(roi, patch, affine, patchSize); + stopTimer(); + } + + SANITY_CHECK_NOTHING(); +} + +typedef TestBaseWithParam > WarpAffinePerfTest; + +PERF_TEST_P(WarpAffinePerfTest, run, ::testing::Combine( + ::testing::Values(cv::InterpolationFlags::INTER_NEAREST, cv::InterpolationFlags::INTER_LINEAR, cv::InterpolationFlags::INTER_AREA), + ::testing::Values(0, 255) // Black and white borders +)) +{ + // Load the source image + cv::Mat src = cv::imread(cvtest::findDataFile("cv/shared/baboon.png"), cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(src.empty()); + + // Generate random values for the affine matrix + std::srand(std::time(0)); + float angle = static_cast(std::rand() % 360); // Random angle between 0 and 360 degrees + float scale = static_cast(std::rand() % 200) / 100.0f + 0.5f; // Random scale between 0.5 and 2.5 + float tx = static_cast(std::rand() % 100) - 50; // Random translation between -50 and 50 + float ty = static_cast(std::rand() % 100) - 50; // Random translation between -50 and 50 + float radians = angle * CV_PI / 180.0; + cv::Mat affine = (cv::Mat_(2, 3) << scale * cos(radians), -scale * sin(radians), tx, + scale * sin(radians), scale * cos(radians), ty); + + // Compute the inverse affine matrix + cv::Mat inverseAffine = getInverseAffine(affine); + + // Define the destination size + cv::Size dsize(src.cols, src.rows); + + // Define the output matrix + cv::Mat dst; + + // Get the parameters + int interpolation = std::get<0>(GetParam()); + int borderValue = std::get<1>(GetParam()); + + while (next()) + { + startTimer(); + cv::fastcv::warpAffine(src, dst, inverseAffine, dsize, interpolation, borderValue); + stopTimer(); + } + + SANITY_CHECK_NOTHING(); +} + } //namespace \ No newline at end of file diff --git a/modules/fastcv/src/scale.cpp b/modules/fastcv/src/scale.cpp index 3e1a3a74b8a..0e37e96213f 100644 --- a/modules/fastcv/src/scale.cpp +++ b/modules/fastcv/src/scale.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -8,55 +8,68 @@ namespace cv { namespace fastcv { -void resizeDownBy2(cv::InputArray _src, cv::OutputArray _dst) +void resizeDown(cv::InputArray _src, cv::OutputArray _dst, Size dsize, double inv_scale_x, double inv_scale_y) { - INITIALIZATION_CHECK; - - CV_Assert(!_src.empty() && _src.type() == CV_8UC1); - - Mat src = _src.getMat(); - CV_Assert((src.cols & 1)==0 && (src.rows & 1)==0); - - int type = _src.type(); - cv::Size dsize(src.cols / 2, src.rows / 2); - - _dst.create(dsize, type); - - Mat dst = _dst.getMat(); + fcvStatus status = FASTCV_SUCCESS; + Size ssize = _src.size(); - fcvStatus status = (fcvStatus)fcvScaleDownBy2u8_v2((const uint8_t*)src.data, src.cols, src.rows, src.step, (uint8_t*)dst.data, - src.cols/2); + CV_Assert(!_src.empty() ); + CV_Assert( _src.type() == CV_8UC1 || _src.type() == CV_8UC2 ); - if (status != FASTCV_SUCCESS) + if( dsize.empty() ) { - std::string s = fcvStatusStrings.count(status) ? fcvStatusStrings.at(status) : "unknown"; - CV_Error( cv::Error::StsInternal, "FastCV error: " + s); + CV_Assert(inv_scale_x > 0); + CV_Assert(inv_scale_y > 0); + dsize = Size(saturate_cast(ssize.width*inv_scale_x), + saturate_cast(ssize.height*inv_scale_y)); + CV_Assert( !dsize.empty() ); + } + else + { + inv_scale_x = static_cast(dsize.width) / ssize.width; + inv_scale_y = static_cast(dsize.height) / ssize.height; + CV_Assert(inv_scale_x > 0); + CV_Assert(inv_scale_y > 0); } -} - -void resizeDownBy4(cv::InputArray _src, cv::OutputArray _dst) -{ - INITIALIZATION_CHECK; - - CV_Assert(!_src.empty() && _src.type() == CV_8UC1); - Mat src = _src.getMat(); - CV_Assert((src.cols & 3)==0 && (src.rows & 3)==0); + CV_Assert(dsize.width <= ssize.width && dsize.height <= ssize.height); - int type = _src.type(); - cv::Size dsize(src.cols / 4, src.rows / 4); + CV_Assert(dsize.width * 20 > ssize.width); + CV_Assert(dsize.height * 20 > ssize.height); - _dst.create(dsize, type); + INITIALIZATION_CHECK; + Mat src = _src.getMat(); + _dst.create(dsize, src.type()); Mat dst = _dst.getMat(); - fcvStatus status = (fcvStatus)fcvScaleDownBy4u8_v2((const uint8_t*)src.data, src.cols, src.rows, src.step, - (uint8_t*)dst.data, src.cols/4); + // Alignment checks + CV_Assert(reinterpret_cast(src.data) % 16 == 0); + CV_Assert(reinterpret_cast(dst.data) % 16 == 0); + + if(src.type() == CV_8UC2) + { + fcvScaleDownMNInterleaveu8((const uint8_t*)src.data, src.cols, src.rows, src.step, (uint8_t*)dst.data, dst.cols, dst.rows, dst.step); + } + else if (src.cols/dst.cols == 4 && src.rows/dst.rows == 4 && src.cols % dst.cols == 0 && src.rows % dst.rows == 0) + { + CV_Assert(src.rows % 4 == 0); + status = (fcvStatus)fcvScaleDownBy4u8_v2((const uint8_t*)src.data, src.cols, src.rows, src.step, (uint8_t*)dst.data, dst.step); + } + else if (src.cols/dst.cols == 2 && src.rows/dst.rows == 2 && src.cols % dst.cols == 0 && src.rows % dst.rows == 0) + { + CV_Assert(src.rows % 2 == 0); + status = (fcvStatus)fcvScaleDownBy2u8_v2((const uint8_t*)src.data, src.cols, src.rows, src.step, (uint8_t*)dst.data, dst.step); + } + else + { + fcvScaleDownMNu8((const uint8_t*)src.data, src.cols, src.rows, src.step, (uint8_t*)dst.data, dst.cols, dst.rows, dst.step); + } if (status != FASTCV_SUCCESS) { std::string s = fcvStatusStrings.count(status) ? fcvStatusStrings.at(status) : "unknown"; - CV_Error( cv::Error::StsInternal, "FastCV error: " + s); + CV_Error(cv::Error::StsInternal, "FastCV error: " + s); } } diff --git a/modules/fastcv/src/warp.cpp b/modules/fastcv/src/warp.cpp index ac806ffc4ae..28e312e26f9 100644 --- a/modules/fastcv/src/warp.cpp +++ b/modules/fastcv/src/warp.cpp @@ -175,5 +175,146 @@ void warpPerspective(InputArray _src, OutputArray _dst, InputArray _M0, Size dsi FcvWarpPerspectiveLoop_Invoker(src, tmp, dst, tmp, matrix, fcvInterpolation, fcvBorder, fcvBorderValue), nStripes); } +void warpAffine(InputArray _src, OutputArray _dst, InputArray _M, Size dsize, + int interpolation, int borderValue) +{ + INITIALIZATION_CHECK; + CV_Assert(!_src.empty()); + CV_Assert(!_M.empty()); + + Mat src = _src.getMat(); + Mat M = _M.getMat(); + + CV_CheckEQ(M.rows, 2, "Affine Matrix must have 2 rows"); + CV_Check(M.cols, M.cols == 2 || M.cols == 3, "Affine Matrix must be 2x2 or 2x3"); + + if (M.rows == 2 && M.cols == 2) + { + CV_CheckTypeEQ(src.type(), CV_8UC1, "2x2 matrix transformation only supports CV_8UC1"); + + // Check if src is a ROI + Size wholeSize; + Point ofs; + src.locateROI(wholeSize, ofs); + bool isROI = (wholeSize.width > src.cols || wholeSize.height > src.rows); + + Mat fullImage; + Point2f center; + + if (isROI) + { + center.x = ofs.x + src.cols / 2.0f; + center.y = ofs.y + src.rows / 2.0f; + + CV_Check(center.x, center.x >= 0 && center.x < wholeSize.width, "ROI center X is outside full image bounds"); + CV_Check(center.y, center.y >= 0 && center.y < wholeSize.height, "ROI center Y is outside full image bounds"); + + size_t offset = ofs.y * src.step + ofs.x * src.elemSize(); + fullImage = Mat(wholeSize, src.type(), src.data - offset); + } + else + { + // Use src as is, center at image center + fullImage = src; + center.x = src.cols / 2.0f; + center.y = src.rows / 2.0f; + + CV_LOG_WARNING(NULL, "2x2 matrix with non-ROI input. Using image center for patch extraction."); + } + + float affineMatrix[4] = { + M.at(0, 0), M.at(0, 1), + M.at(1, 0), M.at(1, 1)}; + + float position[2] = {center.x, center.y}; + + _dst.create(dsize, src.type()); + Mat dst = _dst.getMat(); + dst.step = dst.cols * src.elemSize(); + + int status = fcvTransformAffineu8_v2( + (const uint8_t *)fullImage.data, + fullImage.cols, fullImage.rows, fullImage.step, + position, + affineMatrix, + (uint8_t *)dst.data, + dst.cols, dst.rows, dst.step); + + if (status != 0) + { + CV_Error(Error::StsInternal, "FastCV patch extraction failed"); + } + + return; + } + + // Validate 2x3 matrix for standard transformation + CV_CheckEQ(M.cols, 3, "Matrix must be 2x3 for standard affine transformation"); + CV_Check(src.type(), src.type() == CV_8UC1 || src.type() == CV_8UC3, "Standard transformation supports CV_8UC1 or CV_8UC3"); + + float32_t affineMatrix[6] = { + M.at(0, 0), M.at(0, 1), M.at(0, 2), + M.at(1, 0), M.at(1, 1), M.at(1, 2)}; + + _dst.create(dsize, src.type()); + Mat dst = _dst.getMat(); + + if (src.channels() == 1) + { + fcvStatus status; + fcvInterpolationType fcvInterpolation; + + switch (interpolation) + { + case cv::InterpolationFlags::INTER_NEAREST: + fcvInterpolation = FASTCV_INTERPOLATION_TYPE_NEAREST_NEIGHBOR; + break; + case cv::InterpolationFlags::INTER_LINEAR: + fcvInterpolation = FASTCV_INTERPOLATION_TYPE_BILINEAR; + break; + case cv::InterpolationFlags::INTER_AREA: + fcvInterpolation = FASTCV_INTERPOLATION_TYPE_AREA; + break; + default: + CV_Error(cv::Error::StsBadArg, "Unsupported interpolation type"); + } + + status = fcvTransformAffineClippedu8_v3( + (const uint8_t *)src.data, src.cols, src.rows, src.step, + affineMatrix, + (uint8_t *)dst.data, dst.cols, dst.rows, dst.step, + NULL, + fcvInterpolation, + FASTCV_BORDER_CONSTANT, + borderValue); + + if (status != FASTCV_SUCCESS) + { + std::string s = fcvStatusStrings.count(status) ? fcvStatusStrings.at(status) : "unknown"; + CV_Error(cv::Error::StsInternal, "FastCV error: " + s); + } + } + else if (src.channels() == 3) + { + CV_LOG_INFO(NULL, "warpAffine: 3-channel images use bicubic interpolation internally."); + + std::vector dstBorder; + try + { + dstBorder.resize(dsize.height * 2); + } + catch (const std::bad_alloc &) + { + CV_Error(Error::StsNoMem, "Failed to allocate border array"); + } + + fcv3ChannelTransformAffineClippedBCu8( + (const uint8_t *)src.data, src.cols, src.rows, src.step[0], + affineMatrix, + (uint8_t *)dst.data, dst.cols, dst.rows, dst.step[0], + dstBorder.data()); + } +} + } // fastcv:: } // cv:: \ No newline at end of file diff --git a/modules/fastcv/test/test_scale.cpp b/modules/fastcv/test/test_scale.cpp index b8e84218ed8..46ffa7d32f8 100644 --- a/modules/fastcv/test/test_scale.cpp +++ b/modules/fastcv/test/test_scale.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -7,27 +7,21 @@ namespace opencv_test { namespace { -class ResizeBy2Test : public ::testing::TestWithParam {}; -class ResizeBy4Test : public ::testing::TestWithParam {}; - TEST(resizeDownBy2, accuracy) { cv::Mat inputImage = cv::imread(cvtest::findDataFile("cv/shared/box_in_scene.png"), cv::IMREAD_GRAYSCALE); - Size dsize; cv::Mat resized_image; - cv::fastcv::resizeDownBy2(inputImage, resized_image); + cv::fastcv::resizeDown(inputImage, resized_image, cv::Size(inputImage.cols / 2, inputImage.rows / 2), 0, 0); EXPECT_FALSE(resized_image.empty()); cv::Mat resizedImageOpenCV; cv::resize(inputImage, resizedImageOpenCV, cv::Size(inputImage.cols / 2, inputImage.rows / 2), 0, 0, INTER_AREA); - // Calculate the maximum difference double maxVal = cv::norm(resized_image, resizedImageOpenCV, cv::NORM_INF); - // Assert if the difference is acceptable (max difference should be less than 10) CV_Assert(maxVal < 10 && "Difference between images is too high!"); } @@ -38,67 +32,56 @@ TEST(resizeDownBy4, accuracy) Size dsize; cv::Mat resized_image; - cv::fastcv::resizeDownBy4(inputImage, resized_image); + cv::fastcv::resizeDown(inputImage, resized_image, dsize, 0.25, 0.25); EXPECT_FALSE(resized_image.empty()); cv::Mat resizedImageOpenCV; cv::resize(inputImage, resizedImageOpenCV, cv::Size(inputImage.cols / 4, inputImage.rows / 4), 0, 0, INTER_AREA); - // Calculate the maximum difference double maxVal = cv::norm(resized_image, resizedImageOpenCV, cv::NORM_INF); - // Assert if the difference is acceptable (max difference should be less than 10) CV_Assert(maxVal < 10 && "Difference between images is too high!"); } -TEST_P(ResizeBy2Test, ResizeBy2) { - - //Size size = get<0>(GetParam()); - Size size = GetParam(); - cv::Mat inputImage(size, CV_8UC1); - randu(inputImage, Scalar::all(0), Scalar::all(255)); // Fill with random values +TEST(resizeDownMN, accuracy) +{ + cv::Mat inputImage = cv::imread(cvtest::findDataFile("cv/cascadeandhog/images/class57.png"), cv::IMREAD_GRAYSCALE); - Size dsize; cv::Mat resized_image; - // Resize the image by a factor of 2 - cv::fastcv::resizeDownBy2(inputImage, resized_image); + cv::fastcv::resizeDown(inputImage, resized_image, cv::Size(800, 640), 0, 0); - // Check if the output size is correct - EXPECT_EQ(resized_image.size().width, size.width * 0.5); - EXPECT_EQ(resized_image.size().height, size.height * 0.5); + EXPECT_FALSE(resized_image.empty()); + + cv::Mat resizedImageOpenCV; + cv::resize(inputImage, resizedImageOpenCV, cv::Size(800, 640), 0, 0, INTER_LINEAR); + + double maxVal = cv::norm(resized_image, resizedImageOpenCV, cv::NORM_INF); + + CV_Assert(maxVal < 78 && "Difference between images is too high!"); } -TEST_P(ResizeBy4Test, ResizeBy4) { +TEST(resizeDownInterleaved, accuracy) +{ + cv::Mat inputImage = cv::Mat::zeros(512, 512, CV_8UC2); + cv::randu(inputImage, cv::Scalar(0), cv::Scalar(255)); - //Size size = get<0>(GetParam()); - Size size = GetParam(); - cv::Mat inputImage(size, CV_8UC1); - randu(inputImage, Scalar::all(0), Scalar::all(255)); // Fill with random values Size dsize; cv::Mat resized_image; - // Resize the image by a factor of 4 - cv::fastcv::resizeDownBy4(inputImage, resized_image); + cv::fastcv::resizeDown(inputImage, resized_image, dsize, 0.500, 0.125); - // Check if the output size is correct - EXPECT_EQ(resized_image.size().width, size.width * 0.25); - EXPECT_EQ(resized_image.size().height, size.height * 0.25); -} + EXPECT_FALSE(resized_image.empty()); -INSTANTIATE_TEST_CASE_P( - ResizeTests, - ResizeBy2Test, - ::testing::Values(cv::Size(640, 480), cv::Size(1280, 720), cv::Size(1920, 1080) -)); -INSTANTIATE_TEST_CASE_P( - ResizeTests, - ResizeBy4Test, - ::testing::Values(cv::Size(640, 480), cv::Size(1280, 720), cv::Size(1920, 1080) -)); + cv::Mat resizedImageOpenCV; + cv::resize(inputImage, resizedImageOpenCV, dsize, 0.500, 0.125, INTER_AREA); + double maxVal = cv::norm(resized_image, resizedImageOpenCV, cv::NORM_INF); + + CV_Assert(maxVal < 10 && "Difference between images is too high!"); +} }} // namespaces opencv_test, :: \ No newline at end of file diff --git a/modules/fastcv/test/test_warp.cpp b/modules/fastcv/test/test_warp.cpp index a87902ad102..72f32bda031 100644 --- a/modules/fastcv/test/test_warp.cpp +++ b/modules/fastcv/test/test_warp.cpp @@ -39,6 +39,29 @@ static void getInvertMatrix(Mat& src, Size dstSize, Mat& M) invert(M,M); } +static cv::Mat getInverseAffine(const cv::Mat& affine) +{ + // Extract the 2x2 part + cv::Mat rotationScaling = affine(cv::Rect(0, 0, 2, 2)); + + // Invert the 2x2 part + cv::Mat inverseRotationScaling; + cv::invert(rotationScaling, inverseRotationScaling); + + // Extract the translation part + cv::Mat translation = affine(cv::Rect(2, 0, 1, 2)); + + // Compute the new translation + cv::Mat inverseTranslation = -inverseRotationScaling * translation; + + // Construct the inverse affine matrix + cv::Mat inverseAffine = cv::Mat::zeros(2, 3, CV_32F); + inverseRotationScaling.copyTo(inverseAffine(cv::Rect(0, 0, 2, 2))); + inverseTranslation.copyTo(inverseAffine(cv::Rect(2, 0, 1, 2))); + + return inverseAffine; +} + typedef testing::TestWithParam WarpPerspective2Plane; TEST_P(WarpPerspective2Plane, accuracy) @@ -106,5 +129,82 @@ INSTANTIATE_TEST_CASE_P(FastCV_Extension, WarpPerspective,Combine( )); INSTANTIATE_TEST_CASE_P(FastCV_Extension, WarpPerspective2Plane, Values(perf::szVGA, perf::sz720p, perf::sz1080p)); +TEST(WarpAffine3ChannelTest, accuracy) +{ + cv::Mat src = imread(cvtest::findDataFile("cv/shared/baboon.png")); + + // Define the transformation matrix + cv::Mat M = (cv::Mat_(2, 3) << 2.0, 0, -50.0, 0, 2.0, -50.0); + + cv::Size dsize(src.cols, src.rows); + + cv::Mat dst; + + cv::fastcv::warpAffine(src, dst, M, dsize); + + EXPECT_FALSE(dst.empty()); +} + +TEST(WarpAffineROITest, accuracy) +{ + cv::Mat src = cv::imread(cvtest::findDataFile("cv/shared/baboon.png"), cv::IMREAD_GRAYSCALE); + + // Define the position and affine matrix + cv::Point2f position(src.cols / 2.0f, src.rows / 2.0f); + + float angle = 180.0; // Rotation angle in degrees + float radians = angle * CV_PI / 180.0; + cv::Mat affine = (cv::Mat_(2, 2) << cos(radians), -sin(radians), sin(radians), cos(radians)); + + cv::Mat patch; + cv::Mat roi = src(cv::Rect(0, 0, 100, 100)); + cv::fastcv::warpAffine(roi, patch, affine, cv::Size(100, 100)); + + EXPECT_FALSE(patch.empty()); + EXPECT_EQ(patch.size(), cv::Size(100, 100)); + EXPECT_EQ(patch.type(), CV_8UC1); +} + +typedef testing::TestWithParam> WarpAffineTest; + +TEST_P(WarpAffineTest, accuracy) +{ + // Load the source image + cv::Mat src = cv::imread(cvtest::findDataFile("cv/shared/baboon.png"), cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(src.empty()); + + float angle = 30.0;// Rotation angle in degrees + float scale = 0.5;// Scale factor + cv::Mat affine = cv::getRotationMatrix2D(cv::Point2f(100, 100), angle, scale); + + // Compute the inverse affine matrix + cv::Mat inverseAffine = getInverseAffine(affine); + + // Define the destination size + cv::Size dsize(src.cols, src.rows); + + // Define the output matrix + cv::Mat dst; + + // Get the parameters + int interpolation = std::get<0>(GetParam()); + int borderValue = std::get<1>(GetParam()); + + // Perform the affine transformation + cv::fastcv::warpAffine(src, dst, inverseAffine, dsize, interpolation, borderValue); + + // Check that the output is not empty + EXPECT_FALSE(dst.empty()); +} + +INSTANTIATE_TEST_CASE_P( + FastCV_Extension, + WarpAffineTest, + ::testing::Combine( + ::testing::Values(INTER_NEAREST, INTER_LINEAR, INTER_AREA), + ::testing::Values(0, 255) // Black and white borders + ) +); + } } \ No newline at end of file