简体   繁体   English

从NumPy数组到Mat(OpenCV)的C ++转换

[英]C++ conversion from NumPy array to Mat (OpenCV)

I am writing a thin wrapper around ArUco augmented reality library (which is based on OpenCV). 我正在围绕ArUco增强现实库(基于OpenCV)编写一个薄的包装器。 An interface I am trying to build is very simple: 我想要构建的接口非常简单:

  • Python passes image to C++ code; Python将图像传递给C ++代码;
  • C++ code detects markers and returns their locations and other info to Python as tuple of dicts. C ++代码检测标记并将其位置和其他信息作为dicts元组返回给Python。

However, I couldn't figure out how to represent an image in Python to pass it to C++. 但是,我无法弄清楚如何在Python中表示图像以将其传递给C ++。 For GUI and camera management I am going to use PyQt, so initially it is going to be QImage, but I can't simply pass it to OpenCV (or I can?). 对于GUI和相机管理,我将使用PyQt,所以最初它将是QImage,但我不能简单地将它传递给OpenCV(或者我可以?)。 At first, I tried to use nested tuples to represent row, column and color of each pixel, so I ended up with this sample code: 起初,我尝试使用嵌套元组来表示每个像素的行,列和颜色,所以我最终得到了这个示例代码:

using namespace cv;
namespace py = boost::python;

void display(py::tuple pix)
{
    /*
        Receive image from Python and display it.
    */
    Mat img(py::len(pix), py::len(pix[0]), CV_8UC3, Scalar(0, 0, 255));
    for (int y = 0; y < py::len(pix); y++)
        for (int x = 0; x < py::len(pix[y]); x++)
        {
            Vec3b rgb;
            for (int i = 0; i < 3; i++)
                rgb[i] = py::extract<int>(pix[y][x][i]);
            img.at<Vec3b>(Point(x, y)) = rgb;
        }
    imshow("Image", img);
    waitKey(0);
}

BOOST_PYTHON_MODULE(aruco)
{
    py::def("display", display);
}

It turned out to be painfully slow (a few seconds for a single frame), so I went googling and found solution that should be much faster: use NumPy arrays, so the code would look something like that: 结果是痛苦的慢(单帧几秒钟),所以我去google搜索并找到了应该更快的解决方案:使用NumPy数组,所以代码看起来像这样:

void display(py::object array)
{
    Mat img;
    // ... some magic here to convert NumPy array to Mat ...
    imshow("Image", img);
    waitKey(0);
}

However, I have no idea how to convert NumPy Array (which in C++ level is just a Python Object) to OpenCV Mat. 但是,我不知道如何将NumPy数组(在C ++级别只是一个Python对象)转换为OpenCV Mat。 I would appreciate any help here. 我很感激这里的任何帮助。

Alternatively, maybe NumPy is not really needed, so I could just pass QImage Python object directly to C++ layer? 或者,也许真的不需要NumPy,所以我可以直接将QImage Python对象传递给C ++层? Or maybe there is a different approach to this problem? 或者可能有不同的方法解决这个问题? Any advice is appreciated! 任何建议表示赞赏!

The best solution in your situation is using custom boost::python converter for cv::Mat object. 您遇到的最佳解决方案是为cv :: Mat对象使用自定义boost :: python转换器。 OpenCV has Python wrapper and when you are using this wrapper you are operating on Numpy arrays - you don't even need to know that those arrays are converted to cv::Mat objects while "crossing the c++ <-> python border". OpenCV有Python包装器,当你使用这个包装器时,你在Numpy数组上运行 - 你甚至不需要知道那些数组在“穿越c ++ < - > python border”时被转换为cv :: Mat对象。 Writing such converter for simple type is quite easy, however creating converter for cv::Mat isn't simple. 为简单类型编写这样的转换器非常容易,但是为cv :: Mat创建转换器并不简单。 Fortunetely someone else already did this - here is version for OpenCV 2.x and here for 3.x. 幸运的是其他人已经这样做了 - 这里是OpenCV 2.x的版本, 这里是3.x. If you are not familiar with boost::python converters, this article should help you. 如果您不熟悉boost :: python转换器, 本文应该对您有所帮助。
Hope it helps, if you wil have any problems, let us know. 希望它有所帮助,如果您有任何问题,请告诉我们。

Optionally, if you don't like to use wrappers, and want to use native python extension module, you can do it like this. 或者,如果您不喜欢使用包装器,并且想要使用本机python扩展模块,则可以这样做。

python3: python3:

my_image = cv.imread("my_image.jpg", 1)  # reads colorfull image in python
dims = my_image.shape  # get image shape (h, w, c)
my_image = my_image.ravel()  # flattens 3d array into 1d
cppextenionmodule.np_to_mat(dims, my_image)

c++: C ++:

