Оригинал статьи здесь

Формат хранения картинок *.pph 

В данной статье изложен принцип, лежащий в основе алгоритма сжатия PPH. Что же может заставить лезть в столь избитую тему, как хранение картинок? Прежде всего сложность, присущая распространенному алгоритму JPEG, кажущаяся мне совершенно неоправданной и пугающей.

Все алгоритмы сжатия изображений в той или иной степени используют свойства этого множества информации. Здесь будет не лишним задуматься, какими же свойствами обладают картинки? Коротко можно сказать, что изменение яркости и цвета в большей части картинки происходит “медленно”, а цвет имеет свойство меняться совсем медленно. Итак имеем:

малое отличие друг от друга соседних элементов изображения (1)

Сначала рассмотрим случай с двумя элементами (соседями) и попробуем извлечь из этого выгоду.

Два числа.

Есть два числа А и Б, обладающие свойством (1), набор (А,Б) можно представить в виде (s,d), где s = А + Б, d = Б - А . В результате имеем преобразование:

, Б) ® (s, d),

новая пара (s,d) хороша тем, что вследствие свойства (1) d является небольшой величиной, а значит для хранения этого числа требуется меньше единиц информации.

Примечание: в случае с целыми числами, как у картинок, s и d имеют одинаковую четность, следовательно, можно делить s на два (с округлением в одну сторону), при этом информация не потеряется, а мы будем иметь s в тех же пределах, что А и Б, и в паре с ним маленький d.

В реальном изображении обычно присутствуют более чем два элемента (пиксела), а значит и более одной пары соседей.

Длинный случай

В случае с большим количеством элементов можно разбить их на пары (А,Б). Для простоты возьмем за количество элементов 2N.

В результате преобразования каждой пары (А,Б) получаем N пар (s,d).

ris1

рис. 1. Разбиение M на пару (MS,MD)

Теперь разделим пары (s,d) на два множества MS и MD, как это показано на рисунке 1. Несложно заметить, что размер каждого из них равен N, а множество MD состоит из небольших по абсолютному значению величин.

Длинный случай в рекурсии

Представленное преобразование множества M в пару (MS,MD) можно углубить, т.е. применить преобразование M ® (MS,MD) к полученным множествам MS и MD.

ris2

рис.2 Преобразование M в Q

Сведем все в указанном на рисунке 2 порядке в множество Q. Если внимательно посмотреть на полученное множество, то внимание привлекает интересная особенность, а именно, что слева стоят большие по абсолютному значению элементы, а справа меньшие. Но это только на первый взгляд, на самом деле распределение задается характером множества M. Исследование показало, например, что множество MSDDD… обычно имеет меньшие по абсолютному значению элементы, чем множество MDSSS….

Можно сказать, что каждый элемент множества Q имеет свой уникальный смысл, являясь суммой всех элементов множества M с разным знаком. Однако, те из элементов Q, чье абсолютное значение больше, имеют более важное значение для восстановления M, т.к. кроют в себе наиболее значимые особенности M. В то же время эксперимент показывает, что если обнулить те элементы Q что ниже (по абсолютному значению) некоторого порога p и затем восстановить M, то результат практически не отличается от исходного M при обнулении половины элементов Q.

Количество вычислений в задаче рекурсивного преобразования M в Q несложно показать. Сложность задачи разложения M на пару (MS,MD) прямо пропорциональна N - количеству элементов в M. А уровней разложения будет log2N, соответственно сложность полного преобразования эквивалентна Nlog2N.

Двумерный случай

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

Возьмем упрощенный случай, когда изображение имеет размер 2nx2m и хранится в множестве M2. Тогда преобразуем M2 в M следующим образом:

ris3

рис. 3. Z-преобразование

Данное преобразование замечательно тем, что каждая пара полученного множества M (v1,v2),(v3,v4),(v5,v6)… получается из пары M2, состоящей из соседних элементов M2. Что примечательно, получаемые при преобразовании M ® Q множества MS, MD, MSS, MSD и т.д., обладают тем же свойством.

ris4

рис. 4. Преобразование множества M2 в множество Q

Преобразовав M2 ® M ® Q, получаем набор коэффициентов Q, который необходимо сохранить.

Сохранение множества Q с потерями

Как упоминалось ранее, значимость элементов Q для восстановления исходного изображения распределена не равномерно. А именно, важность элемента (относительно восстановления) эквивалентна его абсолютному значению.

Здесь можно пойти разными путями. Я могу показать свое решение.

