使用PIL将RGBA PNG转换为RGB


96

我正在使用PIL将使用Django上传的透明PNG图像转换为JPG文件。输出看起来坏了。

源文件

透明源文件

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

要么

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

结果

两种方式的结果图像如下所示:

结果文件

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


多亏了出色的答案,我提出了以下函数集合:

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和numpy合成解决方案都可以提供出色的结果,但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

对于更高的速度,我相信im = image.copy()可以在pure_pil_alpha_to_color_v2不更改结果的情况下将其删除。(改变后的后续实例imimage,当然。)
unutbu

@unutbu啊,当然:)谢谢。
Danilo Bargen 2012年

Answers:


127

这是一个简单得多的版本-不确定性能如何。很大程度上基于我在构建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%

在此处输入图片说明

结果@ 50%
在此处输入图片说明


1
看来您的版本是最快的版本:pastebin.com/mC4Wgqzv谢谢!不过,关于您的帖子有两件事:png.load()命令似乎是不必要的,第4行应该是background = Image.new("RGB", png.size, (255, 255, 255))
Danilo Bargen 2012年

3
祝贺您弄清楚如何进行paste适当的混合。
Mark Ransom

@DaniloBargen,啊!确实缺少大小,但是该load方法是必需的split。这真是太快了,而且很简单!
Yuji'Tomita'Tomita 2012年

@YujiTomita:谢谢你!
unutbu 2012年

12
该代码对我造成了错误:tuple index out of range。我通过关注另一个问题(stackoverflow.com/questions/1962795/…)来解决此问题。我必须先将PNG转换为RGBA,然后将其切片:alpha = img.split()[-1]然后在背景蒙版上使用它。
joehand 2012年

37

通过使用Image.alpha_composite,Yuji'Tomita'Tomita的解决方案变得更简单。tuple index out of range如果png没有alpha通道,则此代码可以避免错误。

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)

这对我来说是最好的解决方案,因为我所有的图像都没有Alpha通道。
lenhhoxung '16

2
当我使用此代码的PNG对象的方式仍是“RGBA”
logic1976

1
@ logic1976只是.convert("RGB")在保存之前投入了一个
josch

13

透明部分大部分具有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')

在此处输入图片说明


请注意,徽标还具有一些半透明像素,用于平滑单词和图标周围的边缘。保存为jpeg会忽略半透明性,从而使生成的jpeg看起来参差不齐。

使用imagemagick的convert命令可以得到更好的质量结果:

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

在此处输入图片说明


为了使用numpy进行质量更好的混合,您可以使用alpha合成

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

在此处输入图片说明


谢谢,这种解释很有道理:)
Danilo Bargen'2

@DaniloBargen,您是否注意到转换质量很差?此解决方案不考虑部分透明性。
Mark Ransom'2

@MarkRansom:是的。你知道怎么解决吗?
unutbu 2012年

它需要基于alpha值的完全混合(带有白色)。我一直在寻找PIL的自然方法,但我空了。
Mark Ransom'2

@MarkRansom是的,我已经注意到了这个问题。但就我而言,这只会影响很小一部分输入数据,因此质量对我来说足够好。
Danilo Bargen '02

4

这是纯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')

谢谢,这很好。但是numpy解决方案似乎要快得多:pastebin.com/rv4zcpAV(numpy:8.92ms,pil:79.7ms)
Danilo Bargen

好像还有另一个更快的纯PIL版本。查看新答案。
Danilo Bargen 2012年

2
@DaniloBargen,谢谢-非常感谢看到更好的答案,如果您没有引起我的注意,我也不会知道。
Mark Ransom


0
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

-1

导入图片

def fig2img(fig):“”“ @brief将Matplotlib图形转换为RGBA格式的PIL图像并返回@param图matplotlib图形@返回Python图像库(PIL)图像”“”#将图形像素图放入一个numpy数组buf = fig2data(fig)w,h,d = buf.shape return Image.frombytes(“ RGBA”,(w,h),buf.tostring())

def fig2data(fig):“”“ @brief将Matplotlib图形转换为具有RGBA通道的4D numpy数组,然后将其返回@p​​aram图matplotlib图形@返回RGBA值的numpy 3D数组”“”#绘制渲染器图。 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):如果不是is_already_saved:background = Image.new(“ RGB”,img.size, c)background.paste(img,mask = img.split()[3])#3是Alpha通道

    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.')
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.