JPEG-сжатие цифрового изображения. Сжатие методом JPEG

  • Tutorial

UPD. Был вынужден убрать моноширинное форматирование. В один прекрасный день хабрапарсер перестал воспринимать форматирование внутри тегов pre и code. Весь текст превратился в кашу. Администрация хабра не смогла мне помочь. Теперь неровно, но хотя бы читабельно.

Вам когда-нибудь хотелось узнать как устроен jpg-файл? Сейчас разберемся! Прогревайте ваш любимый компилятор и hex-редактор, будем декодировать это:

Специально взял рисунок поменьше. Это знакомый, но сильно пережатый favicon Гугла:

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

Даже не зная, как происходит кодирование, мы уже можем кое-что извлечь из файла.
- маркер начала. Он всегда находится в начале всех jpg-файлов.
Следом идут байты . Это маркер, означающий начало секции с комментарием. Следующие 2 байта - длина секции (включая эти 2 байта). Значит в следующих двух - сам комментарий. Это коды символов ":" и ")", т.е. обычного смайлика. Вы можете увидеть его в первой строке правой части hex-редактора.

Немного теории

Очень кратко по шагам:
Давайте подумаем, в каком порядке могут быть закодированы эти данные. Допустим, сначала полностью, для всего изображения, закодирован канал Y, затем Cb, потом Cr. Все помнят загрузку картинок на диал-апе. Если бы они кодировались именно так, нам бы пришлось ждать загрузки всего изображения, прежде чем оно появится на экране. Так же будет неприятно, если потерятся конец файла. Вероятно, существуют и другие весомые причины. Поэтому закодированные данные располагаются поочередно, небольшими частями.

Напоминаю, что каждый блок Y ij , Cb ij , Cr ij - это матрица коэффициентов ДКП, закодированная кодами Хаффмана. В файле они располагаются в таком порядке: Y 00 Y 10 Y 01 Y 11 Cb 00 Cr 00 Y 20

Чтение файла

После того, как мы извлекли комментарий, будет легко понять, что:
  • Файл поделен на секторы, предваряемые маркерами.
  • Маркеры имеют длину 2 байта, причем первый байт .
  • Почти все секторы хранят свою длину в следующих 2 байта после маркера.
Для удобства подсветим маркеры:
FF D8 FF FE 00 04 3A 29 FF DB 00 43 00 A0 6E 78



FF FF FF FF FF FF FF FF FF FF FF FF FF FF DB 00
43 01 AA B4 B4 F0 D2 F0 FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF C0 00 11 08 00 10 00 10 03 01 22 00 02
11 01 03 11 01 FF C4 00 15 00 01 01 00 00 00 00
00 00 00 00 00 00 00 00 00 00 03 02 FF C4 00 1A
10 01 00 02 03 01 00 00 00 00 00 00 00 00 00 00
00 01 00 12 02 11 31 21 FF C4 00 15 01 01 01 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 FF
C4 00 16 11 01 01 01 00 00 00 00 00 00 00 00 00
00 00 00 00 11 00 01 FF DA 00 0C 03 01 00 02 11
03 11 00 3F 00 AE E7 61 F2 1B D5 22 85 5D 04 3C
82 C8 48 B1 DC BF FF D9

Маркер : DQT - таблица квантования.

FF DB 00 43 00 A0 6E 78
8C 78 64 A0 8C 82 8C B4 AA A0 BE F0 FF FF F0 DC
DC F0 FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF

Заголовок секции всегда занимает 3 байта. В нашем случае это . Заголовок состоит из:
Длина: 0x43 = 67 байт
Длина значений в таблице: 0 (0 - 1 байт, 1 - 2 байта)
[_0] Идентификатор таблицы: 0
Оставшимися 64-мя байтами нужно заполнить таблицу 8x8.



Приглядитесь, в каком порядке заполнены значения таблицы. Этот порядок называется zigzag order:

Маркер : SOF0 - Baseline DCT

Этот маркер называется SOF0, и означает, что изображение закодировано базовым методом. Он очень распространен. Но в интернете не менее популярен знакомый вам progressive-метод, когда сначала загружается изображение с низким разрешением, а потом и нормальная картинка. Это позволяет понять что там изображено, не дожидаясь полной загрузки. Спецификация определяет еще несколько, как мне кажется, не очень распространенных методов.