Определим некоторое натуральное число F как параметр точности, и преобразуем Q в Q’ так, что каждый элемент Q’ равен соответствующему элементу из Q деленному нацело на F. Как видно из этого преобразования, все элементы, абсолютное значение которых меньше F, обнулились, а остальные при восстановлении будут кратны F. При этом относительная точность (отношение первоначального к восстановленному) будет выше у изначально больших элементов.

Полученное множество Q’ можно сохранять, как душа пожелает (тему можно развернуть).

Многомерный случай

Очевидно, что, при увеличении количества измерений, у элемента из Mn возрастает количество соседних элементов. Это приводит к тому, что полученное множество Q содержит меньшую долю значимых коэффициентов. А значит, и сохранять с потерями соответствующие множества можно эффективнее.

Автор полагает, что степень сжатия с приемлемыми потерями (отношение исходного количества информации к результату) для множества Mn равна w n, где w - показатель потери качества. Например, в случае с видео можно добиться лучшего коэффициента сжатия, если сжимать ряд кадров, как единое исходное множество.

Цвет

Как известно, информация о цвете употребляется глазом человека в гораздо меньших объемах, чем информация о яркости. Тем не менее компьютерное представление цветного изображения подразумевает утроение количества информации за счет разложения информации о цвете в три слоя (RGB). С этим необходимо бороться, тем более при сжатии картинок. Существует общепризнанная схема преобразования RGB-слоев в YCbCr-слои, призванная отделить слои, представляющие информацию о цвете от слоя яркости, следующим образом:

 Y = 0.299 R +0.587 G +0.114 B

Cb = -0.1687R -0.3313G +0.5 B + 128

Cr = 0.5 R -0.4187G -0.0813B + 128

 R = Y + 1.402 (Cr-128)

G = Y - 0.34414(Cb-128) – 0.71414 (Cr-128)

B = Y + 1.772 (Cb-128)

 Преобразование RGB ® YCbCr  Преобразование YCbCr ® RGB
Примечание: в нашем случае, когда интервал получаемых значений не критичен, 128 можно смело заменить на 0.

Заметьте, что Y-слой представляет из себя некоторое подобие яркости, тогда как остальные слои несут информацию о цвете.

При сжатии YCbCr-слоев достаточно сложных (в т.ч. и в смысле цвета) картинок автор нашел весьма любопытным факт, что при сжатии оба “цветовых” слоя (Cb и Cr) вместе требуют для приемлемого качества порядка 10-15% общего объема сжатого материала.

Эпилог

Хотелось бы отметить некоторую деталь: вычисления, основанные на алгоритме преобразования Mn ® M ® Q на 90% состоят из операций сложения и вычитания. Лишь сохранение Q с потерями требует работы с битами и, соответственно, съедает больше времени.

Автор, на основе вышеизложенного и оригинального метода сохранения множества Q, разработал алгоритм сжатия картинок, по качеству близкий (иногда опережающий) к известному алгоритму JPEG. Работу вместе с исходным текстом программ (на Delphi) можно посмотреть в интернете: http://popitch.narod.ru/pph/

Буду рад критическим замечаниям пришедшим по почте.

 

Снятие «сливок» с множества Q

 

Картинка уже преобразована в один (в случае с черно-белым изображением) или три (Y, Cb, Cr – исходные слои) множества Q. Теперь их необходимо сохранить. Предлагаю описание метода, пришедшего в результате предпринятых изысканий. Предлагаемый алгоритм не претендует на идеальность.

Для начала введем параметр точности сохранения, пусть это будет натуральное число F, с точностью до которого будут сохранены все элементы множества Q. Отметим, что Q состоит из 2m+n коэффициентов, полученных преобразованием из двумерного множества M2 размером 2mx2n.

Преобразование Q à QF

Каждый элемент из Q делится на F и результат записывается в QF. Далее сохраняем полученные “сливки”.

ris1

Рис. 1 Преобразование M2 à Q

Примечание: здесь кроется маленькая хитрость. Обычно при делении числа нацело, результат округляется в сторону нуля, а значит восстановленные значения тоже будут ближе к нулю. Все, кроме первого (первый – MSSS… является суммой M), коэффициенты из Q являются суммой всех членов M с разным знаком. Иначе говоря, разностью двух областей M2, а значит, округление в сторону нуля приведет к потери контрастности (станет ближе к нулю разность соседних элементов). Выход заключается в округлении (при делении) в ближайшую сторону, при котором контрастность практически в равной степени где-то повысится, а где-то уменьшится.

