【问题标题】:Convert image to specific palette using PIL without dithering使用 PIL 将图像转换为特定调色板而无需抖动
【发布时间】:2015-06-08 14:32:27
【问题描述】:

我正在尝试使用 Pillow 库(Python 图像库,PIL)将 PNG 格式的 RGB 图像转换为使用特定的索引调色板。但我想使用“四舍五入到最接近的颜色”方法进行转换,而不是抖动,因为图像是像素艺术,抖动会扭曲区域的轮廓并给原本应该是平坦的区域添加噪点。

我试过Image.Image.paste(),它使用了四种指定的颜色,但它产生了一个抖动的图像:

from PIL import Image
oldimage = Image.open("oldimage.png")
palettedata = [0, 0, 0, 102, 102, 102, 176, 176, 176, 255, 255, 255]
newimage = Image.new('P', oldimage.size)
newimage.putpalette(palettedata * 64)
newimage.paste(oldimage, (0, 0) + oldimage.size)
newimage.show()    

我尝试了pictu's answer to a similar question 中提到的Image.Image.quantize(),但它也产生了抖动:

from PIL import Image
palettedata = [0, 0, 0, 102, 102, 102, 176, 176, 176, 255, 255, 255]
palimage = Image.new('P', (16, 16))
palimage.putpalette(palettedata * 64)
oldimage = Image.open("School_scrollable1.png")
newimage = oldimage.quantize(palette=palimage)
newimage.show()

我试过Image.Image.convert(),它在没有抖动的情况下转换了图像,但它包含了指定颜色以外的颜色,大概是因为它使用了网络调色板或自适应调色板

from PIL import Image
oldimage = Image.open("oldimage.png")
palettedata = [0, 0, 0, 102, 102, 102, 176, 176, 176, 255, 255, 255]
expanded_palettedata = palettedata * 64
newimage = oldimage.convert('P', dither=Image.NONE, palette=palettedata)
newimage.show()

如何在不抖动的情况下自动将图像转换为特定调色板?我想避免使用在 Python 中处理每个单独像素的解决方案,如 John La Rooy's answer 和 cmets 中所建议的那样,因为我之前的解决方案涉及用 Python 编写的内部循环已被证明对于大图像来说明显很慢。

【问题讨论】:

  • 给定任意调色板的“四舍五入到最接近的颜色”几乎需要逐像素技术,因此最好用 C 等非解释性语言编写。部分 PIL 是编写的在 C 中——它的开源——所以你可以扩展它而不是从头开始编写一个全新的扩展模块。
  • @martineau 抖动到自定义调色板也需要逐像素技术,但 PIL 可以做到。在网络或自适应调色板中四舍五入到最接近的颜色也需要逐像素技术,但 PIL 可以做到。它只是无法在自定义调色板中四舍五入到最接近的颜色。如果我要分叉 PIL,我将不得不为每个平台购买一台机器,以便为每个平台维护分叉的二进制文件。
  • PIL 可能在其中用 C 编写的部分中完成了大部分或所有这些事情。您不必正式分叉它​​,只需获取当前源并制作它的自定义版本适用于您正在使用的平台。
  • @martineau 在您自己的帐户中复制当前资源的 GitHub 操作称为“fork”。

标签: python image python-imaging-library


【解决方案1】:

Pillow 6 合并 pull request 3699,于 2019-03-11 合并, 它将dither 参数添加到普通的quantize() 方法中。 在 Pillow 6 之前,需要以下内容:

在 C 中实现的 PIL 部分位于 PIL._imaging 模块中,也可以在 from PIL import Image 之后作为 Image.core 使用。 当前版本的 Pillow 为每个 PIL.Image.Image 实例提供一个名为 im 的成员,它是 ImagingCore 的一个实例,PIL._imaging 中定义的一个类。 您可以使用help(oldimage.im) 列出它的方法,但这些方法本身在 Python 中没有记录。

ImagingCore 对象的convert 方法在_imaging.c 中实现。 它接受一到三个参数并创建一个新的ImagingCore 对象(在_imaging.c 中称​​为Imaging_Type)。

  • mode(必填):模式字符串(例如"P"
  • dither(可选,默认 0):PIL 通过 0 或 1
  • paletteimage(可选):带有调色板的ImagingCore

我面临的问题是dist-packages/PIL/Image.py 中的quantize()dither 参数强制为1。 所以我提取了quantize() 方法的副本并更改了它。 因为它依赖于表面上私有的方法,所以它可能无法在 Pillow 的未来版本中工作。 然而,到那时,我们可以预期 Pillow pre-6 已经停止使用,因为 Debian “bullseye”(2021 年中期稳定)和 Ubuntu “focal”(2020 年中期 LTS)都封装了 Pillow 7 或更新版本。

#!/usr/bin/env python3
from PIL import Image

def quantizetopalette(silf, palette, dither=False):
    """Convert an RGB or L mode image to use a given P image's palette."""

    silf.load()

    # use palette from reference image
    palette.load()
    if palette.mode != "P":
        raise ValueError("bad mode for palette image")
    if silf.mode != "RGB" and silf.mode != "L":
        raise ValueError(
            "only RGB or L mode images can be quantized to a palette"
            )
    im = silf.im.convert("P", 1 if dither else 0, palette.im)
    # the 0 above means turn OFF dithering

    # Really old versions of Pillow (before 4.x) have _new
    # under a different name
    try:
        return silf._new(im)
    except AttributeError:
        return silf._makeself(im)

# putpalette() input is a sequence of [r, g, b, r, g, b, ...]
# The data chosen for this particular answer represent
# the four gray values in a game console's palette
palettedata = [0, 0, 0, 102, 102, 102, 176, 176, 176, 255, 255, 255]
# Fill the entire palette so that no entries in Pillow's
# default palette for P images can interfere with conversion
NUM_ENTRIES_IN_PILLOW_PALETTE = 256
num_bands = len("RGB")
num_entries_in_palettedata = len(palettedata) // num_bands
palettedata.extend(palettedata[:num_bands]
                   * (NUM_ENTRIES_IN_PILLOW_PALETTE
                      - num_entries_in_palettedata))
# Create a palette image whose size does not matter
arbitrary_size = 16, 16
palimage = Image.new('P', arbitrary_size)
palimage.putpalette(palettedata)

# Perform the conversion
oldimage = Image.open("School_scrollable1.png")
newimage = quantizetopalette(oldimage, palimage, dither=False)
newimage.show()

【讨论】:

  • 1 if dither else 0 只是dither 或者如果你想要int(dither)
  • @Jean-FrançoisFabre 如果dither 不是bool 的实例,它必须是int(bool(dither))
  • 感谢@DamianYerrick 的回答,但我无法让它工作。你能解释一下为什么你需要创建一个大小为 (16,16)palimage 吗?以及为什么要将palettedata 乘以64
  • (16, 16) 是任意的。我选择它是因为 size 是必需的参数。在我写下答案的那天,我想到的第一个尺寸恰好与 Pillow 调色板中的条目具有相同的像素区域。我将调色板数据乘以 64,因为 Pillow 调色板中有 256 种颜色,而列表中只有足够的条目来描述 4 种颜色,并且 256 / 4 = 64。
【解决方案2】:

我采取了所有这些并使其更快,添加了注释以供您理解并转换为枕头而不是 pil。基本上。

import sys
import PIL
from PIL import Image

def quantizetopalette(silf, palette, dither=False):
    """Convert an RGB or L mode image to use a given P image's palette."""

    silf.load()

    # use palette from reference image made below
    palette.load()
    im = silf.im.convert("P", 0, palette.im)
    # the 0 above means turn OFF dithering making solid colors
    return silf._new(im)

if __name__ == "__main__":
    import sys, os

for imgfn in sys.argv[1:]:
    palettedata = [ 0, 0, 0, 255, 0, 0, 255, 255, 0, 0, 255, 0, 255, 255, 255,85,255,85, 255,85,85, 255,255,85] 

#   palettedata = [ 0, 0, 0, 0,170,0, 170,0,0, 170,85,0,] # pallet 0 dark
#   palettedata = [ 0, 0, 0, 85,255,85, 255,85,85, 255,255,85]  # pallet 0 light

#   palettedata = [ 0, 0, 0, 85,255,255, 255,85,255, 255,255,255,]  #pallete 1 light
#   palettedata = [ 0, 0, 0, 0,170,170, 170,0,170, 170,170,170,] #pallete 1 dark
#   palettedata = [ 0,0,170, 0,170,170, 170,0,170, 170,170,170,] #pallete 1 dark sp

#   palettedata = [ 0, 0, 0, 0,170,170, 170,0,0, 170,170,170,] # pallet 3 dark
#   palettedata = [ 0, 0, 0, 85,255,255, 255,85,85, 255,255,255,] # pallet 3 light

#  grey  85,85,85) blue (85,85,255) green (85,255,85) cyan (85,255,255) lightred 255,85,85 magenta (255,85,255)  yellow (255,255,85) 
# black 0, 0, 0,  blue (0,0,170) darkred 170,0,0 green (0,170,0)  cyan (0,170,170)magenta (170,0,170) brown(170,85,0) light grey (170,170,170) 
#  
# below is the meat we make an image and assign it a palette
# after which it's used to quantize the input image, then that is saved 
    palimage = Image.new('P', (16, 16))
    palimage.putpalette(palettedata *32)
    oldimage = Image.open(sys.argv[1])
    oldimage = oldimage.convert("RGB")
    newimage = quantizetopalette(oldimage, palimage, dither=False)
    dirname, filename= os.path.split(imgfn)
    name, ext= os.path.splitext(filename)
    newpathname= os.path.join(dirname, "cga-%s.png" % name)
    newimage.save(newpathname)

#   palimage.putpalette(palettedata *64)  64 times 4 colors on the 256 index 4 times, == 256 colors, we made a 256 color pallet.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-20
    • 1970-01-01
    • 1970-01-01
    • 2015-12-31
    • 1970-01-01
    • 2018-10-23
    • 2015-08-31
    • 1970-01-01
    相关资源
    最近更新 更多