FF C0 00 11 08 00 10 00 10 03 01 22 00 02
11 01 03 11 01

Длина: 17 байт.
Precision: 8 бит. В базовом методе всегда 8. Как я понял, это разрядность значений каналов.
Высота рисунка: 0x10 = 16
Ширина рисунка: 0x10 = 16
Количество компонентов: 3. Чаще всего это Y, Cb, Cr.

1-й компонент:
Идентификатор: 1
Горизонтальное прореживание (H 1): 2
[_2] Вертикальное прореживание (V 1): 2
Идентификатор таблицы квантования: 0

2-й компонент:
Идентификатор: 2
Горизонтальное прореживание (H 2): 1
[_1] Вертикальное прореживание (V 2): 1

3-й компонент:
Идентификатор: 3
Горизонтальное прореживание (H 3): 1
[_1] Вертикальное прореживание (V 3): 1
Идентификатор таблицы квантования: 1

Теперь посмотрите, как определить насколько прорежено изображение. Находим H max =2 и V max =2 . Канал i будет прорежен в H max /H i раз по горизонтали и V max /V i раз по вертикали.

Маркер : DHT (таблица Хаффмана)

Эта секция хранит коды и значения полученные кодированием Хаффмана .

FF C4 00 15 00 01 01 00 00 00 00
00 00 00 00 00 00 00 00 00 00 03 02

длина: 21 байт.
класс: 0 (0 - таблица DC коэффициэнтов, 1 - таблица AC коэффициэнтов).
[_0] идентификатор таблицы: 0
Длина кода Хаффмана: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Количество кодов:
Количество кодов означает количество кодов такой длины. Обратите внимание, что секция хранит только длины кодов, а не сами коды. Мы должны найти коды сами. Итак, у нас есть один код длины 1 и один - длины 2. Итого 2 кода, больше кодов в этой таблице нет.
С каждым кодом сопоставлено значение, в файле они перечислены следом. Значения однобайтовые, поэтому читаем 2 байта.
- значение 1-го кода.
- значение 2-го кода.

Построение дерева кодов Хаффмана

Мы должны построить бинарное дерево по таблице, которую мы получили в секции DHT. А уже по этому дереву мы узнаем каждый код. Значения добавляем в том порядке, в каком указаны в таблице. Алгоритм прост: в каком бы узле мы ни находились, всегда пытаемся добавить значение в левую ветвь. А если она занята, то в правую. А если и там нет места, то возвращаемся на уровень выше, и пробуем оттуда. Остановиться нужно на уровне равном длине кода. Левым ветвям соответствует значение 0 , правым - 1 .
Замечание:
Не нужно каждый раз начинать с вершины. Добавили значение - вернитесь на уровень выше. Правая ветвь существует? Если да, идите опять вверх. Если нет - создайте правую ветвь и перейдите туда. Затем, с этого места, начинайте поиск для добавления следующего значения.

Деревья для всех таблиц этого примера:


UPD (спасибо ): В узлах первого дерева (DC, id =0) должны быть значения 0x03 и 0x02

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

Маркер : SOS (Start of Scan)

Байт в маркере означает - «ДА! Наконец-то то мы перешли непосредственно к разбору секции закодированного изображения!». Однако секция символично называется SOS.

  FF DA 00 0C 03 01 00 02 11
03 11 00 3F 00

Длина заголовочной части (а не всей секции): 12 байт.
Количество компонентов сканирования. У нас 3, по одному на Y, Cb, Cr.

1-й компонент:
Номер компонента изображения: 1 (Y)
Идентификатор таблицы Хаффмана для DC коэффициэнтов: 0
[_0] Идентификатор таблицы Хаффмана для AC коэффициэнтов: 0

2-й компонент:
Номер компонента изображения: 2 (Cb)

[_1]

3-й компонент:
Номер компонента изображения: 3 (Cr)
Идентификатор таблицы Хаффмана для DC коэффициэнтов: 1
[_1] Идентификатор таблицы Хаффмана для AC коэффициэнтов: 1

Данные компоненты циклически чередуются.

На этом заголовочная часть заканчивается, отсюда и до конца (маркера ) закодированные данные.


0

