使用ctypes数组时PIL的Image.frombuffer预期数据长度

发布于 2024-11-30 11:34:25 字数 2092 浏览 1 评论 0原文

我正在使用 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")

虽然不太漂亮,但它有效。评论了 TODO 并继续。初始管道到位后,分析显示此代码块存在重大性能问题。我想这并不奇怪。

这不起作用

与此问题类似:像素操作使用 PIL.Image 和 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*));
  • 缓冲区的每个像素每个像素有 4 个字节波段 RGBA。
  • Mac OS 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 分配的缓冲区不满意,但无论如何,这是更好的内存管理结构。感谢 ErykSun 和 HYRY!

I am using Python, PIL and ctypes to do image manipulation. As I hacked stuff together, I used PIL's fromstring function to get the pixel buffer from ctypes into a PIL object. I simply iterated over the array, building the python string.

This works

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")

It wasn't pretty, but it worked. Commented with a TODO and moved on. After getting the initial plumbing in place, profiling showed significant performance issues with this code block. Not surprising, I guess.

This does not work

Similar to this question: Pixel manipulation with PIL.Image and ctypes, I am trying to use frombuffer() to get the pixel data into the PIL object more efficiently:

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")

Despite fromstring working, using frombuffer results in the following exception:

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

Environment

The buffer is malloc'ed in C as:

unsigned char *pixels_color = (unsigned char*)malloc((WIDTH*HEIGHT*4)*sizeof(unsigned char*));
  • The buffer has 4 bytes per pixel for each of the bands RGBA.
  • Mac OS X 10.7, python2.7.1, PIL 1.1.7

Edit

Based on eryksun's comment and answer below, I moved the buffer allocation from the malloc in the C library into Python and passed the pointer into 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")

This works as expected. It still doesn't explain why PIL was unhappy with the C-allocated buffer, but this is the better structure for memory management anyway. Thanks to both ErykSun and HYRY!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

转身以后 2024-12-07 11:34:25

当您设置 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 = 转换(结果,POINTER(c_ubyte * 大小))。无论哪种方式,您都需要取消引用指针才能获取数组。使用 buf = pbuf[0]buf = pbuf.contents。然后您可以将 buf 传递给 Image.frombuffer

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

假设动态大小,调用者需要获取图像尺寸才能分配数组,因此我添加了一个包含图像宽度、高度和深度的结构体,该结构体由 C 函数填充>获取图像信息。函数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);

When you set tx.restype = POINTER(c_ubyte), the resulting ctypes pointer object is a buffer of either 4 or 8 bytes for the address of the image buffer. You need to create a ctypes array from this address.

If you know the size ahead of time, you can set SIZE = WIDTH * HEIGHT * 4; tx.restype = POINTER(c_ubyte * SIZE). Otherwise set tx.restype = POINTER(c_ubyte), and cast to the dynamic size, i.e. size = w * h * 4; pbuf = cast(result, POINTER(c_ubyte * size)). Either way, you need to dereference the pointer to get at the array. Use either buf = pbuf[0] or buf = pbuf.contents. Then you can pass buf to Image.frombuffer.

That said, it's usually simpler to allocate memory using ctypes. This gives you reference-counted memory management instead of having to manually free memory. The following is a toy example.

Assuming a dynamic size, the caller needs to get the image dimensions in order to allocate the array, so I've added a struct that has the width, height, and depth of the image, which gets filled in by the C function get_image_info. The function get_image receives a pointer to the image array to copy in data.

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 declarations:

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);
童话 2024-12-07 11:34:25

尝试以下代码:

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 的指针创建图像,您必须将其转换为指向数组的指针,并使用内容属性来获取缓冲区。因此使用以下代码将指针转换为数组:

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

try the following code:

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 is an ubyte array, pbuf is a pointer to ubyte, pbuf2 is a pointer to ubyte[400]. img1 is created from buf directly, img2 is created from pubf2.contents.

your program create image from an pointer to ubyte, you must cast it to pointer to array, and use contents attribute to get the buffer. So use the following code to convert pointer to array:

tmp = cast(result, POINTER(c_ubyte*4*w*h)).contents
Image.frombuffer('RGBA', (w, h), tmp, 'raw', 'RGBA', 0, 1)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文