c++读出像素矩阵_Python传numpy矩阵调c++(求3D图像连通区域)

v2-a96c448fe2595da1592296e14f74dc3b_1440w.jpg?source=172ae18b

Python有很多种调c++的方法,有的复杂有的简单,有时使用的时候反而不知道到底该用哪一种比较好,其实没有最好的方法,只有适合不适合自己。本文从我所遇到的问题说起,然后讲述另一种比较简单的python调c++并且传参numpy矩阵的方法。该方法调用的是python自带的ctypes库,所以使用该方法不用安装或配置任何地第三方库。

背景

之前项目遇到一个问题,求二值图像连通区域,对于一般的图像其实很简单,opencv有函数可以直接调。但是我要处理的是一个3D图像,opencv没有求3D图像连通区域的函数,所以只能自己写算法实现。该算法的核心思想是使用深度遍历来判断有多少像素点是连通的,所以,看到遍历两个字那python就肯定得排除了,所以就选择了c++。

从工程上来看,我的问题可以简单描述为:python环境下有一个numpy矩阵,以及若干参数,现需要传递给c++程序计算,然后返回一个新的numpy矩阵或者直接修改原始numpy矩阵。

然后,我开始思考我到底该使用哪种方式调c++,我也调研了一些方法,在此不一一列举,总的来说,它们的缺点是:额外学习成本相对较高、得另外安装软件或库支持(例如boost)、调试起来各种奇怪的错误等等。再加上我使用的Linux服务器我并没有sudo权限,故更加限制了这些方法的使用。这些方法或许很强大,但是我的要求很简单,只要能实现我上述的要求即可,于是我开始把目光转向ctypes。

解决方案

Python使用ctypes调c++很简单,网上有一堆相关教程,但这些教程中传递的参数基本都是int、float等等一些最基本的类型,而我现在要传的是一个很大的numpy矩阵,该怎么办?

v2-3c5b4099a6da570eecfc55679be27f65_b.png

我仔细研究了一下,发现可以通过ctypes传指针,而且numpy矩阵的指针也可以得到,惊喜。

v2-e2bb3d5df51059b2c8f7eee418542db5_b.png

利用ctypes,numpy矩阵的指针可以这样得到:

p 

于是我想,把numpy指针和numpy的shape信息传递过去不就相当于把整个numpy传递过去了吗?比如src是一个10*10的矩阵,指针为p,那么在c++中是不是就可以使用p[x][y]来定位具体的元素了?

想法是美好的,c++中可以使用p[x][y]的前提是p一个指针的指针,具体来说应该是int**类型的,而事实是p仅仅只是一个指针,是int*类型的,故,此思路行不通。

v2-53739a83e4c3d83f50ee8a5608d2dfa9_b.png

转念又一想,既然它是int*类型,那我就p[x]这样来取值,就把它当成一个一维数组,于是成功。

v2-1dc20f38ce4f8503e7f3a6a9624ef0de_b.png

那么现在问题来了,本来numpy矩阵是多维的,怎样跟一维数组对应呢?对,很简单,就是把多维的矩阵拉成一维的即可,相当于numpy的一个函数reshape,想想reshape成一维的之后元素是怎样跟之前对应的,就是这个对应关系。

比如一个二维矩阵shape为(10,20),那么其中的(i,j)点对应到一维数组里面的坐标就是i*20+j,三维矩阵道理相同。

至此,关键问题解决了。

后续思考

速度问题

现有这样一个任务:三维矩阵所有元素值加一,用遍历方法实现。我们分别使用两种方法来实现并且对比速度,1,纯python实现;2,c++实现,使用刚刚的方法调c++。

c++代码如下(demo.cpp):

#include 

python代码如下(main.py):

import 

然后使用下面命令编译成动态链接库:

=c++11 -shared -fPIC demo.c

最后运行main.py,结果如下:

(10, 512, 512)
c++   :  0.009840011596679688
python:  7.284840822219849

可见速度差别之大。

v2-4d9c44669152d3f7b0ab1dd193865a96_b.png

内存连续问题

可能有小伙伴已经想到了,该方法可行的另一个重要前提是numpy矩阵所有元素在内存中必须是连续的,若不连续肯定会出错。但是有时候我们传递的numpy矩阵可能就是不连续的,那该怎么办?很简单,仅仅只需改变一下传递姿势。

还拿刚才的例子举例,还是元素自加一,但是为了方便展示,我们把numpy矩阵改为二维的。首先创建一个10*10的值为1的二维矩阵m0,然后只取其左上角5*5的一部分m,可以看出m肯定是不连续的,现把m传参测试,python代码如下:

def 

结果:

(5, 5)
[[2 2 2 2 2 2 2 2 2 2]
 [2 2 2 2 2 2 2 2 2 2]
 [2 2 2 2 2 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1]]
[[2 2 2 2 2]
 [2 2 2 2 2]
 [2 2 2 2 2]
 [1 1 1 1 1]
 [1 1 1 1 1]]

可以看出,m0和m的值都被改变了,m的值并没有全部改变,只改变了一部分,而且m0刚好前5*5=25个值改变了,这说明了m并不是深拷贝,它仍然和m0共用一块内存空间,c++处理的时候只会连续着处理,所以只改变了连续空间的这一段长度为5*5的值。

当然,解决方法很简单,只需不让它们共用一份内存重新拷贝一份即可。上述第三行修改为:

m 

或:

m 

即可。

写在后面

前面已经说过,没有最好的方法,只有最适合自己的。我认为这种方法足够简单高效,前提是它能满足你的需求,比如类似传numpy矩阵这种。

对了,还有一点需要注意的地方,就是一定要保证numpy元素的数据类型跟c++中的一致,比如一个numpy的dtype为np.uint8,在c++中却使用int*,那肯定会出错,可以先使用astype函数把数据类型转成一致,然后再调c++。

还有,我本来要解决的问题是3D图像的连通区域求解,现已实现,速度贼快,感兴趣的小伙伴可以评论区回复。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值