@srsergio/taptapp-ar 1.0.43 → 1.0.50
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.
- package/README.md +42 -45
- package/dist/compiler/aframe.js +8 -8
- package/dist/compiler/controller.d.ts +50 -76
- package/dist/compiler/controller.js +72 -116
- package/dist/compiler/detector/detector-lite.js +82 -99
- package/dist/compiler/index.js +3 -3
- package/dist/compiler/matching/hamming-distance.d.ts +8 -0
- package/dist/compiler/matching/hamming-distance.js +35 -16
- package/dist/compiler/matching/hierarchical-clustering.d.ts +9 -0
- package/dist/compiler/matching/hierarchical-clustering.js +76 -56
- package/dist/compiler/matching/matching.js +3 -3
- package/dist/compiler/node-worker.js +144 -18
- package/dist/compiler/offline-compiler.d.ts +34 -83
- package/dist/compiler/offline-compiler.js +92 -96
- package/dist/compiler/simple-ar.d.ts +31 -57
- package/dist/compiler/simple-ar.js +32 -73
- package/dist/compiler/three.d.ts +13 -8
- package/dist/compiler/three.js +6 -6
- package/dist/compiler/tracker/extract.js +17 -14
- package/dist/compiler/utils/images.js +11 -16
- package/dist/compiler/utils/lsh-direct.d.ts +12 -0
- package/dist/compiler/utils/lsh-direct.js +76 -0
- package/dist/compiler/utils/worker-pool.js +10 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/react/types.d.ts +1 -1
- package/dist/react/types.js +1 -1
- package/package.json +2 -1
- package/src/compiler/aframe.js +8 -8
- package/src/compiler/controller.ts +512 -0
- package/src/compiler/detector/detector-lite.js +87 -107
- package/src/compiler/index.js +3 -3
- package/src/compiler/matching/hamming-distance.js +39 -16
- package/src/compiler/matching/hierarchical-clustering.js +85 -57
- package/src/compiler/matching/matching.js +3 -3
- package/src/compiler/node-worker.js +163 -18
- package/src/compiler/offline-compiler.ts +513 -0
- package/src/compiler/{simple-ar.js → simple-ar.ts} +64 -91
- package/src/compiler/three.js +6 -6
- package/src/compiler/tracker/extract.js +18 -15
- package/src/compiler/utils/images.js +11 -21
- package/src/compiler/utils/lsh-direct.js +86 -0
- package/src/compiler/utils/worker-pool.js +9 -1
- package/src/index.ts +2 -2
- package/src/react/types.ts +2 -2
- package/src/compiler/controller.js +0 -554
- package/src/compiler/offline-compiler.js +0 -515
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
|
|
13
13
|
import { FREAKPOINTS } from "./freak.js";
|
|
14
14
|
import { gpuCompute } from "../utils/gpu-compute.js";
|
|
15
|
-
import {
|
|
15
|
+
import { computeLSH64, computeFullFREAK, packLSHIntoDescriptor } from "../utils/lsh-direct.js";
|
|
16
16
|
|
|
17
|
-
const PYRAMID_MIN_SIZE = 4; //
|
|
17
|
+
const PYRAMID_MIN_SIZE = 4; // Restored to 4 for better small-scale detection
|
|
18
18
|
// PYRAMID_MAX_OCTAVE ya no es necesario, el límite lo da PYRAMID_MIN_SIZE
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
const NUM_BUCKETS_PER_DIMENSION =
|
|
22
|
-
const MAX_FEATURES_PER_BUCKET =
|
|
21
|
+
const NUM_BUCKETS_PER_DIMENSION = 10;
|
|
22
|
+
const MAX_FEATURES_PER_BUCKET = 30; // Maximized to ensure robustness in Moonshot mode
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
const ORIENTATION_NUM_BINS = 36;
|
|
@@ -57,7 +57,7 @@ export class DetectorLite {
|
|
|
57
57
|
if (numOctaves === 10) break;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
this.numOctaves = numOctaves;
|
|
60
|
+
this.numOctaves = options.maxOctaves !== undefined ? Math.min(numOctaves, options.maxOctaves) : numOctaves;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
@@ -96,16 +96,19 @@ export class DetectorLite {
|
|
|
96
96
|
this._computeFreakDescriptors(prunedExtremas, pyramidImages);
|
|
97
97
|
|
|
98
98
|
// Convertir a formato de salida
|
|
99
|
-
const featurePoints = prunedExtremas.map(ext =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
const featurePoints = prunedExtremas.map(ext => {
|
|
100
|
+
const scale = Math.pow(2, ext.octave);
|
|
101
|
+
return {
|
|
102
|
+
maxima: ext.score > 0,
|
|
103
|
+
x: ext.x * scale + scale * 0.5 - 0.5,
|
|
104
|
+
y: ext.y * scale + scale * 0.5 - 0.5,
|
|
105
|
+
scale: scale,
|
|
106
|
+
angle: ext.angle || 0,
|
|
107
|
+
descriptors: (this.useLSH && ext.lsh) ? ext.descriptors : (ext.descriptors || [])
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return { featurePoints, pyramid: pyramidImages };
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
/**
|
|
@@ -143,6 +146,10 @@ export class DetectorLite {
|
|
|
143
146
|
|
|
144
147
|
for (let i = 0; i < this.numOctaves; i++) {
|
|
145
148
|
const img1 = this._applyGaussianFilter(currentData, currentWidth, currentHeight);
|
|
149
|
+
|
|
150
|
+
// Only need the second blur if we are going to compute DoG with the NEXT layer
|
|
151
|
+
// or if we need it for this octave's DoG.
|
|
152
|
+
// Actually, for maxOctaves=1, we only need img1 and maybe img2 for one DoG layer.
|
|
146
153
|
const img2 = this._applyGaussianFilter(img1.data, currentWidth, currentHeight);
|
|
147
154
|
|
|
148
155
|
pyramid.push([
|
|
@@ -150,7 +157,7 @@ export class DetectorLite {
|
|
|
150
157
|
{ data: img2.data, width: currentWidth, height: currentHeight }
|
|
151
158
|
]);
|
|
152
159
|
|
|
153
|
-
// Downsample para siguiente octava
|
|
160
|
+
// Downsample para siguiente octava - Only if we have more octaves to go
|
|
154
161
|
if (i < this.numOctaves - 1) {
|
|
155
162
|
const downsampled = this._downsample(img2.data, currentWidth, currentHeight);
|
|
156
163
|
currentData = downsampled.data;
|
|
@@ -168,44 +175,47 @@ export class DetectorLite {
|
|
|
168
175
|
_applyGaussianFilter(data, width, height) {
|
|
169
176
|
const output = new Float32Array(width * height);
|
|
170
177
|
const temp = new Float32Array(width * height);
|
|
171
|
-
const k0 =
|
|
178
|
+
const k0 = 0.0625, k1 = 0.25, k2 = 0.375; // 1/16, 4/16, 6/16
|
|
172
179
|
const w1 = width - 1;
|
|
173
|
-
const h1 = height - 1;
|
|
174
180
|
|
|
175
|
-
// Horizontal pass -
|
|
181
|
+
// Horizontal pass - Speed optimized with manual border handling
|
|
176
182
|
for (let y = 0; y < height; y++) {
|
|
177
183
|
const rowOffset = y * width;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
data[rowOffset + x] * k2 +
|
|
188
|
-
data[rowOffset + x3] * k1 +
|
|
189
|
-
data[rowOffset + x4] * k0;
|
|
184
|
+
|
|
185
|
+
// Left border
|
|
186
|
+
temp[rowOffset] = data[rowOffset] * (k0 + k1 + k2) + data[rowOffset + 1] * k1 + data[rowOffset + 2] * k0;
|
|
187
|
+
temp[rowOffset + 1] = data[rowOffset] * k1 + data[rowOffset + 1] * k2 + data[rowOffset + 2] * k1 + data[rowOffset + 3] * k0;
|
|
188
|
+
|
|
189
|
+
// Main loop - NO boundary checks
|
|
190
|
+
for (let x = 2; x < width - 2; x++) {
|
|
191
|
+
const pos = rowOffset + x;
|
|
192
|
+
temp[pos] = data[pos - 2] * k0 + data[pos - 1] * k1 + data[pos] * k2 + data[pos + 1] * k1 + data[pos + 2] * k0;
|
|
190
193
|
}
|
|
194
|
+
|
|
195
|
+
// Right border
|
|
196
|
+
const r2 = rowOffset + width - 2;
|
|
197
|
+
const r1 = rowOffset + width - 1;
|
|
198
|
+
temp[r2] = data[r2 - 2] * k0 + data[r2 - 1] * k1 + data[r2] * k2 + data[r1] * k1;
|
|
199
|
+
temp[r1] = data[r1 - 2] * k0 + data[r1 - 1] * k1 + data[r1] * (k2 + k1 + k0);
|
|
191
200
|
}
|
|
192
201
|
|
|
193
|
-
// Vertical pass -
|
|
194
|
-
for (let
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
output[
|
|
203
|
-
temp[y0 + x] * k0 +
|
|
204
|
-
temp[y1 + x] * k1 +
|
|
205
|
-
temp[y2 + x] * k2 +
|
|
206
|
-
temp[y3 + x] * k1 +
|
|
207
|
-
temp[y4 + x] * k0;
|
|
202
|
+
// Vertical pass - Speed optimized
|
|
203
|
+
for (let x = 0; x < width; x++) {
|
|
204
|
+
// Top border
|
|
205
|
+
output[x] = temp[x] * (k0 + k1 + k2) + temp[x + width] * k1 + temp[x + width * 2] * k0;
|
|
206
|
+
output[x + width] = temp[x] * k1 + temp[x + width] * k2 + temp[x + width * 2] * k1 + temp[x + width * 3] * k0;
|
|
207
|
+
|
|
208
|
+
// Main loop - NO boundary checks
|
|
209
|
+
for (let y = 2; y < height - 2; y++) {
|
|
210
|
+
const p = y * width + x;
|
|
211
|
+
output[p] = temp[p - width * 2] * k0 + temp[p - width] * k1 + temp[p] * k2 + temp[p + width] * k1 + temp[p + width * 2] * k0;
|
|
208
212
|
}
|
|
213
|
+
|
|
214
|
+
// Bottom border
|
|
215
|
+
const b2 = (height - 2) * width + x;
|
|
216
|
+
const b1 = (height - 1) * width + x;
|
|
217
|
+
output[b2] = temp[b2 - width * 2] * k0 + temp[b2 - width] * k1 + temp[b2] * k2 + temp[b1] * k1;
|
|
218
|
+
output[b1] = temp[b1 - width * 2] * k0 + temp[b1 - width] * k1 + temp[b1] * (k2 + k1 + k0);
|
|
209
219
|
}
|
|
210
220
|
|
|
211
221
|
return { data: output, width, height };
|
|
@@ -215,36 +225,19 @@ export class DetectorLite {
|
|
|
215
225
|
* Downsample imagen por factor de 2
|
|
216
226
|
*/
|
|
217
227
|
_downsample(data, width, height) {
|
|
218
|
-
const newWidth =
|
|
219
|
-
const newHeight =
|
|
228
|
+
const newWidth = width >> 1;
|
|
229
|
+
const newHeight = height >> 1;
|
|
220
230
|
const output = new Float32Array(newWidth * newHeight);
|
|
221
231
|
|
|
222
232
|
for (let y = 0; y < newHeight; y++) {
|
|
233
|
+
const r0 = (y * 2) * width;
|
|
234
|
+
const r1 = r0 + width;
|
|
235
|
+
const dr = y * newWidth;
|
|
223
236
|
for (let x = 0; x < newWidth; x++) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const srcY = y * 2 + 0.5;
|
|
227
|
-
const x0 = Math.floor(srcX);
|
|
228
|
-
const y0 = Math.floor(srcY);
|
|
229
|
-
const x1 = Math.min(x0 + 1, width - 1);
|
|
230
|
-
const y1 = Math.min(y0 + 1, height - 1);
|
|
231
|
-
|
|
232
|
-
const fx = srcX - x0;
|
|
233
|
-
const fy = srcY - y0;
|
|
234
|
-
|
|
235
|
-
const v00 = data[y0 * width + x0];
|
|
236
|
-
const v10 = data[y0 * width + x1];
|
|
237
|
-
const v01 = data[y1 * width + x0];
|
|
238
|
-
const v11 = data[y1 * width + x1];
|
|
239
|
-
|
|
240
|
-
output[y * newWidth + x] =
|
|
241
|
-
v00 * (1 - fx) * (1 - fy) +
|
|
242
|
-
v10 * fx * (1 - fy) +
|
|
243
|
-
v01 * (1 - fx) * fy +
|
|
244
|
-
v11 * fx * fy;
|
|
237
|
+
const i2 = x * 2;
|
|
238
|
+
output[dr + x] = (data[r0 + i2] + data[r0 + i2 + 1] + data[r1 + i2] + data[r1 + i2 + 1]) * 0.25;
|
|
245
239
|
}
|
|
246
240
|
}
|
|
247
|
-
|
|
248
241
|
return { data: output, width: newWidth, height: newHeight };
|
|
249
242
|
}
|
|
250
243
|
|
|
@@ -277,21 +270,19 @@ export class DetectorLite {
|
|
|
277
270
|
_findExtremas(dogPyramid, pyramidImages) {
|
|
278
271
|
const extremas = [];
|
|
279
272
|
|
|
280
|
-
for (let octave =
|
|
273
|
+
for (let octave = 0; octave < dogPyramid.length; octave++) {
|
|
281
274
|
const curr = dogPyramid[octave];
|
|
282
|
-
const prev = dogPyramid[octave - 1];
|
|
283
|
-
const next = dogPyramid[octave + 1];
|
|
275
|
+
const prev = octave > 0 ? dogPyramid[octave - 1] : null;
|
|
276
|
+
const next = octave < dogPyramid.length - 1 ? dogPyramid[octave + 1] : null;
|
|
284
277
|
|
|
285
278
|
const width = curr.width;
|
|
286
279
|
const height = curr.height;
|
|
287
|
-
const prevWidth = prev.width;
|
|
288
|
-
const nextWidth = next.width;
|
|
289
280
|
|
|
290
281
|
for (let y = 1; y < height - 1; y++) {
|
|
291
282
|
for (let x = 1; x < width - 1; x++) {
|
|
292
283
|
const val = curr.data[y * width + x];
|
|
293
284
|
|
|
294
|
-
if (Math.abs(val) < 0.
|
|
285
|
+
if (Math.abs(val) < 0.003) continue; // Aggressively lowered threshold to 0.003 for max sensitivity
|
|
295
286
|
|
|
296
287
|
let isMaxima = true;
|
|
297
288
|
let isMinima = true;
|
|
@@ -306,10 +297,11 @@ export class DetectorLite {
|
|
|
306
297
|
}
|
|
307
298
|
}
|
|
308
299
|
|
|
309
|
-
// Check previous scale (scaled coordinates)
|
|
310
|
-
if (isMaxima || isMinima) {
|
|
311
|
-
const px =
|
|
312
|
-
const py =
|
|
300
|
+
// Check previous scale (scaled coordinates) - skip if no prev layer
|
|
301
|
+
if ((isMaxima || isMinima) && prev) {
|
|
302
|
+
const px = x << 1;
|
|
303
|
+
const py = y << 1;
|
|
304
|
+
const prevWidth = prev.width;
|
|
313
305
|
for (let dy = -1; dy <= 1 && (isMaxima || isMinima); dy++) {
|
|
314
306
|
for (let dx = -1; dx <= 1 && (isMaxima || isMinima); dx++) {
|
|
315
307
|
const xx = Math.max(0, Math.min(prevWidth - 1, px + dx));
|
|
@@ -321,10 +313,11 @@ export class DetectorLite {
|
|
|
321
313
|
}
|
|
322
314
|
}
|
|
323
315
|
|
|
324
|
-
// Check next scale (scaled coordinates)
|
|
325
|
-
if (isMaxima || isMinima) {
|
|
326
|
-
const nx =
|
|
327
|
-
const ny =
|
|
316
|
+
// Check next scale (scaled coordinates) - skip if no next layer
|
|
317
|
+
if ((isMaxima || isMinima) && next) {
|
|
318
|
+
const nx = x >> 1;
|
|
319
|
+
const ny = y >> 1;
|
|
320
|
+
const nextWidth = next.width;
|
|
328
321
|
for (let dy = -1; dy <= 1 && (isMaxima || isMinima); dy++) {
|
|
329
322
|
for (let dx = -1; dx <= 1 && (isMaxima || isMinima); dx++) {
|
|
330
323
|
const xx = Math.max(0, Math.min(nextWidth - 1, nx + dx));
|
|
@@ -391,7 +384,7 @@ export class DetectorLite {
|
|
|
391
384
|
*/
|
|
392
385
|
_computeOrientations(extremas, pyramidImages) {
|
|
393
386
|
for (const ext of extremas) {
|
|
394
|
-
if (ext.octave <
|
|
387
|
+
if (ext.octave < 0 || ext.octave >= pyramidImages.length) {
|
|
395
388
|
ext.angle = 0;
|
|
396
389
|
continue;
|
|
397
390
|
}
|
|
@@ -444,8 +437,8 @@ export class DetectorLite {
|
|
|
444
437
|
*/
|
|
445
438
|
_computeFreakDescriptors(extremas, pyramidImages) {
|
|
446
439
|
for (const ext of extremas) {
|
|
447
|
-
if (ext.octave <
|
|
448
|
-
ext.descriptors =
|
|
440
|
+
if (ext.octave < 0 || ext.octave >= pyramidImages.length) {
|
|
441
|
+
ext.descriptors = new Uint8Array(8);
|
|
449
442
|
continue;
|
|
450
443
|
}
|
|
451
444
|
|
|
@@ -479,28 +472,15 @@ export class DetectorLite {
|
|
|
479
472
|
data[y1 * width + x1] * fracX * fracY;
|
|
480
473
|
}
|
|
481
474
|
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
let bitCount = 0;
|
|
485
|
-
let byteIdx = 0;
|
|
486
|
-
|
|
487
|
-
for (let i = 0; i < FREAKPOINTS.length; i++) {
|
|
488
|
-
for (let j = i + 1; j < FREAKPOINTS.length; j++) {
|
|
489
|
-
if (samples[i] < samples[j]) {
|
|
490
|
-
descriptor[byteIdx] |= (1 << (7 - bitCount));
|
|
491
|
-
}
|
|
492
|
-
bitCount++;
|
|
493
|
-
|
|
494
|
-
if (bitCount === 8) {
|
|
495
|
-
byteIdx++;
|
|
496
|
-
bitCount = 0;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
475
|
+
// 🚀 MOONSHOT: Direct LSH computation
|
|
476
|
+
// Avoids computing 672 bits of FREAK just to sample 64.
|
|
500
477
|
if (this.useLSH) {
|
|
501
|
-
ext.lsh =
|
|
478
|
+
ext.lsh = computeLSH64(samples);
|
|
479
|
+
// Pack LSH into 8-byte descriptors for compatibility
|
|
480
|
+
ext.descriptors = packLSHIntoDescriptor(ext.lsh);
|
|
481
|
+
} else {
|
|
482
|
+
ext.descriptors = computeFullFREAK(samples);
|
|
502
483
|
}
|
|
503
|
-
ext.descriptors = descriptor;
|
|
504
484
|
}
|
|
505
485
|
}
|
|
506
486
|
}
|
package/src/compiler/index.js
CHANGED
|
@@ -2,10 +2,10 @@ import { OfflineCompiler } from "./offline-compiler.js";
|
|
|
2
2
|
|
|
3
3
|
export { OfflineCompiler };
|
|
4
4
|
|
|
5
|
-
if (!window.
|
|
6
|
-
window.
|
|
5
|
+
if (!window.TAAR) {
|
|
6
|
+
window.TAAR = {};
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
window.
|
|
9
|
+
window.TAAR.IMAGE = {
|
|
10
10
|
OfflineCompiler,
|
|
11
11
|
};
|
|
@@ -7,25 +7,51 @@ for (let i = 0; i < 256; i++) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* 🚀 Moonshot Optimized Popcount
|
|
11
|
+
* Uses a slightly faster bitwise sequence for 32-bit integers
|
|
11
12
|
*/
|
|
12
13
|
function popcount32(n) {
|
|
13
|
-
n = n
|
|
14
|
-
n
|
|
15
|
-
|
|
14
|
+
n = n >>> 0; // Force unsigned
|
|
15
|
+
n -= (n >>> 1) & 0x55555555;
|
|
16
|
+
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
|
|
17
|
+
return (((n + (n >>> 4)) & 0x0F0F0F0F) * 0x01010101) >>> 24;
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Super-optimized Hamming distance for 64-bit LSH (2x Uint32)
|
|
22
|
+
* NO OBJECTS, NO OPTIONS, JUST PURE SPEED.
|
|
23
|
+
*/
|
|
24
|
+
const compute64 = (v1, v1Idx, v2, v2Idx) => {
|
|
25
|
+
// Inline XOR and popcount for maximum speed
|
|
26
|
+
let x1 = (v1[v1Idx] ^ v2[v2Idx]) >>> 0;
|
|
27
|
+
let x2 = (v1[v1Idx + 1] ^ v2[v2Idx + 1]) >>> 0;
|
|
28
|
+
|
|
29
|
+
// Popcount 1
|
|
30
|
+
x1 -= (x1 >>> 1) & 0x55555555;
|
|
31
|
+
x1 = (x1 & 0x33333333) + ((x1 >>> 2) & 0x33333333);
|
|
32
|
+
const count1 = (((x1 + (x1 >>> 4)) & 0x0F0F0F0F) * 0x01010101) >>> 24;
|
|
33
|
+
|
|
34
|
+
// Popcount 2
|
|
35
|
+
x2 -= (x2 >>> 1) & 0x55555555;
|
|
36
|
+
x2 = (x2 & 0x33333333) + ((x2 >>> 2) & 0x33333333);
|
|
37
|
+
const count2 = (((x2 + (x2 >>> 4)) & 0x0F0F0F0F) * 0x01010101) >>> 24;
|
|
38
|
+
|
|
39
|
+
return count1 + count2;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generic compute for backward compatibility
|
|
44
|
+
*/
|
|
18
45
|
const compute = (options) => {
|
|
19
46
|
const { v1, v2, v1Offset = 0, v2Offset = 0 } = options;
|
|
47
|
+
const v2Len = v2.length - v2Offset;
|
|
20
48
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// This is a bit hacky check, better if we know the version.
|
|
24
|
-
// Assuming if it's not 84 bytes, it's the new 8-byte format.
|
|
49
|
+
if (v2Len === 2) {
|
|
50
|
+
return compute64(v1, v1Offset, v2, v2Offset);
|
|
25
51
|
}
|
|
26
52
|
|
|
27
|
-
//
|
|
28
|
-
if (
|
|
53
|
+
// Protocol V4: 84-byte descriptors (Uint8Array)
|
|
54
|
+
if (v2Len === 84) {
|
|
29
55
|
let d = 0;
|
|
30
56
|
for (let i = 0; i < 84; i++) {
|
|
31
57
|
d += BIT_COUNT_8[v1[v1Offset + i] ^ v2[v2Offset + i]];
|
|
@@ -33,19 +59,16 @@ const compute = (options) => {
|
|
|
33
59
|
return d;
|
|
34
60
|
}
|
|
35
61
|
|
|
36
|
-
// Protocol V5.1
|
|
37
|
-
|
|
38
|
-
if (v1.length >= v1Offset + 4 && v2.length >= v2Offset + 4 && v1[v1Offset + 3] !== undefined) {
|
|
62
|
+
// Protocol V5.1: 128-bit LSH (4 x Uint32)
|
|
63
|
+
if (v2Len === 4) {
|
|
39
64
|
return popcount32(v1[v1Offset] ^ v2[v2Offset]) +
|
|
40
65
|
popcount32(v1[v1Offset + 1] ^ v2[v2Offset + 1]) +
|
|
41
66
|
popcount32(v1[v1Offset + 2] ^ v2[v2Offset + 2]) +
|
|
42
67
|
popcount32(v1[v1Offset + 3] ^ v2[v2Offset + 3]);
|
|
43
68
|
}
|
|
44
69
|
|
|
45
|
-
// Protocol V5 Path: LSH 64-bit (2 x 32-bit)
|
|
46
|
-
// We expect v1 and v2 to be slices or offsets of Uint32Array
|
|
47
70
|
return popcount32(v1[v1Offset] ^ v2[v2Offset]) +
|
|
48
71
|
popcount32(v1[v1Offset + 1] ^ v2[v2Offset + 1]);
|
|
49
72
|
};
|
|
50
73
|
|
|
51
|
-
export { compute };
|
|
74
|
+
export { compute, compute64 };
|
|
@@ -1,101 +1,133 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { compute64 as hammingCompute64 } from "./hamming-distance.js";
|
|
2
2
|
import { createRandomizer } from "../utils/randomizer.js";
|
|
3
3
|
|
|
4
|
-
const MIN_FEATURE_PER_NODE = 16
|
|
5
|
-
const NUM_ASSIGNMENT_HYPOTHESES =
|
|
4
|
+
const MIN_FEATURE_PER_NODE = 32; // Increased from 16 for speed
|
|
5
|
+
const NUM_ASSIGNMENT_HYPOTHESES = 12; // Reduced from 16 for speed
|
|
6
6
|
const NUM_CENTERS = 8;
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* 🚀 Moonshot Optimized K-Medoids
|
|
10
|
+
*
|
|
11
|
+
* Major Optimizations:
|
|
12
|
+
* 1. Flattened Memory: Operates on a single Uint32Array block instead of objects.
|
|
13
|
+
* 2. Zero Property Access: Avoids .descriptors lookup in the tightest loop.
|
|
14
|
+
* 3. Cache-Friendly: Accesses contiguous descriptor data.
|
|
15
|
+
*/
|
|
8
16
|
const _computeKMedoids = (options) => {
|
|
9
|
-
const {
|
|
17
|
+
const { descriptors, pointIndexes, randomizer } = options;
|
|
18
|
+
const numPointIndexes = pointIndexes.length;
|
|
10
19
|
|
|
11
|
-
const randomPointIndexes =
|
|
12
|
-
for (let i = 0; i <
|
|
13
|
-
randomPointIndexes
|
|
20
|
+
const randomPointIndexes = new Int32Array(numPointIndexes);
|
|
21
|
+
for (let i = 0; i < numPointIndexes; i++) {
|
|
22
|
+
randomPointIndexes[i] = i;
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
let bestSumD = Number.MAX_SAFE_INTEGER;
|
|
17
|
-
let
|
|
26
|
+
let bestAssignment = null;
|
|
27
|
+
|
|
28
|
+
// Pre-fetch centers indices to avoid nested index lookups
|
|
29
|
+
const centerPointIndices = new Int32Array(NUM_CENTERS);
|
|
18
30
|
|
|
19
|
-
const assignments = [];
|
|
20
31
|
for (let i = 0; i < NUM_ASSIGNMENT_HYPOTHESES; i++) {
|
|
21
32
|
randomizer.arrayShuffle({ arr: randomPointIndexes, sampleSize: NUM_CENTERS });
|
|
22
33
|
|
|
34
|
+
// Set centers for this hypothesis
|
|
35
|
+
for (let k = 0; k < NUM_CENTERS; k++) {
|
|
36
|
+
centerPointIndices[k] = pointIndexes[randomPointIndexes[k]];
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
let sumD = 0;
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
const currentAssignment = new Int32Array(numPointIndexes);
|
|
41
|
+
|
|
42
|
+
for (let j = 0; j < numPointIndexes; j++) {
|
|
43
|
+
const pIdx = pointIndexes[j];
|
|
44
|
+
const pOffset = pIdx * 2;
|
|
45
|
+
|
|
46
|
+
let bestD = 255; // Max possible Hamming for 64-bit is 64, but let's be safe
|
|
47
|
+
let bestCenterIdx = -1;
|
|
48
|
+
|
|
27
49
|
for (let k = 0; k < NUM_CENTERS; k++) {
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
const cIdx = centerPointIndices[k];
|
|
51
|
+
const cOffset = cIdx * 2;
|
|
52
|
+
|
|
53
|
+
// DIRECT CALL TO INLINED HAMMING
|
|
54
|
+
const d = hammingCompute64(descriptors, pOffset, descriptors, cOffset);
|
|
55
|
+
|
|
33
56
|
if (d < bestD) {
|
|
34
|
-
|
|
57
|
+
bestCenterIdx = randomPointIndexes[k];
|
|
35
58
|
bestD = d;
|
|
36
59
|
}
|
|
37
60
|
}
|
|
61
|
+
currentAssignment[j] = bestCenterIdx;
|
|
38
62
|
sumD += bestD;
|
|
39
63
|
}
|
|
40
|
-
assignments.push(assignment);
|
|
41
64
|
|
|
42
65
|
if (sumD < bestSumD) {
|
|
43
66
|
bestSumD = sumD;
|
|
44
|
-
|
|
67
|
+
bestAssignment = currentAssignment;
|
|
45
68
|
}
|
|
46
69
|
}
|
|
47
|
-
return
|
|
70
|
+
return bestAssignment;
|
|
48
71
|
};
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// isLeaf: bool,
|
|
54
|
-
// children: [], list of children node
|
|
55
|
-
// pointIndexes: [], list of int, point indexes
|
|
56
|
-
// centerPointIndex: int
|
|
57
|
-
// }
|
|
73
|
+
/**
|
|
74
|
+
* Build hierarchical clusters
|
|
75
|
+
*/
|
|
58
76
|
const build = ({ points }) => {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
const numPoints = points.length;
|
|
78
|
+
if (numPoints === 0) return { rootNode: { leaf: true, pointIndexes: [], centerPointIndex: null } };
|
|
79
|
+
|
|
80
|
+
// 🚀 MOONSHOT: Flatten all descriptors into a single Uint32Array
|
|
81
|
+
// This is the key to sub-second performance.
|
|
82
|
+
const descriptors = new Uint32Array(numPoints * 2);
|
|
83
|
+
for (let i = 0; i < numPoints; i++) {
|
|
84
|
+
const d = points[i].descriptors;
|
|
85
|
+
descriptors[i * 2] = d[0];
|
|
86
|
+
descriptors[i * 2 + 1] = d[1];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const pointIndexes = new Int32Array(numPoints);
|
|
90
|
+
for (let i = 0; i < numPoints; i++) {
|
|
91
|
+
pointIndexes[i] = i;
|
|
62
92
|
}
|
|
63
93
|
|
|
64
94
|
const randomizer = createRandomizer();
|
|
65
95
|
|
|
66
96
|
const rootNode = _build({
|
|
67
|
-
|
|
68
|
-
pointIndexes
|
|
97
|
+
descriptors,
|
|
98
|
+
pointIndexes,
|
|
69
99
|
centerPointIndex: null,
|
|
70
100
|
randomizer,
|
|
71
101
|
});
|
|
72
102
|
return { rootNode };
|
|
73
103
|
};
|
|
74
104
|
|
|
75
|
-
// recursive build hierarchy clusters
|
|
76
105
|
const _build = (options) => {
|
|
77
|
-
const {
|
|
106
|
+
const { descriptors, pointIndexes, centerPointIndex, randomizer } = options;
|
|
107
|
+
const numPoints = pointIndexes.length;
|
|
78
108
|
|
|
79
109
|
let isLeaf = false;
|
|
80
|
-
|
|
81
|
-
if (pointIndexes.length <= NUM_CENTERS || pointIndexes.length <= MIN_FEATURE_PER_NODE) {
|
|
110
|
+
if (numPoints <= NUM_CENTERS || numPoints <= MIN_FEATURE_PER_NODE) {
|
|
82
111
|
isLeaf = true;
|
|
83
112
|
}
|
|
84
113
|
|
|
85
|
-
const clusters =
|
|
114
|
+
const clusters = new Map();
|
|
86
115
|
if (!isLeaf) {
|
|
87
|
-
|
|
88
|
-
const assignment = _computeKMedoids({ points, pointIndexes, randomizer });
|
|
116
|
+
const assignment = _computeKMedoids({ descriptors, pointIndexes, randomizer });
|
|
89
117
|
|
|
90
118
|
for (let i = 0; i < assignment.length; i++) {
|
|
91
|
-
|
|
92
|
-
|
|
119
|
+
const centerIdx = pointIndexes[assignment[i]];
|
|
120
|
+
let cluster = clusters.get(centerIdx);
|
|
121
|
+
if (cluster === undefined) {
|
|
122
|
+
cluster = [];
|
|
123
|
+
clusters.set(centerIdx, cluster);
|
|
93
124
|
}
|
|
94
|
-
|
|
125
|
+
cluster.push(pointIndexes[i]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (clusters.size === 1) {
|
|
129
|
+
isLeaf = true;
|
|
95
130
|
}
|
|
96
|
-
}
|
|
97
|
-
if (Object.keys(clusters).length === 1) {
|
|
98
|
-
isLeaf = true;
|
|
99
131
|
}
|
|
100
132
|
|
|
101
133
|
const node = {
|
|
@@ -104,27 +136,23 @@ const _build = (options) => {
|
|
|
104
136
|
|
|
105
137
|
if (isLeaf) {
|
|
106
138
|
node.leaf = true;
|
|
107
|
-
node.pointIndexes =
|
|
108
|
-
for (let i = 0; i < pointIndexes.length; i++) {
|
|
109
|
-
node.pointIndexes.push(pointIndexes[i]);
|
|
110
|
-
}
|
|
139
|
+
node.pointIndexes = new Int32Array(pointIndexes);
|
|
111
140
|
return node;
|
|
112
141
|
}
|
|
113
142
|
|
|
114
|
-
// recursive build children
|
|
115
143
|
node.leaf = false;
|
|
116
144
|
node.children = [];
|
|
117
145
|
|
|
118
|
-
|
|
146
|
+
for (const [cIdx, clusterPoints] of clusters) {
|
|
119
147
|
node.children.push(
|
|
120
148
|
_build({
|
|
121
|
-
|
|
122
|
-
pointIndexes:
|
|
123
|
-
centerPointIndex:
|
|
149
|
+
descriptors,
|
|
150
|
+
pointIndexes: new Int32Array(clusterPoints),
|
|
151
|
+
centerPointIndex: cIdx,
|
|
124
152
|
randomizer,
|
|
125
153
|
}),
|
|
126
154
|
);
|
|
127
|
-
}
|
|
155
|
+
}
|
|
128
156
|
return node;
|
|
129
157
|
};
|
|
130
158
|
|
|
@@ -4,11 +4,11 @@ import { computeHoughMatches } from "./hough.js";
|
|
|
4
4
|
import { computeHomography } from "./ransacHomography.js";
|
|
5
5
|
import { multiplyPointHomographyInhomogenous, matrixInverse33 } from "../utils/geometry.js";
|
|
6
6
|
|
|
7
|
-
const INLIER_THRESHOLD = 3
|
|
7
|
+
const INLIER_THRESHOLD = 10; // Relaxed from 3 to 10 for better robustness with LSH
|
|
8
8
|
//const MIN_NUM_INLIERS = 8; //default
|
|
9
9
|
const MIN_NUM_INLIERS = 6;
|
|
10
|
-
const CLUSTER_MAX_POP =
|
|
11
|
-
const HAMMING_THRESHOLD = 0.
|
|
10
|
+
const CLUSTER_MAX_POP = 20; // Increased to explore more candidate clusters
|
|
11
|
+
const HAMMING_THRESHOLD = 0.85; // Relaxed ratio test for binary descriptors
|
|
12
12
|
|
|
13
13
|
// match list of querpoints against pre-built list of keyframes
|
|
14
14
|
const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) => {
|