Как функция Image.convert () в PIL работает в режиме 'P' - PullRequest
4 голосов
/ 10 июля 2019

У меня есть набор 24-битных файлов PNG, и я хочу преобразовать их в 8-битные файлы PNG.Я использовал метод Image.convert () PIL для решения этой проблемы.Однако после использования режима «P» в качестве аргумента я обнаружил, что пиксели с одинаковыми значениями RGB могут быть преобразованы по-разному.

Я перенес пример изображения в массив numpy, и исходный 24-битный png-файл имеет следующие значения:

RGB-массив

   ..., 
   [204, 102, 119],
   [204, 102, 119],
   [204, 102, 119],
   [204, 102, 119],
   [204, 102, 119],
   [204, 102, 119],
   [204, 102, 119],
   [204, 102, 119],
   [204, 102, 119],
   [204, 102, 119],
   [204, 102, 119], 
   ...

После использования функции преобразования в режиме 'P' значение изображения стало таким:

8-битный массив

   ..., 98, 98, 134, 98, 98, 98, 134, 98, 98, 98, 134, ...

Пример кода:

from PIL import Image
import numpy as np
img = Image.open("path-to-file.png")
p_img = img.convert("P")

Я ожидаю, что пиксели с одинаковыми значениями RGB преобразуются одинаково.Я знаю, что пиксели преобразуются в индекс палитры, но это все еще не имеет смысла для меня.Я не знаком с библиотекой PIL.Может кто-нибудь объяснить, почему это происходит?Заранее спасибо.


Реализовано кое-что по примеру Марка

import numpy as np
from PIL import Image
#from improc import GenerateNColourImage

# Set image height and width
N    = 6
h, w = 100, 100

# Generate repeatable random Numpy image with N^3 unique colours at most
n = np.random.randint(N, size=(h, w, 3), dtype=np.uint8)
# Work out indices of diagonal elements
diags = np.diag_indices(h)

# Intentionally set all diagonal elements same shade of blue
n[diags] = [10,20,200]
# Make Numpy image into PIL Image, palettise, convert back to Numpy array and check diagonals
a0 = Image.fromarray(n)

unique_colors = np.unique(n.reshape(-1, n.shape[2]), axis=0).shape
print(unique_colors)   #e.g. print (217, 3)
a1 = a0.convert('P')
a2 = np.array(a1)

# Look at diagonals - should all be the same
print(a2[diags])
print(' %s %d' % ("Number of unique colors:  ", np.unique(a2).shape[0]))

Выведены значения диагональных пикселей

... 154 154 154 154 154 154 124 154 160 160 160 154 160 ...

8-битное изображение в режиме «P» содержит 125 уникальных индексов палитры.Кажется, PIL будет выполнять дизеринг, несмотря ни на что.

Ответы [ 2 ]

1 голос
/ 10 июля 2019

Проблема в том, что PIL / Pillow "dithering" . По сути, если в вашем изображении более 256 цветов (максимум, который может удержать палитра), в изображении обязательно должны быть цвета, которых нет в палитре. Таким образом, PIL накапливает ошибки (разницу между исходным цветом и цветом палитры) и время от времени вставляет пиксель немного другого цвета, чтобы изображение выглядело более или менее правильным на расстоянии. Это в основном "распространение ошибок" . Так что, если ваш цвет будет вовлечен в это, он иногда будет выходить по-другому.

Один из способов избежать этого - это квантовать изображение до 256 цветов, тогда нет ошибок для распространения.

# Quantise to 256 colours
im256c = = image.quantize(colors=256, method=2)

Обратите внимание, что это не означает, что ваш оттенок синего всегда будет отображаться с одним и тем же индексом палитры в каждом изображении, это просто означает, что все пиксели с вашим оттенком синего в любом отдельном изображении будут иметь одинаковый индекс палитры.

Вот пример:

#!/usr/bin/env python3

import numpy as np
from PIL import Image
from improc import GenerateNColourImage

# Set image height and width
h, w = 200, 200
N    = 1000

# Generate repeatable random Numpy image with N unique colours
np.random.seed(42)
n = GenerateNColourImage(h,w,N) 

# Work out indices of diagonal elements
diags = np.diag_indices(h)

# Intentionally set all diagonal elements same shade of blue
n[diags] = [10,20,200]

# Make Numpy image into PIL Image, palettise, convert back to Numpy array and check diagonals
a0 = Image.fromarray(n)
a1 = a0.convert('P')
a2 = np.array(a1)

# Look at diagonals - should all be the same
print(a2[diags])

Выход

[154 154 154 154 154 154 154 154 160 154 160 154 154 154 154 154 160 154
 154 154 160 154 154 154 160 160 154 154 154 160 154 154 154 154 154 154
 154 154 160 154 154 154 154 154 154 154 154 160 160 154 154 154 154 154
 154 154 154 154 154 154 154 154 154 160 154 154 154 154 154 154 154 160
 154 160 154 154 154 154 154 154 154 154 154 154 154 160 154 160 160 154
 154 160 154 154 154 160 154 154 154 154 154 160 154 154 154 154 155 154
 154 160 154 154 154 154 154 154 154 154 154 160 154 154 154 160 154 154
 154 154 160 154 154 154 154 154 154 154 154 154 154 154 154 154 154 160
 154 160 154 160 154 160 154 160 160 154 154 154 154 154 154 154 154 154
 154 154 154 154 161 154 154 154 154 154 154 154 154 154 154 160 154 160
 118 154 160 154 154 154 154 154 154 154 154 154 160 154 154 160 154 154
 154 154]

Упс, там есть значения 154, 118 и 160 ...


Теперь повторите эти последние 4 строки с тем же массивом Numpy, но с использованием quantise():

# Make Numpy image into PIL Image, quantise, convert back to Numpy array and check diagonals
b0 = Image.fromarray(n)
b1 = b0.quantize(colors=256,method=2)
b2 = np.array(b1)

# Look at diagonals - should all be the same
print(b2[diags])

выход

[64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
 64 64 64 64 64 64 64 64]

Так лучше - все тоже самое!

Я должен добавить, что если вы сохраните изображение в формате PNG с 256 цветами или менее, PIL / Pillow автоматически сохранит изображение палитры.

1 голос
/ 10 июля 2019

Это нормальное поведение, когда мы конвертируем изображение в P цветной режим.Режим палитры работает так: он создает таблицу сопоставления, которая соответствует индексу (в диапазоне 0 - 255) отдельному цвету в большем цветовом пространстве (например, RGB).Например, значение цвета RGB (0, 0, 255) ( Pure Blue ) в изображении получает индекс 1 ( только гипотетический пример ).Этот же процесс проходит через каждое уникальное значение пикселя в исходном изображении (но размер таблицы не должен превышать 256, в процессе отображения).Итак, массив NumPy (или обычный список), имеющий такие значения: -

   ..., 98, 98, 134, 98, 98, 98, 134, 98, 98, 98, 134, ...

, соответствует индексу в таблице сопоставления, а не самому фактическому значению цвета.Таким образом, вы можете интерпретировать их как индекс, который при чтении изображения преобразуется в фактическое значение цвета, сохраненное в этом индексе.

Но эти значения пикселей не всегда должны означать, что изображение имеет цветовой режим P.Например, если вы просматриваете данные пикселей изображения в градациях серого (L), значения будут выглядеть так же, как в случае режима палитры, но на самом деле будут соответствовать истинным значениям цвета (или оттенкам серого), а неиндекс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...