【问题标题】:How to use PIL to make all white pixels transparent?如何使用 PIL 使所有白色像素透明?
【发布时间】:2010-10-20 10:14:21
【问题描述】:

我正在尝试使用 Python 图像库使所有白色像素透明。 (我是一名 C 黑客,试图学习 python,所以要温柔) 我已经进行了转换(至少像素值看起来正确),但我不知道如何将列表转换为缓冲区以重新创建图像。这是代码

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


    【解决方案1】:

    @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")
    

    【讨论】:

      【解决方案2】:

      此函数结合了之前解决方案的所有优点:它允许任何背景并使用 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")
      

      【讨论】:

        【解决方案3】:

        在打开模式下使用RGBA img = img.convert("RGBA") 或 IMG = Image.new(mode="RGB", size=(width,high)) 所以你可以在图像中添加 alpha IMG.putpixel((Y_Axis, X_Axis),(R, G, B))

        【讨论】:

          【解决方案4】:

          我很惊讶没有人看到不仅需要更改特定颜色,还需要更改该颜色与其他颜色的混合。这就是 Gimp 对“颜色到 alpha”的功能所做的。用https://stackoverflow.com/a/62334218/5189462 扩展cr333 的代码,我们得到了类似这个功能的东西:

          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],
                  max( 
                      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")
          
          

          【讨论】:

            【解决方案5】:

            一种更 Python 的方式,因为循环需要很长时间才能获得大图像

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

            【讨论】:

              【解决方案6】:

              您也可以使用像素访问模式就地修改图像:

              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 像素)。在高端服务器上进行测试,我尝试的所有其他方法都因该大小的内存错误而死,但能够处理(22000x22000 像素)。缺点:这比我尝试过的其他方法慢,例如使用 numpy 替换值,然后 Image.fromarray 将其恢复为 PIL 对象。为了添加到 @MKatz 的参考点,对于 32000x32000 像素的图像,这在 7 分 15 秒内运行。
              • 嘿,有没有办法让除了一种颜色之外的所有颜色都透明?我尝试使用 for 循环,但它需要太多时间!帮助
              • @NithinSai 创建一个只从原始图片中复制一种颜色的副本怎么样?
              • @NithinSai lmk 如果这有帮助:stackoverflow.com/questions/52315895/…
              【解决方案7】:

              由于这是目前在寻找“枕头白色到透明”时的第一个谷歌结果,我想补充一点,在我的基准测试中,numpy 也可以实现同样的效果(一张 8MP 图像,有很多白色背景) ) 大约快 10 倍(对于建议的解决方案,大约 300 毫秒与 3.28 秒)。代码也短一点:

              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 图像被转换为​​只读数组。

              【讨论】:

              • 这个函数会消耗很多内存。
              • 为什么很多?它在空间上仍然是线性的,当然你需要创建一些额外的数组,但即使你考虑到所有因素,它也可能是 5 倍空间(可能更少),对于 10 倍的加速,这是一个很好的折衷(另外,如果你在这样的环境中工作不能在内存中创建 5 个图像的严格条件,那么 python 可能不是适合您任务的语言...)
              • 我在1G VPS中使用这个总是出现内存错误异常,而增加VPS内存一切正常。
              • 你能解释一下为什么使用axis=2吗?我假设它应该是轴 = 3,因为我们正在使 Alpha 'A' 通道透明。
              • 一幅图像共有 3 个轴 - 高度、宽度和通道 - 因此 axis=3 会引发错误。我们保存到 alpha 的事实包含在赋值的 lhs 中,即我们在第三个 ax 的索引 3 中写入(R=0,G=1,B=2,alpha=3)。 rhs 上的.any(axis=2) 表示您想要获取第三维(因为它是[:, :, :3])的前三个索引(R、G 或 B)中至少一个不同于 255 的像素。跨度>
              【解决方案8】:

              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)
              

              【讨论】:

                【解决方案9】:
                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]);
                

                【讨论】:

                  【解决方案10】:

                  您需要进行以下更改:

                  • 附加一个元组(255, 255, 255, 0)而不是一个列表[255, 255, 255, 0]
                  • 使用img.putdata(newData)

                  这是工作代码:

                  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。
                  • 对于 GIF,似乎需要 transparency 作为 save 的参数(Pillow 5.1.0)。另见How to CREATE a transparent gif (or png) with PIL (python-imaging)
                  • “RGBA”中的 A 代表“alpha”,意思是“不透明度”。所以这里0 中的newData.append((255,255,255,0)) 表示“0 不透明度;”换句话说,“完全透明”。进一步的解释可能对好奇的新手有所帮助。我猜putdata() 改变了 PIL 对象,但我不知道引擎盖下发生了什么
                  • 这很有趣地翻转了一些图像 - 知道为什么吗?
                  • 什么样的翻转?你能说得更具体点吗?
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-12-29
                  • 2020-02-14
                  • 2012-01-13
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-06-14
                  相关资源
                  最近更新 更多