C++调用Python

  • C++调用Python
    • 官方文档
    • 准备工作
      • 环境搭建
    • 调用方法
      • 包含头文件以及链接到库
      • 直接调用简单语句
      • 简单函数调用
      • Mat数据转PyObject
      • C++中解析python脚本返回的元组数据
  • 参考文档

C++调用Python

在毕业设计中需要用到一个深度学习网络的已有算法,但是整个框架是用C++写的,所以需要用C++调用Python,整个过程持续了一周,难受~ 但也学到了不少,在这里记录一下!
文中可能会有一些错误,如果又发现的,麻烦留言,我及时更正,非常感谢!

官方文档

  • Python/C API Reference Manual
  • NumPy C-API
  • Embedding Python in Another Application

准备工作

环境搭建

  • 本机环境:
    • Ubuntu18.04
    • Anaconda
  • 虚拟环境:
    • Python 3.6
    • numpy 1.17

调用方法

包含头文件以及链接到库

#include<Python.h>
set(PYTHON_INCLUDE_DIRS "/home/jia/Software/anaconda3/envs/Test/include/python3.6m/")
INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIRS})
link_directories(/home/jia/Software/anaconda3/envs/Test/lib/python3.6/config-3.6m-x86_64-linux-gnu)
set(PYTHON_LIBRARIES "/home/jia/Software/anaconda3/envs/Test/lib/libpython3.6m.so")
add_executable(test main.cpp)
target_link_libraries(test ${PYTHON_LIBRARIES

直接调用简单语句

#include "Python.h"

int main()
{
    Py_Initialize();    ## 初始化

    PyRun_SimpleString("print 'hello'");

    Py_Finalize();      ## 释放资源
}

简单函数调用

  • C++代码
#include <iostream>
#include <string>
#include <Python.h>
using namespace std;
int main()
{
    /*****************************************
	*  Python初始化及路径加载
	******************************************/
	cout << "---------------------初始化Python--------------------" << endl;
    Py_Initialize();    // 使用Python系统前,必须使用Py_Initialize对其进行初始化
    if ( !Py_IsInitialized() )  //检查初始化是否成功
    { 
        return -1;
    }
	cout << "---------------------添加路径--------------------" << endl;
	 // PyRun_SimpleString:这个函数就是执行一条简单的python代码
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("import os");
    //建议用os先获得绝对路径。再将路径加载到python中,不然路径设置的不一定对
    //我这里是获得了编译好的可执行文件的上一级路径,也就是工程根目录,这里只要能够讲python脚本的路径加载正确即可
    PyRun_SimpleString("sys.path.append(os.path.abspath(os.path.join(os.getcwd(), \"..\"))+'/python/')");
     //这里我将路径打印出来,可以查看python脚本的路径是否加载进去了,打印出来的路径有很多,仔细查看
    PyRun_SimpleString("print(sys.path)");

    /*****************************************
	*  Python脚本获取以及脚本内的函数获取
	******************************************/
	cout << "---------------------脚本获取--------------------" << endl;
    PyObject *pName,*pModule,*pDict,*pFunc,*pArgs;
    pName = PyUnicode_FromString("mypythonfile");  //python3中用这个
    pModule = PyImport_Import(pName); 
    if ( !pModule ) {
        printf("can't find mypythonfile.py");
        getchar();
        return -1;
    }
    pDict = PyModule_GetDict(pModule);
    if ( !pDict ) {
        return -1;
    }
    cout << "---------------------函数获取--------------------" << endl;
    // 找出函数名为display的函数
    pFunc = PyDict_GetItemString(pDict, "display");
    if ( !pFunc || !PyCallable_Check(pFunc) ) {
        printf("can't find function [display]");
        getchar();
        return -1;
    }
    
     /*****************************************
	*  构造参数对象
	******************************************/
    cout << "---------------------构造参数对象--------------------" << endl;
    //将参数传进去。1代表一个参数。
    pArgs = PyTuple_New(1);
    //  PyObject* Py_BuildValue(char *format, ...)
    //  把C++的变量转换成一个Python对象。当需要从
    //  C++传递变量到Python时,就会使用这个函数。此函数
    //  有点类似C的printf,但格式不同。常用的格式有
    //  s 表示字符串,
    //  i 表示整型变量,
    //  f 表示浮点数,
    //  O 表示一个Python对象。
    //这里我要传的是字符串所以用s,注意字符串需要双引号!
    PyTuple_SetItem(pArgs, 0, Py_BuildValue("s"," python in C++"));

	/*
	这里展示多个参数:
	pArgs = PyTuple_New(3);
	PyTuple_SetItem(pArgs, 0, Py_BuildValue("s"," 第一个参数"));
	PyTuple_SetItem(pArgs, 1, Py_BuildValue("s"," 第二个参数"));
	PyTuple_SetItem(pArgs, 2, Py_BuildValue("s"," 第三个参数"));
	如果已经有PyObject*的数据对象,那么可以:
	假设有三个图像PyObject*数据对象
	PyObject* img1,img2,img3;
	...
	pArgs = PyTuple_New(3);
	PyTuple_SetItem(pArgs, 0, img1);
	PyTuple_SetItem(pArgs, 1, img2);
	PyTuple_SetItem(pArgs, 2, img3);
	*/
     /*****************************************
	* 调用python脚本中的函数
	******************************************/
    // 调用Python函数
    PyObject_CallObject(pFunc, pArgs);
    
    /*****************************************
	* 脚本执行完之后,需要关闭python
	******************************************/
    // 关闭Python
	Py_Finalize();
	return 0;
}
  • Python代码
#-* -coding: UTF-8 -* -
def display(name):
    print ("hi",name)

class test:
    def say(self):
        print ("hello")
  • CMakeLisits.txt
cmake_minimum_required(VERSION 2.8)
project(test)
set(CMAKE_CXX_STANDARD 14)
set(PYTHON_INCLUDE_DIRS "/home/jia/Software/anaconda3/envs/Test/include/python3.6m/")
INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIRS})
link_directories(/home/jia/Software/anaconda3/envs/Test/lib/python3.6/config-3.6m-x86_64-linux-gnu)
set(PYTHON_LIBRARIES "/home/jia/Software/anaconda3/envs/Test/lib/libpython3.6m.so")
add_executable(test main.cpp)
target_link_libraries(test ${PYTHON_LIBRARIES})
  • 编译运行
