Квантование изображения алгоритм. Переход от непрерывных сигналов и преобразований к дискретным. Определение глубины цвета

Поскольку идея ДИКМ достаточно проста, то, как следует из схем рис. 4.8, характеристики системы сокращения избыточности изображений методом ДИКМ определяются [порядком предсказывающего устройства п, значениями коэффициентов прогнозирования а i , числом уровней квантования и их расположением.

Порядок предсказывающего устройства зависит от статистических характеристик изображения. Как правило, если последовательность отсчетов может быть промоделирована авторегрессионным марковским процессом п-го порядка, то разности, полученные с помощью оптимального предсказывающего устройства п-го порядка, будут образовывать последовательность некоррелированных чисел . Изображения, очевидно, не являются марковскими процессами п-го порядка, но опыт работы по сжатию изображений показывает, что корреляционные свойства изображений можно описать марковским процессом третьего порядка, а это приводит к предсказывающим устройствам третьего порядка (п=3) . Аналогично при моделировании изображений было выяснено, что ДИКМ с предсказывающими устройствами более высоких порядков не дает большего выигрыша в качестве изображения (как по субъективным, так и по объективным данным).

Коэффициенты предсказания а i можно определить с помощыо анализа средних квадратических ошибок. Пусть g ( k ) - отсчеты на строке развертки, a

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

min e = E { g(k) - } (4.21)

повсем k, а i

Это известная задача, и если процесс g ( k ) стационарен, то ее решение имеет вид

, (4.22)

r (j - i) = E [ g (k - j) g (k -i) ] (4.23)

обычно называется автокорреляционной функцией процесса g. Коэффициенты a i получаются решением системы уравнений (4.22).

Оптимальные значения коэффициентов предсказания зависят от взаимосвязей точек изображения, описываемых автокорреляционной функцией. Из определения (4.20) видно, что в случае стационарных данных автокорреляционная функция отличается от вышерассмотренной функции на постоянную величину. При нестационарных данных функция r (в уравнении (4.23) зависит от пространственных переменных и оптимальные коэффициенты предсказания должны изменяться в зависимости от пространственных координат. Это характерно для изображений. К счастью, нестационарные статистические характеристики изображений обычно можно достаточно хорошо аппроксимировать стационарными функциями, так что неперестраивающееся линейное устройство предсказания дает вполне хорошие результаты. При сжатии видеоинформации методом ДИКМ ошибки обычно появляются на границах изображаемых предметов, где предположение о стационарности удовлетворяется в наименьшей степени, и на восстановленном изображении воспринимаются визуально как аномально - светлые или темные точки.

Выбор числа уровней квантования и расположения порогов квантования является задачей отчасти количественной и отчасти качественной. Расположение порогов квантования можно найти количественными расчетами. В работе Макса впервые было рассмотрено неравномерное квантование, зависящее от функции распределения квантуемого сигнала и сводящее к минимуму среднее квадратическое значение ошибки, вызванной ограниченностью числа уровней квантования. Алгоритм Макса позволяет найти оптимальное расположение точек перехода для заданного числа уровней квантования. Однако число уровней квантования выбирается исходя из субъективных качественных соображений.

Минимальное число уровней квантования paвно двум (одноразрядные числа) и соответствует такому квантованию изображений, при котором разность яркостей принимает фиксированное (положительное или отрицательное) значение. Этот способ обычно называют дельта - модуляцией, схему ДИКМ (рис. 4.8) можно упростить заменой квантователя на ограничитель, а предсказывающего устройства n -го порядка на интегратор. При сокращении избыточности изображений методом дельта-модуляции наблюдаются те же недостатки, что и при дельта-модуляции других сигналов, например речевых , а именно затягивание фронтов и искажения дробления. Однако если частота дискретизации изображения выбрана намного больше частоты Найквиста, то сжатие методом дельта - модуляции приводит к малым (субъективно замечаемым) ошибкам. Если частота дискретизации приближается к частоте Найквиста, то на изображении в большей степени будут проявляться затягивания фронтов (на контурах изображений) и искажения дробления (на участках с постоянной яркостью). Как и при сжатии речи , адаптивная дельта-модуляция позволяет уменьшить эти ошибки. Однако в целом при передаче изображений дельта - модуляция оказалась менее эффективной, что при передаче речи.

Квантование с числом уровней, большим двух, позволяет при сокращении избыточности получить изображения более высокого качества. Система сжатия методом ДИКМ с 8-уровневым (З-разрядным) квантованием при оптимальном размещении порогов дает изображения, качество которых такое же, как в системе с ИКМ, имеющей разрядность от 6 до 8. Исключение составляют ошибки вблизи линий резкого изменения яркости.

Сигнал с выхода устройства квантования, конечно, следует кодировать, поскольку распределение вероятностей «квантованных разностей не является равномерным. При удачном выборе кода (например, кода Шеннона - Фано или Хаффмена) удается дополнительно понизить общую скорость создания информации. Прэтт указывает, что при использовании кода Хаффмена в пределе удается понизить скорость создания информации до 2,5 бит/точка. Это дополнительное понижение скорости требуется сопоставить с увеличением стоимости и сложности запоминающего устройства, синхронизаторов и вспомогательных регистров памяти, необходимых для работы с кодами Хаффмена.

Выше обсуждались вопросы сжатия изображений с помощью ДИКМ при выборе элементов по строке (т.е. для прогноза брались точки, лежащие на текущей строке развертки). В силу двумерного характера изображений возможно (и целесообразно) расширить метод ДИКМ так, чтобы при прогнозе учитывались яркости в точках, лежащих не только на текущей, но и на предшествующих строках развертки. Схемы сжатия методом ДИКМ с таким двумерным предсказанием основаны на тех же принципах, что при одномерном предсказании. Поскольку для изображений характерно наличие двумерных статистических взаимосвязей, можно надеяться, что двумерное предсказание даст лучшие результаты по сжатию изображений, так как декорреляция изображений с помощью операций предсказания и вычитания будет производиться по двум координатам. Действительно, устройства с пространственным предсказанием дают более качественные изображения. Хабиби показал, что с помощью двумерного предсказывающего устройства третьего порядка при 8 - уровневом (3 - разрядном) квантовании получались изображения, которые визуально не удавалось отличить от исходных фотографий, обработанных методом ИКМ с 11- разрядными числами.

Для изображений, состоящих из последовательных кадров, например телевизионных, идеи предсказания и вычитания, связанные с ДИКМ, можно распространить на временную область. В подобных изображениях яркость многих точек от кадра к кадру не изменяется или изменяется медленно. Следовательно, можно построить систему сжатия методом ДИКМ, в которой яркость очередной точки прогнозируется на основе яркостей двумерного набора точек текущего кадра и соответствующих точек предшествующих кадров. На практике порядок временного предсказания не может быть высоким, так как для каждого временного слагаемого необходимо иметь запоминающее устройство, где сохранялся бы весь кадр. Моделирование с предсказывающим устройством третьего порядка, в котором для предсказания использовались точки, расположенные в данном (и предшествующем кадрах слева от рассматриваемой точки и вверх от нее, показало, что можно получить очень хорошие изображения при средней разрядности 1 бит/точка .

4.3.3. Схемы сокращения избыточности изображений с обработкой в области преобразований

Для пояснения основных операций, выполняемых системой сжатия видеоинформации с обработкой в области преобразований, обратимся к ковариационной матрице, определяемой соотношением (4.20). Матрица [C g ] описывает корреляцию отсчетов изображения в плоскости (х, у), являющейся координатной плоскостью изображения. Важным методом многомерного статистического анализа служит исследование массива данных не только в их естественных координатах, но и в системах координат с более удобными свойствами. В частности, весьма полезными оказались системы координат, основанные на собственных значениях и собственных векторах ковариационной матрицы

[ C g ] = [ Ф ] [

] [ Ф ] T = , (4.24)

где [Ф ] - матрица, составленная из ортогональных собственных вектор - столбцов Ф i а [ ] - диагональная матрица собственных значений.

Преобразование координат, определяемое матрицей собственных векторов [Ф ], обладает тем свойством, что оно производит преобразование заданного массива чисел в другой с некоррелированными элементами, причем получающиеся компоненты имеют убывающие дисперсии. Пусть собственные значения матрицы расставлены в убывающем порядке и пронумерованы так, что

К методам скрытия в пространственной области можно также отнести метод квантования изображения , основанный на межпиксельной зависимости, которую можно описать некоторой функцией . В простейшем случае можно вычислить разницу между смежными пикселями ; и (или и ) и задать ее как параметр функции : , где - дискретная аппроксимация разницы сигналов .

Поскольку - целое число, а реальная разница - действительное число, то возникают ошибки квантования . Для сильно коррелированных сигналов эта ошибка близка к нулю: .

При данном методе скрытие информации производится путем корректировки разностного сигнала . Стеганоключ представляет собой таблицу, которая каждому возможному значению ставит в соответствие определенный бит, например:

-4 -3 -2 -1
b i

Для скрытия i-го бита сообщения вычисляется разница . Если при этом b i , не соответствует секретному биту, который необходимо скрыть, то значение заменяется ближайшим , для которого такое условие выполняется. При этом соответствующим образом корректируются значения интенсивностей пикселей, между которыми вычислялась разница Извлечение секретного сообщения осуществляется согласно значению , соответствующему разнице .

Рассмотрим пример программы, реализующей метод квантования изображения

Исходные данные - стандартные.

Шаг 2

Стеганоключ вычисляем по модулям (М.28) и (М.29). При этом модуль (М.28) возвращает все возможные разницы сигналов (от -255 до +255), а модуль (М 29) - значения бит, соответствующие этим разницам.

Значения b i в данном случае рассчитываются на основании массива красной цветовой составляющей. При этом для каждой колонки массива R рассчитывается сумма по модулю 2 составляющих ее элементов с булевым прибавлением к результату суммирования единицы при каждом третьем элементе. В конце модуля полученной вектор b расширяется на длину вектора . Таким образом, элементы массива b носят псевдослучайный характер. Фрагменты сформированного стеганоключа показаны на рис. 5.15.

л- b=
-255
-254
-253
-252
-2
-1

Рис. 6.15. Фрагменты стеганоключа

Выполним развертывание массива контейнера С (массив синей цветовой составляющей) в вектор, используя модуль (М.16). Зададим стартовый индекс элемента полученного вектора, начиная с которого будет производиться встраивание бит, сообщения (например, ).

Для расчета величины шага (псевдослучайного интервала) используем модуль (М.15). Пусть при этом К := 8.

Шаг 4

Алгоритм встраивания реализует модуль (М.30). Формирование вектора двоичных данных из строки символов аналогично представленному в (М.21) (при этом, однако, необходимо заменить на ).

Для каждого -го бита сообщения выполняется вычисление индекса z элемента вектора контейнера Cv . Рассчитывается разница между соседними пикселями C vz и C vz-1 Внутренним циклом производится поиск соответствующего значения разницы в векторе . В случае обнаружения, переменной присваивается значение индекса i, который соответствует данной разнице в .

Если значение не соответствует текущему биту скрываемого сообщения, то выполняется поиск ближайшего индекса, при котором bi равняется биту сообщения. Поиск производится вниз (L) и вверх (Н) от индекса .

Предварительное присвоение переменным и значения ±1000 обеспечивает невозможность дублирования предыдущих значений , если движение вниз или вверх от не привело к выполнению поставленного условия (последнее возможно при нахождении индекса слишком близко к нижней или верхней границе вектора b ). После того как значения и найдены, выбирается то из них, которое ближе к начальному значению .

Интенсивность пикселя контейнера Sv z равна увеличенной на величину интенсивности смежного пикселя Sv z -1 . Если данное увеличение приводит к выходу значения интенсивности цвета за пределы диапазона , то, наоборот, интенсивности смежного пикселя Sv z -1 присваивается значение интенсивности пикселя Sv z , уменьшенной на величину ). После встраивания последнего бита сообщения внешний цикл прерывается.

Проводим обратное свертывание вектора Sv в матрицу, имеющую размерность первичного массива С (М.7). Получаем массив S .

Привлекает внимание

Например, старый добрый формат GIF использует палитру, максимум на 256 цветов. Если вы захотите сохранить серию своих селфи как gif-анимацию (кому бы это надо было), то первое, что вам, а точнее программе, которую вы будете для этого использовать, надо будет сделать – создать палитру. Можно использовать статическую палитру, например web-safe colors , алгоритм квантизации получиться очень простым и быстрым, но результат будет «не очень». Можно создать оптимальную палитру на основе цветов изображения, что даст результат наиболее визуально похожий на оригинал.

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

Далее вас ждёт скучное и непонятное повествование о методе медианного сечения, алгоритму рассеивания ошибок (шума квантизации) по Флойду-Стейнбергу (и не только), особенностях цветового восприятия человеческого глаза, а так же немного говно кода.

Предыстория

Давным-давно, когда Nokia была тёплой и ламповой главенствовала на рынке смартфонов, а владельцы смартфонов гордо звали себя «смартфонщики», в те стародавние времена писал я простенькие программки на python для series60. На одну из них намедни наткнулся копаясь в архивах. GifTool – программка для создания gif-анимации из набора картинок. В ней я реализовал квантизацию методом медианного сечения, алгоритм сжатия LZW, вся структура файла создавалась самостоятельно, для неизменившихся на следующем слайде пикселей использовалась прозрачность, чтобы уменьшить итоговый размер файла. Захотелось мне освежить свою память, посмотреть – как же она работала. Открыл код и … Это чувство, когда ты не можешь разобраться в своём говнокоде десятилетней давности. Про PEP8 я тогда не знал, поэтому читаемость кода была чуть менее чем никакой (тогда мне нравился минимализм, как и многим начинающим программистам). Прослезился, поплевался, отрефакторил в PyCharm, разобрался как реализовал метод медианного сечения, по быстрому накидал «грязный» скрипт. Работает! Палитра создаётся, изображение на выходе получается сносное. И тут меня закусило – смогу ли я добиться лучших результатов, чтобы картинка визуально была как можно ближе к оригиналу.


Итак – метод медианного сечения. Он прост до безобразия. Первым делом надо из всех уникальных цветов изображения составить RGB куб. Далее рассечь его по самой длинной стороне. Например, диапазон красного у нас от 7 до 231 (длина 231-7=224), зелёного от 32 до 170 (длина 170-32=138), синего от 12 до 250 (длина 250-12=238), значит, будем «резать» куб по синей стороне. Получившиеся сегменты так же рассекаем по длинной стороне и т.д. пока не получим 256 сегментов. Для каждого сегмента высчитать средний цвет – так мы и получим палитру.

Пара картинок почти в тему, для наглядности



Что здесь можно улучшить? Первое, что приходит в голову – вычислять средний цвет не тупо сложив все цвета и разделив на их количество [ sum(color) / count(color) ], а с учётом, сколько раз каждый цвет встречается в изображении. То есть каждый цвет умножаем на количество его вхождений в изображении, полученные значения складываем, результат делим на количество вхождений в изображении всех цветов данного сегмента [ sum(color * total) / sum(total) ]. В результате, наиболее часто встречаемые цвета имеют приоритет при вычислении, но и редкие цвета вносят свои корректировки, поэтому палитра получается лучше, визуальное отклонение цветов меньше. Для лучших результатов желательно ещё учитывать гамму, но я оставил это на потом. Второе не так явно – медианное сечение совсем не учитывает особенности восприятия цвета человеческим глазом. Оттенки зелёного мы воспринимаем гораздо лучше оттенков синего. Я решил исправить это недоразумение и «сплющил» куб – длины сторон помножил на коэффициенты из этой статьи . В результате по зелёной и красной стороне сечений стало больше, по синей меньше. Такого решения я больше нигде не встречал (может плохо искал), но результат на лицо.

Теперь у нас есть оптимальная палитра, не идеальная конечно (я знаю, что её можно ещё улучшить), но достаточно хорошая. Следующий шаг – индексирование цветов изображения. Самый простой вариант – в каком сегменте находится цвет, такой и индекс. Быстро и просто. Но есть одно но, и даже не одно, поэтому к данному шагу мы ещё вернёмся.

Есть ещё один способ улучшить качество получаемого изображения – рассеивание ошибок. Тут тоже всё довольно просто – из индексируемого цвета вычитаем соответствующий цвет палитры, получаем ошибку, рассеиваем её по соседним пикселям в соответствии с определённой формулой (шаблоном), самая известная формула Флойда-Стейнберга, её я и использовал. При рассеивании ошибок размываются резкие переходы между цветами, и визуально кажется, что изображение содержит больше оттенков (цветов). Если интересно – про рассеивание ошибок подробно и интересно можно почитать . Этот алгоритм я так же решил допилить, помножив ошибку на всё те же коэффициенты, как оказалось, это была очень хорошая идея – так как сечений по синему диапазону стало меньше, в нём получалась значительная ошибка, и без корректировки ошибки коэффициентами рассеивание вносило много «шума».

Вот теперь можно снова вернуться к индексированию. Рассеиванием ошибок мы изменяем цвета пикселей и получаем такие, которых нет в нашем RGB-кубе (напомню, он составлен исключительно из цветов изображения). Теперь нельзя просто посмотреть в каком сегменте находится цвет, чтобы назначить индекс. Решение нашлось сразу – поиск ближайшего цвета в палитре . В данную формулу я подставил всё те же коэффициенты. Сравнивая результаты подбора цвета палитры по индексу сегмента в который входит исходный цвет и результаты поиска ближайшего цвета, наглядно увидел, что ближайший цвет часто оказывается в соседнем сегменте. Если исходный цвет находится ближе к центру сегмента – то индекс сегмента соответствует индексу цвета в палитре, но чем ближе исходный цвет к краям сегмента, тем больше вероятность, что ближайший цвет окажется в соседнем сегменте. В общем, единственный правильный путь индексирования – поиск ближайшего цвета в палитре. Но у поиска есть минус – он медленный, очень медленный. Писать числодробилку на python плохая идея.

Ну вот, хотел объяснить в двух словах, а получилась целая куча непонятной писанины. Надеюсь, код я пишу лучше, чем объясняю, поэтому вот ссылочка на github . Код несколько раз переписывался, сначала совершенствовался алгоритм, пока результат меня не устроил, потом оказалось, что он жрёт слишком много оперативы при обработке фотографий (сначала тестировал на небольших картинках), пришлось перенести RGB-куб, медианное сечение и карту пикселей в базу данных (sqlite). Скрипт работает очень медленно, но результат получается лучше, чем квантизация средствами PIL/Pillow и GIMP’ом (в нём эта операция называется индексирование).

Наглядная демонстрация:

Оригинал

Результат квантизации в GIMP, оптимальная палитра на 256 цветов + размывание цвета по Флойду-Стенбергу (нормальное)

Результат квантизации PIL/Pillow image.convert(mode="P", dither=PIL.Image.FLOYDSTEINBERG, palette=PIL.Image.ADAPTIVE, colors=256)

Результат квантизации моим кодом

На что обратить внимание: рассеивание ошибки у GIMP сильно «шумит», PIL/Pillow создает не очень оптимальную палитру и практически не рассеивает ошибки (резкие переходы между цветами).
Если не видите разницу - посмотрите другие примеры на github .


P.S.: есть замечательная программа Color Quantizer , которая справляется с данной задачей лучше и быстрее, поэтому практического смысла мой скрипт не имеет, сделан исключительно из «спортивного» интереса.
UPD: обновил проект на github . Добавил алгоритм квантизации Octree (октодерево), популярные формулы рассеивания ошибок, поиск ближайшего цвета по среднему значению красного.

Привлекает внимание

Например, старый добрый формат GIF использует палитру, максимум на 256 цветов. Если вы захотите сохранить серию своих селфи как gif-анимацию (кому бы это надо было), то первое, что вам, а точнее программе, которую вы будете для этого использовать, надо будет сделать – создать палитру. Можно использовать статическую палитру, например web-safe colors , алгоритм квантизации получиться очень простым и быстрым, но результат будет «не очень». Можно создать оптимальную палитру на основе цветов изображения, что даст результат наиболее визуально похожий на оригинал.

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

Далее вас ждёт скучное и непонятное повествование о методе медианного сечения, алгоритму рассеивания ошибок (шума квантизации) по Флойду-Стейнбергу (и не только), особенностях цветового восприятия человеческого глаза, а так же немного говно кода.

Предыстория

Давным-давно, когда Nokia была тёплой и ламповой главенствовала на рынке смартфонов, а владельцы смартфонов гордо звали себя «смартфонщики», в те стародавние времена писал я простенькие программки на python для series60. На одну из них намедни наткнулся копаясь в архивах. GifTool – программка для создания gif-анимации из набора картинок. В ней я реализовал квантизацию методом медианного сечения, алгоритм сжатия LZW, вся структура файла создавалась самостоятельно, для неизменившихся на следующем слайде пикселей использовалась прозрачность, чтобы уменьшить итоговый размер файла. Захотелось мне освежить свою память, посмотреть – как же она работала. Открыл код и … Это чувство, когда ты не можешь разобраться в своём говнокоде десятилетней давности. Про PEP8 я тогда не знал, поэтому читаемость кода была чуть менее чем никакой (тогда мне нравился минимализм, как и многим начинающим программистам). Прослезился, поплевался, отрефакторил в PyCharm, разобрался как реализовал метод медианного сечения, по быстрому накидал «грязный» скрипт. Работает! Палитра создаётся, изображение на выходе получается сносное. И тут меня закусило – смогу ли я добиться лучших результатов, чтобы картинка визуально была как можно ближе к оригиналу.


Итак – метод медианного сечения. Он прост до безобразия. Первым делом надо из всех уникальных цветов изображения составить RGB куб. Далее рассечь его по самой длинной стороне. Например, диапазон красного у нас от 7 до 231 (длина 231-7=224), зелёного от 32 до 170 (длина 170-32=138), синего от 12 до 250 (длина 250-12=238), значит, будем «резать» куб по синей стороне. Получившиеся сегменты так же рассекаем по длинной стороне и т.д. пока не получим 256 сегментов. Для каждого сегмента высчитать средний цвет – так мы и получим палитру.

Пара картинок почти в тему, для наглядности



Что здесь можно улучшить? Первое, что приходит в голову – вычислять средний цвет не тупо сложив все цвета и разделив на их количество [ sum(color) / count(color) ], а с учётом, сколько раз каждый цвет встречается в изображении. То есть каждый цвет умножаем на количество его вхождений в изображении, полученные значения складываем, результат делим на количество вхождений в изображении всех цветов данного сегмента [ sum(color * total) / sum(total) ]. В результате, наиболее часто встречаемые цвета имеют приоритет при вычислении, но и редкие цвета вносят свои корректировки, поэтому палитра получается лучше, визуальное отклонение цветов меньше. Для лучших результатов желательно ещё учитывать гамму, но я оставил это на потом. Второе не так явно – медианное сечение совсем не учитывает особенности восприятия цвета человеческим глазом. Оттенки зелёного мы воспринимаем гораздо лучше оттенков синего. Я решил исправить это недоразумение и «сплющил» куб – длины сторон помножил на коэффициенты из . В результате по зелёной и красной стороне сечений стало больше, по синей меньше. Такого решения я больше нигде не встречал (может плохо искал), но результат на лицо.

Теперь у нас есть оптимальная палитра, не идеальная конечно (я знаю, что её можно ещё улучшить), но достаточно хорошая. Следующий шаг – индексирование цветов изображения. Самый простой вариант – в каком сегменте находится цвет, такой и индекс. Быстро и просто. Но есть одно но, и даже не одно, поэтому к данному шагу мы ещё вернёмся.

Есть ещё один способ улучшить качество получаемого изображения – рассеивание ошибок. Тут тоже всё довольно просто – из индексируемого цвета вычитаем соответствующий цвет палитры, получаем ошибку, рассеиваем её по соседним пикселям в соответствии с определённой формулой (шаблоном), самая известная формула Флойда-Стейнберга, её я и использовал. При рассеивании ошибок размываются резкие переходы между цветами, и визуально кажется, что изображение содержит больше оттенков (цветов). Если интересно – про рассеивание ошибок подробно и интересно можно почитать . Этот алгоритм я так же решил допилить, помножив ошибку на всё те же коэффициенты, как оказалось, это была очень хорошая идея – так как сечений по синему диапазону стало меньше, в нём получалась значительная ошибка, и без корректировки ошибки коэффициентами рассеивание вносило много «шума».

Вот теперь можно снова вернуться к индексированию. Рассеиванием ошибок мы изменяем цвета пикселей и получаем такие, которых нет в нашем RGB-кубе (напомню, он составлен исключительно из цветов изображения). Теперь нельзя просто посмотреть в каком сегменте находится цвет, чтобы назначить индекс. Решение нашлось сразу – поиск ближайшего цвета в палитре . В данную формулу я подставил всё те же коэффициенты. Сравнивая результаты подбора цвета палитры по индексу сегмента в который входит исходный цвет и результаты поиска ближайшего цвета, наглядно увидел, что ближайший цвет часто оказывается в соседнем сегменте. Если исходный цвет находится ближе к центру сегмента – то индекс сегмента соответствует индексу цвета в палитре, но чем ближе исходный цвет к краям сегмента, тем больше вероятность, что ближайший цвет окажется в соседнем сегменте. В общем, единственный правильный путь индексирования – поиск ближайшего цвета в палитре. Но у поиска есть минус – он медленный, очень медленный. Писать числодробилку на python плохая идея.

Ну вот, хотел объяснить в двух словах, а получилась целая куча непонятной писанины. Надеюсь, код я пишу лучше, чем объясняю, поэтому вот ссылочка на github . Код несколько раз переписывался, сначала совершенствовался алгоритм, пока результат меня не устроил, потом оказалось, что он жрёт слишком много оперативы при обработке фотографий (сначала тестировал на небольших картинках), пришлось перенести RGB-куб, медианное сечение и карту пикселей в базу данных (sqlite). Скрипт работает очень медленно, но результат получается лучше, чем квантизация средствами PIL/Pillow и GIMP’ом (в нём эта операция называется индексирование).

Наглядная демонстрация:

Оригинал

Результат квантизации в GIMP, оптимальная палитра на 256 цветов + размывание цвета по Флойду-Стенбергу (нормальное)

Результат квантизации PIL/Pillow image.convert(mode="P", dither=PIL.Image.FLOYDSTEINBERG, palette=PIL.Image.ADAPTIVE, colors=256)

Результат квантизации моим кодом

На что обратить внимание: рассеивание ошибки у GIMP сильно «шумит», PIL/Pillow создает не очень оптимальную палитру и практически не рассеивает ошибки (резкие переходы между цветами).
Если не видите разницу - посмотрите другие примеры на github .


P.S.: есть замечательная программа Color Quantizer , которая справляется с данной задачей лучше и быстрее, поэтому практического смысла мой скрипт не имеет, сделан исключительно из «спортивного» интереса.
UPD: обновил проект на github . Добавил алгоритм квантизации Octree (октодерево), популярные формулы рассеивания ошибок, поиск ближайшего цвета по среднему значению красного.