Я использую Python 3.6 для выполнения основных операций с изображениями через подушку. В настоящее время я пытаюсь взять 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()
, которая, кажется, делает именно то, что я хочу. Но поскольку он закодирован, он вызывает дизеринг в результате, чего я не хочу. Это было поднято в другом вопросе 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)
Но эта операция сама по себе казалась заметно медленной.
Я бы предпочел ответы, включающие только Pillow и / или NumPy, пожалуйста. Если использование другой библиотеки не демонстрирует резкое увеличение скорости вычислений по сравнению с любым собственным решением для PIL или NumPy, я не хочу импортировать посторонние библиотеки, чтобы делать то, на что эти две библиотеки должны быть способны сами по себе.
scipy
. - person Paul Panzer   schedule 11.06.2018magick input.png +dither -remap palette.png result.png
. Также тривиально можно распараллелить с помощью GNU Parallelparallel magick {} +dither -remap palette.png {} ::: *.png
- person Mark Setchell   schedule 11.06.2018sqrt()
, поскольку a ‹b, если a ^ 2‹ b ^ 2 - person Mark Setchell   schedule 11.06.2018putpixel()
, используя массив numpy для выходных пикселей и просто преобразовав его в изображение в самом конце. - person Mark Setchell   schedule 13.06.2018