使用 PIL 将 RGBA PNG 转换为 RGB

我正在使用 PIL 来转换一个透明的 PNG 图像与 Django 上传到一个 JPG 文件。

源文件

transparent source file

密码

Image.open(object.logo.path).save('/tmp/output.jpg', 'JPEG')

或者

Image.open(object.logo.path).convert('RGB').save('/tmp/output.png')

结果

不管怎样,结果图像看起来像这样:

resulting file

有没有办法解决这个问题? 我想有白色的背景下的透明背景曾经是。


解决方案

由于这些很棒的答案,我想出了以下函数集合:

import Image
import numpy as np




def alpha_to_color(image, color=(255, 255, 255)):
"""Set all fully transparent pixels of an RGBA image to the specified color.
This is a very simple solution that might leave over some ugly edges, due
to semi-transparent areas. You should use alpha_composite_with color instead.


Source: http://stackoverflow.com/a/9166671/284318


Keyword Arguments:
image -- PIL RGBA Image object
color -- Tuple r, g, b (default 255, 255, 255)


"""
x = np.array(image)
r, g, b, a = np.rollaxis(x, axis=-1)
r[a == 0] = color[0]
g[a == 0] = color[1]
b[a == 0] = color[2]
x = np.dstack([r, g, b, a])
return Image.fromarray(x, 'RGBA')




def alpha_composite(front, back):
"""Alpha composite two RGBA images.


Source: http://stackoverflow.com/a/9166671/284318


Keyword Arguments:
front -- PIL RGBA Image object
back -- PIL RGBA Image object


"""
front = np.asarray(front)
back = np.asarray(back)
result = np.empty(front.shape, dtype='float')
alpha = np.index_exp[:, :, 3:]
rgb = np.index_exp[:, :, :3]
falpha = front[alpha] / 255.0
balpha = back[alpha] / 255.0
result[alpha] = falpha + balpha * (1 - falpha)
old_setting = np.seterr(invalid='ignore')
result[rgb] = (front[rgb] * falpha + back[rgb] * balpha * (1 - falpha)) / result[alpha]
np.seterr(**old_setting)
result[alpha] *= 255
np.clip(result, 0, 255)
# astype('uint8') maps np.nan and np.inf to 0
result = result.astype('uint8')
result = Image.fromarray(result, 'RGBA')
return result




def alpha_composite_with_color(image, color=(255, 255, 255)):
"""Alpha composite an RGBA image with a single color image of the
specified color and the same size as the original image.


Keyword Arguments:
image -- PIL RGBA Image object
color -- Tuple r, g, b (default 255, 255, 255)


"""
back = Image.new('RGBA', size=image.size, color=color + (255,))
return alpha_composite(image, back)




def pure_pil_alpha_to_color_v1(image, color=(255, 255, 255)):
"""Alpha composite an RGBA Image with a specified color.


NOTE: This version is much slower than the
alpha_composite_with_color solution. Use it only if
numpy is not available.


Source: http://stackoverflow.com/a/9168169/284318


Keyword Arguments:
image -- PIL RGBA Image object
color -- Tuple r, g, b (default 255, 255, 255)


"""
def blend_value(back, front, a):
return (front * a + back * (255 - a)) / 255


def blend_rgba(back, front):
result = [blend_value(back[i], front[i], front[3]) for i in (0, 1, 2)]
return tuple(result + [255])


im = image.copy()  # don't edit the reference directly
p = im.load()  # load pixel array
for y in range(im.size[1]):
for x in range(im.size[0]):
p[x, y] = blend_rgba(color + (255,), p[x, y])


return im


def pure_pil_alpha_to_color_v2(image, color=(255, 255, 255)):
"""Alpha composite an RGBA Image with a specified color.


Simpler, faster version than the solutions above.


Source: http://stackoverflow.com/a/9459208/284318


Keyword Arguments:
image -- PIL RGBA Image object
color -- Tuple r, g, b (default 255, 255, 255)


"""
image.load()  # needed for split()
background = Image.new('RGB', image.size, color)
background.paste(image, mask=image.split()[3])  # 3 is the alpha channel
return background

表演

简单的非合成 alpha_to_color函数是最快的解决方案,但留下了难看的边界,因为它不处理半透明区域。

无论是纯 PIL 还是麻烦的复合解决方案都给出了很好的结果,但是 alpha_composite_with_color(8.93毫秒)比 pure_pil_alpha_to_color(79.6毫秒)快得多。如果在您的系统上可以使用 numpy,那么就应该这样做。(更新: 新的纯 PIL 版本是所有提到的解决方案中最快的。)

$ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.alpha_to_color(i)"
10 loops, best of 3: 4.67 msec per loop
$ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.alpha_composite_with_color(i)"
10 loops, best of 3: 8.93 msec per loop
$ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.pure_pil_alpha_to_color(i)"
10 loops, best of 3: 79.6 msec per loop
$ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.pure_pil_alpha_to_color_v2(i)"
10 loops, best of 3: 1.1 msec per loop
191821 次浏览

没有骨折。它完全按照你说的做了那些像素是完全透明的黑色。您需要遍历所有像素,并将完全透明的像素转换为白色。

透明部分大多具有 RGBA 值(0,0,0,0)。因为 JPG 没有透明度,所以 jpeg 值被设置为(0,0,0) ,即黑色。

在圆形图标周围,有一些像素的 RGB 值不为零,其中 A = 0。所以它们在 PNG 中看起来是透明的,但在 JPG 中却是有趣的颜色。

你可以像这样使用 numpy 将所有像素(A = = 0)设置为 R = G = B = 255:

import Image
import numpy as np