Нахождение DC-коэффициента.
1. Читаем последовательность битов (если встретим 2 байта , то это не маркер, а просто байт ) . После каждого бита сдвигаемся по дереву Хаффмана (с соответствующим идентификатором) по ветви 0 или 1, в зависимости от прочитанного бита. Останавливаемся, если оказались в конечном узле.
10 1011101110011101100001111100100

2. Берем значение узла. Если оно равно 0, то коэффициент равен 0, записываем в таблицу и переходим к чтению других коэффициентов. В нашем случае - 02. Это значение - длина коэффициента в битах. Т. е. читаем следующие 2 бита, это и будет коэффициент.
10 10 11101110011101100001111100100

3. Если первая цифра значения в двоичном представлении - 1, то оставляем как есть: DC_coef = значение. Иначе преобразуем: DC_coef = значение-2 длина значения +1 . Записываем коэффициент в таблицу в начало зигзага - левый верхний угол.

Нахождение AC-коэффициентов.
1. Аналогичен п. 1, нахождения DC коэффициента. Продолжаем читать последовательность:
10 10 1110 1110011101100001111100100

2. Берем значение узла. Если оно равно 0, это означает, что оставшиеся значения матрицы нужно заполнить нулями. Дальше закодирована уже следующая матрица. Первые несколько дочитавших до этого места и написавших об этом мне в личку, получат плюс в карму. В нашем случае значение узла: 0x31.
Первый полубайт: 0x3 - именно столько нулей мы должны добавить в матрицу. Это 3 нулевых коэффициэнта.
Второй полубайт: 0x1 - длина коэффициэнта в битах. Читаем следующий бит.
10 10 1110 1 110011101100001111100100

3. Аналогичен п. 3 нахождения DC-коэффициента.

Как вы уже поняли, читать AC-коэффициенты нужно пока не наткнемся на нулевое значение кода, либо пока не заполнится матрица.
В нашем случае мы получим:
10 10 1110 1 1100 11 101 10 0 0 0 1 11110 0 100
и матрицу:





Вы заметили, что значения заполнены в том же зигзагообразном порядке?
Причина использования такого порядка простая - так как чем больше значения v и u, тем меньшей значимостью обладает коэффициент S vu в дискретно-косинусном преобразовании. Поэтому, при высоких степенях сжатия малозначащие коэффициенты обнуляют, тем самым уменьшая размер файла.

