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


Для начала давайте порассуждаем логически. Если вы делаете ресайз изображения, наверное вы хотите чтобы результат хотя бы отдаленно напоминал оригинал. Для этого нужно учесть как можно больше информации из исходного изображения. Вы слышали о методе «ближайшего соседа»? В этом методе для каждой точки конечного изображения просто берется какая-то одна точка из исходного изображения в неизменном виде.



Уменьшение изображения 4928?3280 до 256?170 ближайшим соседом.


Рекомендую смотреть примеры из статьи в браузере в масштабе 100% и без ретины. То есть по максимуму исключить ресайз при просмотре.

Результат не представляет ничего хорошего. Изображение дерганое, зернистое, даже трудно понять что на нем изображено. Особенно если на исходном изображении было много мелких деталей или оно само было зернистым. Почему так получается? Потому что в конечном изображении было учтено очень мало информации из исходного. Если условно отметить на исходном изображении те точки, которые попадают в конечное, получится вот такая сеточка:



Точки, которые попадут в конечное изображение размером 20?13.

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


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



Уменьшение с 4928?3280 до 256?170 свертками с бикубическим фильтром.


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


>>> from PIL import Image
>>> im = Image.open('pineapple.jpeg'); im.load(); im.size
(2560, 1600)
>>> %time im.resize((256, 170), Image.NEAREST)
Wall time: 0.35 ms

>>> im = Image.open('space.jpeg'); im.load(); im.size
(4928, 3280)
>>> %time im.resize((256, 170), Image.NEAREST)
Wall time: 0.44 ms

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

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


>>> from PIL import Image
>>> im = Image.open('pineapple.jpeg'); im.load(); im.size
(2560, 1600)
>>> %time im.resize((256, 170), Image.BICUBIC)
Wall time: 33.2 ms

>>> im = Image.open('space.jpeg'); im.load(); im.size
(4928, 3280)
>>> %time im.resize((256, 170), Image.BICUBIC)
Wall time: 130 ms

Для в 4 раза большего исходного изображения время тоже возросло в 4 раза.


Фиксированное ядро


Некоторые приложения и библиотеки для работы с графикой пользуются такой хитростью: они как бы используют для ресайза те же фильтры, что и при ресайзе свертками (бывают, например, билинейный, бикубический и фильтр Ланцош), но при уменьшении изображения не увеличивают ядро фильтра адаптивно. В результате для построения любой точки конечного изображения используется только 4 пикселя исходного изображения при билинейном фильтре, при бикубическом — 16, с 3-лобным фильтром Ланцоша — 36. То есть время работы тоже получается константным относительно исходного размера.


Вот только такой подход работает для уменьшения примерно до 2 раз, а дальше результат мало чем отличается от «ближайшего соседа».



Из 4928?3280 в 256?170 с билинейным фильтром с фиксированным ядром.


И говоря «мало чем отличается от „ближайшего соседа“» я имею в виду не только то, что он такой же рваный и зернистый, я имею в виду, что он правда почти совпадает с результатом «ближайшего соседа». Откройте обе картинки в соседних вкладках браузера и попереключайте между ними, картинки почти совпадают. Может даже показаться, что где-то ошибка, что так быть не должно, потому что с фиксированным ядром интерполируется 4 пикселя, а не тупо берется первый попавшийся, и результат должен быть ближе к оригиналу. Но ошибки тут нет и вот почему:



Точки, которые будут интерполироваться при уменьшении до 20?13.


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



Из 4928?3280 в 256?170 с бикубическим фильтром с фиксированным ядром.


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


>>> im = Image.open('space.jpeg'); im.load(); im.size
(4928, 3280)

# Ближайший сосед
>>> %time im.resize((256, 170), Image.NEAREST)
Wall time: 0.441 ms

# Билинейное фиксированное ядро
>>> %time im.transform((256, 170), Image.AFFINE,
                       (im.width / 256, 0, 0, 0, im.height / 170, 0), Image.BILINEAR)
Wall time: 3.62 ms

# Бикубическое фиксированное ядро
>>> %time im.transform((256, 170), Image.AFFINE,
                       (im.width / 256, 0, 0, 0, im.height / 170, 0), Image.BICUBIC)
Wall time: 9.21 ms

