Quantization: где теряется качество

Автор: Николай СапуновОбновлено: август 202626 мин чтения
Содержание статьи +

TL;DR

Квантование – единственный этап видеокодека, на котором информация теряется безвозвратно: каждый размытый край, каждый «грязный» блок неба, каждый плоский участок, где должен быть градиент, но его нет, – всё это результат именно этого шага. Кодер делит каждый коэффициент преобразования на шаг квантования, округляет до ближайшего целого, и маленькие числа превращаются в нули, которые почти ничего не стоят при передаче. У каждого кодека своя обёртка вокруг одной и той же идеи: H.264 использует параметр квантования от 0 до 51, при этом шаг удваивается каждые шесть пунктов; HEVC и VP9 применяют ту же шкалу, но на более крупных блоках; AV1 называет это qindex и предлагает 256 уровней; VVC добавляет зависимое квантование, фактически давая кодеру ещё полшага. Освойте квантование – и одной настройкой вы сможете управлять качеством, битрейтом и видимыми артефактами.

Зачем это вам

Главное число, которое настраивает оператор кодера, – это QP (quantization parameter, параметр, контролирующий степень округления коэффициентов). Когда стриминговая платформа снижает битрейт вдвое, чтобы сэкономить на CDN, почти вся экономия достигается за счёт увеличения QP – а не за счёт «умного кодека», новых алгоритмов или аппаратных улучшений. Продакт-менеджер, способный прочитать значение QP в логе FFmpeg и предсказать, будет ли результат смотримым или испорченным, отсекает половину плохих идей в encoding-роудмапе ещё до выхода в продакшн. Основатель, объясняющий инвесторам, почему «снижение битрейта на 30%» иногда выглядит чисто, а иногда – блочно, понимает единственный источник перцептивного риска во всём видеопайплайне. Технический лидер, знающий, когда использовать плоскую матрицу квантования, а когда – кастомную, выигрывает 1–2 пункта VMAF на сервисе, отдающем миллион часов в сутки – а это уже деньги.

Что происходит в quantization, в одном абзаце

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

После того как стадия предсказания сгенерировала residual-блок, а преобразование распределило его энергию по сетке коэффициентов – эти шаги мы подробно разбирали в статьях о внутрикадровом кодировании, межкадровом кодировании и оценке движения и преобразовательном кодировании, – кодер делит каждый коэффициент на шаг квантования и округляет результат до ближайшего целого числа. Это и есть квантование. Шаг – это одно число, которое задаёт оператор: маленький шаг сохраняет большую часть точности и даёт большой битовый поток, большой шаг обнуляет почти все коэффициенты и приводит к компактному битовому потоку. Когда декодер восстанавливает блок, он умножает оставшиеся целые значения на тот же шаг, но ошибка округления при этом теряется навсегда. Эта ошибка округления – единственное место во всём кодеке, где информация теряется безвозвратно; все остальные операции в принципе обратимы.

Рис. 1. Цикл квантования. Кодер округляет каждый коэффициент до ближайшего кратного шагу; декодер умножает обратно. Малые коэффициенты обнуляются и не восстанавливаются.

Шаг квантования – что означает это число

Шаг, обычно обозначаемый как Qstep, – это расстояние на числовой оси между двумя значениями, которые кодек может представить. Если Qstep равен 8, а коэффициент – 21, кодер округляет результат деления 21 ÷ 8 до 3, передаёт это целое число, а декодер умножает его обратно и получает 24 – ошибка составляет 3. Если Qstep равен 32, а коэффициент остаётся тем же – 21, кодер округляет 21 ÷ 32 до 1, декодер выдаёт 32, ошибка – 11. Чем больше шаг, тем выше средняя ошибка на коэффициент, но тем меньше целых чисел приходится передавать энтропийному кодеру.

Арифметика проста:

quantized = round(coefficient / Qstep)        # кодер
recovered = quantized × Qstep                  # декодер
error     = recovered − coefficient            # всегда |error| ≤ Qstep / 2

