- 從 C++ 端呼叫 Python 的一個 Function。
- 將從 C++ 用 OpenCV 讀進來的 Mat 資料結構轉換成 Numpy Array。
- 把 Numpy Array 當成參數傳進 Python Function。
Reference
- Embedding Python in Another Application — Python 3.5.4 documentation
- Passing an image from C++ to Python 3.4 - Stack Overflow
- Sending a C++ array to Python/NumPy and back - Code Review Stack Exchange
廢話不多說,直接上 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()。
PyObject *pArray = PyArray_SimpleNewFromData(
回覆刪除ND, dims, NPY_UBYTE, reinterpret_cast(img_rgb.data));
您好,運行這一行有報錯,可以幫忙解答嗎?
您好,請問可有錯誤訊息且有先大概google過了嗎?
刪除不然我沒有水晶球可能沒辦法解答..