Тут я симулировал ресайз с фиксированным ядром с помощью аффинных преобразований. Но некоторые приложения и библиотеки правда делают это: используют для уменьшения более дорогие фильтры, результат которых почти равен методу ближайшего соседа. Так делает OpenCV, так делают браузеры, когда рисуют изображение на канве, так делают видеокарты при текстурировании без mip-уровней. Потому что хоть время и большее, но оно константное относительно разрешения исходного изображения. Ну а качество? Для качества есть свертки.


Как исправить


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



Результат ресайза 4928?3280 в 256?170 за константное время.


Как видите, результат этого алгоритма не идет ни в какое сравнение с разноцветной размазней, получающейся после «ближайшего соседа» или фиксированного ядра. Для примеров к этой статье я намеренно взял довольно большую картинку с мелкой сеткой, с большим количеством деталей (посмотрите на отражение в шлеме астронавта) и очень сильно её уменьшаю. Я сделал все возможное, чтобы на результате вылезло как можно больше артефактов, но алгоритм все равно справляется! Когда я первый раз узнал об этом методе от random1st, подумал, что метод скорее всего дает лишь незначительное улучшение по сравнению с фиксированным ядром, ведь количество задействованных пикселей такое же. Но результат сильно превзошел мои ожидания.


Секрет в том, чтобы брать для обработки не точки, скучковавшиеся по 4 штуки, как при фиксированном ядре, а использовать равномерную сетку в 2 раза большего разрешения, чем должно получиться в итоге. И из неё уже интерполировать конечное изображение.



Точки, которые будут использоваться при уменьшении до 20?13.


Как видите, все еще берется довольно мало точек исходного изображения. Но из-за того, что они распределены равномерно, они более репрезентативные. А из-за того, что их ровно в 4 раза больше, они все вносят одинаковый вклад в конечное изображение.


А теперь самое интересное: для использования этого метода не нужно ничего программировать! Все что нужно у вас уже есть. Первым шагом можно сделать равномерную сетку пикселей в 2 раза большего разрешения методом «ближайшего соседа», а на втором шаге сжать её в 2 раза хоть фиксированным фильтром, хоть свертками, хоть бокс-фильтром (смотря что есть в вашей библиотеке). Единственное, для сверток я бы посоветовал брать фильтр Хэмминга или бикубический, но не билинейный.


>>> im = Image.open('space.jpeg'); im.load(); im.size
(4928, 3280)

# Пример с не адаптивным ядром
>>> %time im.resize((512, 340), Image.NEAREST)            .transform((256, 170), Image.AFFINE,
                       (2, 0, 0, 0, 2, 0), Image.BILINEAR)
Wall time: 3.59 ms

# Пример со свертками и фильтром Хэмминга
>>> %time im.resize((512, 340), Image.NEAREST)            .resize((256, 170), Image.HAMMING)
Wall time: 2.42 ms

# Пример со свертками и бикубическим фильтром
>>> %time im.resize((512, 340), Image.NEAREST)            .resize((256, 170), Image.BICUBIC)
Wall time: 3.53 ms

# Пример с бокс-фильтром
# Результат будет немного отличаться, потому что в OpenCV
# есть ошибка с точностью работы INTER_NEAREST
# см. https://github.com/opencv/opencv/issues/9096
>>> import cv2
>>> im = cv2.imread('space.jpeg')
>>> %time cv2.resize(cv2.resize(im, (512, 340), interpolation=cv2.INTER_NEAREST),
                     (256, 170), interpolation=cv2.INTER_AREA)
Wall time: 0.81 ms

Дальнейшее развитие идеи


Данное улучшение впечатляет, но можно не останавливаться на достигнутом. Кто сказал, что для построения нужно использоваться именно в 2 раза большее изображение? Почему бы не взять в 3 раза или 4 для лучшего качества. Правда невозможно будет использовать ресайз с фиксированным ядром для второго шага, потому что вылезут те же проблемы, от которых мы пытаемся избавиться. А вот свертки — пожалуйста. При этом время останется константным, просто константа будет побольше.



Ресайз из 4928?3280 в 256?170 используя 2x и 4x промежуточные изображения.


Возможно, на таком масштабе различия не сильно видны, но они достаточно сильные. Чтобы их заметить, посмотрите гифку с зумом:



Ну и время:


>>> im = Image.open('space.jpeg'); im.load(); im.size
(4928, 3280)

# Пример с 2x промежуточным изображением
>>> %time im.resize((512, 340), Image.NEAREST)            .resize((256, 170), Image.BICUBIC)
Wall time: 3.53 ms