Подставим коэффициент 87 и значение Qstep = 16:

quantized = round(87 / 16) = round(5.4375) = 5
recovered = 5 × 16 = 80
error     = 80 − 87 = −7

Ошибка ограничена половиной шага – в данном случае половиной 16, то есть 8, – и граница точная. Каждая ячейка transform-блока получает свою независимую ошибку в пределах этой границы, а сумма этих ошибок, прошедшая через обратный transform, и становится тем, что зритель в итоге воспринимает как снижение качества.

Почему файлы становятся меньше? Потому что большинство ячеек типичного transform-блока содержат небольшие числа, и как только Qstep превышает эти значения, они округляются до нуля. Энтропийный кодер почти не тратит битов на длинные последовательности нулей. Большие числа в верхнем левом углу – структурная энергия блока – остаются, и стоимость представления блока сокращается с «16 целых чисел» до «два ненулевых коэффициента плюс флаг, что остальное – нули».

Параметр квантования – одна настройка, логарифмическая шкала

Современные кодеки не отображают Qstep напрямую. Вместо этого они используют небольшое целое число – quantization parameter (QP) в H.264, HEVC, VP9 и VVC, а также qindex в AV1, из которого Qstep вычисляется по фиксированной математической формуле. Связь между ними логарифмическая – и это сделано намеренно: человеческий глаз реагирует на относительные изменения ошибки, а не на абсолютные. Поэтому удвоение Qstep должно соответствовать одному перцептивному «щелчку», а не пятидесяти.

В H.264, HEVC, VP9 и VVC действует одно и то же правило: Qstep удваивается каждые шесть пунктов QP. Конкретно:

Qstep(QP) = 2 ** ((QP − 4) / 6)

Это фиксирует Qstep на 1 при QP = 4, на 2 при QP = 10, на 4 при QP = 16, на 8 при QP = 22, на 16 при QP = 28, на 32 при QP = 34 и так далее. Минимальный QP в стандарте – 0; для 8-битного контента диапазон H.264 / HEVC составляет от 0 до 51, с расширениями для 10-битного (0..63) и 12-битного (0..75) контента. Каждое увеличение QP на единицу повышает шаг примерно на 12,2%, а каждые шесть пунктов – в точности удваивают его. Кодер выбирает значение QP, декодер считывает его, и оба вычисляют Qstep одинаково.

Правило удвоения делает QP интуитивно понятным, как только с ним поработаешь. Переход с QP 23 на QP 29 удваивает среднюю ошибку квантования, а с QP 23 на QP 35 – увеличивает её в четыре раза. Стриминг-инженер, запоминающий четыре опорные точки – QP 23 для «прозрачного качества», QP 28 для «хорошо», QP 33 для «смотрибельно на маленьком экране», QP 38 для «заметно мягкая картинка» – может предсказать результат транскодинга, даже не запуская его.

AV1 использует более тонкую шкалу под названием qindex с диапазоном от 0 до 255. Соответствие между qindex и Qstep не является чистой степенью двойки – вместо этого применяется таблица, откалиброванная так, чтобы обеспечить более плавное управление на высоком конце диапазона и охватить более широкий диапазон, чем у шкалы 0..51. Примерное соотношение: qindex ≈ 4 × QP, так что qindex 100 в AV1 примерно соответствует QP 25 в HEVC. Таблицы в AV1 разделены по типу коэффициентов – отдельно для DC и AC, а также по цветовым плоскостям (luma и chroma), что позволяет AV1 точнее контролировать, сколько ошибок выделяется на каждый компонент.

Рис. 2. Шаг квантования в зависимости от QP. Кривая удваивается каждые шесть пунктов; qindex AV1 даёт в четыре раза более тонкий шаг.

Пример на одном блоке

Возьмём 4×4 transform-блок из гладкого участка неба после intra-предсказания. DCT-коэффициенты могут выглядеть так (числа подобраны для удобства чтения, в реальности значения обычно больше):

 96   8   2   0
 12   4   1   0
  3   1   0   0
  0   0   0   0

