Сжатие изображений с потерей качества. Алгоритм JPEG. Изобретаем JPEG

Легко подсчитать, что несжатое полноцветное изображение, размером 2000*1000 пикселов будет иметь размер около 6 мегабайт. Если говорить об изображениях, получаемых с профессиональных камер или сканеров высокого разрешения, то их размер может быть ещё больше. Не смотря на быстрый рост ёмкости устройств хранения, по-прежнему весьма актуальными остаются различные алгоритмы сжатия изображений.
Все существующие алгоритмы можно разделить на два больших класса:

  • Алгоритмы сжатия без потерь;
  • Алгоритмы сжатия с потерями.
Когда мы говорим о сжатии без потерь, мы имеем в виду, что существует алгоритм, обратный алгоритму сжатия, позволяющий точно восстановить исходное изображение. Для алгоритмов сжатия с потерями обратного алгоритма не существует. Существует алгоритм, восстанавливающий изображение не обязательно точно совпадающее с исходным. Алгоритмы сжатия и восстановления подбираются так, чтобы добиться высокой степени сжатия и при этом сохранить визуальное качество изображения.

Алгоритмы сжатия без потерь

Алгоритм RLE
Все алгоритмы серии RLE основаны на очень простой идее: повторяющиеся группы элементов заменяются на пару (количество повторов, повторяющийся элемент). Рассмотрим этот алгоритм на примере последовательности бит. В этой последовательности будут чередовать группы нулей и единиц. Причём в группах зачастую будет более одного элемента. Тогда последовательности 11111 000000 11111111 00 будет соответствовать следующий набор чисел 5 6 8 2. Эти числа обозначают количество повторений (отсчёт начинается с единиц), но эти числа тоже необходимо кодировать. Будем считать, что число повторений лежит в пределах от 0 до 7 (т.е. нам хватит 3 бит для кодирования числа повторов). Тогда рассмотренная выше последовательность кодируется следующей последовательностью чисел 5 6 7 0 1 2. Легко подсчитать, что для кодирования исходной последовательности требуется 21 бит, а в сжатом по методу RLE виде эта последовательность занимает 18 бит.
Хоть этот алгоритм и очень прост, но эффективность его сравнительно низка. Более того, в некоторых случаях применение этого алгоритма приводит не к уменьшению, а к увеличению длины последовательности. Для примера рассмотрим следующую последовательность 111 0000 11111111 00. Соответствующая ей RL-последовательность выглядит так: 3 4 7 0 1 2. Длина исходной последовательности – 17 бит, длина сжатой последовательности – 18 бит.
Этот алгоритм наиболее эффективен для чёрно-белых изображений. Также он часто используется, как один из промежуточных этапов сжатия более сложных алгоритмов.

Словарные алгоритмы

Идея, лежащая в основе словарных алгоритмов, заключается в том, что происходит кодирование цепочек элементов исходной последовательности. При этом кодировании используется специальный словарь, который получается на основе исходной последовательности.
Существует целое семейство словарных алгоритмов, но мы рассмотрим наиболее распространённый алгоритм LZW, названный в честь его разработчиков Лепеля, Зива и Уэлча.
Словарь в этом алгоритме представляет собой таблицу, которая заполняется цепочками кодирования по мере работы алгоритма. При декодировании сжатого кода словарь восстанавливается автоматически, поэтому нет необходимости передавать словарь вместе с сжатым кодом.
Словарь инициализируется всеми одноэлементными цепочками, т.е. первые строки словаря представляют собой алфавит, в котором мы производим кодирование. При сжатии происходит поиск наиболее длинной цепочки уже записанной в словарь. Каждый раз, когда встречается цепочка, ещё не записанная в словарь, она добавляется туда, при этом выводится сжатый код, соответствующий уже записанной в словаре цепочки. В теории на размер словаря не накладывается никаких ограничений, но на практике есть смысл этот размер ограничивать, так как со временем начинаются встречаться цепочки, которые больше в тексте не встречаются. Кроме того, при увеличении размеры таблицы вдвое мы должны выделять лишний бит для хранения сжатых кодов. Для того чтобы не допускать таких ситуаций, вводится специальный код, символизирующий инициализацию таблицы всеми одноэлементными цепочками.
Рассмотрим пример сжатия алгоритмом. Будем сжимать строку кукушкакукушонкукупилакапюшон. Предположим, что словарь будет вмещать 32 позиции, а значит, каждый его код будет занимать 5 бит. Изначально словарь заполнен следующим образом:

