@driftengine/capture 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +29 -0
  3. package/README.md +68 -0
  4. package/dist/browserFrames.d.ts +31 -0
  5. package/dist/browserFrames.js +105 -0
  6. package/dist/collide.d.ts +59 -0
  7. package/dist/collide.js +92 -0
  8. package/dist/decimate.d.ts +42 -0
  9. package/dist/decimate.js +439 -0
  10. package/dist/delight.d.ts +42 -0
  11. package/dist/delight.js +351 -0
  12. package/dist/delightScene.d.ts +37 -0
  13. package/dist/delightScene.js +183 -0
  14. package/dist/depth.d.ts +44 -0
  15. package/dist/depth.js +53 -0
  16. package/dist/features.d.ts +33 -0
  17. package/dist/features.js +260 -0
  18. package/dist/frames.d.ts +47 -0
  19. package/dist/frames.js +87 -0
  20. package/dist/fusion.d.ts +77 -0
  21. package/dist/fusion.js +152 -0
  22. package/dist/gaussians/adam.d.ts +57 -0
  23. package/dist/gaussians/adam.js +88 -0
  24. package/dist/gaussians/depth.d.ts +8 -0
  25. package/dist/gaussians/depth.js +73 -0
  26. package/dist/gaussians/gradients.d.ts +36 -0
  27. package/dist/gaussians/gradients.js +228 -0
  28. package/dist/gaussians/harmonics.d.ts +46 -0
  29. package/dist/gaussians/harmonics.js +99 -0
  30. package/dist/gaussians/optimise.d.ts +50 -0
  31. package/dist/gaussians/optimise.js +125 -0
  32. package/dist/gaussians/parameters.d.ts +35 -0
  33. package/dist/gaussians/parameters.js +165 -0
  34. package/dist/gaussians/project.d.ts +72 -0
  35. package/dist/gaussians/project.js +137 -0
  36. package/dist/gaussians/projectGradient.d.ts +15 -0
  37. package/dist/gaussians/projectGradient.js +213 -0
  38. package/dist/gaussians/rasterise.d.ts +13 -0
  39. package/dist/gaussians/rasterise.js +65 -0
  40. package/dist/gaussians/refine.d.ts +36 -0
  41. package/dist/gaussians/refine.js +136 -0
  42. package/dist/gaussians/similarity.d.ts +25 -0
  43. package/dist/gaussians/similarity.js +80 -0
  44. package/dist/index.d.ts +101 -0
  45. package/dist/index.js +96 -0
  46. package/dist/marching.d.ts +38 -0
  47. package/dist/marching.js +258 -0
  48. package/dist/masks.d.ts +59 -0
  49. package/dist/masks.js +165 -0
  50. package/dist/math/dense.d.ts +68 -0
  51. package/dist/math/dense.js +294 -0
  52. package/dist/math/schur.d.ts +23 -0
  53. package/dist/math/schur.js +140 -0
  54. package/dist/models/batchNorm.d.ts +17 -0
  55. package/dist/models/batchNorm.js +24 -0
  56. package/dist/models/clipTokenizer.d.ts +40 -0
  57. package/dist/models/clipTokenizer.js +122 -0
  58. package/dist/models/clipTower.d.ts +27 -0
  59. package/dist/models/clipTower.js +45 -0
  60. package/dist/models/depthAnything2.d.ts +38 -0
  61. package/dist/models/depthAnything2.js +163 -0
  62. package/dist/models/depthAnything3.d.ts +26 -0
  63. package/dist/models/depthAnything3.js +171 -0
  64. package/dist/models/dualDpt.d.ts +19 -0
  65. package/dist/models/dualDpt.js +138 -0
  66. package/dist/models/hiera.d.ts +49 -0
  67. package/dist/models/hiera.js +120 -0
  68. package/dist/models/hieraAttention.d.ts +16 -0
  69. package/dist/models/hieraAttention.js +68 -0
  70. package/dist/models/miniature.d.ts +36 -0
  71. package/dist/models/miniature.js +216 -0
  72. package/dist/models/mobileSam.d.ts +41 -0
  73. package/dist/models/mobileSam.js +88 -0
  74. package/dist/models/owlv2.d.ts +49 -0
  75. package/dist/models/owlv2.js +131 -0
  76. package/dist/models/owlv2Detect.d.ts +25 -0
  77. package/dist/models/owlv2Detect.js +80 -0
  78. package/dist/models/owlv2Miniature.d.ts +27 -0
  79. package/dist/models/owlv2Miniature.js +87 -0
  80. package/dist/models/rope.d.ts +23 -0
  81. package/dist/models/rope.js +84 -0
  82. package/dist/models/sam21.d.ts +45 -0
  83. package/dist/models/sam21.js +126 -0
  84. package/dist/models/sam21Miniature.d.ts +37 -0
  85. package/dist/models/sam21Miniature.js +194 -0
  86. package/dist/models/sam21Video.d.ts +30 -0
  87. package/dist/models/sam21Video.js +101 -0
  88. package/dist/models/sam2Choice.d.ts +19 -0
  89. package/dist/models/sam2Choice.js +55 -0
  90. package/dist/models/sam2Decoder.d.ts +36 -0
  91. package/dist/models/sam2Decoder.js +108 -0
  92. package/dist/models/sam2Memories.d.ts +54 -0
  93. package/dist/models/sam2Memories.js +93 -0
  94. package/dist/models/sam2Memory.d.ts +30 -0
  95. package/dist/models/sam2Memory.js +136 -0
  96. package/dist/models/sam2Positions.d.ts +24 -0
  97. package/dist/models/sam2Positions.js +94 -0
  98. package/dist/models/sam2Tracker.d.ts +69 -0
  99. package/dist/models/sam2Tracker.js +172 -0
  100. package/dist/models/samDecoder.d.ts +45 -0
  101. package/dist/models/samDecoder.js +96 -0
  102. package/dist/models/samMiniature.d.ts +43 -0
  103. package/dist/models/samMiniature.js +161 -0
  104. package/dist/models/samPrompt.d.ts +57 -0
  105. package/dist/models/samPrompt.js +160 -0
  106. package/dist/models/seeded.d.ts +16 -0
  107. package/dist/models/seeded.js +48 -0
  108. package/dist/models/tinyVit.d.ts +38 -0
  109. package/dist/models/tinyVit.js +82 -0
  110. package/dist/models/twoWay.d.ts +46 -0
  111. package/dist/models/twoWay.js +67 -0
  112. package/dist/models/vit.d.ts +45 -0
  113. package/dist/models/vit.js +157 -0
  114. package/dist/models/windowAttention.d.ts +25 -0
  115. package/dist/models/windowAttention.js +104 -0
  116. package/dist/poses.d.ts +31 -0
  117. package/dist/poses.js +750 -0
  118. package/dist/prepare.d.ts +30 -0
  119. package/dist/prepare.js +238 -0
  120. package/dist/propose.d.ts +36 -0
  121. package/dist/propose.js +63 -0
  122. package/dist/resample.d.ts +39 -0
  123. package/dist/resample.js +165 -0
  124. package/dist/run.d.ts +3 -0
  125. package/dist/run.js +1 -0
  126. package/dist/scene.d.ts +104 -0
  127. package/dist/scene.js +109 -0
  128. package/dist/segment.d.ts +40 -0
  129. package/dist/segment.js +184 -0
  130. package/dist/testScene.d.ts +55 -0
  131. package/dist/testScene.js +214 -0
  132. package/dist/twoView.d.ts +22 -0
  133. package/dist/twoView.js +435 -0
  134. package/package.json +63 -0
  135. package/src/browserFrames.ts +145 -0
  136. package/src/collide.ts +128 -0
  137. package/src/decimate.ts +510 -0
  138. package/src/delight.ts +427 -0
  139. package/src/delightScene.ts +249 -0
  140. package/src/depth.ts +100 -0
  141. package/src/features.ts +302 -0
  142. package/src/frames.ts +111 -0
  143. package/src/fusion.ts +229 -0
  144. package/src/gaussians/adam.ts +131 -0
  145. package/src/gaussians/depth.ts +87 -0
  146. package/src/gaussians/gradients.ts +285 -0
  147. package/src/gaussians/harmonics.ts +122 -0
  148. package/src/gaussians/optimise.ts +191 -0
  149. package/src/gaussians/parameters.ts +186 -0
  150. package/src/gaussians/project.ts +207 -0
  151. package/src/gaussians/projectGradient.ts +234 -0
  152. package/src/gaussians/rasterise.ts +81 -0
  153. package/src/gaussians/refine.ts +176 -0
  154. package/src/gaussians/similarity.ts +87 -0
  155. package/src/index.ts +225 -0
  156. package/src/marching.ts +358 -0
  157. package/src/masks.ts +201 -0
  158. package/src/math/dense.ts +317 -0
  159. package/src/math/schur.ts +167 -0
  160. package/src/models/batchNorm.ts +57 -0
  161. package/src/models/clipTokenizer.ts +129 -0
  162. package/src/models/clipTower.ts +87 -0
  163. package/src/models/depthAnything2.ts +276 -0
  164. package/src/models/depthAnything3.ts +229 -0
  165. package/src/models/dualDpt.ts +233 -0
  166. package/src/models/hiera.ts +231 -0
  167. package/src/models/hieraAttention.ts +121 -0
  168. package/src/models/miniature.ts +241 -0
  169. package/src/models/mobileSam.ts +135 -0
  170. package/src/models/owlv2.ts +190 -0
  171. package/src/models/owlv2Detect.ts +116 -0
  172. package/src/models/owlv2Miniature.ts +108 -0
  173. package/src/models/rope.ts +111 -0
  174. package/src/models/sam21.ts +182 -0
  175. package/src/models/sam21Miniature.ts +225 -0
  176. package/src/models/sam21Video.ts +154 -0
  177. package/src/models/sam2Choice.ts +62 -0
  178. package/src/models/sam2Decoder.ts +218 -0
  179. package/src/models/sam2Memories.ts +165 -0
  180. package/src/models/sam2Memory.ts +247 -0
  181. package/src/models/sam2Positions.ts +106 -0
  182. package/src/models/sam2Tracker.ts +300 -0
  183. package/src/models/samDecoder.ts +225 -0
  184. package/src/models/samMiniature.ts +190 -0
  185. package/src/models/samPrompt.ts +227 -0
  186. package/src/models/seeded.ts +50 -0
  187. package/src/models/tinyVit.ts +178 -0
  188. package/src/models/twoWay.ts +122 -0
  189. package/src/models/vit.ts +265 -0
  190. package/src/models/windowAttention.ts +155 -0
  191. package/src/poses.ts +894 -0
  192. package/src/prepare.ts +306 -0
  193. package/src/propose.ts +96 -0
  194. package/src/resample.ts +197 -0
  195. package/src/run.ts +7 -0
  196. package/src/scene.ts +141 -0
  197. package/src/segment.ts +220 -0
  198. package/src/testScene.ts +283 -0
  199. package/src/twoView.ts +536 -0