При QP = 22 шаг равен 8. Делим значение каждой ячейки на 8 и округляем результат.

 12   1   0   0
  2   1   0   0
  0   0   0   0
  0   0   0   0

Шестнадцать ячеек на входе; четыре ненулевых значения на выходе. Энтропийный кодер передаёт эти четыре числа вместе с коротким флагом – примерно пятнадцать бит вместо нескольких сотен, которые потребовались бы для передачи необработанного блока. Декодер выполняет обратное умножение:

 96   8   0   0
 16   8   0   0
  0   0   0   0
  0   0   0   0

По сравнению с оригиналом нижний левый «12» стал «16» – ошибка 4; «4» во втором столбце превратилось в «8»; «2» в верхней строке исчезло; и так далее. Ошибки не превышают 4 (половину шага). После обратного преобразования блок для зрителя становится неотличим от оригинального неба.

Теперь тот же блок при QP = 34. Шаг 32:

 3  0  0  0
 0  0  0  0
 0  0  0  0
 0  0  0  0

Уцелела одна ячейка. Битстрим короче на пятнадцать бит. Декодер восстанавливает блок как плоский патч со значением 3 × 32 = 96 в DC-позиции и нулями во всём остальном – равномерный 4×4 субблок средней яркости. В оригинале была мягкая вариация по четырём строкам и четырём столбцам; теперь она потеряна навсегда. На плоском небе зритель этого не заметит. На лице тот же QP сделает щёку похожей на мазок краски. В этом и состоит всё искусство выбора QP.

Dead zone – маленький приём, который многое значит

Реальный кодек не использует учебное правило quantized = round(coefficient / Qstep). Вместо этого применяется асимметричная версия, при которой маленькие положительные коэффициенты сдвигаются к нулю. Интервал [−Qstep/2, +Qstep/2], в учебниках отображаемый в ноль, расширяется примерно до [−2·Qstep/3, +2·Qstep/3]. Этот расширенный интервал называется dead zone – мёртвая зона, – и внутри него любой коэффициент, будь то маленький положительный или отрицательный, округляется до нуля, а не до ±1.

Зачем асимметрия? Потому что ноль – особенный. Длинная последовательность нулей занимает почти ноль бит; одиночная ±1 посередине нарушает run и стоит реальный бит. Отправляя маленькие коэффициенты в dead zone, мы немного увеличиваем искажение, но получаем реальную экономию бит – и на естественном контенте этот обмен почти всегда оправдан.

Ширина dead zone – скрытый параметр кодера: декодеру не важно, какую ширину выбрал кодер, потому что он просто умножает целое значение обратно на Qstep. Reference-реализация H.264 использует dead zone примерно в 5/6 Qstep для intra-блоков и 2/3 Qstep для inter-блоков; в AV1 форма немного отличается. Настройка dead zone – одна из самых простых визуальных «ручек» в распоряжении команды кодера, и выигрыш вполне реальный: 5–10% битрейта при неизменном VMAF при хорошо подобранной dead zone.

Матрицы квантования – разная точность для разных частот

Глаз более терпим к ошибкам в высокочастотных коэффициентах, чем в низкочастотных. Квантование с постоянным шагом (Qstep) тратит точность на высокочастотные компоненты, которые зритель не замечает. Современные кодеки позволяют использовать матрицу квантования – поэлементный множитель к Qstep, который уменьшает шаг квантования для высоких частот и увеличивает его для низких.

Для блока 8×8 default-матрица в стиле JPEG может выглядеть так:

16  11  10  16   24   40   51   61
12  12  14  19   26   58   60   55
14  13  16  24   40   57   69   56
14  17  22  29   51   87   80   62
18  22  37  56   68  109  103   77
24  35  55  64   81  104  113   92
49  64  78  87  103  121  120  101
72  92  95  98  112  100  103   99

