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阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容