Эта таблица есть, как и на стороне того, кто сжимает информацию, так и на стороне того, кто распаковывает. Сейчас мы рассмотрим процесс сжатия.

В таблице представлен процесс заполнения словаря. Легко подсчитать, что полученный сжатый код занимает 105 бит, а исходный текст (при условии, что на кодирование одного символа мы тратим 4 бита) занимает 116 бит.
По сути, процесс декодирования сводится к прямой расшифровке кодов, при этом важно, чтобы таблица была инициализирована также, как и при кодировании. Теперь рассмотрим алгоритм декодирования.


Строку, добавленную в словарь на i-ом шаге мы можем полностью определить только на i+1. Очевидно, что i-ая строка должна заканчиваться на первый символ i+1 строки. Т.о. мы только что разобрались, как можно восстанавливать словарь. Некоторый интерес представляет ситуация, когда кодируется последовательность вида cScSc, где c - это один символ, а S - строка, причём слово cS уже есть в словаре. На первый взгляд может показаться, что декодер не сможет разрешить такую ситуацию, но на самом деле все строки такого типа всегда должны заканчиваться на тот же символ, на который они начинаются.

Алгоритмы статистического кодирования
Алгоритмы этой серии ставят наиболее частым элементам последовательностей наиболее короткий сжатый код. Т.е. последовательности одинаковой длины кодируются сжатыми кодами различной длины. Причём, чем чаще встречается последовательность, тем короче, соответствующий ей сжатый код.
Алгоритм Хаффмана
Алгоритм Хаффмана позволяет строить префиксные коды. Можно рассматривать префиксные коды как пути на двоичном дереве: прохождение от узла к его левому сыну соответствует 0 в коде, а к правому сыну – 1. Если мы пометим листья дерева кодируемыми символами, то получим представление префиксного кода в виде двоичного дерева.
Опишем алгоритм построения дерева Хаффмана и получения кодов Хаффмана.
  1. Символы входного алфавита образуют список свободных узлов. Каждый лист имеет вес, который равен частоте появления символа
  2. Выбираются два свободных узла дерева с наименьшими весами
  3. Создается их родитель с весом, равным их суммарному весу
  4. Родитель добавляется в список свободных узлов, а двое его детей удаляются из этого списка
  5. Одной дуге, выходящей из родителя, ставится в соответствие бит 1, другой - бит 0
  6. Шаги, начиная со второго, повторяются до тех пор, пока в списке свободных узлов не останется только один свободный узел. Он и будет считаться корнем дерева.
С помощью этого алгоритма мы можем получить коды Хаффмана для заданного алфавита с учётом частоты появления символов.
Арифметическое кодирование
Алгоритмы арифметического кодирования кодируют цепочки элементов в дробь. При этом учитывается распределение частот элементов. На данный момент алгоритмы арифметического кодирования защищены патентами, поэтому мы рассмотрим только основную идею.
Пусть наш алфавит состоит из N символов a1,…,aN, а частоты их появления p1,…,pN соответственно. Разобьем полуинтервал

(0,57); (0,45); (4,23); (1,-30); (0,-16); (2,1); (0,0)

Другая ОСНОВНАЯ вещь: Допустим, где-нибудь на квантованном векторе мы имеем:

57, восемнадцать нулей, 3, 0,0 ,0,0 2, тридцать-три нуля, 895, EOB

Кодирование Хаффмана JPG делает ограничение, по которому число предшествующих нулей должно кодироваться как 4-битовая величина - не может превысить 15.

Так, предшествующий пример должен быть закодирован как:

(0,57); (15,0) (2,3); (4,2); (15,0) (15,0) (1,895), (0,0)

(15,0) - специальная кодированная величина, которая указывает , что там следует за 16 последовательными нулями.

5.3 Конечный шаг - кодирование Хаффмана

Сначала ВАЖНОЕ примечание: Вместо хранения фактической величины, JPEG стандарт определяет, что мы храним минимальный размер в битах, в котором мы можем держать эту величину (это названо категория этой величины) и затем битно кодированное представление этой величины подобно этому:

7,..,-4,4,..,7 3 000,001,010,011,100,101,110,111

15,..,-8,8,..,15 4 0000,..,0111,1000,..,1111

31,..,-16,16,..,31 5 00000,..,01111,10000,..,11111

63,..,-32,32,..,63 6 .

127,..,-64,64,..,127 7 .

255,..,-128,128,..,255 8 .

511,..,-256,256,..,511 9 .

1023,..,-512,512,..,1023 10 .