В верхнем левом углу множитель равен 16, в нижнем правом – 99. При базовом Qstep = 8 эффективный шаг в нижнем правом углу составляет 8 × 99 / 16 ≈ 49, то есть примерно в шесть раз грубее учебного шага, тогда как в верхнем левом углу он остаётся близким к учебному значению. Зритель не замечает потери в высокочастотных ячейках, поскольку глаз на этих частотах не способен различать мелкие детали.

H.264, HEVC и VVC позволяют кодеру выбирать между плоскими матрицами (все ячейки умножаются на одно и то же значение – эквивалент отсутствия матрицы), стандартными матрицами, заложенными в спецификацию, и пользовательскими матрицами, передаваемыми в битстриме. В AV1 используются фиксированные матрицы из стандарта, зависящие от размера преобразования; кодер не может их переопределить, но может выбрать одну из небольшого встроенного набора. У VP9 поддерживается только плоская квантовая матрица.

Практический совет для стриминг-инженеров: плоские матрицы хорошо подходят для межкадровой компрессии и черновых прогонов. Стандартные матрицы показывают лучшие результаты на внутрикадровых кадрах и при медленных пресетах. Кастомные матрицы оправданы только в случае наличия эталонного контента определённого типа – анимация, screen capture, спорт – и чётко измеряемой цели.

Зависимое квантование – полушаг VVC

VVC, финализированный в 2020 году и принятый как ITU-T H.266, добавил новый инструмент – dependent quantization – позволяющий кодеру эффективно удвоить точность квантования без увеличения целочисленной шкалы. Идея, впервые предложенная Хайко Шварцем и коллегами из Heinrich Hertz Institute, заключается в переключении между двумя квантизаторами с уровнями реконструкции, сдвинутыми на полшага, в зависимости от чётности предыдущих коэффициентов в порядке их обхода.

В обычном кодеке при Qstep = 8 уровни реконструкции составляют 0, ±8, ±16, ±24 и так далее. Метод dependent quantization добавляет второй набор уровней, сдвинутый на полшага: 0, ±4, ±12, ±20 – и позволяет кодеру выбирать, к какому из наборов относится каждый коэффициент, по правилу «выбор зависит от предыдущего коэффициента». Маленький конечный автомат с четырьмя состояниями кодирует, какой квантизатор активен на каждом шаге.

Результат – более плотная решётка реконструкции. Кодер находит такие комбинации значений, которые аппроксимируют исходный блок точнее, чем это может сделать одиночный квантизатор, а энтропийный кодер тратит лишь один дополнительный бит на блок для сигнализации состояния автомата. Dependent quantization экономит от 3% до 5% битрейта при сохранении качества в типичном VVC-кодировании и включена по умолчанию в большинстве production-кодеров VVC.

«Pitfall. Некоторые команды отключают dependent quantization в VVC, прочитав спецификацию и решив, что это «дорого». На естественном контенте выигрыш действительно есть, а нагрузка на кодер составляет всего один-два процента – недостаточно, чтобы оправдать отключение. Отключайте этот инструмент только для маломощных декодеров, которые явно не поддерживают его.»

Adaptive quantization – тратить биты там, куда смотрит глаз

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

Простейшая схема – variance-based adaptive quantization, обычно с флагом aq-mode=1. Кодер вычисляет дисперсию каждого макроблока (или CTU, или суперблока) и снижает QP в тех областях, где дисперсия высока. Более продвинутая схема, aq-mode=2, использует модель «spatial complexity», которая аккуратнее работает как с очень однородными участками (где может появиться полосатость), так и с сильно текстурированными (где глаза не различают мелкие детали). x264, x265, libvpx, libaom и SVT-AV1 – все поддерживают подобные подходы; названия и значения по умолчанию различаются, но принцип остаётся тем же.

Вторая ось – temporal adaptive quantization, иногда называемая MB-tree в x264 и x265. Кодер выполняет обратный проход, оценивая, насколько часто каждый блок в reference-кадре будет использоваться последующими блоками, и снижает QP для тех блоков, к которым будет больше обращений. Такие инвестиции окупаются в большинстве кадров. MB-Tree – главная причина, по которой современный x265-кодек при одинаковом QP выглядит лучше, чем тот же кодек с отключённой функцией: выигрыш в 10–20% по PSNR-эквивалентному битрейту – вполне обычное явление.

