我們知道,C++和python各有優(yōu)缺點(diǎn),C++可以直接映射到硬件底層,實(shí)現(xiàn)高效運(yùn)行,而python能夠方便地來進(jìn)行編程,有助于工程的快速實(shí)現(xiàn)。
那能不能發(fā)揮兩者的優(yōu)勢將它們結(jié)合起來?當(dāng)然是可以的!有多種方法可以實(shí)現(xiàn)它們之間的相互轉(zhuǎn)換。
鏈接文章中,有提到一個(gè)簡單的例子,來教我們?nèi)绾紊煽梢员籶ython加載的文件。
但是這只能針對簡單的數(shù)據(jù)進(jìn)行封裝,一旦涉及到自定義的類等封裝數(shù)據(jù),就需要借助第三方庫來幫助更好實(shí)現(xiàn)。
比如numpy與C++的數(shù)據(jù)接口。
這里對python調(diào)用C++生成的pyd(so/dll)文件進(jìn)行進(jìn)一步的探索。
1.首先進(jìn)行如下配置,在VC++目錄中包含python和numpy的文件目錄:
配置為Release平臺(tái),不然numpy的頭文件無法被包含,導(dǎo)致編譯器鏈接出錯(cuò)。
特別要注意的一點(diǎn)是用cmd生成pyd文件時(shí),VS2013可能要輸入: SET VS90COMNTOOLS=%VS120COMNTOOLS%(每次重新打開cmd窗口運(yùn)行pythonsetup.py build的時(shí)候都要輸入一次)才能生成成功。
2.理解python調(diào)用C++的數(shù)據(jù)交互過程:
Python中的代碼通過CPython等將語句解釋為C/C++語言,然后編譯器調(diào)用binding入口函數(shù),將傳進(jìn)來的PyObject*參數(shù)通過PyFloat_AsDouble()等轉(zhuǎn)換成C/C++變量。
這些作為輸入變量傳進(jìn)已經(jīng)寫好的C++函數(shù),調(diào)用該函數(shù),返回C++結(jié)果。最后反過來,將C/C++變量轉(zhuǎn)成CPython可以識別的PyObject*對象返回給python編譯器(如函數(shù)PyFloat_FromDouble()),完成python到C++的調(diào)用。
當(dāng)C/C++里面的輸入變量或者返回值都不是基本類型時(shí),比如自定義的類,那我們同樣要按照類里面定義數(shù)據(jù)的方式以數(shù)據(jù)的方式來對應(yīng)改成python能識別的基本類型的組合。
以Mat和numpy的array對象相互轉(zhuǎn)換為例:
//以Mat的allocator作為基類,Numpy的Allocator作為繼承類
//這樣可以用派生對象指針對基類數(shù)據(jù)進(jìn)行操作
class NumpyAllocator : public MatAllocator
{
public:
NumpyAllocator() { stdAllocator = Mat::getStdAllocator(); }
~NumpyAllocator() {}
UMatData* allocate(PyObject* o, int dims, const int* sizes, int type, size_t* step) const
{
UMatData* u = new UMatData(this);
u->data = u->origdata = (uchar*)PyArray_DATA((PyArrayObject*) o);
npy_intp* _strides = PyArray_STRIDES((PyArrayObject*) o);
for( int i = 0; i dims - 1; i++ )
step[i] = (size_t)_strides[i];
step[dims-1] = CV_ELEM_SIZE(type);
u->size = sizes[0]*step[0];
u->userdata = o;
return u;
}
UMatData* allocate(int dims0, const int* sizes, int type, void* data, size_t* step, int flags, UMatUsageFlags usageFlags) const
{
if( data != 0 )
{
CV_Error(Error::StsAssert, "The data should normally be NULL!");
// probably this is safe to do in such extreme case
return stdAllocator->allocate(dims0, sizes, type, data, step, flags, usageFlags);
}
//確保當(dāng)前使用python的C API是線程安全的
PyEnsureGIL gil;
int depth = CV_MAT_DEPTH(type);
int cn = CV_MAT_CN(type);
const int f = (int)(sizeof(size_t)/8);
int typenum = depth == CV_8U ? NPY_UBYTE : depth == CV_8S ? NPY_BYTE :
depth == CV_16U ? NPY_USHORT : depth == CV_16S ? NPY_SHORT :
depth == CV_32S ? NPY_INT : depth == CV_32F ? NPY_FLOAT :
depth == CV_64F ? NPY_DOUBLE : f*NPY_ULONGLONG + (f^1)*NPY_UINT;
int i, dims = dims0;
cv::AutoBuffernpy_intp> _sizes(dims + 1);
for( i = 0; i dims; i++ )
_sizes[i] = sizes[i];
if( cn > 1 )
_sizes[dims++] = cn;
PyObject* o = PyArray_SimpleNew(dims, _sizes, typenum);
if(!o)
CV_Error_(Error::StsError, ("The numpy array of typenum=%d, ndims=%d can not be created", typenum, dims));
return allocate(o, dims0, sizes, type, step);
}
bool allocate(UMatData* u, int accessFlags, UMatUsageFlags usageFlags) const
{
return stdAllocator->allocate(u, accessFlags, usageFlags);
}
void deallocate(UMatData* u) const
{
if(!u)
return;
PyEnsureGIL gil;
CV_Assert(u->urefcount >= 0);
CV_Assert(u->refcount >= 0);
if(u->refcount == 0)
{
PyObject* o = (PyObject*)u->userdata;
Py_XDECREF(o);
delete u;
}
}
//基類指針,調(diào)用allocate函數(shù)進(jìn)行內(nèi)存分配
const MatAllocator* stdAllocator;
};
上面是先構(gòu)造好能夠相互交互的allocator。
//將PyObject的特性幅值給size,ndims,type
int typenum = PyArray_TYPE(oarr), new_typenum = typenum;
int type = typenum == NPY_UBYTE ? CV_8U :
typenum == NPY_BYTE ? CV_8S :
typenum == NPY_USHORT ? CV_16U :
typenum == NPY_SHORT ? CV_16S :
typenum == NPY_INT ? CV_32S :
typenum == NPY_INT32 ? CV_32S :
typenum == NPY_FLOAT ? CV_32F :
typenum == NPY_DOUBLE ? CV_64F : -1;
//....
int ndims = PyArray_NDIM(oarr);
//....
const npy_intp* _sizes = PyArray_DIMS(oarr);
const npy_intp* _strides = PyArray_STRIDES(oarr);
for ( int i = ndims - 1; i >= 0; --i )
{
size[i] = (int)_sizes[i];
if ( size[i] > 1 )
{
step[i] = (size_t)_strides[i];
default_step = step[i] * size[i];
}
else
{
step[i] = default_step;
default_step *= size[i];
}
}
//....
//這一步直接用PyObject初始化Mat m
m = Mat(ndims, size, type, PyArray_DATA(oarr), step);
m.u = g_numpyAllocator.allocate(o, ndims, size, type, step);
m.addref();
上面是將PyObject對象轉(zhuǎn)為Mat的部分代碼,具體可以參考o(jì)pencv的cv2.cpp文件:..\OpenCV\sources\modules\python\src2
//將Mat轉(zhuǎn)換為PyObject*
template>
PyObject* pyopencv_from(const Mat m)
{
if( !m.data )
Py_RETURN_NONE;
Mat temp, *p = (Mat*)m;
//確保數(shù)據(jù)拷貝不會(huì)對原始數(shù)據(jù)m產(chǎn)生破壞
if(!p->u || p->allocator != g_numpyAllocator)
{
temp.allocator = g_numpyAllocator;
ERRWRAP2(m.copyTo(temp));
p = temp;
}
//將Mat封裝好的userdata指針轉(zhuǎn)給Pyobject*
PyObject* o = (PyObject*)p->u->userdata;
//引用計(jì)數(shù)器加一
Py_INCREF(o);
return o;
}
3.不是所有C++的語法都能轉(zhuǎn)為python可調(diào)用的pyd文件
一個(gè)很重要的知識點(diǎn)是,pyd文件跟dll文件非常相似,所以生成dll比較困難的C++代碼同樣難以生成pyd,C++跟python編譯器各自編譯特性的區(qū)別也會(huì)使得轉(zhuǎn)換存在困難,比如C++的動(dòng)態(tài)編譯。
下面是可以進(jìn)行相互轉(zhuǎn)換的C++特性(可以用swig生成):
類;構(gòu)造函數(shù)和析構(gòu)函數(shù);虛函數(shù);(多重)公有繼承;
靜態(tài)函數(shù);重載(包括大多數(shù)操作符重載);引用;
模板編程(特化和成員模板);命名空間;默認(rèn)參數(shù);智能指針。
下面是不能或者比較困難進(jìn)行轉(zhuǎn)換的C++特性:
嵌套類;特定操作符的重載比如new和delete。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
您可能感興趣的文章:- python中Task封裝協(xié)程的知識點(diǎn)總結(jié)
- Python自動(dòng)化測試PO模型封裝過程詳解
- Python面向?qū)ο蠓庋b繼承和多態(tài)示例講解
- Python如何實(shí)現(xiàn)Paramiko的二次封裝
- 使用Python封裝excel操作指南
- python excel和yaml文件的讀取封裝
- python 使用paramiko模塊進(jìn)行封裝,遠(yuǎn)程操作linux主機(jī)的示例代碼
- Python之根據(jù)輸入?yún)?shù)計(jì)算結(jié)果案例講解