Как получить изображение OpenCV из Python и использовать его в C ++ в pybind11?

Я пытаюсь понять, как можно получить изображение OpenCV из Python на C ++. Я пытаюсь отправить функцию обратного вызова из C ++ в мой модуль Python, а затем, когда я вызываю определенный метод python в моем приложении C ++, я могу получить доступ к необходимому изображению.

Прежде чем добавить более подробную информацию, я должен добавить, что в этом отношении уже есть несколько вопросов, в том числе:

  1. как преобразовать-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-convert-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::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 или что-то в этом роде. Так что я совершенно не понимаю, как это интерпретировать!


person Rika    schedule 29.03.2020    source источник
comment
Пусть обратный вызов принимает массив numpy вместо Mat. Затем в обратном вызове создайте заголовок Mat для буфера данных массива numpy. (имейте в виду, что это разделяет буфер, поэтому, если вам нужно, чтобы Mat имел более длительный срок службы, чем область обратного вызова, вам придется сделать глубокую копию)   -  person Dan Mašek    schedule 29.03.2020
comment
@ DanMašek, спасибо, но что ты имеешь в виду py::array_t<double>?   -  person Rika    schedule 29.03.2020
comment
Да, похоже. Хотя вам, вероятно, придется изменить параметр шаблона, чтобы он соответствовал фактическому типу данных массива numpy - если это изображение, скорее всего, это uint8_t.   -  person Dan Mašek    schedule 29.03.2020
comment
@ DanMašek Большое спасибо. Я действительно ценю какой-то фрагмент. Я как бы заблудился здесь! специально по поводу шапки мата! как мне это сделать?   -  person Rika    schedule 29.03.2020
comment
Этот конструктор (или один из связанных). Вам нужно извлечь ширину и высоту, а также количество каналов из массива numpy shape и получить указатель на его буфер данных. - похоже на функцию прямого доступа в pybind давайте сделаем это.   -  person Dan Mašek    schedule 29.03.2020
comment
Я хотел бы дать вам полный пример кода, но сначала мне нужно настроить PyBind и изучить некоторые детали. Не возражал бы поближе познакомиться с этим, так что это не проблема, но в данный момент у меня все еще есть работа, которую мне нужно сделать сегодня вечером ... может быть, сегодня вечером.   -  person Dan Mašek    schedule 29.03.2020
comment
@ DanMašek, большое спасибо, чувак. это действительно очень ценится. Я доберусь до этого и, надеюсь, решу. в случае, если я с чем-то столкнулся, обновлю вопрос.   -  person Rika    schedule 29.03.2020
comment
@ DanMašek: Я сделал это, но получившееся изображение повреждено. Не могли бы вы взглянуть и увидеть, сможете ли вы определить, где я ошибся? Заранее большое спасибо   -  person Rika    schedule 29.03.2020


Ответы (2)


В конечном итоге я смог успешно заставить это работать благодаря @ 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);
}

обратите внимание, что приведение необходимо, иначе вы получите только черный экран!
Однако, если бы каким-то образом существовал способ, подобный 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);
}


person Rika    schedule 29.03.2020
comment
Интересно. Кстати, эта строка с img2 здесь кажется лишней. | Я дошел до вызова обернутой функции pybind во встроенном Python. Сейчас 3 часа ночи, так что я продолжу завтра. Хорошие идеи здесь! - person Dan Mašek; 30.03.2020
comment
Большое спасибо. это действительно ценится :) - person Rika; 30.03.2020
comment
Хорошее наблюдение с data, а не с ptr. Однако имейте в виду, что прямо сейчас вы используете приведение в стиле C для удаления const из указателя данных. Вы можете использовать mutable_data, который возвращает неконстантный указатель, и в этом случае вам вообще не нужно никакого приведения. | Вот черновик довольно общей функции преобразования: pastebin.com/sawt2EFt Он все еще нуждается в дополнительном тестировании и маркере. -изоляция то. - person Dan Mašek; 01.04.2020
comment
Большое спасибо. Я посмотрю на это. очень признателен вам за любезную помощь и потраченное на это время :) - person Rika; 01.04.2020
comment
@ DanMašek Вы случайно не знаете, как мы можем раскрыть py::object на языке C? рассмотрим эту функцию, которая у нас есть выше, она принимает py::array_t, но если я хочу раскрыть это в C, кажется, что void * не работает (в части python это вызывает исключение). Как нам это сделать? - person Rika; 10.04.2020

Для преобразования между 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();
    ...
}
person Burak    schedule 09.03.2021