Как отправить cv :: Mat в python через общую память? - PullRequest
1 голос
/ 31 марта 2020

У меня есть приложение на c ++, которое отправляет данные в функцию python через общую память. Это прекрасно работает при использовании ctypes в Python, таких как удвоения и числа с плавающей точкой. Теперь мне нужно добавить 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 // передать класс данных

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

// main Python function

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? Я посылаю его как uchar* из c ++, и, как я понимаю, мне нужно, чтобы он был массивом numpy, чтобы получить cv2.Mat в Python. Какой правильный подход здесь к go от 'width, height, channel, frameData' к opencv python cv2.Mat?

Я использую разделяемую память, потому что скорость - это фактор, я проверил используя Python API подход, и он слишком медленный для моих нужд.

1 Ответ

2 голосов
/ 31 марта 2020

Общая идея (как используется в привязках OpenCV Python) - создать numpy ndarray, который совместно использует свой буфер данных с объектом Mat, и передать его функции Python.

Примечание: на данный момент я ограничу пример только непрерывными матрицами.

Мы можем воспользоваться классом pybind11::array.

  • Нам нужно определить соответствующий dtype для использования массива numpy. Это простое отображение 1-к-1, которое мы можем сделать с помощью 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, давайте отобразим одноканальные массивы 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. Мы можем создать pybind11::capsule вокруг мелкой копии источника Mat - благодаря способу реализации объекта это эффективно увеличивает его счетчик ссылок на необходимое количество времени.

    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. Затем, чтобы вызвать эту функцию из C ++, используя Mat в качестве источника, мы сделаем что-то вроде этого:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...