使用SWIG封送Python PIL图像
我有一个库,它使用一个非常简单的C语言图像结构:
// Represents a one-channel 8-bit image
typedef struct simple_image_t {
uint32 rows;
uint32 cols;
uint8 *imgdata;
} simple_image;
这个库和这个结构不是我创建的,所以我不能修改它。我负责用SWIG把这个库封装成Python。这个Python封装需要能够接收一个PIL图像,并把它转换成这个结构。现在我这样做(使用SWIG的%inline%
):
// Allows python to easily create and initialize this structure
simple_image* py_make_simple_image(uint32 width, uint32 height)
{
simple_image* img = new simple_image();
img->rows = height;
img->cols = width;
img->imgdata = new uint8[height * width];
return img;
}
// Allows python to set a particular pixel value
void py_set_simple_image(simple_image* img, uint32 pos, uint8 val)
{
img->imgdata[pos] = val;
}
然后在Python封装那边,现在的情况是这样的:
# Make sure it's an 8-bit image
if pil_image.mode != "L":
pil_image = pil_image.convert("L")
# Create the simple image structure
(width, height) = pil_image.size
img = swig_wrapper.py_make_simple_image(width, height)
try:
# Copy the image data into the simple image structure
pos = 0
for pixel in pil_image.getdata():
swig_wrapper.py_set_simple_image(img, pos, pixel)
pos += 1
# Call some library method that accepts a simple_image*
return swig_wrapper.some_image_method(img)
finally:
# Clean up the simple image structure
swig_wrapper.py_destroy_simple_image(img)
令人惊讶的是,这个方法有效,但你可能猜到了,当处理稍微大一点的图像时,它的速度非常慢。我知道使用SWIG的正确方法是使用类型映射,但这意味着我得深入了解PIL的C API,而我现在没有时间去做这个。
在速度方面我有哪些选择?有没有更快的方法可以把PIL图像的像素数据转到这个简单的图像结构里?有没有人已经做过这个,而我的谷歌搜索技能就是这么差?我是不是只能无奈地去学习PIL的内部结构了?
谢谢。
3 个回答
你可以试试使用 ctypes 这个库。它让你可以直接访问C语言的结构体,这样就不需要在Python中再创建一个相同的结构体了。而且你还可以使用memcpy这个方法,这样复制数据会比逐个像素复制要快很多。
你可以试着用 array
模块把图片转换成字符数组,然后再通过 swig 把这些数据复制到你的 C 数组里。
import array
imagar = array.array('B', pil_image.getdata())
(mem, length) = imagar.buffer_info()
swig_wrapper.py_copy(img, mem, length)
这里的 py_copy
可能是这样的:
void py_copy(simple_image* img, uint32 mem, uint32 length) {
memcpy((void*)img->imgdata ,(void*)mem, length );
}
PIL的Image.tostring()
方法会返回你需要的imgdata
的确切数据。这里用的类型映射(typemap)比较简单,但并不完美,下面我会提到这一点。以下是我在Windows上创建的示例代码,它对我来说是有效的:
sample.h
typedef unsigned int uint32;
typedef unsigned char uint8;
typedef struct simple_image_t {
uint32 rows;
uint32 cols;
uint8 *imgdata;
} simple_image;
#ifdef SAMPLE_EXPORT
# define SAMPLE_API __declspec(dllexport)
#else
# define SAMPLE_API __declspec(dllimport)
#endif
SAMPLE_API void some_func(const simple_image* si);
sample.c
#include <stdio.h>
#define SAMPLE_EXPORT
#include "sample.h"
void some_func(const simple_image* si)
{
uint32 i,j;
printf(
"rows = %d\n"
"cols = %d\n",
si->rows,si->cols);
/* Dump a simple map of the image data */
for(i = 0; i < si->rows; i++)
{
for(j = 0; j < si->cols; j++)
{
if(si->imgdata[i * si->rows + j] < 0x80)
printf(" ");
else
printf("*");
}
printf("\n");
}
}
sample.i
%module sample
%begin %{
#pragma warning(disable:4100 4127 4706)
%}
%{
#include "sample.h"
%}
%include <windows.i>
%typemap(in) uint8* (char* buffer, Py_ssize_t length) {
PyString_AsStringAndSize($input,&buffer,&length);
$1 = (uint8*)buffer;
}
%include "sample.h"
makefile
all: _sample.pyd
sample.dll: sample.c sample.h
cl /nologo /W4 /LD /MD sample.c
sample_wrap.c: sample.i
@echo sample.i
swig -python sample.i
_sample.pyd: sample_wrap.c sample.dll
cl /nologo /W4 /LD /MD /Fe_sample.pyd sample_wrap.c /Ic:\Python27\include -link /LIBPATH:c:\Python27\libs python27.lib sample.lib
example.py
from PIL import Image
import sample
im = Image.open('sample.gif')
im = im.convert('L')
si = sample.simple_image()
si.rows,si.cols = im.size
s = im.tostring() # Must keep a reference
si.imgdata = s
sample.some_func(si)
在这个简单的例子中,我还没有确定类型映射应该如何正确地增加字符串对象的引用计数。请注意,如果使用以下代码,上面的代码可能会崩溃:
si.imgdata = im.tostring()
当前的类型映射PyString_AsStringAndSize
返回的是指向PyString对象缓冲区的直接指针,但并没有增加该对象的引用计数。这意味着在some_func
执行之前,它可能会被垃圾回收(我遇到过这种情况,导致Python崩溃)。将其赋值给s
可以保持对字符串的引用,从而避免问题。类型映射应该复制缓冲区,但你可能是为了速度,所以这个小技巧可能正是你想要的。