1.首先來看Python模塊的部分結構和代碼。ssd_network_classify.py文件中有SSD_Network_Classify類及其識別的成員函數detect_image(),返回值是一個1維的不定長double型數組。
class SSD_Network_Classify:
#其他函數實現省略。。。
def detect_image(self, img_raw=None):
'''
獲取圖片數據進行檢測。
:param img_raw: 從c++傳來的一維480*640*3大小的int型圖片數據(包含負值)
:return: 返回的是一維數組
'''
#將原始圖片數據shape成480*640*3的uint8類型數據。
img_data = np.reshape(img_raw, (480, 640, 3)).astype(np.uint8)
#以下兩行輸出用于顯示傳參的值幫助理解。
print(img_data.shape)
print(img_data)
#進行目標檢測并返回檢測結果。
rclasses, rscores, rbboxes = self.detect(img_data)
#顯示檢測結果
cv2.imshow('DetectImage', img_data)
cv2.waitKey(1)
#以下將返回的結果拼接成一維的數組返回給調用該函數的c程序中。
rclasses = np.reshape(rclasses, (-1, 1))
rscores = np.reshape(rscores, (-1, 1))
result = np.concatenate([rclasses, rscores, rbboxes], axis=1)
result = np.squeeze(np.reshape(result, (1, -1)))
#print(result)
return result
在Python中輸出傳入的參數(從c++傳送過來)示例如下:
data.png
2. C++端獲取攝像頭數據的類CameraBase:
//以下是.h文件中的成員變量
//cv::VideoCapture *capture;
//cv::Mat bgr_image;
//int device_num;
//以下是.cpp中主要成員函數的實現
"構造函數,打開攝像頭"
CameraBase::CameraBase(int dev_num) {
this->device_num = dev_num;
this->capture = new cv::VideoCapture(this->device_num);
if (!capture->isOpened()) {
std::cout << "camera open failed\n";
}
}
"讀取網絡攝像頭的圖片數據"
void CameraBase::grabImages() {
this->capture->read(this->bgr_image);
}
"圖片的Mat數據轉換成char類型的數組數據"
char *CameraBase::bgrImageMatToArray( char *img_arr) {
size_t img_size = this->bgr_image.total() * this->bgr_image.elemSize() ;
std::memcpy(img_arr, this->bgr_image.data, img_size * sizeof(char));
return img_arr;
}
3.核心部分。main.cpp中c++調用Python的函數callPythonForDetect。這塊代碼是c++調用Python的核心代碼,具體代碼解釋已經在代碼注釋中寫得很清楚了。
int callPythonForDetect() {
//調用web攝像頭進行處理
CameraBase *camera = new CameraBase();
//------------------以下是調用Python模塊的代碼------------------//
//python環境初始化
Py_Initialize();
if (!Py_IsInitialized())
return -1;
//導入系統包用于擴展需要加載的Python模塊的路徑,否則即使Python模塊在當前目錄也無法加載
PyRun_SimpleString("import sys \nsys.argv = ['']");
//加載Python模塊的路徑
PyRun_SimpleString("sys.path.append('/absolute/path/to/python/module')");
//導入需要調用的模塊
PyObject *pyModule = PyImport_ImportModule("ssd_network_classify");
if (!pyModule) {
printf("Can not open python module\n");
return -1;
}
//獲取python模塊中的類名并創建對象實例
PyObject *pyClass = PyObject_GetAttrString(pyModule, "SSD_Network_Classify");
PyObject *pyClassInstance = PyObject_CallObject(pyClass, NULL);
//獲取Python模塊中相應的函數名
PyObject *pyFunc = PyObject_GetAttrString(pyClass, "detect_image");
//聲明或定義變量
npy_intp IMGSHAPE[1] = {480 * 640 * 3};//圖片數據的shape參數值
char *img_data = new char[IMGSHAPE[0]];//從攝像頭中獲取的圖片數據保存的變量
PyByteArrayObject *pyIMgArr;//image數組的Python對象
//設置發送給Python函數的參數對象
PyObject *pyArgs = PyTuple_New(2);
while (true) {
camera->grabImages();//獲取攝像頭數據
//獲取image數據并保存至img_data數組中
camera->bgrImageMatToArray(img_data);
//必須添加如下函數,否則無法執行PyArray_SimpleNewFromData
import_array ();
//將c的img數組數據轉換成pyobject類型的數組數據
pyIMgArr = reinterpret_cast<PyByteArrayObject *>
(PyArray_SimpleNewFromData(1, IMGSHAPE, NPY_BYTE, reinterpret_cast<void *>(img_data)));
//設置調用函數的self值為前面該類創建的實例,否則無法使用self變量進行調用而出錯
PyTuple_SetItem(pyArgs, 0, Py_BuildValue("O", pyClassInstance));
//設置變量的第二個參數值為byte類型的數組作為圖片數據
PyTuple_SetItem(pyArgs, 1, reinterpret_cast<PyObject *>(pyIMgArr));
//調用python函數進行識別任務并返回相應的結果
PyObject *pyResult = PyObject_CallObject(pyFunc, pyArgs);
//以下是對返回的一維數組結果進行處理
if (pyResult) {
//將結果類型轉換成數組對象類型
PyArrayObject *pyResultArr = (PyArrayObject *) pyResult;
//也可以使用以下兩行代碼來代替上面的類型轉換。
//PyArray_Descr *descr = PyArray_DescrFromType(NPY_DOUBLE);
//PyArrayObject *pyResultArr = (PyArrayObject*)PyArray_FromAny(pyResult, descr,1,1,NPY_ARRAY_C_CONTIGUOUS,NULL);
//從Python中的PyArrayObject解析出數組數據為c的double類型。
double *resDataArr = (double *) PyArray_DATA(pyResultArr);
int dimNum = PyArray_NDIM(pyResultArr);//返回數組的維度數,此處恒為1
npy_intp *pdim = PyArray_DIMS(pyResultArr);//返回數組各維度上的元素個數值
//以下是對返回結果的輸出顯示
for (int i = 0; i < dimNum; ++i) {
for (int j = 0; j < pdim[0]; ++j)
cout << resDataArr[i * pdim[0] + j] << ",";
}
cout << endl;
}
}
//釋放Python環境
Py_Finalize();
}
從調用的Python函數中返回的數組結果示例如下:
dataout.png
以下是最終C++通過調用Python版本實現的目標識別網絡展示的結果:
result.png
總結:
*** 使用C++調用python3模塊接口的示例基本沒有,有的大部分都是python2版本的示例,而新版本的很多函數名稱和用法改變都很大,導致我在寫這塊代碼的時候碰到很多問題,就這個簡單需求花了我整整3天的時間,期間有考慮使用第三方框架進行解決,但是發現也很麻煩。其中需要注意的是,因為Python的類函數的第一個參數是self,并且是傳入對象本身,因此在c中調用的時候也要考慮為其賦值(PyTuple_SetItem(pyArgs, 0, Py_BuildValue("O", pyClassInstance)); 這行代碼很重要),否則會出錯。很多例子都考慮的是調用非類成員函數,因此不需要考慮self變量而比較容易。我是自己嘗試很久并經過調試才知道該怎么給self賦值的,這也是我寫這篇文章的原因所在,希望給其他需要的人一些參考,少走彎路。***