Это вторая статья из серии о Visual SLAM. Первую статью серии можно найти здесь.
Во второй части серии мы поговорим о движении твёрдого тела (в нашем случае робота) и его позиции в пространстве. В статье будет немного математики. Куда уж без неё в робототехнике. Кому интересно прошу под кат.
Во время выполнения визуального SLAM робот постоянно перемещается в пространстве. Для того чтобы определить его текущую позицию в пространстве необходимо знать его начальную позицию и перемещение в пространстве на данный момент времени. Для этого необходимо понимать как работают трансформации.
Трансформации
Это очень важная и довольно сложная тема в робототехнике. Трансформации используются в двух случаях: во-первых, при применении поворота и сдвига точки в пространстве и во-вторых при переводе координат точки между разными системами координат.
Робот обычно состоит из некоторого числа взаимосвязанных физических компонентов таких как основа, суставы, манипуляторы и сенсоры. Эти компоненты разнесены между собой на некоторое расстояние в пространстве и каждая из них имеет собственную систему координат. Чтобы локализация робота было более точной нужно понимать как преобразовать позицию объекта определенную относительно камеры (те. из системы координат камеры) к системе координат основы робота (конечная цель локализации робота в мире). У меня уже была статья о трансформациях в ROS на Хабре.

Позицию робота в пространстве в 3D можно описать с помощью двух параметров - координаты (x, y, z) и ориентации (три угла относительно систем координат x, y и z).
Для удобства вычисления вводятся однородные координаты - к стандартным координатам x, y и z добавляется ещё одна компонента w:

Однородные координаты упрощают вычисление трансформаций и представление точки на бесконечности. Использование однородных координат позволяет представлять любые аффинные трансформации в виде произведения матриц.
Существует несколько классов проективных геометрических трансформаций, которые отличаются тем какие параметры объекта сохраняются при трансформации (н-р, длины сторон, углы, соотношения площади) и количество степеней свободы (количество параметров которые не фиксированы и могут меняться при трансформации).
Рассмотрим различные типы трансформаций для простоты в пространстве 2D.
Эвклидова трансформация сохраняет размеры и площадь. У нее 3 степени свободы.
Трансформация подобия имеет 4 степени свободы и сохраняет соотношение длин сторон и углы между ними.
Аффинная трансформация имеет 6 степеней свободы и сохраняет параллелизм сторон и соотношение площадей.
Перспективная трансформация имеет 8 степеней свободы и сохраняет параллелизм только для некоторых сторон.

Представление ориентации твердого тела
Классическим представлением ориентации твердого тела является матрица ориентации (точнее три матрицы). Также называются матрицы поворота. Формула поворота вокруг каждой конкретной оси (x, y и z):

Углы Эйлера
Вы когда-либо слышали об углах Эйлера? Углы Эйлера предоставляют простой способ представления ориентации в виде трёхмерного вектора. В аэронавтике и робототехнике обычно используются три параметра: Yaw, Pitch и Roll ([y, p, r]).

Недостатком углов Эйлера является тот факт что если угол Pitch равен 90 или -90 градусов, то мы теряем одну степень свободы (две оси совпадают). Это создаёт сингулярность что добавляет сложность к нашей проблеме. Эта проблема часто называется Gimbal lock.

На рисунке видно что все оси x, y, z совпали (предельный случай Gimbal lock).
Кватернионы
Кватернионы решают эту проблему. Кватернионы добавляют четвертую компоненту которая работает как мнимая часть комплексного числа.

Этот кватернион можно так же представить в следующем виде:

Рассмотрим некоторые операции над кватернионами.
Сложение и вычитание:

Умножение:

Норма:

Группы Ли и алгебра Ли
Эта тема довольно сложная и в материалах по ней обычно представлено очень много сложных математических определений и уравнений. Я решил дать определение которое мне выдало ИИ. Далее я привожу полностью оригинальный ответ от Gemini.
Алгебра Ли — это математический объект, похожий на векторное пространство, но с дополнительной операцией, называемой скобкой Ли или коммутатором, которая показывает, как объекты "переставляются". Группа Ли — это группа, которая также является гладким многообразием (непрерывным пространством). Группы Ли описывают непрерывные симметрии, а их алгебры Ли — бесконечно малые преобразования этих симметрий
Представьте себе вращение или перемещение объекта в пространстве — это непрерывные действия, которые можно выполнять много раз, и их можно выполнить в любом порядке (например, сначала повернуть на 90 градусов, потом на 180, или наоборот). Группа Ли — это множество всех таких возможных непрерывных преобразований, которое обладает свойствами группы (например, есть "ничего не делать", можно "развернуть" любое действие, и результат всегда однозначен).
Алгебра Ли — это "линейное пространство" этих бесконечно малых движений. Сравните это с обычными числами: в обычной алгебре мы складываем числа, а в алгебре Ли мы "коммутируем" или "переставляем" бесконечно малые движения, чтобы понять, как они взаимодействуют.
Группы Ли и алгебра Ли облегчают вычисления трансформаций в непрерывном 3D пространстве (н-р, определение позиции камеры и элементов карты). Алгебра Ли предоставляет простое векторное представление, которое облегчает оценку состояния (state estimation), дифференцирование и оптимизацию. Таким образом группы и алгебра Ли эффективно решают сложные нелинейные проблемы в SLAM.
На хабре есть хорошая статья на тему групп Ли и алгебры Ли. На Youtube есть хорошее видео на эту тему.
Обзор библиотек для работы с трансформациями
Eigen
Для работы с линейной алгеброй используется библиотека Eigen. В Eigen реализованы специальные типы данных для хранения различных сущностей типа вектора, матрицы и тд. Библиотека облегчает работу с трансформациями, предоставляя удобные методы работы с ними.
Пример кода:
int main(int argc, char **argv)
{
Quaterniond q1(0.35, 0.2, 0.3, 0.1), q2(-0.5, 0.4, -0.1, 0.2);
q1.normalize();
q2.normalize();
Vector3d t1(0.3, 0.1, 0.1), t2(-0.1, 0.5, 0.3);
Vector3d p1(0.5, 0, 0.2); // Create homogeneous transformation matrix from rotation and translation
Isometry3d T1w(q1), T2w(q2);
T1w.pretranslate(t1);
T2w.pretranslate(t2); // Apply the transformation R1 to World, World to R2
Vector3d p2 = T2w * T1w.inverse() * p1;
cout << "p_R2 = T_R2_W * T_W_R1 * P_R1 = " << p2.transpose() << endl;
return 0;
}
Sophus
Для работы с операцией из теории Lie используется библиотека Sophus.
Вот например один из примеров использования библиотеки Sophus - SO(3) и so(3) из репозитория - трансформация между группой и алгеброй Ли и внесение маленького возмущения в матрицу поворота:
// Rotation of 90 deg around Z
Matrix3d R = AngleAxisd(M_PI / 2, Vector3d(0, 0, 1)).toRotationMatrix();
Quaterniond q(R);
// ************ SO(3) ************
Sophus::SO3d SO3d_R(R);
Sophus::SO3d SO3d_q(q); // Result should be the same as SO3d_R
cout << "SO(3) from rotation matrix = \n" << SO3d_R.matrix() << endl;
cout << "SO(3) from quaternion = \n" << SO3d_q.matrix() << endl;
// Logarithmic map to get the lie algebra
Vector3d so3 = SO3d_R.log();
cout << "so3 = \n" << so3.transpose() << endl;
// Hat is from vector to skew-symmetric matrix
cout << "so3 hat = \n" << Sophus::SO3d::hat(so3) << endl;
// Vee is from skew-symmetric matrix to vector
cout << "so3 vee = \n" << Sophus::SO3d::vee(Sophus::SO3d::hat(so3)).transpose() << endl;
// Update by perturbation model
Vector3d update_so3(1e-4, 0, 0);
Sophus::SO3d SO3d_updated = Sophus::SO3d::exp(update_so3) * SO3d_R;
cout << "SO3 updated = \n" << SO3d_updated.matrix() << endl;
cout << "****************************" << endl;
Подключение Sophus в CMakeLists.txt
# Sophus practicefind_package( Sophus REQUIRED)
include_directories( ${Sophus_INCLUDE_DIRS} )
add_executable( useSophus useSophus.cpp)
target_link_libraries(useSophus Sophus::Sophus)
Визуализация траектории с Pangolin
Pangolin это набор легковесных и портируемых библиотек, который часто используется в сфере Computer Vision для визуализации работы визуального SLAM.
Для примера визуализируем траекторию из SLAM с помощью библиотеки Pangolin.
Для установки библиотеки Pangolin нужно последовать инструкции на гитхабе.
Скачайте файл траектории отсюда. Исходные файлы примера можно найти здесь.
Создадим файл CMakeLists.txt
cmake_minimum_required( VERSION 2.8 )
project( geomtetry )
set(CMAKE_CXX_FLAGS "-std=c++14")
include_directories( "usr/include/eigen3" )
add_executable( eigenMatrix eigenMatrix.cpp )
add_executable( useGeometry useGeometry.cpp )
add_executable( coordinateTransform coordinateTransform.cpp )
# Pangolinfind_package(Pangolin REQUIRED)
find_package(pybind11 REQUIRED)
include_directories( ${Pangolin_INCLUDE_DIRS} )
add_executable( plotTrajectory plotTrajectory.cpp)
target_link_libraries(plotTrajectory ${Pangolin_LIBRARIES} pybind11::embed)
Создадим файл plotTrajectory.cpp
Файл plotTrajectory.cpp
#include <pangolin/pangolin.h>
#include <Eigen/Core>
#include <unistd.h>
using namespace Eigen;
using namespace std;
// path to trajectory file
string trajectory_file = "./examples/trajectory.txt";
void DrawTrajectory(vector<Isometry3d, Eigen::aligned_allocator<Isometry3d>>);
int main(int argc, char **argv) {
vector<Isometry3d, Eigen::aligned_allocator<Isometry3d>> poses;
ifstream fin(trajectory_file);
if (!fin) {
cout << "cannot find trajectory file at " << trajectory_file << endl;
return 1;
}
while (!fin.eof()) {
double time, tx, ty, tz, qx, qy, qz, qw;
fin >> time >> tx >> ty >> tz >> qx >> qy >> qz >> qw;
Isometry3d Twr(Quaterniond(qw, qx, qy, qz));
Twr.pretranslate(Vector3d(tx, ty, tz));
poses.push_back(Twr);
}
cout << "read total" << poses.size() << "pose entries" << endl;
// draw trajectory in pangolin
DrawTrajectory(poses);
return 0;
}
void DrawTrajectory(vector<Isometry3d, Eigen::aligned_allocator<Isometry3d>> poses) {
// create pangolin window and plot the trajectory
pangolin::CreateWindowAndBind("Trajectory Viewer", 1024, 768);
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
pangolin::OpenGlRenderState s_cam(
pangolin::ProjectionMatrix(1024, 768, 500, 500, 512, 389, 0.1, 1000),
pangolin::ModelViewLookAt(0, -0.1, -1.8, 0, 0, 0, 0.0, -1.0, 0.0)
);
pangolin::View &d_cam = pangolin::CreateDisplay()
.SetBounds(0.0, 1.0, 0.0, 1.0, -1024.0f / 768.0f)
.SetHandler(new pangolin::Handler3D(s_cam));
while (pangolin::ShouldQuit() == false) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
d_cam.Activate(s_cam);
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glLineWidth(2);
for (size_t i = 0; i < poses.size(); i++) {
// 画每个位姿的三个坐标轴
Vector3d Ow = poses[i].translation();
Vector3d Xw = poses[i] * (0.1 * Vector3d(1, 0, 0));
Vector3d Yw = poses[i] * (0.1 * Vector3d(0, 1, 0));
Vector3d Zw = poses[i] * (0.1 * Vector3d(0, 0, 1));
glBegin(GL_LINES);
glColor3f(1.0, 0.0, 0.0);
glVertex3d(Ow[0], Ow[1], Ow[2]);
glVertex3d(Xw[0], Xw[1], Xw[2]);
glColor3f(0.0, 1.0, 0.0);
glVertex3d(Ow[0], Ow[1], Ow[2]);
glVertex3d(Yw[0], Yw[1], Yw[2]);
glColor3f(0.0, 0.0, 1.0);
glVertex3d(Ow[0], Ow[1], Ow[2]);
glVertex3d(Zw[0], Zw[1], Zw[2]);
glEnd();
}
// 画出连线
for (size_t i = 0; i < poses.size(); i++) {
glColor3f(0.0, 0.0, 0.0);
glBegin(GL_LINES);
auto p1 = poses[i], p2 = poses[i+1];
glVertex3d(p1.translation()[0], p1.translation()[1], p1.translation()[2]);
glVertex3d(p2.translation()[0], p2.translation()[1], p2.translation()[2]);
glEnd();
}
pangolin::FinishFrame();
usleep(5000); // sleep 5 ms
}
}
Скомпилируем и запустим приложение

