一、基本使用方法
二、調用簡單語句
三、調用函數
四、調用類
五、調用SSD目標檢測算法
六、遇到的錯誤
最近訓練一個3D分割的模型,需要將其結合到項目中,由於項目是C++開發,而這邊python訓練好的模型嘗試了ONNX、libtorch等轉換C++也沒有成功,因此考慮采用C++直接調用python代碼,這里對里面用到的一些方法做一個總結,方便以后查看。
一、基本使用方法
1.1 調用步驟
- 將數據值從C/C++轉換為Python
- 使用轉換后的值對Python接口例程執行函數調用
- 將Python調用中的數據值轉換為C/C++
1.2 編譯鏈接
使用python提供的C/C++接口,需要包含python安裝目錄下的頭文件Python.h 編譯、鏈接時需要指定頭文件、python庫的地址。
示例:
include: /home/zjh/anaconda3/envs/learn/include/python3.6m Python.h
lib: /home/zjh/anaconda3/envs/learn/lib/ libpython3.6m.a/libpython3.6m.so
注:如果需要Numpy
庫的話需要找到numpy的地址,一般存放於第三方庫路徑下,如:
/home/zjh/anaconda3/envs/learn/lib/python3.6/site-packages/numpy/core/include/numpy arrayobject.h
1.3 基本接口
- 解釋器
在C/C++調用python之前必須為其指定解釋器環境。
void Py_Initialize():
初始化python解釋器.C/C++中調用Python之前必須先初始化解釋器
int Py_IsInitialized():
返回python解析器的是否已經初始化完成,如果已完成,返回大於0,否則返回0
void Py_Finalize() :
撤銷Py_Initialize()和隨后使用Python/C API函數進行的所有初始化,
並銷毀自上次調用Py_Initialize()以來創建並未被銷毀的所有子解釋器。
-
格式轉換
在C/C++中,所有的Python類型都被聲明為PyObject類型,為了讓C/C++能夠操作python的數據,python提供了C語言數據類型到PyObject類型的轉換接口。- 數字/字符串
PyObject* Py_BuildValue( const char *format, ...) Py_BuildValue()提供了類似c語言printf的參數構造方法,format是要構造的參數的類型列表,函數中剩余的參數即要轉換的C語言中的整型、浮點型或者字符串等。 其返回值為PyObject型的指針。
format對應的類型參見官網, 如:
s(str或None)[char *] 使用'utf-8'編碼將以null結尾的C字符串轉換為Python str對象。如果C字符串指針為NULL,則表示None。 i(int)[int] 將普通的C int轉換為Python整數對象。 ...
- 列表
PyObject* PyList_New( Py_ssize_t len) 創建一個新的Python列表,len為所創建列表的長度 int PyList_SetItem( PyObject *list, Py_ssize_t index, PyObject *item) 向列表中添加項。當列表創建以后,可以使用PyList_SetItem()函數向列表中添加項。 list:要添加項的列表。 index:所添加項的位置索引。 item:所添加項的值。 PyObject* PyList_GetItem( PyObject *list, Py_ssize_t index) 獲取列表中某項的值。list:要進行操作的列表。index:項的位置索引。 Py_ssize_t PyList_Size(PyObject * list) 返回列表中列表對象的長度;這相當於列表對象上的 len(list) 。 int PyList_Append( PyObject *list, PyObject *item) int PyList_Sort( PyObject *list) int PyList_Reverse( PyObject *list) Python/C API中提供了與Python中列表操作相對應的函數。例如 列表的append方法對應於PyList_Append()函數。 列表的sort方法對應於PyList_Sort()函數。 列表的reverse方法對應於PyList_Reverse()函數。
- 元組
PyObject* PyTuple_New( Py_ssize_t len) PyTuple_New()函數返回所創建的元組。其函數原型如下所示。len:所創建元組的長度。 int PyTuple_SetItem( PyObject *p, Py_ssize_t pos, PyObject *o) 當元組創建以后,可以使用PyTuple_SetItem()函數向元組中添加項。p:所進行操作的元組,pos:所添加項的位置索引,o:所添加的項值。 PyObject* PyTuple_GetItem( PyObject *p, Py_ssize_t pos) 可以使用Python/C API中PyTuple_GetItem()函數來獲取元組中某項的值。p:要進行操作的元組,pos:項的位置索引 Py_ssize_t PyTuple_Size(PyObject * p) 獲取指向元組對象的指針,並返回該元組的大小。 int _PyTuple_Resize( PyObject **p, Py_ssize_t newsize) 當元組創建以后可以使用_PyTuple_Resize()函數重新調整元組的大小。其函數原型如下所示。p:指向要進行操作的元組的指針,newsize:新元組的大小
- 字典
PyObject* PyDict_New() PyDict_New()函數返回所創建的字典。 int PyDict_SetItem( PyObject *p, PyObject *key, PyObject *val) int PyDict_SetItemString( PyObject *p, const char *key, PyObject *val) 當字典創建后,可以使用PyDict_SetItem()函數和PyDict_SetItemString()函數向字典中添加項。 其參數含義如下。 p:要進行操作的字典。key:添加項的關鍵字, 對於PyDict_SetItem()函數其為PyObject型, 對於PyDict_SetItemString()函數其為char型,val:添加項的值。 PyObject* PyDict_GetItem( PyObject *p, PyObject *key) PyObject* PyDict_GetItemString( PyObject *p, const char *key) 使用Python/C API中的PyDict_GetItem()函數和PyDict_GetItemString()函數來獲取字典中某項的值。它們都返回項的值。 其參數含義如下。p:要進行操作的字典,key:添加項的關鍵字, 對於PyDict_GetItem()函數其為PyObject型 對於PyDict_GetItemString()函數其為char型。 PyObject* PyDict_Items( PyObject *p) PyObject* PyDict_Keys( PyObject *p) PyObject* PyDict_Values( PyObject *p) 在Python/C API中提供了與Python中字典操作相對應的函數。例如 字典的item方法對應於PyDict_Items()函數。 字典的keys方法對應於PyDict_Keys()函數。 字典的values方法對應於PyDict_Values()函數。 其參數p:要進行操作的字典。
-
返回值解析
python執行完返回的結果也是PyObject類型,因此需要將PyObject類型轉換為C/C++類型。
int PyArg_Parse( PyObject *args, char *format, ...)
根據format把args的值轉換成c類型的值,[format](https://docs.python.org/3/c-api/arg.html)接受的類型和上述Py_BuildValue()的是一樣的,
- 釋放資源
Python使用引用計數機制對內存進行管理,實現自動垃圾回收。在Python/C API中提供了Py_CLEAR()、Py_DECREF()等宏來對引用計數進行操作。
當使用Python/C API中的函數創建列表、元組、字典等后,就在內存中生成了這些對象的引用計數,在對其完成操作后應該使用Py_CLEAR()、Py_DECREF()等宏來銷毀這些對象。
void Py_CLEAR(PyObject *o)
void Py_DECREF(PyObject *o)
其中,o的含義是要進行操作的對象。
對於Py_CLEAR()其參數可以為NULL指針,此時,Py_CLEAR()不進行任何操作。而對於Py_DECREF()其參數不能為NULL指針,否則將導致錯誤。
二、調用簡單語句
首先編寫CMakeLists.txt
文件,引入python所需頭文件和庫文件,python版本是3.6.5
。
- CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(helloworld)
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(helloworld helloworld.cpp)
target_link_libraries(helloworld -lpython3.6m)
- helloworld.cpp
#include <python3.6m/Python.h>
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(); ## 初始化
PyRun_SimpleString("print('hello world!')");
Py_Finalize(); ## 釋放資源
PyMem_RawFree(program);
return 0;
}
將CMakeLists.txt
和helloworld.cpp
兩個文件放在同一文件夾下,在新建build
文件夾,進行編譯構建,構建成功后目錄下會生成helloworld
可執行文件。
$ cmake..
$ make
參考鏈接:
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