FNAME = 'logo.png'
img = Image.open(FNAME).convert('RGBA')
x = np.array(img)
r, g, b, a = np.rollaxis(x, axis = -1)
r[a == 0] = 255
g[a == 0] = 255
b[a == 0] = 255
x = np.dstack([r, g, b, a])
img = Image.fromarray(x, 'RGBA')
img.save('/tmp/out.jpg')

enter image description here


请注意,该标志还有一些半透明像素用于平滑的边缘周围的文字和图标。保存为 jpeg 将忽略半透明性,从而使得生成的 jpeg 看起来非常参差不齐。

使用 image magick 的 convert命令可以得到更高质量的结果:

convert logo.png -background white -flatten /tmp/out.jpg

enter image description here


为了使用 numpy 制作一个更好的质量混合,你可以使用 阿尔法复合:

import Image
import numpy as np


def alpha_composite(src, dst):
'''
Return the alpha composite of src and dst.


Parameters:
src -- PIL RGBA Image object
dst -- PIL RGBA Image object


The algorithm comes from http://en.wikipedia.org/wiki/Alpha_compositing
'''
# http://stackoverflow.com/a/3375291/190597
# http://stackoverflow.com/a/9166671/190597
src = np.asarray(src)
dst = np.asarray(dst)
out = np.empty(src.shape, dtype = 'float')
alpha = np.index_exp[:, :, 3:]
rgb = np.index_exp[:, :, :3]
src_a = src[alpha]/255.0
dst_a = dst[alpha]/255.0
out[alpha] = src_a+dst_a*(1-src_a)
old_setting = np.seterr(invalid = 'ignore')
out[rgb] = (src[rgb]*src_a + dst[rgb]*dst_a*(1-src_a))/out[alpha]
np.seterr(**old_setting)
out[alpha] *= 255
np.clip(out,0,255)
# astype('uint8') maps np.nan (and np.inf) to 0
out = out.astype('uint8')
out = Image.fromarray(out, 'RGBA')
return out


FNAME = 'logo.png'
img = Image.open(FNAME).convert('RGBA')
white = Image.new('RGBA', size = img.size, color = (255, 255, 255, 255))
img = alpha_composite(img, white)
img.save('/tmp/out.jpg')

enter image description here

这里有一个纯 PIL 的解决方案。

def blend_value(under, over, a):
return (over*a + under*(255-a)) / 255


def blend_rgba(under, over):
return tuple([blend_value(under[i], over[i], over[3]) for i in (0,1,2)] + [255])


white = (255, 255, 255, 255)


im = Image.open(object.logo.path)
p = im.load()
for y in range(im.size[1]):
for x in range(im.size[0]):
p[x,y] = blend_rgba(white, p[x,y])
im.save('/tmp/output.png')

这是一个更简单的版本——不确定它的性能如何。基于我在构建对 sorl 缩略图的 RGBA -> JPG + BG支持时发现的一些 django 代码片段。

from PIL import Image


png = Image.open(object.logo.path)
png.load() # required for png.split()


background = Image.new("RGB", png.size, (255, 255, 255))
background.paste(png, mask=png.split()[3]) # 3 is the alpha channel


background.save('foo.jpg', 'JPEG', quality=80)

结果@80%

enter image description here

结果@50%
enter image description here

通过使用 Image.alpha_composite,富田裕二的解决方案变得更加简单。如果 png 没有 alpha 通道,这段代码可以避免 tuple index out of range错误。

from PIL import Image


png = Image.open(img_path).convert('RGBA')
background = Image.new('RGBA', png.size, (255, 255, 255))


alpha_composite = Image.alpha_composite(background, png)
alpha_composite.save('foo.jpg', 'JPEG', quality=80)
from PIL import Image
 

def fig2img ( fig ):
"""
@brief Convert a Matplotlib figure to a PIL Image in RGBA format and return it
@param fig a matplotlib figure
@return a Python Imaging Library ( PIL ) image
"""
# put the figure pixmap into a numpy array
buf = fig2data ( fig )
w, h, d = buf.shape
return Image.frombytes( "RGBA", ( w ,h ), buf.tostring( ) )


def fig2data ( fig ):
"""
@brief Convert a Matplotlib figure to a 4D numpy array with RGBA channels and return it
@param fig a matplotlib figure
@return a numpy 3D array of RGBA values
"""
# draw the renderer
fig.canvas.draw ( )
 

# Get the RGBA buffer from the figure
w,h = fig.canvas.get_width_height()
buf = np.fromstring ( fig.canvas.tostring_argb(), dtype=np.uint8 )
buf.shape = ( w, h, 4 )
 

# canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode
buf = np.roll ( buf, 3, axis = 2 )
return buf


def rgba2rgb(img, c=(0, 0, 0), path='foo.jpg', is_already_saved=False, if_load=True):
if not is_already_saved:
background = Image.new("RGB", img.size, c)
background.paste(img, mask=img.split()[3]) # 3 is the alpha channel


background.save(path, 'JPEG', quality=100)
is_already_saved = True
if if_load:
if is_already_saved:
im = Image.open(path)
return np.array(im)
else:
raise ValueError('No image to load.')
import numpy as np
import PIL


def convert_image(image_file):
image = Image.open(image_file) # this could be a 4D array PNG (RGBA)
original_width, original_height = image.size


np_image = np.array(image)
new_image = np.zeros((np_image.shape[0], np_image.shape[1], 3))
# create 3D array


for each_channel in range(3):
new_image[:,:,each_channel] = np_image[:,:,each_channel]
# only copy first 3 channels.


# flushing
np_image = []
return new_image