Python 调用C++封装的进一步探索交流


Posted in Python onMarch 04, 2021

我们知道,C++和python各有优缺点,C++可以直接映射到硬件底层,实现高效运行,而python能够方便地来进行编程,有助于工程的快速实现。

那能不能发挥两者的优势将它们结合起来?当然是可以的!有多种方法可以实现它们之间的相互转换。

链接文章中,有提到一个简单的例子,来教我们如何生成可以被python加载的文件。

但是这只能针对简单的数据进行封装,一旦涉及到自定义的类等封装数据,就需要借助第三方库来帮助更好实现。

比如numpy与C++的数据接口。

这里对python调用C++生成的pyd(so/dll)文件进行进一步的探索。

1.首先进行如下配置,在VC++目录中包含python和numpy的文件目录:

Python 调用C++封装的进一步探索交流

配置为Release平台,不然numpy的头文件无法被包含,导致编译器链接出错。

特别要注意的一点是用cmd生成pyd文件时,VS2013可能要输入: SET VS90COMNTOOLS=%VS120COMNTOOLS%(每次重新打开cmd窗口运行pythonsetup.py build的时候都要输入一次)才能生成成功。

2.理解python调用C++的数据交互过程:

Python 调用C++封装的进一步探索交流

Python中的代码通过CPython等将语句解释为C/C++语言,然后编译器调用binding入口函数,将传进来的PyObject*参数通过PyFloat_AsDouble()等转换成C/C++变量。

这些作为输入变量传进已经写好的C++函数,调用该函数,返回C++结果。最后反过来,将C/C++变量转成CPython可以识别的PyObject*对象返回给python编译器(如函数PyFloat_FromDouble()),完成python到C++的调用。

当C/C++里面的输入变量或者返回值都不是基本类型时,比如自定义的类,那我们同样要按照类里面定义数据的方式以数据的方式来对应改成python能识别的基本类型的组合。

以Mat和numpy的array对象相互转换为例:

//以Mat的allocator作为基类,Numpy的Allocator作为继承类
//这样可以用派生对象指针对基类数据进行操作
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);
  }
 //确保当前使用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::AutoBuffer<npy_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;
  }
 }
 //基类指针,调用allocate函数进行内存分配
 const MatAllocator* stdAllocator;
};

上面是先构造好能够相互交互的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对象转为Mat的部分代码,具体可以参考opencv的cv2.cpp文件:..\OpenCV\sources\modules\python\src2

//将Mat转换为PyObject*
template<>
PyObject* pyopencv_from(const Mat& m)
{
 if( !m.data )
  Py_RETURN_NONE;
 Mat temp, *p = (Mat*)&m;
 //确保数据拷贝不会对原始数据m产生破坏
 if(!p->u || p->allocator != &g_numpyAllocator)
 {
  temp.allocator = &g_numpyAllocator;
  ERRWRAP2(m.copyTo(temp));
  p = &temp;
 }
 //将Mat封装好的userdata指针转给Pyobject*
 PyObject* o = (PyObject*)p->u->userdata;
 //引用计数器加一
 Py_INCREF(o);
 return o;
}

3.不是所有C++的语法都能转为python可调用的pyd文件

一个很重要的知识点是,pyd文件跟dll文件非常相似,所以生成dll比较困难的C++代码同样难以生成pyd,C++跟python编译器各自编译特性的区别也会使得转换存在困难,比如C++的动态编译。

下面是可以进行相互转换的C++特性(可以用swig生成):

类;构造函数和析构函数;虚函数;(多重)公有继承;

静态函数;重载(包括大多数操作符重载);引用;

模板编程(特化和成员模板);命名空间;默认参数;智能指针。

下面是不能或者比较困难进行转换的C++特性:

