Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion include/cvGPUSpeedup.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ inline constexpr auto warp(const cv::cuda::GpuMat& input, const cv::Mat& transfo
if (transform_matrix.type() != CV_64FC1) {
throw std::runtime_error("Transform matrix type should be CV_64FC1.");
}
const auto read = fk::PerThreadRead<fk::ND::_2D, CUDA_T(InputType)>::build(fk::RawPtr<fk::ND::_2D, CUDA_T(InputType)>{ (CUDA_T(InputType)*)input.data, { static_cast<uint>(input.cols), static_cast<uint>(input.rows), static_cast<uint>(input.step) } });
const auto read = getReader<InputType>(input);
if constexpr (WT == fk::WarpType::Affine) {
cv::Mat inverse_transform_matrix;
cv::invertAffineTransform(transform_matrix, inverse_transform_matrix);
Expand All @@ -307,6 +307,28 @@ inline constexpr auto warp(const cv::cuda::GpuMat& input, const cv::Mat& transfo
}
}

template <enum fk::WarpType WT>
inline constexpr auto warp(const cv::Mat& transform_matrix, const cv::Size& dstSize) {
if (transform_matrix.type() != CV_64FC1) {
throw std::runtime_error("Transform matrix type should be CV_64FC1.");
}

if constexpr (WT == fk::WarpType::Affine) {
cv::Mat inverse_transform_matrix;
cv::invertAffineTransform(transform_matrix, inverse_transform_matrix);
const double* const tm_raw = inverse_transform_matrix.ptr<double>();
const auto params = internal::warp_getWarpingAffineParameters(tm_raw, dstSize);

return fk::Warping<fk::WarpType::Affine>::build(params);
} else {
const cv::Mat inverse_transform_matrix(transform_matrix.inv());
const double* const tm_raw = inverse_transform_matrix.ptr<double>();
const auto params = internal::warp_getWarpingPerspectiveParameters(tm_raw, dstSize);

return fk::Warping<fk::WarpType::Perspective>::build(params);
}
}

namespace internal {
template <size_t BATCH>
inline constexpr auto warp_batchAffineParameters_helper_rt(const std::array<cv::Mat, BATCH>& transform_matrices,
Expand Down Expand Up @@ -400,13 +422,35 @@ inline constexpr auto warp(const std::array<cv::cuda::GpuMat, BATCH>& inputs,
return readBatch.then(fk_batch_warp);
}

template <enum fk::WarpType WT, size_t BATCH>
inline constexpr auto warp(const std::array<cv::Mat, BATCH>& transform_matrices,
const std::array<cv::Size, BATCH>& dstSize) {
for (int i = 0; i < BATCH; ++i) {
if (transform_matrices[i].type() != CV_64FC1) {
throw std::runtime_error("Transform matrix type should be CV_64FC1.");
}
}

const auto fk_warpParams = internal::warp_batchParameters<WT>(transform_matrices, dstSize);

const auto fk_batch_warp = fk::Warping<WT>::build(fk_warpParams);

return fk_batch_warp;
}

template <enum fk::WarpType WT, int InputType, size_t BATCH>
inline constexpr auto warp(const std::array<cv::cuda::GpuMat, BATCH>& inputs,
const std::array<cv::Mat, BATCH>& transform_matrices,
const cv::Size& dstSize) {
return warp<WT, InputType>(inputs, transform_matrices, fk::make_set_std_array<BATCH>(dstSize));
}

template <enum fk::WarpType WT, size_t BATCH>
inline constexpr auto warp(const std::array<cv::Mat, BATCH>& transform_matrices,
const cv::Size& dstSize) {
return warp<WT>(transform_matrices, fk::make_set_std_array<BATCH>(dstSize));
}

template <enum fk::WarpType WT, int InputType, size_t BATCH>
inline constexpr auto warp(const std::array<cv::cuda::GpuMat, BATCH>& inputs,
const std::array<cv::Mat, BATCH>& transform_matrices,
Expand All @@ -433,6 +477,25 @@ inline constexpr auto warp(const std::array<cv::cuda::GpuMat, BATCH>& inputs,
return readBatch.then(fk_batch_warp);
}

template <enum fk::WarpType WT, int DEFAULT_TYPE, size_t BATCH>
inline constexpr auto warp(const std::array<cv::Mat, BATCH>& transform_matrices,
const std::array<cv::Size, BATCH>& dstSize,
const int& usedPlanes, const cv::Scalar& defaultValue) {
for (int i = 0; i < usedPlanes; ++i) {
if (transform_matrices[i].type() != CV_64FC1) {
throw std::runtime_error("Transform matrix type should be CV_64FC1.");
}
}
Comment thread
morousg marked this conversation as resolved.
using DefaultType = CUDA_T(DEFAULT_TYPE);
const auto fk_defaultValue = defaultValue == cv::Scalar() ? fk::make_set<DefaultType>(0.f) : cvScalar2CUDAV<DEFAULT_TYPE>::get(defaultValue);

const auto fk_warpParams = internal::warp_batchParameters<WT>(transform_matrices, dstSize, usedPlanes);

const auto fk_batch_warp = fk::Warping<WT>::build(usedPlanes, fk_defaultValue, fk_warpParams);

return fk_batch_warp;
}

template <enum fk::WarpType WT, int InputType, size_t BATCH>
inline constexpr auto warp(const std::array<cv::cuda::GpuMat, BATCH>& inputs,
const std::array<cv::Mat, BATCH>& transform_matrices,
Expand All @@ -441,6 +504,13 @@ inline constexpr auto warp(const std::array<cv::cuda::GpuMat, BATCH>& inputs,
return warp<WT, InputType>(inputs, transform_matrices, fk::make_set_std_array<BATCH>(dstSize), usedPlanes, defaultValue);
}

template <enum fk::WarpType WT, int DEFAULT_TYPE, size_t BATCH>
inline constexpr auto warp(const std::array<cv::Mat, BATCH>& transform_matrices,
const cv::Size& dstSize,
const int& usedPlanes, const cv::Scalar& defaultValue) {
return warp<WT, DEFAULT_TYPE>(transform_matrices, fk::make_set_std_array<BATCH>(dstSize), usedPlanes, defaultValue);
}

template <typename BackIOp, int BATCH>
inline constexpr auto crop(const BackIOp& backIOp, const std::array<cv::Rect2d, BATCH>& rects) {
return backIOp.then(crop(rects));
Expand Down
16 changes: 15 additions & 1 deletion include/cvGPUSpeedupHelpers.cuh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* Copyright 2023-2025 Oscar Amoros Huguet
/* Copyright 2023-2026 Oscar Amoros Huguet
* Copyright 2026 Grup Mediapro S.L.U

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -20,6 +21,19 @@
#include <opencv2/core/cuda.hpp>

namespace cvGS {

template <int InputType>
inline constexpr auto getReader(const cv::cuda::GpuMat& input) {
return fk::PerThreadRead<fk::ND::_2D, CUDA_T(InputType)>::build(
fk::RawPtr<fk::ND::_2D, CUDA_T(InputType)>{
(CUDA_T(InputType)*)input.data,
{ static_cast<uint>(input.cols),
static_cast<uint>(input.rows),
static_cast<uint>(input.step) }
}
);
}

template <int T>
inline cv::Scalar cvScalar_set(const BASE_CUDA_T(T)& value) {
if constexpr (CV_MAT_CN(T) == 1) {
Expand Down
243 changes: 242 additions & 1 deletion tests/warping/test_warping_opencv.cu
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,253 @@ bool testPerspectiveBatchNotAll() {
return true;
}

bool testPerspectiveNoInput() {
// Load the image
const cv::Mat img = cv::imread(getSourceDir() + "/images/NSightSystemsTimeline1.png");
if (img.empty()) {
std::cerr << "Error loading image" << std::endl;
return false;
}

cv::cuda::Stream stream;

// Upload the image to GPU
const cv::cuda::GpuMat d_img(img);

// Define the source and destination points for perspective transformation
cv::Point2f src_points[4] = { cv::Point2f(56, 65), cv::Point2f(368, 52), cv::Point2f(28, 387), cv::Point2f(389, 390) };
cv::Point2f dst_points[4] = { cv::Point2f(0, 0), cv::Point2f(300, 0), cv::Point2f(0, 300), cv::Point2f(300, 300) };

// Get the perspective transformation matrix
cv::Mat perspective_matrix = cv::getPerspectiveTransform(src_points, dst_points);

// Preallocate the result images
cv::cuda::GpuMat d_resultcv(img.size(), CV_8UC3);
cv::cuda::GpuMat d_resultcvGS(img.size(), CV_8UC3);

// Apply the perspective transformation
cv::cuda::warpPerspective(d_img, d_resultcv, perspective_matrix, img.size(), 1, 0, cv::Scalar(), stream);

const auto readIOp = cvGS::getReader<CV_8UC3>(d_img);

const auto warpFunc = cvGS::warp<fk::WarpType::Perspective>(perspective_matrix, img.size());

auto writeFunc = cvGS::write<CV_8UC3>(d_resultcvGS);
cvGS::executeOperations(stream, readIOp, warpFunc, fk::Cast<float3, uchar3>::build(), writeFunc);

stream.waitForCompletion();

// Download the result back to CPU
cv::Mat resultcv(d_resultcv);
cv::Mat resultcvGS(d_resultcvGS);

const bool correct = compareAndCheck<CV_8UC3>(resultcv, resultcvGS);

std::cout << "Perspective transformation: " << (correct ? "PASS" : "EXPECTED_FAIL") << std::endl;

return true;
}

bool testAffineNoInput() {
// Load the image
const cv::Mat img = cv::imread(getSourceDir() + "/images/NSightSystemsTimeline1.png");
if (img.empty()) {
std::cerr << "Error loading image" << std::endl;
return false;
}

cv::cuda::Stream stream;

// Upload the image to GPU
const cv::cuda::GpuMat d_img(img);

// Define the translation values
double tx = 50, ty = 100;

// Get the affine transformation matrix
cv::Mat affine_matrix = (cv::Mat_<double>(2, 3) << 1, 0, tx, 0, 1, ty);

// Preallocate the result images
cv::cuda::GpuMat d_resultcv(img.size(), CV_8UC3);
cv::cuda::GpuMat d_resultcvGS(img.size(), CV_8UC3);

// Apply the affine transformation
cv::cuda::GpuMat d_result;
cv::cuda::warpAffine(d_img, d_resultcv, affine_matrix, img.size());

const auto readIOp = cvGS::getReader<CV_8UC3>(d_img);
const auto warpFunc = cvGS::warp<fk::WarpType::Affine>(affine_matrix, img.size());
auto writeFunc = cvGS::write<CV_8UC3>(d_resultcvGS);
cvGS::executeOperations(stream, readIOp, warpFunc, fk::Cast<float3, uchar3>::build(), writeFunc);

stream.waitForCompletion();

// Download the result back to CPU
cv::Mat resultcv(d_resultcv);
cv::Mat resultcvGS(d_resultcvGS);

const bool correct = compareAndCheck<CV_8UC3>(resultcv, resultcvGS);

std::cout << "Affine transformation: " << (correct ? "PASS" : "FAIL") << std::endl;

return correct;
}

bool testPerspectiveBatchNoInput() {
constexpr size_t NUM_IMGS = 5;

// Load the image
const cv::Mat img = cv::imread(getSourceDir() + "/images/NSightSystemsTimeline1.png");
if (img.empty()) {
std::cerr << "Error loading image" << std::endl;
return false;
}

cv::cuda::Stream stream;

// Upload the image to GPU
const cv::cuda::GpuMat d_img(img);
// Compiler bug: can't use NUM_IMGS with std::array
const std::array<cv::cuda::GpuMat, 5> d_imgs = { d_img, d_img, d_img, d_img, d_img };

// Define the source and destination points for perspective transformation
cv::Point2f src_points1[4] = { cv::Point2f(56, 65), cv::Point2f(368, 52), cv::Point2f(28, 387), cv::Point2f(389, 390) };
cv::Point2f dst_points1[4] = { cv::Point2f(0, 0), cv::Point2f(300, 0), cv::Point2f(0, 300), cv::Point2f(300, 300) };

cv::Point2f src_points2[4] = { cv::Point2f(50, 50), cv::Point2f(400, 50), cv::Point2f(50, 400), cv::Point2f(400, 400) };
cv::Point2f dst_points2[4] = { cv::Point2f(0, 0), cv::Point2f(300, 0), cv::Point2f(0, 300), cv::Point2f(300, 300) };

cv::Point2f src_points3[4] = { cv::Point2f(30, 30), cv::Point2f(350, 30), cv::Point2f(30, 350), cv::Point2f(350, 350) };
cv::Point2f dst_points3[4] = { cv::Point2f(0, 0), cv::Point2f(250, 0), cv::Point2f(0, 250), cv::Point2f(250, 250) };

cv::Point2f src_points4[4] = { cv::Point2f(70, 70), cv::Point2f(370, 70), cv::Point2f(70, 370), cv::Point2f(370, 370) };
cv::Point2f dst_points4[4] = { cv::Point2f(0, 0), cv::Point2f(280, 0), cv::Point2f(0, 280), cv::Point2f(280, 280) };

cv::Point2f src_points5[4] = { cv::Point2f(20, 20), cv::Point2f(320, 20), cv::Point2f(20, 320), cv::Point2f(320, 320) };
cv::Point2f dst_points5[4] = { cv::Point2f(0, 0), cv::Point2f(200, 0), cv::Point2f(0, 200), cv::Point2f(200, 200) };

// Get the perspective transformation matrix
std::array<cv::Mat, NUM_IMGS> perspective_matrices = { cv::getPerspectiveTransform(src_points1, dst_points1),
cv::getPerspectiveTransform(src_points2, dst_points2),
cv::getPerspectiveTransform(src_points3, dst_points3),
cv::getPerspectiveTransform(src_points4, dst_points4),
cv::getPerspectiveTransform(src_points5, dst_points5) };

// Preallocate the result images
std::array<cv::cuda::GpuMat, NUM_IMGS> d_resultscv{ cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3) };
std::array<cv::cuda::GpuMat, NUM_IMGS> d_resultscvGS{ cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3) };

// Apply the perspective transformation

for (int i = 0; i < NUM_IMGS; ++i) {
cv::cuda::warpPerspective(d_imgs[i], d_resultscv[i], perspective_matrices[i], img.size(), 1, 0, cv::Scalar(), stream);
}

const auto readIOp = cvGS::getReader<CV_8UC3>(d_img);
const auto warpFunc = cvGS::warp<fk::WarpType::Perspective, NUM_IMGS>(perspective_matrices, img.size());

auto fk_outputs = cvGS::gpuMat2RawPtr2D_arr<uchar3>(d_resultscvGS);
auto writeFunc = fk::PerThreadWrite<fk::ND::_2D, uchar3>::build(fk_outputs);
cvGS::executeOperations(stream, readIOp, warpFunc, fk::Cast<float3, uchar3>::build(), writeFunc);

stream.waitForCompletion();

// Download the result back to CPU
for (int i = 0; i < NUM_IMGS; ++i) {
cv::Mat resultcv(d_resultscv[i]);
cv::Mat resultcvGS(d_resultscvGS[i]);
const bool correct = compareAndCheck<CV_8UC3>(resultcv, resultcvGS);
std::cout << "Perspective transformation batch " << i << ": " << (correct ? "PASS" : "EXPECTED_FAIL") << std::endl;
}

return true;
}

bool testPerspectiveBatchNotAllNoInput() {
constexpr size_t NUM_IMGS2 = 10;

// Load the image
const cv::Mat img = cv::imread(getSourceDir() + "/images/NSightSystemsTimeline1.png");
if (img.empty()) {
std::cerr << "Error loading image" << std::endl;
return false;
}

cv::cuda::Stream stream;

// Upload the image to GPU
const cv::cuda::GpuMat d_img(img);
const std::array<cv::cuda::GpuMat, NUM_IMGS2> d_imgs = { d_img, d_img, d_img, d_img, d_img, d_img, d_img, d_img, d_img, d_img };

int usedPlanes = 3;

// Define the source and destination points for perspective transformation
cv::Point2f src_points1[4] = { cv::Point2f(56, 65), cv::Point2f(368, 52), cv::Point2f(28, 387), cv::Point2f(389, 390) };
cv::Point2f dst_points1[4] = { cv::Point2f(0, 0), cv::Point2f(300, 0), cv::Point2f(0, 300), cv::Point2f(300, 300) };

cv::Point2f src_points2[4] = { cv::Point2f(50, 50), cv::Point2f(400, 50), cv::Point2f(50, 400), cv::Point2f(400, 400) };
cv::Point2f dst_points2[4] = { cv::Point2f(0, 0), cv::Point2f(300, 0), cv::Point2f(0, 300), cv::Point2f(300, 300) };

cv::Point2f src_points3[4] = { cv::Point2f(30, 30), cv::Point2f(350, 30), cv::Point2f(30, 350), cv::Point2f(350, 350) };
cv::Point2f dst_points3[4] = { cv::Point2f(0, 0), cv::Point2f(250, 0), cv::Point2f(0, 250), cv::Point2f(250, 250) };

cv::Point2f src_points4[4] = { cv::Point2f(70, 70), cv::Point2f(370, 70), cv::Point2f(70, 370), cv::Point2f(370, 370) };
cv::Point2f dst_points4[4] = { cv::Point2f(0, 0), cv::Point2f(280, 0), cv::Point2f(0, 280), cv::Point2f(280, 280) };

cv::Point2f src_points5[4] = { cv::Point2f(20, 20), cv::Point2f(320, 20), cv::Point2f(20, 320), cv::Point2f(320, 320) };
cv::Point2f dst_points5[4] = { cv::Point2f(0, 0), cv::Point2f(200, 0), cv::Point2f(0, 200), cv::Point2f(200, 200) };

// Get the perspective transformation matrix
std::array<cv::Mat, NUM_IMGS2> perspective_matrices2{ cv::getPerspectiveTransform(src_points1, dst_points1),
cv::getPerspectiveTransform(src_points2, dst_points2),
cv::getPerspectiveTransform(src_points3, dst_points3),
{}, {}, {}, {}, {}, {}, {} };

// Preallocate the result images
std::array<cv::cuda::GpuMat, NUM_IMGS2> d_resultscv{ cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3),
cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3) };
std::array<cv::cuda::GpuMat, NUM_IMGS2> d_resultscvGS{ cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3),
cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3), cv::cuda::GpuMat(img.size(), CV_8UC3) };

// Apply the perspective transformation
for (int i = 0; i < usedPlanes; ++i) {
cv::cuda::warpPerspective(d_imgs[i], d_resultscv[i], perspective_matrices2[i], img.size(), 1, 0, cv::Scalar(), stream);
}

const auto readIOp = cvGS::getReader<CV_8UC3>(d_img);

const auto warpFunc = cvGS::warp<fk::WarpType::Perspective, CV_8UC3>(perspective_matrices2, img.size(), usedPlanes, cv::Scalar());

auto fk_outputs = cvGS::gpuMat2RawPtr2D_arr<uchar3>(d_resultscvGS);
auto writeFunc = fk::PerThreadWrite<fk::ND::_2D, uchar3>::build(fk_outputs);

cvGS::executeOperations(stream, readIOp, warpFunc, fk::Cast<float3, uchar3>::build(), writeFunc);

stream.waitForCompletion();

// Download the result back to CPU
for (int i = 0; i < NUM_IMGS2; ++i) {
cv::Mat resultcv(d_resultscv[i]);
cv::Mat resultcvGS(d_resultscvGS[i]);
const bool correct = compareAndCheck<CV_8UC3>(resultcv, resultcvGS);
std::cout << "Perspective transformation batch " << i << ": " << (correct ? "PASS" : "EXPECTED_FAIL") << std::endl;
}

return true;
}

int launch() {
const bool correctPerspective = testPerspective();
const bool correctAffine = testAffine();
const bool correctPerspectiveBatch = testPerspectiveBatch();
const bool correctPerspectiveBatchNotAll = testPerspectiveBatchNotAll();
const bool correctAll = correctPerspective && correctAffine && correctPerspectiveBatch && correctPerspectiveBatchNotAll;
const bool correctPerspectiveNoInput = testPerspectiveNoInput();
const bool correctAffineNoInput = testAffineNoInput();
const bool correctPerspectiveBatchNoInput = testPerspectiveBatchNoInput();
const bool correctPerspectiveBatchNotAllNoInput = testPerspectiveBatchNotAllNoInput();
const bool correctAll = correctPerspective && correctAffine && correctPerspectiveBatch && correctPerspectiveBatchNotAll
&& correctPerspectiveNoInput && correctAffineNoInput && correctPerspectiveBatchNoInput && correctPerspectiveBatchNotAllNoInput;

// warpPerspective is almost identical to OpenCV's implementation, but there are a few pixels of difference in
// the border. The reason is hard to find, since OpenCV is using NPP for the warping.
return correctAll ? 0 : -1;
Expand Down