mkdir build
cd build
./test
  • 错误解决(1)
$ ./test 
Could not find platform independent libraries <prefix>
Could not find platform dependent libraries <exec_prefix>
Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]
Fatal Python error: Py_Initialize: Unable to get the locale encoding
ModuleNotFoundError: No module named 'encodings'

Current thread 0x00007f7618918740 (most recent call first):
已放弃 (核心已转储)

解决方法:
因为用的是anaconda环境中的python,所以在程序执行的之前,需要先激活ananconda的相应环境,这样才能够成功运行。

conda activate Test
./test

Mat数据转PyObject

我没找到有什么快速的办法实现,只能用遍历的笨办法。

  • opencv Mat转PyObject
	Mat img=imread("1.png");
	int row = img.rows;
    int col = img.cols;
    int channel = img.channels();
    auto *imgData = new uchar[row*col*channel];

    int irow = row,icol = col*channel;
    //判断图像的Mat数据是不是一行保存的
    if (!img.isContinuous())
    {
        icol *= irow;
        irow = 1;
    }
    int id = 0;
    for(int i = 0; i<irow; i++)
    {
        for(int j = 0; j<icol; j++)
        {
            imgData[id] = img.at<uchar>(i,j);
            id++;
        }
    }
    npy_intp Dims[3] = { row, col, channel}; //注意这个维度数据!
    //这里相当于reshape了,构建了numpy可接受的PyObject对象
    PyObject *PyImg = PyArray_SimpleNewFromData(3, Dims, NPY_UBYTE, imgData);

