使用ctypes数组时,PIL的Image.frombuffer需要的数据长度

2024-04-25 14:00:36 发布

您现在位置:Python中文网/ 问答频道 /正文

我使用Python、PIL和ctypes进行图像处理。当我把这些东西放在一起时,我使用PIL的fromstring函数将ctypes中的像素缓冲区转换成PIL对象。我只是遍历数组,构建python字符串。

这有效

tx = foo.tx
tx.restype = POINTER(c_ubyte)
result = tx(...args...)

#TODO there must also be a better way to do this
pystr = ""
for i in xrange(w*h*4):
   pystr += result[i]
i = Image.fromstring("RGBA", (w, h), pystr)
i.save("out.png")

虽然不漂亮,但很管用。以待办事项发表评论并继续。在安装了初始管道之后,分析显示此代码块存在严重的性能问题。我想这并不奇怪。

这不起作用

类似于这个问题:Pixel manipulation with PIL.Image and ctypes,我试图使用frombuffer()来更有效地将像素数据导入PIL对象:

tx = foo.tx
tx.restype = POINTER(c_ubyte)
result = tx(...args...)
i = Image.frombuffer('RGBA', (w, h), result, 'raw', 'RGBA', 0, 1)
i.save("out.png")

尽管fromstring工作正常,但使用frombuffer会导致以下异常:

Traceback (most recent call last):
  File "test.py", line 53, in <module>
    i = Image.frombuffer('RGBA', (w, h), res, 'raw', 'RGBA', 0, 1)
  File "/Library/Python/2.7/site-packages/PIL/Image.py", line 1853, in frombuffer
    core.map_buffer(data, size, decoder_name, None, 0, args)
ValueError: buffer is not large enough

环境

缓冲区在C中是malloc'

unsigned char *pixels_color = (unsigned char*)malloc((WIDTH*HEIGHT*4)*sizeof(unsigned char*));
  • 缓冲区对于每个RGBA频带每像素有4个字节。
  • Mac操作系统X 10.7,python2.7.1,PIL 1.1.7

编辑

基于下面eryksun的注释和答案,我将缓冲区分配从C库中的malloc移到Python中,并将指针传递到C:

tx = foo.tx
tx.restype = POINTER(c_ubyte)

pix_clr = (c_ubyte*(w*h*4))()

tx(...args..., pix_clr)
i = Image.frombuffer('RGBA', (w, h), pix_clr, 'raw', 'RGBA', 0, 1)
i.save("out.png")

这和预期的一样。它仍然不能解释为什么PIL对C分配的缓冲区不满意,但这是内存管理的更好结构。感谢艾尔克森和海瑞!


Tags: imagepilfooargs像素resultctypes缓冲区
2条回答

当设置tx.restype = POINTER(c_ubyte)时,生成的ctypes指针对象是图像缓冲区的地址的4或8字节缓冲区。您需要从此地址创建ctypes数组。

如果提前知道大小,可以设置SIZE = WIDTH * HEIGHT * 4; tx.restype = POINTER(c_ubyte * SIZE)。否则设置tx.restype = POINTER(c_ubyte),并转换为动态大小,即size = w * h * 4; pbuf = cast(result, POINTER(c_ubyte * size))。不管怎样,都需要取消对指针的引用才能到达数组。使用buf = pbuf[0]buf = pbuf.contents。然后可以将buf传递给Image.frombuffer

也就是说,使用ctypes分配内存通常更简单。这将为您提供引用计数内存管理,而不必手动释放内存。下面是一个玩具的例子。

假设是动态大小,调用者需要获取图像维度才能分配数组,因此我添加了一个具有图像宽度、高度和深度的结构,该结构由C函数get_image_info填充。函数get_image接收指向要在数据中复制的图像数组的指针。

import ctypes

lib = ctypes.CDLL('imgtest.dll')

class ImageInfo(ctypes.Structure):
    _fields_ = (
        ('width', ctypes.c_int),
        ('height', ctypes.c_int),
        ('depth', ctypes.c_int),
    )

pImageInfo = ctypes.POINTER(ImageInfo)
pImage = ctypes.POINTER(ctypes.c_ubyte)

lib.get_image_info.argtypes = [pImageInfo]
lib.get_image_info.restype = ctypes.c_int

lib.get_image.argtypes = [pImage]
lib.get_image.restype = ctypes.c_int

imgnfo = ImageInfo()
lib.get_image_info(ctypes.byref(imgnfo))
w, h, d = imgnfo.width, imgnfo.height, imgnfo.depth

imgdata = (w * h * d * ctypes.c_ubyte)()
lib.get_image(imgdata)

from PIL import Image
img = Image.frombuffer('RGBA', (w, h), imgdata, 'raw', 'RGBA', 0, 1)

C声明:

typedef struct _ImageInfo {
    int width;
    int height;
    int depth;
} ImageInfo, *pImageInfo;

typedef unsigned char *pImage;

int get_image_info(pImageInfo imgnfo);
int get_image(pImage img);

请尝试以下代码:

import Image
from ctypes import c_ubyte, cast, POINTER

buf = (c_ubyte * 400)()
pbuf = cast(buf, POINTER(c_ubyte))
pbuf2 = cast(pbuf, POINTER(c_ubyte*400))

img1 = Image.frombuffer("RGBA", (10,10), buf, "raw", "RGBA", 0, 1)
img2 = Image.frombuffer("RGBA", (10,10), pbuf2.contents, "raw", "RGBA", 0, 1)

buf是一个ubyte数组,pbuf是指向ubyte的指针,pbuf2是指向ubyte的指针[400]。img1直接从buf创建,img2从pubf2.contents创建。

程序从指向ubyte的指针创建图像,必须将其转换为指向数组的指针,并使用contents属性获取缓冲区。因此,请使用以下代码将指针转换为数组:

tmp = cast(result, POINTER(c_ubyte*4*w*h)).contents
Image.frombuffer('RGBA', (w, h), tmp, 'raw', 'RGBA', 0, 1)

相关问题 更多 >