Я использую 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()
медленная операция - Вычисление функции расстояния несколько раз на пиксель неэффективно в вычислительном отношении
Есть ли более быстрый метод для этого?
Я заметил, что у Подушки есть встроенная функция Image.quantize()
, которая, кажется, делает именно то, что я хочу.Но поскольку он закодирован, он вызывает сглаживание в результате, что я не хочу.Это было поднято в другом вопросе StackOverflow .Ответ на этот вопрос состоял в том, чтобы просто извлечь внутренний код Pillow и настроить контрольную переменную для дизеринга, которую я тестировал, но я обнаружил, что Pillow искажает палитру, которую я ей предоставляю, и последовательно выдает изображение, где квантованные цвета значительно темнее, чем они.должно быть.
Image.point()
- это дразнящий метод, но он работает только для каждого цветового канала индивидуально, где для квантования цвета требуется работа со всеми каналами как набором.Было бы неплохо иметь возможность объединить все каналы в один канал с 32-битными целочисленными значениями, что кажется тем, что сделал бы плохо документированный режим "I", но если бы язапустите 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()
- еще одна медленная операция, поскольку она сортирует выходные данные.Поскольку мне кажется, что я не могу закончить код так, как я хочу, я так и не смог проверить, быстрее ли этот метод.
Я также рассмотрел попытку сгладить ось RGBA путем преобразования значений в32-разрядное целое число и желание создать одномерную таблицу поиска с более простым индексом:
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)
Но сама эта операция показалась заметно медленной.
Я бы предпочел ответы, которыезадействуйте только подушку и / или NumPy , пожалуйста.Если использование другой библиотеки не демонстрирует резкого увеличения скорости вычислений по сравнению с любым родным решением PIL или NumPy, я не хочу импортировать посторонние библиотеки, чтобы сделать что-то, на что эти две библиотеки должны быть способны самостоятельно.