C++使用Py*調用Python3模塊中類成員函數及數組參數傳遞

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賦值的,這也是我寫這篇文章的原因所在,希望給其他需要的人一些參考,少走彎路。***

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容