C++调用python(二)


一、基本使用方法
二、调用简单语句
三、调用函数
四、调用类
五、调用SSD目标检测算法
六、遇到的错误


三、调用函数

3.1 无参

-CMakeLists.txt

cmake_minimum_required(VERSION 3.9)

project(say_hello)

set(SDK_VERSION 0_0_1)
# >>> build type 
set(CMAKE_BUILD_TYPE "Release")				# 指定生成的版本
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
# <<<


# >>> CXX11 
set(CMAKE_CXX_STANDARD 11)				# C++ 11 编译器
SET(CMAKE_CXX_STANDARD_REQUIRED TRUE)
# <<< 


# >>> Python3 
set(PYTHON_ROOT "/home/zjh/anaconda3/envs/learn")
message("python root: " ${PYTHON_ROOT})
include_directories(${PYTHON_ROOT}/include/)
link_directories(${PYTHON_ROOT}/lib/)
# <<<

# --- generate ---
add_executable(say_hello hello.cpp)
target_link_libraries(say_hello -lpython3.6m)
  • hello.py
def say():
    print("hello")


if __name__ == "__main__":
    say()

  • hello.cpp
#include <python3.6m/Python.h>
#include <iostream>


int main(int argc, char *argv[])
{
    wchar_t *program = Py_DecodeLocale(argv[0], nullptr);
    if ( program == nullptr ){
        std::cout << "Fatal Error: cannot decode argv[0]!" << std::endl;
        return -1;
    }
    Py_SetProgramName(program);
    Py_SetPythonHome((wchar_t*)L"/home/zjh/anaconda3/envs/learn");
    Py_Initialize();
    if ( !Py_IsInitialized() ){
        std::cout << "Python init failed!" << std::endl;
	return 0;
    }
    
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('/media/zjh/资料/code/C++_call_python/test/test_hello')");
    
    PyObject* pModule = PyImport_ImportModule("hello");
    if ( pModule == nullptr ){
        std::cout << "module not found!" << std::endl;
        return 1;
    }
    
    PyObject* pFunc = PyObject_GetAttrString(pModule, "say");
    if ( !pFunc || !PyCallable_Check(pFunc) ){
        std::cout << "not found function add_num!" << std::endl;
        return 2;
    }
    PyObject_CallObject(pFunc, nullptr);

    if ( Py_FinalizeEx() < 0 ){
        exit(120);
    }
    PyMem_RawFree(program);
    return 0;
}

3.2 有参

  • CMakeLists.txt
    在3.1的基础上修改开始的program 和最后generate部分就行。

  • add.py

def add_num(a, b):
    print("the result {} + {} is {}".format(a, b, a+b))
    return a + b


if __name__ == "__main__":
    add_num(1, 2)

  • add.cpp
#include <python3.6m/Python.h>
#include <iostream>


int main(int argc, char *argv[])
{
    wchar_t *program = Py_DecodeLocale(argv[0], nullptr);
    if ( program == nullptr ){
        std::cout << "Fatal Error: cannot decode argv[0]!" << std::endl;
        return -1;
    }
    Py_SetProgramName(program);
    Py_Initialize();
    if ( !Py_IsInitialized() ){
        std::cout << "Python init failed!" << std::endl;
	return 0;
    }
    
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('/home/heygears/work/C++_python/test/test_add')");
    
    PyObject* pModule = PyImport_ImportModule("add");
    if ( pModule == nullptr ){
        std::cout << "module not found!" << std::endl;
        return 1;
    }
    
    PyObject* pFunc = PyObject_GetAttrString(pModule, "add_num");
    if ( !pFunc || !PyCallable_Check(pFunc) ){
        std::cout << "not found function add_num!" << std::endl;
        return 2;
    }
    
    PyObject* args = Py_BuildValue("(ii)", 28, 103);
    PyObject* pRet = PyObject_CallObject(pFunc, args);
    Py_DECREF(args);
     
    int res = 0;
    PyArg_Parse(pRet, "i", &res);
    Py_DECREF(pRet);
    std::cout << "the res is: " << res << std::endl;

    if ( Py_FinalizeEx() < 0 ){
        exit(120);
    }
    PyMem_RawFree(program);
    return 0;
}

