如何从 Python 获取 OpenCV 图像并在 pybind11 的 C++ 中使用它?

Rik*_*ika 5 c++ python opencv pybind11

我试图弄清楚如何从 C++ 中的 Python 接收 OpenCV 图像。我正在尝试将回调函数从 C++ 发送到我的 Python 模块,然后当我在我的 C++ 应用程序中调用特定的 python 方法时,我可以访问所需的图像。

在我添加更多细节之前,我需要补充一点,这方面已经有几个问题,包括:

  1. how-to-convert-opencv-image-data-from-python-to-c
  2. pass-image-data-from-python-to-cvmat-in-c
  3. 编写-python-bindings-for-c-code-that-use-opencv
  4. c-conversion-from-numpy-array-to-mat-opencv

但他们都没有关于Pybind11. 事实上,他们都在使用PyObject(from Python.hheader) with and without Boost.Python. 所以我的第一个尝试是知道如何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;
}

Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

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")();

Run Code Online (Sandbox Code Playgroud)

但它失败了,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],
        ...,

Run Code Online (Sandbox Code Playgroud)

使用py::objectorpy::array_t<uint8_t>而不是cv::Mat不会导致任何错误,但我似乎无法找到一种方法将它们cv::Mat正确地转换回!

我试图cv::Mat按照评论中的说明将 numpy 数组转换为 a ,但输出是垃圾:

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);
}

Run Code Online (Sandbox Code Playgroud)

结果是 :

在此处输入图片说明

在我看来,那个方向的步伐或某些东西被弄乱了,图像显示为这样。我在这里做错了什么?不过我不能使用 img.strides() !当使用 py::print 打印它时,它会显示960或类似的东西。所以我完全不知道如何解释它!

Rik*_*ika 5

感谢 @ DanMasek和这个链接,我最终可以成功地让它工作:

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);
}

Run Code Online (Sandbox Code Playgroud)

请注意,演员是必要的,否则,你只会得到一个黑色的屏幕!
但是,如果有一种类似的方法py::return_value_policy可以用来改变引用的类型,那么即使 python 部分结束,c++ 端也不会崩溃会很棒。

旁注:
似乎ptrnumpy数组中公开的属性实际上不是 apy::handle而是 a 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.
Run Code Online (Sandbox Code Playgroud)

所以我使用的原始代码img.ptr()可以img.data()像这样使用:

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);
}

Run Code Online (Sandbox Code Playgroud)

  • 使用“data”而不是“ptr”进行良好的观察。但是,请记住,现在您正在使用 C 样式转换来从数据指针中删除 const。您可以使用返回非常量指针的“mutable_data”,在这种情况下您根本不需要任何强制转换。| 这是一个相当通用的转换函数的草稿:https://pastebin.com/sawt2EFt 它仍然需要更多的测试和防弹。 (2认同)

Bur*_*rak 5

要在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)
Run Code Online (Sandbox Code Playgroud)

cpp2py.cpp

#include "ndarray_converter.h"

PYBIND11_MODULE(mymodule, m)
{
    NDArrayConverter::init_numpy();
    ...
}
Run Code Online (Sandbox Code Playgroud)

  • @Guinther CMake 尝试找到应该有 C++ 标头的 NumPy 库,因此 pip install 不起作用。我假设你有一个类“MyCppObject”,它有一个公共成员“cv::Matframe”。请随意通过提供一个最小的工作示例来改进这篇文章。 (2认同)