|
@@ -28,6 +28,7 @@ import { SpeedyVector2 } from 'speedy-vision/types/core/speedy-vector';
|
28
|
28
|
import { SpeedyPromise } from 'speedy-vision/types/core/speedy-promise';
|
29
|
29
|
import { Nullable, Utils } from '../utils/utils';
|
30
|
30
|
import { Settings } from '../core/settings';
|
|
31
|
+import { PoseFilter } from './pose-filter';
|
31
|
32
|
import { IllegalOperationError, IllegalArgumentError } from '../utils/errors';
|
32
|
33
|
|
33
|
34
|
/** A guess of the horizontal field-of-view of a typical camera, in degrees */
|
|
@@ -36,12 +37,6 @@ const HFOV_GUESS = 60; // https://developer.apple.com/library/archive/documentat
|
36
|
37
|
/** Number of iterations used to refine the estimated pose */
|
37
|
38
|
const POSE_ITERATIONS = 30;
|
38
|
39
|
|
39
|
|
-/** Number of samples used in the rotation filter */
|
40
|
|
-const ROTATION_FILTER_SAMPLES = 10;
|
41
|
|
-
|
42
|
|
-/** Number of samples used in the translation filter */
|
43
|
|
-const TRANSLATION_FILTER_SAMPLES = 5;
|
44
|
|
-
|
45
|
40
|
/** Convert degrees to radians */
|
46
|
41
|
const DEG2RAD = 0.017453292519943295; // pi / 180
|
47
|
42
|
|
|
@@ -83,11 +78,8 @@ export class CameraModel
|
83
|
78
|
/** extrinsics matrix, in column-major format */
|
84
|
79
|
private _extrinsics: number[];
|
85
|
80
|
|
86
|
|
- /** filter: samples of partial rotation matrix [ r1 | r2 ] */
|
87
|
|
- private _partialRotationBuffer: number[][];
|
88
|
|
-
|
89
|
|
- /** filter: samples of translation vector t */
|
90
|
|
- private _translationBuffer: number[][];
|
|
81
|
+ /** smoothing filter */
|
|
82
|
+ private _filter: PoseFilter;
|
91
|
83
|
|
92
|
84
|
|
93
|
85
|
|
|
@@ -100,8 +92,7 @@ export class CameraModel
|
100
|
92
|
this._matrix = Speedy.Matrix.Eye(3, 4);
|
101
|
93
|
this._intrinsics = [1,0,0,0,1,0,0,0,1]; // 3x3 identity matrix
|
102
|
94
|
this._extrinsics = [1,0,0,0,1,0,0,0,1,0,0,0]; // 3x4 matrix [ R | t ] = [ I | 0 ] no rotation & no translation
|
103
|
|
- this._partialRotationBuffer = [];
|
104
|
|
- this._translationBuffer = [];
|
|
95
|
+ this._filter = new PoseFilter();
|
105
|
96
|
}
|
106
|
97
|
|
107
|
98
|
/**
|
|
@@ -177,12 +168,13 @@ export class CameraModel
|
177
|
168
|
|
178
|
169
|
// estimate the pose
|
179
|
170
|
const pose = this._estimatePose(homography);
|
180
|
|
- this._extrinsics = pose.read();
|
|
171
|
+ if(this._filter.feed(pose))
|
|
172
|
+ this._extrinsics = this._filter.run().read();
|
181
|
173
|
|
182
|
174
|
// compute the camera matrix
|
183
|
175
|
const C = this.denormalizer();
|
184
|
176
|
const K = Speedy.Matrix(3, 3, this._intrinsics);
|
185
|
|
- const E = pose; //Speedy.Matrix(3, 4, this._extrinsics);
|
|
177
|
+ const E = Speedy.Matrix(3, 4, this._extrinsics);
|
186
|
178
|
this._matrix.setToSync(K.times(E).times(C));
|
187
|
179
|
//console.log("intrinsics -----------", K.toString());
|
188
|
180
|
//console.log("matrix ----------------",this._matrix.toString());
|
|
@@ -320,9 +312,8 @@ export class CameraModel
|
320
|
312
|
this._extrinsics.fill(0);
|
321
|
313
|
this._extrinsics[0] = this._extrinsics[4] = this._extrinsics[8] = 1;
|
322
|
314
|
|
323
|
|
- // reset filters
|
324
|
|
- this._partialRotationBuffer.length = 0;
|
325
|
|
- this._translationBuffer.length = 0;
|
|
315
|
+ // reset filter
|
|
316
|
+ this._filter.reset();
|
326
|
317
|
}
|
327
|
318
|
|
328
|
319
|
/**
|
|
@@ -755,72 +746,15 @@ export class CameraModel
|
755
|
746
|
}
|
756
|
747
|
|
757
|
748
|
/**
|
758
|
|
- * Apply a smoothing filter to the partial pose
|
759
|
|
- * @param partialPose 3x3 [ r1 | r2 | t ]
|
760
|
|
- * @returns filtered partial pose
|
761
|
|
- */
|
762
|
|
- private _filterPartialPose(partialPose: SpeedyMatrix): SpeedyMatrix
|
763
|
|
- {
|
764
|
|
- const avg: number[] = new Array(9).fill(0);
|
765
|
|
- const entries = partialPose.read();
|
766
|
|
- const rotationBlock = entries.slice(0, 6);
|
767
|
|
- const translationBlock = entries.slice(6, 9);
|
768
|
|
-
|
769
|
|
- // how many samples should we store, at most?
|
770
|
|
- const div = (Settings.powerPreference == 'low-power') ? 1.5 : 1; // low-power ~ half the fps
|
771
|
|
- const N = Math.ceil(ROTATION_FILTER_SAMPLES / div);
|
772
|
|
- const M = Math.ceil(TRANSLATION_FILTER_SAMPLES / div);
|
773
|
|
-
|
774
|
|
- // is it a valid partial pose?
|
775
|
|
- if(!Number.isNaN(entries[0])) {
|
776
|
|
- // store samples
|
777
|
|
- this._partialRotationBuffer.unshift(rotationBlock);
|
778
|
|
- if(this._partialRotationBuffer.length > N)
|
779
|
|
- this._partialRotationBuffer.length = N;
|
780
|
|
-
|
781
|
|
- this._translationBuffer.unshift(translationBlock);
|
782
|
|
- if(this._translationBuffer.length > M)
|
783
|
|
- this._translationBuffer.length = M;
|
784
|
|
- }
|
785
|
|
- else if(this._partialRotationBuffer.length == 0) {
|
786
|
|
- // invalid pose, no samples
|
787
|
|
- return Speedy.Matrix.Eye(3);
|
788
|
|
- }
|
789
|
|
-
|
790
|
|
- // average *nearby* rotations
|
791
|
|
- const n = this._partialRotationBuffer.length;
|
792
|
|
- for(let i = 0; i < n; i++) {
|
793
|
|
- const r = this._partialRotationBuffer[i];
|
794
|
|
- for(let j = 0; j < 6; j++)
|
795
|
|
- avg[j] += r[j] / n;
|
796
|
|
- }
|
797
|
|
- const r = this._refineRotation(avg);
|
798
|
|
-
|
799
|
|
- // average translations
|
800
|
|
- const m = this._translationBuffer.length;
|
801
|
|
- for(let i = 0; i < m; i++) {
|
802
|
|
- const t = this._translationBuffer[i];
|
803
|
|
- for(let j = 0; j < 3; j++)
|
804
|
|
- avg[6 + j] += (m - i) * t[j] / ((m * m + m) / 2);
|
805
|
|
- //avg[6 + j] += t[j] / m;
|
806
|
|
- }
|
807
|
|
- const t = [ avg[6], avg[7], avg[8] ];
|
808
|
|
-
|
809
|
|
- // done!
|
810
|
|
- return Speedy.Matrix(3, 3, r.concat(t));
|
811
|
|
- }
|
812
|
|
-
|
813
|
|
- /**
|
814
|
|
- * Estimate extrinsics [ R | t ] given a partial pose [ r1 | r2 | t ]
|
815
|
|
- * @param partialPose
|
816
|
|
- * @returns 3x4 matrix
|
|
749
|
+ * Find a 3x3 rotation matrix R given two orthonormal vectors [ r1 | r2 ]
|
|
750
|
+ * @param partialRotation partial rotation matrix [ r1 | r2 ] in column-major format
|
|
751
|
+ * @returns a rotation matrix R in column-major format
|
817
|
752
|
*/
|
818
|
|
- private _estimateFullPose(partialPose: SpeedyMatrix): SpeedyMatrix
|
|
753
|
+ private _computeFullRotation(partialRotation: number[]): number[]
|
819
|
754
|
{
|
820
|
|
- const p = partialPose.read();
|
821
|
|
- const r11 = p[0], r12 = p[3], t1 = p[6];
|
822
|
|
- const r21 = p[1], r22 = p[4], t2 = p[7];
|
823
|
|
- const r31 = p[2], r32 = p[5], t3 = p[8];
|
|
755
|
+ const r11 = partialRotation[0], r12 = partialRotation[3];
|
|
756
|
+ const r21 = partialRotation[1], r22 = partialRotation[4];
|
|
757
|
+ const r31 = partialRotation[2], r32 = partialRotation[5];
|
824
|
758
|
|
825
|
759
|
// r3 = +- ( r1 x r2 )
|
826
|
760
|
let r13 = r21 * r32 - r31 * r22;
|
|
@@ -836,12 +770,11 @@ export class CameraModel
|
836
|
770
|
}
|
837
|
771
|
|
838
|
772
|
// done!
|
839
|
|
- return Speedy.Matrix(3, 4, [
|
|
773
|
+ return [
|
840
|
774
|
r11, r21, r31,
|
841
|
775
|
r12, r22, r32,
|
842
|
|
- r13, r23, r33,
|
843
|
|
- t1, t2, t3,
|
844
|
|
- ]);
|
|
776
|
+ r13, r23, r33
|
|
777
|
+ ];
|
845
|
778
|
}
|
846
|
779
|
|
847
|
780
|
/**
|
|
@@ -870,19 +803,16 @@ export class CameraModel
|
870
|
803
|
}
|
871
|
804
|
//console.log('-----------');
|
872
|
805
|
|
873
|
|
- // refine the translation vector
|
|
806
|
+ // read the partial pose
|
874
|
807
|
const mat = partialPose.read();
|
875
|
|
- const r = mat.slice(0, 6);
|
|
808
|
+ const r0 = mat.slice(0, 6);
|
876
|
809
|
const t0 = mat.slice(6, 9);
|
877
|
|
- const t = this._refineTranslation(normalizedHomography, r, t0);
|
878
|
|
- const refinedPartialPose = Speedy.Matrix(3, 3, r.concat(t));
|
879
|
810
|
|
880
|
|
- // filter the partial pose
|
881
|
|
- const filteredPartialPose = this._filterPartialPose(refinedPartialPose);
|
|
811
|
+ // refine the translation vector and compute the full rotation matrix
|
|
812
|
+ const t = this._refineTranslation(normalizedHomography, r0, t0);
|
|
813
|
+ const r = this._computeFullRotation(r0);
|
882
|
814
|
|
883
|
|
- // estimate the full pose
|
884
|
|
- //const finalPartialPose = partialPose;
|
885
|
|
- const finalPartialPose = filteredPartialPose;
|
886
|
|
- return this._estimateFullPose(finalPartialPose);
|
|
815
|
+ // done!
|
|
816
|
+ return Speedy.Matrix(3, 4, r.concat(t));
|
887
|
817
|
}
|
888
|
818
|
}
|