四、调用类

  • CMakeLists.txt

  • test_class.py

class Test(object):
    def __init__(self):
        self.i = 1
        print("init!")

    def modify(self):
        self.i += 1

    def do(self):
        print(self.i)
 
if __name__ == "__main__":
    test_class = Test()
    test_class.do()
    test_class.modify()
    test_class.do()

  • testClass.cpp
#include <python3.6m/Python.h>
#include <iostream>


int main(int argc, char *argv[])
{
    wchar_t *program = Py_DecodeLocale(argv[0], nullptr);
    if ( program == nullptr ){
        std::cout << "Fatal Error: cannot decode argv[0]!" << std::endl;
        return -1;
    }
    Py_SetProgramName(program);
    Py_Initialize();
    if ( !Py_IsInitialized() ){
        std::cout << "Python init failed!" << std::endl;
	return 0;
    }
    
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('/home/heygears/work/C++_python/test/test_class')");
    
    // 1. 导入模块
    PyObject* pModule = PyImport_ImportModule("test_class");
    if ( pModule == nullptr ){
        std::cout << "module not found!" << std::endl;
        return 1;
    }

    PyObject* pTestDict = PyModule_GetDict(pModule);
    
    
    // 2. 导入模块中方法或类
    PyObject* pTestClass = PyDict_GetItemString(pTestDict, "Test");

    // 3. 创建实例
    PyObject* pTestInstance = nullptr;
    if ( PyCallable_Check(pTestClass) ){
        pTestInstance = PyObject_CallObject(pTestClass, nullptr);
    }
    
    // 4. 调用类方法
    PyObject_CallMethod(pTestInstance, "do", nullptr);
    PyObject_CallMethod(pTestInstance, "modify", nullptr);
    PyObject_CallMethod(pTestInstance, "do", nullptr);

    if ( Py_FinalizeEx() < 0 ){
        exit(120);
    }
    PyMem_RawFree(program);
    return 0;
}

注:如果类函数有参数,可以参照4.2中方法

五、调用SSD目标检测算法

参考C/C++调用Python [OpenCV与Numpy]Windows下C++调用Python版的Pytorch模型
这里以调用SSD模型为例

  • CMakeLists.txt
    由于需要用到Numpy,所以在CMakeLists.txt中先引入Numpy的头文件,可以参考一、基本使用方法
    另外还需要用到OpenCV库,在CMakeLists.txt后面添加如下代码, target_link_libraries也需要追加 ${OpenCV_LIBS}库。
# >>> opencv
set(OpenCV_DIR "/usr/local")
message(STATUS ${OpenCV_DIR})
find_package(OpenCV REQUIRED)
if(OpenCV_FOUND)
    include_directories(${OpenCV_DIR}/include/opencv4/opencv2)
    include_directories( ${OpenCV_INCLUDE_DIRS})
    link_directories(${OpenCV_DIR}/lib)
    message(STATUS "Configure package with OpenCV!")
    set(HAVE_OpenCV True)
else()
    set(HAVE_OpenCV False)
    message(STATUS "Configure package without OpenCV!")
endif()
# <<<
  • inferencePb.py
# -*- coding :  utf-8 -*-
# @File      :  inferencePb.py
# Desctiption:  MobileNetV2SSDlite


import os
import cv2
import time
import numpy as np
import tensorflow as tf

#os.environ["CUDA_VISIBLE_DEVICES"]="-1"
###############################################################################
#- 定义识别函数


def arrayreset(array):
    a = array[:, 0:len(array[0] - 2):3]
    b = array[:, 1:len(array[0] - 2):3]
    c = array[:, 2:len(array[0] - 2):3]
    a = a[:, :, None]
    b = b[:, :, None]
    c = c[:, :, None]
    m = np.concatenate((a, b, c), axis=2)
    return m



