20 KiB
tags, source
| tags | source | |||
|---|---|---|---|---|
|
|
Уточняю, что конспект - естественный выхлоп моей работы пока я ищу информацию по билету. О полноте речи не идет и приложен он тут только потому что я иногда на эти конспекты ссылаюсь и потому что мне не жалко
2.5. Графические данные, их представление и кодирование
Классы изображений
Кодирование цвета
Способы и точность кодирования цвета для отображения
Форматы для хранения графических данных
Краткая характеристика наиболее распространенных растровых форматов
Алгоритмы сжатия без потерь
Неплохое видео: https://www.youtube.com/watch?v=CJFUN6BrkGE
Априори все считается последовательностью чисел. Как правило за единицу данных, которые будут сжиматься, берется 1 байт
Групповое кодирование RLE
Вместо того, чтобы писать последовательно много раз одинаковое значение, записывается сначала количество повторений байта, а затем сам байт
То есть если у нас есть последовательность 111112222222227, то мы как бы говорим: "запиши 5 раз 1, потом 9 раз 2, потом 1 раз 7". То есть запишется оно как: "[5]1[9]2[1]7" и если исходное сообщение занимало у нас 15 байт, то новое 6 байт.
Основная проблема данного алгоритма в том, что число повторений тоже надо хранить и остается хранить их только в сжатом тексте, так что если повторяющихся данных не так много, то алгоритм становится даже вреден. Например: 112431 превратится в [2]1[1]2[1]4[1]3[1]1, и того 10 байт против исходных 6. То есть размер даже увеличился
То есть алгоритм отлично себя показывает на изображениях, где много областей сплошной одноцветной заливки (как например схема самого алгоритм, приведенная выше)
Кодирование по Хаффману (Huffman, 1952)
Много поработав с естественным языком, мы довольно быстро заметим, что определенные буквы в нем встречаются чаще чем другие. Частотностью символа называется количество этого символа в тексте, к количеству символов в тексте. Проще говоря какой процент всех символов составляет например буква "а"
Эта особенность языка испокон веков использовалась для взлома простого шифра подстановки 1 , а потому частотность символов была многократно посчитана для почти всех языков, которые имеют письменность. Вот например частотность символов русского языка:
Видно, что буква "о" упоминается существенно чаще, чем буквы вроде "ф" или многострадальной "ё", (к которой у русских большая нелюбовь из-за неудобства ее печатного набора).
Эта особенность, которая несколько веков портила жизнь людям и не давала зашифровать сообщение, но ее можно обернуть и себе на пользу: при шифровании текста логично использовать для шифрования буквы "о" символ с наименьшей длинной, чуть длиннее сделать "е", еще чуть длиннее "а" и так далее вплоть до "ё", которую зашифруем уже как получится.
Ну и вот перед нами знакомая задачка из ЕГЭ - подобрать коды для символов так, чтобы их можно было однозначно декодировать, а длина сообщения была наименьшей. Здесь же предлагаю вспомнить, что такое прямое условие Фано - ни одно кодовое слово не может быть началом другого кодового слова. Это является условием, чтобы наши сжатые данные можно было однозначно декодировать
После этого мы алгоритмически реализуем подбор таких символов.
У этого метода есть одна проблема технического характера: частотности символов для языка в целом и для конкретного файла в частности могут весьма сильно различаться, а мы ведь этот алгоритм не только к текстам хотим применять (У нас-то в голове все с байтами работает). Построить таблицу частотностей для конкретного файла - пол беды, это займет O(n), что вполне допустима даже для гигабайтных данных. А вот как ее при разжимании получить? В классической реализации алгоритма Хаффмана она хранится в начале файла, но она, как вы можете догадаться, сама прибавляет размера нашим данным.
У этой проблемы есть решение - на практике мы можем не генерировать таблицу частотностей, а производить кодирование и построение дерева кодирования символов параллельно. Алгоритм Молодяковым не описан, просто знайте что он есть. Если кому интересно, он разбирался в этом видео (или если ютуб не грузит: https://vk.com/video-209186427_456239058 на 9:41). Этот алгоритм называется "адаптивный метод Хаффмана"
Алгоритм LZW (Lempel-Ziv-Welch - 1984)
Весьма сложный для понимания на слух. Алгоритм существует в двух вариантах LZW и LZW78
Идея: до этого мы кодировали текст уменьшая длинну символов, теперь мы кодируем текст при помощи частей этого самого текста. То есть в слове abracadabra есть 2 абсолютно идентичных набора символов. Хотелось бы это как-то учитывать при кодировании
Держим в уме, что текст тут только для примера и действительности мы всегда работаем с последовательностями байт
LZW исходный
[!note]+ Скользящее окно Некий кусок непрерывной памяти из двух частей - словаря, и буффера. Изначально кодируемый текст, насколько помещается, заносится в буффер (представьте буффер справа), а в процессе кодирования вся эта область памяти сдвигается влево (обычным сдвигом << 8)
Идея: кодируемый текст мы помещаем в буффер и в процессе обработки смещаем его в словарь, а внутри словаря потом в процессе кодирования ищем совпадения. Если совпадение нашлось, мы просто указываем в словаре где это совпадение можно отыскать
Реализация (в общих чертах): загоняем весь текст в буффер, после чего начинаем искать сопадение для первого символа в словаре (в самом начале кодирования совпадений не будет). Пока совпадений нет, сдвигаем всю цепочку на 1 влево, если есть еще символы, которых нет в буффере, попутно доставляем их туда (сдвинулись на 1 влево, в буффере освободилось место под один символ, дописываем в это место следующий в файле символ). Словарь и буффер - одна область памяти, называемая скользящим окном (см примечание выше), поэтому при сдвиге влево на один символ, самый левый символ из буффера появится в словаре, вытеснив самый старый символ уже словаря. Вытесненный символ нигде не хранится и просто исчезает из оперативной памяти. Как только есть совпадение на 1 символ, пытаемся найти совпадения на 2 символа, потом на 3 и так далее. То есть пытаемся найти как можно более длинное совпадение с содержимым словаря. Отмечу, что совпадения мы ищем даже тогда, когда словарь пуст. Если такое есть - даем на него ссылку в закодированном сообщении словаре (в классической реализации - смещение от конца влево и длину совпавшего куска)
Проблема: Если сначала мы ищем совпадение в один символ, потом в 2 символа, потом в 3 и так далее, то оценка сложности покажет сложность алгоритма по времени O(n!). Не очень хорошо. Чем больше будет размер скользящего окна, тем больше потенциальных длинных цепочек мы сможем найти и тем сильнее, соответственно, сжать данные, но тем дольше будет процесс кодирования
LZW78
Модифицированная версия, также и проще для понимания
У нас есть кодируемый текст и словарь. Поскольку примером данных будет текст, словарь у нас будет представлять обычный массив строк (только придется договориться, что нумерация в нем начинается с единицы а не с нуля).
Идея: мы кодируем текст и попутно составляем словарь. Мы ищем повторяющиеся последовательности символов длиной начиная от нуля и доскольки угодно. Если некоторой последовательности еще нет в словаре, мы ее туда заносим, если есть, мы в выходных данных даем номер совпавшей последовательности
[!note]+ Примечание Искать последовательности мы начинаем с длины 1, что означает, что при кодировании в словарь сначала добавится вхождение в 1 символ. Потом если с этим вхождением в 1 символ случится совпадение, в словарь будет добавлено совпадение уже из двух символов, а если будет совпадение с последовательностью из двух символов, будет добавлена новая последовательность из 3 символов.
Каждая новая добавленная последовательность создается из совпавшей последовательности + одна буква справа от нее. То есть если "abba" уже есть в словаре, а совпадение случилось в состоянии буфера "abbahelloworld", то следующей в словарь добавится последовательность "abbah", потому что слева стояла буква "h"
Реализация: теперь вместо символа или последовательности символов мы пишем номер, под которым соответствующая последовательность хранится в словаре (напоминаю, что нумеруем элементы массива в этом примере мы с единицы), после чего добавляем следующий за этой последовательностью символ. Если символ в словаре не найден, пишем в качестве числа 0. {'a', 'b', 'r'}), а вот потом встретится "a", которая есть в словаре, поэтому мы напишем ее порядковый номер, а затем следующий за совпавшей последовательностью символ: 1c (словарь: {'a', 'b', 'r', 'ac'}), потом в ход пойдет "ad", который закодируется соответственно в 1d и так далее после кодирования получим следующий результат: 0a0b0r1c1d1b3a (словарь: {'a', 'b', 'r', 'ac', 'ad', 'ab', 'ra'}) (заметим, что в данном случае мы ничего не сжали. Это нормально, потому что данные коротковаты, а алгоритмы сжатия дописывают довольно много информации. Порой сжатие не эффективно, тут ничего не поделать)
Проблемы: пусть здесь мы и поправили скорость сжатия (теперь на поиск совпадений уходит O(n)), а также мы убрали ограничения на размер скользящего окна и теоретически можем искать совпадения большой длины, можно увидеть, что в этом варианте алгоритма мы не заметили, что "abra" встречается 2 раза без изменений. То есть чтобы алгоритм был эффективен, желательно данные иметь побольше. Также не трудно понять, что если длинных цепочек повторяющихся данных у нас нет, то сжатие пойдет во вред размеру файла и это надо учитывать, поэтому если в файле нечему повторяться, то этот алгоритм не только не эффективен, но подчас и вреден, как мы могли убедиться
Алгоритмы сжатия с потерями
Когда мы говорим о сжатии какого-нибудь текстового документа, потери данных оче ид о н прив дут ни к ч му хор шему, как можете видеть, читать такое не то чтобы очень приятно. Но вот если дело касается изображений - другой разговор. Наш глаз, вообще говоря, штука крайне не точная и вполне может не заметить каких-то деталей. Так что для фото и видео сжатия с потерями - отличный выход (потому что весят эти картиночки они будь здоров)
JPEG
JPEG - формат хранения изображения с потерями. Само по себе его обоснование довольно сложно и требует некоторых знаний в области математики. Тем не менее основывается он на некоторых вполне понятных идеях:
- Если мы сжимаем фотографию или художественное полотно, то как правило 2 соседних пикселя будут близки по цвету
- Человеческий глаз построен таким образом, что он гораздо восприимчивее к перепадам яркости, чем к изменению цвета (кому интересно, это связано с тем, что в нашей сетчатке глаза больше палочек чем колбочек). То есть человеческий глаз намного чувствительнее к изменению яркости нежели к изменению цвета
Процесс сжатия в этом формате довольно длинный. Подробно все шаги пояснять не буду, потому что это ну прям совсем хана
-
Шифр простой подстановки - шифр, где каждому символу исходного алфавита (например русского), ставится в соответствии символ другого алфавита. Классический пример - замена одних букв на другие: а -> в, г -> а и т.д. И тогда сообщение "Бегите, глупцы" станет "Гжакфж, анхсшэ" (тут поменяны и другие буквы, можете попытаться прикинуть, какой алфавит тут использовался). Символом другого алфавита может быть и просто другой символ. Ярким примером может стать например шифр Стенфорда Пайнса из Гравити Фолз (кстати рекомендую попробовать его расшифровать, мне это доставило некоторое удовольствие) ↩︎

