C++中将NumPy数组转换为Mat(OpenCV)

4
我正在编写一个ArUco增强现实库的轻量级封装(基于OpenCV)。我试图构建的接口非常简单:
  • Python将图像传递给C++代码;
  • C++代码检测标记并将它们的位置和其他信息作为字典元组返回给Python。
然而,我无法弄清楚如何在Python中表示图像以便传递给C++。对于GUI和相机管理,我将使用PyQt,因此最初它将是QImage,但我不能简单地将其传递给OpenCV吗?起初,我尝试使用嵌套元组来表示每个像素的行、列和颜色,所以我最终得到了这个样例代码:
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);
}

结果发现运行速度非常缓慢(每帧需要几秒钟),于是我进行了谷歌搜索并找到了一种更快的解决方案:使用NumPy数组,这样代码将会变成下面这个样子:

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

然而,我不知道如何将NumPy数组(在C++级别上只是Python对象)转换为OpenCV Mat。希望能得到任何帮助。

另外,也许不需要使用NumPy,可以直接将QImage Python对象传递给C++层?或者可能有不同的解决方法吗?感谢您的任何建议!


4个回答

5
在您的情况下,最好的解决方案是使用自定义boost :: python转换器来处理cv :: Mat对象。OpenCV具有Python包装器,当您使用此包装器时,您将操作Numpy数组 - 您甚至不需要知道这些数组在“跨越c ++ <-> python边界”时被转换为cv :: Mat对象。编写简单类型的转换器非常容易,但创建cv :: Mat的转换器并不简单。幸运的是,其他人已经做到了这一点 - 这里是OpenCV 2.x版本的链接,3.x版本的链接。如果您不熟悉boost :: python转换器,则本文应该会对您有所帮助。
希望能够帮到您,如果您有任何问题,请告诉我们。

非常感谢,看起来正是我所寻找的! - kreo

3

我为那些不知道有Boost Numpy模块的人编写了这个示例。您可以看到如何将Mat转换为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;
}

2
这是afewthings/DomQ回答的pybind11版本。我发现对于我的项目来说,pybind11比boost::python更好(两个库都非常好)。
// convert a cv::Mat to an np.array
py::array to_array(const cv::Mat& im) {
    const ssize_t channels = im.channels();
    const ssize_t height = im.rows;
    const ssize_t width = im.cols;
    const ssize_t dim = sizeof(uchar) * height * width * channels;
    auto data = new uchar[dim];
    std::copy(im.data, im.data + dim, data);
    return py::array_t<uchar>(
        py::buffer_info(
            data,
            sizeof(uchar), //itemsize
            py::format_descriptor<uchar>::format(),
            channels, // ndim
            std::vector<ssize_t> { height, width, channels }, // shape
            std::vector<ssize_t> { width * channels, channels, sizeof(uchar) } // strides
        ),
        py::capsule(data, [](void* f){
            // handle releasing data
            delete[] reinterpret_cast<uchar*>(f);
        })
    );
}


// convert an np.array to a cv::Mat
cv::Mat from_array(const py::array& ar) {
    if (!ar.dtype().is(py::dtype::of<uchar>())) {
        std::cout << "ERROR unsupported dtype!" << std::endl;
        return cv::Mat();
    }

    auto shape = ar.shape();
    int rows = shape[0];
    int cols = shape[1];
    int channels = shape[2];
    int type = CV_MAKETYPE(CV_8U, channels); // CV_8UC3
    cv::Mat mat = cv::Mat(rows, cols, type);
    memcpy(mat.data, ar.data(), sizeof(uchar) * rows * cols * channels);

    return mat;
}

2

如果您不喜欢使用包装器,并希望使用本地的Python扩展模块,则可以按照以下方式操作。

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++:

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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接