После того как я написал статью про то, что ваш монитор не умеет показывать бирюзовый и 65% видимых цветов для него просто не существуют, один мой знакомый (далекий правда от технической отрасли) спросил: «Окей, монитор врёт, а что тогда делает JPEG с оставшимися 35%?» И это хороший вопрос. Я полез в спеку, а через полчаса забыл, зачем вообще полез. Потому меня уже интересовало другое: ребята, которые в 1992-м финализировали этот стандарт, по сути заревёрсили человеческое зрение и запихнули его в алгоритм сжатия.

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

И я хочу вам про это рассказать, потому что это самый красивый кусок инженерии, который я видел за 12 лет в индустрии. Там я разбирал, как мало мы на самом деле видим. Здесь — как мало нам на самом деле нужно видеть, чтобы мозг поверил, что видит всё. А потом я решил это проверить руками.


Ваш глаз — не камера (спасибо кэп)

Мы привыкли думать о глазах как о сенсорах. Свет попал на сетчатку, сетчатка отправила данные в мозг, мозг «увидел». Этакий биологический аналог матрицы камеры. Ну, примерно.

Нет.

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

Еще раз, соотношение примерно 20:1. Ваша сетчатка — это, грубо говоря, высокочёткий чёрно-белый сенсор, к которому скотчем примотали дешевую цветную вебку из 2005-го.

И создатели JPEG это знали.


YCbCr, или Зачем JPEG первым делом выбрасывает RGB

Когда вы сохраняете картинку в JPEG, первое, что происходит — не сжатие, ( и даже не оптимизация) а смена языка на котором описан цвет.

Картинка приходит в RGB. Так работает монитор, так снимает камера, но JPEG тут же переводит всё в другую систему — YCbCr:

  • Y — яркость. Насколько светлый пиксель.

  • Cb — насколько он синий (точнее, разница между синим и яркостью).

  • Cr — насколько он красный (аналогично).

Зачем? Кажется, что это абсолютно лишний шаг. RGB уже есть, все его понимают, зачем конвертировать туда-сюда?

А вот зачем. В RGB все три канала одинаково важны. Убери красный — картинка развалится. Убери зелёный — тоже. Все три равноправны, ни один нельзя тронуть.

В YCbCr — другая ситуация. Канал Y, яркость — это то, что ваш глаз видит отлично. За яркость отвечают 120 миллионов палочек в сетчатке. Они различают мельчайшие градации — тени, контуры, текстуры. А Cb и Cr, цветность — это всего 6 миллионов колбочек. В двадцать раз меньше. Цвет ваш глаз видит грубо.

Перевод в YCbCr — это по сути разделение картинки на «то, что глаз заметит» и «то, что можно втихаря порезать».

Формула, кстати, красноречива:

Y = 0.299 × R + 0.587 × G + 0.114 × B

Посмотрите на коэффициенты. Зелёный — 0.587, больше половины. Красный — 0.299. Синий — жалкие 0.114. Это буквально модель вашей сетчатки. Пик чувствительности человеческого глаза — около 555 нм, жёлто-зелёная область. Эволюция затачивала зрение приматов под «найди спелый фрукт среди зелёных листьев», и формула 1992 года это копирует.

Если вы когда-нибудь конвертировали фотографию в чёрно-белую и удивлялись, почему 0.33R + 0.33G + 0.33B выглядит плоско, а с правильными коэффициентами объёмно и контрастно — вот это оно самое. «Правильные коэффициенты» — это Y-канал JPEG.


Chroma subsampling — главный чит-код

После конвертации в YCbCr стандарт JPEG позволяет хранить цветовые каналы (Cb и Cr) с пониженным разрешением. Это называется chroma subsampling, и самый распространённый режим — 4:2:0.

Что это значит на практике? Y-канал хранится в полном разрешении. А Cb и Cr в разрешении 2×2, то есть один цветовой пиксель на четыре яркостных.

То есть, вы выбрасываете 75% цветовой информации и этого почти никто не замечает. (почему?) Потому что ваша сетчатка тоже так делает. Колбочки сконцентрированы в центральной ямке, а на периферии их почти нет. Пространственное разрешение вашего цветового зрения примерно вчетверо ниже яркостного.

