我使用Python 3.6通过Pillow执行基本的图像处理。目前,我正在尝试拍摄任意颜色组成和大小的32位PNG图像(RGBA),并将它们量化为16种颜色的已知调色板。最佳地,该量化方法应该能够单独保留完全透明(A = 0)像素,同时迫使所有半透明像素完全不透明(A = 255)。我已经设计了执行此操作的工作代码,但我想知道它是否效率低下:
import math
from PIL import Image
# a list of 16 RGBA tuples
palette = [
(0, 0, 0, 255),
# ...
]
with Image.open('some_image.png').convert('RGBA') as img:
for py in range(img.height):
for px in range(img.width):
pix = img.getpixel((px, py))
if pix[3] == 0: # Ignore fully transparent pixels
continue
# Perform exhaustive search for closest Euclidean distance
dist = 450
best_fit = (0, 0, 0, 0)
for c in palette:
if pix[:3] == c: # If pixel matches exactly, break
best_fit = c
break
tmp = sqrt(pow(pix[0]-c[0], 2) + pow(pix[1]-c[1], 2) + pow(pix[2]-c[2], 2))
if tmp < dist:
dist = tmp
best_fit = c
img.putpixel((px, py), best_fit + (255,))
img.save('quantized.png')
我想到了这段代码的两个主要效率低下:
Image.putpixel()
操作缓慢是否有更快的方法来执行此操作?
我注意到Pillow有一个原生函数Image.quantize()
,似乎完全符合我的要求。但是当它被编码时,它会在结果中强制抖动,这是我不想要的。这已在another StackOverflow question中提出。这个问题的答案只是提取内部Pillow代码并调整控制变量以进行抖动,我测试过,但我发现Pillow会破坏我给它的调色板并持续产生一个图像,其中量化颜色比它们暗很多应该是。
Image.point()
是一种诱人的方法,但它只适用于每个颜色通道,其中颜色量化需要将所有通道作为一组进行处理。很高兴能够将所有通道强制转换为32位整数值的单个通道,似乎是不良记录模式“我”会做的,但是如果我运行img.convert('I')
,我得到了一个完全灰度的结果,破坏了所有颜色。
另一种方法似乎是使用NumPy并直接改变图像。我试图创建一个RGB值的查找表,但NumPy语法的三维索引让我疯狂。理想情况下,我想要一些像这样工作的代码:
img_arr = numpy.array(img)
# Find all unique colors
unique_colors = numpy.unique(arr, axis=0)
# Generate lookup table
colormap = numpy.empty(unique_colors.shape)
for i, c in enumerate(unique_colors):
dist = 450
best_fit = None
for pc in palette:
tmp = sqrt(pow(c[0] - pc[0], 2) + pow(c[1] - pc[1], 2) + pow(c[2] - pc[2], 2))
if tmp < dist:
dist = tmp
best_fit = pc
colormap[i] = best_fit
# Hypothetical pseudocode I can't seem to write out
for iy in range(arr.size):
for ix in range(arr[0].size):
if arr[iy, ix, 3] == 0: # Skip transparent
continue
index = # Find index of matching color in unique_colors, somehow
arr[iy, ix] = colormap[index]
我注意到这个假设的例子,numpy.unique()
是另一个缓慢的操作,因为它对输出进行排序。因为我似乎无法按照我想要的方式完成代码,所以我无法测试这种方法是否更快。
我还考虑通过将值转换为32位整数并希望创建具有更简单索引的一维查找表来尝试展平RGBA轴:
def shift(a):
return a[0] << 24 | a[1] << 16 | a[2] << 8 | a[3]
img_arr = numpy.apply_along_axis(shift, 1, img_arr)
但是这个操作本身似乎显得很慢。
我更喜欢那些涉及 Pillow和/或NumPy 的答案。除非使用另一个库演示了比任何PIL或NumPy本机解决方案显着的计算速度提升,否则我不想导入无关的库来执行这两个库本身应该合理的能力。
答案 0 :(得分:0)
for
循环应该避免速度。
我认为你应该做一个像:
的张量d2[x,y,color_index,rgb] = distance_squared
其中rgb = 0..2(0 = r,1 = g,2 = b)。
然后计算距离:
d[x,y,color_index] =
sqrt(sum(rgb,d2))
然后选择最小距离的color_index:
c[x,y] = min_index(color_index, d)
最后根据需要替换alpha:
alpha = ceil(orig_image.alpha)
img = c,alpha