【问题标题】:How do I convert any image to a 4-color paletted image using the Python Imaging Library?如何使用 Python 图像库将任何图像转换为 4 色调色板图像?
【发布时间】:2010-09-19 04:45:37
【问题描述】:

我有一台支持 4 色图形的设备(很像过去的 CGA)。

我想使用PIL 读取图像并使用我的 4 色调色板(红色、绿色、黄色、黑色)对其进行转换,但我根本不知道这是否可行。我发现一些邮件列表存档帖子似乎暗示其他人尝试这样做但失败了。

一个简单的python示例将不胜感激!

如果您添加一些东西然后将图像转换为字节字符串,其中每个字节代表 4 个像素的数据(每两个位代表从 0 到 3 的颜色),则会获得奖励积分

【问题讨论】:

标签: python image-processing python-imaging-library


【解决方案1】:

首先:您的四种调色板(黑、绿、红、黄)有没有蓝色成分。所以,你必须接受你的输出图像几乎不会接近输入图像,除非没有蓝色分量开始。

试试这个代码:

import Image

def estimate_color(c, bit, c_error):
    c_new= c -  c_error
    if c_new > 127:
        c_bit= bit
        c_error= 255 - c_new
    else:
        c_bit= 0
        c_error= -c_new
    return c_bit, c_error

def image2cga(im):
    "Produce a sequence of CGA pixels from image im"
    im_width= im.size[0]
    for index, (r, g, b) in enumerate(im.getdata()):
        if index % im_width == 0: # start of a line
            r_error= g_error= 0
        r_bit, r_error= estimate_color(r, 1, r_error)
        g_bit, g_error= estimate_color(g, 2, g_error)
        yield r_bit|g_bit

def cvt2cga(imgfn):
    "Convert an RGB image to (K, R, G, Y) CGA image"
    inp_im= Image.open(imgfn) # assume it's RGB
    out_im= Image.new("P", inp_im.size, None)
    out_im.putpalette( (
        0, 0, 0,
        255, 0, 0,
        0, 255, 0,
        255, 255, 0,
    ) )
    out_im.putdata(list(image2cga(inp_im)))
    return out_im

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

    for imgfn in sys.argv[1:]:
        im= cvt2cga(imgfn)
        dirname, filename= os.path.split(imgfn)
        name, ext= os.path.splitext(filename)
        newpathname= os.path.join(dirname, "cga-%s.png" % name)
        im.save(newpathname)

这将创建一个 PNG 调色板图像,其中只有前四个调色板条目设置为您的颜色。此示例图片:

变成

获取image2cga 的输出(产生0-3 个值的序列)并将每四个值打包到一个字节是很简单的。

如果您需要有关代码功能的帮助,请询问,我会解释。

EDIT1:不要重新发明轮子

当然,事实证明我太热情了——正如 Thomas 发现的那样——Image.quantize 方法可以将调色板图像作为参数,并以比我上面的 ad-hoc 方法更好的结果进行量化:

def cga_quantize(image):
    pal_image= Image.new("P", (1,1))
    pal_image.putpalette( (0,0,0, 0,255,0, 255,0,0, 255,255,0) + (0,0,0)*252)
    return image.convert("RGB").quantize(palette=pal_image)

EDIT1,续:将像素打包成字节

对于“附加值”,以下代码生成打包字符串(每字节 4 个像素):

import itertools as it

# setup: create a map with tuples [(0,0,0,0)‥(3,3,3,3)] as keys
# and values [chr(0)‥chr(255)], because PIL does not yet support
# 4 colour palette images

TUPLE2CHAR= {}

# Assume (b7, b6) are pixel0, (b5, b4) are pixel1…
# Call it "big endian"

KEY_BUILDER= [
    (0, 64, 128, 192), # pixel0 value used as index
    (0, 16, 32, 48), # pixel1
    (0, 4, 8, 12), # pixel2
    (0, 1, 2, 3), # pixel3
]
# For "little endian", uncomment the following line
## KEY_BUILDER.reverse()

# python2.6 has itertools.product, but for compatibility purposes
# let's do it verbosely:
for ix0, px0 in enumerate(KEY_BUILDER[0]):
    for ix1, px1 in enumerate(KEY_BUILDER[1]):
        for ix2, px2 in enumerate(KEY_BUILDER[2]):
            for ix3, px3 in enumerate(KEY_BUILDER[3]):
                TUPLE2CHAR[ix0,ix1,ix2,ix3]= chr(px0+px1+px2+px3)

# Another helper function, copied almost verbatim from itertools docs
def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return it.izip(*[it.chain(iterable, it.repeat(padvalue, n-1))]*n)

# now the functions
def seq2str(seq):
    """Takes a sequence of [0..3] values and packs them into bytes
    using two bits per value"""
    return ''.join(
        TUPLE2CHAR[four_pixel]
        for four_pixel in grouper(4, seq, 0))

# and the image related function
# Note that the following function is correct,
# but is not useful for Windows 16 colour bitmaps,
# which start at the *bottom* row…
def image2str(img):
    return seq2str(img.getdata())

【讨论】:

    【解决方案2】:

    约翰,我也找到了第一个链接,但它并没有直接帮助我解决问题。不过,它确实让我更深入地了解了量化。

    我昨天睡前想出了这个:

    import sys
    
    import PIL
    import Image
    
    PALETTE = [
        0,   0,   0,  # black,  00
        0,   255, 0,  # green,  01
        255, 0,   0,  # red,    10
        255, 255, 0,  # yellow, 11
    ] + [0, ] * 252 * 3
    
    # a palette image to use for quant
    pimage = Image.new("P", (1, 1), 0)
    pimage.putpalette(PALETTE)
    
    # open the source image
    image = Image.open(sys.argv[1])
    image = image.convert("RGB")
    
    # quantize it using our palette image
    imagep = image.quantize(palette=pimage)
    
    # save
    imagep.save('/tmp/cga.png')
    

    TZ.TZIOY,您的解决方案似乎遵循相同的原则。荣誉,我应该停止工作并等待您的回复。我的有点简单,虽然肯定不比你的更合乎逻辑。 PIL 使用起来很麻烦。你的解释了怎么做。

    【讨论】:

      【解决方案3】:
      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
          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
          return silf._makeself(im)
      
      if __name__ == "__main__":
          import sys, os
      
      for imgfn in sys.argv[1:]:
          palettedata = [ 0, 0, 0, 0, 255, 0, 255, 0, 0, 255, 255, 0,] 
          palimage = Image.new('P', (16, 16))
          palimage.putpalette(palettedata + [0, ] * 252 * 3)
          oldimage = Image.open(sys.argv[1])
          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)
      

      对于那些不希望通过抖动获得纯色的用户。我修改了:Convert image to specific palette using PIL without dithering 使用此线程中的两个解决方案。即使这个线程很旧,我们中的一些人也想要这些信息。点赞

      【讨论】:

      • 欢迎来到 Stack Overflow!感谢您提供此代码 sn-p,它可能会提供一些即时帮助。一个正确的解释would greatly improve 其教育价值通过展示为什么这是一个很好的解决问题的方法,并将使它对未来有类似但不相同的问题的读者更有用。请edit您的答案添加解释,并说明适用的限制和假设。
      猜你喜欢
      • 2016-11-25
      • 2011-03-02
      • 1970-01-01
      • 2021-04-22
      • 2015-08-31
      • 2017-05-09
      • 1970-01-01
      • 1970-01-01
      • 2011-02-11
      相关资源
      最近更新 更多