На этом пока все. Мы покрыли одну из самых сложных глав теории о визуальном SLAM. Еще раз напомню, что есть хорошая книга SLAM Handbook где очень подробно разбирается теория SLAM (с большим количеством математики). Скачать ее можно здесь.
Жду комментариев и вашего мнения насколько материал оказался для вас полезен чтобы мне планировать последующие статьи из цикла. Всем удачи!
kovserg
Как-то ниочем. Где мясо-то? Ни что такое гиперкомплексные числа, ни как с помощью из записать поворот, ни простейших преобразований, ни примеров. И группы ли тут тоже не нужны.
Кватернион это гиперкомплексное число q={w,x,y,z}=w+i∙x+j∙y+k∙z действительная часть w и 3 мнимых вектора i,j,k. Обладающие следующими свойствами i∙i=j∙j=k∙k=-1, i∙j=k, j∙i=-k, i∙k=-j, k∙i=j, j∙k=i, k∙j=-i
Далее как для обычно комплексного числа вводятся сложения, умножения и
Операция комплексного сопряжения: conj(q)={w,-x,-y,-z}
Норма q: norm(q)=q∙conj(q)=w∙w+x∙x+y∙y+z∙z
Алгебра кватенионов похожа на обычные числа, но не коммутативна т.е. надо учитывать что q1∙q2 ≠ q2∙q1
Далее можно показть что если |q|=1 то его можно записать в виде q={ cos(a/2), n∙sin(a/2) } где n единичный вектор. ( q={w,x,y,z}={ cos(a/2), nx∙sin(a/2), ny∙sin(a/2), nz∙sin(a/2) }
Тогда выражение r' = q∙r∙conj(q)
или в развёрнутом виде:
rx'=rx∙(w∙w+x∙x-y∙y-z∙z)+2∙(ry∙(x∙y+w∙z)+rz∙(x∙z-w∙y))
ry'=ry∙(w∙w-x∙x+y∙y-z∙z)+2∙(rz∙(y∙z+w∙x)+rx∙(x∙y-w∙z))
rz'=rz∙(w∙w-x∙x-y∙y+z∙z)+2∙(rx∙(x∙z+w∙y)+ry∙(y∙z-w∙x))
r'=(rx',ry',rz') будет вектором который получается из вектора r=(rx,ry,rz) путём поворота на угол a вокруг единичного вектора n=(nx,ny,nz). То есть кватернион может быть использован для описание ориентации твёрдого тела (камеры например) и его можно однозначно преобразовать в матрицу вращения и обратно. (При этом q и -q описывают одно и тоже преобразование).
Можно определить
Rx(a)={ cos(a/2), sin(a/2),0,0 }
Ry(a)={ cos(a/2), 0,sin(a/2),0 }
Rz(a)={ cos(a/2), 0,0,sin(a/2) }
При этом умножение кватернионов позволяет накапливать изменение ориентации простым умножением кватернионов. q12=q1∙q2 например Rx(a)*Rx(a)=Rx(2a)
И с помощью них явно определить координаты Эйлера или другие использемые координаты
Euler(α,β,γ)=Rz(γ)∙Rx(β)∙Rz(α)
HPR(head,pitch,roll)=Ry(head)∙Rx(pitch)∙Rz(-roll)
т.к. есть разночтения (особенно если учесть что бывают левые и правые системы отсчета).