2047,..,-1024,1024,..,2047 11 .

4095,..,-2048,2048,..,4095 12 .

8191,..,-4096,4096,..,8191 13 .

16383,..,-8192,8192,..,16383 14 .

32767,..,-16384,16384,..,32767 15 .

Впоследствии для предшествующего примера:

(0,57); (0,45); (4,23); (1,-30); (0,-8); (2,1); (0,0)

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

45, аналогично , будет закодирован как (6,101101)

30 -> (5,00001)

И теперь, мы напишем снова строку пар:

(0,6), 111001; (0,6), 101101; (4,5), 10111; (1,5), 00001; (0,4), 0111; (2,1), 1; (0,0)

Пары 2 величин, заключенные в скобки, могут быть представлены в байте, так как фактически каждая из 2 величин может быть представлена в 4-битном кусочке (счетчик предшествующих нулей - всегда меньше, чем 15 и также как и категория [числа закодированные в файле JPG - в области -32767..32767]). В этом байте, старший кусочек представляет число предшествующих нулей, а младший кусочек - категорию новой величины, отличной от 0.

Конечный шаг кодировки состоит в кодировании Хаффмана этого байта, и затем записи в файле JPG , как поток из битов, кода Хаффмана этого байта, сопровождающийся битовым представлением этого числа.

Например, для байта 6 (эквивалент (0,6)) у нас есть код Хаффмана = 111000;

21 = (1,5) - 11111110110

4 = (0,4) - 1011

33 = (2,1) - 11011

0 = EOB= (0,0) - 1010

Конечный поток битов записанных в файле JPG на диск для предшествующего примера 63 коэффициентов (запомните, что мы пропустили первый коэффициент) -

111000 111001 111000 101101 1111111110011001 10111 11111110110 00001

1011 0111 11011 1 1010
Достоинства и недостатки

К недостаткам формата следует отнести то, что при сильных степенях сжатия дает знать о себе блочная структура данных, изображение «дробится на квадратики» (каждый размером 8x8 пикселей). Этот эффект особенно заметен на областях с низкой пространственной частотой (плавные переходы изображения, например, чистое небо). В областях с высокой пространственной частотой (например, контрастные границы изображения), возникают характерные «артефакты» - иррегулярная структура пикселей искаженного цвета и/или яркости. Кроме того, из изображения пропадают мелкие цветные детали. Не стоит также забывать и о том, что данный формат не поддерживает прозрачность.

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

2. Алгоритм JPEG2000

Алгоритм JPEG-2000 разработан той же группой экспертов в области фотографии, что и JPEG. Формирование JPEG как международного стандарта было закончено в 1992 году. В 1997 стало ясно, что необходим новый, более гибкий и мощный стандарт, который и был доработан к зиме 2000 года.

Основные отличия алгоритма в JPEG 2000 от алгоритма в JPEG заключаются в следующем:

1)Лучшее качество изображения при сильной степени сжатия. Или, что то же самое , большая степень сжатия при том же качестве для высоких степеней сжатия. Фактически это означает заметное уменьшение размеров графики "Web-качества", используемой большинством сайтов.

2)Поддержка кодирования отдельных областей с лучшим качеством. Известно, что отдельные области изображения критичны для восприятия человеком (например, глаза на фотографии), в то время как качеством других можно пожертвовать (например, задний план). При "ручной" оптимизации увеличение степени сжатия проводится до тех пор, пока не будет потеряно качество в какой-то важной части изображения. Сейчас появляется возможность задать качество в критичных областях, сжав остальные области сильнее, т.е. мы получаем еще большую окончательную степень сжатия при субъективно равном качестве изображения.

3)Основной алгоритм сжатия заменен на wavelet. Помимо указанного повышения степени сжатия это позволило избавиться от 8-пиксельной блочности, возникающей при повышении степени сжатия. Кроме того, плавное проявление изображения теперь изначально заложено в стандарт (Progressive JPEG, активно применяемый в Интернет, появился много позднее JPEG).

4)Для повышения степени сжатия в алгоритме используется арифметическое сжатие. Изначально в стандарте JPEG также было заложено арифметическое сжатие, однако позднее оно было заменено менее эффективным сжатием по Хаффману, поскольку арифметическое сжатие было защищено патентами. Сейчас срок действия основного патента истек , и появилась возможность улучшить алгоритм.

5)Поддержка сжатия без потерь. Помимо привычного сжатия с потерями новый JPEG теперь будет поддерживать и сжатие без потерь. Таким образом, становится возможным использование JPEG для сжатия медицинских изображений, в полиграфии, при сохранении текста под распознавание OCR системами и т.д.