То есть JPEG не «сжимает цвет», он отбрасывает ровно то, что ваш глаз и так не обрабатывает.

Хотите почувствовать это на себе? Откройте любое фото в Photoshop, разделите на каналы в YCbCr, и посмотрите на Cb/Cr отдельно ,и вы увидите мутное, расплывчатое нечто, и ваш мозг будет настаивать, что «ну не может так быть, фотка же была чёткая». А вы на самом деле просто никогда раньше не смотрели на цвет отдельно от яркости, потому что ваша зрительная система их мержит автоматически. (кто не хочет копаться в Photoshop ниже сделал это за вас, и для вас)


DCT, или Как эксплуатировать нейроны зрительной коры

Окей, мы порезали цвет. Но основное сжатие в JPEG — это же дискретное косинусное преобразование, квантование и энтропийное кодирование. Причём тут сетчатка?

А вот причём — DCT разбивает изображение на блоки 8×8 пикселей и раскладывает каждый блок на 64 частотных компоненты, от низкой (плавные градиенты) до высокой (резкие переходы, детали и шум).

Вот так выглядит базис DCT для блока 8×8 — 64 «кирпичика», из комбинации которых складывается любой блок:

И знаете что? Нейроны первичной зрительной коры (V1) работают поразительно похоже. Ещё в 60-х Хьюбел и Визел (Нобелевка 1981 года, между прочим) обнаружили, что нейроны V1 реагируют на ориентированные полоски разной частоты. По сути на пространственные частоты. Ваша зрительная кора делает что-то вроде преобразования Фурье над входящим сигналом.

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


Квантование — таблица ваших слепых пятен

После DCT у нас есть 64 коэффициента для каждого блока 8×8. Чтобы сжать, нужно часть из них обнулить или огрубить. Для этого каждый коэффициент делится на число из таблицы квантования, и результат округляется.

Стандартная таблица квантования для яркостного канала:

Читается она так: левый верхний угол — низкие частоты (маленькие числа, слабое квантование, сохраняем точность). Правый нижний — высокие частоты (большие числа, грубое квантование, почти выбрасываем).

Эта таблица — карта контрастной чувствительности вашей зрительной системы.

CSF описывает, насколько хорошо человек различает контрастные паттерны на разных пространственных частотах. Пик где-то на 3-5 циклах на градус угла зрения. Ниже — хуже (слишком плавные переходы). Выше — тоже хуже (слишком мелкие детали). А за пределами ~60 циклов на градус вы вообще ничего не видите — это предел пространственного разрешения глаза.

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

Когда вы двигаете ползунок «качество» в Photoshop с 12 на 8, то вы увеличиваете коэффициенты в этой таблице, всё глубже забираясь в зону того, что ваш глаз ещё может заметить. Quality 100 — почти без потерь, а quality 10 — алгоритм предполагает, что ваш глаз ещё более слеп, чем на самом деле.


«А докажи»

Ну на словах интересно, а можно ли это увидеть? Что именно JPEG выбрасывает, и правда ли я этого не замечаю?

Я написал скрипт на Python. Скрипт несложный, и идея простая: берём исходное PNG-изображение, сохраняем в JPEG с разным quality, потом вычитаем одно из другого — и смотрим на разницу. На то, что алгоритм решил выбросить.

from PIL import Image
import numpy as np

original = np.array(Image.open("photo.png")).astype(np.float32)

for q in [95, 80, 50, 20, 5]:
    Image.open("photo.png").save(f"q{q}.jpg", quality=q)
    compressed = np.array(Image.open(f"q{q}.jpg")).astype(np.float32)
    
    diff = original - compressed
    # Нормализуем для визуализации (сдвиг + масштаб)
    diff_visual = ((diff - diff.min()) / (diff.max() - diff.min()) * 255)
    Image.fromarray(diff_visual.astype(np.uint8)).save(f"diff_q{q}.png")

Признаюсь, ожидал увидеть что-то скучное. Равномерный шум, может быть. Ну, блоки 8×8 — это я знал заранее. (позже добавил еще контраста, чтобы разница была более заметной)


Призрак в разнице

