Ожидаемая длина данных PIL's Image.frombuffer при использовании массива ctypes - PullRequest
4 голосов
/ 22 августа 2011

Я использую 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

Среда

Буфер malloc 'имеет C-образный вид:

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Комментарий и ответ ниже, я переместил распределение буфера из malloc в библиотеке C в 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!

Ответы [ 2 ]

3 голосов
/ 22 августа 2011

Когда вы устанавливаете 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);
3 голосов
/ 22 августа 2011

попробуйте следующий код:

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