6)Поддержка сжатия однобитных (2-цветных) изображений. Для сохранения однобитных изображений (рисунки тушью, отсканированный текст и т.п.) ранее повсеместно рекомендовался формат GIF, поскольку сжатие с использованием ДКП весьма неэффективно к изображениям с резкими переходами цветов. В JPEG при сжатии 1-битная картинка приводилась к 8-битной, т.е. увеличивалась в 8 раз, после чего делалась попытка сжимать, нередко менее чем в 8 раз. Сейчас можно рекомендовать JPEG 2000 как универсальный алгоритм.

7)На уровне формата поддерживается прозрачность. Плавно накладывать фон при создании WWW страниц теперь можно будет не только в GIF, но и в JPEG 2000. Кроме того, поддерживается не только 1 бит прозрачности (пиксель прозрачен/непрозрачен), а отдельный канал , что позволит задавать плавный переход от непрозрачного изображения к прозрачному фону.

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

Этапы кодирования

Процесс сжатия по схеме JPEG2000 включает ряд этапов:

1. Преобразование изображения в оптимальное цветовое пространство.
На данном этапе кодирования с помощью соответствующих соотношений цветовая модель RGB преобразуется в YUV:

При декомпрессии применяется соответствующее обратное преобразование:

2. Дискретное вейвлет преобразование.

Дискретное wavelet преобразование (DWT) также может быть двух видов - для случая сжатия с потерями и для сжатия без потерь.

Это преобразование в одномерном случае представляет собой скалярное произведение соответствующих коэффициентов на строку значений. Но т.к. многие коэффициенты нулевые, то прямое и обратное вейвлет преобразование можно записать следующими формулами (для преобразования крайних элементов строки используется ее расширение на 2 пикселя в каждую сторону, значения которых симметричны с значениями элементов строки относительно ее крайних пикселей):
y(2*n + 1) = x(2*n + 1) - (int)(x(2*n) + x(2*n + 2)) / 2

y(2*n) = x(2*n) + (int)(y(2*n - 1) + y(2*n + 1) + 2) / 4

и обратное

x(2*n) = y(2*n) - (int)(y(2*n - 1) + y(2*n + 1) + 2) / 4

x(2*n + 1) = y(2*n + 1) + (int)(x(2*n) + x(2*n + 2)) / 2.

3. Квантование коэффициентов.

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


4. Этап Вторичного Сжатия

. Как и в JPEG, в новом формате последним этапом алгоритма сжатия является кодирование без потерь. Но, в отличие от предыдущего формата, в JPEG2000 используется алгоритм арифметического сжатия.

Программная реализация

В данной работе реализованы алгоритмы JPEG и JPEG2000. В обоих алгоритмах реализовано прямое и обратное кодирование (отсутствует последний этап вторичного сжатия). Расчет JPEG происходит довольно долго (порядка 30 секунд) в связи «прямым» высчитыванием DCT. Если потребуется увеличить скорость работы , следует изначально вычислить матрицу DCT(изменения производить в классе DCT).

Перейдем к рассмотрению программы:


  1. После запуска выводится окно, где

