LOST 0.0.1
LOST: Open-source Star Tracker
Loading...
Searching...
No Matches
attitude-estimators.cpp
Go to the documentation of this file.
2
3#include <eigen3/Eigen/Dense>
4#include <eigen3/Eigen/Eigenvalues>
5
6#include "decimal.hpp"
7
8namespace lost {
9
10#define EPSILON DECIMAL(0.0001) // threshold for 0 for Newton-Raphson method
11
13 const Stars &stars,
14 const Catalog &catalog,
15 const StarIdentifiers &starIdentifiers) {
16 if (starIdentifiers.size() < 2) {
17 return Attitude();
18 }
19 assert(stars.size() >= 2);
20
21 // attitude profile matrix
22 #ifdef LOST_FLOAT_MODE
23 Eigen::Matrix3f B;
24 #else
25 Eigen::Matrix3d B;
26 #endif
27
28
29 B.setZero();
30 for (const StarIdentifier &s : starIdentifiers) {
31 Star bStar = stars[s.starIndex];
32 Vec3 bStarSpatial = camera.CameraToSpatial(bStar.position);
33
34 #ifdef LOST_FLOAT_MODE
35 Eigen::Vector3f bi;
36 Eigen::Vector3f ri;
37 #else
38 Eigen::Vector3d bi;
39 Eigen::Vector3d ri;
40 #endif
41
42 bi << bStarSpatial.x, bStarSpatial.y, bStarSpatial.z;
43
44 CatalogStar rStar = catalog[s.catalogIndex];
45 ri << rStar.spatial.x, rStar.spatial.y, rStar.spatial.z;
46 //Weight = 1 (can be changed later, in which case we want to make a vector to hold all weights {ai})
47 // Calculate matrix B = sum({ai}{bi}{ri}T)
48 B += ri * bi.transpose() * s.weight;
49 }
50
51 // S = B + Transpose(B)
52 #ifdef LOST_FLOAT_MODE
53 Eigen::Matrix3f S = B + B.transpose();
54 #else
55 Eigen::Matrix3d S = B + B.transpose();
56 #endif
57
58 //sigma = B[0][0] + B[1][1] + B[2][2]
59 decimal sigma = B.trace();
60
61 //Z = [[B[1][2] - B[2][1]], [B[2][0] - B[0][2]], [B[0][1] - B[1][0]]]
62 #ifdef LOST_FLOAT_MODE
63 Eigen::Vector3f Z;
64 #else
65 Eigen::Vector3d Z;
66 #endif
67
68 Z << B(1,2) - B(2,1),
69 B(2,0) - B(0,2),
70 B(0,1) - B(1,0);
71
72 // K = [[[sigma], [Z[0]], [Z[1]], [Z[2]]], [[Z[0]], [S[0][0] - sigma], [S[0][1]], [S[0][2]]], [[Z[1]], [S[1][0]], [S[1][1] - sigma], [S[1][2]]], [[Z[2]], [S[2][0]], [S[2][1]], [S[2][2] - sigma]]]
73 #ifdef LOST_FLOAT_MODE
74 Eigen::Matrix4f K;
75 #else
76 Eigen::Matrix4d K;
77 #endif
78
79 K << sigma, Z(0), Z(1), Z(2),
80 Z(0), S(0,0) - sigma, S(0,1), S(0,2),
81 Z(1), S(1,0), S(1,1) - sigma, S(1,2),
82 Z(2), S(2,0), S(2,1), S(2,2) - sigma;
83
84 //Find eigenvalues of K, store the largest one as lambda
85 //find the maximum index
86 #ifdef LOST_FLOAT_MODE
87 Eigen::EigenSolver<Eigen::Matrix4f> solver(K);
88 Eigen::Vector4cf values = solver.eigenvalues();
89 Eigen::Matrix4cf vectors = solver.eigenvectors();
90 #else
91 Eigen::EigenSolver<Eigen::Matrix4d> solver(K);
92 Eigen::Vector4cd values = solver.eigenvalues();
93 Eigen::Matrix4cd vectors = solver.eigenvectors();
94 #endif
95
96 int maxIndex = 0;
97 decimal maxEigenvalue = values(0).real();
98 for (int i = 1; i < values.size(); i++) {
99 if (values(i).real() > maxEigenvalue) {
100 maxIndex = i;
101 maxEigenvalue = values(i).real();
102 }
103 }
104
105 //The return quaternion components = eigenvector assocaited with lambda
106 #ifdef LOST_FLOAT_MODE
107 Eigen::Vector4cf maxEigenvector = vectors.col(maxIndex);
108 #else
109 Eigen::Vector4cd maxEigenvector = vectors.col(maxIndex);
110 #endif
111
112 // IMPORTANT: The matrix K is symmetric -- clearly first row and first column are equal.
113 // Furthermore, S is symmetric because s_i,j = b_i,j + b_j,i and s_j,i=b_j,i + b_i,j, so the
114 // bottom right 3x3 block of K is symmetric too. Thus all its eigenvalues and eigenvectors
115 // are real.
116 return Attitude(Quaternion(maxEigenvector[0].real(),
117 maxEigenvector[1].real(),
118 maxEigenvector[2].real(),
119 maxEigenvector[3].real()));
120}
121
123static Mat3 TriadCoordinateFrame(Vec3 v1, Vec3 v2) {
124 Vec3 d1 = v1.Normalize();
125 Vec3 d2 = v1.CrossProduct(v2).Normalize();
126 Vec3 d3 = d1.CrossProduct(d2).Normalize();
127 return {
128 d1.x, d2.x, d3.x,
129 d1.y, d2.y, d3.y,
130 d1.z, d2.z, d3.z,
131 };
132}
133
135 const Stars &stars,
136 const Catalog &catalog,
137 const StarIdentifiers &starIds) {
138 if (starIds.size() < 2) {
139 return Attitude();
140 }
141 assert(stars.size() >= 2);
142
143 // TODO: Better way of picking the two stars
145 a = starIds[0],
146 b = starIds[starIds.size()/2];
147
148 Mat3 photoFrame = TriadCoordinateFrame(camera.CameraToSpatial(stars[a.starIndex].position),
149 camera.CameraToSpatial(stars[b.starIndex].position));
150 Mat3 catalogFrame = TriadCoordinateFrame(catalog[a.catalogIndex].spatial,
151 catalog[b.catalogIndex].spatial);
152
153 // Now, we calculate the rotation that would bring the catalog vector to the photo vector. We
154 // have the relationship photoFrame = attitude*catalogFrame, and since catalogFrame is an
155 // orthogonal matrix, we have attitude = photoFrame*transpase(catalogFrame)
156 return Attitude(photoFrame*catalogFrame.Transpose());
157}
158
163decimal QuestCharPoly(decimal x, decimal a, decimal b, decimal c, decimal d, decimal s) {return (DECIMAL_POW(x,2)-a) * (DECIMAL_POW(x,2)-b) - (c*x) + (c*s) - d;}
164
168decimal QuestCharPolyPrime(decimal x, decimal a, decimal b, decimal c) {return 4*DECIMAL_POW(x,3) - 2*(a+b)*x - c;}
169
175 decimal height;
176 do {
177 height = QuestCharPoly(guess, a, b, c, d, s) / QuestCharPolyPrime(guess, a, b, c);
178 guess -= height;
179 } while (abs(height) >= EPSILON);
180
181 return guess;
182}
183
185 const Stars &stars,
186 const Catalog &catalog,
187 const StarIdentifiers &starIdentifiers) {
188
189 if (starIdentifiers.size() < 2) {
190 return Attitude();
191 }
192 assert(stars.size() >= 2);
193
194 // initial guess for eigenvalue (sum of the weights)
195 decimal guess = 0;
196
197 // attitude profile matrix
198 Mat3 B = {0, 0, 0, 0, 0, 0, 0, 0, 0};
199 for (const StarIdentifier &s : starIdentifiers) {
200 Star bStar = stars[s.starIndex];
201 Vec3 bStarSpatial = camera.CameraToSpatial(bStar.position);
202
203 CatalogStar rStar = catalog[s.catalogIndex];
204 Vec3 rStarSpatial = {rStar.spatial.x, rStar.spatial.y, rStar.spatial.z};
205
206 //Weight = 1 (can be changed later, in which case we want to make a vector to hold all weights {ai})
207 // Calculate matrix B = sum({ai}{bi}{ri}T)
208 B = B + (rStarSpatial.OuterProduct(bStarSpatial) * s.weight);
209
210 // sum up the weights for initial guess of eigenvalue
211 guess += s.weight;
212 }
213
214 // S = B + Transpose(B)
215 Mat3 S = B + B.Transpose();
216 //sigma = B[0][0] + B[1][1] + B[2][2]
217 decimal sigma = B.Trace();
218 //Z = [[B[1][2] - B[2][1]], [B[2][0] - B[0][2]], [B[0][1] - B[1][0]]]
219 Vec3 Z = {
220 B.At(1,2) - B.At(2,1),
221 B.At(2,0) - B.At(0,2),
222 B.At(0,1) - B.At(1,0)
223 };
224
225 // calculate coefficients for characteristic polynomial
226 decimal delta = S.Det();
227 decimal kappa = (S.Inverse() * delta).Trace();
228 decimal a = DECIMAL_POW(sigma,2) - kappa;
229 decimal b = DECIMAL_POW(sigma,2) + (Z * Z);
230 decimal c = delta + (Z * S * Z);
231 decimal d = Z * (S * S) * Z;
232
233 // Newton-Raphson method for estimating the largest eigenvalue
234 decimal eig = QuestEigenvalueEstimator(guess, a, b, c, d, sigma);
235
236 // solve for the optimal quaternion: from https://ahrs.readthedocs.io/en/latest/filters/quest.html
237 decimal alpha = DECIMAL_POW(eig,2) - DECIMAL_POW(sigma, 2) + kappa;
238 decimal beta = eig - sigma;
239 decimal gamma = (eig + sigma) * alpha - delta;
240
241 Vec3 X = ((kIdentityMat3 * alpha) + (S * beta) + (S * S)) * Z;
242 decimal scalar = 1 / DECIMAL_SQRT(DECIMAL_POW(gamma,2) + X.MagnitudeSq());
243 X = X * scalar;
244 gamma *= scalar;
245
246 return Attitude(Quaternion(gamma, X.x, X.y, X.z));
247}
248
249}
#define EPSILON
The attitude (orientation) of a spacecraft.
A full description of a camera. Enough information to reconstruct the camera matrix and then some.
Definition camera.hpp:9
Vec3 CameraToSpatial(const Vec2 &) const
Gives a point in 3d space that could correspond to the given vector, using the same coordinate system...
Definition camera.cpp:35
A star from the Bright Star Catalog.
Vec3 spatial
The point on the unit sphere where the star lies.
Attitude Go(const Camera &, const Stars &, const Catalog &, const StarIdentifiers &)
Actually run the star-id algorithm.
3x3 vector with decimaling point components
Mat3 Inverse() const
Inverse of a matrix.
decimal Det() const
Determinant of a matrix.
Mat3 Transpose() const
Transpose of a matrix.
decimal Trace() const
Trace of a matrix.
decimal At(int i, int j) const
Access the i,j-th element of the matrix.
A quaternion is a common way to represent a 3d rotation.
Attitude Go(const Camera &, const Stars &, const Catalog &, const StarIdentifiers &)
Actually run the star-id algorithm.
A "centroid" detected in an image.
Vec2 position
The (x,y) pixel coordinates in the image (top left is 0,0)
Records that a certain Star (detected in the image) corresponds to a certain CatalogStar.
int catalogIndex
An index into an array of CatalogStar objects.
int starIndex
An index into an array of Star objects.
Attitude Go(const Camera &, const Stars &, const Catalog &, const StarIdentifiers &)
Actually run the star-id algorithm.
Three dimensional vector with decimaling point components.
Vec3 Normalize() const
Create a vector pointing in the same direction with magnitude 1.
decimal MagnitudeSq() const
The square of the magnitude.
Mat3 OuterProduct(const Vec3 &) const
The outer product of two vectors.
Vec3 CrossProduct(const Vec3 &) const
Usual vector cross product.
double decimal
Definition decimal.hpp:11
#define DECIMAL_POW(base, power)
Definition decimal.hpp:39
#define DECIMAL_SQRT(x)
Definition decimal.hpp:40
LOST starting point.
std::vector< StarIdentifier > StarIdentifiers
std::vector< Star > Stars
decimal QuestEigenvalueEstimator(decimal guess, decimal a, decimal b, decimal c, decimal d, decimal s)
Approximates roots of a real function using the Newton-Raphson algorithm.
const Mat3 kIdentityMat3
3x3 identity matrix
decimal QuestCharPoly(decimal x, decimal a, decimal b, decimal c, decimal d, decimal s)
Characteristic polynomial of the quest K-matrix.
decimal QuestCharPolyPrime(decimal x, decimal a, decimal b, decimal c)
Derivitive of the characteristic polynomial of the quest K-matrix.
std::vector< CatalogStar > Catalog