def recognize(src_image):
    """
    MobileNetV2-SSDLite
    :param src_image: 输入视频流或图像
    :param pb_file_path: the model file path
    :return: 
    """
    
    with tf.Graph().as_default():
        output_graph_def = tf.GraphDef()


        #---------------------------------------
        # 打开 .pb 模型
        pb_file = "ssd300_pascal_07+12_epoch-86_loss-1.2568_val_loss-0.5428.pb"

        with open(pb_file, "rb") as f:
            output_graph_def.ParseFromString(f.read())
            tensors = tf.import_graph_def(output_graph_def, name="")
            print("tensors:",tensors)


        with tf.Session() as sess:
           # init = tf.global_variables_initializer()
           # sess.run(init)

            #---------------------------------------
            # 打开图中所有的操作
            op = sess.graph.get_operations()
            for i,m in enumerate(op):
                print('op{}:'.format(i),m.values())

            #---------------------------------------
            # 模型的的输入和输出名称

            #--------------------------------------
            # 遍历某目录下的图像

            input_x = sess.graph.get_tensor_by_name("input_1:0")
            #print("input_X:",input_x)
            output_tensor = sess.graph.get_tensor_by_name("ssd_decoded_predictions/loop_over_batch/TensorArrayStack/TensorArrayGatherV3:0")
            #print("Output:",output_tensor)
     

            #--------------------------------------
            # 计算时间, 持续加载同一张图像

            # src_image = arrayreset(src_image)       
            src_image = cv2.imread(src_image) 
            org_img = src_image.copy()

            img=cv2.resize(src_image,(300,300))
            img=img.astype(np.float32)
                  
            y_pred = sess.run([output_tensor], feed_dict={input_x:np.reshape(img,(1,300,300,3))})
                      
            confidence_threshold = 0.8

            y_pred_array = np.array(y_pred[0])

            y_pred_thresh = [y_pred_array[k][y_pred_array[k,:,1] > confidence_threshold] for k in range(y_pred_array.shape[0])]
            classes = ['background', 'tank']
            image_size = (300, 300, 3)

            for box in y_pred_thresh[0]:
                xmin = box[2] * org_img.shape[1] / image_size[0]
                ymin = box[3] * org_img.shape[0] / image_size[1]
                xmax = box[4] * org_img.shape[1] / image_size[1]
                ymax = box[5] * org_img.shape[0] / image_size[0]
                label = '{}: {:.2f}'.format(classes[int(box[0])], box[1])
                print("label", label)


       


def main():
    src_image = "002394.jpg"
    pb_file = "ssd300_pascal_07+12_epoch-86_loss-1.2568_val_loss-0.5428.pb"
    recognize(src_image)



if __name__ == '__main__':
    main()

  • TargetDetection.cpp
/******************************************************************************
 * @file       :   TargetDetection.cpp
 * @Desctiption:   c++ 调用 SSD inferencePb.py 模块
 *
 *****************************************************************************/

#include <Python.h> 
#include <iostream>
#include <cstring>
#include <opencv2/opencv.hpp>
#include <numpy/arrayobject.h> 
#include <time.h>

static void help()
{
	std::cout << std::endl;
	std::cout << "This sample demostrates MobileNet-V2-SSDLite detection with tensorflow server inference." << std::endl;
	std::cout << "Call" << std::endl;
}


