mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 07:13:02 +04:00
Merge pull request #27581 from dkurt:d.kuryaev/dlpack
### Pull Request Readiness Checklist resolves #16295 ``` docker run --gpus 0 -v ~/opencv:/opencv -v ~/opencv_contrib:/opencv_contrib -it nvidia/cuda:12.8.1-cudnn-devel-ubuntu22.04 apt-get update && apt-get install -y cmake python3-dev python3-pip python3-venv && python3 -m venv .venv && source .venv/bin/activate && pip install -U pip && pip install -U numpy && pip install torch --index-url https://download.pytorch.org/whl/cu128 && cmake \ -DWITH_OPENCL=OFF \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_DOCS=OFF \ -DWITH_CUDA=ON \ -DOPENCV_DNN_CUDA=ON \ -DOPENCV_EXTRA_MODULES_PATH=/opencv_contrib/modules \ -DBUILD_LIST=ts,cudev,python3 \ -S /opencv -B /opencv_build && cmake --build /opencv_build -j16 export PYTHONPATH=/opencv_build/lib/python3/:$PYTHONPATH ``` 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
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
|
||||
#ifdef HAVE_OPENCV_CORE
|
||||
|
||||
#include "dlpack/dlpack.h"
|
||||
|
||||
static PyObject* pycvMakeType(PyObject* , PyObject* args, PyObject* kw) {
|
||||
const char *keywords[] = { "depth", "channels", NULL };
|
||||
|
||||
@@ -20,6 +22,201 @@ static PyObject* pycvMakeTypeCh(PyObject*, PyObject *value) {
|
||||
return PyInt_FromLong(CV_MAKETYPE(depth, channels));
|
||||
}
|
||||
|
||||
#define CV_DLPACK_CAPSULE_NAME "dltensor"
|
||||
#define CV_DLPACK_USED_CAPSULE_NAME "used_dltensor"
|
||||
|
||||
template<typename T>
|
||||
bool fillDLPackTensor(const T& src, DLManagedTensor* tensor, const DLDevice& device);
|
||||
|
||||
template<typename T>
|
||||
bool parseDLPackTensor(DLManagedTensor* tensor, T& obj, bool copy);
|
||||
|
||||
template<typename T>
|
||||
int GetNumDims(const T& src);
|
||||
|
||||
// source: https://github.com/dmlc/dlpack/blob/7f393bbb86a0ddd71fde3e700fc2affa5cdce72d/docs/source/python_spec.rst#L110
|
||||
static void dlpack_capsule_deleter(PyObject *self){
|
||||
if (PyCapsule_IsValid(self, CV_DLPACK_USED_CAPSULE_NAME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DLManagedTensor *managed = (DLManagedTensor *)PyCapsule_GetPointer(self, CV_DLPACK_CAPSULE_NAME);
|
||||
if (managed == NULL) {
|
||||
PyErr_WriteUnraisable(self);
|
||||
return;
|
||||
}
|
||||
|
||||
if (managed->deleter) {
|
||||
managed->deleter(managed);
|
||||
}
|
||||
}
|
||||
|
||||
static void array_dlpack_deleter(DLManagedTensor *self)
|
||||
{
|
||||
if (!Py_IsInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PyGILState_STATE state = PyGILState_Ensure();
|
||||
|
||||
PyObject *array = (PyObject *)self->manager_ctx;
|
||||
PyMem_Free(self);
|
||||
Py_XDECREF(array);
|
||||
|
||||
PyGILState_Release(state);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static PyObject* to_dlpack(const T& src, PyObject* self, PyObject* py_args, PyObject* kw)
|
||||
{
|
||||
int stream = 0;
|
||||
PyObject* maxVersion = nullptr;
|
||||
PyObject* dlDevice = nullptr;
|
||||
bool copy = false;
|
||||
const char* keywords[] = { "stream", "max_version", "dl_device", "copy", NULL };
|
||||
if (!PyArg_ParseTupleAndKeywords(py_args, kw, "|iOOp:__dlpack__", (char**)keywords, &stream, &maxVersion, &dlDevice, ©))
|
||||
return nullptr;
|
||||
|
||||
DLDevice device = {(DLDeviceType)-1, 0};
|
||||
if (dlDevice && dlDevice != Py_None && PyTuple_Check(dlDevice))
|
||||
{
|
||||
device.device_type = static_cast<DLDeviceType>(PyLong_AsLong(PyTuple_GetItem(dlDevice, 0)));
|
||||
device.device_id = PyLong_AsLong(PyTuple_GetItem(dlDevice, 1));
|
||||
}
|
||||
|
||||
int ndim = GetNumDims(src);
|
||||
void* ptr = PyMem_Malloc(sizeof(DLManagedTensor) + sizeof(int64_t) * ndim * 2);
|
||||
if (!ptr) {
|
||||
PyErr_NoMemory();
|
||||
return nullptr;
|
||||
}
|
||||
DLManagedTensor* tensor = reinterpret_cast<DLManagedTensor*>(ptr);
|
||||
tensor->manager_ctx = self;
|
||||
tensor->deleter = array_dlpack_deleter;
|
||||
tensor->dl_tensor.ndim = ndim;
|
||||
tensor->dl_tensor.shape = reinterpret_cast<int64_t*>(reinterpret_cast<char*>(ptr) + sizeof(DLManagedTensor));
|
||||
tensor->dl_tensor.strides = tensor->dl_tensor.shape + ndim;
|
||||
fillDLPackTensor(src, tensor, device);
|
||||
|
||||
PyObject* capsule = PyCapsule_New(ptr, CV_DLPACK_CAPSULE_NAME, dlpack_capsule_deleter);
|
||||
if (!capsule) {
|
||||
PyMem_Free(ptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// the capsule holds a reference
|
||||
Py_INCREF(self);
|
||||
|
||||
return capsule;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static PyObject* from_dlpack(PyObject* py_args, PyObject* kw)
|
||||
{
|
||||
PyObject* arr = nullptr;
|
||||
PyObject* device = nullptr;
|
||||
bool copy = false;
|
||||
const char* keywords[] = { "device", "copy", NULL };
|
||||
if (!PyArg_ParseTupleAndKeywords(py_args, kw, "O|Op:from_dlpack", (char**)keywords, &arr, &device, ©))
|
||||
return nullptr;
|
||||
|
||||
PyObject* capsule = nullptr;
|
||||
if (PyCapsule_CheckExact(arr))
|
||||
{
|
||||
capsule = arr;
|
||||
}
|
||||
else
|
||||
{
|
||||
PyGILState_STATE gstate;
|
||||
gstate = PyGILState_Ensure();
|
||||
capsule = PyObject_CallMethodObjArgs(arr, PyString_FromString("__dlpack__"), NULL);
|
||||
PyGILState_Release(gstate);
|
||||
}
|
||||
|
||||
DLManagedTensor* tensor = reinterpret_cast<DLManagedTensor*>(PyCapsule_GetPointer(capsule, CV_DLPACK_CAPSULE_NAME));
|
||||
if (tensor == nullptr)
|
||||
{
|
||||
if (capsule != arr)
|
||||
Py_DECREF(capsule);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
T retval;
|
||||
bool success = parseDLPackTensor(tensor, retval, copy);
|
||||
if (success)
|
||||
{
|
||||
PyCapsule_SetName(capsule, CV_DLPACK_USED_CAPSULE_NAME);
|
||||
}
|
||||
if (capsule != arr)
|
||||
Py_DECREF(capsule);
|
||||
|
||||
return success ? pyopencv_from(retval) : nullptr;
|
||||
}
|
||||
|
||||
static DLDataType GetDLPackType(size_t elemSize1, int depth) {
|
||||
DLDataType dtype;
|
||||
dtype.bits = static_cast<uint8_t>(8 * elemSize1);
|
||||
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_16F: case CV_32F: case CV_64F: dtype.code = kDLFloat; break;
|
||||
default:
|
||||
CV_Error(Error::StsNotImplemented, "__dlpack__ data type");
|
||||
}
|
||||
return dtype;
|
||||
}
|
||||
|
||||
static int DLPackTypeToCVType(const DLDataType& dtype, int channels) {
|
||||
if (dtype.code == kDLInt)
|
||||
{
|
||||
switch (dtype.bits)
|
||||
{
|
||||
case 8: return CV_8SC(channels);
|
||||
case 16: return CV_16SC(channels);
|
||||
case 32: return CV_32SC(channels);
|
||||
default:
|
||||
{
|
||||
PyErr_SetString(PyExc_BufferError,
|
||||
format("Unsupported int dlpack depth: %d", dtype.bits).c_str());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dtype.code == kDLUInt)
|
||||
{
|
||||
switch (dtype.bits)
|
||||
{
|
||||
case 8: return CV_8UC(channels);
|
||||
case 16: return CV_16UC(channels);
|
||||
default:
|
||||
{
|
||||
PyErr_SetString(PyExc_BufferError,
|
||||
format("Unsupported uint dlpack depth: %d", dtype.bits).c_str());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dtype.code == kDLFloat)
|
||||
{
|
||||
switch (dtype.bits)
|
||||
{
|
||||
case 16: return CV_16FC(channels);
|
||||
case 32: return CV_32FC(channels);
|
||||
case 64: return CV_64FC(channels);
|
||||
default:
|
||||
{
|
||||
PyErr_SetString(PyExc_BufferError,
|
||||
format("Unsupported float dlpack depth: %d", dtype.bits).c_str());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
PyErr_SetString(PyExc_BufferError, format("Unsupported dlpack data type: %d", dtype.code).c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define PYOPENCV_EXTRA_METHODS_CV \
|
||||
{"CV_MAKETYPE", CV_PY_FN_WITH_KW(pycvMakeType), "CV_MAKETYPE(depth, channels) -> retval"}, \
|
||||
{"CV_8UC", (PyCFunction)(pycvMakeTypeCh<CV_8U>), METH_O, "CV_8UC(channels) -> retval"}, \
|
||||
|
||||
@@ -21,17 +21,175 @@ template<> struct pyopencvVecConverter<cuda::GpuMat>
|
||||
};
|
||||
|
||||
CV_PY_TO_CLASS(cuda::GpuMat)
|
||||
CV_PY_TO_CLASS(cuda::GpuMatND)
|
||||
CV_PY_TO_CLASS(cuda::Stream)
|
||||
CV_PY_TO_CLASS(cuda::Event)
|
||||
CV_PY_TO_CLASS(cuda::HostMem)
|
||||
|
||||
CV_PY_TO_CLASS_PTR(cuda::GpuMat)
|
||||
CV_PY_TO_CLASS_PTR(cuda::GpuMatND)
|
||||
CV_PY_TO_CLASS_PTR(cuda::GpuMat::Allocator)
|
||||
|
||||
CV_PY_FROM_CLASS(cuda::GpuMat)
|
||||
CV_PY_FROM_CLASS(cuda::GpuMatND)
|
||||
CV_PY_FROM_CLASS(cuda::Stream)
|
||||
CV_PY_FROM_CLASS(cuda::HostMem)
|
||||
|
||||
CV_PY_FROM_CLASS_PTR(cuda::GpuMat::Allocator)
|
||||
|
||||
template<>
|
||||
bool fillDLPackTensor(const Ptr<cv::cuda::GpuMat>& src, DLManagedTensor* tensor, const DLDevice& device)
|
||||
{
|
||||
if ((device.device_type != -1 && device.device_type != kDLCUDA) || device.device_id != 0)
|
||||
{
|
||||
PyErr_SetString(PyExc_BufferError, "GpuMat can be exported only on GPU:0");
|
||||
return false;
|
||||
}
|
||||
tensor->dl_tensor.data = src->cudaPtr();
|
||||
tensor->dl_tensor.device.device_type = kDLCUDA;
|
||||
tensor->dl_tensor.device.device_id = 0;
|
||||
tensor->dl_tensor.dtype = GetDLPackType(src->elemSize1(), src->depth());
|
||||
tensor->dl_tensor.shape[0] = src->rows;
|
||||
tensor->dl_tensor.shape[1] = src->cols;
|
||||
tensor->dl_tensor.shape[2] = src->channels();
|
||||
tensor->dl_tensor.strides[0] = src->step1();
|
||||
tensor->dl_tensor.strides[1] = src->channels();
|
||||
tensor->dl_tensor.strides[2] = 1;
|
||||
tensor->dl_tensor.byte_offset = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool fillDLPackTensor(const Ptr<cv::cuda::GpuMatND>& src, DLManagedTensor* tensor, const DLDevice& device)
|
||||
{
|
||||
if ((device.device_type != -1 && device.device_type != kDLCUDA) || device.device_id != 0)
|
||||
{
|
||||
PyErr_SetString(PyExc_BufferError, "GpuMatND can be exported only on GPU:0");
|
||||
return false;
|
||||
}
|
||||
tensor->dl_tensor.data = src->getDevicePtr();
|
||||
tensor->dl_tensor.device.device_type = kDLCUDA;
|
||||
tensor->dl_tensor.device.device_id = 0;
|
||||
tensor->dl_tensor.dtype = GetDLPackType(src->elemSize1(), CV_MAT_DEPTH(src->flags));
|
||||
for (int i = 0; i < src->dims; ++i)
|
||||
tensor->dl_tensor.shape[i] = src->size[i];
|
||||
for (int i = 0; i < src->dims; ++i)
|
||||
tensor->dl_tensor.strides[i] = src->step[i];
|
||||
tensor->dl_tensor.byte_offset = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool parseDLPackTensor(DLManagedTensor* tensor, cv::cuda::GpuMat& obj, bool copy)
|
||||
{
|
||||
if (tensor->dl_tensor.byte_offset != 0)
|
||||
{
|
||||
PyErr_SetString(PyExc_BufferError, "Unimplemented from_dlpack for GpuMat with memory offset");
|
||||
return false;
|
||||
}
|
||||
if (tensor->dl_tensor.ndim != 3)
|
||||
{
|
||||
PyErr_SetString(PyExc_BufferError, "cuda_GpuMat.from_dlpack expects a 3D tensor. Use cuda_GpuMatND.from_dlpack instead");
|
||||
return false;
|
||||
}
|
||||
if (tensor->dl_tensor.device.device_type != kDLCUDA)
|
||||
{
|
||||
PyErr_SetString(PyExc_BufferError, "cuda_GpuMat.from_dlpack expects a tensor on CUDA device");
|
||||
return false;
|
||||
}
|
||||
if (tensor->dl_tensor.strides[1] != tensor->dl_tensor.shape[2] ||
|
||||
tensor->dl_tensor.strides[2] != 1)
|
||||
{
|
||||
PyErr_SetString(PyExc_BufferError, "Unexpected strides for image. Try use GpuMatND");
|
||||
return false;
|
||||
}
|
||||
int type = DLPackTypeToCVType(tensor->dl_tensor.dtype, (int)tensor->dl_tensor.shape[2]);
|
||||
if (type == -1)
|
||||
return false;
|
||||
|
||||
obj = cv::cuda::GpuMat(
|
||||
static_cast<int>(tensor->dl_tensor.shape[0]),
|
||||
static_cast<int>(tensor->dl_tensor.shape[1]),
|
||||
type,
|
||||
tensor->dl_tensor.data,
|
||||
tensor->dl_tensor.strides[0] * tensor->dl_tensor.dtype.bits / 8
|
||||
);
|
||||
if (copy)
|
||||
obj = obj.clone();
|
||||
return true;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool parseDLPackTensor(DLManagedTensor* tensor, cv::cuda::GpuMatND& obj, bool copy)
|
||||
{
|
||||
if (tensor->dl_tensor.byte_offset != 0)
|
||||
{
|
||||
PyErr_SetString(PyExc_BufferError, "Unimplemented from_dlpack for GpuMat with memory offset");
|
||||
return false;
|
||||
}
|
||||
if (tensor->dl_tensor.device.device_type != kDLCUDA)
|
||||
{
|
||||
PyErr_SetString(PyExc_BufferError, "cuda_GpuMat.from_dlpack expects a tensor on CUDA device");
|
||||
return false;
|
||||
}
|
||||
int type = DLPackTypeToCVType(tensor->dl_tensor.dtype, (int)tensor->dl_tensor.shape[2]);
|
||||
if (type == -1)
|
||||
return false;
|
||||
|
||||
std::vector<size_t> steps(tensor->dl_tensor.ndim - 1);
|
||||
std::vector<int> sizes(tensor->dl_tensor.ndim);
|
||||
for (int i = 0; i < tensor->dl_tensor.ndim - 1; ++i)
|
||||
{
|
||||
steps[i] = tensor->dl_tensor.strides[i] * tensor->dl_tensor.dtype.bits / 8;
|
||||
sizes[i] = static_cast<int>(tensor->dl_tensor.shape[i]);
|
||||
}
|
||||
sizes.back() = static_cast<int>(tensor->dl_tensor.shape[tensor->dl_tensor.ndim - 1]);
|
||||
obj = cv::cuda::GpuMatND(sizes, type, tensor->dl_tensor.data, steps);
|
||||
if (copy)
|
||||
obj = obj.clone();
|
||||
return true;
|
||||
}
|
||||
|
||||
template<>
|
||||
int GetNumDims(const Ptr<cv::cuda::GpuMat>& src) { return 3; }
|
||||
|
||||
template<>
|
||||
int GetNumDims(const Ptr<cv::cuda::GpuMatND>& src) { return src->dims; }
|
||||
|
||||
static PyObject* pyDLPackGpuMat(PyObject* self, PyObject* py_args, PyObject* kw) {
|
||||
Ptr<cv::cuda::GpuMat> * self1 = 0;
|
||||
if (!pyopencv_cuda_GpuMat_getp(self, self1))
|
||||
return failmsgp("Incorrect type of self (must be 'cuda_GpuMat' or its derivative)");
|
||||
return to_dlpack(*(self1), self, py_args, kw);
|
||||
}
|
||||
|
||||
static PyObject* pyDLPackGpuMatND(PyObject* self, PyObject* py_args, PyObject* kw) {
|
||||
Ptr<cv::cuda::GpuMatND> * self1 = 0;
|
||||
if (!pyopencv_cuda_GpuMatND_getp(self, self1))
|
||||
return failmsgp("Incorrect type of self (must be 'cuda_GpuMatND' or its derivative)");
|
||||
return to_dlpack(*(self1), self, py_args, kw);
|
||||
}
|
||||
|
||||
static PyObject* pyDLPackDeviceCUDA(PyObject*, PyObject*, PyObject*) {
|
||||
return pyopencv_from(std::tuple<int, int>(kDLCUDA, 0));
|
||||
}
|
||||
|
||||
static PyObject* pyGpuMatFromDLPack(PyObject*, PyObject* py_args, PyObject* kw) {
|
||||
return from_dlpack<cv::cuda::GpuMat>(py_args, kw);
|
||||
}
|
||||
|
||||
static PyObject* pyGpuMatNDFromDLPack(PyObject*, PyObject* py_args, PyObject* kw) {
|
||||
return from_dlpack<cv::cuda::GpuMatND>(py_args, kw);
|
||||
}
|
||||
|
||||
#define PYOPENCV_EXTRA_METHODS_cuda_GpuMat \
|
||||
{"__dlpack__", CV_PY_FN_WITH_KW(pyDLPackGpuMat), ""}, \
|
||||
{"__dlpack_device__", CV_PY_FN_WITH_KW(pyDLPackDeviceCUDA), ""}, \
|
||||
{"from_dlpack", CV_PY_FN_WITH_KW_(pyGpuMatFromDLPack, METH_STATIC), ""}, \
|
||||
|
||||
#define PYOPENCV_EXTRA_METHODS_cuda_GpuMatND \
|
||||
{"__dlpack__", CV_PY_FN_WITH_KW(pyDLPackGpuMatND), ""}, \
|
||||
{"__dlpack_device__", CV_PY_FN_WITH_KW(pyDLPackDeviceCUDA), ""}, \
|
||||
{"from_dlpack", CV_PY_FN_WITH_KW_(pyGpuMatNDFromDLPack, METH_STATIC), ""}, \
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user