如何通过共享内存发送 cv::Mat 到 python?
How to send a cv::Mat to python over shared memory?
我有一个 C++ 应用程序,它通过共享内存将数据发送到 python 函数。
这在 Python 中使用 ctypes
效果很好,例如双打和浮点数。现在,我需要在函数中添加一个 cv::Mat
。
我目前的代码是:
//h
#include <iostream>
#include <opencv2\core.hpp>
#include <opencv2\highgui.hpp>
struct TransferData
{
double score;
float other;
int num;
int w;
int h;
int channels;
uchar* data;
};
#define C_OFF 1000
void fill(TransferData* data, int run, uchar* frame, int w, int h, int channels)
{
data->score = C_OFF + 1.0;
data->other = C_OFF + 2.0;
data->num = C_OFF + 3;
data->w = w;
data->h = h;
data->channels = channels;
data->data = frame;
}
//.cpp
namespace py = pybind11;
using namespace boost::interprocess;
void main()
{
//python setup
Py_SetProgramName(L"PYTHON");
py::scoped_interpreter guard{};
py::module py_test = py::module::import("Transfer_py");
// Create Data
windows_shared_memory shmem(create_only, "TransferDataSHMEM",
read_write, sizeof(TransferData));
mapped_region region(shmem, read_write);
std::memset(region.get_address(), 0, sizeof(TransferData));
TransferData* data = reinterpret_cast<TransferData*>(region.get_address());
//loop
for (int i = 0; i < 10; i++)
{
int64 t0 = cv::getTickCount();
std::cout << "C++ Program - Filling Data" << std::endl;
cv::Mat frame = cv::imread("input.jpg");
fill(data, i, frame.data, frame.cols, frame.rows, frame.channels());
//run the python function
//process
py::object result = py_test.attr("datathrough")();
int64 t1 = cv::getTickCount();
double secs = (t1 - t0) / cv::getTickFrequency();
std::cout << "took " << secs * 1000 << " ms" << std::endl;
}
std::cin.get();
}
//Python
//传输数据class
import ctypes
class TransferData(ctypes.Structure):
_fields_ = [
('score', ctypes.c_double),
('other', ctypes.c_float),
('num', ctypes.c_int),
('w', ctypes.c_int),
('h', ctypes.c_int),
('frame', ctypes.c_void_p),
('channels', ctypes.c_int)
]
PY_OFF = 2000
def fill(data):
data.score = PY_OFF + 1.0
data.other = PY_OFF + 2.0
data.num = PY_OFF + 3
//主要Python函数
import TransferData
import sys
import mmap
import ctypes
def datathrough():
shmem = mmap.mmap(-1, ctypes.sizeof(TransferData.TransferData), "TransferDataSHMEM")
data = TransferData.TransferData.from_buffer(shmem)
print('Python Program - Getting Data')
print('Python Program - Filling Data')
TransferData.fill(data)
如何将 cv::Mat
帧数据添加到 Python 端?我从 C++ 中将它作为 uchar*
发送,据我所知,我需要它是一个 numpy
数组才能在 Python 中获得 cv2.Mat
。从 'width, height, channels, frameData' 到 opencv python cv2.Mat
的正确方法是什么?
我正在使用共享内存,因为速度是一个因素,我已经使用 Python API 方法进行了测试,但它对我的需求来说太慢了。
总体思路(在 OpenCV Python 绑定中使用)是创建一个与 Mat
对象共享其数据缓冲区的 numpy ndarray
,并将其传递给Python 函数。
注意:此时,我将示例仅限于连续矩阵。
我们可以利用 pybind11::array
class.
我们需要为要使用的 numpy 数组确定合适的 dtype
。这是一个简单的一对一映射,我们可以使用 switch
:
py::dtype determine_np_dtype(int depth)
{
switch (depth) {
case CV_8U: return py::dtype::of<uint8_t>();
case CV_8S: return py::dtype::of<int8_t>();
case CV_16U: return py::dtype::of<uint16_t>();
case CV_16S: return py::dtype::of<int16_t>();
case CV_32S: return py::dtype::of<int32_t>();
case CV_32F: return py::dtype::of<float>();
case CV_64F: return py::dtype::of<double>();
default:
throw std::invalid_argument("Unsupported data type.");
}
}
确定 numpy 数组的形状。为了使它的行为与 OpenCV 相似,我们让它将 1 通道 Mat
s 映射到 2D numpy 数组,并将多通道 Mat
s 映射到 3D numpy 数组。
std::vector<std::size_t> determine_shape(cv::Mat& m)
{
if (m.channels() == 1) {
return {
static_cast<size_t>(m.rows)
, static_cast<size_t>(m.cols)
};
}
return {
static_cast<size_t>(m.rows)
, static_cast<size_t>(m.cols)
, static_cast<size_t>(m.channels())
};
}
提供将共享缓冲区的生命周期延长到 numpy 数组生命周期的方法。我们可以围绕源 Mat
的浅表副本创建一个 pybind11::capsule
—— 由于对象的实现方式,这有效地增加了所需时间的引用计数。
py::capsule make_capsule(cv::Mat& m)
{
return py::capsule(new cv::Mat(m)
, [](void *v) { delete reinterpret_cast<cv::Mat*>(v); }
);
}
现在,我们可以进行转换了。
py::array mat_to_nparray(cv::Mat& m)
{
if (!m.isContinuous()) {
throw std::invalid_argument("Only continuous Mats supported.");
}
return py::array(determine_np_dtype(m.depth())
, determine_shape(m)
, m.data
, make_capsule(m));
}
假设,我们有一个 Python 函数,例如
def foo(arr):
print(arr.shape)
在 pybind 对象中捕获 fun
。然后使用 Mat
作为源从 C++ 调用这个函数,我们会做这样的事情:
cv::Mat img; // Initialize this somehow
auto result = fun(mat_to_nparray(img));
示例程序
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include <pybind11/numpy.h>
#include <pybind11/stl.h>
#include <opencv2/opencv.hpp>
#include <iostream>
namespace py = pybind11;
// The 4 functions from above go here...
int main()
{
// Start the interpreter and keep it alive
py::scoped_interpreter guard{};
try {
auto locals = py::dict{};
py::exec(R"(
import numpy as np
def test_cpp_to_py(arr):
return (arr[0,0,0], 2.0, 30)
)");
auto test_cpp_to_py = py::globals()["test_cpp_to_py"];
for (int i = 0; i < 10; i++) {
int64 t0 = cv::getTickCount();
cv::Mat img(cv::Mat::zeros(1024, 1024, CV_8UC3) + cv::Scalar(1, 1, 1));
int64 t1 = cv::getTickCount();
auto result = test_cpp_to_py(mat_to_nparray(img));
int64 t2 = cv::getTickCount();
double delta0 = (t1 - t0) / cv::getTickFrequency() * 1000;
double delta1 = (t2 - t1) / cv::getTickFrequency() * 1000;
std::cout << "* " << delta0 << " ms | " << delta1 << " ms" << std::endl;
}
} catch (py::error_already_set& e) {
std::cerr << e.what() << "\n";
}
return 0;
}
控制台输出
* 4.56413 ms | 0.225657 ms
* 3.95923 ms | 0.0736127 ms
* 3.80335 ms | 0.0438603 ms
* 3.99262 ms | 0.0577587 ms
* 3.82262 ms | 0.0572 ms
* 3.72373 ms | 0.0394603 ms
* 3.74014 ms | 0.0405079 ms
* 3.80621 ms | 0.054546 ms
* 3.72177 ms | 0.0386222 ms
* 3.70683 ms | 0.0373651 ms
我真的很喜欢 Dan Mašek 的回答!基于这些见解,我构建了一个小型库 (https://github.com/pthom/cvnp),它提供:
- cv::Mat 和 numpy.ndarray 之间具有共享内存的显式转换器(如他的回答所示)
- cv::Matx 和 numpy.ndarray 之间的显式转换器,共享内存
它还提供自动转换:
- 在
cv::Mat
、cv::Matx
、cv::Vec
和 numpy.ndarray
之间共享内存的转换
- 在
cv::Size
、cv::Point
、cv::Point3
和 python tuple
之间的简单类型的无共享内存的转换
我有一个 C++ 应用程序,它通过共享内存将数据发送到 python 函数。
这在 Python 中使用 ctypes
效果很好,例如双打和浮点数。现在,我需要在函数中添加一个 cv::Mat
。
我目前的代码是:
//h
#include <iostream>
#include <opencv2\core.hpp>
#include <opencv2\highgui.hpp>
struct TransferData
{
double score;
float other;
int num;
int w;
int h;
int channels;
uchar* data;
};
#define C_OFF 1000
void fill(TransferData* data, int run, uchar* frame, int w, int h, int channels)
{
data->score = C_OFF + 1.0;
data->other = C_OFF + 2.0;
data->num = C_OFF + 3;
data->w = w;
data->h = h;
data->channels = channels;
data->data = frame;
}
//.cpp
namespace py = pybind11;
using namespace boost::interprocess;
void main()
{
//python setup
Py_SetProgramName(L"PYTHON");
py::scoped_interpreter guard{};
py::module py_test = py::module::import("Transfer_py");
// Create Data
windows_shared_memory shmem(create_only, "TransferDataSHMEM",
read_write, sizeof(TransferData));
mapped_region region(shmem, read_write);
std::memset(region.get_address(), 0, sizeof(TransferData));
TransferData* data = reinterpret_cast<TransferData*>(region.get_address());
//loop
for (int i = 0; i < 10; i++)
{
int64 t0 = cv::getTickCount();
std::cout << "C++ Program - Filling Data" << std::endl;
cv::Mat frame = cv::imread("input.jpg");
fill(data, i, frame.data, frame.cols, frame.rows, frame.channels());
//run the python function
//process
py::object result = py_test.attr("datathrough")();
int64 t1 = cv::getTickCount();
double secs = (t1 - t0) / cv::getTickFrequency();
std::cout << "took " << secs * 1000 << " ms" << std::endl;
}
std::cin.get();
}
//Python //传输数据class
import ctypes
class TransferData(ctypes.Structure):
_fields_ = [
('score', ctypes.c_double),
('other', ctypes.c_float),
('num', ctypes.c_int),
('w', ctypes.c_int),
('h', ctypes.c_int),
('frame', ctypes.c_void_p),
('channels', ctypes.c_int)
]
PY_OFF = 2000
def fill(data):
data.score = PY_OFF + 1.0
data.other = PY_OFF + 2.0
data.num = PY_OFF + 3
//主要Python函数
import TransferData
import sys
import mmap
import ctypes
def datathrough():
shmem = mmap.mmap(-1, ctypes.sizeof(TransferData.TransferData), "TransferDataSHMEM")
data = TransferData.TransferData.from_buffer(shmem)
print('Python Program - Getting Data')
print('Python Program - Filling Data')
TransferData.fill(data)
如何将 cv::Mat
帧数据添加到 Python 端?我从 C++ 中将它作为 uchar*
发送,据我所知,我需要它是一个 numpy
数组才能在 Python 中获得 cv2.Mat
。从 'width, height, channels, frameData' 到 opencv python cv2.Mat
的正确方法是什么?
我正在使用共享内存,因为速度是一个因素,我已经使用 Python API 方法进行了测试,但它对我的需求来说太慢了。
总体思路(在 OpenCV Python 绑定中使用)是创建一个与 Mat
对象共享其数据缓冲区的 numpy ndarray
,并将其传递给Python 函数。
注意:此时,我将示例仅限于连续矩阵。
我们可以利用 pybind11::array
class.
我们需要为要使用的 numpy 数组确定合适的
dtype
。这是一个简单的一对一映射,我们可以使用switch
:py::dtype determine_np_dtype(int depth) { switch (depth) { case CV_8U: return py::dtype::of<uint8_t>(); case CV_8S: return py::dtype::of<int8_t>(); case CV_16U: return py::dtype::of<uint16_t>(); case CV_16S: return py::dtype::of<int16_t>(); case CV_32S: return py::dtype::of<int32_t>(); case CV_32F: return py::dtype::of<float>(); case CV_64F: return py::dtype::of<double>(); default: throw std::invalid_argument("Unsupported data type."); } }
确定 numpy 数组的形状。为了使它的行为与 OpenCV 相似,我们让它将 1 通道
Mat
s 映射到 2D numpy 数组,并将多通道Mat
s 映射到 3D numpy 数组。std::vector<std::size_t> determine_shape(cv::Mat& m) { if (m.channels() == 1) { return { static_cast<size_t>(m.rows) , static_cast<size_t>(m.cols) }; } return { static_cast<size_t>(m.rows) , static_cast<size_t>(m.cols) , static_cast<size_t>(m.channels()) }; }
提供将共享缓冲区的生命周期延长到 numpy 数组生命周期的方法。我们可以围绕源
Mat
的浅表副本创建一个pybind11::capsule
—— 由于对象的实现方式,这有效地增加了所需时间的引用计数。py::capsule make_capsule(cv::Mat& m) { return py::capsule(new cv::Mat(m) , [](void *v) { delete reinterpret_cast<cv::Mat*>(v); } ); }
现在,我们可以进行转换了。
py::array mat_to_nparray(cv::Mat& m)
{
if (!m.isContinuous()) {
throw std::invalid_argument("Only continuous Mats supported.");
}
return py::array(determine_np_dtype(m.depth())
, determine_shape(m)
, m.data
, make_capsule(m));
}
假设,我们有一个 Python 函数,例如
def foo(arr):
print(arr.shape)
在 pybind 对象中捕获 fun
。然后使用 Mat
作为源从 C++ 调用这个函数,我们会做这样的事情:
cv::Mat img; // Initialize this somehow
auto result = fun(mat_to_nparray(img));
示例程序
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include <pybind11/numpy.h>
#include <pybind11/stl.h>
#include <opencv2/opencv.hpp>
#include <iostream>
namespace py = pybind11;
// The 4 functions from above go here...
int main()
{
// Start the interpreter and keep it alive
py::scoped_interpreter guard{};
try {
auto locals = py::dict{};
py::exec(R"(
import numpy as np
def test_cpp_to_py(arr):
return (arr[0,0,0], 2.0, 30)
)");
auto test_cpp_to_py = py::globals()["test_cpp_to_py"];
for (int i = 0; i < 10; i++) {
int64 t0 = cv::getTickCount();
cv::Mat img(cv::Mat::zeros(1024, 1024, CV_8UC3) + cv::Scalar(1, 1, 1));
int64 t1 = cv::getTickCount();
auto result = test_cpp_to_py(mat_to_nparray(img));
int64 t2 = cv::getTickCount();
double delta0 = (t1 - t0) / cv::getTickFrequency() * 1000;
double delta1 = (t2 - t1) / cv::getTickFrequency() * 1000;
std::cout << "* " << delta0 << " ms | " << delta1 << " ms" << std::endl;
}
} catch (py::error_already_set& e) {
std::cerr << e.what() << "\n";
}
return 0;
}
控制台输出
* 4.56413 ms | 0.225657 ms
* 3.95923 ms | 0.0736127 ms
* 3.80335 ms | 0.0438603 ms
* 3.99262 ms | 0.0577587 ms
* 3.82262 ms | 0.0572 ms
* 3.72373 ms | 0.0394603 ms
* 3.74014 ms | 0.0405079 ms
* 3.80621 ms | 0.054546 ms
* 3.72177 ms | 0.0386222 ms
* 3.70683 ms | 0.0373651 ms
我真的很喜欢 Dan Mašek 的回答!基于这些见解,我构建了一个小型库 (https://github.com/pthom/cvnp),它提供:
- cv::Mat 和 numpy.ndarray 之间具有共享内存的显式转换器(如他的回答所示)
- cv::Matx 和 numpy.ndarray 之间的显式转换器,共享内存
它还提供自动转换:
- 在
cv::Mat
、cv::Matx
、cv::Vec
和numpy.ndarray
之间共享内存的转换
- 在
cv::Size
、cv::Point
、cv::Point3
和 pythontuple
之间的简单类型的无共享内存的转换