Eigen - Реортогонализация матрицы вращения
После умножения большого количества матриц вращения конечный результат может перестать быть действительной матрицей вращения из-за проблем с округлением (деортогонализация)
Один из способов повторной ортогонализации состоит в следующем:
- Преобразовать матрицу вращения в представление оси-угла ( ссылка)
- Преобразовать обратно ось-угол в матрицу вращения ( ссылка)
Есть ли что-то в библиотеке Eigen, которая делает то же самое, скрывая все детали? Или есть лучший рецепт?
Эту процедуру нужно обрабатывать с осторожностью из-за особых случаев, когда Eigen предлагает лучший инструмент для этого, это было бы здорово.
7 ответов
Я не использую Eigen и не удосужился посмотреть API, но здесь есть простая, вычислительно дешевая и стабильная процедура для повторной ортогонализации матрицы вращения. Эта процедура ортогонализации взята из Направления косинусной матрицы IMU: теория Уильяма Премерлани и Пола Бизера; уравнения 19-21.
Позволять x
, y
а также z
быть векторными строками (слегка испорченной) матрицы вращения. Позволять error=dot(x,y)
где dot()
это скалярное произведение. Если матрица была ортогональной, то скалярное произведение x
а также y
, это error
будет ноль.
error
распространяется по x
а также y
в равной степени: x_ort=x-(error/2)*y
а также y_ort=y-(error/2)*x
, Третий ряд z_ort=cross(x_ort, y_ort)
, который по определению ортогонален x_ort
а также y_ort
,
Теперь вам нужно нормализовать x_ort
, y_ort
а также z_ort
так как эти векторы должны быть единичными векторами.
x_new = 1/2*(3-dot(x_ort,x_ort))*x_ort
y_new = 1/2*(3-dot(y_ort,y_ort))*y_ort
z_new = 1/2*(3-dot(z_ort,z_ort))*z_ort
Вот и все, были сделаны.
Это должно быть довольно легко реализовать с помощью API, предоставляемого Eigen. Вы можете легко придумать другие ортогональные процедуры, но я не думаю, что это будет заметно на практике. Я использовал описанную выше процедуру в своем приложении отслеживания движения, и оно работало прекрасно; это и стабильно и быстро.
Вы можете использовать QR-декомпозицию для систематической повторной ортогонализации, где вы заменяете исходную матрицу на Q-фактор. В подпрограммах библиотеки вы должны проверить и исправить, если необходимо, путем отрицания соответствующего столбца в Q, что диагональные элементы R положительны (близко к 1, если исходная матрица была близка к ортогональной).
Ближайшая матрица вращения Q к данной матрице получается из полярного или QP-разложения, где P - положительная полуопределенная симметричная матрица. Разложение QP может быть вычислено итеративно или с использованием SVD. Если последний имеет факторизацию USV', то Q=UV'.
Разложение по единственному значению должно быть очень устойчивым. Цитировать из ссылки:
Пусть M=UΣV - сингулярное разложение M, тогда R=UV.
Для вашей матрицы сингулярные значения в Σ должны быть очень близки к единице. Матрица R гарантированно будет ортогональной, что является определяющим свойством матрицы вращения. Если при вычислении исходной матрицы вращения не было ошибок округления, то значение R будет точно таким же, как у вашего M, с точностью до числовой точности.
— матрица, которую мы хотим ортонормировать, и — матрица вращения, ближайшая к .
Аналитическое решение
Matrix R = M*inverse(sqrt(transpose(M)*M));
Итеративное решение
// To re-orthogonalize matrix M, repeat:
M = 0.5f*(inverse(transpose(M)) + M);
// until M converges
сходится к ближайшей матрице вращения. Количество цифр точности будет примерно удваиваться с каждой итерацией.
Проверить, является ли сумма квадратов элементов(M - M^-T)/2
меньше квадрата вашей цели по ошибке, чтобы узнать, когда он соответствует вашему порогу точности. является обратным транспонированием .
Почему это работает
Мы хотим найти матрицу вращения, ближайшую к . Определим ошибку как сумму квадратов разностей элементов. то есть свести к минимумуtrace((M - R)^T (M - R))
.
Аналитическое решениеR = M (M^T M)^-(1/2)
, изложено здесь.
Проблема в том, что для этого нужно найти квадратный корень изM^T M
. Однако, если мы заметим, есть много матриц, ближайшая матрица вращения которых равна . Один из нихM (M^T M)^-1
, что упрощается до , обратное транспонирование. Приятно то, что иM^-T
находятся на противоположных сторонах , (интуитивно какa
и1/a
находятся на противоположной стороне1
).
Мы признаем, что средний,(M + M^-T)/2
будет ближе к , и поскольку это линейная комбинация, она также будет поддерживаться как ближайшая матрица вращения. Делая это рекурсивно, мы сходимся кR
.
Сходимость в наихудшем случае (спекулятивная)
Поскольку он связан с вавилонским алгоритмом квадратного корня, он сходится квадратично.
Точная ошибка наихудшего случая после одной итерации матрицыM
и ошибкаe
являетсяnextE
:
nextE = e^2/(2 e + 2)
e = sqrt(trace((M - R)^T (M - R)))
R = M (M^T M)^-(1/2)
В это время:
#include <Eigen/Geometry>
Eigen::Matrix3d mmm;
Eigen::Matrix3d rrr;
rrr << 0.882966, -0.321461, 0.342102,
0.431433, 0.842929, -0.321461,
-0.185031, 0.431433, 0.882966;
// replace this with any rotation matrix
mmm = rrr;
Eigen::AngleAxisd aa(rrr); // RotationMatrix to AxisAngle
rrr = aa.toRotationMatrix(); // AxisAngle to RotationMatrix
std::cout << mmm << std::endl << std::endl;
std::cout << rrr << std::endl << std::endl;
std::cout << rrr-mmm << std::endl << std::endl;
Что является хорошей новостью, потому что я могу избавиться от своего собственного метода и иметь одну головную боль меньше (как можно быть уверенным, что он заботится обо всех особенностях?),
но я очень хочу ваше мнение о лучших / альтернативных способах:)
Альтернативой является использование Eigen::Quaternion
представлять ваше вращение. Это гораздо проще нормализовать, и rotation*rotation
продукты, как правило, быстрее. Если у вас много rotation*vector
продукты (с той же матрицей), вы должны локально преобразовать кватернион в матрицу 3х3.
Вот реализация с использованием Eigen. Это минимально повлияет на вращение, представленное матрицей вращения, путем корректировки любых двух векторов, которые не являются одинаково ортогональными, по направлению друг к другу или от него.
void orthonormalize(Eigen::Matrix3d &rot) {
Vector3d x = rot.col(0);
Vector3d y = rot.col(1);
Vector3d z = rot.col(2);
// normalize
x.normalize();
y.normalize();
z.normalize();
// orthogonalize
double errorXY = 0.5 * x.dot(y);
double errorYZ = 0.5 * y.dot(z);
double errorZX = 0.5 * z.dot(x);
rot.col(0) = x - errorXY * y - errorZX * z;
rot.col(1) = y - errorXY * x - errorYZ * z;
rot.col(2) = z - errorYZ * y - errorZX * x;
}
Мы можем проверить, что это действительно работает с
double orthonormalityError(const Matrix3d &rot) {
Matrix3d prod = rot * rot.transpose();
return (prod - Matrix3d::Identity()).norm();
}
Matrix3d randomRotationMatrix() {
Matrix3d rand_mat = Matrix3d::Random();
HouseholderQR<Matrix3d> qr(rand_mat);
return rot = qr.householderQ();
}
void testOrthonormalize() {
Eigen::Matrix3d rot = randomRotationMatrix();
rot += Eigen::Matrix3d::Random() * 0.1;
std::cout << orthonormalityError(rot) << std::endl;
for (int j = 0; j < 4; j++) {
orthonormalize(rot);
std::cout << orthonormalityError(rot) << std::endl;
}
}
int main(int argc, char** argv) {
testOrthonormalize();
return 0;
}
Что печатает:
0.246795
0.00948343
1.45409e-05
3.59979e-11
3.4066e-16