C++中解析python脚本返回的元组数据

C++中解析python脚本返回的元组数据,转换为了vector
这里我的项目中需要vector数据,其实解析出来之后你想转成什么数据都可以

  • python代码:

    def test:
    	array1 = np.random.rand(4,3)
    	array2 = np.random.rand(3,2)
    	array3 = np.random.rand(7,8)
    	return array1,array2,array3
    
  • C++代码:

    /*
    *  其中的PyArray是numpy的对象,如果需要查看函数,需要在numpy C API Reference中查看,
    *  其他在Python C API Reference 中 
    */
    // 调用Python函数
    PyObject* Py_result = PyObject_CallObject(pFunc, pArgs);
    //判断返回是否为空
    if(Py_result == NULL)
    {
        Py_Finalize();
        return false;
    }
    PyArrayObject *Py_array1,*Py_array2,*Py_array3;
    vector<vector<double>> array1;
    vector<vector<double>> array2;
    vector<vector<double>> array3;
    vector<float> thisData;
    //查看是否是元组数据
    if(PyTuple_Check(Py_result))
    {
    	//将元组数据解开
    	//这里的参数先留一个坑,以后填,如果着急,可以自行对阅读Python C Reference,文章开头有链接
    	//如果只返回一个矩阵,则不需要这一句
        PyArg_UnpackTuple(Py_result, "ref", 3, 3, &Py_array1, &Py_array2, &Py_array3);
        //获取矩阵维度
        npy_intp *Py_array1_shape = PyArray_DIMS(Py_pts1);
        npy_intp *Py_array2_shape = PyArray_DIMS(Py_pts2);
        npy_intp *Py_array3_shape = PyArray_DIMS(Py_matches);
        int array1row = Py_array1_shape[0];
        int array1col   = Py_array1_shape[1];
        int array2row = Py_array2_shape[0];
        int array2col   = Py_array2_shape[1];
         int array3row = Py_array3_shape[0];
        int array3col   = Py_array3_shape[1];
    
        vector<double> temp;
        double thisdata;
        //array1
        for(npy_intp row = 0; row < (npy_intp)array1row; row++)
        {
        	temp.clear();
        	for(npy_intp col = 0; col < (npy_intp)array1col; col++)
        	{
    			thisdata = *(double*)PyArray_GETPTR2(Py_array1,row,col);
    			temp.push_back(thisData);
    		}
    		array1.push_back(temp);
        }
    	//array2
        for(npy_intp row = 0; row < (npy_intp)array2row; row++)
        {
        	temp.clear();
        	for(npy_intp col = 0; col < (npy_intp)array2col; col++)
        	{
    			thisdata = *(double*)PyArray_GETPTR2(Py_array2,row,col);
    			temp.push_back(thisData);
    		}
    		array2.push_back(temp);
        }
        //array3
        for(npy_intp row = 0; row < (npy_intp)array3row; row++)
        {
        	temp.clear();
        	for(npy_intp col = 0; col < (npy_intp)array3col; col++)
        	{
    			thisdata = *(double*)PyArray_GETPTR2(Py_array3,row,col);
    			temp.push_back(thisData);
    		}
    		array3.push_back(temp);
        }
    	//至此,python返回的array数据解析完成
    }
    

参考文档

非常感谢各位博主的博文,受益匪浅!

  • 浅析 C++ 调用 Python 模块
  • C++调用Python
  • c++调用python numpy编程
  • c++向python传输图片 高效方法 mat转numpy
  • ubuntu下C++如何调用python程序,gdb调试C++代码
  • Python3执行PyImport_Import()一直返回NULL—路径设置问题
  • C++调用Python浅析
  • C++ 调用 Python3.6中的各种坑

更多推荐

C++调用python,并且在python和C++之间传输数据(numpy和mat数据)