и сможете его сохранить , нажав кнопку (2) и введя желаемое название в диалоговом окне.

  • При достаточно большом Quality Factor изображение сильно измениться. Если это JPEG алгоритм то будут ярко выражены блоки размера 8x8.(в случае алгоритма JPEG2000, блочного деления не будет)
  • До:

    После:



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

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

    Цветовая модель

    Первый шаг JPEG - выбор подходящего способа представления цветов. Цвета обычно задаются в трехмерной системе координат. Хорошо известная большинству программистов система описывает цвет, как комбинацию красного, зеленого и синего (RGB). К несчастью, с точки зрения возможности сжатия, это не лучший способ описания цвета. Проблема заключается в том, что все три компонента: красный, зеленый и синий - равнозначны. Однако переход к другой системе цветопередачи позволяет выделить некоторую более важную информацию.

    Профессионалы используют две цветовые модели: HSL (Hue-Saturation-Lightness) и HSV (Hue-Saturation-Value). Интуитивно понятно, что яркостная компонента (Lightness) модели HSL и яркостная компонента (Value) модели HSV каждая по-своему определяют соотношение света и тени. Насыщенность (saturation) определяет уровень "чистого" цвета. Ненасыщенные цвета часто неформально называют "грязными" (greyish). Оттенок (Hue) - это то, что мы воспринимаем, как цвет предмета, например красный или серовато-зеленый. Здесь важно отметить удивительный факт: человеческое зрение более чувствительно к изменению освещенности, а не цвета как такового!

    Различные реализации алгоритма сжатия JPEG используют различные цветовые системы. Используемая форматом JFIF система цветопередачи YCbCr во многом схожа со схемой, разработанной много лет назад для цветного телевидения.

    Прореживание

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

    С помощью прореживания (subsampling) регулируются цвета изображения цветного телевизора. Обычно в телевидение черно-белое изображение и информация о цвете передаются по отдельности. Причем информация о цвете передается в менее строгом виде, чем информация о яркости изображения.

    Дискретное косинусное преобразование (DCT)

    Каждая компонента цвета обрабатывается отдельно, как если бы они были не одним цветным, а тремя полутоновыми изображениями. Если вы посмотрите на детальное изображение с большого расстояния, то вы различите лишь общий тон картины. Например, "главным образом синий" или "преимущественно красный". Чем ближе вы будете подходить к изображению, тем больше деталей сможете различить. Для эмуляции этого эффекта JPEG использует один математический прием, называемый дискретным косинусным преобразованием (DCT). DCT преобразует информацию о пикселах в информацию об изменении пикселов. Первое, что может дать DCT - усредненный цвет области. Затем он все больше и больше уточняет детали.

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

    Считается, что потери обусловливаются именно этим этапом. Если вы с помощью DCT закодируете изображение и затем с помощью функции обратного DCT восстановите его, то вы не получите абсолютно такой же набор бит. Однако эта ошибка - ошибка округления. Она возникает при выполнении арифметических действий и обычно не очень велика. Поэтому я предпочитаю думать об этапе DCT, как о действии, происходящем "в основном без потерь".

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

    Квантование

    Разработчиков JPEG прежде всего интересовали изображения фотографического качества (photographic, contnuous tone). Как правило, эти полутоновые изображения характеризуются мягкими переходами от одного цвета к другому. Для таких изображений низкочастотная (медленно изменяющаяся) компонента DCT важнее высокочастотной (быстро меняющаяся).

    Термин квантование (quantization) означает просто "округление". JPEG отбрасывает некоторую графическую информацию за счет округления каждого члена DCT с различными весовыми коэффициентами, опираясь при этом на различные факторы. Высокочастотная компонента округляется сильнее низкочастотной. Например, низкочастотная компонента, которая хранит среднюю величину яркости, может быть округлена до значения, кратного трем, в то время как высокочастотная компонента может быть округлена до значения, кратного ста!

    Операция квантования объясняет, почему сжатие JPEG в случае четких контуров приводит к образованию "дрожащих" линий. Контуры определяются высокочастотной (быстро меняющейся) пространственной компонентой. (На первый взгляд может показаться, что вы должны получить размытый контур, однако вспомните, что C в сокращении DCT обозначает косинус.)

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

    Сжатие

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

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

    Изменение цветовой модели позволило проредить информацию каналов и затем более энергично их квантовать.

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

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

    Стандарт JPEG предоставляет два различных метода сжатия без потерь, которые могут быть использованы на последнем этапе. Сжатие Хаффмана (Huffman compression - это давно известный незапатентованный, легко программируемый алгоритм. В отличие от него более новый алгоритм арифметического кодирования (arithmetic coding) является объектом многочисленных патентов. (Поэтому не удивительно, что многие программы сжатия JPEG поддерживают только сжатие Хаффмана.)

    При декодировании изображений JPEG необходимо совершить все эти шаги в обратном порядке. Поток данных вначале распаковывается, затем каждый блок 8ґ8 подвергается обратному DCT и наконец изображение конвертируется в соответствующую цветовую модель (обычно это RGB). Отметим, что информация, которая была обдуманно отброшена с помощью прореживания и квантования, никогда не восстанавливается. Однако если все было сделано корректно, потеря информации не вызовет никакого видимого ухудшения изображения.

    • Tutorial


    Вы правильно поняли из названия, что это не совсем обычное описание алгоритма JPEG (формат файла я подробно описывал в статье «Декодирование JPEG для чайников»). В первую очередь, выбранный способ подачи материала предполагает, что мы ничего не знаем не только о JPEG, но и о преобразовании Фурье, и кодировании Хаффмана. И вообще, мало что помним из лекций. Просто взяли картинку и стали думать как же ее можно сжать. Поэтому я попытался доступно выразить только суть, но при которой у читателя будет выработано достаточно глубокое и, главное, интуитивное понимание алгоритма. Формулы и математические выкладки - по самому минимуму, только те, которые важны для понимания происходящего.

    Знание алгоритма JPEG очень полезно не только для сжатия изображений. В нем используется теория из цифровой обработки сигналов, математического анализа, линейной алгебры, теории информации, в частности, преобразование Фурье, кодирование без потерь и др. Поэтому полученные знания могут пригодиться где угодно.

    Если есть желание, то предлагаю пройти те же этапы самостоятельно параллельно со статьей. Проверить, насколько приведенные рассуждения подходят для разных изображений, попытаться внести свои модификации в алгоритм. Это очень интересно. В качестве инструмента могу порекомендовать замечательную связку Python + NumPy + Matplotlib + PIL(Pillow). Почти вся моя работа (в т. ч. графики и анимация), была произведена с помощью них.

    Внимание, трафик! Много иллюстраций, графиков и анимаций (~ 10Мб). По иронии судьбы, в статье про JPEG всего 2 изображения с этим форматом из полусотни.

    Каков бы ни был алгоритм сжатия информации, его принцип всегда будет один - нахождение и описание закономерностей. Чем больше закономерностей, тем больше избыточности, тем меньше информации. Архиваторы и кодеры обычно «заточены» под конкретный тип информации, и знают где можно их найти. В некоторых случаях закономерность видна сразу, например картина голубого неба. Каждый ряд его цифрового представления можно довольно точно описать прямой.

    Будем тренироваться на кошках енотах. В качестве примера взято серое изображение, приведенное выше. Оно хорошо совмещает как однородные области, так и контрастные. А если мы научимся сжимать серое, то и с цветным не будет проблем.

    Векторное представление

    Для начала проверим насколько зависимы два соседних пикселя. Логично предположить, что скорее всего, они будут очень похожи. Проверим это для всех пар изображения. Отметим их на координатной плоскости точками так, что значение точки по оси X - значение первого пикселя, по оси Y - второго. Для нашего изображения размером 256 на 256 получим 256*256/2 точек:


    Предсказуемо, что большинство точек находится на или рядом с прямой y=x (а их там еще больше, чем видно на рисунке, так как они многократно накладываются друг на друга, и, к тому же, они полупрозрачные). А раз так, то было бы проще работать, повернув их на 45°. Для этого нужно выразить их в другой системе координат.


    Базисные вектора новой системы, очевидно, такие: . Вынуждены делить на корень из двойки, чтобы получить ортонормированную систему (длины базисных векторов равны единичке). Здесь показано, что некоторая точка p = (x, y) в новой системе будет представлена как точка (a 0 , a 1). Зная новые коэффициенты, мы легко можем получить старые обратным поворотом. Очевидно, первая (новая) координата является средним, а вторая - разностью x и y (но деленные на корень из 2). Представьте, что вам предложено оставить только одно из значений: либо a 0 , либо a 1 (то есть другое приравнять нулю). Лучше выбрать a 0 , так как значение a 1 и так, скорее всего, будет около нуля. Вот, что получится, если мы восстановим изображение только по a 0:


    Увеличение в 4 раза:


    Такое сжатие не очень впечатляет, честно говоря. Лучше аналогично разобьем картинку по тройкам пикселей и представим их в трехмерном пространстве.

    Это один и тот же график, но с разных точек зрения. Красные линии - оси, которые напрашивались сами собой. Им соответствуют вектора: . Напоминаю, что приходится делить на некоторые константы, чтобы длины векторов стали равны единице. Таким образом, разложив по такому базису, мы получим 3 значения a 0 , a 1 , a 2 , причем a 0 важнее a 1 , а a 1 важнее a 2 . Если мы выбросим a 2 , то график «сплющится» в направлении вектора e 2 . Этот и так довольно не толстый трехмерный лист станет плоским. Он потеряет не так много, зато мы избавимся от трети значений. Сравним изображения, восстановленные по тройкам: (a 0 , 0, 0), (a 1 , a 2 , 0) и (a 0 , a 1 , a 2). В последнем варианте мы ничего не выбросили, поэтому получим оригинал.


    Увеличение в 4 раза:


    Второй рисунок уже хорош. Резкие участки немного сгладились, но в целом картинка сохранилась очень неплохо. А теперь, точно так же поделим на четверки и визуально определим базис в четырехмерном пространстве… А, ну да. Но можно догадаться, каким будет один из векторов базиса, это: (1,1,1,1)/2. Поэтому можно посмотреть проекцию четырехмерного пространства на пространство, перпендикулярное вектору (1,1,1,1), чтобы выявить другие. Но это не лучший путь.
    Наша цель - научиться преобразовывать (x 0 , x 1 , ..., x n-1) к (a 0 , a 1 , ..., a n-1) так, что каждое значение a i все менее важно, чем предыдущие. Если мы сможем так делать, то, возможно, последние значения последовательности вообще можно будет выбросить. Вышеприведенные опыты намекают, что можно. Но без математического аппарата не обойтись.
    Итак, нужно преобразовать точки к новому базису. Но сначала необходимо найти подходящий базис. Вернемся к первому эксперименту разбиения на пары. Будем считать обобщенно. Мы определили базисные векторы:

    Выразили через них вектор p :

    или в координатах:

    Чтобы найти a 0 и a 1 нужно спроецировать p на e 0 и e 1 соответственно. А для этого нужно найти скалярное произведение

    аналогично:

    В координатах:

    Часто бывает удобнее проводить преобразование в матричной форме.

    Тогда A = EX и X = E T A. Это красивая и удобная форма. Матрица E называется матрицей преобразования и является ортогональной, с ней мы еще встретимся.

    Переход от векторов к функциям.

    С векторами малых размерностей работать удобно. Однако мы хотим находить закономерности в бОльших блоках, поэтому вместо N-мерных векторов удобнее оперировать последовательностями, которыми представлено изображение. Такие последовательности я буду называть дискретными функциями, так как следующие рассуждения применимы и к непрерывным функциям.
    Возвращаясь к нашему примеру, представим такую функцию f(i), которая определена всего в двух точках: f(0)=x и f(1)=y. Аналогично зададим базисные функции e 0 (i) и e 1 (i) на основе базисов e 0 и e 1 . Получим:

    Это очень важный вывод. Теперь во фразе «разложение вектора по ортонормированным векторам» мы можем заменить слово «вектор» на «функция» и получить вполне корректное выражение «разложение функции по ортонормированным функциям». Не беда, что мы получили такую куцую функцию, так как такие же рассуждения работают и для N-мерного вектора, который можно представить как дискретную функцию с N значениями. А работа с функциями нагляднее, чем с N-мерными векторами. Можно и наоборот, представить такую функцию как вектор. Более того, обычную непрерывную функцию можно представить бесконечномерным вектором, правда уже не в евклидовом, а гильбертовом пространстве. Но мы туда не пойдем, нас будут интересовать только дискретные функции.
    А наша задача нахождения базиса превращается в задачу нахождения подходящей системы ортонормированных функций. В следующих рассуждениях предполагается, что мы уже как-то определили набор базисных функций, по которым и будем раскладывать.
    Допустим, у нас есть некоторая функция (представленная, например, значениями), которую мы хотим представить в виде суммы других. Можно представлять этот процесс в векторном виде. Для разложения функции нужно «спроецировать» ее на базисные функции по очереди. В векторном смысле вычисление проекции дает минимальное сближение исходного вектора к другому по расстоянию. Помня о том, что расстояние вычисляется с помощью теоремы Пифагора, то аналогичное представление в виде функций дает наилучшее среднеквадратичное приближение функции к другой. Таким образом, каждый коэффициент (k) определяет «близость» функции. Более формально, k*e(x) - лучшее среднеквадратичное приближение к f(x) среди l*e(x).
    В следующем примере показан процесс приближения функции только по двум точкам. Справа - векторное представление.


    Применительно к нашему эксперименту разбивания на пары, можно сказать, что эти две точки (0 и 1 по абсцисс) - пара соседних пикселей (x, y).
    То же самое, но с анимацией:


    Если мы возьмем 3 точки, то нужно рассматривать трехмерные вектора, однако приближение будет точнее. А для дискретной функции с N значениями нужно рассматривать N-мерные вектора.
    Имея набор полученных коэффициентов, можно легко получить исходную функцию, просуммировав базисные функции, взятые с соответствующими коэффициентами. Анализ этих коэффициентов может дать много полезной информации (в зависимости от базиса). Частным случаем этих соображений является принцип разложения в ряд Фурье. Ведь наши рассуждения применимы к любому базису, а при разложении в ряд Фурье берется вполне конкретный.

    Дискретные преобразования Фурье (ДПФ)

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

    • 1-й график - чистый тон частотой 2500 герц.
    • 2-й - белый шум. Т. е. шум c равномерно распределенными частотами по всему диапазону.
    • 3-й - сумма первых двух.
    Если бы мне дали значения последней функции на тот момент, когда я не знал про ряды Фурье, и попросили проанализировать их, то я бы точно растерялся и не смог бы сказать ничего путного. Ну, да, какая-то функция, но как понять, что там есть что-то упорядоченное? Но если бы я догадался прослушать последнюю функцию, то ухо уловило бы чистый тон среди шума. Хотя и не очень хорошо, так как я специально при генерации подобрал такие параметры, чтобы на суммарном графике сигнал визуально растворился в шуме. Как я понял, до сих пор точно не уставлено, как слуховой аппарат делает это. Однако, недавно стало ясно, что он не раскладывает звук на синусоиды. Возможно, когда-нибудь мы поймем как это происходит, и появятся более совершенные алгоритмы. Ну, а мы пока по старинке.
    Почему бы не попробовать взять синусоиды в качестве базиса? На самом деле мы фактически уже сделали это. Вспомним наше разложение на 3 базисных вектора и представим их на графике:


    Да-да, знаю, это выглядит как подгонка, но с тремя векторами трудно ожидать большего. Зато теперь понятно, как получить, например, 8 базисных векторов:


    Не очень сложная проверка показывает, что эти вектора попарно перпендикулярны, т. е. ортогональны. Это значит, их можно использовать как базис. Преобразование по такому базису широко известно, и называется дискретным косинусным преобразованием (DCT). Думаю, из приведенных графиков понятно как получается формула DCT-преобразования:

    Это все та же формула: A = EX с подставленным базисом. Базисные вектора указанного DCT (они же векторы-строки матрицы E) ортогональны, но не ортонормированы. Это следует помнить при обратном преобразовании (не буду останавливаться на этом, но, кому интересно - у inverse DCT появляется слагаемое 0.5*a 0 , так как нулевой вектор базиса больше остальных).
    На следующем примере показан процесс приближения промежуточных сумм к исходным значениям. На каждой итерации очередной базис умножается на очередной коэффициент и прибавляется к промежуточной сумме (то есть так же, как и в ранних опытах над енотом - треть значений, две трети).


    Но, все-таки, несмотря на некоторые доводы о целесообразности выбора такого базиса, реальных аргументов пока нет. Действительно, в отличие от звука, целесообразность разложения изображения на периодические функции гораздо менее очевидна. Впрочем, изображение действительно может быть слишком непредсказуемым даже на небольшом участке. Поэтому, картинку делят на достаточно маленькие кусочки, но не совсем крохотные, чтобы разложение имело смысл. В JPEG изображение «нарезается» на квадраты 8x8. В пределах такого кусочка фотографии обычно очень однородны: фон плюс небольшие колебания. Такие области шикарно приближаются синусоидами.
    Ну, допустим, этот факт более или менее понятен интуитивно. Но появляется нехорошее предчувствие насчет резких цветовых переходов, ведь медленно изменяющиеся функции нас не спасут. Приходится добавлять разные высокочастотные функции, которые справляются со своей работой, но побочно проявляются на однородном фоне. Возьмем изображение 256x256 с двумя контрастными областями:


    Разложим каждую строку с помощью DCT, получив, таким образом, по 256 коэффициентов на строку.
    Затем оставим только первые n коэффициентов, а остальные приравняем нулю, и, поэтому, изображение будет представлено в виде суммы только первых гармоник:


    Число на картинке - количество оставленных коэффициентов. На первом изображении осталось только среднее значение. На второй уже добавилась одна низкочастотная синусоида, и т. д. Кстати, обратите внимание на границу - несмотря на все лучшее приближение, рядом с диагональю хорошо заметны 2 полоски, одна светлее, другая темнее. Часть последнего изображения увеличенного в 4 раза:

    И вообще, если вдали от границы мы видим первоначальный равномерный фон, то при приближении к ней, амплитуда начинает расти, наконец достигает минимального значения, а затем резко становится максимальным. Это явление известно как эффект Гиббса.


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


    При изучении DCT может сложиться ложное впечатление, что всегда вполне достаточно всего нескольких первых (низкочастотных) коэффициентов. Это верно для многих кусочков фотографий, тех, чьи значения не меняются резко. Однако, на границе контрастных участков значения будут резво «скакать» и даже последние коэффициенты будут велики. Поэтому, когда слышите о свойстве сохранения энергии DCT, делайте поправку на то, что оно относится ко многим видам встречаемых сигналов, �