@srsergio/taptapp-ar 1.0.101 → 1.1.2
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/dist/compiler/node-worker.js +1 -197
- package/dist/compiler/offline-compiler.js +1 -207
- package/dist/core/constants.js +1 -38
- package/dist/core/detector/crop-detector.js +1 -88
- package/dist/core/detector/detector-lite.js +1 -455
- package/dist/core/detector/freak.js +1 -89
- package/dist/core/estimation/estimate.js +1 -16
- package/dist/core/estimation/estimator.js +1 -30
- package/dist/core/estimation/morph-refinement.js +1 -116
- package/dist/core/estimation/non-rigid-refine.js +1 -70
- package/dist/core/estimation/pnp-solver.js +1 -109
- package/dist/core/estimation/refine-estimate.js +1 -311
- package/dist/core/estimation/utils.js +1 -67
- package/dist/core/features/auto-rotation-feature.js +1 -30
- package/dist/core/features/crop-detection-feature.js +1 -26
- package/dist/core/features/feature-base.js +1 -1
- package/dist/core/features/feature-manager.js +1 -55
- package/dist/core/features/one-euro-filter-feature.js +1 -44
- package/dist/core/features/temporal-filter-feature.js +1 -57
- package/dist/core/image-list.js +1 -54
- package/dist/core/input-loader.js +1 -87
- package/dist/core/matching/hamming-distance.js +1 -66
- package/dist/core/matching/hdc.js +1 -102
- package/dist/core/matching/hierarchical-clustering.js +1 -130
- package/dist/core/matching/hough.js +1 -170
- package/dist/core/matching/matcher.js +1 -66
- package/dist/core/matching/matching.js +1 -401
- package/dist/core/matching/ransacHomography.js +1 -132
- package/dist/core/perception/bio-inspired-engine.js +1 -232
- package/dist/core/perception/foveal-attention.js +1 -280
- package/dist/core/perception/index.js +1 -17
- package/dist/core/perception/predictive-coding.js +1 -278
- package/dist/core/perception/saccadic-controller.js +1 -269
- package/dist/core/perception/saliency-map.js +1 -254
- package/dist/core/perception/scale-orchestrator.js +1 -68
- package/dist/core/protocol.js +1 -254
- package/dist/core/tracker/extract-utils.js +1 -29
- package/dist/core/tracker/extract.js +1 -306
- package/dist/core/tracker/tracker.js +1 -352
- package/dist/core/utils/cumsum.js +1 -37
- package/dist/core/utils/delaunay.js +1 -125
- package/dist/core/utils/geometry.js +1 -101
- package/dist/core/utils/gpu-compute.js +1 -231
- package/dist/core/utils/homography.js +1 -138
- package/dist/core/utils/images.js +1 -108
- package/dist/core/utils/lsh-binarizer.js +1 -37
- package/dist/core/utils/lsh-direct.js +1 -76
- package/dist/core/utils/projection.js +1 -51
- package/dist/core/utils/randomizer.js +1 -25
- package/dist/core/utils/worker-pool.js +1 -89
- package/dist/index.js +1 -7
- package/dist/libs/one-euro-filter.js +1 -70
- package/dist/react/TaptappAR.js +1 -151
- package/dist/react/types.js +1 -16
- package/dist/react/use-ar.js +1 -118
- package/dist/runtime/aframe.js +1 -272
- package/dist/runtime/bio-inspired-controller.js +1 -358
- package/dist/runtime/controller.js +1 -592
- package/dist/runtime/controller.worker.js +1 -93
- package/dist/runtime/index.js +1 -5
- package/dist/runtime/three.js +1 -304
- package/dist/runtime/track.js +1 -381
- package/package.json +9 -3
|
@@ -1,306 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { gpuCompute } from "../utils/gpu-compute.js";
|
|
3
|
-
const SEARCH_SIZE1 = 10;
|
|
4
|
-
const SEARCH_SIZE2 = 2;
|
|
5
|
-
// Template parameters - ajustados para más puntos
|
|
6
|
-
const TEMPLATE_SIZE = 6;
|
|
7
|
-
const TEMPLATE_SD_THRESH = 4.0; // Reducido de 5.0 para aceptar más candidatos
|
|
8
|
-
const MAX_SIM_THRESH = 0.95;
|
|
9
|
-
const MAX_THRESH = 0.9;
|
|
10
|
-
const MIN_THRESH = 0.2;
|
|
11
|
-
const SD_THRESH = 8.0;
|
|
12
|
-
const OCCUPANCY_SIZE = 8; // Reduced from 10 to allow more density
|
|
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
|
-
};
|
|
22
|
-
/*
|
|
23
|
-
* Input image is in grey format. the imageData array size is width * height. value range from 0-255
|
|
24
|
-
* pixel value at row r and c = imageData[r * width + c]
|
|
25
|
-
*
|
|
26
|
-
* @param {Uint8Array} options.imageData
|
|
27
|
-
* @param {int} options.width image width
|
|
28
|
-
* @param {int} options.height image height
|
|
29
|
-
*/
|
|
30
|
-
const extract = (image) => {
|
|
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;
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
// Original JS implementation
|
|
41
|
-
dValue = new Float32Array(imageData.length);
|
|
42
|
-
isCandidate = new Uint8Array(imageData.length);
|
|
43
|
-
for (let j = 1; j < height - 1; j++) {
|
|
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);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
// Step 1.2 - Local Maxima (for JS path)
|
|
60
|
-
for (let j = 1; j < height - 1; j++) {
|
|
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
|
-
}
|
|
70
|
-
}
|
|
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);
|
|
83
|
-
if (k > 999)
|
|
84
|
-
k = 999;
|
|
85
|
-
dValueHist[k]++;
|
|
86
|
-
allCount++;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
// Determine dValue threshold for top 5% (aumentado de 2% para más candidatos)
|
|
91
|
-
const maxPoints = 0.10 * width * height; // Increased to 10% for more candidates
|
|
92
|
-
let kThresh = 999;
|
|
93
|
-
let filteredCount = 0;
|
|
94
|
-
while (kThresh >= 0) {
|
|
95
|
-
filteredCount += dValueHist[kThresh];
|
|
96
|
-
if (filteredCount > maxPoints)
|
|
97
|
-
break;
|
|
98
|
-
kThresh--;
|
|
99
|
-
}
|
|
100
|
-
const minDValue = kThresh / 1000;
|
|
101
|
-
// Step 2
|
|
102
|
-
const imageDataSqr = new Float32Array(imageData.length);
|
|
103
|
-
for (let i = 0; i < imageData.length; i++) {
|
|
104
|
-
imageDataSqr[i] = imageData[i] * imageData[i];
|
|
105
|
-
}
|
|
106
|
-
const imageDataCumsum = new Cumsum(imageData, width, height);
|
|
107
|
-
const imageDataSqrCumsum = new Cumsum(imageDataSqr, width, height);
|
|
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)
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
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);
|
|
126
|
-
const coords = [];
|
|
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;
|
|
139
|
-
}
|
|
140
|
-
const vlen = _templateVar({
|
|
141
|
-
image,
|
|
142
|
-
cx: x,
|
|
143
|
-
cy: y,
|
|
144
|
-
sdThresh: TEMPLATE_SD_THRESH,
|
|
145
|
-
imageDataCumsum,
|
|
146
|
-
imageDataSqrCumsum,
|
|
147
|
-
});
|
|
148
|
-
if (vlen === null)
|
|
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
|
-
}
|
|
160
|
-
}
|
|
161
|
-
// Step 2.1: Find max similarity in search area (On demand!)
|
|
162
|
-
let max = -1.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)
|
|
166
|
-
continue;
|
|
167
|
-
const sim = _getSimilarityOptimized({
|
|
168
|
-
image,
|
|
169
|
-
cx: x + ii,
|
|
170
|
-
cy: y + jj,
|
|
171
|
-
vlen: vlen,
|
|
172
|
-
templateData,
|
|
173
|
-
templateAvg,
|
|
174
|
-
templateWidth,
|
|
175
|
-
imageDataCumsum,
|
|
176
|
-
imageDataSqrCumsum,
|
|
177
|
-
width,
|
|
178
|
-
height
|
|
179
|
-
});
|
|
180
|
-
if (sim !== null && sim > max) {
|
|
181
|
-
max = sim;
|
|
182
|
-
if (max > MAX_THRESH)
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (max > MAX_THRESH)
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
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
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
if (coords.length >= maxFeatureNum)
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
return coords;
|
|
249
|
-
};
|
|
250
|
-
// compute variances of the pixels, centered at (cx, cy)
|
|
251
|
-
const _templateVar = ({ image, cx, cy, sdThresh, imageDataCumsum, imageDataSqrCumsum }) => {
|
|
252
|
-
if (cx - TEMPLATE_SIZE < 0 || cx + TEMPLATE_SIZE >= image.width)
|
|
253
|
-
return null;
|
|
254
|
-
if (cy - TEMPLATE_SIZE < 0 || cy + TEMPLATE_SIZE >= image.height)
|
|
255
|
-
return null;
|
|
256
|
-
const templateWidth = 2 * TEMPLATE_SIZE + 1;
|
|
257
|
-
const nPixels = templateWidth * templateWidth;
|
|
258
|
-
let average = imageDataCumsum.query(cx - TEMPLATE_SIZE, cy - TEMPLATE_SIZE, cx + TEMPLATE_SIZE, cy + TEMPLATE_SIZE);
|
|
259
|
-
average /= nPixels;
|
|
260
|
-
//v = sum((pixel_i - avg)^2) for all pixel i within the template
|
|
261
|
-
// = sum(pixel_i^2) - sum(2 * avg * pixel_i) + sum(avg^avg)
|
|
262
|
-
let vlen = imageDataSqrCumsum.query(cx - TEMPLATE_SIZE, cy - TEMPLATE_SIZE, cx + TEMPLATE_SIZE, cy + TEMPLATE_SIZE);
|
|
263
|
-
vlen -=
|
|
264
|
-
2 *
|
|
265
|
-
average *
|
|
266
|
-
imageDataCumsum.query(cx - TEMPLATE_SIZE, cy - TEMPLATE_SIZE, cx + TEMPLATE_SIZE, cy + TEMPLATE_SIZE);
|
|
267
|
-
vlen += nPixels * average * average;
|
|
268
|
-
if (vlen / nPixels < sdThresh * sdThresh)
|
|
269
|
-
return null;
|
|
270
|
-
vlen = Math.sqrt(vlen);
|
|
271
|
-
return vlen;
|
|
272
|
-
};
|
|
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;
|
|
277
|
-
if (cx - templateSize < 0 || cx + templateSize >= width)
|
|
278
|
-
return null;
|
|
279
|
-
if (cy - templateSize < 0 || cy + templateSize >= height)
|
|
280
|
-
return null;
|
|
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
|
-
// 🚀 MOONSHOT Early Exit: Check variance (vlen2) before expensive sxy loop
|
|
285
|
-
let vlen2 = sxx - (sx * sx) / nP;
|
|
286
|
-
if (vlen2 <= 0)
|
|
287
|
-
return null;
|
|
288
|
-
vlen2 = Math.sqrt(vlen2);
|
|
289
|
-
// Full calculation - Optimized with 2x2 sub-sampling for SPEED
|
|
290
|
-
let sxy = 0;
|
|
291
|
-
const p1_start = (cy - templateSize) * width + (cx - templateSize);
|
|
292
|
-
for (let j = 0; j < templateWidth; j++) {
|
|
293
|
-
const rowOffset1 = p1_start + j * width;
|
|
294
|
-
const rowOffset2 = j * templateWidth;
|
|
295
|
-
for (let i = 0; i < templateWidth; i++) {
|
|
296
|
-
sxy += imageData[rowOffset1 + i] * templateData[rowOffset2 + i];
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
const sampledCount = templateWidth * templateWidth;
|
|
300
|
-
const totalCount = templateWidth * templateWidth;
|
|
301
|
-
sxy *= (totalCount / sampledCount);
|
|
302
|
-
// Covariance check
|
|
303
|
-
const sxy_final = sxy - templateAvg * sx;
|
|
304
|
-
return (1.0 * sxy_final) / (vlen * vlen2);
|
|
305
|
-
};
|
|
306
|
-
export { extract };
|
|
1
|
+
import{Cumsum as t}from"../utils/cumsum.js";import{gpuCompute as e}from"../utils/gpu-compute.js";let n=!0;export const setGPUMode=t=>{n=t};const o=o=>{const{data:r,width:i,height:s}=o;let u,m;if(n){const t=e.edgeDetection(r,i,s);u=t.dValue,m=t.isCandidate}else{u=new Float32Array(r.length),m=new Uint8Array(r.length);for(let t=1;t<s-1;t++){const e=t*i,n=(t-1)*i,o=(t+1)*i;for(let t=1;t<i-1;t++){const a=e+t;let l=(r[n+t+1]-r[n+t-1]+r[e+t+1]-r[e+t-1]+r[o+t+1]-r[o+t-1])/768,i=(r[o+t-1]-r[n+t-1]+r[o+t]-r[n+t]+r[o+t+1]-r[n+t+1])/768;u[a]=Math.sqrt((l*l+i*i)/2)}}for(let t=1;t<s-1;t++){const e=t*i;for(let t=1;t<i-1;t++){const n=e+t,o=u[n];o>0&&o>=u[n-1]&&o>=u[n+1]&&o>=u[n-i]&&o>=u[n+i]&&(m[n]=1)}}}const c=new Uint32Array(1e3);for(let t=1;t<s-1;t++){const e=t*i;for(let t=1;t<i-1;t++){const n=e+t;if(m[n]){const t=u[n];let e=Math.floor(1e3*t);e>999&&(e=999),c[e]++}}}const f=.1*i*s;let h=999,g=0;for(;h>=0&&(g+=c[h],!(g>f));)h--;const d=h/1e3,y=new Float32Array(r.length);for(let t=0;t<r.length;t++)y[t]=r[t]*r[t];const p=new t(r,i,s),q=new t(y,i,s),D=[];for(let t=0;t<r.length;t++)m[t]&&u[t]>=d&&D.push({pos:t,dval:u[t],x:t%i,y:Math.floor(t/i)});D.sort((t,e)=>e.dval-t.dval);const w=Math.floor(i/8)*Math.floor(s/8)+Math.floor(i/39)*Math.floor(s/39),M=[],C=new Uint8Array(i*s),x=Math.floor(Math.min(i,s)/12);for(let t=0;t<D.length;t++){const{x:e,y:n,pos:u}=D[t];if(C[u])continue;if(e<16||e>=i-6-10||n<16||n>=s-6-10)continue;const m=a({image:o,cx:e,cy:n,sdThresh:4,imageDataCumsum:p,imageDataSqrCumsum:q});if(null===m)continue;const c=p.query(e-6,n-6,e+6,n+6)/169,f=new Uint8Array(169);let h=0;const g=(n-6)*i+(e-6);for(let t=0;t<13;t++){const e=g+t*i;for(let t=0;t<13;t++)f[h++]=r[e+t]}let d=-1;for(let t=-10;t<=10;t++){for(let a=-10;a<=10;a++){if(a*a+t*t<=4)continue;const r=l({image:o,cx:e+a,cy:n+t,vlen:m,templateData:f,templateAvg:c,templateWidth:13,imageDataCumsum:p,imageDataSqrCumsum:q,width:i,height:s});if(null!==r&&r>d&&(d=r,d>.9))break}if(d>.9)break}if(d<.9){let t=1,a=-1,r=!1;for(let u=-2;u<=2;u++){for(let h=-2;h<=2;h++){if(h*h+u*u>4)continue;if(0===h&&0===u)continue;const g=l({image:o,vlen:m,cx:e+h,cy:n+u,templateData:f,templateAvg:c,templateWidth:13,imageDataCumsum:p,imageDataSqrCumsum:q,width:i,height:s});if(null!==g&&(g<t&&(t=g),g>a&&(a=g),t<.2||a>.99)){r=!0;break}}if(r)break}if(!r){M.push({x:e,y:n});for(let t=-x;t<=x;t++){const o=n+t;if(o<0||o>=s)continue;const a=o*i;for(let t=-x;t<=x;t++){const n=e+t;n<0||n>=i||(C[a+n]=1)}}}}if(M.length>=w)break}return M},a=({image:t,cx:e,cy:n,sdThresh:o,imageDataCumsum:a,imageDataSqrCumsum:l})=>{if(e-6<0||e+6>=t.width)return null;if(n-6<0||n+6>=t.height)return null;let r=a.query(e-6,n-6,e+6,n+6);r/=169;let i=l.query(e-6,n-6,e+6,n+6);return i-=2*r*a.query(e-6,n-6,e+6,n+6),i+=169*r*r,i/169<o*o?null:(i=Math.sqrt(i),i)},l=t=>{const{cx:e,cy:n,vlen:o,templateData:a,templateAvg:l,templateWidth:r,imageDataCumsum:i,imageDataSqrCumsum:s,width:u,height:m}=t,c=t.image.data,f=(r-1)/2;if(e-f<0||e+f>=u)return null;if(n-f<0||n+f>=m)return null;const h=r*r,g=i.query(e-f,n-f,e+f,n+f);let d=s.query(e-f,n-f,e+f,n+f)-g*g/h;if(d<=0)return null;d=Math.sqrt(d);let y=0;const p=(n-f)*u+(e-f);for(let t=0;t<r;t++){const e=p+t*u,n=t*r;for(let t=0;t<r;t++)y+=c[e+t]*a[n+t]}return y*=r*r/(r*r),1*(y-l*g)/(o*d)};export{o as extract};
|
|
@@ -1,352 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { refineNonRigid, projectMesh } from "../estimation/non-rigid-refine.js";
|
|
3
|
-
import { AR_CONFIG } from "../constants.js";
|
|
4
|
-
const AR2_DEFAULT_TS = AR_CONFIG.TRACKER_TEMPLATE_SIZE;
|
|
5
|
-
const AR2_DEFAULT_TS_GAP = 1;
|
|
6
|
-
const AR2_SEARCH_SIZE = AR_CONFIG.TRACKER_SEARCH_SIZE;
|
|
7
|
-
const AR2_SEARCH_GAP = 1;
|
|
8
|
-
const AR2_SIM_THRESH = AR_CONFIG.TRACKER_SIMILARITY_THRESHOLD;
|
|
9
|
-
const TRACKING_KEYFRAME = 0; // 0: 128px (optimized)
|
|
10
|
-
class Tracker {
|
|
11
|
-
constructor(markerDimensions, trackingDataList, projectionTransform, inputWidth, inputHeight, debugMode = false) {
|
|
12
|
-
this.markerDimensions = markerDimensions;
|
|
13
|
-
this.trackingDataList = trackingDataList;
|
|
14
|
-
this.projectionTransform = projectionTransform;
|
|
15
|
-
this.inputWidth = inputWidth;
|
|
16
|
-
this.inputHeight = inputHeight;
|
|
17
|
-
this.debugMode = debugMode;
|
|
18
|
-
this.trackingKeyframeList = []; // All octaves for all targets: [targetIndex][octaveIndex]
|
|
19
|
-
this.prebuiltData = []; // [targetIndex][octaveIndex]
|
|
20
|
-
for (let i = 0; i < trackingDataList.length; i++) {
|
|
21
|
-
const targetOctaves = trackingDataList[i];
|
|
22
|
-
this.trackingKeyframeList[i] = targetOctaves;
|
|
23
|
-
this.prebuiltData[i] = targetOctaves.map(keyframe => ({
|
|
24
|
-
px: new Float32Array(keyframe.px),
|
|
25
|
-
py: new Float32Array(keyframe.py),
|
|
26
|
-
data: new Uint8Array(keyframe.d),
|
|
27
|
-
width: keyframe.w,
|
|
28
|
-
height: keyframe.h,
|
|
29
|
-
scale: keyframe.s,
|
|
30
|
-
mesh: keyframe.mesh,
|
|
31
|
-
// Recyclable projected image buffer
|
|
32
|
-
projectedImage: new Float32Array(keyframe.w * keyframe.h)
|
|
33
|
-
}));
|
|
34
|
-
}
|
|
35
|
-
// Maintain mesh vertices state for temporal continuity
|
|
36
|
-
this.meshVerticesState = []; // [targetIndex][octaveIndex]
|
|
37
|
-
// Pre-allocate template data buffer to avoid garbage collection
|
|
38
|
-
const templateOneSize = AR2_DEFAULT_TS;
|
|
39
|
-
const templateSize = templateOneSize * 2 + 1;
|
|
40
|
-
this.templateBuffer = new Float32Array(templateSize * templateSize);
|
|
41
|
-
}
|
|
42
|
-
dummyRun(inputData) {
|
|
43
|
-
let transform = [
|
|
44
|
-
[1, 0, 0, 0],
|
|
45
|
-
[0, 1, 0, 0],
|
|
46
|
-
[0, 0, 1, 0],
|
|
47
|
-
];
|
|
48
|
-
for (let targetIndex = 0; targetIndex < this.trackingKeyframeList.length; targetIndex++) {
|
|
49
|
-
this.track(inputData, transform, targetIndex);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
track(inputData, lastModelViewTransform, targetIndex) {
|
|
53
|
-
let debugExtra = {};
|
|
54
|
-
// Select the best octave based on current estimated distance/scale
|
|
55
|
-
// We want the octave where the marker size is closest to its projected size on screen
|
|
56
|
-
const modelViewProjectionTransform = buildModelViewProjectionTransform(this.projectionTransform, lastModelViewTransform);
|
|
57
|
-
// Estimate current marker width on screen
|
|
58
|
-
const [mW, mH] = this.markerDimensions[targetIndex];
|
|
59
|
-
const p0 = computeScreenCoordiate(modelViewProjectionTransform, 0, 0);
|
|
60
|
-
const p1 = computeScreenCoordiate(modelViewProjectionTransform, mW, 0);
|
|
61
|
-
const screenW = Math.sqrt((p1.x - p0.x) ** 2 + (p1.y - p0.y) ** 2);
|
|
62
|
-
// Select octave whose image width is closest to screenW
|
|
63
|
-
// Select the best octave based on current estimated distance/scale
|
|
64
|
-
// Hysteresis: prevent flip-flopping between octaves
|
|
65
|
-
if (!this.lastOctaveIndex)
|
|
66
|
-
this.lastOctaveIndex = [];
|
|
67
|
-
let octaveIndex = this.lastOctaveIndex[targetIndex] !== undefined ? this.lastOctaveIndex[targetIndex] : 0;
|
|
68
|
-
let minDiff = Math.abs(this.prebuiltData[targetIndex][octaveIndex].width - screenW);
|
|
69
|
-
// Threshold to switch: only switch if another octave is much better (20% improvement)
|
|
70
|
-
const switchThreshold = 0.8;
|
|
71
|
-
for (let i = 0; i < this.prebuiltData[targetIndex].length; i++) {
|
|
72
|
-
const diff = Math.abs(this.prebuiltData[targetIndex][i].width - screenW);
|
|
73
|
-
if (diff < minDiff * switchThreshold) {
|
|
74
|
-
minDiff = diff;
|
|
75
|
-
octaveIndex = i;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
this.lastOctaveIndex[targetIndex] = octaveIndex;
|
|
79
|
-
const prebuilt = this.prebuiltData[targetIndex][octaveIndex];
|
|
80
|
-
// 1. Compute Projection (Warping)
|
|
81
|
-
this._computeProjection(modelViewProjectionTransform, inputData, prebuilt);
|
|
82
|
-
const projectedImage = prebuilt.projectedImage;
|
|
83
|
-
// 2. Compute Matching (NCC)
|
|
84
|
-
const { matchingPoints, sim } = this._computeMatching(prebuilt, projectedImage);
|
|
85
|
-
const trackingFrame = this.trackingKeyframeList[targetIndex][octaveIndex];
|
|
86
|
-
const worldCoords = [];
|
|
87
|
-
const screenCoords = [];
|
|
88
|
-
const goodTrack = [];
|
|
89
|
-
const { px, py, s: scale } = trackingFrame;
|
|
90
|
-
const reliabilities = [];
|
|
91
|
-
for (let i = 0; i < matchingPoints.length; i++) {
|
|
92
|
-
const reliability = sim[i];
|
|
93
|
-
if (reliability > AR2_SIM_THRESH && i < px.length) {
|
|
94
|
-
goodTrack.push(i);
|
|
95
|
-
const point = computeScreenCoordiate(modelViewProjectionTransform, matchingPoints[i][0], matchingPoints[i][1]);
|
|
96
|
-
screenCoords.push(point);
|
|
97
|
-
worldCoords.push({
|
|
98
|
-
x: px[i] / scale,
|
|
99
|
-
y: py[i] / scale,
|
|
100
|
-
z: 0,
|
|
101
|
-
});
|
|
102
|
-
reliabilities.push(reliability);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
// --- 🚀 MOONSHOT: Non-Rigid Mesh Refinement ---
|
|
106
|
-
let deformedMesh = null;
|
|
107
|
-
if (prebuilt.mesh && goodTrack.length >= 4) {
|
|
108
|
-
if (!this.meshVerticesState[targetIndex])
|
|
109
|
-
this.meshVerticesState[targetIndex] = [];
|
|
110
|
-
let currentOctaveVertices = this.meshVerticesState[targetIndex][octaveIndex];
|
|
111
|
-
// Initial setup: If no state, use the reference points (normalized) as first guess
|
|
112
|
-
if (!currentOctaveVertices) {
|
|
113
|
-
currentOctaveVertices = new Float32Array(px.length * 2);
|
|
114
|
-
for (let i = 0; i < px.length; i++) {
|
|
115
|
-
currentOctaveVertices[i * 2] = px[i];
|
|
116
|
-
currentOctaveVertices[i * 2 + 1] = py[i];
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// Data fidelity: Prepare tracked targets for mass-spring
|
|
120
|
-
const trackedTargets = [];
|
|
121
|
-
for (let j = 0; j < goodTrack.length; j++) {
|
|
122
|
-
const idx = goodTrack[j];
|
|
123
|
-
trackedTargets.push({
|
|
124
|
-
meshIndex: idx,
|
|
125
|
-
x: matchingPoints[idx][0] * scale, // Convert back to octave space pixels
|
|
126
|
-
y: matchingPoints[idx][1] * scale
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
// Relax mesh in octave space
|
|
130
|
-
const refinedOctaveVertices = refineNonRigid({
|
|
131
|
-
mesh: prebuilt.mesh,
|
|
132
|
-
trackedPoints: trackedTargets,
|
|
133
|
-
currentVertices: currentOctaveVertices,
|
|
134
|
-
iterations: 5
|
|
135
|
-
});
|
|
136
|
-
this.meshVerticesState[targetIndex][octaveIndex] = refinedOctaveVertices;
|
|
137
|
-
// Project deformed mesh to screen space
|
|
138
|
-
const screenMeshVertices = new Float32Array(refinedOctaveVertices.length);
|
|
139
|
-
for (let i = 0; i < refinedOctaveVertices.length; i += 2) {
|
|
140
|
-
const p = computeScreenCoordiate(modelViewProjectionTransform, refinedOctaveVertices[i] / scale, refinedOctaveVertices[i + 1] / scale);
|
|
141
|
-
screenMeshVertices[i] = p.x;
|
|
142
|
-
screenMeshVertices[i + 1] = p.y;
|
|
143
|
-
}
|
|
144
|
-
deformedMesh = {
|
|
145
|
-
vertices: screenMeshVertices,
|
|
146
|
-
triangles: prebuilt.mesh.t
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
// 2.1 Spatial distribution check: Avoid getting stuck in corners/noise
|
|
150
|
-
if (screenCoords.length >= 8) {
|
|
151
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
152
|
-
for (const p of screenCoords) {
|
|
153
|
-
if (p.x < minX)
|
|
154
|
-
minX = p.x;
|
|
155
|
-
if (p.y < minY)
|
|
156
|
-
minY = p.y;
|
|
157
|
-
if (p.x > maxX)
|
|
158
|
-
maxX = p.x;
|
|
159
|
-
if (p.y > maxY)
|
|
160
|
-
maxY = p.y;
|
|
161
|
-
}
|
|
162
|
-
const detectedDiagonal = Math.sqrt((maxX - minX) ** 2 + (maxY - minY) ** 2);
|
|
163
|
-
// If the points cover too little space compared to the screen size of the marker, it's a glitch
|
|
164
|
-
if (detectedDiagonal < screenW * 0.15) {
|
|
165
|
-
return { worldCoords: [], screenCoords: [], reliabilities: [], debugExtra };
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (this.debugMode) {
|
|
169
|
-
debugExtra = {
|
|
170
|
-
octaveIndex,
|
|
171
|
-
// Remove Array.from to avoid massive GC pressure
|
|
172
|
-
projectedImage: projectedImage,
|
|
173
|
-
matchingPoints,
|
|
174
|
-
goodTrack,
|
|
175
|
-
trackedPoints: screenCoords,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
return { worldCoords, screenCoords, reliabilities, indices: goodTrack, octaveIndex, deformedMesh, debugExtra };
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Pure JS implementation of NCC matching
|
|
182
|
-
*/
|
|
183
|
-
_computeMatching(prebuilt, projectedImage) {
|
|
184
|
-
const { px, py, scale, data: markerPixels, width: markerWidth, height: markerHeight } = prebuilt;
|
|
185
|
-
const featureCount = px.length;
|
|
186
|
-
const templateOneSize = AR2_DEFAULT_TS;
|
|
187
|
-
const templateSize = templateOneSize * 2 + 1;
|
|
188
|
-
const nPixels = templateSize * templateSize;
|
|
189
|
-
const oneOverNPixels = 1.0 / nPixels;
|
|
190
|
-
const searchOneSize = AR2_SEARCH_SIZE;
|
|
191
|
-
const searchGap = AR2_SEARCH_GAP;
|
|
192
|
-
const matchingPoints = [];
|
|
193
|
-
const sims = new Float32Array(featureCount);
|
|
194
|
-
// Reuse shared template buffer
|
|
195
|
-
const templateData = this.templateBuffer;
|
|
196
|
-
for (let f = 0; f < featureCount; f++) {
|
|
197
|
-
const sCenterX = (px[f] + 0.5) | 0; // Faster Math.round
|
|
198
|
-
const sCenterY = (py[f] + 0.5) | 0;
|
|
199
|
-
let bestSim = -1.0;
|
|
200
|
-
let bestX = px[f] / scale;
|
|
201
|
-
let bestY = py[f] / scale;
|
|
202
|
-
// Pre-calculate template stats for this feature
|
|
203
|
-
let sumT = 0;
|
|
204
|
-
let sumT2 = 0;
|
|
205
|
-
let tidx = 0;
|
|
206
|
-
for (let ty = -templateOneSize; ty <= templateOneSize; ty++) {
|
|
207
|
-
const fyOffset = (sCenterY + ty) * markerWidth;
|
|
208
|
-
for (let tx = -templateOneSize; tx <= templateOneSize; tx++) {
|
|
209
|
-
const val = markerPixels[fyOffset + sCenterX + tx];
|
|
210
|
-
templateData[tidx++] = val;
|
|
211
|
-
sumT += val;
|
|
212
|
-
sumT2 += val * val;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
const varT = Math.sqrt(Math.max(0, sumT2 - (sumT * sumT) * oneOverNPixels));
|
|
216
|
-
if (varT < 0.0001) {
|
|
217
|
-
sims[f] = -1.0;
|
|
218
|
-
matchingPoints.push([bestX, bestY]);
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
// 🚀 MOONSHOT: Coarse-to-Fine Search for MAXIMUM FPS
|
|
222
|
-
// Step 1: Coarse search (Gap 4)
|
|
223
|
-
const coarseGap = 4;
|
|
224
|
-
for (let sy = -searchOneSize; sy <= searchOneSize; sy += coarseGap) {
|
|
225
|
-
const cy = sCenterY + sy;
|
|
226
|
-
if (cy < templateOneSize || cy >= markerHeight - templateOneSize)
|
|
227
|
-
continue;
|
|
228
|
-
for (let sx = -searchOneSize; sx <= searchOneSize; sx += coarseGap) {
|
|
229
|
-
const cx = sCenterX + sx;
|
|
230
|
-
if (cx < templateOneSize || cx >= markerWidth - templateOneSize)
|
|
231
|
-
continue;
|
|
232
|
-
let sumI = 0, sumI2 = 0, sumIT = 0;
|
|
233
|
-
for (let ty = -templateOneSize; ty <= templateOneSize; ty++) {
|
|
234
|
-
const rowOffset = (cy + ty) * markerWidth;
|
|
235
|
-
const tRowOffset = (ty + templateOneSize) * templateSize;
|
|
236
|
-
for (let tx = -templateOneSize; tx <= templateOneSize; tx++) {
|
|
237
|
-
const valI = projectedImage[rowOffset + (cx + tx)];
|
|
238
|
-
const valT = templateData[tRowOffset + (tx + templateOneSize)];
|
|
239
|
-
sumI += valI;
|
|
240
|
-
sumI2 += valI * valI;
|
|
241
|
-
sumIT += valI * valT;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
const varI = Math.sqrt(Math.max(0, sumI2 - (sumI * sumI) * oneOverNPixels));
|
|
245
|
-
if (varI < 0.0001)
|
|
246
|
-
continue;
|
|
247
|
-
const sim = (sumIT - (sumI * sumT) * oneOverNPixels) / (varI * varT);
|
|
248
|
-
if (sim > bestSim) {
|
|
249
|
-
bestSim = sim;
|
|
250
|
-
bestX = cx / scale;
|
|
251
|
-
bestY = cy / scale;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
// Step 2: Fine refinement (Gap 1) only around the best coarse match
|
|
256
|
-
if (bestSim > AR2_SIM_THRESH) {
|
|
257
|
-
const fineCenterX = (bestX * scale) | 0;
|
|
258
|
-
const fineCenterY = (bestY * scale) | 0;
|
|
259
|
-
const fineSearch = coarseGap;
|
|
260
|
-
for (let sy = -fineSearch; sy <= fineSearch; sy++) {
|
|
261
|
-
const cy = fineCenterY + sy;
|
|
262
|
-
if (cy < templateOneSize || cy >= markerHeight - templateOneSize)
|
|
263
|
-
continue;
|
|
264
|
-
for (let sx = -fineSearch; sx <= fineSearch; sx++) {
|
|
265
|
-
const cx = fineCenterX + sx;
|
|
266
|
-
if (cx < templateOneSize || cx >= markerWidth - templateOneSize)
|
|
267
|
-
continue;
|
|
268
|
-
let sumI = 0, sumI2 = 0, sumIT = 0;
|
|
269
|
-
for (let ty = -templateOneSize; ty <= templateOneSize; ty++) {
|
|
270
|
-
const rowOffset = (cy + ty) * markerWidth;
|
|
271
|
-
const tRowOffset = (ty + templateOneSize) * templateSize;
|
|
272
|
-
for (let tx = -templateOneSize; tx <= templateOneSize; tx++) {
|
|
273
|
-
const valI = projectedImage[rowOffset + (cx + tx)];
|
|
274
|
-
const valT = templateData[tRowOffset + (tx + templateOneSize)];
|
|
275
|
-
sumI += valI;
|
|
276
|
-
sumI2 += valI * valI;
|
|
277
|
-
sumIT += valI * valT;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const varI = Math.sqrt(Math.max(0, sumI2 - (sumI * sumI) * oneOverNPixels));
|
|
281
|
-
if (varI < 0.0001)
|
|
282
|
-
continue;
|
|
283
|
-
const sim = (sumIT - (sumI * sumT) * oneOverNPixels) / (varI * varT);
|
|
284
|
-
if (sim > bestSim) {
|
|
285
|
-
bestSim = sim;
|
|
286
|
-
bestX = cx / scale;
|
|
287
|
-
bestY = cy / scale;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
sims[f] = bestSim;
|
|
293
|
-
matchingPoints.push([bestX, bestY]);
|
|
294
|
-
}
|
|
295
|
-
return { matchingPoints, sim: sims };
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Pure JS implementation of Bilinear Warping
|
|
299
|
-
*/
|
|
300
|
-
_computeProjection(M, inputData, prebuilt) {
|
|
301
|
-
const { width: markerWidth, height: markerHeight, scale: markerScale, projectedImage } = prebuilt;
|
|
302
|
-
const invScale = 1.0 / markerScale;
|
|
303
|
-
const inputW = this.inputWidth;
|
|
304
|
-
const inputH = this.inputHeight;
|
|
305
|
-
const m00 = M[0][0];
|
|
306
|
-
const m01 = M[0][1];
|
|
307
|
-
const m03 = M[0][3];
|
|
308
|
-
const m10 = M[1][0];
|
|
309
|
-
const m11 = M[1][1];
|
|
310
|
-
const m13 = M[1][3];
|
|
311
|
-
const m20 = M[2][0];
|
|
312
|
-
const m21 = M[2][1];
|
|
313
|
-
const m23 = M[2][3];
|
|
314
|
-
for (let j = 0; j < markerHeight; j++) {
|
|
315
|
-
const y = j * invScale;
|
|
316
|
-
const jOffset = j * markerWidth;
|
|
317
|
-
for (let i = 0; i < markerWidth; i++) {
|
|
318
|
-
const x = i * invScale;
|
|
319
|
-
const uz = (x * m20) + (y * m21) + m23;
|
|
320
|
-
const invZ = 1.0 / uz;
|
|
321
|
-
const ux = ((x * m00) + (y * m01) + m03) * invZ;
|
|
322
|
-
const uy = ((x * m10) + (y * m11) + m13) * invZ;
|
|
323
|
-
// Bilinear interpolation
|
|
324
|
-
const x0 = ux | 0; // Faster Math.floor
|
|
325
|
-
const y0 = uy | 0;
|
|
326
|
-
const x1 = x0 + 1;
|
|
327
|
-
const y1 = y0 + 1;
|
|
328
|
-
if (x0 >= 0 && x1 < inputW && y0 >= 0 && y1 < inputH) {
|
|
329
|
-
const dx = ux - x0;
|
|
330
|
-
const dy = uy - y0;
|
|
331
|
-
const omDx = 1.0 - dx;
|
|
332
|
-
const omDy = 1.0 - dy;
|
|
333
|
-
const y0Offset = y0 * inputW;
|
|
334
|
-
const y1Offset = y1 * inputW;
|
|
335
|
-
const v00 = inputData[y0Offset + x0];
|
|
336
|
-
const v10 = inputData[y0Offset + x1];
|
|
337
|
-
const v01 = inputData[y1Offset + x0];
|
|
338
|
-
const v11 = inputData[y1Offset + x1];
|
|
339
|
-
projectedImage[jOffset + i] =
|
|
340
|
-
v00 * omDx * omDy +
|
|
341
|
-
v10 * dx * omDy +
|
|
342
|
-
v01 * omDx * dy +
|
|
343
|
-
v11 * dx * dy;
|
|
344
|
-
}
|
|
345
|
-
else {
|
|
346
|
-
projectedImage[jOffset + i] = 0;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
export { Tracker };
|
|
1
|
+
import{buildModelViewProjectionTransform as t,computeScreenCoordiate as e}from"../estimation/utils.js";import{refineNonRigid as s,projectMesh as i}from"../estimation/non-rigid-refine.js";import{AR_CONFIG as o}from"../constants.js";const n=o.TRACKER_TEMPLATE_SIZE,r=o.TRACKER_SEARCH_SIZE,a=o.TRACKER_SIMILARITY_THRESHOLD;class h{constructor(t,e,s,i,o,r=!1){this.markerDimensions=t,this.trackingDataList=e,this.projectionTransform=s,this.inputWidth=i,this.inputHeight=o,this.debugMode=r,this.trackingKeyframeList=[],this.prebuiltData=[];for(let t=0;t<e.length;t++){const s=e[t];this.trackingKeyframeList[t]=s,this.prebuiltData[t]=s.map(t=>({px:new Float32Array(t.px),py:new Float32Array(t.py),data:new Uint8Array(t.d),width:t.w,height:t.h,scale:t.s,mesh:t.mesh,projectedImage:new Float32Array(t.w*t.h)}))}this.meshVerticesState=[];const a=2*n+1;this.templateBuffer=new Float32Array(a*a)}dummyRun(t){let e=[[1,0,0,0],[0,1,0,0],[0,0,1,0]];for(let s=0;s<this.trackingKeyframeList.length;s++)this.track(t,e,s)}track(i,o,n){let r={};const h=t(this.projectionTransform,o),[c,l]=this.markerDimensions[n],m=e(h,0,0),f=e(h,c,0),d=Math.sqrt((f.x-m.x)**2+(f.y-m.y)**2);this.lastOctaveIndex||(this.lastOctaveIndex=[]);let p=void 0!==this.lastOctaveIndex[n]?this.lastOctaveIndex[n]:0,u=Math.abs(this.prebuiltData[n][p].width-d);for(let t=0;t<this.prebuiltData[n].length;t++){const e=Math.abs(this.prebuiltData[n][t].width-d);e<.8*u&&(u=e,p=t)}this.lastOctaveIndex[n]=p;const g=this.prebuiltData[n][p];this._computeProjection(h,i,g);const x=g.projectedImage,{matchingPoints:y,sim:w}=this._computeMatching(g,x),I=this.trackingKeyframeList[n][p],M=[],b=[],A=[],{px:k,py:j,s:E}=I,D=[];for(let t=0;t<y.length;t++){const s=w[t];if(s>a&&t<k.length){A.push(t);const i=e(h,y[t][0],y[t][1]);b.push(i),M.push({x:k[t]/E,y:j[t]/E,z:0}),D.push(s)}}let R=null;if(g.mesh&&A.length>=4){this.meshVerticesState[n]||(this.meshVerticesState[n]=[]);let t=this.meshVerticesState[n][p];if(!t){t=new Float32Array(2*k.length);for(let e=0;e<k.length;e++)t[2*e]=k[e],t[2*e+1]=j[e]}const i=[];for(let t=0;t<A.length;t++){const e=A[t];i.push({meshIndex:e,x:y[e][0]*E,y:y[e][1]*E})}const o=s({mesh:g.mesh,trackedPoints:i,currentVertices:t,iterations:5});this.meshVerticesState[n][p]=o;const r=new Float32Array(o.length);for(let t=0;t<o.length;t+=2){const s=e(h,o[t]/E,o[t+1]/E);r[t]=s.x,r[t+1]=s.y}R={vertices:r,triangles:g.mesh.t}}if(b.length>=8){let t=1/0,e=1/0,s=-1/0,i=-1/0;for(const o of b)o.x<t&&(t=o.x),o.y<e&&(e=o.y),o.x>s&&(s=o.x),o.y>i&&(i=o.y);if(Math.sqrt((s-t)**2+(i-e)**2)<.15*d)return{worldCoords:[],screenCoords:[],reliabilities:[],debugExtra:r}}return this.debugMode&&(r={octaveIndex:p,projectedImage:x,matchingPoints:y,goodTrack:A,trackedPoints:b}),{worldCoords:M,screenCoords:b,reliabilities:D,indices:A,octaveIndex:p,deformedMesh:R,debugExtra:r}}_computeMatching(t,e){const{px:s,py:i,scale:o,data:h,width:c,height:l}=t,m=s.length,f=n,d=2*f+1,p=1/(d*d),u=r,g=[],x=new Float32Array(m),y=this.templateBuffer;for(let t=0;t<m;t++){const n=s[t]+.5|0,r=i[t]+.5|0;let m=-1,w=s[t]/o,I=i[t]/o,M=0,b=0,A=0;for(let t=-f;t<=f;t++){const e=(r+t)*c;for(let t=-f;t<=f;t++){const s=h[e+n+t];y[A++]=s,M+=s,b+=s*s}}const k=Math.sqrt(Math.max(0,b-M*M*p));if(k<1e-4){x[t]=-1,g.push([w,I]);continue}const j=4;for(let t=-u;t<=u;t+=j){const s=r+t;if(!(s<f||s>=l-f))for(let t=-u;t<=u;t+=j){const i=n+t;if(i<f||i>=c-f)continue;let r=0,a=0,h=0;for(let t=-f;t<=f;t++){const o=(s+t)*c,n=(t+f)*d;for(let t=-f;t<=f;t++){const s=e[o+(i+t)];r+=s,a+=s*s,h+=s*y[n+(t+f)]}}const l=Math.sqrt(Math.max(0,a-r*r*p));if(l<1e-4)continue;const u=(h-r*M*p)/(l*k);u>m&&(m=u,w=i/o,I=s/o)}}if(m>a){const t=w*o|0,s=I*o|0,i=j;for(let n=-i;n<=i;n++){const r=s+n;if(!(r<f||r>=l-f))for(let s=-i;s<=i;s++){const i=t+s;if(i<f||i>=c-f)continue;let n=0,a=0,h=0;for(let t=-f;t<=f;t++){const s=(r+t)*c,o=(t+f)*d;for(let t=-f;t<=f;t++){const r=e[s+(i+t)];n+=r,a+=r*r,h+=r*y[o+(t+f)]}}const l=Math.sqrt(Math.max(0,a-n*n*p));if(l<1e-4)continue;const u=(h-n*M*p)/(l*k);u>m&&(m=u,w=i/o,I=r/o)}}}x[t]=m,g.push([w,I])}return{matchingPoints:g,sim:x}}_computeProjection(t,e,s){const{width:i,height:o,scale:n,projectedImage:r}=s,a=1/n,h=this.inputWidth,c=this.inputHeight,l=t[0][0],m=t[0][1],f=t[0][3],d=t[1][0],p=t[1][1],u=t[1][3],g=t[2][0],x=t[2][1],y=t[2][3];for(let t=0;t<o;t++){const s=t*a,o=t*i;for(let t=0;t<i;t++){const i=t*a,n=1/(i*g+s*x+y),w=(i*l+s*m+f)*n,I=(i*d+s*p+u)*n,M=0|w,b=0|I,A=M+1,k=b+1;if(M>=0&&A<h&&b>=0&&k<c){const s=w-M,i=I-b,n=1-s,a=1-i,c=b*h,l=k*h,m=e[c+M],f=e[c+A],d=e[l+M],p=e[l+A];r[o+t]=m*n*a+f*s*a+d*n*i+p*s*i}else r[o+t]=0}}}}export{h as Tracker};
|