# Пример с 3x промежуточным изображением
>>> %time im.resize((768, 510), Image.NEAREST)            .resize((256, 170), Image.BICUBIC)
Wall time: 6.27 ms

# Пример с 4x промежуточным изображением
>>> %time im.resize((1024, 680), Image.NEAREST)            .resize((256, 170), Image.BICUBIC)
Wall time: 9.23 ms

Как видно, вариант с 2x промежуточным изображением работает за время, примерно равное билинейному фильтру с фиксированным ядром, а вариант с 4x промежуточным изображением за время бикубического. Ну и вообще говоря, можно использовать не целое кол-во точек.


Как сделать правильный выбор


Возникает вопрос: если этот метод дает настолько лучшие результаты и работает со скоростью фиксированного ядра, зачем вообще использовать фиксированное ядро для уменьшения? У этого метода конечно есть область применимости — его лучше не использовать при уменьшении меньше чем в 2 раза. И это совпадает с границей применимости фиксированного ядра, которое лучше не использовать при уменьшении больше чем в 2 раза. Получается, комбинируя методы, возможно получить ресайз приемлемого качества за фиксированное время при любом масштабе.


Важное дополнение


В комментариях vintage верно указывает, что этот метод правильно называть суперсемплингом. Суперсемплинг часто используется в играх для устранения алиасинга. По сути игровая сцена — это изображение бесконечного разрешения, потому что мы могли бы отрисовать её в любом разрешении. Для суперсемплинга сцена рисуется в большем разрешении чем нужно и несколько соседних пикселей усредняются в один. То есть аналогия полная. Но это не отменяет факта, что такой метод очень редко применяется в ПО несмотря на его достоинства.


Примеры


И напоследок несколько примеров с другими изображениями. Слева направо:
1) фиксированное ядро, билинейный фильтр (то, что многие используют сейчас)
2) бикубические свертки в качестве эталона
3) суперсемплинг с 2x увеличением
4) суперсемплинг с 4x увеличением


Главное при просмотре помнить, что третье изображение генерируется ровно за такое же время, что и первое, а четвертое хоть и дольше в ?3 раза, но тоже за константное время и часто до 20 раз быстрее, чем второе.


Еще раз пример из статьи. Изображение 4928?3280 уменьшенное в 19,25 раз.


Эта же картинка, но на этот раз уменьшенная из 600?399, то есть в 2,34 раза.
Такое небольшое уменьшение более сложный случай для данного метода.


Изображение 2560?1600 уменьшенное в 10 раз.


Изображение 4000?2667 уменьшенное в 15,625 раз.


Изображение 2448?3264 уменьшенное в 9,5625 раз.


