Аффинные преобразования на плоскости

Аффинные преобразования на плоскости

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

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

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

Хотя, как мне кажется, больше отпугивает само понятие матрицы. Давайте немного поботаним.

Поворот

Предположим, стоит задача повернуть прямоугольник на некоторый угол относительно его центра. Очевидно, надо рассчитать 4 угловые точки и построить по ним полигон.

Рис.1. Поворот точки Р1 на угол β

Имеем некий прямоугольник с вершинами в точках P1, P2, P3, P4. Рассмотрим точку P1(x,y). Она отстоит от оси абсцисс на угол α. Повернем ее на угол β. Очевидно, что вращение происходит по окружности с центром, находящимся в центре заданного прямоугольника O(x,y).

Рассчитаем координаты новой точки P1′( x′, y′).

Latex formula

Где R – радиус окружности на которой расположена точка P1, и равен (O, P1). Воспользуемся формулами сложения углов (1.1 и 2.1) из справочника:

Latex formulaLatex formula

или

Latex formulaLatex formula

Рис.2. Координаты точки P1 через угол α

Замечаем, что R × cos(α) это не что иное, как координата X точки P1, а R × sin(α) – координата Y. Таким образом, формулы расчета координат новой точки P1′ приобретают вид:

Latex formula

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

Latex formula

И мы только что получили матрицу поворота аффинного преобразования.

Рис.3. Поворот прямоугольника на угол β относительно его центра

В общем виде формулы можно записать как:

Latex formulaLatex formula

В матричном виде:

Latex formula

Где: M11, M12, M21, M22, Dx, Dy – коэффициенты, определяющие преобразование.

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

Таким образом, матрицей поворота будет следующая:

Latex formula

Немного кода:

Сдвиг

Еще одно интересное преобразование. Состоит из вертикального сдвига, когда меняется только координата Y, и горизонтального, когда меняется только X.

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

Снова рассматриваем прямоугольник, помня, что это на самом деле плоскость.

Рис.4. Плоскость с центром трансформации в левой нижней точке P3

Для наглядности деформация будет происходить относительно левой нижней точки P3.

Вначале деформируем следующим образом:

Рис.5. Вертикальный сдвиг

При вертикальном сдвиге координаты X не меняются. Изменяются координаты Y. В данном случае координата Y точки P1 изменилась на расстояние (P2,P2′).

Latex formula

Latex formula

Из чего получим формулу преобразования для Y:

Latex formula

Рис.6. Вертикальный + горизонтальный сдвиги

Теперь добавим горизонтальный сдвиг. Координата X точки P1 изменилась на расстояние (P4,P4′), которое рассчитывается аналогичным образом.

В итоге, формулы для трансформации сдвига выглядят следующим образом:

Latex formula

Или матрицей:

Latex formula

В позиции M12 находится коэффициент, на который нужно умножить расстояние X, чтобы получить величину вертикального смещения по Y. Аналогично, в позиции М21 находится коэффициент, на который умножается расстояние Y для определения горизонтального сдвига. Это на тот случай, если не нужны углы и заранее знаем, на какие величины хотим деформировать.

Немного кода:

Перемещение и масштабирование

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

ПермещениеМасштабирование
Latex formula
Latex formula
Latex formula
Latex formula
Latex formulaLatex formula

Композиция

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

Предположим, у нас есть следующее преобразование:

Latex formula

На которое следом идет такое преобразование:

Latex formula

Latex formula

Раскроем скобки и преобразуем:

Latex formula

Latex formula

или

Latex formula

где

Latex formula

Это ничто иное, как перемножение матриц коэффициентов:

Latex formula

Соответственно, расчет новых координат выглядит как:

Latex formula

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

Утверждение, прямо скажем, смелое, но правильное.

Проверим. Допустим, я хочу вначале применить деформацию, а потом повернуть на угол.

Рис.7. Деформация без поворота

На рисунке 7 пока только деформация. Теперь к деформированному прямоугольнику (плоскости) применяю поворот на 80 градусов.