static PyObject *np_to_mat(PyObject *self, PyObject *args){
    PyObject *size;
    PyArrayObject *image;

    if (!PyArg_ParseTuple(args, "O!O!", &PyTuple_Type, &size, &PyArray_Type, &image)) {
        return NULL;
    }
    int rows = PyLong_AsLong(PyTuple_GetItem(size ,0));
    int cols = PyLong_AsLong(PyTuple_GetItem(size ,1));
    int nchannels = PyLong_AsLong(PyTuple_GetItem(size ,2));
    char my_arr[rows * nchannels * cols];

    for(size_t length = 0; length<(rows * nchannels * cols); length++){
        my_arr[length] = (*(char *)PyArray_GETPTR1(image, length));
    }

    cv::Mat my_img = cv::Mat(cv::Size(cols, rows), CV_8UC3, &my_arr);

    ... whatever with the image
}

I wrote this example for who didn't know there is Boost Numpy module. 我写了这个例子,谁不知道有Boost Numpy模块。 You can see how to convert Mat to NDArray and vice versa. 您可以看到如何将Mat转换为NDArray,反之亦然。 it will gives you idea the way of convert ndarray. 它会让你了解转换ndarray的方式。

#define BOOST_PYTHON_STATIC_LIB
#define BOOST_LIB_NAME "boost_numpy35"
//#include <boost/config/auto_link.hpp>
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
#include <iostream>
#include <opencv2/opencv.hpp>

namespace py = boost::python;
namespace np = boost::python::numpy;

void Init() {
    // set your python location.
    wchar_t str[] = L"D:\\Anaconda3\\envs\\tensorflow_vision";

    Py_SetPythonHome(str);

    Py_Initialize();
    np::initialize();
}

np::ndarray ConvertMatToNDArray(const cv::Mat& mat) {
    py::tuple shape = py::make_tuple(mat.rows, mat.cols, mat.channels());
    py::tuple stride = py::make_tuple(mat.channels() * mat.cols * sizeof(uchar), mat.channels() * sizeof(uchar), sizeof(uchar));
    np::dtype dt = np::dtype::get_builtin<uchar>();
    np::ndarray ndImg = np::from_data(mat.data, dt, shape, stride, py::object());

    return ndImg;
}

cv::Mat ConvertNDArrayToMat(const np::ndarray& ndarr) {
    //int length = ndarr.get_nd(); // get_nd() returns num of dimensions. this is used as a length, but we don't need to use in this case. because we know that image has 3 dimensions.
    const Py_intptr_t* shape = ndarr.get_shape(); // get_shape() returns Py_intptr_t* which we can get the size of n-th dimension of the ndarray.
    char* dtype_str = py::extract<char *>(py::str(ndarr.get_dtype()));

    // variables for creating Mat object
    int rows = shape[0];
    int cols = shape[1];
    int channel = shape[2];
    int depth;

    // you should find proper type for c++. in this case we use 'CV_8UC3' image, so we need to create 'uchar' type Mat.
    if (!strcmp(dtype_str, "uint8")) {
        depth = CV_8U;
    }
    else {
        std::cout << "wrong dtype error" << std::endl;
        return cv::Mat();
    }

    int type = CV_MAKETYPE(depth, channel); // CV_8UC3

    cv::Mat mat = cv::Mat(rows, cols, type);
    memcpy(mat.data, ndarr.get_data(), sizeof(uchar) * rows * cols * channel);

    return mat;
}

int main()
{
    using namespace std;

    try
    {
        // initialize boost python and numpy
        Init();

        // import module
        py::object main_module = py::import("__main__");
        py::object print = main_module.attr("__builtins__").attr("print"); // this is for printing python object

        // get image
        cv::Mat img;
        img = cv::imread("Lenna.jpg", cv::IMREAD_COLOR);
        if (img.empty())
        {
            std::cout << "can't getting image" << std::endl;
            return -1;
        }

        // convert Mat to NDArray
        cv::Mat cloneImg = img.clone(); // converting functions will access to same data between Mat and NDArray. so we should clone Mat object. This may important in your case.
        np::ndarray ndImg = ConvertMatToNDArray(cloneImg);

        // You can check if it's properly converted.
        //print(ndImg);

        // convert NDArray to Mat
        cv::Mat matImg = ConvertNDArrayToMat(ndImg); // also you can convert ndarray to mat.

        // add 10 brightness to converted image
        for (int i = 0; i < matImg.rows; i++) {
            for (int j = 0; j < matImg.cols; j++) {
                for (int c = 0; c < matImg.channels(); c++) {
                    matImg.at<cv::Vec3b>(i, j)[c] += 10;
                }
            }
        }

        // show image
        cv::imshow("original image", img);
        cv::imshow("converted image", matImg);
        cv::waitKey(0);
        cv::destroyAllWindows();
    }
    catch (py::error_already_set&)
    {
        PyErr_Print();
        system("pause");
    }

    system("pause");
    return 0;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM