DVD – как много в этой аббревиатуре! Уверен, что вы наверняка помните такое явление, как ларьки и палатки с дисками, исчезнувшие только к началу 2010-х годов (по крайней мере так было в столице). В один из таких ларьков в конце 2009-го заглянул десятилетний я, внимание которого тут же привлекла коробка с надписью «3D Studio Max 2010»... Аниматором я, увы, так и не стал, однако интерес к области визуальных эффектов сохранился надолго. В поисках обучающих материалов с англо-русским словарём наперевес в один прекрасный день я забрёл на Videocopilot. В одном из уроков автор с помощью неведомой волшебной софтины под названием Boujou показал, как отследить движение камеры в отснятом материале для его дальнейшего совмещения с трёхмерной графикой, что в подростковом мозгу произвело эффект разорвавшейся бомбы. Много позже, курсе на втором-третьем, меня всё чаще посещала навязчивая мысль – а как это вообще работает? В данной статье мне бы хотелось рассмотреть математический аппарат, применяемый в данном классе задач, а также сдобрить её щепоткой практики. Приятного чтения! Отправной точкой стал фундаментальный труд Роджерса и Адамса – «Математические основы машинной графики» 1980 года. Восстановлению трёхмерных объектов по проекциям посвящена отдельная глава, однако описанный подход был очень быстро отброшен, т. к.: Требует наличия в кадре калибровочного объекта, необходимо как минимум шесть некомпланарных точек, для которых известны координаты в пространстве; Вишенка на торте – при восстановлении координат по двум проекциям вся эта радость превращается в однородную систему из двенадцати уравнений с двенадцатью неизвестными. Т. к. система однородная, то уравнения содержат произвольный масштабный коэффициент, а задача получить решение отличное от тривиального несколько усложняется. Следующим шагом, определившим дальнейшее направление, стали документация к OpenCV и монументальный труд Multiple View Geometry in Computer Vision (Hartley R., Zisserman A.). Также мне очень помогла эта статья. Данный материал практически полностью основан на этих источниках, если не указано иное. Однородные координаты – система координат, используемая в проективной геометрии. Их использование позволяет представить точки в бесконечности конечными координатами и значительно упрощает выполнение аффинных преобразований по сравнению с более привычными декартовыми. Однородный вектор можно получить, просто добавив 1 к n-мерному декартову: Обратное преобразование выполняется путём деления каждой из компонент вектора на его последнюю компоненту с дальнейшим её отбрасыванием: Разумеется, если W ≠ 0. Это всё замечательно, – скажете вы, – но хотелось бы больше конкретики. Пожалуйста! Вспоминаем алгебру за первый курс. Чтобы получить матрицу линейного преобразования нам нужно: Выбрать базис; Преобразовать базисные векторы; Найти координаты преобразованных векторов в выбранном базисе; Наконец получить матрицу, записывая координаты преобразованных векторов по столбцам. Таким образом мы получаем растяжение/сжатие и сдвиги по осям, но не параллельный перенос; произвольное аффинное преобразование будет выглядеть примерно так: Эта запись не выглядит пугающей ровно до тех пор, пока нам не понадобится получить композицию из нескольких преобразований (а нам это понадобится): В однородных координатах вышеописанный пример с преобразованием плоскости уже будет выглядеть так: Теперь поворот и параллельный перенос превращаются всего в одно умножение матрицы на вектор. Если же нам нужно последовательно выполнить несколько преобразований, то достаточно будет просто перемножить их матрицы! Накапливать преобразования стало приятно как никогда.  Пусть центр камеры находится в начале координат, камера сонаправлена оси OZ, а фокальная плоскость задана уравнением где f – фокусное расстояние. Тогда точка в пространстве с координатами (X, Y, Z) отображается в точку Вспоминаем курс геометрии за шестой класс (перспективная проекция через подобие треугольников с коэффициентом f/Z). В матричном виде это можно записать следующим образом: Или: Всё бы ничего, но координаты точки на изображении выражены не в метрах/миллиметрах/попугаях, а в пикселях с отсчётом от верхнего левого угла – требуется перевод в новую систему координат (сдвиг, масштабирование). Для этого нам нужно знать не только фокусное расстояние, но и размеры кадра: где  W, H – физические размеры кадра, w, h – размеры кадра в пикселях, cx, cy – главная точка (пересечение фокальной плоскости с оптической осью) в координатах изображения. Коэффициенты fx и fy называются фокусными расстояниями по осям и могут отличаться друг от друга в случае неквадратных пикселей. Осталось рассмотреть случай, когда камера повёрнута и/или смещена – требуется перейти из мировых координат в координаты камеры. Всё и сразу в матричном виде: Или более кратко: где K – матрица параметров камеры, R – матрица поворота, T – вектор смещения. Пусть имеются две камеры, как изображено на рисунке ниже. C и C’ – центры камер. Точка в пространстве X, C и C’ формируют эпиполярную плоскость. Пересечения этой плоскости с плоскостями изображений – эпиполярные прямые. Линия, соединяющая центры проецирования, пересекает плоскости изображения в сопряжённых точках, которые называются эпиполюсами. Пусть точка X проецируется в x на плоскость изображения левой камеры и в x’ на плоскость изображения правой камеры. Тогда точке x на левом изображении соответствует эпиполярная прямая l’ на правом. При этом пара для x на изображении правой камеры может лежать только на соответствующей эпиполярной линии. То же самое работает и в обратную сторону. Теперь всё то же самое, но в координатах: F – фундаментальная матрица, где для вектора e обозначение [e]x вычисляется как Кроме фундаментальной матрицы также существует сущностная (или существенная? – видел оба варианта) матрица, выражающая то же ограничение: Разница между ними заключается в том, что сущностная матрица оперирует не пиксельными (как фундаментальная), а нормализованными координатами (имеют начало в оптическом центре изображения, безразмерны, т. к. нормализованы по fx и fy), а также имеет меньше степеней свободы.С помощью сущностной матрицы можно вычислить движение камеры; пусть есть сингулярное разложение E, P1 = (I|0), где U, V – ортогональные матрицы, I – единичная матрица, а P1 и P2 – матрицы проекций соответствующих камер. Тогда возможны 4 решения для P2: где но нас интересует только одно из них: Наконец, найти координаты точки по её проекциям можно, зная матрицы проекций каждой из камер: где: x1, x2 – проекции (в однородных координатах), P1, P2 – матрицы проекций камер. Я знаю, что все снимки были сделаны камерой с APS-C матрицей (приблизительно 24×16 мм), фокусное расстояние объектива – 18 мм, дисторсия исправлена средствами камеры. Грубо прикинем матрицу параметров камеры: Сначала я попробовал ORB, однако методом научного тыка выяснилось, что детектор AKAZE даёт больше соответствий, хотя ORB работает быстрее. Далее нужно отфильтровать ложные совпадения, для чего ключевые точки должны пройти два теста, по результатам которых они будут приниматься или же не приниматься к рассмотрению: Первый тест предложен Дэвидом Лоу, автором алгоритма SIFT. Каждая найденная точка первого изображения сопоставляется с несколькими точками второго изображения. Мы рассматриваем по два лучших соответствия для каждой точки (лучшими считаем соответствия с наименьшим расстоянием). Из предположения, что точка на первом изображении не может иметь более одного соответствия на втором, следует, что хотя бы одно из этих соответствий ложно. Таким образом, тест Лоу проверяет, что два этих расстояния достаточно отличаются друг от друга. В противном случае точка отбрасывается и исключается из дальнейших расчётов. Также должны быть соблюдены ограничения, накладываемые фундаментальной матрицей. Для этого считаем матрицу методом RANSAC с учётом ложных соответствий. Главной проблемой станет отслеживание точек, присутствующих на нескольких изображениях: это важно, т. к. для восстановления относительного масштаба вектора смещения камеры нужны точки, которые видны минимум на трёх изображениях. Основная часть магии происходит здесь. Я использую следующие функции OpenCV: findEssentialMat – без комментариев; recoverPose – для восстановления поворота и смещения из сущностной матрицы. Она возвращает матрицу поворота R (3×3) и нормализованный вектор смещения t (3×1). Тогда положение n-й камеры можно посчитать как triangulatePoints – если точку видно в двух проекциях, то её можно восстановить с помощью этой функции. В качестве параметров функция принимает координаты точек и матрицы проекций для каждой камеры. Напомню, что матрицу проекции P (3×4) для n-й камеры можно найти следующим образом: Помним, что вектор, который нам вернула recoverPose нормализован, т. е. мы знаем только направление движения. Пора это исправить. Сравниваем изображения попарно и выполняем триангуляцию точек – они будут ориентиром для восстановления масштаба. Находим соответствующие точки на соседних парах изображений и вычисляем отношение расстояний между ними. Вычисляем матрицу проекции с новым вектором и повторяем триангуляцию. Новая итерация. Всё проделанное ранее бесполезно, если полученные результаты нельзя экспортировать в любимый графический пакет. Самый простой способ оценить корректность расчётов – посмотреть на точки в пространстве. Для этих целей я использовал такой софт как MeshLab, т. к. он позволяет посмотреть на облако точек из текстового файла (формат PLY), состоящего из заголовка и перечня координат, каждая точка с новой строки. Пример содержимого приведён ниже: Особенно хорошо получились обложки книг, а вот череп из-за равномерности окраски получился куда менее эпично. Нет, использовать 3ds Max SDK в данном случае мы не будем, ибо из пушки по воробьям, для наших целей вполне достаточно возможностей MAXScript   встроенного скриптового языка. MAXScript поддерживает матрицы, но довольно специфичным образом. Класс Matrix3 описывает матрицы размером 4×3, которые задают транс-формацию объекта относительно родительской системы координат и содержат в себе локальную систему координат объекта. Каждая строка – вектор, определяющий направление осей X, Y, Z и начала координат в системе координат матрицы. Очевидно, использовать полученные матрицы поворота напрямую не выйдет, но есть альтернатива в виде описания вращения с помощью кватернионов. Если не вдаваться в подробности и не говорить о гиперкомплексных числах, то кватернион – набор из четырёх чисел; первое (скалярная часть) определяет угол поворота вокруг вектора, оставшиеся три определяют вектор оси поворота. Весь скрипт целиком вместе с изображениями, использованными в статье, можно найти на Github. К сожалению, как и во всех остальных моих проектах, тут есть существенная недоделка: судя по форме облака точек (а оно не расслаивается, как могло бы), положение камер вычислено корректно, но при экспорте в 3ds Max корректное положение камера имеет только в первом кадре. Я сломал всю голову, привлёк друзей и знакомых, но решения так и не нашёл... Бонус – по-хорошему следует перейти от Y-down к Z-up системе координат графического пакета. Отдельно хочу поблагодарить Диму, Алёну и команду Хабра за помощь с рецензированием и редактурой.  Быдлокодер