@srsergio/taptapp-ar 1.0.0 → 1.0.3

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 (88) hide show
  1. package/README.md +102 -26
  2. package/dist/compiler/aframe.js +0 -3
  3. package/dist/compiler/compiler-base.d.ts +3 -7
  4. package/dist/compiler/compiler-base.js +28 -14
  5. package/dist/compiler/compiler.js +1 -1
  6. package/dist/compiler/compiler.worker.js +1 -1
  7. package/dist/compiler/controller.js +4 -5
  8. package/dist/compiler/controller.worker.js +0 -2
  9. package/dist/compiler/detector/crop-detector.js +0 -2
  10. package/dist/compiler/detector/detector-lite.d.ts +73 -0
  11. package/dist/compiler/detector/detector-lite.js +430 -0
  12. package/dist/compiler/detector/detector.js +236 -243
  13. package/dist/compiler/detector/kernels/cpu/binomialFilter.js +0 -1
  14. package/dist/compiler/detector/kernels/cpu/computeLocalization.js +0 -4
  15. package/dist/compiler/detector/kernels/cpu/computeOrientationHistograms.js +0 -18
  16. package/dist/compiler/detector/kernels/cpu/fakeShader.js +1 -1
  17. package/dist/compiler/detector/kernels/cpu/prune.d.ts +7 -1
  18. package/dist/compiler/detector/kernels/cpu/prune.js +1 -42
  19. package/dist/compiler/detector/kernels/webgl/upsampleBilinear.js +2 -2
  20. package/dist/compiler/estimation/refine-estimate.js +0 -1
  21. package/dist/compiler/estimation/utils.d.ts +1 -1
  22. package/dist/compiler/estimation/utils.js +1 -14
  23. package/dist/compiler/image-list.js +4 -4
  24. package/dist/compiler/input-loader.js +2 -2
  25. package/dist/compiler/matching/hamming-distance.js +13 -13
  26. package/dist/compiler/matching/hierarchical-clustering.js +1 -1
  27. package/dist/compiler/matching/matching.d.ts +20 -4
  28. package/dist/compiler/matching/matching.js +67 -41
  29. package/dist/compiler/matching/ransacHomography.js +1 -2
  30. package/dist/compiler/node-worker.d.ts +1 -0
  31. package/dist/compiler/node-worker.js +84 -0
  32. package/dist/compiler/offline-compiler.d.ts +171 -6
  33. package/dist/compiler/offline-compiler.js +303 -421
  34. package/dist/compiler/tensorflow-setup.js +27 -1
  35. package/dist/compiler/three.js +3 -5
  36. package/dist/compiler/tracker/extract.d.ts +1 -0
  37. package/dist/compiler/tracker/extract.js +200 -244
  38. package/dist/compiler/tracker/tracker.d.ts +1 -1
  39. package/dist/compiler/tracker/tracker.js +13 -18
  40. package/dist/compiler/utils/cumsum.d.ts +4 -2
  41. package/dist/compiler/utils/cumsum.js +17 -19
  42. package/dist/compiler/utils/gpu-compute.d.ts +57 -0
  43. package/dist/compiler/utils/gpu-compute.js +262 -0
  44. package/dist/compiler/utils/images.d.ts +4 -4
  45. package/dist/compiler/utils/images.js +67 -53
  46. package/dist/compiler/utils/worker-pool.d.ts +14 -0
  47. package/dist/compiler/utils/worker-pool.js +84 -0
  48. package/dist/index.d.ts +0 -2
  49. package/dist/index.js +0 -2
  50. package/package.json +19 -13
  51. package/src/compiler/aframe.js +2 -4
  52. package/src/compiler/compiler-base.js +29 -14
  53. package/src/compiler/compiler.js +1 -1
  54. package/src/compiler/compiler.worker.js +1 -1
  55. package/src/compiler/controller.js +4 -5
  56. package/src/compiler/controller.worker.js +0 -2
  57. package/src/compiler/detector/crop-detector.js +0 -2
  58. package/src/compiler/detector/detector-lite.js +494 -0
  59. package/src/compiler/detector/detector.js +1052 -1063
  60. package/src/compiler/detector/kernels/cpu/binomialFilter.js +0 -1
  61. package/src/compiler/detector/kernels/cpu/computeLocalization.js +0 -4
  62. package/src/compiler/detector/kernels/cpu/computeOrientationHistograms.js +0 -17
  63. package/src/compiler/detector/kernels/cpu/fakeShader.js +1 -1
  64. package/src/compiler/detector/kernels/cpu/prune.js +1 -37
  65. package/src/compiler/detector/kernels/webgl/upsampleBilinear.js +2 -2
  66. package/src/compiler/estimation/refine-estimate.js +0 -1
  67. package/src/compiler/estimation/utils.js +9 -24
  68. package/src/compiler/image-list.js +4 -4
  69. package/src/compiler/input-loader.js +2 -2
  70. package/src/compiler/matching/hamming-distance.js +11 -15
  71. package/src/compiler/matching/hierarchical-clustering.js +1 -1
  72. package/src/compiler/matching/matching.js +72 -42
  73. package/src/compiler/matching/ransacHomography.js +0 -2
  74. package/src/compiler/node-worker.js +93 -0
  75. package/src/compiler/offline-compiler.js +339 -504
  76. package/src/compiler/tensorflow-setup.js +29 -1
  77. package/src/compiler/three.js +3 -5
  78. package/src/compiler/tracker/extract.js +211 -267
  79. package/src/compiler/tracker/tracker.js +13 -22
  80. package/src/compiler/utils/cumsum.js +17 -19
  81. package/src/compiler/utils/gpu-compute.js +303 -0
  82. package/src/compiler/utils/images.js +84 -53
  83. package/src/compiler/utils/worker-pool.js +89 -0
  84. package/src/index.ts +0 -2
  85. package/src/compiler/estimation/esimate-experiment.js +0 -316
  86. package/src/compiler/estimation/refine-estimate-experiment.js +0 -512
  87. package/src/react/AREditor.tsx +0 -394
  88. package/src/react/ProgressDialog.tsx +0 -185
