
2.5.2 和其他对象共享内存
在前面的章节中介绍过,当其他对象提供了获取其内部数据存取区的接口时,可以是用numpy.frombuffer( )创建一个数组与此对象共享数据内存。如果对象没有提供该接口,但是能够获取数据存储区的地址,可以通过ctypes和numpy.ctypeslib模块中提供的函数,创建与对象共享内存的数组。下面以PyQt4中的QImage对象为例,介绍如何创建一个与QImage对象共享内存的数组。
首先创建一个QImage对象,并载入"lena.png"文件中的内容。然后输出与图像相关的一些信息,为了创建与该图像共享内存的数组,我们需要使用这些信息。
from PyQt4.QtGui import QImage, qRgb img = QImage("lena.png") print "width & height:", img.width( ), img.height( ) print "depth:", img.depth( ) #每个像素的比特数 print "format:", img.format( ), QImage.Format_RGB32 print "byteCount:", img.byteCount( ) #图像的总字节数 print "bytesPerLine:", img.bytesPerLine( ) #每行的字节数 print "bits:", int(img.bits( )) #图像第一个字节的地址 width & height: 512 393 depth: 32 format: 4 4 byteCount: 804864 bytesPerLine: 2048 bits: 156041248
❶由于我们只知道数据的地址,首先需要使用ctypes.cast( )将整数转换为一个指向单字节类型的指针。❷然后使用numpy.ctypeslib.as_array( )将ctypes的指针指向的内存转换成NumPy的数组。as_array( )的第二个参数是该数组的形状,注意数组的第0轴为图像的高,第1轴为图像的宽,第2轴为每个像素的字节数。
import ctypes addr = int(img.bits( )) pointer = ctypes.cast(addr, ctypes.POINTER(ctypes.c_uint8)) ❶ arr = np.ctypeslib.as_array(pointer, (img.height( ), img.width( ), img.depth( )//8)) ❷
下面通过arr数组和img对象查看位于像素坐标(50,100)处的像素颜色值,可以看到二者是完全相同的:
x, y = 100, 50 b, g, r, a = arr[y, x] print qRgb(r, g, b) print img.pixel(x, y) 4289282380 4289282380
下面通过arr数组修改颜色值,并通过img对象查看修改的结果,由结果可知二者的确共享着同一块内存:
arr[y, x, :3] = 0x12, 0x34, 0x56 print hex(img.pixel(x, y)) 0xff563412L
使用上述方法共享内存时需注意必须保持目标对象处于可访问状态。例如在上例中,如果执行del img语句引起img对象被垃圾回收,则通过arr数组将访问被释放掉的内存区域。为了解决这个问题,可以让数组的base属性引用目标对象,这样只要数组不被释放,则目标对象也不会被释放。为了能正确设置base属性,需要使用数组的__array_interface__接口。
❶在调用array( )将目标对象转换成数组时,如果目标对象拥有__array_interface__属性,则根据该属性的描述创建数组。它是一个具有特定键值的字典,参见表2-10。
表2-10 键值及含义

❷设置copy参数为False,这样所创建的数组与目标对象共享内存,否则将复制目标对象的内存。❸在创建完数组之后,可以删除__array_interface__属性。❹所得到的数组arr2与arr相同,并且其base属性为img对象。
interface = { 'shape': (img.height( ), img.width( ), 4), 'data': (int(img.bits( )), False), 'strides': (img.bytesPerLine( ), 4, 1), 'typestr': "|u1", 'version': 3, } img.__array_interface__ = interface ❶ arr2 = np.array(img, copy=False) ❷ del img.__array_interface__ ❸ print np.all(arr2 == arr), arr2.base is img ❹ True True
如果目标对象只读,无法为其添加__array_interface__属性,可以创建一个代理用的ArrayProxy对象,在该代理对象中引用目标对象,使其不会被垃圾回收,同时提供__array_interface__属性,以供创建相应的数组。
class ArrayProxy(object): def __init__(self, base, interface): self.base = base self.__array_interface__ = interface arr3 = np.array(ArrayProxy(img, interface), copy=False) print np.all(arr3 == arr) True