Изображение 2000?2000 уменьшенное в 7,8125 раз.

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


  1. napa3um
    26.10.2017 11:04

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


    1. homm Автор
      26.10.2017 11:29

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


      1. DjOnline
        26.10.2017 18:08

        В фотошопе до сих пор из коробки нет качественных Lanczos/Mitchell, только bicubic и nearest, поэтому чтобы добиться качественного рейсайза делали всё как в статье.


  1. kovserg
    26.10.2017 12:18

    Что-то я не понял «константное время относительно размера исходного изображения» — свёртки тоже работают за константное время относительно исходного изображения. Что у вас исходным изображением и вход и выход называется?


    1. homm Автор
      26.10.2017 12:37

      Свертки работают тем медленнее, чем больше размер исходного изображения. Смотрите второй листинг в статье:


      >>> from PIL import Image
      >>> im = Image.open('pineapple.jpeg'); im.load(); im.size
      (2560, 1600)
      >>> %time im.resize((256, 170), Image.BICUBIC)
      Wall time: 33.2 ms
      
      >>> im = Image.open('space.jpeg'); im.load(); im.size
      (4928, 3280)
      >>> %time im.resize((256, 170), Image.BICUBIC)
      Wall time: 130 ms


    1. AllexIn
      26.10.2017 17:48

      Вы на опечатку намекаете? Что надо не «исходного», а «целевого»?
      Опечатки вроде в личку принято писать.


      1. homm Автор
        26.10.2017 17:52

        Тут нет опечатки. Время константное относительно разрешения исходного изображения. От разрешения конечного изображения время линейное.


        1. AllexIn
          26.10.2017 17:55

          А разве не наоборот?
          Предположим конечное — 1х1 пиксель.
          Целевое 1 — 1000х1000. Взять из него один пиксель — X
          Целевое 2 — 2000х2000. Взять из него один пиксель — Х
          Константа.

          А вот относительно конечного линейное — чем болье пикселей надо взять, тем больше будет время. Но время увеличится линейно.


          1. homm Автор
            26.10.2017 17:57

            У вас в одном примере есть и «конечное» и «целевое» изображение. Я не понимаю откуда куда вы ресазите )


            1. AllexIn
              26.10.2017 18:18

              Опечатался. :))
              Иcходное 1 — 1000х1000. Взять из него один пиксель — X
              Иcходное 2 — 2000х2000. Взять из него один пиксель — Х


              1. homm Автор
                26.10.2017 18:35
                +1

                Так. Размер исходного изображения поменялся, сложность и время нет. Значит сложность относительно исходного размера — константа. Верно же? Или у меня где-то не так написано?


                1. AllexIn
                  26.10.2017 18:37
                  +2

                  Всё, я понял вашу формулировку. Спасибо. Извините за потраченное время.


  1. not_ice
    26.10.2017 12:31

    А если брать пиксели не из регулярной сетки, а случайным образом, с вкладом, обратно пропорциональным расстоянию до узлов регулярной сетки? Тогда можно будет брать любое число опорных точек, а не фиксированное (х2, х4) количество.


    1. homm Автор
      26.10.2017 12:50

      Вот тут есть несколько вариантов, как выбирать точки: https://en.wikipedia.org/wiki/Supersampling#Supersampling_patterns


      Случайный мне не нравится, потому что на саму рандомизацию уйдет много ресурсов. А больше всего нравится RGSS. Там смысл в том, что точки распределены более-менее равномерно, но ни одна пара не лежит на горизонтальной или вертикальной прямой, что дает более репрезентативную выборку для реальных изображений, где вертикальные и горизонтальные линии встречаются часто. Но любой алгоритм кроме равномерной сеточки придется программировать, а тут смысл именно в том, что программировать ничего не надо.


  1. aamonster
    26.10.2017 12:53
    +1

    Мда… "фильтр с фиксированным ядром" — надо же… Остались ещё разработчики графического софта, которые не знают, что при уменьшении изображений тупая интерполяция не работает? Казалось бы, 20 лет назад все знали про муар и прочие проблемы...


    Кстати, описанный вами метод (который можно описать гораздо короче: по ближайшим соседям уменьшаем в целое количество раз, а потом интерполируем) на классическом тестовом изображении в виде "шахматной доски" тоже даст интересные эффекты :-) (вполне понятные из описания через разбиение на 2 этапа). Ну а из реальных, не синтетических картинок — проблемы вызовут тонкие линии на изображении (станут прерывистыми). Так что если готовы сколько-то просадить производительность — лучше всё же не жадничать и задействовать mip-maps. Если их создать сразу при загрузке/распаковке изображения в память — просадки по производительности практически не будет.


    1. homm Автор
      26.10.2017 13:18

      Остались ещё разработчики графического софта, которые не знают, что при уменьшении изображений тупая интерполяция не работает?

      В статье есть не полный список ПО: OpenCV, канва в браузере. Еще я много раз видел, как браузеры используют его для обычных картинок для быстрой черновой отрисовки при изминении размеров, а уже через секунду отрисовывают на чистовую с помощью сверток. Ну а какой-нибудь ИЕ11 использует фиксированное ядро всегда.


      который можно описать гораздо короче

      Вы ошиблись в описании. Уменьшаем не в целое количество раз, а до размера в целое количество раз больше конечного.


      проблемы вызовут тонкие линии на изображении (станут прерывистыми)

      Это можно посмотреть на фотографии схемы метро, там действительно некоторые линии прерываются при 2x.


      1. aamonster
        26.10.2017 14:31

        Насчёт OpenCV — не совсем верно. Для него в доке английским по белому написано: "To shrink an image, it will generally look best with CV_INTER_AREA interpolation". Т.е. использование билинейной/бикубической интерполяции для уменьшения изображения — это не ошибка в библиотеке, а misuse (некорректное использование). Ну, как если из ящика с инструментами вынуть молоток и забить им шуруп.


        А в описании действительно ошибся, спасибо (рефлекторно описал последовательность, которая лучше будет работать при масштабе близком к 1x-2x)


        1. homm Автор
          26.10.2017 18:09
          +1

          Для него в доке

          Это PHP-стайл: когда делается полурабочая функция, а в документации пишется как именно она сломана. Не хочу никого обидеть, но почему-то в PHP такое встречается очень часто.


          1. aamonster
            27.10.2017 09:49

            Не скажу за PHP, но, учитывая область применения OpenCV, универсальная функция масштабирования, работающая для разных масштабов по разным алгоритмам (и автоматически выбирающая алгоритм), кажется не оптимальным решением. Хотя иметь помимо констант CV_INTER_AREA/CV_INTER_CUBIC/CV_INTER_LINEAR/CV_INTER_NN какую-нибудь CV_INTER_HIGH_QUALITY, которая бы автоматически переключалась между CV_INTER_CUBIC и CV_INTER_AREA — выглядит разумным. Ну, мало ли — кто-то на OpenCV свой Фотошоп писать будет =)


  1. vintage
    26.10.2017 13:30

    Что-то я не понял чем ваш алгоритм отличается от суперсемплинга?


    1. homm Автор
      26.10.2017 14:24

      Я ни разу не встречал, чтобы описанный метод назывался суперсемлпнгом (даже не встречал, чтобы он в принципе был где-то реализован). Но если подумать, что делает видеокарта при суперсемплинге? Она из бесконечного кол-ва точек (отренидирить сцену мы можем в бесконечном разрешении) выбирает 4 точки для каждого конечного пикселя, так что да, в каком-то смысле это и есть суперсемплинг.


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


      1. vintage
        26.10.2017 16:11

        Не в каком-то смысле, а это он самый и есть: https://en.wikipedia.org/wiki/Supersampling


        1. homm Автор
          26.10.2017 17:50

          Спасибо, добавил в статью.


  1. Aingis
    26.10.2017 16:39

    Неплохо, но на крышах хорошо видны недостатки:



    1. AllexIn
      26.10.2017 17:50

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

      На выходе у нас выбор из:
      1 — очень дешево/очень плохо
      2 — очень дорого/отлично
      3 — дешево/хорошо


      1. Aingis
        27.10.2017 13:20

        Мне казалось очевидным, что такая технология более пригодна там, где нужна высокая скорость, и достаточно удовлетворительного качества. Можно вообще подобрать такой контр-пример, где техника завалится. С другой стороны, техника может быть удобна в критичных к скорости вычисления местах, когда можно подготовить графику заранее, например, для игровых текстур.


  1. Videoman
    26.10.2017 22:21

    По сути, ваш метод это «ближайший сосед», но с подавлением шума в заданное количество раз. Ведь от куда берется дребезг в оригинальном методе — это высокочастотный шум. Вы делаете 2, 3, 4 — несколько «ближайших соседей» и усредняете это шум в заданное количество раз, т.е. эмитируете длинную выдержку фотоаппарата при больших (шумящих) ISO.


  1. arcman
    27.10.2017 02:33

    А чем будет плох метод, когда все точки исходного изображения усредняются условно говоря за один проход?
    Мне нужно было уменьшить изображение в 80 раз по одной оси и в 60 по другой. Я шел сначала по линиям и усреднял 80 точек в одну, формируя при этом новую матрицу, а потом проходил новую матрицу по столбцам усредняя в 60 раз. Поскольку все происходило линейно, было быстро.
    Все входные изображения единой размерности и кратно уменьшаются.


    1. homm Автор
      27.10.2017 02:36

      А чем будет плох метод, когда все точки исходного изображения усредняются условно говоря за один проход?

      Тем, что это метод ресайза не за константное время относительно размера исходного изображения.


  1. SlavikF
    27.10.2017 05:28

    Раз уж тут такая тема — кто-нибудь может посоветовать, как правильно уменьшать картинки в PHP?
    Я использовал imagecopyresized и результат похож на первую картинку в этой статье.


    1. bolk
      27.10.2017 07:39

      Документация и подскажет. Прямо в статье по этой функции есть строка, под уменьшеной картинкой:

      The image will be output at half size, though better quality could be obtained using imagecopyresampled().


  1. arquolo
    27.10.2017 11:03

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


  1. Videoman
    27.10.2017 18:28

    Извиняюсь, промахнулся. Для @arquolo — нет, для интегрального преобразования нужно один раз пройтись по всем точкам исходного изображения, а потом уже можно будет быстро получать уменьшенные копии. Автор же предлагает усовершенствованный «nearest nebour», но с подавлением шума в заданное количество раз. Метод, конечно, имеет свои ограничения, но для превьюшек, по-моему, не плохо.


    1. kovserg
      27.10.2017 22:56

      И в чем беда. Для получения изображения его же надо загрузить (подготовить). Так что интегральное преобразование в замер быстродействия не попадёт. Если нужно качественное преобразование то еще и цветовое представление сменить и другие подготовительные операции. А уже потом масштабировать в разных вариантах за константное время.
      Если исходное изображение закодировать на основе wevalet-ов как это сделано в djvu. То можно практически «бесплатно» получать разные масштабы.


      1. Videoman
        28.10.2017 13:44

        В более частных случаях скорость того или иного метода может быть сколь угодно высокой.

        Так что интегральное преобразование в замер быстродействия не попадёт.
        Если так рассуждать, то мы просто не сможем ничего сравнить. Очевидно, сравнение методов уменьшения изображений нужно проводить в одних и тех же начальных условиях. Обычно это монохромная битовая карта яркостей.
        Если нужно качественное преобразование то еще и цветовое представление сменить и другие подготовительные операции. А уже потом масштабировать в разных вариантах за константное время.
        Это уже совсем другая задача. В статье рассматривается метод как сделать быстро (nearest nebour), но при этом уменьшить высокочастотный шум. О качестве речи не идет. И как не странно, есть куча прикладных задач где нужен именно такой подход.


  1. EnGNJ2Ib
    29.10.2017 12:13

    Вот только почему-то забыли, что открытие (декодирование) исходной картинки будет происходить не за константное время, а за пропорциональное её размеру.
    Так что общее время работы алгоритма будет не костантным, а линейным от количества пикселей (асимптотически).
    А вот реальные замеры (желательно используя библиотечные методы масштабирования, напианные на C), хотелось бы увидеть для различных размеров картинок в виде графиков.


    1. homm Автор
      29.10.2017 12:43

      А почему я должен «помнить» о декодированиии изображения, если речь о ресайзе?


      1. EnGNJ2Ib
        29.10.2017 14:10

        Потому что исходные картинки будут в .jpg или .png.
        Нет разницы, будет ресайз занимать 3 мс или 20 мс, если декодирование картинки будет занимать 300 мс.


        1. dom1n1k
          29.10.2017 14:37

          Автор решает конкретную задачу. Переход в надсистему в её рамках не обсуждается.


        1. homm Автор
          29.10.2017 14:51

          Потому что исходные картинки будут в .jpg или .png.

          Я не понял откуда взялось ваше «будут». Исходные картинки могут быть в .jpg или .png. Могут быть загружены час назад, могут быть сгенерированы на лету, может быть все что угодно. И все это никак не связано с ресайзом.


          1. EnGNJ2Ib
            30.10.2017 01:15

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


            1. homm Автор
              30.10.2017 12:47

              Основное применение данной техники — это генерация thumbnails.

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


  1. EnGNJ2Ib
    30.10.2017 01:16

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


  1. EnGNJ2Ib
    30.10.2017 01:25

    Кстати, для уменьшения муара (как на крышах) можно смещать точки внутри их «квадратиков» случайным образом. Имеет смысл если генератор ПСЧ быстрый, а выборка медленная.


    1. homm Автор
      30.10.2017 13:05

      см https://habrahabr.ru/post/340966/#comment_10492298


      Я кстати попробовал RGSS (сэмулировал как мог), результат мне не понравился:


      # coding: utf-8
      from __future__ import division
      
      import sys
      from PIL import Image
      
      im = Image.open(sys.argv[1])
      
      w = 256
      h = int(w / im.width * im.height + 0.5)
      s = im.width / w
      
      im0 = im.transform((w, h), Image.AFFINE,
                         (im.width / w, 0, -s*.35, 0, im.height / h, -s*.15),
                         Image.NEAREST)
      im1 = im.transform((w, h), Image.AFFINE,
                         (im.width / w, 0,  s*.35, 0, im.height / h,  s*.15),
                         Image.NEAREST)
      im2 = im.transform((w, h), Image.AFFINE,
                         (im.width / w, 0,  s*.15, 0, im.height / h, -s*.35),
                         Image.NEAREST)
      im3 = im.transform((w, h), Image.AFFINE,
                         (im.width / w, 0, -s*.15, 0, im.height / h,  s*.35),
                         Image.NEAREST)
      
      Image.blend(
          Image.blend(im0, im1, 0.5),
          Image.blend(im2, im3, 0.5),
          0.5
      ).save('_out.RGSS.png')