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

    Поворот растрового изображения на углы, кратные 90°, относительно геометрического центра изображения – задача тривиальная и решается без потери качества простым преобразованием координат каждого пикселя.

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

    Ниже мы рассмотрим алгоритм прецизионного поворота растрового изображения на произвольный угол относительно произвольного центра с минимальными потерями.

    Выражаю благодарность Харченко Владиславу Владимировичу за оказанную помощь.

    Алгоритм


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

    В качестве модели пикселя исходного изображения мы будем использовать квадрат со стороной = 1, с такими обозначениями углов:
    i1 — самый правый угол;
    i2 — самый нижний угол;
    i3 — самый левый угол;
    i4 — самый верхний угол.

    Моделью итогового изображения будет сетка из параллельных горизонтальных и вертикальных линий с расстоянием между линиями = 1.

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

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

    Поворот растрового изображения можно разделить на две части:
    1. Поворот квадрата каждого пикселя исходного изображения относительно центра этого квадрата на заданный угол.
    2. Смещение центра квадрата пикселя в соответствии с углом поворота изображения относительно центра поворота изображения таким образом, чтобы квадрат занял своё итоговое положение на сетке итогового изображения.
    При этом сетка итогового изображения рассекает квадрат каждого пикселя исходного изображения на «осколки» в количестве 4, 5 или 6 штук.

    Чтобы систематизировать разнообразие получающихся вариантов мне пришлось составить таксономию всевозможных пересечений квадрата пикселя исходного изображения с сеткой итогового изображения. Существенно разных вариантов оказалось всего 23:


    Условные обозначения здесь следующие:
    — цифры в ячейках обозначают номера углов квадрата пикселя, которые попали в данную ячейку сетки итогового изображения после поворота изображения;
    — зелёным цветом обозначены ячейки, в которые попали участки пикселя и гарантированно оставили там по «осколку»;
    — жёлтым цветом обозначены ячейки, в которые, в зависимости от условий, могут попасть (а могут и не попасть) «осколки» квадрата пикселя, образованные не углами квадрата, а сторонами квадрата.

    Для наглядности приведу одну из возможных вариаций варианта №3:

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

    Формула 1

    Эта формула не используется для расчёта окончательной площади «осколка», но её удобно использовать для быстрого расчёта вспомогательных промежуточных площадей, поскольку нам заведомо известно, что площадь всего пикселя = 1.
    В качестве входных переменных во всех формулах используются высоты, опущенные из углов квадрата на сетку итогового изображения, по той простой причине, что расчёт этих высот сводится к мгновенному выделению дробной части числового значения координаты соответствующего угла квадрата пикселя.

    Формула 2


    Данная формула используется только в вариантах 1 и 2.

    Формула 3

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

    Формула 4

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

    С учётом всего вышесказанного, в общем виде алгоритм будет выглядеть так:
    1. Загружаем в память ЭВМ исходное изображение.
    2. Рассчитываем размеры итогового изображения в пикселях.
    3. Создаём промежуточный двумерный массив, каждый элемент которого содержит 3 составляющие цвета RGB в формате числа с плавающей запятой. Размеры массива равны размерам итогового изображения.
    4. Последовательно перебираем все пиксели исходного изображения; поворачиваем каждый из них на заданный угол и располагаем на сетке итогового изображения, рассчитав 4 координаты углов квадрата пикселя; классифицируем пиксель по 23 вариантам и считаем площади «осколков»; добавляем в соответствующие элементы промежуточного массива цвета полученных «осколков» пропорционально площади этих «осколков».
    5. После обработки всех пикселей исходного изображения округляем значения RGB в промежуточном массиве до целого значения для каждого элемента и создаём на базе этих целых значений итоговое изображение в формате BMP.

    Программа


    На основании приведённого алгоритма была написана программа для Windows. Исходные коды на Object Pascal и скомпилированный исполняемый файл можно скачать здесь.

    Интерфейс программы.
    По нажатию на кнопку «Open...» открывается диалог выбора BMP-файла. Поддерживаются битмапы только с 24-битной палитрой. Открытое изображение отображается в окне. В заголовке окна выводится полный путь к файлу и размеры изображения.

    В поле «Angle» задаётся угол поворота в градусах – любое положительное число.
    В качестве десятичного разделителя при вводе дробных чисел может быть использована как точка, так и запятая.

    Радиокнопками «CW» и «CCW» задаётся направление вращения: «по часовой стрелке» и «против часовой стрелки», соответственно.

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

    В полях «Centre X» и «Centre Y» задаются координаты центра поворота. При этом следует учитывать, что начало координат находится в левом верхнем углу изображения и Y увеличивается вниз. По умолчанию центр поворота устанавливается в геометрическом центре загруженного изображения.

    По нажатию на кнопку «Rotate» либо по нажатию на клавишу Enter изображение поворачивается на заданный угол относительно заданного центра поворота и отображается в окне. Поворот изображения на углы, кратные 90°, реализован по упрощённой схеме, путём простого преобразования координат пикселей исходного изображения, при этом значения «Centre X» и «Centre Y» игнорируются.
    Время работы алгоритма в секундах отображается под кнопкой «Rotate».

    Через кнопку «Save…» повёрнутое изображение можно сохранить в BMP-файл.

    Если итоговое изображение не помещается в окне – оно подгоняется к границам окна API-функцией StretchBlt – поэтому оценить реальное качество больших картинок можно только по сохранённому BMP-файлу.
    Для поворота изображения на другой угол его не нужно загружать повторно — поворачивается изображение из выбранного файла, а не отображённое в данный момент в окне.

    Изображение с размерами 1024 х 768 на машине с четырёхядерным процессором 2,67 ГГц поворачивается данной программой на произвольный угол, в среднем, приблизительно, за 0,5 секунды. Изображение с размерами 4000 х 4000 — примерно за 10 секунд. Время работы алгоритма для разных углов может отличаться в связи с тем, что изображение при разных углах дробится на разное количество «осколков», на вычисление площадей которых суммарно тратится, соответственно, разное время.

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

    Результаты


    Проведём сравнительный анализ работы прецизионного алгоритма и алгоритма поворота изображений, реализованного в программе Photoshop.

    Тест 1

    Для первого теста я взял очень простое изображение — горизонтальную линию чёрного цвета толщиной 1 пиксель и длиной 10 пикселей, смещённую относительно центра белого квадрата с размерами 100 х 100 пикселей:


    После чего я повернул данное изображение относительно точки с координатами (0, 0) на 3° по часовой стрелке. Точка (0, 0) выбрана потому, что, судя по моим экспериментам, Photoshop поворачивает изображение относительно именно этой точки. Вот сравнительный результат (увеличено в 24 раза):


    Прецизионный алгоритм





    Photoshop 7.0.1





    Photoshop CS6 (64 Bit)
    Алгоритм Photoshop даёт более контрастную картинку, прецизионный алгоритм несколько «размывает» изображение. Но в целом, при визуальной оценке, результат получается почти одинаковым. Попутно отметим, что алгоритм поворота, реализованный в Photoshop, не претерпел за 10 лет существенных изменений.

    Тест 2

    Для второго теста я выбрал тюльпан из стандартного дистрибутива Win7:



    После поворота данного изображения на 5° по часовой стрелке относительно геометрического центра я суммировал цвет всех пикселей в разрезе каналов RGB. Вот результат для прецизионного алгоритма и алгоритма Photoshop:
    Оригинал
    Прецизионный поворот
    (до округления)
    Прецизионный поворот
    (после округления)
    Photoshop CS6
    R = 33381406
    G = 27933900
    B = 11239213
    R = 33381406,0000004 (~0)
    G = 27933899,9999997 (~0)
    B = 11239212,9999999 (~0)
    R = 33382786 (1380)
    G = 27933920 (20)
    B = 11238086 (-1127)
    R = 33387726 (6320)
    G = 27950823 (16923)
    B = 11245937 (6724)
    Числа в скобках показывают абсолютное отклонение данного показателя от оригинала.
    Цвет изображения после прецизионного поворота и до округления практически не поменялся — чего и следовало ожидать.
    Самое большое отклонение, в данном конкретном случае, мы обнаруживаем по каналу G для алгоритма Photoshop. В процентном отношении это отклонение составляет всего 0,06%, поэтому «на глаз» оно не заметно, но из соображений перфекционизма результат у Photoshop получается хуже, чем у прецизионного алгоритма.
    Важно отметить, что округление цвета каждого пикселя в прецизионном алгоритме до целочисленного значения, необходимого для формата BMP, необратимо уничтожает часть информации о цвете.

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


    повёрнутого на 5° по часовой стрелке, соответственно, Photoshop'ом:


    и прецизионным алгоритмом:


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

    Выводы


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

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

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

    UPD: Для практического использования написал программу, реализующую упрощённый алгоритм, в котором для каждого пикселя итогового изображения все необходимые кусочки пикселей исходного изображения рассчитываются последовательно и округление цвета происходит немедленно. Только после этого рассчитывается следующий пиксель итогового изображения. При этом к отдельному пикселю исходного изображения программа обращается несколько раз. Время расчётов, таким образом, увеличилось, в среднем, в 1,7 раз, но память в этой версии алгоритма расходуется только на хранение битмапов, что позволяет работать с изображениями больших размеров. Скачать программу и исходники можно здесь.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 40

      +15
      почему в профессиональных графических редакторах нет опциипочему в профессиональных графических редакторах нет опции ...
      Есть: Transform → Rotate → (вводите угол) → Interpolation: (выбираете нужный из 6ти типов)
        0
        Это в какой программе?
          +3
          Adobe Photoshop.
            +2
            В CS6 добавили в сам free transform, до этого настраивалось только глобально в General.
          0
          А разве не достаточно простого увеличения разрешения, потом применения матрицы поворота для каждого субпикселя и последующего антиалиасинга/фильрации?
            –12
            3 преобразования => 3-х кратная потеря качества изображения.
              +14
              Единственное преобразование с потерями- это поворот. Когда вы увеличиваете разрешение, скажем в 2 или 4 раза — все пиксели делятся на соответственно 4 или 16 субпикселей. Это преобразование без потерь.

              Теперь, если не делать поворот, и снова уменьшить изображение, применив хоть линейную интерполяцию — изображение вернется в исходное состояние.

              Все потери именно из-за поворота.
                0
                Если я верно понимаю, то в этом случае многое будет зависеть от способа интерполяции пикселей при смене разрешений.
            +15
            Кардинально неверный подход, по-моему.
            Невозможно ставить задачу «прецизионного» преобразования для растрового изображения, которое само по себе уже приближение. Фактически, лучше всего было бы сделать векторизацию, а затем её растрирование после поворота.
            Photoshop'овский метод в данном случае честнее — сделайте 5 последовательных поворотов направо, а затем — налево. Ваш алгоритм, скорее всего, очень сильно «размажет» изображение, а «профессиональные» — скорее всего сохранят хотя бы контуры.
              +14
              А при суммировании цвета пикселя вы учитываете необходимость проведения гамма-коррекции? Так как RGB — это нелинейная шкала, то нельзя просто суммировать значения цвета в RGB: так получится неверный цвет пикселя.
                +1
                Впервые о таком слышу. Можно подробнее?
                • UFO just landed and posted this here
                    0
                    Интенсивность цвета:
                    (R * 77 + G * 150 + B * 28) >> 8) & 255
                    

                    Оно?
                      0
                      Нет, это

                      Csrgb = Clinear <= 0.0031308? 12.92*Clinear: (1+a)*Clinear1/2.4-a; where a=0.055.

                      The forward transformation
                    0
                    Вам нужно преобразовать изображения к гамме 1.0, сделать необходимые операции, вернуть исходную гамму.
                    Будьте осторожны с sRGB и ему подобными системами, там гамма «плавающая», подробнее тут.
                  +2
                  У меня, как правило, не стояло задачи аккуратно крутить, а стояла задача — быстро крутить. Поэтому юзал всю жизнь что-то типа такого:

                  // buffer = source buffer RGB565[320x200]
                  // scn   = target buffer RGB565[320x200]
                  void FastRotate(float scale, //the scaling factor
                                  float ang) //the rotation angle
                  {
                      long xscale=sin(ang)*65536L/scale;
                      long yscale=cos(ang)*65536L/scale;
                  
                      long xc=(screen_dx/2)*65536L - ((screen_dx/2)*yscale+(screen_dy/2)*xscale);
                      long yc=(screen_dy/2)*65536L - ((screen_dy/2)*yscale-(screen_dx/2)*xscale);
                  
                      WORD far *scn=shadow_buffer;
                      for (int y=0;y<screen_dy;y++) //normally from 0 to 199
                      {
                          long xlong=xc,ylong=yc; //init x/ylong to topleft of square
                          for (int x=0;x<screen_dx;) //normally from 0 to 319
                          {
                              register int tempx=xlong>>16;
                              register int tempy=ylong>>16;
                  
                              if( (tempx<0) || (tempx>=screen_dx) || (tempy<0) || (tempy>=screen_dy) )
                                  scn[x++]=0;
                              else
                  		scn[x++]= buffer[tempx+y320[tempy]];
                  			
                              xlong+=yscale;ylong-=xscale;
                          }
                          scn+=screen_dx; // WARNING
                          xc+=xscale;yc+=yscale;
                      }
                  }
                  


                  Вроде бы брал алгоритм из demo.design.faq, если не ошибаюсь.
                    +14
                    Хороший подход, но теперь Вам ещё предстоит поработать над теорией цвета (и света). Дело в том, что коэффициенты смешения нельзя брать пропорциональными просто площади «осколков». Поясню на примере. Если белый пиксел на черном фоне сдвинуть на 50% в сторону, то результирующие два пиксела не будут содержать цвета (128,128,128) ни (127,127,127) — т.к. количество света, излучаемые парой таких серых пикселов, будет отличаться от количества света, излучаемое одним белым пикселом (из-за гаммы), хотя при прецизионном смещении очевидно, что яркость картинки должна была бы остаться полностью прежней.
                      +5
                      А почему бы на сделать тест, при котором изображение поворачивается на небольшой угол за один шаг и обратно за два шага и сравнить изображения?
                        +10
                        Идея интересная.
                        Покажите, пожалуйста, сравнение результата поворота 365 раз на 1° вашим алгоритмом и фотошопом.
                        Желательно картинку поконтрастнее.
                          +8
                          А почему 365, а не 360?
                            +30
                            Потому что в году 365 дней %)
                              0
                              360 + 5, чтобы сравнить с поворотом просто на 5.
                              Предполагаю.
                                +4
                                360 конечно, это я ошибся.
                              +1
                              Попутно отметим, что алгоритм поворота, реализованный в Photoshop, не претерпел за 10 лет каких-либо ощутимых изменений.

                              Наверное потому, что он является интеллектуальной собственностью компании и запатентован всеми мыслимыми и немыслимыми патентами. :)

                              Но некоторые изменения все же были внесены. Если я не ошибаюсь, то начиная с Creative Suite, поворот на углы кратные 90 градусам проводятся без потери качества.
                                +3
                                Ваш алгоритм, автор, эквивалентен следующему. Применим для увеличения разрешения исходного изображения Билинейную интерполяцию и повысим разрешение, скажем, в 100 раз. После этого выполним поворот простым алгоритмом «ближайшего соседа», который для каждого повернутого пикселя в результирующем изображени, имеющем такое же разрешение, как исходное, находит координаты пикселя в обработанном изображении, разрешение которого было многократно увеличено. Так как эти координаты, вообще говоря, будут не совпадать с координатами центров пикселей исходного изображения, а будут ближе к центрам каких-нибудь интерполированных пикселей — то в результирующем изображении и окажутся значения, промежуточные между соседними пикселями исходного изображения.

                                Неважно, что в реализации это как бы выглядит сложнее, но это позволяет по-другому взглянуть на задачу математически. Теперь можно подумать об использовании других видов интерполяции, кроме билинейной. Например, взять бикубическую интерполяцию, которая используется в настоящее время как один из прецизионных методов изменения разрешения изображения. Или можно провести найквист-интерполяцию исходного изображения. В общем, вариантов очень много, хотя ни один из них не является идеальным.
                                  0
                                  Вы не правы. Очевидно, что предложенный алгоритм не содержит в себе (ни явно, ни неявно) билинейной интерполяции. Если говорить о формальном аналоге, то тут используется кусочно постоянная интерполяция исходного изображения, т.е. фактически точечные пикселы заменяются квадратными пикселами конечного размера (что явно написано в статье) и потом производится интегрирование полученной кусочно-постоянной функции-интерполянта по квадратам, соответствующим повернутым пикселам.
                                    –1
                                    Это вы не правы. Данный алгоритм содержит билинейную интерполяцию неявно. К ней приводит интегрирование кусочно-постоянной функции. Ведь интеграл от константы является линейной функцией!

                                    Представьте себе ту же ситуацию в одномерном случае. Возьмем таблично заданную функцию y[i] в равноотстоящих точках x[i]: x[i+1]-x[i]=d=const при любом i. Проинтерполируем ее кусочно-постоянно с центрами постоянных кусков, расположенных в x[i]. Получим: y(x)=y[i] при x[i]-d/2<=x<x[i]+d/2.

                                    Возьмем интервал (x1, x2), равный расстоянию между значениями в таблице: x2-x1=d. Это будет аналог повернутых пикселей. Проинтегрируем y(x) на этом интервале. Интеграл разобьется на два слагаемых, так как, в общем случае, в интервал (x1,x2) попадет два соседних интервала, на которых y(x) постоянна. Найдем i такое, что x[i]-d/2 <= x1< x[i]+d/2. Тогда значение интеграла будет равно:
                                    I = y[i]*(x[i]+d/2-x1) + y[i+1]*(x2 — x[i] — d/2)

                                    Если задать интервал координатой его центра x3, так что x1=x3-d/2, x2=x3+d/2 — то интеграл будет равен:

                                    I(x3) = y[i]*(x[i] + d/2 — x3 + d/2) + y[i+1]*(x3 + d/2 — x[i] — d/2)
                                    Упростим выражение:
                                    I(x3) = y[i]*(x[i]+d-x3) + y[i+1]*(x3-x[i])

                                    Но значение этого интеграла есть линейная интерполяция исходной функции, с поправкой лишь на постоянный множитель d. Если I(x3) разделить на d — то получится линейная интерполяция от исходной функции. В этом несложно убедиться, если подставить x3=x[i]. В этом случае второе слагаемое обращается в нуль, и значение интеграла, деленное на d, будет равно y[i], то есть интерполяция проходит через y[i], как и требуется. С другой стороны, если устремить x3 к x[i]+d (сделать равным нельзя, т.к. при этом требуемое значение i изменится) — то в пределе обратится в нуль первое слагаемое, а от второго, деленного на d, останется y[i+1]. То есть интерполяция проходит и через y[i+1]. Значение I является линейной функцией относительно x3. А через две точки (x[i],y[i]) и (x[i+1],y[i+1]) можно провести одну и только одну прямую, поэтому приведенный выше способ приводит к линейной интерполяции.

                                    Все вышесказанное обобщается для двумерного случая и приводит к билинейной интерполяции.
                                  +4
                                  Человеческий глаз лучше воспринимает разницу в яркости (контраст) чем цвет. Именно поэтому качественные алгоритмы над графикой подгоняются под наше восприятие (предпочтение контрасту, а не цвету), а не математическую точность. Алгоритм в фотошопе дает более качественное изображение на выходе, т.к. не замыливает картинку. Это гораздо сложней, чем вы подумали.
                                    +1
                                    Теперь бы к идеальному повороту добавить еще и идеальное повышение резкости
                                      +17
                                      Несмотря на критику метода в комментариях, статья мне понравилась. Побольше бы таких на хабре.
                                        –1
                                        В чем смысл, если картинка выглядит хуже, а потери все равно есть?)
                                          +5
                                          Неоправданно сложный алгоритм, в плане анализа различных геометрических case'ов наложения.

                                          Намного проще (и с той же точностью) можно было бы сделать так: берем математически воображаемый градиент между центрами каждых соседних пикселей (2-мерный). Таким образом получаем непрерывную функцию по каждой компоненте изображения (r, g, b) = f(x, y), где x, y — любые вещественные. При повороте — соответственно пересчитываем новые значения для центров пикселей (для повернутого растра), и через ранее определенную функцию для каждого получаем цвет.

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

                                          p.s. При повороте на 0* предложенный алгоритм вернет в точности то же изображение, т.к. центры поподут на «целочисленные» части функции, для которых цвет определен из исходных пикселей.
                                            +7
                                            Натыкался на вот такой подход:



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

                                                У вас исходное изображение крепко шарплено. Возмите фотографию без обработки для примера.
                                                  0
                                                  Ореолы искаженного цвета будут почти всегда — цвет объекта при поворотах будет смешиваться с цветом фона. Чтобы их избежать, пришлось бы брать пиксель исходного изображения, площадь пересечения которого с нашим максимальна, и использовать его цвет (компоненту H или HS в пространстве HSL или компоненты AB в LAB), а яркость получать усреднением. Но не уверен, выйдет ли из этого что-нибудь хорошее.
                                                  0
                                                  Благодарю всех за отзывы! Они были действительно ценными и нтересными.
                                                  С вашего позволения, отвечу в одном сообщении на ключевое. Материал статьи я готовил месяц, потому и с комментарием задержался.

                                                  1. На практике редко возникает потребность в том, чтобы вращать одно и то же изображение много раз. В таких условиях применение любого алгоритма приведёт к накоплению ошибок и усилению искажений. На практике возникает задача повернуть одно изображение один раз. И здесь вполне уместно поставить вопрос, что ценнее: контрастные детали или информация о цвете? Фотошоп даёт ответ — контрастные детали важнее, цветом можно пожертвовать. Предложенный мной алгоритм отвечает на вопрос иначе — цвет важнее, контрастом можно пожертвовать. Я отнюдь не настаиваю на том, что мой алгоритм лучше всех алгоритмов. Он просто предоставляет выбор метода под конкретную задачу.

                                                  Вместе с тем, я всё же провёл эксперимент, подобный тому, который предложили Krovosos и archim. В итоге изображение, повёрнутое прецизионным алгоритмом, действительно, получилось более размытым, чем у фотошопа, но и ошибка в цвете у фотошопа получилась большей. Например, по каналу G для изображения тюльпана из теста №2 ошибка после 32 последовательных поворотов на 5° у Фотошопа достигла 0,4%, а у моего алгоритма оказалась на уровне 0,005% т.е. на 2 порядка лучше.
                                                  Тут же следует заметить, что размытость изображения — это не приговор. Можно подумать над эффективным алгоритмом шарпизации, специфичными для данного алгоритма поворота. Вопрос не так прост и очевиден, тут действительно есть над чем подумать.

                                                  2. Касательно гамма-коррекции я предлагаю провести такой мысленный эксперимент. Представим, что тестовое изображение получено непосредственно с идеальной фотоматрицы. Теперь представим, что мы повернули фотоматрицу на небольшой угол и снова сделали снимок таким образом, чтобы элемент изображения, который снимался одним фотоэлементом фотоматрицы оказался разделённым между двумя фотоэлементами. Совершенно понятно, что в таком случае каждый фотоэлемент получит в 2 раза меньше света, поэтому и в прецизионном алгоритме яркость этого пикселя должна разделиться. С другой стороны, если RGB шкала действительно нелинейная (что для меня новость), коррекцию итогового цвета произвести надо обязательно. Над этим нюансом я тоже обещаю подумать.

                                                  Ещё раз всем спасибо за комментарии.
                                                    0
                                                    >> Представим, что тестовое изображение получено непосредственно с идеальной фотоматрицы.
                                                    Вот именно, а теперь вернёмся в реальность и поймём что тестовое изображение получено с фотоматрицы очень далёкой от идеала, где пиксель нередко состоит из элементов RGGB (и это не опечатка). И соответственно признаем что гамма-коррекцию не то чтобы не всегда надо делать, а надо делать вообще всегда.
                                                    0
                                                    На досуге переработал и упростил структуру программы. Время расчётов при этом увеличилось примерно в 1,7 раз но память теперь расходуется только на хранение битмапов, что позволяет работать с изображениями любых размеров.
                                                    Скачать программу и исходники можно здесь: prosolver.kiev.ua/PreciseRotation2.zip

                                                    Only users with full accounts can post comments. Log in, please.