如何使用PIL使所有白色像素透明?

88 人关注

我试图用Python图像库使所有的白色像素透明。 (我是一个试图学习Python的C黑客,所以请温柔一点) 我已经完成了转换(至少像素值看起来是正确的),但我不知道如何将列表转换成缓冲区来重新创建图像。 以下是我的代码

img = Image.open('img.png')
imga = img.convert("RGBA")
datas = imga.getdata()
newData = list()
for item in datas:
    if item[0] == 255 and item[1] == 255 and item[2] == 255:
        newData.append([255, 255, 255, 0])
    else:
        newData.append(item)
imgb = Image.frombuffer("RGBA", imga.size, newData, "raw", "RGBA", 0, 1)
imgb.save("img2.png", "PNG")
    
python
image
python-imaging-library
haseman
haseman
发布于 2009-04-20
11 个回答
cr333
cr333
发布于 2021-04-29
已采纳
0 人赞同

你需要做以下修改。

  • append a tuple (255, 255, 255, 0) and not a list [255, 255, 255, 0]
  • use img.putdata(newData)
  • This is the working code:

    from PIL import Image
    img = Image.open('img.png')
    img = img.convert("RGBA")
    datas = img.getdata()
    newData = []
    for item in datas:
        if item[0] == 255 and item[1] == 255 and item[2] == 255:
            newData.append((255, 255, 255, 0))
        else:
            newData.append(item)
    img.putdata(newData)
    img.save("img2.png", "PNG")
        
    只是为了可能地给你节省一些时间。如果你正在使用Python3,你必须使用Pillow( python-pillow.org ),而不是PIL。
    For GIF, it seems transparency 需要作为参数用于 保存 (Pillow 5.1.0)。另见 如何用PIL(Python-imaging)创建一个透明的gif(或png)。 .
    RGBA "中的A代表 "alpha",意味着 "不透明度"。 所以在这里, newData.append((255,255,255,0)) 中的 0 意味着 "0不透明度";换句话说,"完全透明"。 进一步的解释可能有助于好奇的新手。 我猜测 putdata() 会突变PIL对象,但我不知道在引擎盖下发生了什么。
    有趣的是,这将翻转一些图像--知道为什么吗?
    什么样的翻转?你能说得更具体些吗?
    keithb
    keithb
    发布于 2021-04-29
    0 人赞同

    你也可以使用像素访问模式来就地修改图像。

    from PIL import Image
    img = Image.open('img.png')
    img = img.convert("RGBA")
    pixdata = img.load()
    width, height = img.size
    for y in range(height):
        for x in range(width):
            if pixdata[x, y] == (255, 255, 255, 255):
                pixdata[x, y] = (255, 255, 255, 0)
    img.save("img2.png", "PNG")
    

    如果你经常使用,你或许也可以把上述内容包成一个脚本。

    作为效率的参考点,上述循环在我的普通机器上处理一张256x256的图片需要大约0.05秒。这比我预期的要快。
    好处:这在巨大的图像(32000x32000 px)上确实有效。在一个高端服务器上测试,我试过的所有其他方法在这个尺寸上都出了内存错误,但已经能够处理(22000x22000 px)。缺点:这比我试过的其他方法要慢,比如用numpy替换数值,然后用 Image.fromarray 把它弄回一个PIL对象。为了补充@MKatz的参考意见,对于一张32000x32000 px的图片,这个方法运行了7分15秒。
    嘿,有没有一种方法可以让所有的颜色都透明,除了一种颜色?我试着用for循环,但它花费了太多的时间!帮助
    @NithinSai 创建一个只复制原图中一种颜色的副本如何?
    @NithinSai 如果这有帮助,请联系我。 stackoverflow.com/questions/52315895/...
    Marco Spinaci
    Marco Spinaci
    发布于 2021-04-29
    0 人赞同

    由于这是目前谷歌搜索 "Pillow white to transparent "的第一个结果,我想补充的是,用numpy也可以实现同样的效果,而且在我的基准测试中(一张有很多白色背景的800万像素的图片),速度要快10倍左右(大约300ms vs 3.28s的拟议解决方案)。代码也更短一些。

    import numpy as np
    def white_to_transparency(img):
        x = np.asarray(img.convert('RGBA')).copy()
        x[:, :, 3] = (255 * (x[:, :, :3] != 255).any(axis=2)).astype(np.uint8)
        return Image.fromarray(x)
    

    它也很容易变成 "几乎是白色的"(例如一个通道是254而不是255)是 "几乎透明的 "版本。当然,这将使整个图片部分透明,除了纯黑色。

    def white_to_transparency_gradient(img):
        x = np.asarray(img.convert('RGBA')).copy()
        x[:, :, 3] = (255 - x[:, :, :3].mean(axis=2)).astype(np.uint8)
        return Image.fromarray(x)
    

    备注:需要.copy()是因为默认情况下,Pillow图像被转换为只读数组。

    This function will cost lots of memorys.
    为什么是很多?它在空间上仍然是线性的,当然你需要创建一些额外的数组,但即使你把所有的东西都考虑进去,也可能是5倍的空间(可能更少),对于10倍的速度来说,这是一个很好的权衡(另外,如果你在如此紧张的条件下工作,你不能在内存中创建5个图像,那么可能python不是你任务的正确语言...)。
    gotounix
    我在一个1G的VPS上使用,总是得到内存错误的异常,而增加VPS的内存时,一切都很正常。
    你能解释一下为什么使用axe=2吗? 我认为应该是axe=3,因为我们要使Alpha'A'通道透明。
    一张图片总共有3个轴--高度、宽度和通道--所以axis=3会引起一个错误。我们要保存到alpha的事实被赋值的lhs所包含,也就是说,我们要写进第三个轴的索引3(R=0,G=1,B=2,alpha=3)。rhs上的 .any(axis=2) 意味着你想得到第三轴的前三个指数(R、G或B)中至少有一个指数(因为它是 [:, :, :3] )不同于255的像素。
    Dardo
    Dardo
    发布于 2021-04-29
    0 人赞同
    import Image
    import ImageMath
    def distance2(a, b):
        return (a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]) + (a[2] - b[2]) * (a[2] - b[2])
    def makeColorTransparent(image, color, thresh2=0):
        image = image.convert("RGBA")
        red, green, blue, alpha = image.split()
        image.putalpha(ImageMath.eval("""convert(((((t - d(c, (r, g, b))) >> 31) + 1) ^ 1) * a, 'L')""",
            t=thresh2, d=distance2, c=color, r=red, g=green, b=blue, a=alpha))
        return image
    if __name__ == '__main__':
        import sys
        makeColorTransparent(Image.open(sys.argv[1]), (255, 255, 255)).save(sys.argv[2]);
        
    kdebugging
    kdebugging
    发布于 2021-04-29
    0 人赞同

    由于循环对一张大图片来说需要很长的时间,所以采用了一种更加pythonic的方式。

    from PIL import Image
    img = Image.open('img.png')
    img = img.convert("RGBA")
    imgnp = np.array(img)
    white = np.sum(imgnp[:,:,:3], axis=2)
    white_mask = np.where(white == 255*3, 1, 0)
    alpha = np.where(white_mask, 0, imgnp[:,:,-1])
    imgnp[:,:,-1] = alpha 
    img = Image.fromarray(np.uint8(imgnp))
    img.save("img2.png", "PNG")
        
    PythonProgrammi
    PythonProgrammi
    发布于 2021-04-29
    0 人赞同

    Python 3版本,所有文件都在一个目录中

    import glob
    from PIL import Image
    def transparent(myimage):
        img = Image.open(myimage)
        img = img.convert("RGBA")
        pixdata = img.load()
        width, height = img.size
        for y in range(height):
            for x in range(width):
                if pixdata[x, y] == (255, 255, 255, 255):
                    pixdata[x, y] = (255, 255, 255, 0)
        img.save(myimage, "PNG")
    for image in glob.glob("*.png"):
        transparent(image)
        
    Jonathan Dauwe
    Jonathan Dauwe
    发布于 2021-04-29
    0 人赞同

    这个函数结合了之前解决方案的所有优点:它允许任何背景,并使用numpy(这比经典的列表更快)。

    import numpy as np
    from PIL import Image
    def convert_png_transparent(src_file, dst_file, bg_color=(255,255,255)):
        image = Image.open(src_file).convert("RGBA")
        array = np.array(image, dtype=np.ubyte)
        mask = (array[:,:,:3] == bg_color).all(axis=2)
        alpha = np.where(mask, 0, 255)
        array[:,:,-1] = alpha
        Image.fromarray(np.ubyte(array)).save(dst_file, "PNG")
        
    egeres
    egeres
    发布于 2021-04-29
    0 人赞同

    我很惊讶,没有人看到不仅需要改变一个特定的颜色,而是需要改变该颜色与其他颜色的混合。这就是Gimp的 "颜色转α "功能。将cr333的代码扩展为 https://stackoverflow.com/a/62334218/5189462 我们得到类似于这种功能的东西。

    from PIL import Image
    target_color = (255, 255, 255)
    img   = Image.open('img.png')
    imga  = img.convert("RGBA")
    datas = imga.getdata()
    newData = list()
    for item in datas:
        newData.append((
            item[0], item[1], item[2],
                abs(item[0] - target_color[0]), 
                abs(item[1] - target_color[1]), 
                abs(item[2] - target_color[2]), 
    imgb = Image.frombuffer("RGBA", imga.size, newData, "raw", "RGBA", 0, 1)
    imgb.save("img2.png", "PNG")
        
    leifdenby
    leifdenby
    发布于 2021-04-29
    0 人赞同

    @egeres使用到目标颜色的距离来创建一个alpha值的方法真的很整洁,并创造了一个更漂亮的结果。这里是使用numpy。

    import numpy as np
    import matplotlib.pyplot as plt
    def color_to_alpha(im, target_color):
        alpha = np.max(
                np.abs(im[..., 0] - target_color[0]),
                np.abs(im[..., 1] - target_color[1]),
                np.abs(im[..., 2] - target_color[2]),
            axis=0,
        ny, nx, _ = im.shape
        im_rgba = np.zeros((ny, nx, 4), dtype=im.dtype)
        for i in range(3):
            im_rgba[..., i] = im[..., i]
        im_rgba[..., 3] = alpha
        return im_rgba
    target_color = (0.0, 0.0, 0.0)
    im = plt.imread("img.png")
    im_rgba = color_to_alpha(im, target_color)
    

    为了完整起见,我在下面附上了与应用于matplotlib标识的基于遮罩的版本的对比。

    from pathlib import Path
    import matplotlib.pyplot as pl
    import numpy as np
    def color_to_alpha(im, alpha_color):
        alpha = np.max(
                np.abs(im[..., 0] - alpha_color[0]),
                np.abs(im[..., 1] - alpha_color[1]),
                np.abs(im[..., 2] - alpha_color[2]),
            axis=0,
        ny, nx, _ = im.shape
        im_rgba = np.zeros((ny, nx, 4), dtype=im.dtype)
        for i in range(3):
            im_rgba[..., i] = im[..., i]
        im_rgba[..., 3] = alpha
        return im_rgba
    def color_to_alpha_mask(im, alpha_color):
        mask = (im[..., :3] == alpha_color).all(axis=2)
        alpha = np.where(mask, 0, 255)
        ny, nx, _ = im.shape
        im_rgba = np.zeros((ny, nx, 4), dtype=im.dtype)
        im_rgba[..., :3] = im
        im_rgba[..., -1] = alpha
        return im_rgba
    # load example from images included with matplotlib
    fn_img = Path(plt.__file__).parent / "mpl-data" / "images" / "matplotlib_large.png"
    im = plt.imread(fn_img)[..., :3]  # get rid of alpha channel already in image
    target_color = [1.0, 1.0, 1.0]
    im_rgba = color_to_alpha(im, target_color)
    im_rgba_masked = color_to_alpha_mask(im, target_color)
    fig, axes = plt.subplots(ncols=3, figsize=(12, 4))
    [ax.set_facecolor("lightblue") for ax in axes]
    axes[0].imshow(im)
    axes[0].set_title("original")
    axes[1].imshow(im_rgba)
    axes[1].set_title("using distance to color")
    axes[2].imshow(im_rgba_masked)
    axes[2].set_title("mask on color")
        
    L.Lauenburg
    L.Lauenburg
    发布于 2021-04-29
    0 人赞同

    我非常喜欢Jonathan的回答。可以用NumPy和不使用 np.where 的另一种方式来实现。

    import numpy as np
    from PIL import Image
    img = Image.open('img.png') # n x m x 3
    imga = img.convert("RGBA")  # n x m x 4
    imga = np.asarray(imga) 
    r, g, b, a = np.rollaxis(imga, axis=-1) # split into 4 n x m arrays 
    r_m = r != 255 # binary mask for red channel, True for all non white values
    g_m = g != 255 # binary mask for green channel, True for all non white values
    b_m = b != 255 # binary mask for blue channel, True for all non white values
    # combine the three masks using the binary "or" operation 
    # multiply the combined binary mask with the alpha channel
    a = a * ((r_m == 1) | (g_m == 1) | (b_m == 1))
    # stack the img back together 
    imga =  Image.fromarray(np.dstack([r, g, b, a]), 'RGBA')
    

    我把我的方法与keithb的方法(评分最高的答案)进行了比较,我的方法要快18个百分点(102张124*124大小的图片的平均数)。