From 4159c0ad98a9b4a8e0b71eabe1d48ab8cdd4bf35 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Thu, 25 Sep 2025 10:14:46 +0300 Subject: [PATCH] Merge pull request #27779 from dkurt:dlpack_5.x_types Add 5.x types for DLPack. Keep uint32/int64/uint64 data type for conversion to Numpy. More types support for GpuMat::convertTo #27779 ### Pull Request Readiness Checklist **Merge with contrib**: https://github.com/opencv/opencv_contrib/pull/4000 related: https://github.com/opencv/opencv/pull/27581 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/core/CMakeLists.txt | 5 ++ modules/core/misc/python/pyopencv_core.hpp | 23 +++++++-- modules/core/src/cuda/gpu_mat.cu | 48 +++++++++++------- modules/core/test/test_cuda.cpp | 58 ++++++++++++++++++++++ modules/python/src2/cv2.cpp | 15 ++++++ modules/python/src2/cv2_numpy.cpp | 5 +- modules/python/test/test_cuda.py | 14 +++++- 7 files changed, 144 insertions(+), 24 deletions(-) diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index 990bb6a0e9..fcc79a9882 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -210,6 +210,11 @@ endif() ocv_add_accuracy_tests() ocv_add_perf_tests() +if (HAVE_CUDA) + ocv_target_compile_definitions(opencv_test_core PRIVATE "HAVE_CUDA=1") + ocv_target_compile_definitions(opencv_perf_core PRIVATE "HAVE_CUDA=1") +endif() + ocv_install_3rdparty_licenses(SoftFloat "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/SoftFloat/COPYING.txt") diff --git a/modules/core/misc/python/pyopencv_core.hpp b/modules/core/misc/python/pyopencv_core.hpp index ef24dac8ac..836075ed83 100644 --- a/modules/core/misc/python/pyopencv_core.hpp +++ b/modules/core/misc/python/pyopencv_core.hpp @@ -159,8 +159,10 @@ static DLDataType GetDLPackType(size_t elemSize1, int depth) { dtype.lanes = 1; switch (depth) { - case CV_8S: case CV_16S: case CV_32S: dtype.code = kDLInt; break; - case CV_8U: case CV_16U: dtype.code = kDLUInt; break; + case CV_Bool: dtype.code = kDLBool; break; + case CV_16BF: dtype.code = kDLBfloat; break; + case CV_8S: case CV_16S: case CV_32S: case CV_64S: dtype.code = kDLInt; break; + case CV_8U: case CV_16U: case CV_32U: case CV_64U: dtype.code = kDLUInt; break; case CV_16F: case CV_32F: case CV_64F: dtype.code = kDLFloat; break; default: CV_Error(Error::StsNotImplemented, "__dlpack__ data type"); @@ -176,6 +178,7 @@ static int DLPackTypeToCVType(const DLDataType& dtype, int channels) { case 8: return CV_8SC(channels); case 16: return CV_16SC(channels); case 32: return CV_32SC(channels); + case 64: return CV_64SC(channels); default: { PyErr_SetString(PyExc_BufferError, @@ -190,6 +193,8 @@ static int DLPackTypeToCVType(const DLDataType& dtype, int channels) { { case 8: return CV_8UC(channels); case 16: return CV_16UC(channels); + case 32: return CV_32UC(channels); + case 64: return CV_64UC(channels); default: { PyErr_SetString(PyExc_BufferError, @@ -213,6 +218,14 @@ static int DLPackTypeToCVType(const DLDataType& dtype, int channels) { } } } + if (dtype.code == kDLBool) + { + return CV_BoolC(channels); + } + if (dtype.code == kDLBfloat) + { + return CV_16BFC(channels); + } PyErr_SetString(PyExc_BufferError, format("Unsupported dlpack data type: %d", dtype.code).c_str()); return -1; } @@ -227,7 +240,11 @@ static int DLPackTypeToCVType(const DLDataType& dtype, int channels) { {"CV_32FC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_32FC(channels) -> retval"}, \ {"CV_64FC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_64FC(channels) -> retval"}, \ {"CV_16FC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_16FC(channels) -> retval"}, \ - {"CV_BoolC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_BoolC(channels) -> retval"}, + {"CV_BoolC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_BoolC(channels) -> retval"}, \ + {"CV_32UC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_32UC(channels) -> retval"}, \ + {"CV_64UC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_64UC(channels) -> retval"}, \ + {"CV_64SC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_64SC(channels) -> retval"}, \ + {"CV_16BFC", (PyCFunction)(pycvMakeTypeCh), METH_O, "CV_16BFC(channels) -> retval"}, #endif // HAVE_OPENCV_CORE #endif // OPENCV_CORE_PYOPENCV_CORE_HPP diff --git a/modules/core/src/cuda/gpu_mat.cu b/modules/core/src/cuda/gpu_mat.cu index 7eccc6cdfe..b212138d63 100644 --- a/modules/core/src/cuda/gpu_mat.cu +++ b/modules/core/src/cuda/gpu_mat.cu @@ -550,7 +550,7 @@ void cv::cuda::GpuMat::convertTo(OutputArray _dst, int rtype, Stream& stream) co return; } - CV_Assert( sdepth <= CV_64F && ddepth <= CV_64F ); + CV_Assert(sdepth < CV_DEPTH_CURR_MAX && ddepth < CV_DEPTH_CURR_MAX); GpuMat src = *this; @@ -558,15 +558,21 @@ void cv::cuda::GpuMat::convertTo(OutputArray _dst, int rtype, Stream& stream) co GpuMat dst = _dst.getGpuMat(); typedef void (*func_t)(const GpuMat& src, const GpuMat& dst, Stream& stream); - static const func_t funcs[7][7] = + static const func_t funcs[CV_DEPTH_CURR_MAX][CV_DEPTH_CURR_MAX] = { - {0, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale}, - {convertToNoScale, 0, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale}, - {convertToNoScale, convertToNoScale, 0, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale}, - {convertToNoScale, convertToNoScale, convertToNoScale, 0, convertToNoScale, convertToNoScale, convertToNoScale}, - {convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, convertToNoScale, convertToNoScale}, - {convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, convertToNoScale}, - {convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0} + {0, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, 0, 0, convertToNoScale, convertToNoScale, convertToNoScale}, + {convertToNoScale, 0, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, 0, 0, convertToNoScale, convertToNoScale, convertToNoScale}, + {convertToNoScale, convertToNoScale, 0, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, 0, 0, convertToNoScale, convertToNoScale, convertToNoScale}, + {convertToNoScale, convertToNoScale, convertToNoScale, 0, convertToNoScale, convertToNoScale, convertToNoScale, 0, 0, 0, convertToNoScale, convertToNoScale, convertToNoScale}, + {convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, convertToNoScale, convertToNoScale, 0, 0, 0, convertToNoScale, convertToNoScale, convertToNoScale}, + {convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, convertToNoScale, 0, 0, 0, convertToNoScale, convertToNoScale, convertToNoScale}, + {convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, 0, 0, 0, convertToNoScale, convertToNoScale, convertToNoScale}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, 0, 0, 0, convertToNoScale, convertToNoScale}, + {convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, 0, 0, convertToNoScale, 0, convertToNoScale}, + {convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, 0, 0, convertToNoScale, convertToNoScale, 0}, }; funcs[sdepth][ddepth](src.reshape(1), dst.reshape(1), stream); @@ -582,7 +588,7 @@ void cv::cuda::GpuMat::convertTo(OutputArray _dst, int rtype, double alpha, doub const int sdepth = depth(); const int ddepth = CV_MAT_DEPTH(rtype); - CV_Assert(sdepth <= CV_64F && ddepth <= CV_64F); + CV_Assert(sdepth < CV_DEPTH_CURR_MAX && ddepth < CV_DEPTH_CURR_MAX); GpuMat src = *this; @@ -590,15 +596,21 @@ void cv::cuda::GpuMat::convertTo(OutputArray _dst, int rtype, double alpha, doub GpuMat dst = _dst.getGpuMat(); typedef void (*func_t)(const GpuMat& src, const GpuMat& dst, double alpha, double beta, Stream& stream); - static const func_t funcs[7][7] = + static const func_t funcs[CV_DEPTH_CURR_MAX][CV_DEPTH_CURR_MAX] = { - {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale}, - {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale}, - {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale}, - {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale}, - {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale}, - {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale}, - {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale} + {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, 0, 0, 0, convertToScale, convertToScale, convertToScale}, + {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, 0, 0, 0, convertToScale, convertToScale, convertToScale}, + {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, 0, 0, 0, convertToScale, convertToScale, convertToScale}, + {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, 0, 0, 0, convertToScale, convertToScale, convertToScale}, + {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, 0, 0, 0, convertToScale, convertToScale, convertToScale}, + {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, 0, 0, 0, convertToScale, convertToScale, convertToScale}, + {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, 0, 0, 0, convertToScale, convertToScale, convertToScale}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, 0, 0, 0, convertToScale, convertToScale, convertToScale}, + {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, 0, 0, 0, convertToScale, convertToScale, convertToScale}, + {convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, convertToScale, 0, 0, 0, convertToScale, convertToScale, convertToScale}, }; func_t func = funcs[sdepth][ddepth]; diff --git a/modules/core/test/test_cuda.cpp b/modules/core/test/test_cuda.cpp index a3e0a9034b..8d0f140526 100755 --- a/modules/core/test/test_cuda.cpp +++ b/modules/core/test/test_cuda.cpp @@ -16,6 +16,64 @@ TEST(CUDA_Stream, construct_cudaFlags) EXPECT_NE(stream.cudaPtr(), nullptr); } +typedef testing::TestWithParam< tuple > GpuMat; +TEST_P(GpuMat, convertTo) +{ + int sdepth = get<0>(GetParam()); + int ddepth = get<1>(GetParam()); + if (sdepth == CV_16F || sdepth == CV_Bool || sdepth == CV_16BF) + throw SkipTestException("Unsupported src type"); + if (ddepth == CV_16F || ddepth == CV_Bool || ddepth == CV_16BF) + throw SkipTestException("Unsupported dst type"); + + Mat ref(16, 20, CV_8U), testMat; + randu(ref, 0, 128); + ref.convertTo(ref, sdepth); + + cv::cuda::GpuMat src, dst; + src.upload(ref); + EXPECT_EQ(sdepth, src.depth()); + src.convertTo(dst, ddepth); + EXPECT_EQ(ddepth, dst.depth()); + dst.download(testMat); + + Mat expected; + ref.convertTo(expected, ddepth); + EXPECT_EQ(ddepth, testMat.depth()); + ASSERT_EQ(0, cvtest::norm(expected, testMat, NORM_INF)); +} + +TEST_P(GpuMat, convertToScale) +{ + int sdepth = get<0>(GetParam()); + int ddepth = get<1>(GetParam()); + if (sdepth == CV_16F || sdepth == CV_Bool || sdepth == CV_16BF) + throw SkipTestException("Unsupported src type"); + if (ddepth == CV_16F || ddepth == CV_Bool || ddepth == CV_16BF) + throw SkipTestException("Unsupported dst type"); + + Mat ref(16, 20, CV_8U), testMat; + randu(ref, 10, 50); + ref.convertTo(ref, sdepth); + + cv::cuda::GpuMat src, dst; + src.upload(ref); + EXPECT_EQ(sdepth, src.depth()); + src.convertTo(dst, ddepth, 2, -1); + EXPECT_EQ(ddepth, dst.depth()); + dst.download(testMat); + + Mat expected; + ref.convertTo(expected, ddepth, 2, -1); + EXPECT_EQ(ddepth, testMat.depth()); + ASSERT_EQ(0, cvtest::norm(expected, testMat, NORM_INF)); +} + +INSTANTIATE_TEST_CASE_P(/*nothing*/, GpuMat, testing::Combine( + testing::Range(perf::MatType(CV_8U), perf::MatType(CV_DEPTH_CURR_MAX)), + testing::Range(perf::MatType(CV_8U), perf::MatType(CV_DEPTH_CURR_MAX)) +)); + }} // namespace #endif diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index cdbc6b51b3..443a582d9a 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -546,11 +546,26 @@ static bool init_body(PyObject * m) PUBLISH(CV_16SC2); PUBLISH(CV_16SC3); PUBLISH(CV_16SC4); + PUBLISH(CV_32U); + PUBLISH(CV_32UC1); + PUBLISH(CV_32UC2); + PUBLISH(CV_32UC3); + PUBLISH(CV_32UC4); PUBLISH(CV_32S); PUBLISH(CV_32SC1); PUBLISH(CV_32SC2); PUBLISH(CV_32SC3); PUBLISH(CV_32SC4); + PUBLISH(CV_64U); + PUBLISH(CV_64UC1); + PUBLISH(CV_64UC2); + PUBLISH(CV_64UC3); + PUBLISH(CV_64UC4); + PUBLISH(CV_64S); + PUBLISH(CV_64SC1); + PUBLISH(CV_64SC2); + PUBLISH(CV_64SC3); + PUBLISH(CV_64SC4); PUBLISH(CV_32F); PUBLISH(CV_32FC1); PUBLISH(CV_32FC2); diff --git a/modules/python/src2/cv2_numpy.cpp b/modules/python/src2/cv2_numpy.cpp index 0b6ca60121..3c211830de 100644 --- a/modules/python/src2/cv2_numpy.cpp +++ b/modules/python/src2/cv2_numpy.cpp @@ -36,8 +36,9 @@ UMatData* NumpyAllocator::allocate(int dims0, const int* sizes, int type, void* const int f = (int)(sizeof(size_t)/8); int typenum = depth == CV_8U ? NPY_UBYTE : depth == CV_8S ? NPY_BYTE : depth == CV_16U ? NPY_USHORT : depth == CV_16S ? NPY_SHORT : - depth == CV_32S ? NPY_INT : depth == CV_32F ? NPY_FLOAT : - depth == CV_64F ? NPY_DOUBLE : depth == CV_16F ? NPY_HALF : depth == CV_Bool ? NPY_BOOL : f*NPY_ULONGLONG + (f^1)*NPY_UINT; + depth == CV_32U ? NPY_UINT32 : depth == CV_32S ? NPY_INT32 : depth == CV_64S ? NPY_INT64 : + depth == CV_32F ? NPY_FLOAT : depth == CV_64F ? NPY_DOUBLE : depth == CV_16F ? NPY_HALF : + depth == CV_Bool ? NPY_BOOL : f*NPY_ULONGLONG + (f^1)*NPY_UINT; int i, dims = dims0; cv::AutoBuffer _sizes(dims + 1); for( i = 0; i < dims; i++ ) diff --git a/modules/python/test/test_cuda.py b/modules/python/test/test_cuda.py index c41bfc957d..4362cca044 100644 --- a/modules/python/test/test_cuda.py +++ b/modules/python/test/test_cuda.py @@ -146,13 +146,25 @@ class cuda_test(NewOpenCVTests): self.assertEqual(True, hasattr(cv.cuda, 'nonLocalMeans')) def test_dlpack_GpuMat(self): - for dtype in [np.int8, np.uint8, np.int16, np.uint16, np.float16, np.int32, np.float32, np.float64]: + for dtype in [np.int8, np.uint8, np.int16, np.uint16, np.float16, np.int32, np.float32, np.float64, np.int64, np.uint32, np.uint64, np.bool_]: for channels in [2, 3, 5]: ref = (np.random.random((64, 128, channels)) * 255).astype(dtype) src = cv.cuda_GpuMat() src.upload(ref) + + # workaround int64/uint64 conversion to int32/uint32 + if dtype == np.int64: + print("skip because of https://github.com/opencv/opencv/issues/27671") + continue + src = src.convertTo(cv.CV_64S) + elif dtype == np.uint64: + print("skip because of https://github.com/opencv/opencv/issues/27671") + continue + src = src.convertTo(cv.CV_64U) + dst = cv.cuda_GpuMat.from_dlpack(src) test = dst.download() + self.assertEqual(ref.dtype, test.dtype) equal = np.array_equal(ref, test) if not equal: print(f"Failed test with dtype {dtype} and {channels} channels")