Рис.8. Деформация + поворот

Расчет координат в точности воспроизводит приведенные выше выкладки:

Применение

Аффинные преобразования изменяют геометрию плоскости, при этом сохраняя параллельность линий и соотношение расстояний.

Безусловно, это один из основных методов обработки изображений. Аффинные преобразования используются для исправления искажений и деформаций, возникающих при не самых идеальных ракурсах камеры. Широко используется в машинном обучении и компьютерном зрении.

Лично мне чаще всего попадались проблемы, которые обобщенно можно выразить двумя вопросами:

  • как повернуть изображение?
  • как нарисовать эллипс под углом?

Эти два вопроса объединяет одно. Изображение – набор точек, эллипс — геометрическое место точек. Решая вопрос в лоб, надо к каждой точке применить функции поворота. И если для изображения – ну, может быть, то к эллипсу, как фигуре векторной графики, это выглядит топорней некуда.

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

Центр трансформации

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

В Windows этим занимается функция:

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

Аффинное преобразование Windows

В основе работы с аффинным преобразованием лежит изменение матрицы с учетом масштабирования, перемещения, деформации  или поворота. В Windows GDI за матрицу отвечает класс TXForm.

Знакомые обозначения. Поля имеют смысл ровно тот, который описан и в справочнике, и выше в статье. Это коэффициенты матрицы преобразования.

Необходимо проинициализировать поля записи и применить преобразование функцией Windows GDI:

Однако, работать с мировыми координатами и аффинными преобразованиями в Windows GDI — удовольствие так себе. Поэтому рекомендуется это делать в GDI+.

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

Необходимо создать экземпляр класса TGPMatrix. Затем проинициализировать его методом:

Снова знакомые имена аргументов. Аргументы имеют тот же смысл, что и поля записи TXForm.

После инициализации применяем трансформацию методом TGPGraphics:

После этих манипуляций рисуем все, что хотим нарисовать, не заботясь о расчетах, углах и синусах.

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

Рекомендован следующий порядок при работе с аффинными преобразованиями:

  1. Назначение нового центра координат (спорное утверждение)
  2. Инициализация матрицы преобразования
  3. Применение матрицы
  4. Рисуем эллипс  

Композиция

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

Здесь p2 и p3 — первая и вторая структуры TXForm, результат их комбинирования будет находиться в параметре p1. Т.е. надо понимать, что p3 – это преобразование, накладываемое на p2, т.е. идущее следом за ним. Порядок применения матриц очень важен.

Практика

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

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

Аффинный поворот

Итак, у нас есть демонстрационной приложение, написанное для статьи. Внизу есть ряд кнопок 1..15. Это разные режимы демонстрации. Режимы 9..11 отвечают за демонстрацию аффинных преобразований с предварительным назначением нового центра координат . В режиме 9 видим такую картину:

Рис.9. «Спокойное» аффинное преобразование

Ну, это мы уже видели. Однако, если повернуть прямоугольник, за вершину P1 или в поле «Rotate Angle» увидим интересное изменение:

Рис.10. Подписи к вершинам тоже под углом

Помимо поворота непосредственно прямоугольника, повернулись и подписи вершин.

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

Поэтому, можно предположить, что такой же фокус пройдет и с эллипсом:

Рис.11. Эллипс под углом

То же самое касается и изображения:

Рис.12. Изображение под углом

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

Фрагмент из вышеприведенного кода:

В функции DrawEllipse нет ничего особенного, вынесена, чтобы не загромождать код:

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

Аффинный сдвиг

Аналогичные чудеса в режиме деформации сдвига (кнопка 10):

Рис.13. Поперечная деформация (сдвиг) кота

Перед рисованием инициализируем матрицы TXForm и в зависимости, от того что требуется, применяем ту или иную матрицу. Фрагмент из кода выше:

Аффинная композиция

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

Рис.14. Деформированный, повернутый, замученный кот

Сравним с деформацией на рис.8. Там мы делали композицию матриц руками (в интерфейсе – режим 8), теперь этим занимается Windows GDI.

