【发布时间】:2017-03-18 08:51:19
【问题描述】:
我想做一些颜色转换,例如给定 RGB 通道
R = G + B / 2
或其他一些转换,其中通道值是根据同一像素的其他通道的值计算的。
.point() 函数似乎只能在一个通道上运行。有没有办法做我想做的事?
【问题讨论】:
-
顺便说一句,你可能也对stackoverflow.com/q/40233986/4014959感兴趣
我想做一些颜色转换,例如给定 RGB 通道
R = G + B / 2
或其他一些转换,其中通道值是根据同一像素的其他通道的值计算的。
.point() 函数似乎只能在一个通道上运行。有没有办法做我想做的事?
【问题讨论】:
使用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,但当然这种差异并不容易看出。 :)
【讨论】:
对于这个特定的操作,颜色转换可以写成矩阵乘法,所以你可以使用 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))
【讨论】: