|
@@ -5,7 +5,7 @@
|
5
|
5
|
* https://github.com/alemart/martins-js
|
6
|
6
|
*
|
7
|
7
|
* @license LGPL-3.0-or-later
|
8
|
|
- * Date: 2024-07-16T00:44:39.450Z
|
|
8
|
+ * Date: 2024-07-29T00:26:10.319Z
|
9
|
9
|
*/
|
10
|
10
|
(function webpackUniversalModuleDefinition(root, factory) {
|
11
|
11
|
if(typeof exports === 'object' && typeof module === 'object')
|
|
@@ -20006,8 +20006,10 @@ class BaseViewport extends ViewportEventTarget {
|
20006
|
20006
|
container.webkitRequestFullscreen();
|
20007
|
20007
|
return new (speedy_vision_default()).Promise((resolve, reject) => {
|
20008
|
20008
|
setTimeout(() => {
|
20009
|
|
- if (container === document.webkitFullscreenElement)
|
|
20009
|
+ if (container === document.webkitFullscreenElement) {
|
|
20010
|
+ Utils.log('Entering fullscreen mode...');
|
20010
|
20011
|
resolve();
|
|
20012
|
+ }
|
20011
|
20013
|
else
|
20012
|
20014
|
reject(new TypeError());
|
20013
|
20015
|
}, 100);
|
|
@@ -20020,7 +20022,10 @@ class BaseViewport extends ViewportEventTarget {
|
20020
|
20022
|
return new (speedy_vision_default()).Promise((resolve, reject) => {
|
20021
|
20023
|
container.requestFullscreen({
|
20022
|
20024
|
navigationUI: 'hide'
|
20023
|
|
- }).then(resolve, reject);
|
|
20025
|
+ }).then(() => {
|
|
20026
|
+ Utils.log('Entering fullscreen mode...');
|
|
20027
|
+ resolve();
|
|
20028
|
+ }, reject);
|
20024
|
20029
|
});
|
20025
|
20030
|
}
|
20026
|
20031
|
/**
|
|
@@ -20038,16 +20043,24 @@ class BaseViewport extends ViewportEventTarget {
|
20038
|
20043
|
doc.webkitExitFullscreen();
|
20039
|
20044
|
return new (speedy_vision_default()).Promise((resolve, reject) => {
|
20040
|
20045
|
setTimeout(() => {
|
20041
|
|
- if (doc.webkitFullscreenElement === null)
|
|
20046
|
+ if (doc.webkitFullscreenElement === null) {
|
|
20047
|
+ Utils.log('Exiting fullscreen mode...');
|
20042
|
20048
|
resolve();
|
|
20049
|
+ }
|
20043
|
20050
|
else
|
20044
|
20051
|
reject(new TypeError());
|
20045
|
20052
|
}, 100);
|
20046
|
20053
|
});
|
20047
|
20054
|
}
|
|
20055
|
+ // error if not in fullscreen mode
|
|
20056
|
+ if (document.fullscreenElement === null)
|
|
20057
|
+ return speedy_vision_default().Promise.reject(new IllegalOperationError('Not in fullscreen mode'));
|
20048
|
20058
|
// exit fullscreen
|
20049
|
20059
|
return new (speedy_vision_default()).Promise((resolve, reject) => {
|
20050
|
|
- document.exitFullscreen().then(resolve, reject);
|
|
20060
|
+ document.exitFullscreen().then(() => {
|
|
20061
|
+ Utils.log('Exiting fullscreen mode...');
|
|
20062
|
+ resolve();
|
|
20063
|
+ }, reject);
|
20051
|
20064
|
});
|
20052
|
20065
|
}
|
20053
|
20066
|
/** Is the fullscreen mode available? */
|
|
@@ -23420,18 +23433,14 @@ class ImageTrackerEvent extends AREvent {
|
23420
|
23433
|
|
23421
|
23434
|
|
23422
|
23435
|
|
23423
|
|
-/** Number of samples we'll be keeping to help calibrate the camera */
|
23424
|
|
-const INTRISICS_SAMPLES = 401; //201; //31; // odd number
|
23425
|
|
-/** Whether or not to auto-calibrate the camera */
|
23426
|
|
-const FOVY_AUTODETECT = false; //true;
|
23427
|
|
-/** A guess of the vertical field-of-view of a generic camera, in degrees */
|
23428
|
|
-const FOVY_GUESS = 45; //50; // will be part of the viewing frustum
|
|
23436
|
+/** A guess of the horizontal field-of-view of a typical camera, in degrees */
|
|
23437
|
+const HFOV_GUESS = 60; // https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Cameras/Cameras.html
|
23429
|
23438
|
/** Number of iterations used to refine the estimated pose */
|
23430
|
23439
|
const POSE_ITERATIONS = 30;
|
23431
|
23440
|
/** Number of samples used in the rotation filter */
|
23432
|
23441
|
const ROTATION_FILTER_SAMPLES = 10;
|
23433
|
23442
|
/** Number of samples used in the translation filter */
|
23434
|
|
-const TRANSLATION_FILTER_SAMPLES = 10;
|
|
23443
|
+const TRANSLATION_FILTER_SAMPLES = 5;
|
23435
|
23444
|
/** Convert degrees to radians */
|
23436
|
23445
|
const DEG2RAD = 0.017453292519943295; // pi / 180
|
23437
|
23446
|
/** Convert radians to degrees */
|
|
@@ -23446,31 +23455,6 @@ const FY = 4;
|
23446
|
23455
|
const U0 = 6;
|
23447
|
23456
|
/** Index of the vertical position of the principal point in the camera intrinsics matrix */
|
23448
|
23457
|
const V0 = 7;
|
23449
|
|
-/** Translation refinement: predefined buffers for efficiency */
|
23450
|
|
-const TRANSLATION_REFINEMENT_BUFFERS = (() => {
|
23451
|
|
- const l = 1.0;
|
23452
|
|
- const x = [0, l, 0, -l, 0];
|
23453
|
|
- const y = [-l, 0, l, 0, 0];
|
23454
|
|
- const n = x.length;
|
23455
|
|
- return Object.freeze({
|
23456
|
|
- x, y,
|
23457
|
|
- a1: new Array(n),
|
23458
|
|
- a2: new Array(n),
|
23459
|
|
- a3: new Array(n),
|
23460
|
|
- m: new Array(3 * n * 3),
|
23461
|
|
- v: new Array(3 * n),
|
23462
|
|
- t: new Array(3),
|
23463
|
|
- r: new Array(3 * n),
|
23464
|
|
- c: new Array(3),
|
23465
|
|
- Mc: new Array(3 * n),
|
23466
|
|
- });
|
23467
|
|
-})();
|
23468
|
|
-/** Translation refinement: number of iterations */
|
23469
|
|
-const TRANSLATION_REFINEMENT_ITERATIONS = 3; // 1; // 5;
|
23470
|
|
-/** Translation refinement: number of samples */
|
23471
|
|
-const TRANSLATION_REFINEMENT_SAMPLES = 5; // TRANSLATION_REFINEMENT_BUFFERS.x.length;
|
23472
|
|
-/** Translation refinement: the triple of the number of samples */
|
23473
|
|
-const TRANSLATION_REFINEMENT_SAMPLES_3X = 15; //3 * TRANSLATION_REFINEMENT_SAMPLES;
|
23474
|
23458
|
/**
|
23475
|
23459
|
* Camera model
|
23476
|
23460
|
*/
|
|
@@ -23481,10 +23465,8 @@ class CameraModel {
|
23481
|
23465
|
constructor() {
|
23482
|
23466
|
this._screenSize = speedy_vision_default().Size(0, 0);
|
23483
|
23467
|
this._matrix = speedy_vision_default().Matrix.Eye(3, 4);
|
23484
|
|
- this._intrinsics = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // identity matrix
|
23485
|
|
- this._extrinsics = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]; // no rotation & no translation [ R | t ] = [ I | 0 ]
|
23486
|
|
- this._f = (new Array(INTRISICS_SAMPLES)).fill(this._intrinsics[FY]);
|
23487
|
|
- this._fp = 0;
|
|
23468
|
+ this._intrinsics = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // 3x3 identity matrix
|
|
23469
|
+ this._extrinsics = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]; // 3x4 matrix [ R | t ] = [ I | 0 ] no rotation & no translation
|
23488
|
23470
|
this._partialRotationBuffer = [];
|
23489
|
23471
|
this._translationBuffer = [];
|
23490
|
23472
|
}
|
|
@@ -23500,8 +23482,7 @@ class CameraModel {
|
23500
|
23482
|
this._screenSize.width = screenSize.width;
|
23501
|
23483
|
this._screenSize.height = screenSize.height;
|
23502
|
23484
|
// reset the model
|
23503
|
|
- this._resetIntrinsics();
|
23504
|
|
- this._resetExtrinsics();
|
|
23485
|
+ this.reset();
|
23505
|
23486
|
// log
|
23506
|
23487
|
Utils.log(`Initializing the camera model...`);
|
23507
|
23488
|
}
|
|
@@ -23543,18 +23524,13 @@ class CameraModel {
|
23543
|
23524
|
Utils.warning(`Can't update the camera model using an invalid homography matrix`);
|
23544
|
23525
|
return speedy_vision_default().Promise.resolve(this._matrix);
|
23545
|
23526
|
}
|
23546
|
|
- // estimate the focal length (auto-calibration)
|
23547
|
|
- const f = this._estimateFocal(homography);
|
23548
|
|
- if (f > 0)
|
23549
|
|
- this._storeFocal(f);
|
23550
|
|
- //console.log(this.fovy * RAD2DEG);
|
23551
|
23527
|
// estimate the pose
|
23552
|
23528
|
const pose = this._estimatePose(homography);
|
23553
|
|
- this._storePose(pose);
|
|
23529
|
+ this._extrinsics = pose.read();
|
23554
|
23530
|
// compute the camera matrix
|
23555
|
23531
|
const C = this.denormalizer();
|
23556
|
23532
|
const K = speedy_vision_default().Matrix(3, 3, this._intrinsics);
|
23557
|
|
- const E = speedy_vision_default().Matrix(3, 4, this._extrinsics);
|
|
23533
|
+ const E = pose; //Speedy.Matrix(3, 4, this._extrinsics);
|
23558
|
23534
|
this._matrix.setToSync(K.times(E).times(C));
|
23559
|
23535
|
//console.log("intrinsics -----------", K.toString());
|
23560
|
23536
|
//console.log("matrix ----------------",this._matrix.toString());
|
|
@@ -23675,81 +23651,38 @@ class CameraModel {
|
23675
|
23651
|
* Reset camera intrinsics
|
23676
|
23652
|
*/
|
23677
|
23653
|
_resetIntrinsics() {
|
|
23654
|
+ const cameraWidth = Math.max(this._screenSize.width, this._screenSize.height); // portrait?
|
23678
|
23655
|
const u0 = this._screenSize.width / 2;
|
23679
|
23656
|
const v0 = this._screenSize.height / 2;
|
23680
|
|
- const f = v0 / Math.tan(DEG2RAD * FOVY_GUESS / 2);
|
23681
|
|
- this._intrinsics[FX] = f;
|
23682
|
|
- this._intrinsics[FY] = f;
|
|
23657
|
+ const fx = (cameraWidth / 2) / Math.tan(DEG2RAD * HFOV_GUESS / 2);
|
|
23658
|
+ const fy = fx;
|
|
23659
|
+ this._intrinsics[FX] = fx;
|
|
23660
|
+ this._intrinsics[FY] = fy;
|
23683
|
23661
|
this._intrinsics[U0] = u0;
|
23684
|
23662
|
this._intrinsics[V0] = v0;
|
23685
|
|
- this._f.fill(this._intrinsics[FY]);
|
23686
|
|
- this._fp = 0;
|
23687
|
|
- }
|
23688
|
|
- /**
|
23689
|
|
- * Estimate the focal length
|
23690
|
|
- * @param homography valid homography
|
23691
|
|
- * @returns estimated focal length, or 0 on error
|
23692
|
|
- */
|
23693
|
|
- _estimateFocal(homography) {
|
23694
|
|
- // auto-detect the focal length?
|
23695
|
|
- if (!FOVY_AUTODETECT)
|
23696
|
|
- return 0;
|
23697
|
|
- // read the entries of the homography
|
23698
|
|
- const h = homography.read();
|
23699
|
|
- const h11 = h[0], h12 = h[3]; //, h13 = h[6];
|
23700
|
|
- const h21 = h[1], h22 = h[4]; //, h23 = h[7];
|
23701
|
|
- const h31 = h[2], h32 = h[5]; //, h33 = h[8];
|
23702
|
|
- // read the principal point
|
23703
|
|
- const u0 = this._intrinsics[U0];
|
23704
|
|
- const v0 = this._intrinsics[V0];
|
23705
|
|
- // estimate the focal length based on the orthogonality
|
23706
|
|
- // constraint r1'r2 = 0 of a rotation matrix
|
23707
|
|
- const f2 = -((h11 / h31 - u0) * (h12 / h32 - u0) + (h21 / h31 - v0) * (h22 / h32 - v0));
|
23708
|
|
- // can't estimate it?
|
23709
|
|
- if (f2 < 0)
|
23710
|
|
- return this._intrinsics[FY];
|
23711
|
|
- //return 0;
|
23712
|
|
- // done!
|
23713
|
|
- return Math.sqrt(f2);
|
23714
|
|
- }
|
23715
|
|
- /**
|
23716
|
|
- * Store an estimated focal length
|
23717
|
|
- * @param f estimated focal length
|
23718
|
|
- */
|
23719
|
|
- _storeFocal(f) {
|
23720
|
|
- // store the focal length
|
23721
|
|
- this._f[this._fp] = f;
|
23722
|
|
- this._fp = (this._fp + 1) % INTRISICS_SAMPLES;
|
23723
|
|
- // take the median of the estimated focal lengths
|
23724
|
|
- const sorted = this._f.concat([]).sort((a, b) => a - b);
|
23725
|
|
- const median = sorted[sorted.length >>> 1];
|
23726
|
|
- // update the intrinsics matrix
|
23727
|
|
- this._intrinsics[FX] = this._intrinsics[FY] = median;
|
23728
|
|
- /*
|
23729
|
|
- // test
|
23730
|
|
- const u0 = this._intrinsics[U0];
|
23731
|
|
- const v0 = this._intrinsics[V0];
|
23732
|
|
- const fovx = 2 * Math.atan(u0 / median) * RAD2DEG;
|
23733
|
|
- const fovy = 2 * Math.atan(v0 / median) * RAD2DEG;
|
23734
|
|
- console.log('---------------');
|
23735
|
|
- console.log("fov:",fovx,fovy);
|
23736
|
|
- console.log("f:",median);
|
23737
|
|
- */
|
23738
|
23663
|
}
|
23739
|
23664
|
/**
|
23740
|
|
- * Compute a normalized homography H' = K^(-1) * H for an
|
|
23665
|
+ * Compute a normalized homography H^ = K^(-1) * H for an
|
23741
|
23666
|
* ideal pinhole with f = 1 and principal point = (0,0)
|
23742
|
23667
|
* @param homography homography H to be normalized
|
23743
|
|
- * @param f focal length
|
23744
|
|
- * @returns normalized homography H'
|
|
23668
|
+ * @returns normalized homography H^
|
23745
|
23669
|
*/
|
23746
|
|
- _normalizeHomography(homography, f = this._intrinsics[FY]) {
|
|
23670
|
+ _normalizeHomography(homography) {
|
23747
|
23671
|
const h = homography.read();
|
23748
|
23672
|
const u0 = this._intrinsics[U0];
|
23749
|
23673
|
const v0 = this._intrinsics[V0];
|
23750
|
|
- const h11 = h[0] - u0 * h[2], h12 = h[3] - u0 * h[5], h13 = h[6] - u0 * h[8];
|
23751
|
|
- const h21 = h[1] - v0 * h[2], h22 = h[4] - v0 * h[5], h23 = h[7] - v0 * h[8];
|
23752
|
|
- const h31 = h[2] * f, h32 = h[5] * f, h33 = h[8] * f;
|
|
23674
|
+ const fx = this._intrinsics[FX];
|
|
23675
|
+ const fy = this._intrinsics[FY];
|
|
23676
|
+ const u0fx = u0 / fx;
|
|
23677
|
+ const v0fy = v0 / fy;
|
|
23678
|
+ const h11 = h[0] / fx - u0fx * h[2], h12 = h[3] / fx - u0fx * h[5], h13 = h[6] / fx - u0fx * h[8];
|
|
23679
|
+ const h21 = h[1] / fy - v0fy * h[2], h22 = h[4] / fy - v0fy * h[5], h23 = h[7] / fy - v0fy * h[8];
|
|
23680
|
+ const h31 = h[2], h32 = h[5], h33 = h[8];
|
|
23681
|
+ /*console.log([
|
|
23682
|
+ h11, h21, h31,
|
|
23683
|
+ h12, h22, h32,
|
|
23684
|
+ h13, h23, h33,
|
|
23685
|
+ ]);*/
|
23753
|
23686
|
return speedy_vision_default().Matrix(3, 3, [
|
23754
|
23687
|
h11, h21, h31,
|
23755
|
23688
|
h12, h22, h32,
|
|
@@ -23766,53 +23699,70 @@ class CameraModel {
|
23766
|
23699
|
const h11 = h[0], h12 = h[3], h13 = h[6];
|
23767
|
23700
|
const h21 = h[1], h22 = h[4], h23 = h[7];
|
23768
|
23701
|
const h31 = h[2], h32 = h[5], h33 = h[8];
|
23769
|
|
- // select the sign so that t3 = tz > 0
|
23770
|
|
- const sign = h33 >= 0 ? 1 : -1;
|
23771
|
|
- // compute the scale factor
|
23772
|
|
- const h1norm = Math.sqrt(h11 * h11 + h21 * h21 + h31 * h31);
|
23773
|
|
- const h2norm = Math.sqrt(h12 * h12 + h22 * h22 + h32 * h32);
|
23774
|
|
- //const scale = sign * 2 / (h1norm + h2norm);
|
23775
|
|
- //const scale = sign / h1norm;
|
23776
|
|
- //const scale = sign / h2norm;
|
23777
|
|
- const scale = sign / Math.max(h1norm, h2norm); // this seems to work. why?
|
23778
|
|
- // invalid homography?
|
23779
|
|
- if (Number.isNaN(scale))
|
23780
|
|
- return speedy_vision_default().Matrix(3, 3, (new Array(9)).fill(Number.NaN));
|
|
23702
|
+ const h1norm2 = h11 * h11 + h21 * h21 + h31 * h31;
|
|
23703
|
+ const h2norm2 = h12 * h12 + h22 * h22 + h32 * h32;
|
|
23704
|
+ const h1norm = Math.sqrt(h1norm2);
|
|
23705
|
+ const h2norm = Math.sqrt(h2norm2);
|
|
23706
|
+ //const hnorm = (h1norm + h2norm) / 2;
|
|
23707
|
+ //const hnorm = Math.sqrt(h1norm * h2norm);
|
|
23708
|
+ const hnorm = Math.max(h1norm, h2norm); // this seems to work. why?
|
23781
|
23709
|
// we expect h1norm to be approximately h2norm, but sometimes there is a lot of noise
|
23782
|
23710
|
// if h1norm is not approximately h2norm, it means that the first two columns of
|
23783
|
23711
|
// the normalized homography are not really encoding a rotation (up to a scale)
|
23784
|
|
- // what is causing this? does h3 (and h33) tell us anything about it?
|
23785
|
|
- // what about the intrinsics matrix? the principal point...? the fov...?
|
23786
|
23712
|
//console.log("h1,h2",h1norm,h2norm);
|
23787
|
23713
|
//console.log(normalizedHomography.toString());
|
23788
|
|
- // recover the translation and the rotation
|
23789
|
|
- const t1 = scale * h13;
|
23790
|
|
- const t2 = scale * h23;
|
23791
|
|
- const t3 = scale * h33;
|
23792
|
|
- const r11 = scale * h11;
|
23793
|
|
- const r21 = scale * h21;
|
23794
|
|
- const r31 = scale * h31;
|
23795
|
|
- const r12 = scale * h12;
|
23796
|
|
- const r22 = scale * h22;
|
23797
|
|
- const r32 = scale * h32;
|
23798
|
|
- // refine the pose
|
23799
|
|
- const r = this._refineRotation(r11, r21, r31, r12, r22, r32);
|
23800
|
|
- const t = this._refineTranslation(normalizedHomography, r, [t1, t2, t3]);
|
23801
|
|
- //const t = [t1, t2, t3]; // faster, but less accurate
|
|
23714
|
+ // compute a rough estimate for the scale factor
|
|
23715
|
+ // select the sign so that t3 = tz > 0
|
|
23716
|
+ const sign = h33 >= 0 ? 1 : -1;
|
|
23717
|
+ let scale = sign / hnorm;
|
|
23718
|
+ // sanity check
|
|
23719
|
+ if (Number.isNaN(scale))
|
|
23720
|
+ return speedy_vision_default().Matrix(3, 3, (new Array(9)).fill(Number.NaN));
|
|
23721
|
+ // recover the rotation
|
|
23722
|
+ let r = new Array(6);
|
|
23723
|
+ r[0] = scale * h11;
|
|
23724
|
+ r[1] = scale * h21;
|
|
23725
|
+ r[2] = scale * h31;
|
|
23726
|
+ r[3] = scale * h12;
|
|
23727
|
+ r[4] = scale * h22;
|
|
23728
|
+ r[5] = scale * h32;
|
|
23729
|
+ // refine the rotation
|
|
23730
|
+ r = this._refineRotation(r); // r is initially noisy
|
|
23731
|
+ /*
|
|
23732
|
+
|
|
23733
|
+ After refining the rotation vectors, let's adjust the scale factor as
|
|
23734
|
+ follows:
|
|
23735
|
+
|
|
23736
|
+ We know that [ r1 | r2 | t ] is equal to the normalized homography H up
|
|
23737
|
+ to a non-zero scale factor s, i.e., [ r1 | r2 | t ] = s H. Let's call M
|
|
23738
|
+ the first two columns of H, i.e., M = [ h1 | h2 ], and R = [ r1 | r2 ].
|
|
23739
|
+ It follows that R = s M, meaning that M'R = s M'M. The trace of 2x2 M'R
|
|
23740
|
+ is such that tr(M'R) = tr(s M'M) = s tr(M'M), which means:
|
|
23741
|
+
|
|
23742
|
+ s = tr(M'R) / tr(M'M) = (r1'h1 + r2'h2) / (h1'h1 + h2'h2)
|
|
23743
|
+
|
|
23744
|
+ (also: s^2 = det(M'R) / det(M'M))
|
|
23745
|
+
|
|
23746
|
+ */
|
|
23747
|
+ // adjust the scale factor
|
|
23748
|
+ scale = r[0] * h11 + r[1] * h21 + r[2] * h31;
|
|
23749
|
+ scale += r[3] * h12 + r[4] * h22 + r[5] * h32;
|
|
23750
|
+ scale /= h1norm2 + h2norm2;
|
|
23751
|
+ // recover the translation
|
|
23752
|
+ let t = new Array(3);
|
|
23753
|
+ t[0] = scale * h13;
|
|
23754
|
+ t[1] = scale * h23;
|
|
23755
|
+ t[2] = scale * h33;
|
23802
|
23756
|
// done!
|
23803
|
|
- return speedy_vision_default().Matrix(3, 3, r.concat(t)); // this is possibly NaN... why? homography...
|
|
23757
|
+ return speedy_vision_default().Matrix(3, 3, r.concat(t));
|
23804
|
23758
|
}
|
23805
|
23759
|
/**
|
23806
|
23760
|
* Make two non-zero and non-parallel input vectors, r1 and r2, orthonormal
|
23807
|
|
- * @param r11 x of r1
|
23808
|
|
- * @param r21 y of r1
|
23809
|
|
- * @param r31 z of r1
|
23810
|
|
- * @param r12 x of r2
|
23811
|
|
- * @param r22 y of r2
|
23812
|
|
- * @param r32 z of r2
|
|
23761
|
+ * @param rot rotation vectors [ r1 | r2 ] in column-major format
|
23813
|
23762
|
* @returns a 3x2 matrix R such that R'R = I (column-major format)
|
23814
|
23763
|
*/
|
23815
|
|
- _refineRotation(r11, r21, r31, r12, r22, r32) {
|
|
23764
|
+ _refineRotation(rot) {
|
|
23765
|
+ const [r11, r21, r31, r12, r22, r32] = rot;
|
23816
|
23766
|
/*
|
23817
|
23767
|
|
23818
|
23768
|
A little technique I figured out to correct the rotation vectors
|
|
@@ -23925,10 +23875,6 @@ class CameraModel {
|
23925
|
23875
|
above observation. H, r1, r2 are known.
|
23926
|
23876
|
|
23927
|
23877
|
*/
|
23928
|
|
- const B = TRANSLATION_REFINEMENT_BUFFERS;
|
23929
|
|
- const n = TRANSLATION_REFINEMENT_SAMPLES;
|
23930
|
|
- const n3 = TRANSLATION_REFINEMENT_SAMPLES_3X;
|
23931
|
|
- Utils.assert(B.x.length === n);
|
23932
|
23878
|
const h = normalizedHomography.read();
|
23933
|
23879
|
const h11 = h[0], h12 = h[3], h13 = h[6];
|
23934
|
23880
|
const h21 = h[1], h22 = h[4], h23 = h[7];
|
|
@@ -23936,17 +23882,32 @@ class CameraModel {
|
23936
|
23882
|
const r11 = rot[0], r12 = rot[3];
|
23937
|
23883
|
const r21 = rot[1], r22 = rot[4];
|
23938
|
23884
|
const r31 = rot[2], r32 = rot[5];
|
23939
|
|
- // get sample points (xi, yi), 0 <= i < n
|
23940
|
|
- const x = B.x, y = B.y;
|
|
23885
|
+ // sample points [ xi yi ]' in AR screen space
|
|
23886
|
+ //const x = [ 0.5, 0.0, 1.0, 1.0, 0.0, 0.5, 1.0, 0.5, 0.0 ];
|
|
23887
|
+ //const y = [ 0.5, 0.0, 0.0, 1.0, 1.0, 0.0, 0.5, 1.0, 0.5 ];
|
|
23888
|
+ const x = [0.5, 0.0, 1.0, 1.0, 0.0];
|
|
23889
|
+ const y = [0.5, 0.0, 0.0, 1.0, 1.0];
|
|
23890
|
+ const n = x.length;
|
|
23891
|
+ const n3 = 3 * n;
|
|
23892
|
+ const width = this._screenSize.width;
|
|
23893
|
+ const height = this._screenSize.height;
|
|
23894
|
+ for (let i = 0; i < n; i++) {
|
|
23895
|
+ x[i] *= width;
|
|
23896
|
+ y[i] *= height;
|
|
23897
|
+ }
|
23941
|
23898
|
// set auxiliary values: ai = H [ xi yi 1 ]'
|
23942
|
|
- const a1 = B.a1, a2 = B.a2, a3 = B.a3;
|
|
23899
|
+ const a1 = new Array(n);
|
|
23900
|
+ const a2 = new Array(n);
|
|
23901
|
+ const a3 = new Array(n);
|
23943
|
23902
|
for (let i = 0; i < n; i++) {
|
23944
|
23903
|
a1[i] = x[i] * h11 + y[i] * h12 + h13;
|
23945
|
23904
|
a2[i] = x[i] * h21 + y[i] * h22 + h23;
|
23946
|
23905
|
a3[i] = x[i] * h31 + y[i] * h32 + h33;
|
23947
|
23906
|
}
|
23948
|
|
- // solve M t = v for t; M: 3n x 3, v: 3n x 1, t: 3 x 1 (linear least squares)
|
23949
|
|
- const m = B.m, v = B.v;
|
|
23907
|
+ // we'll solve M t = v for t with linear least squares
|
|
23908
|
+ // M: 3n x 3, v: 3n x 1, t: 3 x 1
|
|
23909
|
+ const m = new Array(3 * n * 3);
|
|
23910
|
+ const v = new Array(3 * n);
|
23950
|
23911
|
for (let i = 0, k = 0; k < n; i += 3, k++) {
|
23951
|
23912
|
m[i] = m[i + n3 + 1] = m[i + n3 + n3 + 2] = 0;
|
23952
|
23913
|
m[i + n3] = -(m[i + 1] = a3[k]);
|
|
@@ -24003,14 +23964,20 @@ class CameraModel {
|
24003
|
23964
|
where c = A'r = A'(Ax - b)
|
24004
|
23965
|
|
24005
|
23966
|
*/
|
|
23967
|
+ // gradient descent: super lightweight implementation
|
|
23968
|
+ const r = new Array(3 * n);
|
|
23969
|
+ const c = new Array(3);
|
|
23970
|
+ const Mc = new Array(3 * n);
|
24006
|
23971
|
// initial guess
|
24007
|
|
- const t = B.t;
|
|
23972
|
+ const t = new Array(3);
|
24008
|
23973
|
t[0] = t0[0];
|
24009
|
23974
|
t[1] = t0[1];
|
24010
|
23975
|
t[2] = t0[2];
|
24011
|
|
- // gradient descent: super lightweight implementation
|
24012
|
|
- const r = B.r, c = B.c, Mc = B.Mc;
|
24013
|
|
- for (let it = 0; it < TRANSLATION_REFINEMENT_ITERATIONS; it++) {
|
|
23976
|
+ // iterate
|
|
23977
|
+ const MAX_ITERATIONS = 15;
|
|
23978
|
+ const TOLERANCE = 1;
|
|
23979
|
+ for (let it = 0; it < MAX_ITERATIONS; it++) {
|
|
23980
|
+ //console.log("it",it+1);
|
24014
|
23981
|
// compute residual r = Mt - v
|
24015
|
23982
|
for (let i = 0; i < n3; i++) {
|
24016
|
23983
|
r[i] = 0;
|
|
@@ -24030,17 +23997,22 @@ class CameraModel {
|
24030
|
23997
|
for (let j = 0; j < 3; j++)
|
24031
|
23998
|
Mc[i] += m[j * n3 + i] * c[j];
|
24032
|
23999
|
}
|
24033
|
|
- // compute num = c'c and den = (Mc)'(Mc)
|
24034
|
|
- let num = 0, den = 0;
|
|
24000
|
+ // compute c'c
|
|
24001
|
+ let num = 0;
|
24035
|
24002
|
for (let i = 0; i < 3; i++)
|
24036
|
24003
|
num += c[i] * c[i];
|
|
24004
|
+ //console.log("c'c=",num);
|
|
24005
|
+ if (num < TOLERANCE)
|
|
24006
|
+ break;
|
|
24007
|
+ // compute (Mc)'(Mc)
|
|
24008
|
+ let den = 0;
|
24037
|
24009
|
for (let i = 0; i < n3; i++)
|
24038
|
24010
|
den += Mc[i] * Mc[i];
|
24039
|
|
- // compute num / den
|
|
24011
|
+ // compute frc = c'c / (Mc)'(Mc)
|
24040
|
24012
|
const frc = num / den;
|
24041
|
|
- if (Number.isNaN(frc))
|
|
24013
|
+ if (Number.isNaN(frc)) // this shouldn't happen
|
24042
|
24014
|
break;
|
24043
|
|
- // iterate: t = t - (num / den) * c
|
|
24015
|
+ // iterate: t = t - frc * c
|
24044
|
24016
|
for (let i = 0; i < 3; i++)
|
24045
|
24017
|
t[i] -= frc * c[i];
|
24046
|
24018
|
}
|
|
@@ -24084,7 +24056,7 @@ class CameraModel {
|
24084
|
24056
|
for (let j = 0; j < 6; j++)
|
24085
|
24057
|
avg[j] += r[j] / n;
|
24086
|
24058
|
}
|
24087
|
|
- const r = this._refineRotation(avg[0], avg[1], avg[2], avg[3], avg[4], avg[5]);
|
|
24059
|
+ const r = this._refineRotation(avg);
|
24088
|
24060
|
// average translations
|
24089
|
24061
|
const m = this._translationBuffer.length;
|
24090
|
24062
|
for (let i = 0; i < m; i++) {
|
|
@@ -24129,51 +24101,38 @@ class CameraModel {
|
24129
|
24101
|
/**
|
24130
|
24102
|
* Estimate the pose [ R | t ] given a homography in AR screen space
|
24131
|
24103
|
* @param homography must be valid
|
24132
|
|
- * @param f focal length
|
24133
|
24104
|
* @returns 3x4 matrix
|
24134
|
24105
|
*/
|
24135
|
|
- _estimatePose(homography, f = this._intrinsics[FY]) {
|
24136
|
|
- const normalizedHomography = this._normalizeHomography(homography, f);
|
|
24106
|
+ _estimatePose(homography) {
|
|
24107
|
+ const normalizedHomography = this._normalizeHomography(homography);
|
24137
|
24108
|
const partialPose = speedy_vision_default().Matrix.Eye(3);
|
24138
|
24109
|
// we want the estimated partial pose [ r1 | r2 | t ] to be as close
|
24139
|
24110
|
// as possible to the normalized homography, up to a scale factor;
|
24140
|
24111
|
// i.e., H * [ r1 | r2 | t ]^(-1) = s * I for a non-zero scalar s
|
24141
|
|
- // it won't be a perfect equality due to noise in the homography
|
|
24112
|
+ // it won't be a perfect equality due to noise in the homography.
|
|
24113
|
+ // remark: composition of homographies
|
24142
|
24114
|
const residual = speedy_vision_default().Matrix(normalizedHomography);
|
24143
|
24115
|
for (let k = 0; k < POSE_ITERATIONS; k++) {
|
24144
|
24116
|
// incrementally improve the partial pose
|
24145
|
24117
|
const rt = this._estimatePartialPose(residual); // rt should converge to the identity matrix
|
24146
|
24118
|
partialPose.setToSync(rt.times(partialPose));
|
24147
|
24119
|
residual.setToSync(residual.times(rt.inverse()));
|
|
24120
|
+ //console.log("rt",rt.toString());
|
24148
|
24121
|
//console.log("residual",residual.toString());
|
24149
|
24122
|
}
|
24150
|
24123
|
//console.log('-----------');
|
24151
|
|
- /*
|
24152
|
|
- // test
|
24153
|
|
- const result = Speedy.Matrix.Zeros(3);
|
24154
|
|
- result.setToSync(partialPose.times(normalizedHomography.inverse()));
|
24155
|
|
- const m11 = result.at(0,0);
|
24156
|
|
- result.setToSync(result.times(1/m11));
|
24157
|
|
- console.log("Pose * NORMALIZED HOM^-1", result.toString());
|
24158
|
|
- */
|
24159
|
|
- /*
|
24160
|
|
- const rt = partialPose.read();
|
24161
|
|
- const r = rt.slice(0, 6);
|
24162
|
|
- const t = this._refineTranslation(normalizedHomography, r, rt.slice(6, 9));
|
24163
|
|
- const refinedPartialPose = Speedy.Matrix(3, 3, r.concat(t));
|
24164
|
|
- const filteredPartialPose = this._filterPartialPose(refinedPartialPose);
|
24165
|
|
- */
|
|
24124
|
+ // refine the translation vector
|
|
24125
|
+ const mat = partialPose.read();
|
|
24126
|
+ const r = mat.slice(0, 6);
|
|
24127
|
+ const t0 = mat.slice(6, 9);
|
|
24128
|
+ const t = this._refineTranslation(normalizedHomography, r, t0);
|
|
24129
|
+ const refinedPartialPose = speedy_vision_default().Matrix(3, 3, r.concat(t));
|
24166
|
24130
|
// filter the partial pose
|
24167
|
|
- const filteredPartialPose = this._filterPartialPose(partialPose);
|
|
24131
|
+ const filteredPartialPose = this._filterPartialPose(refinedPartialPose);
|
24168
|
24132
|
// estimate the full pose
|
24169
|
|
- return this._estimateFullPose(filteredPartialPose);
|
24170
|
|
- }
|
24171
|
|
- /**
|
24172
|
|
- * Store an estimated pose
|
24173
|
|
- * @param pose 3x4 matrix
|
24174
|
|
- */
|
24175
|
|
- _storePose(pose) {
|
24176
|
|
- this._extrinsics = pose.read();
|
|
24133
|
+ //const finalPartialPose = partialPose;
|
|
24134
|
+ const finalPartialPose = filteredPartialPose;
|
|
24135
|
+ return this._estimateFullPose(finalPartialPose);
|
24177
|
24136
|
}
|
24178
|
24137
|
}
|
24179
|
24138
|
|