@@ -5,6 +5,24 @@ import "@tensorflow/tfjs-backend-webgl";
5
5
  // Registrar kernels personalizados
6
6
  import "./detector/kernels/cpu/index.js";
7
7
  import "./detector/kernels/webgl/index.js";
8
+ /**
9
+ * Intenta cargar el backend de Node.js si está disponible
10
+ */
11
+ const loadNodeBackend = async () => {
12
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
13
+ try {
14
+ // Usar import dinámico para evitar errores en el navegador
15
+ await import("@tensorflow/tfjs-node");
16
+ console.log("🚀 TensorFlow Node.js backend cargado correctamente");
17
+ return true;
18
+ }
19
+ catch (e) {
20
+ console.warn("⚠️ No se pudo cargar @tensorflow/tfjs-node, usando fallback");
21
+ return false;
22
+ }
23
+ }
24
+ return false;
25
+ };
8
26
  /**
9
27
  * Configuración optimizada de TensorFlow para diferentes entornos
10
28
  * @returns {Promise<string>} El backend activo ('webgl', 'cpu')
@@ -12,14 +30,22 @@ import "./detector/kernels/webgl/index.js";
12
30
  export async function setupTensorFlow() {
13
31
  console.log("🔧 Iniciando configuración optimizada de TensorFlow.js...");
14
32
  try {
33
+ // Intentar cargar backend de Node.js primero
34
+ const nodeBackendLoaded = await loadNodeBackend();
15
35
  // Optimizaciones base para todos los backends
16
36
  tf.ENV.set("DEBUG", false);
17
37
  tf.ENV.set("WEBGL_CPU_FORWARD", false);
18
38
  tf.ENV.set("WEBGL_FORCE_F16_TEXTURES", true);
19
39
  // Configuración adaptativa de memoria según el entorno
20
40
  const isServerless = typeof window === "undefined";
21
- const memoryThreshold = isServerless ? 1024 * 1024 * 4 : 1024 * 1024 * 8; // 4MB en serverless, 8MB en cliente
41
+ const memoryThreshold = isServerless ? 1024 * 1024 * 4 : 1024 * 1024 * 8;
22
42
  tf.ENV.set("CPU_HANDOFF_SIZE_THRESHOLD", memoryThreshold);
43
+ if (nodeBackendLoaded) {
44
+ await tf.setBackend("tensorflow");
45
+ console.log("🚀 Backend TensorFlow (Node.js) activado");
46
+ await tf.ready();
47
+ return "tensorflow";
48
+ }
23
49
  // Configuración específica para entorno serverless
24
50
  if (isServerless) {
25
51
  try {
@@ -87,7 +87,7 @@ export class MindARThree {
87
87
  return anchor;
88
88
  }
89
89
  _startVideo() {
90
- return new Promise((resolve, reject) => {
90
+ return new Promise((resolve) => {
91
91
  this.video = document.createElement("video");
92
92
  this.video.setAttribute("autoplay", "");
93
93
  this.video.setAttribute("muted", "");
@@ -139,9 +139,8 @@ export class MindARThree {
139
139
  });
140
140
  }
141
141
  _startAR() {
142
- return new Promise(async (resolve, reject) => {
142
+ return new Promise(async (resolve) => {
143
143
  const video = this.video;
144
- const container = this.container;
145
144
  this.controller = new Controller({
146
145
  inputWidth: video.videoWidth,
147
146
  inputHeight: video.videoHeight,
@@ -221,7 +220,7 @@ export class MindARThree {
221
220
  postMatrix.compose(position, quaternion, scale);
222
221
  this.postMatrixs.push(postMatrix);
223
222
  }
224
- await this.controller.dummyRun(this.video);
223
+ this.controller.dummyRun(this.video);
225
224
  this.ui.hideLoading();
226
225
  this.ui.showScanning();
227
226
  this.controller.processVideo(this.video);
@@ -273,7 +272,6 @@ export class MindARThree {
273
272
  const fov = (2 * Math.atan((1 / proj[5]) * fovAdjust) * 180) / Math.PI; // vertical fov
274
273
  const near = proj[14] / (proj[10] - 1.0);
275
274
  const far = proj[14] / (proj[10] + 1.0);
276
- const ratio = proj[5] / proj[0]; // (r-l) / (t-b)
277
275
  camera.fov = fov;
278
276
  camera.near = near;
279
277
  camera.far = far;
@@ -1,3 +1,4 @@
1
+ export function setGPUMode(enabled: boolean): void;
1
2
  export function extract(image: any): {
2
3
  x: number;
3
4
  y: number;
@@ -1,15 +1,24 @@
1
1
  import { Cumsum } from "../utils/cumsum.js";
2
+ import { gpuCompute } from "../utils/gpu-compute.js";
2
3
  const SEARCH_SIZE1 = 10;
3
4
  const SEARCH_SIZE2 = 2;
4
- //const TEMPLATE_SIZE = 22 // DEFAULT
5
+ // Template parameters - ajustados para más puntos
5
6
  const TEMPLATE_SIZE = 6;
6
- const TEMPLATE_SD_THRESH = 5.0;
7
+ const TEMPLATE_SD_THRESH = 4.0; // Reducido de 5.0 para aceptar más candidatos
7
8
  const MAX_SIM_THRESH = 0.95;
8
9
  const MAX_THRESH = 0.9;
9
- //const MIN_THRESH = 0.55;
10
10
  const MIN_THRESH = 0.2;
11
11
  const SD_THRESH = 8.0;
12
- const OCCUPANCY_SIZE = (24 * 2) / 3;
12
+ const OCCUPANCY_SIZE = 10; // Reducido de 16 para permitir puntos más cercanos
13
+ // GPU mode flag - set to false to use original JS implementation
14
+ let useGPU = true;
15
+ /**
16
+ * Set GPU mode for extraction
17
+ * @param {boolean} enabled - Whether to use GPU acceleration
18
+ */
19
+ export const setGPUMode = (enabled) => {
20
+ useGPU = enabled;
21
+ };
13
22
  /*
14
23
  * Input image is in grey format. the imageData array size is width * height. value range from 0-255
15
24
  * pixel value at row r and c = imageData[r * width + c]
@@ -19,264 +28,222 @@ const OCCUPANCY_SIZE = (24 * 2) / 3;
19
28
  * @param {int} options.height image height
20
29
  */
