我使用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评论并继续前进。获得初始管道后,分析显示此代码块存在严重的性能问题。我想这并不奇怪。
这不起作用
与此问题类似: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*));
修改
根据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!
答案 0 :(得分:3)
尝试以下代码:
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)
答案 1 :(得分:3)
设置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);