int main(int argc, char* argv[])
{   
    if (argc != 2) 
	{
		help();
	}
    

    Py_Initialize();                // 初始化 Python 环境
    if (!Py_IsInitialized())
    {
        std::cout << "init faild ..." << std::endl; 
    }
    
    import_array();  // 初始化numpy

    // 如果查找函数文件一直是 nullptr 则加上下面两行路径
    PyRun_SimpleString("import sys");
	PyRun_SimpleString("sys.path.append('/home/xm/project_ssd/build/')");
    

	PyObject* pModule = nullptr;                               //.py文件  
    pModule = PyImport_ImportModule("inferencePb");            //调用上述路径下的inferencePb.py文件
    if (pModule == nullptr)
	{
        std::cout << "don't find the python file!" << std::endl;
        return -1;
	}
    
    clock_t start, end;
    for (int i=0;i<100;i++)
    {
    start = clock();
    // 这里用视频流替换传入的图像参数
    std::string image = argv[1];
    cv::Mat img = cv::imread(image);
    if (img.empty())
    {
        std::cout << "could not load image ..." << std::endl;
        return -1;
    }

    int m, n;
    n = img.cols *3;
    m = img.rows;


    unsigned char *data = (unsigned char*)malloc(sizeof(unsigned char) * m * n);
    int p = 0;
    for (int i = 0; i < m;i++)
    {
        for (int j = 0; j < n; j++)
        {
            data[p]= img.at<unsigned char>(i, j);
            p++;
        }
    }

    npy_intp Dims[2] = { m,n };  //图像的维度信息

    PyObject* PyArray = nullptr;
    PyArray = PyArray_SimpleNewFromData(2, Dims, NPY_UINT8, data);      //建立函数的形参

    PyObject* ArgArray =nullptr;
    ArgArray = PyTuple_New(1);                  //新建长度为1的元组
    PyTuple_SetItem(ArgArray, 0, PyArray);      //设置元组ArgArray[0]为PyArray图像

    
    PyObject* pFunc = nullptr;                  //py文件中的函数

    pFunc = PyObject_GetAttrString(pModule,"recognize");
    if (pFunc==nullptr)
    {
        std::cout << "can't find function recognize ... " << std::endl;
        return -1;
    }
    
    PyObject* pReturnValue;
    pReturnValue = PyObject_CallObject(pFunc, ArgArray);
    
    // 从结果中得到结果,将结果画在图像上
     
    if (pReturnValue)
    {   
        int list_len = PyObject_Size(pReturnValue);
        std::cout << list_len << std::endl;
        std::cout << "++++++++++++++++++++++++" << std::endl; // 返回 list 长度,表示检测到目标的个数 

        PyArrayObject *pyResultArr = (PyArrayObject *) pReturnValue;
        float *resDataArr = (float *) PyArray_DATA(pyResultArr);

        //int dimNum = PyArray_NDIM(pyResultArr);//返回数组的维度数,此处恒为1
        //std::cout << dimNum << std::endl;

        
        npy_intp *pdim = PyArray_DIMS(pyResultArr);//返回数组各维度上的元素个数值
        //std::cout << pdim << "+++++++++++++++" << pdim[1] << "+++++++++" << std::endl;

        for (int i = 0; i < list_len; ++i) 
        {
            for (int j = 0; j < pdim[1]; ++j)
            {   
                std::cout << resDataArr[i*pdim[1] + j] << ", ";
            }
            std::cout << std::endl;
        }
    }
    
    end = clock();

    std::cout << "Time is" << (double) (end-start) / 1000000 << std::endl;
    }
    // 这张图是用来传入 下面 Python 的算法中的一张图像,需要提前处理,
    // 功能实现之后,该功能替换为海康相机 SDK 的视频流
    
    std::cout << "Finish ..." << std::endl; 
       
    Py_Finalize(); // 释放 python 环境 
    return 0;
}

六、遇到的错误

6.1 Error: ModuleNotFoundError: No module named 'encodings'

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 0x00007ff4011626c0 (most recent call first):
Aborted (core dumped)

解决办法
如果采用anaconda3的base环境就可以正常运行。我采用的是虚拟环境,在初始化之前需加入Py_SetPythonHome函数,如:

    Py_Initialize();

变为:

   Py_SetPythonHome((wchar_t*)L"/home/zjh/anaconda3/envs/learn");
   Py_Initialize();

6.2 numpy Warning:

Warning "Using deprecated Numpy API, disable it with "

解决办法
在cpp文件最开始加入


#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION

参考链接:

https://docs.python.org/2/extending/embedding.html
https://zhuanlan.zhihu.com/p/79896193
https://blog.csdn.net/ziweipolaris/article/details/83689597
https://blog.csdn.net/u011681952/article/details/92765549
https://blog.csdn.net/hnlylyb/article/details/89498651

原文地址:https://www.cnblogs.com/xiaxuexiaoab/p/14471181.html