[-4 1 1 1 0 0 0 0] [ 5 -1 1 0 0 0 0 0]
[ 0 0 1 0 0 0 0 0] [-1 -2 -1 0 0 0 0 0]
[ 0 -1 0 0 0 0 0 0] [ 0 -1 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [-1 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]

[-4 2 2 1 0 0 0 0]
[-1 0 -1 0 0 0 0 0]
[-1 -1 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]

Ой, я забыл сказать, что закодированные DC-коэффициенты - это не сами DC-коэффициенты, а их разности между коэффициентами предыдущей таблицы (того же канала)! Нужно поправить матрицы:
DC для 2-ой: 2 + (-4) = -2
DC для 3-ой: -2 + 5 = 3
DC для 4-ой: 3 + (-4) = -1

[-2 1 1 1 0 0 0 0] [ 3 -1 1 0 0 0 0 0] [-1 2 2 1 0 0 0 0]
………

Теперь порядок. Это правило действует до конца файла.

… и по матрице для Cb и Cr:

[-1 0 0 0 0 0 0 0]
[ 1 1 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]

Так как тут только по одной матрице, DC-коэфициенты можно не трогать.

Вычисления

Квантование

Вы помните, что матрица проходит этап квантования? Элементы матрицы нужно почленно перемножить с элементами матрицы квантования. Осталось выбрать нужную. Сначала мы просканировали первый компонент, его компонента изображения = 1. Компонент изображения с таким идентификатором использует матрицу квантования 0 (у нас она первая из двух). Итак, после перемножения:


[ 0 120 280 0 0 0 0 0]
[ 0 -130 -160 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]

Аналогично получаем еще 3 матрицы Y-канала…

[-320 110 100 160 0 0 0 0] [ 480 -110 100 0 0 0 0 0]
[ 0 0 140 0 0 0 0 0] [-120 -240 -140 0 0 0 0 0]
[ 0 -130 0 0 0 0 0 0] [ 0 -130 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [-140 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]

[-160 220 200 160 0 0 0 0]
[-120 0 -140 0 0 0 0 0]
[-140 -130 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]

… и по матрице для Cb и Cr.

[-170 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]
[ 180 210 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]

Обратное дискретно-косинусное преобразование

Формула не должна доставить сложностей*. S vu - наша полученная матрица коэффициентов. u - столбец, v - строка. s yx - непосредственно значения каналов.

*Вообще говоря, это не совсем правда. Когда я смог декодировать и отобразить на экране рисунок 16x16, я взял изображение размером 600x600 (кстати, это была обложка любимого альбома Mind.In.A.Box - Lost Alone). Получилось не сразу - всплыли различные баги. Вскоре я мог любоваться корректно загруженной картинкой. Только очень огорчала скорость загрузки. До сих пор помню, она занимала 7 секунд. Но это и неудивительно, если бездумно пользоваться приведенной формулой, то для вычисления одного канала одного пикселя потребуется нахождения 128 косинусов, 768 умножений, и сколько-то там сложений. Только вдумайтесь - почти тысяча непростых операций только на один канал одного пиксела! К счастью, тут есть простор для отимизации (после долгих экспериментов уменьшил время загрузки до предела точности таймера 15мс, и после этого сменил изображение на фотографию в 25 раз большей площадью. Возможно, напишу об этом отдельной статьей).

Напишу результат вычисления только первой матрицы канала Y (значения округлены):


[ 87 72 50 36 37 55 79 95]
[-10 5 31 56 71 73 68 62]
[-87 -50 6 56 79 72 48 29]

И 2-х оставшихся:
Cb Cr
[ 60 52 38 20 0 -18 -32 -40] [ 19 27 41 60 80 99 113 120]
[ 48 41 29 13 -3 -19 -31 -37] [ 0 6 18 34 51 66 78 85]
[ 25 20 12 2 -9 -19 -27 -32] [-27 -22 -14 -4 7 17 25 30]
[ -4 -6 -9 -13 -17 -20 -23 -25] [-43 -41 -38 -34 -30 -27 -24 -22]
[ -37 -35 -33 -29 -25 -21 -18 -17] [-35 -36 -39 -43 -47 -51 -53 -55]
[ -67 -63 -55 -44 -33 -22 -14 -10] [ -5 -9 -17 -28 -39 -50 -58 -62]
[ -90 -84 -71 -56 -39 -23 -11 -4] [ 32 26 14 -1 -18 -34 -46 -53]
[-102 -95 -81 -62 -42 -23 -9 -1] [ 58 50 36 18 -2 -20 -34 -42]

  1. О, пойду-ка поем!
  2. Да я вообще не въезжаю, о чем речь.
  3. Раз значение цветов YCbCr получены, осталось преобразовать в RGB, типа так: YCbCrToRGB(Y ij , Cb ij , Cr ij) , Y ij , Cb ij , Cr ij - наши полученные матрицы.
  4. 4 матрицы Y, и по одной Cb и Cr, так как мы прореживали каналы и 4 пикселям Y соответствует по одному Cb и Cr. Поэтому вычислять так: YCbCrToRGB(Y ij , Cb , Cr )
Если вы выбрали 1 и 4, то я рад за вас. Либо вы все правильно поняли, либо скоро будете получать удовольствие от еды.

YCbCr в RGB

R = Y + 1.402 * Cr
G = Y - 0.34414 * Cb - 0.71414 * Cr
B = Y + 1.772 * Cb
Не забудьте прибавить по 128. Если значения выйдут за пределы интервала , то присвоить граничные значения. Формула простая, но тоже отжирает долю процессорного времени.

Вот полученные таблицы для каналов R, G, B для левого верхнего квадрата 8x8 нашего примера:
255 248 194 148 169 215 255 255
255 238 172 115 130 178 255 255
255 208 127 59 64 112 208 255
255 223 143 74 77 120 211 255
237 192 133 83 85 118 184 222
177 161 146 132 145 162 201 217
56 73 101 126 144 147 147 141
0 17 76 126 153 146 127 108

231 185 117 72 67 113 171 217
229 175 95 39 28 76 139 189
254 192 100 31 15 63 131 185
255 207 115 46 28 71 134 185
255 241 175 125 112 145 193 230
226 210 187 173 172 189 209 225
149 166 191 216 229 232 225 220
72 110 166 216 238 231 206 186

255 255 249 203 178 224 255 255
255 255 226 170 140 187 224 255
255 255 192 123 91 138 184 238
255 255 208 139 103 146 188 239
255 255 202 152 128 161 194 232
255 244 215 200 188 205 210 227
108 125 148 172 182 184 172 167
31 69 122 172 191 183 153 134

Конец

Вообще я не специалист по 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 соответственно. Разобьем полуинтервал представлена реализация распараллеливания всех стадий алгоритма JPEG по технологии CUDA, что значительно ускорило производительность сжатия и декодирования по стандарту JPEG.

В 2010 году ученые из проекта PLANETS поместили инструкции по чтению формата JPEG в специальную капсулу, которую поместили в специальный бункер в швейцарских Альпах. Сделано это было с целью сохранения для потомков информации о популярных в начале XXI века цифровых форматах.

См. также

Примечания

Ссылки

  • Спецификация JFIF 1.02 (текстовый файл)
  • Оптимизация JPEG. Часть 1 , Часть 2 , Часть 3 .

Алгоритм разработан группой экспертов в области фотографии (Joint Photographic Expert Group) специально для сжатия 24-битных и полутоновых изображений в 1991 году. Этот алгоритм не очень хорошо сжимает двухуровневые изображении, но он прекрасно обрабатывает изображения с непрерывными тонами, в которых близкие пикселы обычно имеют схожие цвета. Обычно глаз не в состоянии заметить какой-либо разницы при сжатии этим методом в 10 или 20 раз.

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

Рассмотрим работу алгоритма подробнее. Предположим, что сжимается полноцветное 24-битное изображение. В этом случае получаем следующие этапы работы.

Шаг 1. Переводим изображение из пространства RGB в пространство YCbCr с помощью следующего выражения:

Отметим сразу, что обратное преобразование легко получается путем умножения обратной матрицы на вектор , который по существу является пространством YUV:

.

Шаг 2. Разбиваем исходное изображение на матрицы 8х8. Формируем из каждой три рабочие матрицы ДКП – по 8 бит отдельно для каждой компоненты. При больших степенях сжатия блок 8х8 раскладывается на компоненты YCbCr в формате 4:2:0, т.е. компоненты для Cb и Cr берутся через точку по строкам и столбцам.

Шаг 3. Применение ДКП к блокам изображения 8х8 пикселей. Формально прямое ДКП для блока 8х8 можно записать в виде

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

.

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

Здесь - результат ДКП, для вычисления которого требуется операций умножения и почти столько же сложений, что существенно меньше прямых вычислений по формуле выше. Например, для преобразования изображения размером 512х512 пикселей потребуется арифметических операций. Учитывая 3 яркостных компоненты, получаем значение 12 582 912 арифметических операций. Количество умножений и сложений можно еще больше сократить, если воспользоваться алгоритмом быстрого преобразования Фурье. В результате для преобразования одного блока 8х8 нужно будет сделать 54 умножений, 468 сложений и битовых сдвигов.

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

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

.

Причем для каждой матрицы Y, Cb и Cr можно задавать свои таблицы квантования. Стандарт JPEG даже допускает использование собственных таблиц квантования, которые, однако, необходимо будет передавать декодеру вместе со сжатыми данными, что увеличит общий размер файла. Понятно, что пользователю сложно самостоятельно подобрать 64 коэффициента, поэтому стандарт JPEG использует два подхода для матриц квантования. Первый заключается в том, что в стандарт JPEG включены две рекомендуемые таблицы квантования: одна для яркости, вторая для цветности. Эти таблицы представлены ниже. Второй подход заключается в синтезе (вычислении на лету) таблицы квантовании, зависящей от одного параметра , который задается пользователем. Сама таблица строится по формуле

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

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

Шаг 5. Переводим матрицу 8х8 в 64-элементный вектор при помощи «зигзаг»-сканирования (рис. 2).

Рис. 2. «Зигзаг»-сканирование

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

Шаг 6. Преобразовываем вектор с помощью модифицированного алгоритма RLE, на выходе которого получаем пары типа (пропустить, число), где «пропустить» является счетчиком пропускаемых нулей, а «число» - значение, которое необходимо поставить в следующую ячейку. Например, вектор 1118 3 0 0 0 -2 0 0 0 0 1 … будет свернут в пары (0, 1118) (0,3) (3,-2) (4,1) … .

Следует отметить, что первое число преобразованной компоненты , по существу, равно средней яркости блока 8х8 и носит название DC-коэффициента. Аналогично для всех блоков изображения. Это обстоятельство наводит на мысль, что коэффициенты DC можно эффективно сжать, если запоминать не их абсолютные значения, а относительные в виде разности между DC коэффициентом текущего блока и DC коэффициентом предыдущего блока, а первый коэффициент запомнить так, как он есть. При этом упорядочение коэффициентов DC можно сделать, например, так (рис. 3). Остальные коэффициенты, которые называются AC-коэффициентами сохраняются без изменений.

Шаг 7. Свертываем получившиеся пары с помощью неравномерных кодов Хаффмана с фиксированной таблицей. Причем для DC и AC коэффициентов используются разные коды, т.е. разные таблицы с кодами Хаффмана.

Рис. 3. Схема упорядочения DC коэффициентов

Рис. 4. Структурная схема алгоритма JPEG

Процесс восстановления изображения в этом алгоритме полностью симметричен. Метод позволяет сжимать изображения в 10-15 раз без заметных визуальных потерь.

При разработке данного стандарта руководствовались тем, что данный алгоритм должен был сжимать изображения довольно быстро – не более минуты на среднем изображении. Это в 1991 году! А его аппаратная реализация должна быть относительно простой и дешевой. При этом алгоритм должен был быть симметричным по времени работы. Выполнение последнего требования сделало возможным появление цифровых фотоаппаратов, снимающие 24 битные изображения. Если бы алгоритм был несимметричен, было бы неприятно долго ждать, пока аппарат «перезарядится» - сожмет изображение.

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

Сжатие методом JPEG является самым распространенным и эффективным способом сжатия. В 1992 году стал международным стандартом в области ГИ. JPEG – аббревиатура группы экспертов создавших стандарт (Joint Photographic Experts Group).

В основе метода лежит поиск разницы между соседними пикселями. Кодирование осуществляется в несколько этапов.

1. Конвертирование цветовой модели изображения (обычно RGB) в цветовую модель YUV, некоторый аналог модели Lab, где яркостная составляющая отделена от цветовой. В модели RGB изменение любого компонента цвета приводит к изменению яркости пикселя. В модели YUV компонента Y – компонента яркости, а компоненты U, V – хранят информацию о цвете.

Y = 0,299 x R + 0,587 x G + 0,114 x B

U = (B – Y) / 0,433 +128

V = (R – Y) / 0,355 + 128

2. Подвыборка – соседние пиксели группируются попарно в каналах U и V. Разделение яркости и цвета позволяет уделять больше внимание яркости, чем данным о цвете. Этот процесс осуществляется для компонент с различной частотой.

Используется два метода подвыборки 4:1:1 и 4:2:2. Так как каждый цвет кодируется 3 байтами, для передачи цвета 4 пикселей (12 байт в RGB) требуется 6 байт и 8 байт соответственно. Т.е. YUV411 сокращает объем данных в два раза, а YUV422 в 1,5 раз. С точки зрения математики происходит значительная потеря информации, но человеческий глаз ее не воспринимает, ввиду избыточности информации в самом изображении.

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

4. Квантование – удаление малозначительной для глаза информации. Все составляющие делятся на коэффициенты относительной важности и округляются до целого.

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

5. Этап Zig-Zag . Этап назван так вследствие того, что пиксели собираются в последовательность по размерам (сначала располагаются пиксели, отвечающие за более крупные объекты). Кодировщик движется как бы зигзагом.

6. Сжатие методом RLE.

7. Сжатие методом JPEG.

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

Вопросы для самопроверки.

1. Для чего необходимы алгоритмы сжатия данных?

2. Что такое сжатие "без потерь" и сжатие "с потерями"?

3. Опишите основные алгоритмы сжатия графической информации "без потерь".

4. Опишите алгоритм сжатия методом JPEG.