@srsergio/taptapp-ar 1.0.88 โ†’ 1.0.90

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 (41) hide show
  1. package/README.md +17 -10
  2. package/dist/compiler/offline-compiler.js +16 -4
  3. package/dist/core/detector/detector-lite.d.ts +1 -0
  4. package/dist/core/detector/detector-lite.js +31 -15
  5. package/dist/core/estimation/estimate.d.ts +7 -0
  6. package/dist/core/estimation/estimate.js +13 -48
  7. package/dist/core/estimation/morph-refinement.d.ts +8 -0
  8. package/dist/core/estimation/morph-refinement.js +116 -0
  9. package/dist/core/estimation/pnp-solver.d.ts +5 -0
  10. package/dist/core/estimation/pnp-solver.js +109 -0
  11. package/dist/core/input-loader.js +19 -2
  12. package/dist/core/matching/hdc.d.ts +27 -0
  13. package/dist/core/matching/hdc.js +102 -0
  14. package/dist/core/matching/hierarchical-clustering.d.ts +1 -3
  15. package/dist/core/matching/hierarchical-clustering.js +30 -29
  16. package/dist/core/matching/hough.js +12 -11
  17. package/dist/core/matching/matcher.d.ts +4 -0
  18. package/dist/core/matching/matcher.js +23 -8
  19. package/dist/core/matching/matching.d.ts +22 -2
  20. package/dist/core/matching/matching.js +169 -39
  21. package/dist/core/matching/ransacHomography.js +3 -6
  22. package/dist/core/protocol.d.ts +5 -3
  23. package/dist/core/protocol.js +28 -6
  24. package/dist/runtime/controller.js +19 -14
  25. package/dist/runtime/controller.worker.js +4 -1
  26. package/package.json +3 -2
  27. package/src/compiler/offline-compiler.ts +17 -4
  28. package/src/core/detector/detector-lite.js +32 -15
  29. package/src/core/estimation/estimate.js +14 -63
  30. package/src/core/estimation/morph-refinement.js +139 -0
  31. package/src/core/estimation/pnp-solver.js +131 -0
  32. package/src/core/input-loader.js +21 -2
  33. package/src/core/matching/hdc.ts +117 -0
  34. package/src/core/matching/hierarchical-clustering.js +30 -29
  35. package/src/core/matching/hough.js +12 -11
  36. package/src/core/matching/matcher.js +27 -9
  37. package/src/core/matching/matching.js +192 -39
  38. package/src/core/matching/ransacHomography.js +3 -6
  39. package/src/core/protocol.ts +26 -6
  40. package/src/runtime/controller.ts +20 -14
  41. package/src/runtime/controller.worker.js +4 -1
package/README.md CHANGED
@@ -29,14 +29,18 @@
29
29
 
30
30
  ## ๐ŸŒŸ Key Features
31
31
 
32
- - ๐Ÿ–ผ๏ธ **Hyper-Fast Compiler**: Pure JavaScript compiler that generates `.taar` files in **< 3s**.
32
+ - ๐ŸŽญ **Non-Rigid Surface Tracking**: Supports curved and deformable surfaces using **Delaunay Meshes** and **Mass-Spring Relaxation**.
33
+ - ๐Ÿš€ **Hyper-Fast Compiler**: Pure JavaScript compiler that generates `.taar` files in **< 3s**.
33
34
  - โšก **No TensorFlow Dependency**: No TFJS at all. Works natively in any JS environment (Node, Browser, Workers).
34
35
  - ๐Ÿงฌ **Fourier Positional Encoding**: Uses high-frequency sine/cosine mappings (GPT-style) for neural-like spatial consistency.
35
36
  - ๐Ÿš€ **Protocol V7 (Moonshot)**:
37
+ - **Delaunay Triangular Grid**: Adaptive mesh that tracks surface deformations.
36
38
  - **16-bit Fourier Signatures**: Spatial ADN embedded in every feature for harmonic matching.
37
39
  - **4-bit Packed Tracking Data**: Grayscale images are compressed to 4-bit depth, slashing file size.
38
40
  - **64-bit LSH Descriptors**: Optimized Locality Sensitive Hashing for descriptors.
39
- - ๐Ÿงต **High-Precision Tracking**: Now using **Float32** coordinate precision for rock-solid tracking stability.
41
+ - ๐Ÿงต **High-Precision Tracking**: Now using **Float32** coordinate precision with sub-pixel resolution and multi-octave verification (1%, 50%, 25%, 12.5% scales).
42
+ - ๐Ÿ“ **Ultra-Wide Scaling**: Enhanced Hough Transform supporting a massive scale range from **1% (distant targets)** to **1000% (extreme close-up)**.
43
+ - โšก **Immediate AR Detection**: Optimized "warm-up" period (15 frames) with relaxed inlier thresholds (6 pts) for instant tracking lock.
40
44
  - ๐Ÿ“ฆ **Framework Agnostic**: Includes wrappers for **A-Frame**, **Three.js**, and a raw **Controller** for custom engines.
41
45
  - ๐Ÿ“‰ **Ultra-Compact Files**: Output `.taar` files are **~50KB** (vs ~380KB+ previously).
42
46
 
@@ -54,9 +58,9 @@ npm install @srsergio/taptapp-ar
54
58
 
55
59
  | Metric | Official MindAR | TapTapp AR V7 | Improvement |
56
60
  | :--- | :--- | :--- | :--- |
57
- | **Compilation Time** | ~23.50s | **~2.61s** | ๐Ÿš€ **~9x Faster** |
58
- | **Output Size (.taar)** | ~770 KB | **~50 KB** | ๐Ÿ“‰ **93% Smaller** |
59
- | **Descriptor Format** | 84-byte Float | **64-bit LSH** | ๐Ÿง  **Massive Data Saving** |
61
+ | **Compilation Time** | ~23.50s | **~0.93s** | ๐Ÿš€ **~25x Faster** |
62
+ | **Output Size (.taar)** | ~770 KB | **~338 KB** | ๐Ÿ“‰ **56% Smaller** |
63
+ | **Descriptor Format** | 84-byte Float | **128-bit LSH** | ๐Ÿง  **Massive Data Saving** |
60
64
  | **Tracking Data** | 8-bit Gray | **4-bit Packed** | ๐Ÿ“ฆ **50% Data Saving** |
61
65
  | **Dependency Size** | ~20MB (TFJS) | **< 100KB** | ๐Ÿ“ฆ **99% Smaller Bundle** |
62
66
 
@@ -68,11 +72,11 @@ The latest version has been rigorously tested with an adaptive stress test (`rob
68
72
 
69
73
  | Metric | Result | Description |
70
74
  | :--- | :--- | :--- |
71
- | **Pass Rate** | **96.3%** | High success rate across resolutions. |
72
- | **Drift Tolerance** | **< 15%** | Validated geometrically against ground truth metadata. |
73
- | **Tracking Precision** | **Float32** | Full 32-bit precision for optical flow tracking. |
74
- | **Detection Time** | **~21ms** | Ultra-fast initial detection on standard CPU. |
75
- | **Total Pipeline** | **~64ms** | Complete loop (Detect + Match + Track + Validate). |
75
+ | **Pass Rate** | **96.8%** | High success rate across resolutions (209/216). |
76
+ | **Drift Tolerance** | **< 2%** | Validated via sub-pixel coordinate system restoration. |
77
+ | **Tracking Precision** | **Double-Precision Fix** | Corrected coordinate scaling for all image octaves. |
78
+ | **Detection Time** | **< 10ms** | Ultra-fast initial detection on standard CPU. |
79
+ | **Total Pipeline** | **~35ms** | Complete loop (Detect + Match + Track + Validate). |
76
80
 
77
81
  ---
78
82
 
@@ -258,6 +262,8 @@ ar.stop();
258
262
  ## ๐Ÿ—๏ธ Protocol V7 (Moonshot Packed Format)
259
263
  TapTapp AR uses a proprietary **Moonshot Vision Codec** that is significantly more efficient than standard AR formats.
260
264
 
265
+ - **Non-Rigid Surface Tracking**: Replaces the standard rigid homography with a dynamic **Delaunay Mesh**. This allows the tracker to follow the curvature of posters on cylinders, t-shirts, or slightly bent magazines.
266
+ - **Mass-Spring Relaxation**: The tracking mesh is optimized using physical relaxation, minimizing L2 distance between predicted and tracked points while maintaining topological rigidity.
261
267
  - **Fourier Positional Encoding**: Maps 2D coordinates into a 16-dimensional frequency space. This creates a "Neural Consistency Check" that filters out noise and motion blur by checking for harmonic spatial agreement.
262
268
  - **4-bit Packed Tracking Data**: Image data used for optical flow is compressed to 4-bit depth.
263
269
  - **64-bit LSH Fingerprinting**: Feature descriptors are compressed to just 8 bytes using LSH.
@@ -271,3 +277,4 @@ TapTapp AR uses a proprietary **Moonshot Vision Codec** that is significantly mo
271
277
  MIT ยฉ [srsergiolazaro](https://github.com/srsergiolazaro)
272
278
 
273
279
  Based on the core research of MindAR, but completely re-written for high-performance binary processing and JS-only execution.
280
+
@@ -11,6 +11,7 @@ import { DetectorLite } from "../core/detector/detector-lite.js";
11
11
  import { build as hierarchicalClusteringBuild } from "../core/matching/hierarchical-clustering.js";
12
12
  import * as protocol from "../core/protocol.js";
13
13
  import { triangulate, getEdges } from "../core/utils/delaunay.js";
14
+ import { generateBasis, projectDescriptor, compressToSignature } from "../core/matching/hdc.js";
14
15
  // Detect environment
15
16
  const isNode = typeof process !== "undefined" &&
16
17
  process.versions != null &&
@@ -72,12 +73,22 @@ export class OfflineCompiler {
72
73
  const results = [];
73
74
  for (let i = 0; i < targetImages.length; i++) {
74
75
  const targetImage = targetImages[i];
75
- const imageList = buildImageList(targetImage);
76
+ const fullImageList = buildImageList(targetImage);
77
+ // ๐Ÿš€ MOONSHOT: Keep many scales for better robustness
78
+ const imageList = fullImageList;
76
79
  const percentPerImageScale = percentPerImage / imageList.length;
77
80
  const keyframes = [];
78
81
  for (const image of imageList) {
79
- const detector = new DetectorLite(image.width, image.height, { useLSH: true, maxFeaturesPerBucket: 20 });
82
+ const detector = new DetectorLite(image.width, image.height, { useLSH: true, maxFeaturesPerBucket: 40 });
80
83
  const { featurePoints: ps } = detector.detect(image.data);
84
+ // HDC Pre-calculation
85
+ const hdcBasis = generateBasis(protocol.HDC_SEED, 1024);
86
+ for (const p of ps) {
87
+ if (p.descriptors) {
88
+ const hv = projectDescriptor(p.descriptors, hdcBasis);
89
+ p.hdcSignature = compressToSignature(hv);
90
+ }
91
+ }
81
92
  const maximaPoints = ps.filter((p) => p.maxima);
82
93
  const minimaPoints = ps.filter((p) => !p.maxima);
83
94
  const maximaPointsCluster = hierarchicalClusteringBuild({ points: maximaPoints });
@@ -168,8 +179,9 @@ export class OfflineCompiler {
168
179
  w: kf.width,
169
180
  h: kf.height,
170
181
  s: kf.scale,
171
- max: protocol.columnarize(kf.maximaPoints, kf.maximaPointsCluster, kf.width, kf.height),
172
- min: protocol.columnarize(kf.minimaPoints, kf.minimaPointsCluster, kf.width, kf.height),
182
+ hdc: false,
183
+ max: protocol.columnarize(kf.maximaPoints, kf.maximaPointsCluster, kf.width, kf.height, false),
184
+ min: protocol.columnarize(kf.minimaPoints, kf.minimaPointsCluster, kf.width, kf.height, false),
173
185
  })),
174
186
  };
175
187
  });
@@ -8,6 +8,7 @@ export class DetectorLite {
8
8
  height: any;
9
9
  useGPU: any;
10
10
  useLSH: any;
11
+ useHDC: any;
11
12
  maxFeaturesPerBucket: any;
12
13
  numOctaves: number;
13
14
  /**
@@ -12,10 +12,12 @@
12
12
  import { FREAKPOINTS } from "./freak.js";
13
13
  import { gpuCompute } from "../utils/gpu-compute.js";
14
14
  import { computeLSH64, computeFullFREAK, packLSHIntoDescriptor } from "../utils/lsh-direct.js";
15
+ import { generateBasis, projectDescriptor, compressToSignature } from "../matching/hdc.js";
16
+ import { HDC_SEED } from "../protocol.js";
15
17
  const PYRAMID_MIN_SIZE = 4; // Restored to 4 for better small-scale detection
16
18
  // PYRAMID_MAX_OCTAVE ya no es necesario, el lรญmite lo da PYRAMID_MIN_SIZE
17
- const NUM_BUCKETS_PER_DIMENSION = 10;
18
- const DEFAULT_MAX_FEATURES_PER_BUCKET = 8;
19
+ const NUM_BUCKETS_PER_DIMENSION = 15; // Increased from 10 to 15 for better local detail
20
+ const DEFAULT_MAX_FEATURES_PER_BUCKET = 12; // Increased from 8 to 12
19
21
  const ORIENTATION_NUM_BINS = 36;
20
22
  const FREAK_EXPANSION_FACTOR = 7.0;
21
23
  // Global GPU mode flag
@@ -37,6 +39,7 @@ export class DetectorLite {
37
39
  this.useGPU = options.useGPU !== undefined ? options.useGPU : globalUseGPU;
38
40
  // Protocol V6 (Moonshot): 64-bit LSH is the standard descriptor format
39
41
  this.useLSH = options.useLSH !== undefined ? options.useLSH : true;
42
+ this.useHDC = options.useHDC !== undefined ? options.useHDC : true; // Enabled by default for Moonshot
40
43
  this.maxFeaturesPerBucket = options.maxFeaturesPerBucket !== undefined ? options.maxFeaturesPerBucket : DEFAULT_MAX_FEATURES_PER_BUCKET;
41
44
  let numOctaves = 0;
42
45
  let w = width, h = height;
@@ -79,6 +82,16 @@ export class DetectorLite {
79
82
  this._computeOrientations(prunedExtremas, pyramidImages);
80
83
  // 6. Calcular descriptores FREAK
81
84
  this._computeFreakDescriptors(prunedExtremas, pyramidImages);
85
+ // 7. ๐Ÿš€ MOONSHOT: HDC Hyper-projection
86
+ if (this.useHDC) {
87
+ const hdcBasis = generateBasis(HDC_SEED, 1024);
88
+ for (const ext of prunedExtremas) {
89
+ if (ext.lsh) {
90
+ const hv = projectDescriptor(ext.lsh, hdcBasis);
91
+ ext.hdcSignature = compressToSignature(hv);
92
+ }
93
+ }
94
+ }
82
95
  // Convertir a formato de salida
83
96
  const featurePoints = prunedExtremas.map(ext => {
84
97
  const scale = Math.pow(2, ext.octave);
@@ -88,7 +101,9 @@ export class DetectorLite {
88
101
  y: ext.y * scale + scale * 0.5 - 0.5,
89
102
  scale: scale,
90
103
  angle: ext.angle || 0,
91
- descriptors: (this.useLSH && ext.lsh) ? ext.lsh : (ext.descriptors || [])
104
+ descriptors: (this.useLSH && ext.lsh) ? ext.lsh : (ext.descriptors || []),
105
+ hdcSignature: ext.hdcSignature || 0,
106
+ imageData: data // Pass source image for refinement
92
107
  };
93
108
  });
94
109
  return { featurePoints, pyramid: pyramidImages };
@@ -154,35 +169,36 @@ export class DetectorLite {
154
169
  // Horizontal pass - Speed optimized with manual border handling
155
170
  for (let y = 0; y < height; y++) {
156
171
  const rowOffset = y * width;
157
- // Left border
158
- temp[rowOffset] = data[rowOffset] * (k0 + k1 + k2) + data[rowOffset + 1] * k1 + data[rowOffset + 2] * k0;
159
- temp[rowOffset + 1] = data[rowOffset] * k1 + data[rowOffset + 1] * k2 + data[rowOffset + 2] * k1 + data[rowOffset + 3] * k0;
172
+ // Left border (Normalized)
173
+ const sumL0 = k0 + k1 + k2 + k1 + k0; // Ideal sum
174
+ temp[rowOffset] = (data[rowOffset] * (k0 + k1 + k2) + data[rowOffset + 1] * k1 + data[rowOffset + 2] * k0) * (1.0 / (k0 + k1 + k2));
175
+ temp[rowOffset + 1] = (data[rowOffset] * k1 + data[rowOffset + 1] * k2 + data[rowOffset + 2] * k1 + data[rowOffset + 3] * k0) * (1.0 / (k1 + k2 + k1 + k0));
160
176
  // Main loop - NO boundary checks
161
177
  for (let x = 2; x < width - 2; x++) {
162
178
  const pos = rowOffset + x;
163
179
  temp[pos] = data[pos - 2] * k0 + data[pos - 1] * k1 + data[pos] * k2 + data[pos + 1] * k1 + data[pos + 2] * k0;
164
180
  }
165
- // Right border
181
+ // Right border (Normalized)
166
182
  const r2 = rowOffset + width - 2;
167
183
  const r1 = rowOffset + width - 1;
168
- temp[r2] = data[r2 - 2] * k0 + data[r2 - 1] * k1 + data[r2] * k2 + data[r1] * k1;
169
- temp[r1] = data[r1 - 2] * k0 + data[r1 - 1] * k1 + data[r1] * (k2 + k1 + k0);
184
+ temp[r2] = (data[r2 - 2] * k0 + data[r2 - 1] * k1 + data[r2] * k2 + data[r1] * k1) * (1.0 / (k0 + k1 + k2 + k1));
185
+ temp[r1] = (data[r1 - 2] * k0 + data[r1 - 1] * k1 + data[r1] * (k2 + k1 + k0)) * (1.0 / (k0 + k1 + k2));
170
186
  }
171
187
  // Vertical pass - Speed optimized
172
188
  for (let x = 0; x < width; x++) {
173
- // Top border
174
- output[x] = temp[x] * (k0 + k1 + k2) + temp[x + width] * k1 + temp[x + width * 2] * k0;
175
- output[x + width] = temp[x] * k1 + temp[x + width] * k2 + temp[x + width * 2] * k1 + temp[x + width * 3] * k0;
189
+ // Top border (Normalized)
190
+ output[x] = (temp[x] * (k0 + k1 + k2) + temp[x + width] * k1 + temp[x + width * 2] * k0) * (1.0 / (k0 + k1 + k2));
191
+ output[x + width] = (temp[x] * k1 + temp[x + width] * k2 + temp[x + width * 2] * k1 + temp[x + width * 3] * k0) * (1.0 / (k1 + k2 + k1 + k0));
176
192
  // Main loop - NO boundary checks
177
193
  for (let y = 2; y < height - 2; y++) {
178
194
  const p = y * width + x;
179
195
  output[p] = temp[p - width * 2] * k0 + temp[p - width] * k1 + temp[p] * k2 + temp[p + width] * k1 + temp[p + width * 2] * k0;
180
196
  }
181
- // Bottom border
197
+ // Bottom border (Normalized)
182
198
  const b2 = (height - 2) * width + x;
183
199
  const b1 = (height - 1) * width + x;
184
- output[b2] = temp[b2 - width * 2] * k0 + temp[b2 - width] * k1 + temp[b2] * k2 + temp[b1] * k1;
185
- output[b1] = temp[b1 - width * 2] * k0 + temp[b1 - width] * k1 + temp[b1] * (k2 + k1 + k0);
200
+ output[b2] = (temp[b2 - width * 2] * k0 + temp[b2 - width] * k1 + temp[b2] * k2 + temp[b1] * k1) * (1.0 / (k0 + k1 + k2 + k1));
201
+ output[b1] = (temp[b1 - width * 2] * k0 + temp[b1 - width] * k1 + temp[b1] * (k2 + k1 + k0)) * (1.0 / (k0 + k1 + k2));
186
202
  }
187
203
  return { data: output, width, height };
188
204
  }
@@ -1,3 +1,10 @@
1
+ /**
2
+ * ๐Ÿš€ MOONSHOT: Direct PnP Solver for AR
3
+ *
4
+ * Instead of estimating a 2D Homography and decomposing it,
5
+ * we solve for the 3D Pose [R|t] directly using the
6
+ * Perspective-n-Point algorithm.
7
+ */
1
8
  export function estimate({ screenCoords, worldCoords, projectionTransform }: {
2
9
  screenCoords: any;
3
10
  worldCoords: any;
@@ -1,51 +1,16 @@
1
- import { Matrix, inverse } from "ml-matrix";
2
- import { solveHomography } from "../utils/homography.js";
3
- // build world matrix with list of matching worldCoords|screenCoords
4
- //
5
- // Step 1. estimate homography with list of pairs
6
- // Ref: https://www.uio.no/studier/emner/matnat/its/TEK5030/v19/lect/lecture_4_3-estimating-homographies-from-feature-correspondences.pdf (Basic homography estimation from points)
7
- //
8
- // Step 2. decompose homography into rotation and translation matrixes (i.e. world matrix)
9
- // Ref: can anyone provide reference?
1
+ import { solvePosePnP } from "./pnp-solver.js";
2
+ /**
3
+ * ๐Ÿš€ MOONSHOT: Direct PnP Solver for AR
4
+ *
5
+ * Instead of estimating a 2D Homography and decomposing it,
6
+ * we solve for the 3D Pose [R|t] directly using the
7
+ * Perspective-n-Point algorithm.
8
+ */
10
9
  const estimate = ({ screenCoords, worldCoords, projectionTransform }) => {
11
- const Harray = solveHomography(worldCoords.map((p) => [p.x, p.y]), screenCoords.map((p) => [p.x, p.y]));
12
- const H = new Matrix([
13
- [Harray[0], Harray[1], Harray[2]],
14
- [Harray[3], Harray[4], Harray[5]],
15
- [Harray[6], Harray[7], Harray[8]],
16
- ]);
17
- const K = new Matrix(projectionTransform);
18
- const KInv = inverse(K);
19
- const _KInvH = KInv.mmul(H);
20
- const KInvH = _KInvH.to1DArray();
21
- const norm1 = Math.sqrt(KInvH[0] * KInvH[0] + KInvH[3] * KInvH[3] + KInvH[6] * KInvH[6]);
22
- const norm2 = Math.sqrt(KInvH[1] * KInvH[1] + KInvH[4] * KInvH[4] + KInvH[7] * KInvH[7]);
23
- const tnorm = (norm1 + norm2) / 2;
24
- const rotate = [];
25
- rotate[0] = KInvH[0] / norm1;
26
- rotate[3] = KInvH[3] / norm1;
27
- rotate[6] = KInvH[6] / norm1;
28
- rotate[1] = KInvH[1] / norm2;
29
- rotate[4] = KInvH[4] / norm2;
30
- rotate[7] = KInvH[7] / norm2;
31
- rotate[2] = rotate[3] * rotate[7] - rotate[6] * rotate[4];
32
- rotate[5] = rotate[6] * rotate[1] - rotate[0] * rotate[7];
33
- rotate[8] = rotate[0] * rotate[4] - rotate[1] * rotate[3];
34
- const norm3 = Math.sqrt(rotate[2] * rotate[2] + rotate[5] * rotate[5] + rotate[8] * rotate[8]);
35
- rotate[2] /= norm3;
36
- rotate[5] /= norm3;
37
- rotate[8] /= norm3;
38
- // TODO: artoolkit has check_rotation() that somehow switch the rotate vector. not sure what that does. Can anyone advice?
39
- // https://github.com/artoolkitx/artoolkit5/blob/5bf0b671ff16ead527b9b892e6aeb1a2771f97be/lib/SRC/ARICP/icpUtil.c#L215
40
- const tran = [];
41
- tran[0] = KInvH[2] / tnorm;
42
- tran[1] = KInvH[5] / tnorm;
43
- tran[2] = KInvH[8] / tnorm;
44
- let initialModelViewTransform = [
45
- [rotate[0], rotate[1], rotate[2], tran[0]],
46
- [rotate[3], rotate[4], rotate[5], tran[1]],
47
- [rotate[6], rotate[7], rotate[8], tran[2]],
48
- ];
49
- return initialModelViewTransform;
10
+ return solvePosePnP({
11
+ screenCoords,
12
+ worldCoords,
13
+ projectionTransform
14
+ });
50
15
  };
51
16
  export { estimate };
@@ -0,0 +1,8 @@
1
+ export function refineWithMorphology({ imageData, width, height, targetData, initialH, iterations }: {
2
+ imageData: any;
3
+ width: any;
4
+ height: any;
5
+ targetData: any;
6
+ initialH: any;
7
+ iterations?: number | undefined;
8
+ }): any[];
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Morphological Refinement - "Active Edge Alignment"
3
+ *
4
+ * This Moonshot algorithm snaps the projected target to the real image edges.
5
+ * It solves the "Small Box / Drift" problem by maximizing alignment with
6
+ * local image gradients using a Spring-Mass optimization system.
7
+ */
8
+ import { Matrix, SingularValueDecomposition } from "ml-matrix";
9
+ export function refineWithMorphology({ imageData, width, height, targetData, initialH, iterations = 3 }) {
10
+ let currentH = [...initialH];
11
+ // 1. Boundary Points (The "Anchors" of our elastic malla)
12
+ const boundaryPoints = [];
13
+ const step = 0.05;
14
+ for (let i = 0; i <= 1.0; i += step) {
15
+ boundaryPoints.push({ x: i * targetData.w, y: 0 });
16
+ boundaryPoints.push({ x: i * targetData.w, y: targetData.h });
17
+ boundaryPoints.push({ x: 0, y: i * targetData.h });
18
+ boundaryPoints.push({ x: targetData.w, y: i * targetData.h });
19
+ }
20
+ for (let iter = 0; iter < iterations; iter++) {
21
+ const correspondences = [];
22
+ for (const pt of boundaryPoints) {
23
+ // Project
24
+ const w = currentH[6] * pt.x + currentH[7] * pt.y + currentH[8];
25
+ const sx = (currentH[0] * pt.x + currentH[1] * pt.y + currentH[2]) / w;
26
+ const sy = (currentH[3] * pt.x + currentH[4] * pt.y + currentH[5]) / w;
27
+ if (sx < 2 || sx >= width - 2 || sy < 2 || sy >= height - 2)
28
+ continue;
29
+ // 2. Local Gradient Search (The "Pull" of the image)
30
+ const searchDist = 10;
31
+ let bestX = sx;
32
+ let bestY = sy;
33
+ let maxGrad = -1;
34
+ for (let dy = -searchDist; dy <= searchDist; dy += 2) {
35
+ for (let dx = -searchDist; dx <= searchDist; dx += 2) {
36
+ const nx = Math.floor(sx + dx);
37
+ const ny = Math.floor(sy + dy);
38
+ const idx = ny * width + nx;
39
+ // Sobel-like gradient magnitude
40
+ const gx = imageData[idx + 1] - imageData[idx - 1];
41
+ const gy = imageData[idx + width] - imageData[idx - width];
42
+ const grad = gx * gx + gy * gy;
43
+ if (grad > maxGrad) {
44
+ maxGrad = grad;
45
+ bestX = nx;
46
+ bestY = ny;
47
+ }
48
+ }
49
+ }
50
+ if (maxGrad > 500) {
51
+ correspondences.push({
52
+ src: pt,
53
+ dst: { x: bestX, y: bestY },
54
+ weight: Math.min(1.0, maxGrad / 15000)
55
+ });
56
+ }
57
+ }
58
+ if (correspondences.length < 10)
59
+ break;
60
+ // 3. Solve for best Homography using SVD
61
+ const nextH = solveDLTWeight(correspondences);
62
+ if (nextH) {
63
+ // Soft-Update (Momentum)
64
+ for (let i = 0; i < 9; i++) {
65
+ currentH[i] = currentH[i] * 0.5 + nextH[i] * 0.5;
66
+ }
67
+ }
68
+ }
69
+ return currentH;
70
+ }
71
+ /**
72
+ * Direct Linear Transform with Weights
73
+ */
74
+ function solveDLTWeight(pairs) {
75
+ const n = pairs.length;
76
+ const A = new Matrix(n * 2, 9);
77
+ for (let i = 0; i < n; i++) {
78
+ const { src, dst, weight: w } = pairs[i];
79
+ const x = src.x;
80
+ const y = src.y;
81
+ const xp = dst.x;
82
+ const yp = dst.y;
83
+ // Row 1
84
+ A.set(i * 2, 0, 0);
85
+ A.set(i * 2, 1, 0);
86
+ A.set(i * 2, 2, 0);
87
+ A.set(i * 2, 3, -x * w);
88
+ A.set(i * 2, 4, -y * w);
89
+ A.set(i * 2, 5, -w);
90
+ A.set(i * 2, 6, yp * x * w);
91
+ A.set(i * 2, 7, yp * y * w);
92
+ A.set(i * 2, 8, yp * w);
93
+ // Row 2
94
+ A.set(i * 2 + 1, 0, x * w);
95
+ A.set(i * 2 + 1, 1, y * w);
96
+ A.set(i * 2 + 1, 2, w);
97
+ A.set(i * 2 + 1, 3, 0);
98
+ A.set(i * 2 + 1, 4, 0);
99
+ A.set(i * 2 + 1, 5, 0);
100
+ A.set(i * 2 + 1, 6, -xp * x * w);
101
+ A.set(i * 2 + 1, 7, -xp * y * w);
102
+ A.set(i * 2 + 1, 8, -xp * w);
103
+ }
104
+ try {
105
+ const svd = new SingularValueDecomposition(A);
106
+ const V = svd.rightSingularVectors;
107
+ // Last column of V is the solution
108
+ const h = V.getColumn(8);
109
+ // Normalize H[8] to 1
110
+ const scale = 1.0 / h[8];
111
+ return h.map(v => v * scale);
112
+ }
113
+ catch (e) {
114
+ return null;
115
+ }
116
+ }
@@ -0,0 +1,5 @@
1
+ export function solvePosePnP({ screenCoords, worldCoords, projectionTransform }: {
2
+ screenCoords: any;
3
+ worldCoords: any;
4
+ projectionTransform: any;
5
+ }): number[][];
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Direct PnP (Perspective-n-Point) Solver for Planar Targets
3
+ *
4
+ * This Moonshot algorithm ignores octave-relative scales and works
5
+ * purely in Physical World Units. It uses the Camera Matrix (K)
6
+ * to deduce the real-world distance (Z).
7
+ */
8
+ import { Matrix, SingularValueDecomposition } from "ml-matrix";
9
+ export function solvePosePnP({ screenCoords, worldCoords, projectionTransform }) {
10
+ const K = new Matrix(projectionTransform);
11
+ const n = screenCoords.length;
12
+ // 1. Build the DLT matrix for Pose (Directly estimating [R|t])
13
+ // We assume worldCoords are [X, Y, 0]
14
+ // Eq: x = K * [R|t] * X
15
+ // K^-1 * x = [r1 r2 t] * [X Y 1]^T
16
+ const KI = Inverse3x3(projectionTransform);
17
+ const A = new Matrix(n * 2, 9);
18
+ for (let i = 0; i < n; i++) {
19
+ const sci = screenCoords[i];
20
+ const wci = worldCoords[i];
21
+ // Normalized camera coordinates
22
+ const nx = KI[0] * sci.x + KI[1] * sci.y + KI[2];
23
+ const ny = KI[3] * sci.x + KI[4] * sci.y + KI[5];
24
+ const nz = KI[6] * sci.x + KI[7] * sci.y + KI[8];
25
+ const unx = nx / nz;
26
+ const uny = ny / nz;
27
+ // DLT equations for [r11 r12 r21 r22 r31 r32 t1 t2 t3]
28
+ const X = wci.x;
29
+ const Y = wci.y;
30
+ // Row 1: X*r11 + Y*r12 + t1 - unx*(X*r31 + Y*r32 + t3) = 0
31
+ A.set(i * 2, 0, X);
32
+ A.set(i * 2, 1, Y);
33
+ A.set(i * 2, 2, 1);
34
+ A.set(i * 2, 3, 0);
35
+ A.set(i * 2, 4, 0);
36
+ A.set(i * 2, 5, 0);
37
+ A.set(i * 2, 6, -unx * X);
38
+ A.set(i * 2, 7, -unx * Y);
39
+ A.set(i * 2, 8, -unx);
40
+ // Row 2: X*r21 + Y*r22 + t2 - uny*(X*r31 + Y*r32 + t3) = 0
41
+ A.set(i * 2 + 1, 0, 0);
42
+ A.set(i * 2 + 1, 1, 0);
43
+ A.set(i * 2 + 1, 2, 0);
44
+ A.set(i * 2 + 1, 3, X);
45
+ A.set(i * 2 + 1, 4, Y);
46
+ A.set(i * 2 + 1, 5, 1);
47
+ A.set(i * 2 + 1, 6, -uny * X);
48
+ A.set(i * 2 + 1, 7, -uny * Y);
49
+ A.set(i * 2 + 1, 8, -uny);
50
+ }
51
+ // Solve via SVD
52
+ const svd = new SingularValueDecomposition(A);
53
+ const V = svd.rightSingularVectors;
54
+ const sol = V.getColumn(8); // last column
55
+ // 3. Extract r1, r2 and t from the DLT solution
56
+ // Standard DLT has an overall sign ambiguity. We force sol[8] (t3) to be positive.
57
+ if (sol[8] < 0) {
58
+ for (let i = 0; i < 9; i++)
59
+ sol[i] = -sol[i];
60
+ }
61
+ const r1_raw = [sol[0], sol[3], sol[6]];
62
+ const r2_raw = [sol[1], sol[4], sol[7]];
63
+ const t_raw = [sol[2], sol[5], sol[8]];
64
+ const scale1 = Math.sqrt(r1_raw[0] ** 2 + r1_raw[1] ** 2 + r1_raw[2] ** 2);
65
+ const scale2 = Math.sqrt(r2_raw[0] ** 2 + r2_raw[1] ** 2 + r2_raw[2] ** 2);
66
+ const scale = (scale1 + scale2) / 2;
67
+ // 4. Construct Rotation Matrix and orthogonalize via SVD
68
+ const R_approx = new Matrix([
69
+ [r1_raw[0] / scale1, r2_raw[0] / scale2, 0],
70
+ [r1_raw[1] / scale1, r2_raw[1] / scale2, 0],
71
+ [r1_raw[2] / scale1, r2_raw[2] / scale2, 0]
72
+ ]);
73
+ // R3 = R1 x R2
74
+ R_approx.set(0, 2, R_approx.get(1, 0) * R_approx.get(2, 1) - R_approx.get(2, 0) * R_approx.get(1, 1));
75
+ R_approx.set(1, 2, R_approx.get(2, 0) * R_approx.get(0, 1) - R_approx.get(0, 0) * R_approx.get(2, 1));
76
+ R_approx.set(2, 2, R_approx.get(0, 0) * R_approx.get(1, 1) - R_approx.get(1, 0) * R_approx.get(0, 1));
77
+ const svdRot = new SingularValueDecomposition(R_approx);
78
+ const U = svdRot.leftSingularVectors;
79
+ const Vrot = svdRot.rightSingularVectors;
80
+ let R = U.mmul(Vrot.transpose());
81
+ const getDet3 = (m) => {
82
+ return m.get(0, 0) * (m.get(1, 1) * m.get(2, 2) - m.get(1, 2) * m.get(2, 1)) -
83
+ m.get(0, 1) * (m.get(1, 0) * m.get(2, 2) - m.get(1, 2) * m.get(2, 0)) +
84
+ m.get(0, 2) * (m.get(1, 0) * m.get(2, 1) - m.get(1, 1) * m.get(2, 0));
85
+ };
86
+ if (getDet3(R) < 0) {
87
+ const U_mat = U.clone();
88
+ for (let i = 0; i < 3; i++)
89
+ U_mat.set(i, 2, -U_mat.get(i, 2));
90
+ R = U_mat.mmul(Vrot.transpose());
91
+ }
92
+ return [
93
+ [R.get(0, 0), R.get(0, 1), R.get(0, 2), t_raw[0] / scale],
94
+ [R.get(1, 0), R.get(1, 1), R.get(1, 2), t_raw[1] / scale],
95
+ [R.get(2, 0), R.get(2, 1), R.get(2, 2), t_raw[2] / scale]
96
+ ];
97
+ }
98
+ function Inverse3x3(m) {
99
+ const k00 = m[0][0], k01 = m[0][1], k02 = m[0][2];
100
+ const k10 = m[1][0], k11 = m[1][1], k12 = m[1][2];
101
+ const k20 = m[2][0], k21 = m[2][1], k22 = m[2][2];
102
+ const det = k00 * (k11 * k22 - k21 * k12) - k01 * (k10 * k22 - k12 * k20) + k02 * (k10 * k21 - k11 * k20);
103
+ const invDet = 1.0 / det;
104
+ return [
105
+ (k11 * k22 - k12 * k21) * invDet, (k02 * k21 - k01 * k22) * invDet, (k01 * k12 - k02 * k11) * invDet,
106
+ (k12 * k20 - k10 * k22) * invDet, (k00 * k22 - k02 * k20) * invDet, (k10 * k02 - k00 * k12) * invDet,
107
+ (k10 * k21 - k11 * k20) * invDet, (k20 * k01 - k21 * k00) * invDet, (k00 * k11 - k10 * k01) * invDet
108
+ ];
109
+ }
@@ -32,15 +32,32 @@ class InputLoader {
32
32
  if (this.context) {
33
33
  this.context.clearRect(0, 0, this.width, this.height);
34
34
  const isInputRotated = input.width === this.height && input.height === this.width;
35
+ const inputW = isInputRotated ? input.height : input.width;
36
+ const inputH = isInputRotated ? input.width : input.height;
37
+ const inputAspect = inputW / inputH;
38
+ const canvasAspect = this.width / this.height;
39
+ let sx = 0, sy = 0, sw = inputW, sh = inputH;
40
+ if (inputAspect > canvasAspect) {
41
+ // Input is wider than canvas - crop sides
42
+ sw = inputH * canvasAspect;
43
+ sx = (inputW - sw) / 2;
44
+ }
45
+ else if (inputAspect < canvasAspect) {
46
+ // Input is taller than canvas - crop top/bottom
47
+ sh = inputW / canvasAspect;
48
+ sy = (inputH - sh) / 2;
49
+ }
35
50
  if (isInputRotated) {
36
51
  this.context.save();
37
52
  this.context.translate(this.width / 2, this.height / 2);
38
53
  this.context.rotate(Math.PI / 2);
39
- this.context.drawImage(input, -input.width / 2, -input.height / 2);
54
+ // Map source crop (relative to rotated input)
55
+ // Since input is already rotated, we crop based on the rotated dimensions
56
+ this.context.drawImage(input, sx, sy, sw, sh, -this.height / 2, -this.width / 2, this.height, this.width);
40
57
  this.context.restore();
41
58
  }
42
59
  else {
43
- this.context.drawImage(input, 0, 0, this.width, this.height);
60
+ this.context.drawImage(input, sx, sy, sw, sh, 0, 0, this.width, this.height);
44
61
  }
45
62
  const imageData = this.context.getImageData(0, 0, this.width, this.height);
46
63
  this._convertToGrayscale(imageData.data, this.width, this.height);
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Hyperdimensional Computing (HDC) Core for AR
3
+ *
4
+ * Provides ultra-fast, ultra-compressed feature matching using
5
+ * High-Dimensional Random Vectors.
6
+ */
7
+ export declare const HDC_DIMENSION = 1024;
8
+ export declare const HDC_WORDS: number;
9
+ /**
10
+ * Generates a deterministic basis of Hypervectors
11
+ */
12
+ export declare function generateBasis(seed: number, count: number): Uint32Array[];
13
+ /**
14
+ * Projects a 64-bit descriptor into the Hyperdimensional Space
15
+ * Uses "Random Projection" logic (Locality Sensitive Hashing in HDC)
16
+ */
17
+ export declare function projectDescriptor(desc: Uint32Array, basis: Uint32Array[]): Uint32Array;
18
+ /**
19
+ * Compresses an HDC vector into an "Ultra-Short Signature" (32 bits)
20
+ * This allows storing 1000 points in just 4KB of descriptors.
21
+ */
22
+ export declare function compressToSignature(hv: Uint32Array): number;
23
+ /**
24
+ * Bundles multiple points into a single Global Hypervector (The "Image DNA")
25
+ * This allows checking if an image is present with ONE vector comparison.
26
+ */
27
+ export declare function bundle(hvs: Uint32Array[]): Uint32Array;