21
30
  const extract = (image) => {
22
- const { data: imageData, width, height, scale } = image;
23
- // Step 1 - filter out interesting points. Interesting points have strong pixel value changed across neighbours
24
- const isPixelSelected = [width * height];
25
- for (let i = 0; i < isPixelSelected.length; i++)
26
- isPixelSelected[i] = false;
27
- // Step 1.1 consider a pixel at position (x, y). compute:
28
- // dx = ((data[x+1, y-1] - data[x-1, y-1]) + (data[x+1, y] - data[x-1, y]) + (data[x+1, y+1] - data[x-1, y-1])) / 256 / 3
29
- // dy = ((data[x+1, y+1] - data[x+1, y-1]) + (data[x, y+1] - data[x, y-1]) + (data[x-1, y+1] - data[x-1, y-1])) / 256 / 3
30
- // dValue = sqrt(dx^2 + dy^2) / 2;
31
- const dValue = new Float32Array(imageData.length);
32
- for (let i = 0; i < width; i++) {
33
- dValue[i] = -1;
34
- dValue[width * (height - 1) + i] = -1;
35
- }
36
- for (let j = 0; j < height; j++) {
37
- dValue[j * width] = -1;
38
- dValue[j * width + width - 1] = -1;
31
+ const { data: imageData, width, height } = image;
32
+ let dValue, isCandidate;
33
+ if (useGPU) {
34
+ // GPU-accelerated edge detection
35
+ const result = gpuCompute.edgeDetection(imageData, width, height);
36
+ dValue = result.dValue;
37
+ isCandidate = result.isCandidate;
39
38
  }
40
- for (let i = 1; i < width - 1; i++) {
39
+ else {
40
+ // Original JS implementation
41
+ dValue = new Float32Array(imageData.length);
42
+ isCandidate = new Uint8Array(imageData.length);
41
43
  for (let j = 1; j < height - 1; j++) {
42
- let pos = i + width * j;
43
- let dx = 0.0;
44
- let dy = 0.0;
45
- for (let k = -1; k <= 1; k++) {
46
- dx += imageData[pos + width * k + 1] - imageData[pos + width * k - 1];
47
- dy += imageData[pos + width + k] - imageData[pos - width + k];
44
+ const rowOffset = j * width;
45
+ const prevRowOffset = (j - 1) * width;
46
+ const nextRowOffset = (j + 1) * width;
47
+ for (let i = 1; i < width - 1; i++) {
48
+ const pos = rowOffset + i;
49
+ // dx/dy with tight loops
50
+ let dx = (imageData[prevRowOffset + i + 1] - imageData[prevRowOffset + i - 1] +
51
+ imageData[rowOffset + i + 1] - imageData[rowOffset + i - 1] +
52
+ imageData[nextRowOffset + i + 1] - imageData[nextRowOffset + i - 1]) / 768;
53
+ let dy = (imageData[nextRowOffset + i - 1] - imageData[prevRowOffset + i - 1] +
54
+ imageData[nextRowOffset + i] - imageData[prevRowOffset + i] +
55
+ imageData[nextRowOffset + i + 1] - imageData[prevRowOffset + i + 1]) / 768;
56
+ dValue[pos] = Math.sqrt((dx * dx + dy * dy) / 2);
48
57
  }
49
- dx /= 3 * 256;
50
- dy /= 3 * 256;
51
- dValue[pos] = Math.sqrt((dx * dx + dy * dy) / 2);
52
58
  }
53
- }
54
- // Step 1.2 - select all pixel which is dValue largest than all its neighbour as "potential" candidate
55
- // the number of selected points is still too many, so we use the value to further filter (e.g. largest the dValue, the better)
56
- const dValueHist = new Uint32Array(1000); // histogram of dvalue scaled to [0, 1000)
57
- for (let i = 0; i < 1000; i++)
58
- dValueHist[i] = 0;
59
- const neighbourOffsets = [-1, 1, -width, width];
60
- let allCount = 0;
61
- for (let i = 1; i < width - 1; i++) {
59
+ // Step 1.2 - Local Maxima (for JS path)
62
60
  for (let j = 1; j < height - 1; j++) {
63
- let pos = i + width * j;
64
- let isMax = true;
65
- for (let d = 0; d < neighbourOffsets.length; d++) {
66
- if (dValue[pos] <= dValue[pos + neighbourOffsets[d]]) {
67
- isMax = false;
68
- break;
61
+ const rowOffset = j * width;
62
+ for (let i = 1; i < width - 1; i++) {
63
+ const pos = rowOffset + i;
64
+ const val = dValue[pos];
65
+ if (val > 0 &&
66
+ val >= dValue[pos - 1] && val >= dValue[pos + 1] &&
67
+ val >= dValue[pos - width] && val >= dValue[pos + width]) {
68
+ isCandidate[pos] = 1;
69
69
  }
70
70
  }
71
- if (isMax) {
72
- let k = Math.floor(dValue[pos] * 1000);
71
+ }
72
+ }
73
+ // Step 1.2 - Build Histogram from detected candidates
74
+ const dValueHist = new Uint32Array(1000);
75
+ let allCount = 0;
76
+ for (let j = 1; j < height - 1; j++) {
77
+ const rowOffset = j * width;
78
+ for (let i = 1; i < width - 1; i++) {
79
+ const pos = rowOffset + i;
80
+ if (isCandidate[pos]) {
81
+ const val = dValue[pos];
82
+ let k = Math.floor(val * 1000);
73
83
  if (k > 999)
74
- k = 999; // k>999 should not happen if computaiton is correction
75
- if (k < 0)
76
- k = 0; // k<0 should not happen if computaiton is correction
77
- dValueHist[k] += 1;
78
- allCount += 1;
79
- isPixelSelected[pos] = true;
84
+ k = 999;
85
+ dValueHist[k]++;
86
+ allCount++;
80
87
  }
81
88
  }
82
89
  }
83
- // reduce number of points according to dValue.
84
- // actually, the whole Step 1. might be better to just sort the dvalues and pick the top (0.02 * width * height) points
85
- const maxPoints = 0.02 * width * height;
86
- let k = 999;
90
+ // Determine dValue threshold for top 5% (aumentado de 2% para más candidatos)
91
+ const maxPoints = 0.05 * width * height;
92
+ let kThresh = 999;
87
93
  let filteredCount = 0;
88
- while (k >= 0) {
89
- filteredCount += dValueHist[k];
94
+ while (kThresh >= 0) {
95
+ filteredCount += dValueHist[kThresh];
90
96
  if (filteredCount > maxPoints)
91
97
  break;
92
- k--;
93
- }
94
- //console.log("image size: ", width * height);
95
- //console.log("extracted featues: ", allCount);
96
- //console.log("filtered featues: ", filteredCount);
97
- for (let i = 0; i < isPixelSelected.length; i++) {
98
- if (isPixelSelected[i]) {
99
- if (dValue[i] * 1000 < k)
100
- isPixelSelected[i] = false;
101
- }
98
+ kThresh--;
102
99
  }
103
- //console.log("selected count: ", isPixelSelected.reduce((a, b) => {return a + (b?1:0);}, 0));
100
+ const minDValue = kThresh / 1000;
104
101
  // Step 2
105
- // prebuild cumulative sum matrix for fast computation
106
- const imageDataSqr = [];
102
+ const imageDataSqr = new Float32Array(imageData.length);
107
103
  for (let i = 0; i < imageData.length; i++) {
108
104
  imageDataSqr[i] = imageData[i] * imageData[i];
109
105
  }
110
106
  const imageDataCumsum = new Cumsum(imageData, width, height);
111
107
  const imageDataSqrCumsum = new Cumsum(imageDataSqr, width, height);
112
- // holds the max similariliy value computed within SEARCH area of each pixel
113
- // idea: if there is high simliarity with another pixel in nearby area, then it's not a good feature point
114
- // next step is to find pixel with low similarity
115
- const featureMap = new Float32Array(imageData.length);
116
- for (let i = 0; i < width; i++) {
117
- for (let j = 0; j < height; j++) {
118
- const pos = j * width + i;
119
- if (!isPixelSelected[pos]) {
120
- featureMap[pos] = 1.0;
121
- continue;
122
- }
123
- const vlen = _templateVar({
124
- image,
125
- cx: i,
126
- cy: j,
127
- sdThresh: TEMPLATE_SD_THRESH,
128
- imageDataCumsum,
129
- imageDataSqrCumsum,
108
+ // Collect candidates above threshold
109
+ const candidates = [];
110
+ for (let i = 0; i < imageData.length; i++) {
111
+ if (isCandidate[i] && dValue[i] >= minDValue) {
112
+ candidates.push({
113
+ pos: i,
114
+ dval: dValue[i],
115
+ x: i % width,
116
+ y: Math.floor(i / width)
130
117
  });
131
- if (vlen === null) {
132
- featureMap[pos] = 1.0;
133
- continue;
134
- }
135
- let max = -1.0;
136
- for (let jj = -SEARCH_SIZE1; jj <= SEARCH_SIZE1; jj++) {
137
- for (let ii = -SEARCH_SIZE1; ii <= SEARCH_SIZE1; ii++) {
138
- if (ii * ii + jj * jj <= SEARCH_SIZE2 * SEARCH_SIZE2)
139
- continue;
140
- const sim = _getSimilarity({
141
- image,
142
- cx: i + ii,
143
- cy: j + jj,
144
- vlen: vlen,
145
- tx: i,
146
- ty: j,
147
- imageDataCumsum,
148
- imageDataSqrCumsum,
149
- });
150
- if (sim === null)
151
- continue;
152
- if (sim > max) {
153
- max = sim;
154
- if (max > MAX_SIM_THRESH)
155
- break;
156
- }
157
- }
158
- if (max > MAX_SIM_THRESH)
159
- break;
160
- }
161
- featureMap[pos] = max;
162
118
  }
163
119
  }
164
- // Step 2.2 select feature
165
- const coords = _selectFeature({
166
- image,
167
- featureMap,
168
- templateSize: TEMPLATE_SIZE,
169
- searchSize: SEARCH_SIZE2,
170
- occSize: OCCUPANCY_SIZE,
171
- maxSimThresh: MAX_THRESH,
172
- minSimThresh: MIN_THRESH,
173
- sdThresh: SD_THRESH,
174
- imageDataCumsum,
175
- imageDataSqrCumsum,
176
- });
177
- return coords;
178
- };
179
- const _selectFeature = (options) => {
180
- let { image, featureMap, templateSize, searchSize, occSize, maxSimThresh, minSimThresh, sdThresh, imageDataCumsum, imageDataSqrCumsum, } = options;
181
- const { data: imageData, width, height, scale } = image;
182
- //console.log("params: ", templateSize, templateSize, occSize, maxSimThresh, minSimThresh, sdThresh);
183
- //occSize *= 2;
184
- occSize = Math.floor(Math.min(image.width, image.height) / 10);
185
- const divSize = (templateSize * 2 + 1) * 3;
186
- const xDiv = Math.floor(width / divSize);
187
- const yDiv = Math.floor(height / divSize);
188
- let maxFeatureNum = Math.floor(width / occSize) * Math.floor(height / occSize) + xDiv * yDiv;
189
- //console.log("max feature num: ", maxFeatureNum);
120
+ // Sort by dValue DESCENDING
121
+ candidates.sort((a, b) => b.dval - a.dval);
122
+ // Step 3 - On-Demand Feature Selection (The 10x Win)
123
+ const divSize = (TEMPLATE_SIZE * 2 + 1) * 3;
124
+ const maxFeatureNum = Math.floor(width / OCCUPANCY_SIZE) * Math.floor(height / OCCUPANCY_SIZE) +
125
+ Math.floor(width / divSize) * Math.floor(height / divSize);
190
126
  const coords = [];
191
- const image2 = new Float32Array(imageData.length);
192
- for (let i = 0; i < image2.length; i++) {
193
- image2[i] = featureMap[i];
194
- }
195
- let num = 0;
196
- while (num < maxFeatureNum) {
197
- let minSim = maxSimThresh;
198
- let cx = -1;
199
- let cy = -1;
200
- for (let j = 0; j < height; j++) {
201
- for (let i = 0; i < width; i++) {
202
- if (image2[j * width + i] < minSim) {
203
- minSim = image2[j * width + i];
204
- cx = i;
205
- cy = j;
206
- }
207
- }
127
+ const invalidated = new Uint8Array(width * height);
128
+ const templateWidth = 2 * TEMPLATE_SIZE + 1;
129
+ const nPixels = templateWidth * templateWidth;
130
+ const actualOccSize = Math.floor(Math.min(width, height) / 12); // Reducido de 10 para más densidad
131
+ for (let i = 0; i < candidates.length; i++) {
132
+ const { x, y, pos } = candidates[i];
133
+ if (invalidated[pos])
134
+ continue;
135
+ // Boundary safety for template
136
+ if (x < TEMPLATE_SIZE + SEARCH_SIZE1 || x >= width - TEMPLATE_SIZE - SEARCH_SIZE1 ||
137
+ y < TEMPLATE_SIZE + SEARCH_SIZE1 || y >= height - TEMPLATE_SIZE - SEARCH_SIZE1) {
138
+ continue;
208
139
  }
209
- if (cx === -1)
210
- break;
211
140
  const vlen = _templateVar({
212
141
  image,
213
- cx: cx,
214
- cy: cy,
215
- sdThresh: 0,
142
+ cx: x,
143
+ cy: y,
144
+ sdThresh: TEMPLATE_SD_THRESH,
216
145
  imageDataCumsum,
217
146
  imageDataSqrCumsum,
218
147
  });
219
- if (vlen === null) {
220
- image2[cy * width + cx] = 1.0;
221
- continue;
222
- }
223
- if (vlen / (templateSize * 2 + 1) < sdThresh) {
224
- image2[cy * width + cx] = 1.0;
148
+ if (vlen === null)
225
149
  continue;
150
+ const templateAvg = imageDataCumsum.query(x - TEMPLATE_SIZE, y - TEMPLATE_SIZE, x + TEMPLATE_SIZE, y + TEMPLATE_SIZE) / nPixels;
151
+ // Optimization: Cache template once per candidate
152
+ const templateData = new Uint8Array(templateWidth * templateWidth);
153
+ let tidx = 0;
154
+ const tStart = (y - TEMPLATE_SIZE) * width + (x - TEMPLATE_SIZE);
155
+ for (let tj = 0; tj < templateWidth; tj++) {
156
+ const rowOffset = tStart + tj * width;
157
+ for (let ti = 0; ti < templateWidth; ti++) {
158
+ templateData[tidx++] = imageData[rowOffset + ti];
159
+ }
226
160
  }
227
- let min = 1.0;
161
+ // Step 2.1: Find max similarity in search area (On demand!)
228
162
  let max = -1.0;
229
- for (let j = -searchSize; j <= searchSize; j++) {
230
- for (let i = -searchSize; i <= searchSize; i++) {
231
- if (i * i + j * j > searchSize * searchSize)
232
- continue;
233
- if (i === 0 && j === 0)
163
+ for (let jj = -SEARCH_SIZE1; jj <= SEARCH_SIZE1; jj++) {
164
+ for (let ii = -SEARCH_SIZE1; ii <= SEARCH_SIZE1; ii++) {
165
+ if (ii * ii + jj * jj <= SEARCH_SIZE2 * SEARCH_SIZE2)
234
166
  continue;
235
- const sim = _getSimilarity({
167
+ const sim = _getSimilarityOptimized({
236
168
  image,
237
- vlen,
238
- cx: cx + i,
239
- cy: cy + j,
240
- tx: cx,
241
- ty: cy,
169
+ cx: x + ii,
170
+ cy: y + jj,
171
+ vlen: vlen,
172
+ templateData,
173
+ templateAvg,
174
+ templateWidth,
242
175
  imageDataCumsum,
243
176
  imageDataSqrCumsum,
177
+ width,
178
+ height
244
179
  });
245
- if (sim === null)
246
- continue;
247
- if (sim < min) {
248
- min = sim;
249
- if (min < minSimThresh && min < minSim)
250
- break;
251
- }
252
- if (sim > max) {
180
+ if (sim !== null && sim > max) {
253
181
  max = sim;
254
- if (max > 0.99)
182
+ if (max > MAX_THRESH)
255
183
  break;
256
184
  }
257
185
  }
258
- if ((min < minSimThresh && min < minSim) || max > 0.99)
186
+ if (max > MAX_THRESH)
259
187
  break;
260
188
  }
261
- if ((min < minSimThresh && min < minSim) || max > 0.99) {
262
- image2[cy * width + cx] = 1.0;
263
- continue;
264
- }
265
- coords.push({ x: cx, y: cy });
266
- //coords.push({
267
- //mx: 1.0 * cx / scale,
268
- //my: 1.0 * (height - cy) / scale,
269
- //})
270
- num += 1;
271
- //console.log(num, '(', cx, ',', cy, ')', minSim, 'min = ', min, 'max = ', max, 'sd = ', vlen/(templateSize*2+1));
272
- // no other feature points within occSize square
273
- for (let j = -occSize; j <= occSize; j++) {
274
- for (let i = -occSize; i <= occSize; i++) {
275
- if (cy + j < 0 || cy + j >= height || cx + i < 0 || cx + i >= width)
276
- continue;
277
- image2[(cy + j) * width + (cx + i)] = 1.0;
189
+ // Now decide if we select it
190
+ if (max < MAX_THRESH) {
191
+ // Uniqueness check (Step 2.2 sub-loop)
192
+ let minUnique = 1.0;
193
+ let maxUnique = -1.0;
194
+ let failedUnique = false;
195
+ for (let jj = -SEARCH_SIZE2; jj <= SEARCH_SIZE2; jj++) {
196
+ for (let ii = -SEARCH_SIZE2; ii <= SEARCH_SIZE2; ii++) {
197
+ if (ii * ii + jj * jj > SEARCH_SIZE2 * SEARCH_SIZE2)
198
+ continue;
199
+ if (ii === 0 && jj === 0)
200
+ continue;
201
+ const sim = _getSimilarityOptimized({
202
+ image,
203
+ vlen,
204
+ cx: x + ii,
205
+ cy: y + jj,
206
+ templateData,
207
+ templateAvg,
208
+ templateWidth,
209
+ imageDataCumsum,
210
+ imageDataSqrCumsum,
211
+ width,
212
+ height
213
+ });
214
+ if (sim === null)
215
+ continue;
216
+ if (sim < minUnique)
217
+ minUnique = sim;
218
+ if (sim > maxUnique)
219
+ maxUnique = sim;
220
+ if (minUnique < MIN_THRESH || maxUnique > 0.99) {
221
+ failedUnique = true;
222
+ break;
223
+ }
224
+ }
225
+ if (failedUnique)
226
+ break;
227
+ }
228
+ if (!failedUnique) {
229
+ coords.push({ x, y });
230
+ // Invalidate neighbors
231
+ for (let jj = -actualOccSize; jj <= actualOccSize; jj++) {
232
+ const yy = y + jj;
233
+ if (yy < 0 || yy >= height)
234
+ continue;
235
+ const rowStart = yy * width;
236
+ for (let ii = -actualOccSize; ii <= actualOccSize; ii++) {
237
+ const xx = x + ii;
238
+ if (xx < 0 || xx >= width)
239
+ continue;
240
+ invalidated[rowStart + xx] = 1;
241
+ }
242
+ }
278
243
  }
279
244
  }
245
+ if (coords.length >= maxFeatureNum)
246
+ break;
280
247
  }
281
248
  return coords;
282
249
  };
@@ -303,47 +270,36 @@ const _templateVar = ({ image, cx, cy, sdThresh, imageDataCumsum, imageDataSqrCu
303
270
  vlen = Math.sqrt(vlen);
304
271
  return vlen;
305
272
  };
306
- const _getSimilarity = (options) => {
307
- const { image, cx, cy, vlen, tx, ty, imageDataCumsum, imageDataSqrCumsum } = options;
308
- const { data: imageData, width, height } = image;
309
- const templateSize = TEMPLATE_SIZE;
273
+ const _getSimilarityOptimized = (options) => {
274
+ const { cx, cy, vlen, templateData, templateAvg, templateWidth, imageDataCumsum, imageDataSqrCumsum, width, height } = options;
275
+ const imageData = options.image.data;
276
+ const templateSize = (templateWidth - 1) / 2;
310
277
  if (cx - templateSize < 0 || cx + templateSize >= width)
311
278
  return null;
312
279
  if (cy - templateSize < 0 || cy + templateSize >= height)
313
280
  return null;
314
- const templateWidth = 2 * templateSize + 1;
315
- let sx = imageDataCumsum.query(cx - templateSize, cy - templateSize, cx + templateSize, cy + templateSize);
316
- let sxx = imageDataSqrCumsum.query(cx - templateSize, cy - templateSize, cx + templateSize, cy + templateSize);
281
+ const nP = templateWidth * templateWidth;
282
+ const sx = imageDataCumsum.query(cx - templateSize, cy - templateSize, cx + templateSize, cy + templateSize);
283
+ const sxx = imageDataSqrCumsum.query(cx - templateSize, cy - templateSize, cx + templateSize, cy + templateSize);
284
+ // Full calculation
317
285
  let sxy = 0;
318
- // !! This loop is the performance bottleneck. Use moving pointers to optimize
319
- //
320
- // for (let i = cx - templateSize, i2 = tx - templateSize; i <= cx + templateSize; i++, i2++) {
321
- // for (let j = cy - templateSize, j2 = ty - templateSize; j <= cy + templateSize; j++, j2++) {
322
- // sxy += imageData[j*width + i] * imageData[j2*width + i2];
323
- // }
324
- // }
325
- //
326
286
  let p1 = (cy - templateSize) * width + (cx - templateSize);
327
- let p2 = (ty - templateSize) * width + (tx - templateSize);
328
- let nextRowOffset = width - templateWidth;
287
+ let p2 = 0;
288
+ const nextRowOffset = width - templateWidth;
329
289
  for (let j = 0; j < templateWidth; j++) {
330
290
  for (let i = 0; i < templateWidth; i++) {
331
- sxy += imageData[p1] * imageData[p2];
332
- p1 += 1;
333
- p2 += 1;
291
+ sxy += imageData[p1++] * templateData[p2++];
334
292
  }
335
293
  p1 += nextRowOffset;
336
- p2 += nextRowOffset;
337
294
  }
338
- let templateAverage = imageDataCumsum.query(tx - templateSize, ty - templateSize, tx + templateSize, ty + templateSize);
339
- templateAverage /= templateWidth * templateWidth;
340
- sxy -= templateAverage * sx;
341
- let vlen2 = sxx - (sx * sx) / (templateWidth * templateWidth);
342
- if (vlen2 == 0)
295
+ // Covariance check
296
+ // E[(X-EX)(Y-EY)] = E[XY] - EX*EY
297
+ // sum((Xi - avgX)(Yi - avgY)) = sum(XiYi) - avgY * sum(Xi)
298
+ const sxy_final = sxy - templateAvg * sx;
299
+ let vlen2 = sxx - (sx * sx) / (nP);
300
+ if (vlen2 <= 0)
343
301
  return null;
344
302
  vlen2 = Math.sqrt(vlen2);
345
- // covariance between template and current pixel
346
- const sim = (1.0 * sxy) / (vlen * vlen2);
347
- return sim;
303
+ return (1.0 * sxy_final) / (vlen * vlen2);
348
304
  };
349
305
  export { extract };
@@ -1,5 +1,5 @@
1
1
  export class Tracker {
2
- constructor(markerDimensions: any, trackingDataList: any, projectionTransform: any, inputWidth: any, inputHeight: any, debugMode?: boolean);
2
+ constructor(markerDimensions: any, trackingDataList: any, projectionTransform: any, debugMode?: boolean);
3
3
  markerDimensions: any;
4
4
  trackingDataList: any;
5
5
  projectionTransform: any;