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

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

TL;DR

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

Зачем это вам

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

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

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

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

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

Шаг, обычно записываемый как Qstep, – это расстояние на числовой оси между двумя значениями, которые кодек может представить. Если Qstep равен 8, а коэффициент 21, кодер округляет 21 ÷ 8 до 3, передаёт целое число 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 целых чисел» до «два ненулевых коэффициента плюс флаг, что остальное – нули».

Quantization parameter – одна ручка, логарифмическая шкала

Современные кодеки не показывают Qstep напрямую. Они показывают маленькое целое – quantization parameterQP в 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, декодер читает 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 – не чистая степень двойки, а таблица, откалиброванная для более плавного управления у high-end-конца и для более широкого диапазона, чем у 0..51-шкалы. Грубое правило: qindex ≈ 4 × QP, так что AV1 qindex 100 примерно соответствует HEVC QP 25. Таблицы у 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 (половина шага). После обратного transform блок для зрителя неотличим от оригинального неба.

Теперь тот же блок при 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 около Qstep × 5/6 для intra-блоков и Qstep × 2/3 для inter-блоков; у AV1 форма чуть другая. Тюнинг dead zone – одна из самых простых psy-визуальных ручек, доступных команде кодера, и выигрыш реальный – 5–10% битрейта при том же VMAF на хорошо настроенной dead zone.

Quantization matrices – разная точность для разных частот

Глаз более терпим к ошибке в высокочастотных коэффициентах, чем в низкочастотных. Quantization с плоским Qstep тратит точность на высокочастотные ячейки, где зритель её не видит. Каждый современный кодек позволяет кодеру отгрузить quantization matrix – поячеечный множитель поверх 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 дают кодеру выбор между плоскими матрицами (все ячейки умножаются на одно и то же значение, эквивалент «без матрицы»), default-матрицами, зашитыми в стандарт, и кастомными матрицами в битстриме. AV1 использует фиксированные матрицы из стандарта, зависящие от размера transform; кодер не может их переопределить, но может выбрать из небольшого встроенного набора. У VP9 – только плоский quantization.

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

Dependent quantization – полушаг VVC

VVC, финализированный в 2020 году и принятый как ITU-T H.266 в 2020-м, добавил новый инструмент – 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 виден сразу; тратить одинаковое число бит на оба типа – расточительство. Каждый современный кодер поддерживает adaptive quantization – поблочный сдвиг QP, который понижает QP в детализированных или перцептивно важных блоках и повышает в плоских.

Простейшая схема – variance-based adaptive quantization, обычно с флагом aq-mode=1. Кодер считает variance каждого макроблока (или CTU, или superblock) и понижает QP там, где variance высокий. Более тонкая схема, aq-mode=2, использует «spatial complexity»-модель, которая аккуратнее обрабатывает и очень плоские области (где появляется banding), и очень текстурированные (где глаз деталь не разрешает). 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 стоимость quantization-выборов так, чтобы кодер предпочитал блоки с сохранённой видимой текстурой, даже ценой чуть большего численного искажения. Результат хуже по PSNR, но лучше в субъективных тестах и в VMAF на настроенных моделях. Подробнее в метриках качества: PSNR, SSIM, VMAF.

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

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

В таблице ниже выровнены основные quantization-фичи кодеков, которые мы отгружали или видели в 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 – quantization под другими именами

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

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

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

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

Частые артефакты от quantization

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

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

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

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

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

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

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

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

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

  • Quantization – единственный этап кодека, где информация уничтожается навсегда.
  • Quantization parameter (QP) управляет логарифмическим шагом: каждые шесть пунктов шаг удваивается.
  • AV1 qindex 0..255 даёт в четыре раза более тонкий шаг, чем 0..51.
  • Quantization-матрицы тратят точность там, куда смотрит глаз, и экономят там, куда не смотрит.
  • Adaptive quantization перенаправляет биты к перцептивно важным блокам внутри кадра.
  • Любой режим rate control (CRF, CBR, VBR) – это политика выбора QP на лету.

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

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

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