Google Code Prettify

2017年9月2日 星期六

[C++] Pass an OpenCV Mat Image to a Python Function

  最近因工作需要而研究了一陣子 C++ 與 Python 的互動,主要達成以下幾件事:
  1. 從 C++ 端呼叫 Python 的一個 Function。
  2. 將從 C++ 用 OpenCV 讀進來的 Mat 資料結構轉換成 Numpy Array。
  3. 把 Numpy Array 當成參數傳進 Python Function。




Reference


廢話不多說,直接上 Code。


#ifdef _DEBUG
#undef _DEBUG
#include <Python.h>
#define _DEBUG
#else
#include <Python.h>
#endif
#include <iostream>
#include <numpy/arrayobject.h>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>

  • Python.h - 安裝Python時會附帶的 C/C++ Header file,位於 Python3X/include。另外要記得 link 對應的 python3x.lib,位於 Python3X/libs。而由於使用的 .lib 是 Release 版,想在 Debug 版使用則需要如上的寫法。
  • numpy/arrayobject.h - 由於我們要使用 Numpy 的 C/C++ API 來將 cv::Mat 轉換成 numpy array,因此需要 include 該 header,位於 Python3X/Lib/site-packages/numpy/core/include。


int
main(int argc, char *argv[])
{
    PyObject *pName = nullptr, *pModule = nullptr, *pFunc = nullptr;
    PyObject *pArgs = nullptr, *pReturn = nullptr, *pString = nullptr;

    if (argc < 3) {
        fprintf(stderr, "Usage: call pythonfile funcname [args]\n");
        return EXIT_FAILURE;
    }

    wchar_t *wcsProgram = Py_DecodeLocale(argv[0], NULL);
    Py_SetProgramName(wcsProgram);
    Py_Initialize();
    pName = PyUnicode_DecodeFSDefault(argv[1]);
    if (PyErr_Occurred()) {
        std::cerr << "pName decoding failed." << std::endl;
        return EXIT_FAILURE;
    }

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, argv[2]);
        /* pFunc is a new reference */

上例中,假設存在一 .py 檔名為 argv[1],內含一 function 名為 argv[2],於是我們將喚醒一個 Python Interpreter,令其幫我們 import 該 .py,並抓取該 function。


        if (pFunc && PyCallable_Check(pFunc)) {

            cv::Mat img = cv::imread(argv[4], cv::IMREAD_COLOR), img_rgb;
            cv::cvtColor(img, img_rgb, cv::COLOR_BGR2RGB);

            // Build the array in C++
            const int ND = 2;
            npy_intp dims[2]{ img_rgb.rows, img_rgb.cols * img_rgb.channels() };

            // Convert it to a NumPy array.
            import_array();
            PyObject *pArray = PyArray_SimpleNewFromData(
                ND, dims, NPY_UBYTE, reinterpret_cast<void*>(img_rgb.data));
            if (!pArray) {
                std::cerr << "PyArray_SimpleNewFromData failed." << std::endl;
                return EXIT_FAILURE;
            }

            PyObject *pValue = PyLong_FromLong(img_rgb.channels());
            pString = PyUnicode_FromString(argv[3]);
            pReturn = PyObject_CallFunctionObjArgs(pFunc, pString, pArray, pValue, NULL);
            if (pReturn != NULL) {
                fprintf(stdout, "Result of call: %ld\n", PyLong_AsLong(pReturn));
                Py_DECREF(pReturn);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr, "Call failed\n");
                return EXIT_FAILURE;
            }
        }

重點部分來了。要將 Image 傳入 Python function 使用,我們透過 Numpy 提供的 C API "PyArray_SimpleNewFromData()" 將 cv::Mat 轉換成 numpy array。
要注意的是,OpenCV中針對彩圖所使用的資料結構為 2 維的 BGR array,每個 pixel 預設使用 CV_8UC3 型別,其在 memory 中以

[B G R B G R . . . . . .]
[. . . . . . . . . . . .]
[. . . . . . B G R B G R]
的方式儲存,因此我們轉換的目標是 2 維的 numpy array,寬為 cols * channels,高為 rows,型別為 NPY_UBYTE。(前面有先 BGR2RGB)
另外,在使用 Numpy API 之前,絕對一定必然要先呼叫 import_array(),否則程式永遠都會執行失敗!

最後,就可以 PyObject_CallFunctionObjArgs() 去呼叫 Python function 並傳入需要的參數了。


        }
        else {
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    }
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
        return EXIT_FAILURE;
    }

    Py_Finalize();
    return EXIT_SUCCESS;
}

毫無反應,就一些例外處理(並不完善)。但最後的 Py_Finalize() 是必要的,對應開頭的 Py_Initialize()。

2 則留言:

  1. PyObject *pArray = PyArray_SimpleNewFromData(
    ND, dims, NPY_UBYTE, reinterpret_cast(img_rgb.data));
    您好,運行這一行有報錯,可以幫忙解答嗎?

    回覆刪除
    回覆
    1. 您好,請問可有錯誤訊息且有先大概google過了嗎?

      不然我沒有水晶球可能沒辦法解答..

      刪除