如何从 Python 获取 OpenCV 图像并在 pybind11 的 C++ 中使用它?
How to get the OpenCV image from Python and use it in C++ in pybind11?
我正在尝试弄清楚如何在 C++ 中从 Python 接收 OpenCV 图像。我正在尝试将回调函数从 C++ 发送到我的 Python 模块,然后当我在我的 C++ 应用程序中调用特定的 python 方法时,我可以访问所需的图像。
在我添加更多细节之前,我需要补充一点,在这方面已经有几个问题,包括:
- pass-image-data-from-python-to-cvmat-in-c
- writing-python-bindings-for-c-code-that-use-opencv
但其中 none 有关于 Pybind11
的信息。事实上,无论是否使用 Boost.Python
,他们都在使用 PyObject
(来自 Python.h
header)。因此,我的第一个尝试是了解如何在 Pybind11
中知道它支持 Numpy
数组,因此它有望使事情变得容易得多。
同样在 C++
方面,OpenCV
有两个版本,3.x 和 4.x 我最近发现 4.x 是 C++11
合规。在 Python 方面,我使用了 OpenCV 3.x
并且我正处于选择哪个的十字路口
以及它对 Pybind11
.
有什么影响
到目前为止我已经尝试过:我做了一个快速的虚拟回调并尝试像这样传递一个简单的 cv::Mat&
:
#include <pybind11/embed.h>
#include <pybind11/numpy.h>
#include <pybind11/stl.h>
#include <pybind11/functional.h>
namespace py = pybind11;
...
void cpp_callback1(bool i, std::string id, cv::Mat img)
{
auto timenow = chrono::system_clock::to_time_t(chrono::system_clock::now());
cout <<"arg1: " << i << " arg2: " << id<<" arg3: " << typeid(img).name() <<" " << ctime(&timenow)<<endl;
}
并像这样使用它:
py::list callback_lst;
callback_lst.attr("append")(py::cpp_function(cpp_callback1));
py::dict core_kwargs = py::dict("callback_list"_a = callback_lst,
"debug_show_feed"_a = true);
py::object core_obj = core_cls(**core_kwargs);
core_obj.attr("start")();
但它失败了,在 python 部分出现异常:
29/03/2020 21:56:47 : exception occured ("(): incompatible function arguments. The following argument types are supported:\n 1. (arg0: bool, arg1: str, arg2: cv::Mat) -> None\n\nInvoked with: True, '5', array([[[195, 217, 237],\n [195, 217, 237],\n [196, 218, 238],\n ...,\n [211, 241, 255],\n [211, 241, 255],\n [211, 241, 255]],\n\n [[195, 217, 237],\n [195, 217, 237],\n [195, 217, 237],\n ...,\n [211, 241, 255],\n [211, 241, 255],\n [211, 241, 255]],\n\n [[195, 217, 237],\n [195, 217, 237],\n [195, 217, 237],\n ...,\n [211, 241, 255],\n [211, 241, 255],\n [211, 241, 255]],\n\n ...,\n\n [[120, 129, 140],\n [110, 120, 130],\n [113, 122, 133],\n ...,\n [196, 209, 245],\n [195, 207, 244],\n [195, 207, 244]],\n\n [[120, 133, 142],\n [109, 121, 130],\n [114, 120, 131],\n ...,\n [195, 208, 242],\n [195, 208, 242],\n [195, 208, 242]],\n\n [[121, 134, 143],\n [106, 119, 128],\n [109, 114, 126],\n ...,\n [194, 207, 241],\n [195, 208, 242],\n [195, 208, 242]]], dtype=uint8)",)
Traceback (most recent call last):
File "C:\Users\Master\Anaconda3\Lib\site-packages\F\utils.py", line 257, in start
self._main_loop()
File "C:\Users\Master\Anaconda3\Lib\site-packages\F\utils.py", line 301, in _main_loop
self._execute_callbacks(is_valid, name, frame)
File "C:\Users\Master\Anaconda3\Lib\site-packages\F\utils.py", line 142, in _execute_callbacks
callback(*args)
TypeError: (): incompatible function arguments. The following argument types are supported:
1. (arg0: bool, arg1: str, arg2: cv::Mat) -> None
Invoked with: True, '5', array([[[195, 217, 237],
[195, 217, 237],
[196, 218, 238],
...,
[211, 241, 255],
[211, 241, 255],
[211, 241, 255]],
[[195, 217, 237],
[195, 217, 237],
[195, 217, 237],
...,
使用 py::object
或 py::array_t<uint8_t>
而不是 cv::Mat
不会导致任何错误,但我似乎无法找到一种方法将它们转换回 cv::Mat
正确!
我试图按照评论中的说明将 numpy 数组转换为 cv::Mat
,但输出是垃圾:
void cpp_callback1(bool i, std::string id, py::array_t<uint8_t>& img)
{
auto im = img.unchecked<3>();
auto rows = img.shape(0);
auto cols = img.shape(1);
auto type = CV_8UC3;
//py::buffer_info buf = img.request();
cv::Mat img2(rows, cols, type, img.ptr());
cv::imshow("test", img2);
}
结果:
在我看来,在那个方向上的步幅或某些东西搞砸了,图像是这样显示的。我在这里做错了什么?但是我不能使用 img.strides() !当使用 py::print 打印它时,它显示 960
或类似的东西。所以我完全不知道如何解释它!
多亏了@DanMasek and this link,我最终才能成功实现它:
void cpp_callback1(bool i, std::string id, py::array_t<uint8_t>& img)
{
py::buffer_info buf = img.request();
cv::Mat mat(buf.shape[0], buf.shape[1], CV_8UC3, (unsigned char*)buf.ptr);
cv::imshow("test", mat);
}
请注意强制转换是必要的,否则,您只会看到黑屏!
但是,如果有某种方法可以用来更改引用类型,例如 py::return_value_policy
,那么即使 python 部分结束,c++ 端也不会崩溃,那就太好了。
旁注:
似乎 ptr
属性 暴露在 numpy
数组中,实际上不是 py::handle
而是 PyObject*&
。我无法成功转换,因此求助于我在上面发布的解决方案。当我弄清楚时,我会更新这个答案。
更新:
我发现,数组 data
包含指向底层缓冲区的指针,也可以轻松使用。
来自 <pybind11/numpy.h>
L681:
/// Pointer to the contained data. If index is not provided, points to the
/// beginning of the buffer. May throw if the index would lead to out of bounds access.
所以我使用 img.ptr()
的原始代码可以像这样使用 img.data()
工作:
void cpp_callback1(bool i, std::string id, py::array_t<uint8_t>& img)
{
//auto im = img.unchecked<3>();
auto rows = img.shape(0);
auto cols = img.shape(1);
auto type = CV_8UC3;
cv::Mat img2(rows, cols, type, (unsigned char*)img.data());
cv::imshow("test", img2);
}
要在 cv::Mat
和 np.ndarray
之间转换,您可以使用 pybind11_opencv_numpy。
将 ndarray_converter.h
和 ndarray_converter.cpp
复制到您的项目目录。
CMakeLists.txt
add_subdirectory(pybind11)
execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "import numpy; print(numpy.get_include())" OUTPUT_VARIABLE NUMPY_INCLUDE OUTPUT_STRIP_TRAILING_WHITESPACE)
message(STATUS "NUMPY_INCLUDE: " ${NUMPY_INCLUDE})
include_directories(${NUMPY_INCLUDE})
pybind11_add_module(mymodule "cpp2py.cpp" "ndarray_converter.cpp")
target_link_libraries(mymodule PRIVATE ${OpenCV_LIBS})
target_compile_definitions(mymodule PRIVATE)
cpp2py.cpp
#include "ndarray_converter.h"
PYBIND11_MODULE(mymodule, m)
{
NDArrayConverter::init_numpy();
...
}
这将是图像的通用转换,具有任意数量的通道和可能与标准图像不同的步幅(例如,如果 Mat
已作为更大矩阵中的感兴趣区域获得)
#include <pybind11/pybind11.h>
void cpp_callback1(py::array_t<uint8_t>& img)
{
cv::Mat mat(img.shape(0), img.shape(1), CV_MAKETYPE(CV_8U, img.shape(2)),
const_cast<uint8_t*>(img.data()), img.strides(0));
cv::imshow("test", mat);
}
img.shape(0)
-> 行
img.shape(1)
-> 列
img.shape(2)
-> n_channels
img.strides(0)
-> 同一图像列上两个相邻像素之间的跨度(以字节为单位)
你也可以试试https://github.com/pthom/cvnp
它提供自动转换:
- 在
cv::Mat
、cv::Matx
、cv::Vec
和 numpy.ndarray
之间共享内存的转换
- 在
cv::Size
、cv::Point
、cv::Point3
和 python 之间 tuple
之间的简单类型的无共享内存的转换
它还在 cv::Mat
、cv::Matx
和 numpy.ndarray
之间使用共享内存提供显式转换器
我正在尝试弄清楚如何在 C++ 中从 Python 接收 OpenCV 图像。我正在尝试将回调函数从 C++ 发送到我的 Python 模块,然后当我在我的 C++ 应用程序中调用特定的 python 方法时,我可以访问所需的图像。
在我添加更多细节之前,我需要补充一点,在这方面已经有几个问题,包括:
- pass-image-data-from-python-to-cvmat-in-c
- writing-python-bindings-for-c-code-that-use-opencv
但其中 none 有关于 Pybind11
的信息。事实上,无论是否使用 Boost.Python
,他们都在使用 PyObject
(来自 Python.h
header)。因此,我的第一个尝试是了解如何在 Pybind11
中知道它支持 Numpy
数组,因此它有望使事情变得容易得多。
同样在 C++
方面,OpenCV
有两个版本,3.x 和 4.x 我最近发现 4.x 是 C++11
合规。在 Python 方面,我使用了 OpenCV 3.x
并且我正处于选择哪个的十字路口
以及它对 Pybind11
.
到目前为止我已经尝试过:我做了一个快速的虚拟回调并尝试像这样传递一个简单的 cv::Mat&
:
#include <pybind11/embed.h>
#include <pybind11/numpy.h>
#include <pybind11/stl.h>
#include <pybind11/functional.h>
namespace py = pybind11;
...
void cpp_callback1(bool i, std::string id, cv::Mat img)
{
auto timenow = chrono::system_clock::to_time_t(chrono::system_clock::now());
cout <<"arg1: " << i << " arg2: " << id<<" arg3: " << typeid(img).name() <<" " << ctime(&timenow)<<endl;
}
并像这样使用它:
py::list callback_lst;
callback_lst.attr("append")(py::cpp_function(cpp_callback1));
py::dict core_kwargs = py::dict("callback_list"_a = callback_lst,
"debug_show_feed"_a = true);
py::object core_obj = core_cls(**core_kwargs);
core_obj.attr("start")();
但它失败了,在 python 部分出现异常:
29/03/2020 21:56:47 : exception occured ("(): incompatible function arguments. The following argument types are supported:\n 1. (arg0: bool, arg1: str, arg2: cv::Mat) -> None\n\nInvoked with: True, '5', array([[[195, 217, 237],\n [195, 217, 237],\n [196, 218, 238],\n ...,\n [211, 241, 255],\n [211, 241, 255],\n [211, 241, 255]],\n\n [[195, 217, 237],\n [195, 217, 237],\n [195, 217, 237],\n ...,\n [211, 241, 255],\n [211, 241, 255],\n [211, 241, 255]],\n\n [[195, 217, 237],\n [195, 217, 237],\n [195, 217, 237],\n ...,\n [211, 241, 255],\n [211, 241, 255],\n [211, 241, 255]],\n\n ...,\n\n [[120, 129, 140],\n [110, 120, 130],\n [113, 122, 133],\n ...,\n [196, 209, 245],\n [195, 207, 244],\n [195, 207, 244]],\n\n [[120, 133, 142],\n [109, 121, 130],\n [114, 120, 131],\n ...,\n [195, 208, 242],\n [195, 208, 242],\n [195, 208, 242]],\n\n [[121, 134, 143],\n [106, 119, 128],\n [109, 114, 126],\n ...,\n [194, 207, 241],\n [195, 208, 242],\n [195, 208, 242]]], dtype=uint8)",)
Traceback (most recent call last):
File "C:\Users\Master\Anaconda3\Lib\site-packages\F\utils.py", line 257, in start
self._main_loop()
File "C:\Users\Master\Anaconda3\Lib\site-packages\F\utils.py", line 301, in _main_loop
self._execute_callbacks(is_valid, name, frame)
File "C:\Users\Master\Anaconda3\Lib\site-packages\F\utils.py", line 142, in _execute_callbacks
callback(*args)
TypeError: (): incompatible function arguments. The following argument types are supported:
1. (arg0: bool, arg1: str, arg2: cv::Mat) -> None
Invoked with: True, '5', array([[[195, 217, 237],
[195, 217, 237],
[196, 218, 238],
...,
[211, 241, 255],
[211, 241, 255],
[211, 241, 255]],
[[195, 217, 237],
[195, 217, 237],
[195, 217, 237],
...,
使用 py::object
或 py::array_t<uint8_t>
而不是 cv::Mat
不会导致任何错误,但我似乎无法找到一种方法将它们转换回 cv::Mat
正确!
我试图按照评论中的说明将 numpy 数组转换为 cv::Mat
,但输出是垃圾:
void cpp_callback1(bool i, std::string id, py::array_t<uint8_t>& img)
{
auto im = img.unchecked<3>();
auto rows = img.shape(0);
auto cols = img.shape(1);
auto type = CV_8UC3;
//py::buffer_info buf = img.request();
cv::Mat img2(rows, cols, type, img.ptr());
cv::imshow("test", img2);
}
结果:
在我看来,在那个方向上的步幅或某些东西搞砸了,图像是这样显示的。我在这里做错了什么?但是我不能使用 img.strides() !当使用 py::print 打印它时,它显示 960
或类似的东西。所以我完全不知道如何解释它!
多亏了@DanMasek and this link,我最终才能成功实现它:
void cpp_callback1(bool i, std::string id, py::array_t<uint8_t>& img)
{
py::buffer_info buf = img.request();
cv::Mat mat(buf.shape[0], buf.shape[1], CV_8UC3, (unsigned char*)buf.ptr);
cv::imshow("test", mat);
}
请注意强制转换是必要的,否则,您只会看到黑屏!
但是,如果有某种方法可以用来更改引用类型,例如 py::return_value_policy
,那么即使 python 部分结束,c++ 端也不会崩溃,那就太好了。
旁注:
似乎 ptr
属性 暴露在 numpy
数组中,实际上不是 py::handle
而是 PyObject*&
。我无法成功转换,因此求助于我在上面发布的解决方案。当我弄清楚时,我会更新这个答案。
更新:
我发现,数组 data
包含指向底层缓冲区的指针,也可以轻松使用。
来自 <pybind11/numpy.h>
L681:
/// Pointer to the contained data. If index is not provided, points to the
/// beginning of the buffer. May throw if the index would lead to out of bounds access.
所以我使用 img.ptr()
的原始代码可以像这样使用 img.data()
工作:
void cpp_callback1(bool i, std::string id, py::array_t<uint8_t>& img)
{
//auto im = img.unchecked<3>();
auto rows = img.shape(0);
auto cols = img.shape(1);
auto type = CV_8UC3;
cv::Mat img2(rows, cols, type, (unsigned char*)img.data());
cv::imshow("test", img2);
}
要在 cv::Mat
和 np.ndarray
之间转换,您可以使用 pybind11_opencv_numpy。
将 ndarray_converter.h
和 ndarray_converter.cpp
复制到您的项目目录。
CMakeLists.txt
add_subdirectory(pybind11)
execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "import numpy; print(numpy.get_include())" OUTPUT_VARIABLE NUMPY_INCLUDE OUTPUT_STRIP_TRAILING_WHITESPACE)
message(STATUS "NUMPY_INCLUDE: " ${NUMPY_INCLUDE})
include_directories(${NUMPY_INCLUDE})
pybind11_add_module(mymodule "cpp2py.cpp" "ndarray_converter.cpp")
target_link_libraries(mymodule PRIVATE ${OpenCV_LIBS})
target_compile_definitions(mymodule PRIVATE)
cpp2py.cpp
#include "ndarray_converter.h"
PYBIND11_MODULE(mymodule, m)
{
NDArrayConverter::init_numpy();
...
}
这将是图像的通用转换,具有任意数量的通道和可能与标准图像不同的步幅(例如,如果 Mat
已作为更大矩阵中的感兴趣区域获得)
#include <pybind11/pybind11.h>
void cpp_callback1(py::array_t<uint8_t>& img)
{
cv::Mat mat(img.shape(0), img.shape(1), CV_MAKETYPE(CV_8U, img.shape(2)),
const_cast<uint8_t*>(img.data()), img.strides(0));
cv::imshow("test", mat);
}
img.shape(0)
-> 行img.shape(1)
-> 列img.shape(2)
-> n_channelsimg.strides(0)
-> 同一图像列上两个相邻像素之间的跨度(以字节为单位)
你也可以试试https://github.com/pthom/cvnp
它提供自动转换:
- 在
cv::Mat
、cv::Matx
、cv::Vec
和numpy.ndarray
之间共享内存的转换
- 在
cv::Size
、cv::Point
、cv::Point3
和 python 之间tuple
之间的简单类型的无共享内存的转换
它还在 cv::Mat
、cv::Matx
和 numpy.ndarray
之间使用共享内存提供显式转换器