【问题标题】:Channel mix with Pillow带枕头的频道混合
【发布时间】:2017-03-18 08:51:19
【问题描述】:

我想做一些颜色转换,例如给定 RGB 通道

R =  G + B / 2

或其他一些转换,其中通道值是根据同一像素的其他通道的值计算的。

.point() 函数似乎只能在一个通道上运行。有没有办法做我想做的事?

【问题讨论】:

标签: python pillow


【解决方案1】:

使用PIL.ImageChops 的替代方法是将图像数据转换为 Numpy 数组。 Numpy 使用本机机器数据类型,与在 Python 数字对象上执行 Python 循环相比,它的编译例程可以非常快速地处理数组数据。所以 Numpy 代码的速度与使用 ImageChops 的速度相当。您可以在 Numpy 中或使用相关库(如 SciPy)进行各种数学运算。

Numpy 提供了一个函数np.asarray,它可以从 PIL 数据创建一个 Numpy 数组。而PIL.Image 有一个.fromarray 方法可以从Numpy 数组中加载图像数据。

这里的脚本显示了两种不同的 Numpy 方法,以及一种基于 kennytm 的 ImageChops 代码的方法。

#!/usr/bin/env python3

''' PIL Image channel manipulation demo

    Replace each RGB channel by the mean of the other 2 channels, i.e.,

    R_new = (G_old + B_old) / 2
    G_new = (R_old + B_old) / 2
    B_new = (R_old + G_old) / 2

    This can be done using PIL's own ImageChops functions
    or by converting the pixel data to a Numpy array and
    using standard Numpy aray arithmetic

    Written by kennytm & PM 2Ring 2017.03.18
'''

from PIL import Image, ImageChops
import numpy as np

def comp_mean_pil(iname, oname):
    print('Loading', iname)
    img = Image.open(iname)
    #img.show()

    rgb = img.split()
    half = ImageChops.constant(rgb[0], 128)
    rh, gh, bh = [ImageChops.multiply(x, half) for x in rgb] 
    rgb = [
        ImageChops.add(gh, bh), 
        ImageChops.add(rh, bh), 
        ImageChops.add(rh, gh),
    ]
    out_img = Image.merge(img.mode, rgb)
    out_img.show()
    out_img.save(oname)
    print('Saved to', oname)

# Do the arithmetic using 'uint8' arrays, so we must be 
# careful that the data doesn't overflow
def comp_mean_npA(iname, oname):
    print('Loading', iname)
    img = Image.open(iname)
    in_data = np.asarray(img)

    # Halve all RGB values
    in_data = in_data // 2

    # Split image data into R, G, B channels
    r, g, b = np.split(in_data, 3, axis=2)

    # Create new channel data
    rgb = (g + b), (r + b), (r + g)

    # Merge channels
    out_data = np.concatenate(rgb, axis=2)

    out_img = Image.fromarray(out_data)
    out_img.show()
    out_img.save(oname)
    print('Saved to', oname)

# Do the arithmetic using 'uint16' arrays, so we don't need
# to worry about data overflow. We can use dtype='float'
# if we want to do more sophisticated operations
def comp_mean_npB(iname, oname):
    print('Loading', iname)
    img = Image.open(iname)
    in_data = np.asarray(img, dtype='uint16')

    # Split image data into R, G, B channels
    r, g, b = in_data.T

    # Transform channel data
    r, g, b = (g + b) // 2, (r + b) // 2, (r + g) // 2

    # Merge channels
    out_data = np.stack((r.T, g.T, b.T), axis=2).astype('uint8')

    out_img = Image.fromarray(out_data)
    out_img.show()
    out_img.save(oname)
    print('Saved to', oname)

# Test

iname = 'Glasses0.png'
oname = 'Glasses0_out.png'

comp_mean = comp_mean_npB

comp_mean(iname, oname)

输入图像

输出图像

FWIW,该输出图像是使用 comp_mean_npB 创建的。

由于它们执行计算的方式不同,由 3 个函数生成的计算通道值可能彼此相差 1,但当然这种差异并不容易看出。 :)

【讨论】:

  • 很好的答案,这种方法灵活且可重复使用。
  • “眼镜”图像由Gilles Tran 使用 POV-Ray 创建。 Gilles 已将这张图片的源代码放在公共领域。
【解决方案2】:

对于这个特定的操作,颜色转换可以写成矩阵乘法,所以你可以使用 convert() 方法和自定义矩阵(假设没有 alpha 通道):

# img must be in RGB mode (not RGBA):
transformed_img = img.convert('RGB', (
    0, 1, .5, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
))

否则,您可以将图像split() 转换为每个色带的 3 或 4 张图像,应用您喜欢的任何操作,最后将merge() 这些色带恢复为单个图像。同样,原始图像应为 RGB 或 RGBA 模式。

(red, green, blue, *rest) = img.split()
half_blue = PIL.ImageChops.multiply(blue, PIL.ImageChops.constant(blue, 128))
new_red = PIL.ImageChops.add(green, half_blue)
transformed_img = PIL.Image.merge(img.mode, (new_red, green, blue, *rest))

【讨论】:

    猜你喜欢
    • 2018-07-10
    • 1970-01-01
    • 2011-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-22
    • 2019-02-22
    相关资源
    最近更新 更多