【问题标题】:How to get the OpenCV image from Python and use it in C++ in pybind11?如何从 Python 获取 OpenCV 图像并在 pybind11 中的 C++ 中使用它?
【发布时间】:2020-07-10 01:39:48
【问题描述】:

我试图弄清楚如何在 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. writing-python-bindings-for-c-code-that-use-opencv
  4. c-conversion-from-numpy-array-to-mat-opencv

但他们都没有关于Pybind11 的任何信息。事实上,他们都在使用PyObject(来自Python.h 标头)有和没有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;
}

并像这样使用它:

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::objectpy::array_t&lt;uint8_t&gt; 而不是cv::Mat 不会导致任何错误,但我似乎无法找到将它们正确转换回cv::Mat 的方法!

我尝试按照 cmets 中的说明将 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 或类似的东西。所以我完全不知道如何解释!

【问题讨论】:

  • 让回调采用 numpy 数组而不是 Mat。然后在回调中,为 numpy 数组的数据缓冲区创建一个 Mat 标头。 (请记住,这共享缓冲区,因此如果您需要 Mat 的生命周期比回调的范围更长,则必须进行深层复制)
  • @DanMašek,非常感谢,但您的意思是 py::array_t&lt;double&gt; 吗?
  • 是的,看起来像。尽管您可能必须更改模板参数以匹配 numpy 数组的实际数据类型——如果它是图像,则可能是 uint8_t
  • 我很乐意为您提供一个完整的代码示例,但我首先必须设置 PyBind 并了解一些细节。不介意再熟悉一点,所以这不是问题,但在这个特殊的时刻,我今晚还有一些工作要做……也许今晚晚些时候。
  • @DanMašek,非常感谢。它真的非常感激。我会解决的,希望能解决。如果我遇到问题,我会更新问题。

标签: python c++ opencv pybind11


【解决方案1】:

感谢@DanMaseklink,我最终可以成功地让它工作:

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++ 端也不会崩溃。

旁注:
似乎numpy 数组中暴露的ptr 属性实际上不是py::handle,而是PyObject*&amp;。我无法成功转换,因此求助于我上面发布的解决方案。当我弄清楚这一点时,我会更新这个答案。

更新:

我发现,数组data 包含一个指向底层缓冲区的指针,并且也可以轻松使用。 来自&lt;pybind11/numpy.h&gt;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);
}


【讨论】:

  • 有趣。顺便说一句,img2 的那条线在这里似乎是多余的。 |我到了在嵌入式 Python 中调用 pybind 包装函数的地步。现在是凌晨 3 点,所以我明天继续。这里有很好的见解!
  • 非常感谢。真的很感激:)
  • 使用data 而非ptr 进行良好观察。但是,请记住,现在您正在使用 C 风格的强制转换从数据指针中丢弃 const。您可以使用 mutable_data 返回一个非常量指针,在这种情况下您根本不需要任何强制转换。 |这是一个相当通用的转换函数的草稿:pastebin.com/sawt2EFt 它仍然需要更多的测试和防弹。
  • 非常感谢。我会看看它。非常感谢您的帮助和时间:)
  • @DanMašek 你知道我们如何在 C 中暴露py::object 吗?考虑我们上面的这个函数,它接受py::array_t,但如果我想在C 中公开它,似乎void* 不起作用(在python 部分它会引发异常)。我们应该怎么做?
【解决方案2】:

要在cv::Matnp.ndarray 之间进行转换,可以使用pybind11_opencv_numpy

ndarray_converter.hndarray_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();
    ...
}

【讨论】:

  • 工作简单而完美。我只是失去了一些时间,因为我忘记使用“sudo apt-get install python-numpy”而不是“pip install numpy”正确安装 numpy。
  • 另外,要从python端访问对象属性,必须使用:.def_readwrite("frame", &MyCppObject::frame)
  • @Guinther CMake 尝试查找应该具有 C++ 标头的 NumPy 库,因此 pip install 不起作用。我假设你有一个类MyCppObject,它有一个公共成员cv::Mat frame。随意提供一个最小的工作示例来改进帖子。
【解决方案3】:

这将是具有任意数量通道和步幅的图像的通用转换,可能与标准图像不同(例如,如果 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) -> 同一图像列上两个相邻像素之间的跨度(以字节为单位)

【讨论】:

    猜你喜欢
    • 2023-04-10
    • 1970-01-01
    • 1970-01-01
    • 2012-07-28
    • 2020-08-11
    • 2010-12-20
    • 1970-01-01
    • 1970-01-01
    • 2018-08-26
    相关资源
    最近更新 更多