На quality 95 карта различий практически чёрная. Еле видно, и только после того как я выкрутил контраст.

На quality 80 — стандарт для веба — уже видно что-то. Но не шум. Карта различий показывает контуры, края объектов, и тонкие линии. Текстуры с высокой частотой — волосы и ткань. Именно то, что DCT кладёт в высокочастотные коэффициенты, которые квантование обнуляет первыми.

На quality 50 — карта различий выглядит прям как художественная гравюра. Все контуры исходного изображения и вся мелкая текстура.

А вот на quality 5 стало интереснее.

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

Вдумайтесь: граница между «нормальная фотка» и «глаза вытекают» — это граница вашей контрастной чувствительности. И она проходит где-то между quality 40 и quality 20 для большинства изображений.


Quality 80: а почему, собственно, 80?

После экспериментов мне стало интересно ещё кое-что. Каждый веб-разработчик «знает», что quality 80 — золотой стандарт для JPEG на вебе.

Почему?

Я построил график. По оси X — quality от 5 до 100. По оси Y — размер файла и SSIM (structural similarity index, метрика, которая пытается оценить видимое сходство).

from PIL import Image
import numpy as np
from skimage.metrics import structural_similarity as ssim
import io

original = np.array(Image.open("photo.png"))
results = []

for q in range(5, 101):
    buf = io.BytesIO()
    Image.open("photo.png").save(buf, format="JPEG", quality=q)
    size_kb = buf.tell() / 1024
    
    buf.seek(0)
    compressed = np.array(Image.open(buf))
    score = ssim(original, compressed, channel_axis=2)
    results.append((q, size_kb, score))

Размер файла растёт примерно экспоненциально при приближении к 100. Между quality 80 и quality 95 размер файла может отличаться вдвое — при разнице в SSIM на третьем знаке после запятой.

Quality 80 — это точка перегиба. Место, где кривая «выигрыш в качестве / проигрыш в размере» резко меняет наклон. И это место (сюрприз) определяется всё той же CSF. До quality ~80 квантование режет только те частоты, к которым ваш глаз слабо чувствителен. После 80 начинает жертвовать частотами, которые вы видите.

Эмпирическое правило «ставь 80» не привычка. Это, по сути, упрощённая формулировка для «отрежь ровно то, что не видит человеческий глаз, и ни битом больше». Просто никто не объясняет это именно так.


Почему мне не даёт покоя эта история

Я каждый день пишу <img src="photo.jpg"> и не задумываюсь, что за этим стоит. Мы деплоим картинки, оптимизируем через ImageMagick или Sharp, ругаемся на размеры бандла — и воспринимаем JPEG как данность. Как TCP/IP, и как UTF-8. Работает и работает.

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

JPEG — алгоритм сжатия восприятия.

Если задуматься, WebP и AVIF делают ровно то же самое, просто с более тонкими моделями. У AVIF есть film grain synthesis — он не хранит шум, а генерирует его при декодировании, потому что ваш мозг не запоминает конкретное расположение зерна, а только его «характер». Опять хак поверх биологии.


А что дальше?

Знаете, что самое забавное? Эти таблицы квантования в спецификации — примерные. Annex K явно говорит: «These are not default quantization tables. These are provided for illustration purposes only.» Каждый энкодер волен генерировать свои.

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

А JPEG XL (стандарт 2022 года, который Chrome поддержал и потом выпилил — отдельная грустная история) пошёл ещё дальше. Там психовизуальная модель встроена прямо в ядро кодека. Там не таблица 8×8, а полноценная перцептивная метрика Butteraugli, которая моделирует ваше зрение включая маскировку, адаптацию к яркости и чувствительность к направлению контуров. По сути — кодек, который симулирует ваш зрительный тракт для каждого блока и спрашивает: «увидит или не увидит?»

Тридцать лет спустя мы всё ещё оптимизируем тот же трюк: найти границу между «видит» и «не видит», и пройти по ней как можно ближе. Просто граница стала тоньше, а модели точнее.

Каждый раз, когда вы открываете JPEG, ваш компьютер по сути говорит: «Я знаю, как устроены твои глаза, и я этим воспользуюсь».