Третья ось – psycho-visual quantization, семейство приёмов, основанных на наблюдении: человеческий глаз предпочитает сохранение высокочастотной «энергии» (текстур) по сравнению с плоским сглаживанием. Флаги --psy-rd и --psy-rdoq в x264/x265 корректируют стоимость rate-distortion при выборе квантования так, что кодер отдаёт предпочтение блокам с сохранённой видимой текстурой, даже если это приводит к небольшому увеличению численного искажения. В результате PSNR может быть хуже, но субъективное восприятие и оценка по VMAF на настроенных моделях – лучше. Подробнее – в статье метриках качества: PSNR, SSIM, VMAF.

Рис. 3. Плоский и адаптивный квантование. Средний QP одинаковый, воспринимаемое качество – очень разное, потому что при адаптивном кодировании битовый бюджет направлен на те области, которые замечает глаз.

Как сравниваются кодеки – одна таблица

В таблице ниже приведены основные характеристики квантования кодеков, которые мы отгружали или использовали в production в Фора Софт. Столбцы слева направо: кодек, QP-шкала, правило шага, поддержка матриц, dependent quantization, инструменты adaptive quantization.

КодекQP-шкалаПравило шагаМатрицыDependent QAdaptive Q
H.264 / AVC0..51 (8-bit)удваивается каждые 6Default + customНетНа стороне кодера (x264)
H.265 / HEVC0..51 (8-bit), расширение до 63/75удваивается каждые 6Default + custom, поблочная deltaНетНа стороне кодера (x265, MB-tree)
VP90..255 qindexкалиброванная таблицаТолько плоскийНетНа стороне кодера (libvpx)
AV10..255 qindexкалиброванная таблица, на DC/AC и на плоскостьВстроенный набор, без overrideНетВстроенные segment-карты + логика кодера
VVC / H.2660..63 (8-bit), расширение до 75 (10-bit)удваивается каждые 6Default + custom + LFNST-awareДа (по умолчанию)На стороне кодера + tools-aware

Паттерн поколений очевиден: базовое правило шага практически не менялось с H.264, но каждое новое поколение добавляет всё более тонкие инструменты – поблочный delta QP, таблицы на плоскость, частотозависимые матрицы, dependent quantization – позволяющие кодеру точнее распределять биты. Quantization – одна и та же идея, применяемая всё более хирургически.

CRF, CBR, VBR – квантизация под другими именами

Большинство флагов кодера, с которыми реально работают стриминг-инженеры – CRF (Constant Rate Factor), CBR (Constant Bit Rate), VBR (Variable Bit Rate), ABR (Average Bit Rate) – являются режимами управления битрейтом, которые динамически подстраивают QP под заданные параметры. Подробно о каждом из них – в статье rate control: CBR, VBR, CRF.

CRF – самый простой в понимании режим: кодер берёт базовый QP, ориентированный на постоянное перцептивное качество, и применяет адаптивную квантование, чтобы варьировать QP по блокам. CRF 23 в x264 примерно соответствует среднему QP, на котором кодер остановился бы, если бы ему поручили закодировать фильм 1080p в «прозрачном качестве»; CRF 28 в x265 примерно соответствует той же точке. Цифра на ручке – не буквально QP, но управляет тем же базовым параметром.

CBR и VBR накладывают буферную модель поверх логики CRF. CBR ограничивает QP кодера таким значением, чтобы уложиться в целевой битовый бюджет каждую секунду; VBR позволяет кодеру тратить больше бит на динамичные сцены и меньше – на статичные, также изменяя QP. Шкала QP – это основа; режим управления битрейтом – политика, определяющая, где на этой шкале находиться.