嵌套类;特定操作符的重载比如new和delete。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Python 相关文章推荐
Python多进程编程技术实例分析
Sep 16 Python
Python多线程下载文件的方法
Jul 10 Python
详解Python Socket网络编程
Jan 05 Python
在Linux命令行终端中使用python的简单方法(推荐)
Jan 23 Python
Python变量和数据类型详解
Feb 15 Python
Python使用requests及BeautifulSoup构建爬虫实例代码
Jan 24 Python
python判断列表的连续数字范围并分块的方法
Nov 16 Python
在Python 中实现图片加框和加字的方法
Jan 26 Python
python TF-IDF算法实现文本关键词提取
May 29 Python
详解Python中的正斜杠与反斜杠
Aug 09 Python
Python数据可视化:饼状图的实例讲解
Dec 07 Python
如何基于Python按行合并两个txt
Nov 03 Python
使用Python webdriver图书馆抢座自动预约的正确方法
Mar 04 #Python
Python与C/C++的相互调用案例
Mar 04 #Python
解决Python import .pyd 可能遇到路径的问题
Mar 04 #Python
关于PySnooper 永远不要使用print进行调试的问题
Mar 04 #Python
pip/anaconda修改镜像源,加快python模块安装速度的操作
Mar 04 #Python
Pytorch实现WGAN用于动漫头像生成
Mar 04 #Python
基于PyInstaller各参数的含义说明
Mar 04 #Python
You might like
php保存任意网络图片到服务器的方法
2015/04/14 PHP
PHP.ini安全配置检测工具pcc简单介绍
2015/07/02 PHP
在Mac OS上编译安装Nginx+PHP+MariaDB开发环境的教程
2016/02/23 PHP
php中static 静态变量和普通变量的区别
2016/12/01 PHP
数组任意位置插入元素,删除特定元素的实例
2017/03/02 PHP
由prototype_1.3.1进入javascript殿堂-类的初探
2006/11/06 Javascript
JS Timing
2007/04/21 Javascript
jquery中常用的SET和GET
2009/01/13 Javascript
[Web]防止用户复制页面内容和另存页面的方法
2009/02/06 Javascript
根据鼠标的位置动态的控制层的位置
2009/11/24 Javascript
js 页面刷新location.reload和location.replace的区别小结
2009/12/24 Javascript
IE6弹出“已终止操作”的解决办法
2010/11/27 Javascript
jquery实现图片滚动效果的简单实例
2013/11/23 Javascript
js获取上传文件大小示例代码
2014/04/10 Javascript
深入分析escape()、encodeURI()、encodeURIComponent()的区别及示例
2014/08/04 Javascript
跨域请求的完美解决方法(JSONP, CORS)
2016/06/12 Javascript
jquery 正整数数字校验正则表达式
2017/01/10 Javascript
JavaScript基于replace+正则实现ES6的字符串模版功能
2017/04/25 Javascript
如何解决.vue文件url引用文件的问题
2019/01/18 Javascript
python 正则表达式 概述及常用字符
2009/05/04 Python
python生成器表达式和列表解析
2016/03/10 Python
Python通过for循环理解迭代器和生成器实例详解
2019/02/16 Python
Opencv+Python实现图像运动模糊和高斯模糊的示例
2019/04/11 Python
图文详解python安装Scrapy框架步骤
2019/05/20 Python
HTML5 Notification(桌面提醒)功能使用实例
2014/03/17 HTML / CSS
哈曼俄罗斯官方网上商店:Harman.club
2020/07/24 全球购物
Ibatis的核心配置文件都有什么
2014/09/08 面试题
学院领导推荐信
2013/10/30 职场文书
机械设计职业生涯规划书
2013/12/27 职场文书
给交警的表扬信
2014/01/12 职场文书
愚人节活动策划方案
2014/03/11 职场文书
公务员更新知识培训实施方案
2014/03/31 职场文书
2016年国庆节67周年活动总结
2016/04/01 职场文书
python基于scrapy爬取京东笔记本电脑数据并进行简单处理和分析
2021/04/14 Python
Win11绿屏怎么办?Win11绿屏死机的解决方法
2021/11/21 数码科技
vue3不同环境下实现配置代理
2022/05/25 Vue.js