package/src/twoView.ts ADDED
@@ -0,0 +1,536 @@
1
+ /**
2
+ * The pose between two views, and the points it puts in front of them.
3
+ *
4
+ * **Two models are fitted, not one.** An essential matrix explains a pair that moved; a homography
5
+ * explains a pair that turned, or one looking at a plane. Both are fitted by RANSAC and scored the
6
+ * same way, and the homography wins where it explains what the essential explains — because a
7
+ * camera that only turned can be fitted with a translation that reproduces both pictures exactly
8
+ * and means nothing. **That is the failure this exists to prevent**: the translation would go into
9
+ * the reconstruction as a baseline, every triangulated point would take its scale from it, and
10
+ * nothing downstream could tell.
11
+ *
12
+ * **So parallax is measured and reported**, as the median angle between the two rays to a point.
13
+ * Below a threshold the pair is a turn: the rotation is answered, the translation is zeroed, and
14
+ * the caller is told which model spoke.
15
+ *
16
+ * **RANSAC takes its randomness as an argument.** A capture must give the same answer twice, so the
17
+ * caller passes the generator — a seeded one — rather than this reaching for `Math.random`.
18
+ */
19
+ import { exactAcos } from '@driftengine/core';
20
+
21
+ import { cholesky, choleskySolve, svd3, svdN } from './math/dense.ts';
22
+
23
+ export interface RelativePoseOut {
24
+ /** 3 × 3 row-major: the second camera's rotation relative to the first. */
25
+ readonly rotation: Float64Array;
26
+ /** A unit direction, or zeros where the pair has no parallax to fix one. */
27
+ readonly translation: Float64Array;
28
+ }
29
+
30
+ export interface RelativePoseResult {
31
+ readonly inliers: number;
32
+ readonly model: 'essential' | 'homography';
33
+ /** The median angle between a point's two rays, in degrees. */
34
+ readonly parallax: number;
35
+ }
36
+
37
+ /** How many samples RANSAC draws, and how far a point may sit from its epipolar line. */
38
+ const TRIALS = 200;
39
+ const THRESHOLD = 1.5;
40
+ /** A pair with less parallax than this, in degrees, is a turn rather than a move. */
41
+ const TURNED = 1;
42
+ /** The homography wins when it explains at least this share of what both models explain. */
43
+ const PLANAR = 0.45;
44
+
45
+ /** The last right-singular vector of `rows × cols`: the least-squares null direction. */
46
+ function nullVector(rows: Float64Array, count: number, cols: number): Float64Array {
47
+ const u = new Float64Array(count * cols);
48
+ const s = new Float64Array(cols);
49
+ const v = new Float64Array(cols * cols);
50
+ svdN(rows, count, cols, u, s, v);
51
+ const out = new Float64Array(cols);
52
+ for (let r = 0; r < cols; r += 1) out[r] = v[r * cols + cols - 1] as number;
53
+ return out;
54
+ }
55
+
56
+ /** An essential matrix from eight or more normalised correspondences. */
57
+ function essentialFrom(a: Float64Array, b: Float64Array, picks: readonly number[]): Float64Array {
58
+ const rows = new Float64Array(Math.max(9, picks.length) * 9);
59
+ picks.forEach((at, row) => {
60
+ const [x1, y1] = [a[at * 2] as number, a[at * 2 + 1] as number];
61
+ const [x2, y2] = [b[at * 2] as number, b[at * 2 + 1] as number];
62
+ const terms = [x2 * x1, x2 * y1, x2, y2 * x1, y2 * y1, y2, x1, y1, 1];
63
+ for (let c = 0; c < 9; c += 1) rows[row * 9 + c] = terms[c] as number;
64
+ });
65
+ const flat = nullVector(rows, Math.max(9, picks.length), 9);
66
+ /* An essential matrix has two equal singular values and a third of zero: make it one. */
67
+ const u = new Float64Array(9);
68
+ const s = new Float64Array(3);
69
+ const v = new Float64Array(9);
70
+ svd3(flat, u, s, v);
71
+ const scale = ((s[0] as number) + (s[1] as number)) / 2;
72
+ const out = new Float64Array(9);
73
+ for (let r = 0; r < 3; r += 1) {
74
+ for (let c = 0; c < 3; c += 1) {
75
+ out[r * 3 + c] =
76
+ scale *
77
+ ((u[r * 3] as number) * (v[c * 3] as number) +
78
+ (u[r * 3 + 1] as number) * (v[c * 3 + 1] as number));
79
+ }
80
+ }
81
+ return out;
82
+ }
83
+
84
+ /** A homography from four or more normalised correspondences. */
85
+ function homographyFrom(a: Float64Array, b: Float64Array, picks: readonly number[]): Float64Array {
86
+ const count = Math.max(9, picks.length * 2);
87
+ const rows = new Float64Array(count * 9);
88
+ picks.forEach((at, pick) => {
89
+ const [x1, y1] = [a[at * 2] as number, a[at * 2 + 1] as number];
90
+ const [x2, y2] = [b[at * 2] as number, b[at * 2 + 1] as number];
91
+ const first = [-x1, -y1, -1, 0, 0, 0, x2 * x1, x2 * y1, x2];
92
+ const second = [0, 0, 0, -x1, -y1, -1, y2 * x1, y2 * y1, y2];
93
+ for (let c = 0; c < 9; c += 1) {
94
+ rows[pick * 2 * 9 + c] = first[c] as number;
95
+ rows[(pick * 2 + 1) * 9 + c] = second[c] as number;
96
+ }
97
+ });
98
+ return nullVector(rows, count, 9);
99
+ }
100
+
101
+ /** Sampson's distance from an essential matrix, in pixels of the first camera. */
102
+ function essentialError(
103
+ e: Float64Array,
104
+ a: Float64Array,
105
+ b: Float64Array,
106
+ at: number,
107
+ focal: number,
108
+ ): number {
109
+ const x1 = [a[at * 2] as number, a[at * 2 + 1] as number, 1];
110
+ const x2 = [b[at * 2] as number, b[at * 2 + 1] as number, 1];
111
+ const ex1 = [0, 0, 0];
112
+ const etx2 = [0, 0, 0];
113
+ for (let r = 0; r < 3; r += 1) {
114
+ for (let c = 0; c < 3; c += 1) {
115
+ ex1[r] = (ex1[r] as number) + (e[r * 3 + c] as number) * (x1[c] as number);
116
+ etx2[c] = (etx2[c] as number) + (e[r * 3 + c] as number) * (x2[r] as number);
117
+ }
118
+ }
119
+ const numerator =
120
+ (x2[0] as number) * (ex1[0] as number) +
121
+ (x2[1] as number) * (ex1[1] as number) +
122
+ (x2[2] as number) * (ex1[2] as number);
123
+ const denominator =
124
+ (ex1[0] as number) * (ex1[0] as number) +
125
+ (ex1[1] as number) * (ex1[1] as number) +
126
+ (etx2[0] as number) * (etx2[0] as number) +
127
+ (etx2[1] as number) * (etx2[1] as number);
128
+ if (denominator === 0) return Infinity;
129
+ return (Math.abs(numerator) / Math.sqrt(denominator)) * focal;
130
+ }
131
+
132
+ /** The symmetric transfer error of a homography, in pixels. */
133
+ function homographyError(
134
+ h: Float64Array,
135
+ a: Float64Array,
136
+ b: Float64Array,
137
+ at: number,
138
+ focal: number,
139
+ ): number {
140
+ const apply = (m: Float64Array, x: number, y: number): [number, number] | null => {
141
+ const u = (m[0] as number) * x + (m[1] as number) * y + (m[2] as number);
142
+ const v = (m[3] as number) * x + (m[4] as number) * y + (m[5] as number);
143
+ const w = (m[6] as number) * x + (m[7] as number) * y + (m[8] as number);
144
+ return w === 0 ? null : [u / w, v / w];
145
+ };
146
+ const inverse = invert3(h);
147
+ if (inverse === null) return Infinity;
148
+ const forward = apply(h, a[at * 2] as number, a[at * 2 + 1] as number);
149
+ const backward = apply(inverse, b[at * 2] as number, b[at * 2 + 1] as number);
150
+ if (forward === null || backward === null) return Infinity;
151
+ /* A root of a sum, not `Math.hypot`, which the determinism gate refuses. */
152
+ const forwardX = forward[0] - (b[at * 2] as number);
153
+ const forwardY = forward[1] - (b[at * 2 + 1] as number);
154
+ const backwardX = backward[0] - (a[at * 2] as number);
155
+ const backwardY = backward[1] - (a[at * 2 + 1] as number);
156
+ const first = Math.sqrt(forwardX * forwardX + forwardY * forwardY);
157
+ const second = Math.sqrt(backwardX * backwardX + backwardY * backwardY);
158
+ return ((first + second) / 2) * focal;
159
+ }
160
+
161
+ function invert3(m: Float64Array): Float64Array | null {
162
+ const a = m[0] as number;
163
+ const b = m[1] as number;
164
+ const c = m[2] as number;
165
+ const d = m[3] as number;
166
+ const e = m[4] as number;
167
+ const f = m[5] as number;
168
+ const g = m[6] as number;
169
+ const h = m[7] as number;
170
+ const i = m[8] as number;
171
+ const determinant = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
172
+ if (Math.abs(determinant) < 1e-14) return null;
173
+ const out = new Float64Array(9);
174
+ out[0] = (e * i - f * h) / determinant;
175
+ out[1] = (c * h - b * i) / determinant;
176
+ out[2] = (b * f - c * e) / determinant;
177
+ out[3] = (f * g - d * i) / determinant;
178
+ out[4] = (a * i - c * g) / determinant;
179
+ out[5] = (c * d - a * f) / determinant;
180
+ out[6] = (d * h - e * g) / determinant;
181
+ out[7] = (b * g - a * h) / determinant;
182
+ out[8] = (a * e - b * d) / determinant;
183
+ return out;
184
+ }
185
+
186
+ /** One point from a pair of rays, by the linear method, into `out` at `at`. */
187
+ function triangulatePoint(
188
+ ax: number,
189
+ ay: number,
190
+ bx: number,
191
+ by: number,
192
+ rotation: Float64Array,
193
+ translation: Float64Array,
194
+ out: Float64Array,
195
+ at: number,
196
+ ): boolean {
197
+ /* Four rows of `x × (P · X) = 0`, two from each camera, solved by their normal equations. */
198
+ const rows = [
199
+ [-1, 0, ax, 0],
200
+ [0, -1, ay, 0],
201
+ [
202
+ (rotation[0] as number) - bx * (rotation[6] as number),
203
+ (rotation[1] as number) - bx * (rotation[7] as number),
204
+ (rotation[2] as number) - bx * (rotation[8] as number),
205
+ (translation[0] as number) - bx * (translation[2] as number),
206
+ ],
207
+ [
208
+ (rotation[3] as number) - by * (rotation[6] as number),
209
+ (rotation[4] as number) - by * (rotation[7] as number),
210
+ (rotation[5] as number) - by * (rotation[8] as number),
211
+ (translation[1] as number) - by * (translation[2] as number),
212
+ ],
213
+ ];
214
+ const normals = new Float64Array(9);
215
+ const right = new Float64Array(3);
216
+ for (const row of rows) {
217
+ for (let r = 0; r < 3; r += 1) {
218
+ right[r] = (right[r] as number) - (row[r] as number) * (row[3] as number);
219
+ for (let c = 0; c < 3; c += 1) {
220
+ normals[r * 3 + c] =
221
+ (normals[r * 3 + c] as number) + (row[r] as number) * (row[c] as number);
222
+ }
223
+ }
224
+ }
225
+ const factor = new Float64Array(9);
226
+ if (!cholesky(normals, 3, factor)) return false;
227
+ const point = Float64Array.from(right);
228
+ choleskySolve(factor, 3, point);
229
+ out[at * 3] = point[0] as number;
230
+ out[at * 3 + 1] = point[1] as number;
231
+ out[at * 3 + 2] = point[2] as number;
232
+ return true;
233
+ }
234
+
235
+ /**
236
+ * The points behind `count` correspondences, in the first camera's frame, into `out` (3 each), and
237
+ * how many landed in front of both cameras. A point that did not is left at zero.
238
+ */
239
+ export function triangulate(
240
+ a: Float64Array,
241
+ b: Float64Array,
242
+ count: number,
243
+ intrinsics: readonly [number, number, number, number],
244
+ rotation: Float64Array,
245
+ translation: Float64Array,
246
+ out: Float64Array,
247
+ ): number {
248
+ const [fx, fy, cx, cy] = intrinsics;
249
+ let ahead = 0;
250
+ for (let at = 0; at < count; at += 1) {
251
+ const ax = ((a[at * 2] as number) - cx) / fx;
252
+ const ay = ((a[at * 2 + 1] as number) - cy) / fy;
253
+ const bx = ((b[at * 2] as number) - cx) / fx;
254
+ const by = ((b[at * 2 + 1] as number) - cy) / fy;
255
+ out[at * 3] = 0;
256
+ out[at * 3 + 1] = 0;
257
+ out[at * 3 + 2] = 0;
258
+ if (!triangulatePoint(ax, ay, bx, by, rotation, translation, out, at)) continue;
259
+ const z = out[at * 3 + 2] as number;
260
+ if (!(z > 0)) {
261
+ out[at * 3] = 0;
262
+ out[at * 3 + 1] = 0;
263
+ out[at * 3 + 2] = 0;
264
+ continue;
265
+ }
266
+ /* And in front of the second camera too, which is what a wrong decomposition fails. */
267
+ let second = translation[2] as number;
268
+ for (let k = 0; k < 3; k += 1) {
269
+ second += (rotation[6 + k] as number) * (out[at * 3 + k] as number);
270
+ }
271
+ if (second > 0) ahead += 1;
272
+ else {
273
+ out[at * 3] = 0;
274
+ out[at * 3 + 1] = 0;
275
+ out[at * 3 + 2] = 0;
276
+ }
277
+ }
278
+ return ahead;
279
+ }
280
+
281
+ /** The four poses an essential matrix allows, and the one its points stand in front of. */
282
+ function decompose(
283
+ e: Float64Array,
284
+ a: Float64Array,
285
+ b: Float64Array,
286
+ picks: readonly number[],
287
+ out: RelativePoseOut,
288
+ ): number {
289
+ const u = new Float64Array(9);
290
+ const s = new Float64Array(3);
291
+ const v = new Float64Array(9);
292
+ svd3(e, u, s, v);
293
+ const w = Float64Array.from([0, -1, 0, 1, 0, 0, 0, 0, 1]);
294
+ const build = (turned: boolean): Float64Array => {
295
+ /* U · W · Vᵀ, or U · Wᵀ · Vᵀ: the two rotations an essential matrix allows. */
296
+ const out2 = new Float64Array(9);
297
+ const uw = new Float64Array(9);
298
+ for (let r = 0; r < 3; r += 1) {
299
+ for (let c = 0; c < 3; c += 1) {
300
+ let sum = 0;
301
+ for (let k = 0; k < 3; k += 1) {
302
+ sum +=
303
+ (u[r * 3 + k] as number) *
304
+ (turned ? (w[c * 3 + k] as number) : (w[k * 3 + c] as number));
305
+ }
306
+ uw[r * 3 + c] = sum;
307
+ }
308
+ }
309
+ for (let r = 0; r < 3; r += 1) {
310
+ for (let c = 0; c < 3; c += 1) {
311
+ let sum = 0;
312
+ for (let k = 0; k < 3; k += 1) sum += (uw[r * 3 + k] as number) * (v[c * 3 + k] as number);
313
+ out2[r * 3 + c] = sum;
314
+ }
315
+ }
316
+ /* A rotation has determinant one; the other sign is a reflection. */
317
+ const determinant =
318
+ (out2[0] as number) *
319
+ ((out2[4] as number) * (out2[8] as number) - (out2[5] as number) * (out2[7] as number)) -
320
+ (out2[1] as number) *
321
+ ((out2[3] as number) * (out2[8] as number) - (out2[5] as number) * (out2[6] as number)) +
322
+ (out2[2] as number) *
323
+ ((out2[3] as number) * (out2[7] as number) - (out2[4] as number) * (out2[6] as number));
324
+ if (determinant < 0) for (let i = 0; i < 9; i += 1) out2[i] = -(out2[i] as number);
325
+ return out2;
326
+ };
327
+
328
+ const direction = Float64Array.from([u[2] as number, u[5] as number, u[8] as number]);
329
+ const scratch = new Float64Array(picks.length * 3);
330
+ const subA = new Float64Array(picks.length * 2);
331
+ const subB = new Float64Array(picks.length * 2);
332
+ picks.forEach((at, i) => {
333
+ subA[i * 2] = a[at * 2] as number;
334
+ subA[i * 2 + 1] = a[at * 2 + 1] as number;
335
+ subB[i * 2] = b[at * 2] as number;
336
+ subB[i * 2 + 1] = b[at * 2 + 1] as number;
337
+ });
338
+ let best = -1;
339
+ for (const turned of [false, true]) {
340
+ const rotation = build(turned);
341
+ for (const sign of [1, -1]) {
342
+ const translation = Float64Array.from([
343
+ sign * (direction[0] as number),
344
+ sign * (direction[1] as number),
345
+ sign * (direction[2] as number),
346
+ ]);
347
+ const ahead = triangulate(
348
+ subA,
349
+ subB,
350
+ picks.length,
351
+ [1, 1, 0, 0],
352
+ rotation,
353
+ translation,
354
+ scratch,
355
+ );
356
+ if (ahead > best) {
357
+ best = ahead;
358
+ out.rotation.set(rotation);
359
+ out.translation.set(translation);
360
+ }
361
+ }
362
+ }
363
+ return best;
364
+ }
365
+
366
+ /** The median angle between the two rays to each point, in degrees. */
367
+ function parallaxOf(
368
+ a: Float64Array,
369
+ b: Float64Array,
370
+ picks: readonly number[],
371
+ rotation: Float64Array,
372
+ translation: Float64Array,
373
+ ): number {
374
+ const angles: number[] = [];
375
+ const point = new Float64Array(3);
376
+ const centre = [0, 0, 0];
377
+ for (let k = 0; k < 3; k += 1) {
378
+ /* The second camera's centre in the first's frame: −Rᵀ · t. */
379
+ centre[k] = -(
380
+ (rotation[k] as number) * (translation[0] as number) +
381
+ (rotation[3 + k] as number) * (translation[1] as number) +
382
+ (rotation[6 + k] as number) * (translation[2] as number)
383
+ );
384
+ }
385
+ for (const at of picks) {
386
+ if (
387
+ !triangulatePoint(
388
+ a[at * 2] as number,
389
+ a[at * 2 + 1] as number,
390
+ b[at * 2] as number,
391
+ b[at * 2 + 1] as number,
392
+ rotation,
393
+ translation,
394
+ point,
395
+ 0,
396
+ )
397
+ ) {
398
+ continue;
399
+ }
400
+ const first = [point[0] as number, point[1] as number, point[2] as number];
401
+ const second = [
402
+ first[0] - (centre[0] as number),
403
+ first[1] - (centre[1] as number),
404
+ first[2] - (centre[2] as number),
405
+ ];
406
+ const dot = first[0] * second[0] + first[1] * second[1] + first[2] * second[2];
407
+ const lengths =
408
+ Math.sqrt(first[0] * first[0] + first[1] * first[1] + first[2] * first[2]) *
409
+ Math.sqrt(second[0] * second[0] + second[1] * second[1] + second[2] * second[2]);
410
+ if (lengths === 0) continue;
411
+ angles.push((exactAcos(Math.min(1, Math.max(-1, dot / lengths))) * 180) / Math.PI);
412
+ }
413
+ if (angles.length === 0) return 0;
414
+ angles.sort((p, q) => p - q);
415
+ return angles[Math.floor(angles.length / 2)] as number;
416
+ }
417
+
418
+ /**
419
+ * The pose of the second view relative to the first, from `count` correspondences in pixels.
420
+ * `random` is the caller's seeded generator: a capture answers the same twice.
421
+ */
422
+ export function relativePose(
423
+ a: Float64Array,
424
+ b: Float64Array,
425
+ count: number,
426
+ intrinsics: readonly [number, number, number, number],
427
+ random: () => number,
428
+ out: RelativePoseOut,
429
+ ): RelativePoseResult {
430
+ const [fx, fy, cx, cy] = intrinsics;
431
+ const focal = (fx + fy) / 2;
432
+ const normalA = new Float64Array(count * 2);
433
+ const normalB = new Float64Array(count * 2);
434
+ for (let at = 0; at < count; at += 1) {
435
+ normalA[at * 2] = ((a[at * 2] as number) - cx) / fx;
436
+ normalA[at * 2 + 1] = ((a[at * 2 + 1] as number) - cy) / fy;
437
+ normalB[at * 2] = ((b[at * 2] as number) - cx) / fx;
438
+ normalB[at * 2 + 1] = ((b[at * 2 + 1] as number) - cy) / fy;
439
+ }
440
+
441
+ const sample = (size: number): number[] => {
442
+ const picks: number[] = [];
443
+ while (picks.length < size) {
444
+ const at = Math.min(count - 1, Math.floor(random() * count));
445
+ if (!picks.includes(at)) picks.push(at);
446
+ }
447
+ return picks;
448
+ };
449
+ const score = (
450
+ fit: (picks: readonly number[]) => Float64Array,
451
+ error: (model: Float64Array, at: number) => number,
452
+ size: number,
453
+ ): { model: Float64Array | null; inliers: number[]; score: number } => {
454
+ let best: Float64Array | null = null;
455
+ let bestInliers: number[] = [];
456
+ let bestScore = -1;
457
+ for (let trial = 0; trial < TRIALS; trial += 1) {
458
+ const model = fit(sample(size));
459
+ let sum = 0;
460
+ const inliers: number[] = [];
461
+ for (let at = 0; at < count; at += 1) {
462
+ const off = error(model, at);
463
+ if (off < THRESHOLD) {
464
+ inliers.push(at);
465
+ /* A score that rewards a close fit rather than counting alone. */
466
+ sum += THRESHOLD - off;
467
+ }
468
+ }
469
+ if (sum > bestScore) {
470
+ bestScore = sum;
471
+ best = model;
472
+ bestInliers = inliers;
473
+ }
474
+ }
475
+ return { model: best, inliers: bestInliers, score: bestScore };
476
+ };
477
+
478
+ const essential = score(
479
+ (picks) => essentialFrom(normalA, normalB, picks),
480
+ (model, at) => essentialError(model, normalA, normalB, at, focal),
481
+ 8,
482
+ );
483
+ const homography = score(
484
+ (picks) => homographyFrom(normalA, normalB, picks),
485
+ (model, at) => homographyError(model, normalA, normalB, at, focal),
486
+ 4,
487
+ );
488
+
489
+ const total = essential.score + homography.score;
490
+ const planar = total > 0 && homography.score / total > PLANAR;
491
+ out.rotation.set([1, 0, 0, 0, 1, 0, 0, 0, 1]);
492
+ out.translation.set([0, 0, 0]);
493
+
494
+ if (!planar && essential.model !== null && essential.inliers.length >= 8) {
495
+ const refined = essentialFrom(normalA, normalB, essential.inliers);
496
+ decompose(refined, normalA, normalB, essential.inliers, out);
497
+ const parallax = parallaxOf(normalA, normalB, essential.inliers, out.rotation, out.translation);
498
+ if (parallax >= TURNED) {
499
+ return { inliers: essential.inliers.length, model: 'essential', parallax };
500
+ }
501
+ /* It moved too little to say where: answer the turn and claim no baseline. */
502
+ out.translation.set([0, 0, 0]);
503
+ return { inliers: essential.inliers.length, model: 'homography', parallax };
504
+ }
505
+
506
+ if (homography.model !== null) {
507
+ /* A turn's homography is `R` itself once the intrinsics are out of it; scale it to a rotation. */
508
+ const h = homography.model;
509
+ const u = new Float64Array(9);
510
+ const s = new Float64Array(3);
511
+ const v = new Float64Array(9);
512
+ svd3(h, u, s, v);
513
+ const rotation = new Float64Array(9);
514
+ for (let r = 0; r < 3; r += 1) {
515
+ for (let c = 0; c < 3; c += 1) {
516
+ let sum = 0;
517
+ for (let k = 0; k < 3; k += 1) sum += (u[r * 3 + k] as number) * (v[c * 3 + k] as number);
518
+ rotation[r * 3 + c] = sum;
519
+ }
520
+ }
521
+ const determinant =
522
+ (rotation[0] as number) *
523
+ ((rotation[4] as number) * (rotation[8] as number) -
524
+ (rotation[5] as number) * (rotation[7] as number)) -
525
+ (rotation[1] as number) *
526
+ ((rotation[3] as number) * (rotation[8] as number) -
527
+ (rotation[5] as number) * (rotation[6] as number)) +
528
+ (rotation[2] as number) *
529
+ ((rotation[3] as number) * (rotation[7] as number) -
530
+ (rotation[4] as number) * (rotation[6] as number));
531
+ if (determinant < 0) for (let i = 0; i < 9; i += 1) rotation[i] = -(rotation[i] as number);
532
+ out.rotation.set(rotation);
533
+ return { inliers: homography.inliers.length, model: 'homography', parallax: 0 };
534
+ }
535
+ return { inliers: 0, model: 'homography', parallax: 0 };
536
+ }