Следствие для продуктовых решений – краткое. Каждое «снизить битрейт» на практике означает «повысить QP». Если QP становится слишком высоким, блоки теряют детализацию, края размываются, градиенты превращаются в ступеньки. Никакой магии в rate control нет – он не скрывает потерю качества, а лишь решает, когда её распределить.

Частые артефакты при квантовании

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

Blockiness – видимая граница между transform-блоками. Когда значение QP слишком высокое, каждый блок реконструируется как почти однородный фрагмент, и переход между соседними фрагментами становится заметным. Современные кодеки применяют in-loop deblocking filter, который сглаживает эти границы – подробнее об этом см. в статье in-loop filtering, – однако у фильтра есть пределы. При QP выше 38 в H.264 он уже не справляется.

Banding – видимые ступеньки в градиенте: закат, дымка, синяя стена – там, где в оригинале был плавный переход, а в закодированном изображении видны две-три равномерные полосы. Причина – квантование на низких частотах на гладком изображении: DC-коэффициент каждого блока округляется до ближайшего целого, и шаг округления проявляется как полоса. Banding – классическая проблема 8-битного кодирования и главный аргумент в пользу 10-битного даже для SDR; подробнее – в статье 8-бит vs 10-бит кодирование.

Blurring – потеря высокочастотных деталей: текст становится «пушистым», волосы – размытой массой, трава – сплошным пятном. Причина – quantization matrix, обнуляющая высокочастотные коэффициенты. При значениях QP выше определённого порога высокочастотные элементы матрицы обнуляются даже для настоящих границ, и обратный transform восстанавливает блок как низкочастотную версию.

Ringing – ореол волнистых артефактов вокруг резкого края. Причина – асимметричное квантование высокочастотных коэффициентов, которые DCT распределяет по области вокруг края; если часть из них сохраняется, а большинство обнуляется, реконструированный блок отображает оставшиеся как видимую рябь. Ringing – классический недостаток кодеков, использующих только DCT, при кодировании мультфильмов и текста, а также классическая причина, по которой были разработаны DST и ADST, которые мы рассмотрели в статье transform coding.

Mosquito noise – быстро меняющиеся мелкие артефакты вокруг движущихся краёв. Причина в том, что квантование отличается от кадра к кадру, из-за чего сохраняющиеся высокочастотные коэффициенты на границах «мерцают». Устраняется на стороне кодера: более точный контроль битрейта и меньшая вариация QP между соседними кадрами – а не на стороне декодера.

Где здесь Фора Софт

Мы применяем quantization-тюнинг в production-кодировании с 2010 года в таких областях, как видеостриминг, OTT и интернет-ТВ, видеоконференции и видеонаблюдение. Эффективные шаги, которые экономят ресурсы, обычно одинаковы: выбрать оптимальный анкер CRF или qindex для каждого типа контента, убедиться, что адаптивная квантование включена и правильно настроена под конкретный жанр (спорт, talking-head, анимация, screen capture), при необходимости внедрить небольшую кастомную матрицу квантования для наименее эффективного типа контента, если это оправдано цифрами, и обязательно проверять каждое изменение через A/B-тест на реальных устройствах пользователей. На WebRTC SFU нам удавалось достигать экономии трафика на 20–30% без заметной потери качества, просто настроив поведение кодера по QP для контента типа talking-head.

Ключевые выводы

  • Квантование – единственный этап кодека, на котором информация теряется безвозвратно.
  • Параметр квантования (QP) управляет логарифмическим шагом: каждые шесть единиц шаг удваивается.
  • В AV1 диапазон qindex 0..255 обеспечивает в четыре раза более точную градацию, чем диапазон 0..51.
  • Матрицы квантования повышают точность в областях, на которые обращает внимание глаз, и снижают её там, где он не фокусируется.
  • Адаптивное квантование перераспределяет биты в пользу перцептивно значимых блоков внутри кадра.
  • Любой режим управления битрейтом (CRF, CBR, VBR) – это стратегия динамического выбора QP.

Что почитать дальше

Строите такую систему?

Подберём параметры кодирования под ваш контент и посчитаем стоимость доставки до старта разработки.