А вы когда-нибудь задумывались, почему JPEG-артефакты выглядят именно так — этими характерными блоками 8×8? Теперь вы знаете. Это сетка, по которой алгоритм резал вашу реальность на кусочки, оптимизированные под биологию ваших глаз. Просто обычно он попадает так точно, что вы не замечаете швов.


А вы знали про эту историю? Или, как и я, просто писали quality: 80 и не задумывались почему именно 80?


UPD: Примечание для биологов: да, я знаю, что при дневном свете палочки «слепнут» и яркость тоже считывается колбочками, а разница в резкости обусловлена нейронным пулингом. Но для понимания логики JPEG метафора с ч/б сенсором работает слишком хорошо, чтобы от неё отказываться.

Комментарии (30)


  1. tarasovav
    24.03.2026 09:44

    Я всегда просто ставила quality 80 и всё, а оказывается там целая психофизика, нейроны и DCT-блоки… Как будто кто-то научился обманывать твой мозг так, чтобы ты этого не замечала. Теперь буду смотреть на фото и думать, а что я реально вижу, а что мозг догадался сам


    1. exalon
      24.03.2026 09:44

      Думаю вы как и все сначала покрутили крутилку качества и присмотрелись - норм/нет и в какой то момент остановились на 80, т.к.. это оказалось лучшим вариантом. А сейчас вы просто узнали почему вы как все)

      Про обман мозга тут ещё бы про MP3 упомянуть не помешает, там тоже "убираем что итак не услышит")


      1. nixtonixto
        24.03.2026 09:44

        Любое сжатие С потерями эксплуатирует недостатки рецепторов человека.


      1. vadimk91
        24.03.2026 09:44

        Про музыку - в то же время есть немало людей, которые фанатеют от оцифровки LP в lossless с дискретизацией 192k. Что-то они слышат. Лично я в таких оцифровках кроме лишнего шума и размера файла ничего не наблюдаю :)


        1. biophyzix
          24.03.2026 09:44

          Именно оцифровка LP, скорее всего, смысла не имеет.

          А вот электронную музыку, которую пишу сам, я экспортирую во FLAC 192k, если проект не совсем тяжёлый, ибо большие проекты и в 44.1k иногда с трудом допиливаю.

          Там разница скорее в том, что все этапы синтеза и обработки звука проходят в 192k, а потому снижается алиасинг (что влияет на звучание дисторшна и большинства синтезаторов) и чище обрабатывают эффекты, в основе которых — запись в буфер и последующее воспроизведение с изменениями скорости.

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

          Затем отрендеренный файл можно конвертнуть из 192k в 44.1k.

          Чуть-чуть могут быть заметны артефакты в высоких частотах, но они еле слышны.

          В 48k вообще не заметны.

          т е разница именно в частоте дискретизации при рендере композиции, а не в файле, который попадает к конечному слушателю


    1. Javian
      24.03.2026 09:44

      Насколько я помню у программ начала 2000-х по умолчанию было 75.


  1. NickDoom
    24.03.2026 09:44

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

    Ну, или чтобы меня врачи временно на ноги поставили, тогда сам допишу. Если бы можно было вену протезировать, эххх… но, увы, на вене клапана, это не артерия — трубочкой не заменишь.


    1. vadimk91
      24.03.2026 09:44

      Когда я купил экшн камеру для поездок, умеющую снимать в 4К с кодеками H264/265 с fps 120, думал "во, можно снимать только видео, а фотки я надергаю из отдельных кадров". Но сжатие видео оказаласось суровее, чем я ожидал, такие "фото" годны только для превью...

      И да, здоровья, это самый главный ресурс.


    1. astromc
      24.03.2026 09:44

      блин. я не специалист вот вообще, но вырастить то можно? во всяком случае как это демонстрируют всякие разные исследователи. полагаю вопрос - в том, что между нынешней медициной и нынешними исследованиями - пропасть. Ищите того, кто сделает первый шаг, как Святослав Фёдоров.

      Всех благ вам.


  1. rybakolbasa
    24.03.2026 09:44

    Напомнило ZX Spectrum. Тоже низкое разрешение цветности.


    1. Faven
      24.03.2026 09:44

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


      1. AcckiyGerman
        24.03.2026 09:44

        Может у них зрение в радиодиапазоне или ультрафиолете и наши фотки они вообще не увидят.


  1. wataru
    24.03.2026 09:44

    Место, где кривая «выигрыш в качестве / проигрыш в размере» резко меняет наклон. И это место (сюрприз) определяется всё той же CSF. До quality ~80 квантование режет только те частоты, к которым ваш глаз слабо чувствителен.

    Тут натягивание совы на глубус. У вас там график SSIM - тупо численная характеристика разности по пикселям, ей на частоты, и какие из них наш глаз хорошо видит, вообще пофигу.

    Это просто универсальный принцип убывающей отдачи, или же принцип Парето - 20% усилий отвечает за 80% результата. Следующие 20 за 80 от остатка. И так дале. Поэтому чем больше усилий тем меньше скорость роста результата.


    1. DandyDan
      24.03.2026 09:44

      Отличительной особенностью метода, помимо упомянутых ранее (MSE и PSNR), является то, что метод учитывает «восприятие ошибки» благодаря учёту структурного изменения информации. Идея заключается в том, что пиксели имеют сильную взаимосвязь, особенно когда они близки пространственно. Данные зависимости несут важную информацию о структуре объектов и о сцене в целом.


      1. wataru
        24.03.2026 09:44

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


        1. DandyDan
          24.03.2026 09:44

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


          1. wataru
            24.03.2026 09:44

            Зачем? У меня нет никаких претензий ни к SSIM, ни к графику из статьи. У меня лишь претензии к выводу, которым этот график сопроводили, который ни из графика, ни из считаемой метрики никак не выходит.


  1. AVX
    24.03.2026 09:44

    Описано весьма хорошо, но не хватает объяснения шкалы качества по "е**чим шакалам")


    1. Kurochkin
      24.03.2026 09:44

      И отдельного объяснения на тему цветоаномалии (зрения).


  1. AbuMohammed
    24.03.2026 09:44

    Строго говоря не преобразование Фурье, а преобразование Габора. Кстати, характерно для всех высших mammals. А вот для других - нет.


  1. Aggle
    24.03.2026 09:44

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


  1. engine9
    24.03.2026 09:44

    Прореживание канала цветности придумали задолго до развития цифровой техники, в стандарте цветного аналогово телевещания.

    Статья хорошая, спасибо.


  1. rubyrabbit
    24.03.2026 09:44

    Спасибо за историю.

    Всё же очень интересно, насколько индивидуально различны сенсоры между людьми. Как со звуком. Наверняка кому-то жпег больнее, чем другим. А кто-то вообще не понимает, о чём мы, всё комфортно.


  1. GlazOtca
    24.03.2026 09:44

    Эволюция затачивала зрение приматов под «найди спелый фрукт среди зелёных листьев»

    Не совсем так. Если посмотреть на спектр излучения нашего Солнца - то максимум приходится как раз на зеленую часть - вот на что ориентировалась эволюция.


  1. Prohard
    24.03.2026 09:44

    Хорошая статья, спасибо. Вот как-то раз знакомый офтальмолог сказал мне: - Наши глаза смотрят, а видим мы своими мозгами."


  1. consalt
    24.03.2026 09:44

    Jpeg xl как раз недавно был добавлен в хромобраузеры. #enable-jxl-image-format


  1. AngryEvilCookie
    24.03.2026 09:44

    YCbCr ненене, 2026 год ICtCp есть, но кодеков новых все нет, печаль прям.


  1. greenkey
    24.03.2026 09:44

    Очень интересно! Я каждый год школьникам рассказываю общее устройство глаза, когда дело доходит до RGB. Небольшое замечание - канал Y, который автор называет "яркостью" на самом деле это середина спектра, как раз желто-зеленая зона, что и отражено в названии - Y. Условно, ее бы и следовало изображать зеленоватой.


  1. DandyDan
    24.03.2026 09:44

    Хоть и знал это ещё с 90х, а всё равно интересно ;)

    И картинка с частотами – прямо супер, гораздо нагляднее, чем текст.


  1. MaxBunin_1188
    24.03.2026 09:44

    Молодец чо. Большая работа, хорошая подача, нужный вектор. Благодарю.