Рис.15. Все правильно делаем

Перед вызовом функции DrawAffine, необходимо перевести центр системы координат в центр прямоугольника. Напомню, начало системы координат – это центр трансформации. Именно вокруг него вертится вселенная.

Это фрагмент обработчика OnPaint того PaintBox, на котором рисуем . Вначале идет вызов SetViewportOrgEx, что переводит начало координат в центр прямоугольника. Потом происходит коррекция прямоугольника области рисования OffsetRect(rct, -Trunc(pnt.X), -Trunc(pnt.y)), иначе все координаты «уплывут» вправо на величину (pnt.x, pnt.y), затем вызов DrawAffine.

Если б мышь знала… Координаты вершин

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

И только потом

И только после этого

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

Конечно, можно сформировать и скомбинировать матрицы, взять коэффициенты и посчитать по формуле. Почти как в CalcComboPoints.

В GDI+ в TGPMatrix для этих целей существуют методы:

Здесь они не используются. Потом как-нибудь обязательно вернемся к ним.

Смещать ли начало координат?

Теоретически рекомендуется делать именно так, смещать и потом трансформировать. И, возможно, это правильно. Но можно обойтись и без этого. Просто привычней работать в «обычной» системе координат, когда точка (0,0) системы находится в левом верхнем углу.

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

Порядок действий таков:

  1. Определяемся с прямоугольником, в котором что-то хотим нарисовать
  2. Определяемся с центром трансформации (pnt), т.е. точкой, вокруг которой будет происходить трансформация
  3. Смещаем прямоугольник на -pnt.x, -pnt.y, тем самым совмещая центр трансформации с началом координат
  4. Формируем матрицу трансформации
  5. Добавляем матрицу перемещения с параметрами (или просто инициализируем в текущей матрице) eDx=pnt.x, eDy=pnt.y
  6. Рисуем в получившемся смещенном прямоугольнике

Сильно сократил код за счет того, что не использую TXForm, а инициализирую сразу TGPMatrix. Что тут изменилось. В матрицу поворота добавил еще смещение в точку (ACenter.x, ACenter.y) – центр прямоугольника, или центр трансформации. Перед рисованием сместил прямоугольник отрисовки так, чтобы он оказался центрирован по началу координат, т.е. точки (0,0). Аффинное преобразование отработает поворот и сместиться на указанные величины по x и y.

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

В демонстрационном примере это режимы 12..14, которые визуально будут совпадать с 9..11, с той лишь разницей, что перевода центра координат не происходит.

Чтобы продемонстрировать, работоспособность метода, существует последний режим – 15. Помимо основного прямоугольника, рисуется звезда и текст под углом, обратным текущему. Текст в дополнение к повороту также подвергается масштабированию.

Рис.16. Аффинные преобразования в любой требуемой точке плоскости

Я грозился вернуться к теме поворота звезд. Говорил, что есть более правильные методы. Брутальная звезда имени Стетхама, помните? Так вот, наиболее правильным будет вращать звезды, и не только, через аффинное преобразование.

Процедура, отвечающая за рисовку всего этого хозяйства:

Как видно, не очень много, и в принципе, просто.

Процедура для рисовки текста, чуть посложнее. Добавил к повороту еще и преобразование масштаба.

Как видим, матрицы TXForm не используются. Чтобы добавить преобразование в GDI+ используется метод TGPMatrix:

Суть такая же, как CombineTransform для Windows GDI. В листинге вначале происходит масштаб, потом поворот.

Видео + скачать

На этом пока все.


Друзья, спасибо за внимание!

Подписывайтесь на телеграм-канал.

Оцените мой труд, жду ваших комментариев.


Скачать (1 036 Кб): Исходники (Delphi XE 7-10)

Скачать (1 843 Кб): Исполняемый файл

Скачать (4 205 Кб): Affine2D

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


5 8 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest
1 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Елена
Елена
6 месяцев назад

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

1
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x
()
x