Python科学计算(第2版)
上QQ阅读APP看书,第一时间看更新

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