@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.
- package/README.md +102 -26
- package/dist/compiler/aframe.js +0 -3
- package/dist/compiler/compiler-base.d.ts +3 -7
- package/dist/compiler/compiler-base.js +28 -14
- package/dist/compiler/compiler.js +1 -1
- package/dist/compiler/compiler.worker.js +1 -1
- package/dist/compiler/controller.js +4 -5
- package/dist/compiler/controller.worker.js +0 -2
- package/dist/compiler/detector/crop-detector.js +0 -2
- package/dist/compiler/detector/detector-lite.d.ts +73 -0
- package/dist/compiler/detector/detector-lite.js +430 -0
- package/dist/compiler/detector/detector.js +236 -243
- package/dist/compiler/detector/kernels/cpu/binomialFilter.js +0 -1
- package/dist/compiler/detector/kernels/cpu/computeLocalization.js +0 -4
- package/dist/compiler/detector/kernels/cpu/computeOrientationHistograms.js +0 -18
- package/dist/compiler/detector/kernels/cpu/fakeShader.js +1 -1
- package/dist/compiler/detector/kernels/cpu/prune.d.ts +7 -1
- package/dist/compiler/detector/kernels/cpu/prune.js +1 -42
- package/dist/compiler/detector/kernels/webgl/upsampleBilinear.js +2 -2
- package/dist/compiler/estimation/refine-estimate.js +0 -1
- package/dist/compiler/estimation/utils.d.ts +1 -1
- package/dist/compiler/estimation/utils.js +1 -14
- package/dist/compiler/image-list.js +4 -4
- package/dist/compiler/input-loader.js +2 -2
- package/dist/compiler/matching/hamming-distance.js +13 -13
- package/dist/compiler/matching/hierarchical-clustering.js +1 -1
- package/dist/compiler/matching/matching.d.ts +20 -4
- package/dist/compiler/matching/matching.js +67 -41
- package/dist/compiler/matching/ransacHomography.js +1 -2
- package/dist/compiler/node-worker.d.ts +1 -0
- package/dist/compiler/node-worker.js +84 -0
- package/dist/compiler/offline-compiler.d.ts +171 -6
- package/dist/compiler/offline-compiler.js +303 -421
- package/dist/compiler/tensorflow-setup.js +27 -1
- package/dist/compiler/three.js +3 -5
- package/dist/compiler/tracker/extract.d.ts +1 -0
- package/dist/compiler/tracker/extract.js +200 -244
- package/dist/compiler/tracker/tracker.d.ts +1 -1
- package/dist/compiler/tracker/tracker.js +13 -18
- package/dist/compiler/utils/cumsum.d.ts +4 -2
- package/dist/compiler/utils/cumsum.js +17 -19
- package/dist/compiler/utils/gpu-compute.d.ts +57 -0
- package/dist/compiler/utils/gpu-compute.js +262 -0
- package/dist/compiler/utils/images.d.ts +4 -4
- package/dist/compiler/utils/images.js +67 -53
- package/dist/compiler/utils/worker-pool.d.ts +14 -0
- package/dist/compiler/utils/worker-pool.js +84 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/package.json +19 -13
- package/src/compiler/aframe.js +2 -4
- package/src/compiler/compiler-base.js +29 -14
- package/src/compiler/compiler.js +1 -1
- package/src/compiler/compiler.worker.js +1 -1
- package/src/compiler/controller.js +4 -5
- package/src/compiler/controller.worker.js +0 -2
- package/src/compiler/detector/crop-detector.js +0 -2
- package/src/compiler/detector/detector-lite.js +494 -0
- package/src/compiler/detector/detector.js +1052 -1063
- package/src/compiler/detector/kernels/cpu/binomialFilter.js +0 -1
- package/src/compiler/detector/kernels/cpu/computeLocalization.js +0 -4
- package/src/compiler/detector/kernels/cpu/computeOrientationHistograms.js +0 -17
- package/src/compiler/detector/kernels/cpu/fakeShader.js +1 -1
- package/src/compiler/detector/kernels/cpu/prune.js +1 -37
- package/src/compiler/detector/kernels/webgl/upsampleBilinear.js +2 -2
- package/src/compiler/estimation/refine-estimate.js +0 -1
- package/src/compiler/estimation/utils.js +9 -24
- package/src/compiler/image-list.js +4 -4
- package/src/compiler/input-loader.js +2 -2
- package/src/compiler/matching/hamming-distance.js +11 -15
- package/src/compiler/matching/hierarchical-clustering.js +1 -1
- package/src/compiler/matching/matching.js +72 -42
- package/src/compiler/matching/ransacHomography.js +0 -2
- package/src/compiler/node-worker.js +93 -0
- package/src/compiler/offline-compiler.js +339 -504
- package/src/compiler/tensorflow-setup.js +29 -1
- package/src/compiler/three.js +3 -5
- package/src/compiler/tracker/extract.js +211 -267
- package/src/compiler/tracker/tracker.js +13 -22
- package/src/compiler/utils/cumsum.js +17 -19
- package/src/compiler/utils/gpu-compute.js +303 -0
- package/src/compiler/utils/images.js +84 -53
- package/src/compiler/utils/worker-pool.js +89 -0
- package/src/index.ts +0 -2
- package/src/compiler/estimation/esimate-experiment.js +0 -316
- package/src/compiler/estimation/refine-estimate-experiment.js +0 -512
- package/src/react/AREditor.tsx +0 -394
- 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;
|
|
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 {
|
package/dist/compiler/three.js
CHANGED
|
@@ -87,7 +87,7 @@ export class MindARThree {
|
|
|
87
87
|
return anchor;
|
|
88
88
|
}
|
|
89
89
|
_startVideo() {
|
|
90
|
-
return new Promise((resolve
|
|
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
|
|
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
|
-
|
|
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,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
|
-
//
|
|
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 =
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
for (let
|
|
46
|
-
|
|
47
|
-
dy
|
|
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
|
-
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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;
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
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 (
|
|
89
|
-
filteredCount += dValueHist[
|
|
94
|
+
while (kThresh >= 0) {
|
|
95
|
+
filteredCount += dValueHist[kThresh];
|
|
90
96
|
if (filteredCount > maxPoints)
|
|
91
97
|
break;
|
|
92
|
-
|
|
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
|
-
|
|
100
|
+
const minDValue = kThresh / 1000;
|
|
104
101
|
// Step 2
|
|
105
|
-
|
|
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
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
let
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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:
|
|
214
|
-
cy:
|
|
215
|
-
sdThresh:
|
|
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
|
-
|
|
161
|
+
// Step 2.1: Find max similarity in search area (On demand!)
|
|
228
162
|
let max = -1.0;
|
|
229
|
-
for (let
|
|
230
|
-
for (let
|
|
231
|
-
if (
|
|
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 =
|
|
167
|
+
const sim = _getSimilarityOptimized({
|
|
236
168
|
image,
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
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 >
|
|
182
|
+
if (max > MAX_THRESH)
|
|
255
183
|
break;
|
|
256
184
|
}
|
|
257
185
|
}
|
|
258
|
-
if (
|
|
186
|
+
if (max > MAX_THRESH)
|
|
259
187
|
break;
|
|
260
188
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
307
|
-
const {
|
|
308
|
-
const
|
|
309
|
-
const templateSize =
|
|
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
|
|
315
|
-
|
|
316
|
-
|
|
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 =
|
|
328
|
-
|
|
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] *
|
|
332
|
-
p1 += 1;
|
|
333
|
-
p2 += 1;
|
|
291
|
+
sxy += imageData[p1++] * templateData[p2++];
|
|
334
292
|
}
|
|
335
293
|
p1 += nextRowOffset;
|
|
336
|
-
p2 += nextRowOffset;
|
|
337
294
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
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,
|
|
2
|
+
constructor(markerDimensions: any, trackingDataList: any, projectionTransform: any, debugMode?: boolean);
|
|
3
3
|
markerDimensions: any;
|
|
4
4
|
trackingDataList: any;
|
|
5
5
|
projectionTransform: any;
|