Точное сохранение QF

Алгоритм выглядит следующим образом:

1. p=1, pold=0, Nold = количество_элементов_в_QF

2. создадим массив битов B длиной в Nold бит, в который о всех из QF, что по абсолютному значению не меньше pold:

вместе с тем подсчитаем количество установленных битов в переменную N

3. сохраним B (размером в Nold бит) с помощью специальной процедуры, см. ниже.

4. если N ¹ 0, то: Nold = N, pold = p, p = 2p, переходим к пункту 2.

Таким образом, сначала сохранится массив B, указывающий на ненулевые элементы QF. Далее – массив B рассказывающий о тех ненулевых, что по абсолютному значению не меньше 2. Потом о тех, кто по модулю не меньше 2, укажем кто еще и не меньше 4. 8, 16 и т.д.

Короче говоря, мы сохранили старшие биты абсолютных значений всех элементов QF.

5. сохраним для каждого элемента QF: все биты его абсолютного значения, которые младше максимально сохраненного (без сжатия)

6. для всех ненулевых элементов QF: сохраним их знак (1 если больше 0, иначе 0), тоже без сжатия (просто в поток).

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

Размер картинки

Мы почти научились сохранять множество M2, но только размером 2mx2n. Сначала автор решил преобразовывать картинку из любого размера в 2mx2n, но при этом приходилось делать два преобразования (туда – обратно), что сжирало время и делало изображение нерезким. Значит ничего не поделаешь, а изображение придется резать на куски.

Разрежем, для примера, число на составные части размером 2x (рис. 2а.) и картинку (рис. 2б).

а)ris2

б)ris3

рис. 2 Почикать на кусочки: а – число, б – картинку.

Как видно из примера, получилось 6 частей требуемого размера. В худшем случае их может получиться многовато, а именно - 100 в случае с исходным размером картинки 1023x1023, это не хорошо! Если читатель может придумать что-то лучше, прошу сообщить мне о находке.

Еще прошу сообщить, если известно, где можно найти подробное описание всех процедур, применяемых в алгоритме wavelet, некоторое сходство с коим было обнаружено внимательным читателем предыдущей главы.

В следующей главе мы обсудим не влезшее в данное описание сжатие массива битов B, основанное на его одном замечательном свойстве.

 

Сжатие массива бит B без потерь

 

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

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

Процедура является рекурсивной и на входе получает: B – массив битов, N – размер массива, M – количество установленных бит. Из комбинаторики имеем: количество вариантов расстановки M бит в массиве B равно количеству сочетаний по M из N – CMN.

Пополам

Если это значение больше максимально допустимого в переменной типа Integer, то делим множество B пополам, подсчитываем M1 – количество установленных бит в первой половине, сохраняем это количество (во второй половине становится тоже известно, потому как M1+M2=M), и вызываем сохранение обеих половин.

Примечание: можно оптимизировать сохранение за счет того, что иногда M1 находится на отрезке более узком, чем [0, SIZE/2], а именно интервал значений числа M1 равен [max(0,M-SIZE/2), min(M,SIZE/2)]. Значит сохранить остается только разность между M1 и минимальным значением отрезка с учетом, что максимальное значение этой разности равно длине того же отрезка.

Код

В случае же, когда ограничения числа типа Integer не мешают нам поместить код комбинации в переменной, мы можем сохранить число, кодирующее данную комбинацию установленных бит. Например, если в массиве из 100 бит установленных всего 5 (или 95), то количество различных комбинаций равно 75287520, значит код комбинации займет 27 бит, т.к. 226<75287520<=227, налицо сжатие почти в 4 раза.

Код комбинации вычисляется так. При установленном первом бите код равен коду подмножества B состоящего из всех остальных битов (всего CM-1N-1 комбинаций), иначе сумме CM-1N-1 и кода того же подмножества, состоящего теперь из CMN-1 комбинаций, т.к. количество установленных бит не изменилось. Заметьте: CMN = CMN-1 + CM-1N-1. Алгоритм может показаться рекурсивным, но он легко реализуется циклом.

Реализацию данного алгоритма на Delphi см. http://popitch.narod.ru/pph/pphsource.zip.

Схема

В целом алгоритм сжатия выглядит следующим образом:

sxema

Вот теперь все! Вроде бы, надеюсь описывать алгорим восстановления нет необходимости, хотя…

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

© Сережа Попов, 2001-02-20