@camstack/addon-vision 0.1.6 → 0.1.9
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/addons/animal-classifier/index.d.mts +30 -0
- package/dist/addons/animal-classifier/index.d.ts +30 -0
- package/dist/addons/animal-classifier/index.js +822 -999
- package/dist/addons/animal-classifier/index.js.map +1 -1
- package/dist/addons/animal-classifier/index.mjs +7 -242
- package/dist/addons/animal-classifier/index.mjs.map +1 -1
- package/dist/addons/audio-classification/index.d.mts +36 -0
- package/dist/addons/audio-classification/index.d.ts +36 -0
- package/dist/addons/audio-classification/index.js +378 -501
- package/dist/addons/audio-classification/index.js.map +1 -1
- package/dist/addons/audio-classification/index.mjs +4 -224
- package/dist/addons/audio-classification/index.mjs.map +1 -1
- package/dist/addons/bird-global-classifier/index.d.mts +31 -0
- package/dist/addons/bird-global-classifier/index.d.ts +31 -0
- package/dist/addons/bird-global-classifier/index.js +825 -1002
- package/dist/addons/bird-global-classifier/index.js.map +1 -1
- package/dist/addons/bird-global-classifier/index.mjs +7 -248
- package/dist/addons/bird-global-classifier/index.mjs.map +1 -1
- package/dist/addons/bird-nabirds-classifier/index.d.mts +33 -0
- package/dist/addons/bird-nabirds-classifier/index.d.ts +33 -0
- package/dist/addons/bird-nabirds-classifier/index.js +825 -1002
- package/dist/addons/bird-nabirds-classifier/index.js.map +1 -1
- package/dist/addons/bird-nabirds-classifier/index.mjs +7 -289
- package/dist/addons/bird-nabirds-classifier/index.mjs.map +1 -1
- package/dist/addons/face-detection/index.d.mts +29 -0
- package/dist/addons/face-detection/index.d.ts +29 -0
- package/dist/addons/face-detection/index.js +934 -1196
- package/dist/addons/face-detection/index.js.map +1 -1
- package/dist/addons/face-detection/index.mjs +7 -227
- package/dist/addons/face-detection/index.mjs.map +1 -1
- package/dist/addons/face-recognition/index.d.mts +29 -0
- package/dist/addons/face-recognition/index.d.ts +29 -0
- package/dist/addons/face-recognition/index.js +807 -1003
- package/dist/addons/face-recognition/index.js.map +1 -1
- package/dist/addons/face-recognition/index.mjs +6 -197
- package/dist/addons/face-recognition/index.mjs.map +1 -1
- package/dist/addons/motion-detection/index.d.mts +28 -0
- package/dist/addons/motion-detection/index.d.ts +28 -0
- package/dist/addons/motion-detection/index.js +111 -214
- package/dist/addons/motion-detection/index.js.map +1 -1
- package/dist/addons/motion-detection/index.mjs +9 -12
- package/dist/addons/motion-detection/index.mjs.map +1 -1
- package/dist/addons/object-detection/index.d.mts +31 -0
- package/dist/addons/object-detection/index.d.ts +31 -0
- package/dist/addons/object-detection/index.js +1082 -1287
- package/dist/addons/object-detection/index.js.map +1 -1
- package/dist/addons/object-detection/index.mjs +7 -373
- package/dist/addons/object-detection/index.mjs.map +1 -1
- package/dist/addons/plate-detection/index.d.mts +30 -0
- package/dist/addons/plate-detection/index.d.ts +30 -0
- package/dist/addons/plate-detection/index.js +868 -1075
- package/dist/addons/plate-detection/index.js.map +1 -1
- package/dist/addons/plate-detection/index.mjs +7 -230
- package/dist/addons/plate-detection/index.mjs.map +1 -1
- package/dist/addons/plate-recognition/index.d.mts +31 -0
- package/dist/addons/plate-recognition/index.d.ts +31 -0
- package/dist/addons/plate-recognition/index.js +505 -684
- package/dist/addons/plate-recognition/index.js.map +1 -1
- package/dist/addons/plate-recognition/index.mjs +5 -244
- package/dist/addons/plate-recognition/index.mjs.map +1 -1
- package/dist/addons/segmentation-refiner/index.d.mts +30 -0
- package/dist/addons/segmentation-refiner/index.d.ts +30 -0
- package/dist/addons/segmentation-refiner/index.js +790 -967
- package/dist/addons/segmentation-refiner/index.js.map +1 -1
- package/dist/addons/segmentation-refiner/index.mjs +17 -21
- package/dist/addons/segmentation-refiner/index.mjs.map +1 -1
- package/dist/addons/vehicle-classifier/index.d.mts +31 -0
- package/dist/addons/vehicle-classifier/index.d.ts +31 -0
- package/dist/addons/vehicle-classifier/index.js +410 -581
- package/dist/addons/vehicle-classifier/index.js.map +1 -1
- package/dist/addons/vehicle-classifier/index.mjs +16 -20
- package/dist/addons/vehicle-classifier/index.mjs.map +1 -1
- package/dist/chunk-22BHCDT5.mjs +101 -0
- package/dist/{chunk-WG66JYYW.mjs.map → chunk-22BHCDT5.mjs.map} +1 -1
- package/dist/chunk-2IOKI4ES.mjs +335 -0
- package/dist/{chunk-PIFS7AIT.mjs.map → chunk-2IOKI4ES.mjs.map} +1 -1
- package/dist/chunk-7DYHXUPZ.mjs +36 -0
- package/dist/{chunk-BS4DKYGN.mjs.map → chunk-7DYHXUPZ.mjs.map} +1 -1
- package/dist/chunk-BJTO5JO5.mjs +11 -0
- package/dist/chunk-BP7H4NFS.mjs +412 -0
- package/dist/{chunk-MGT6RUVX.mjs.map → chunk-BP7H4NFS.mjs.map} +1 -1
- package/dist/chunk-BR2FPGOX.mjs +98 -0
- package/dist/{chunk-YYDM6V2F.mjs.map → chunk-BR2FPGOX.mjs.map} +1 -1
- package/dist/chunk-D6WEHN33.mjs +276 -0
- package/dist/chunk-D6WEHN33.mjs.map +1 -0
- package/dist/chunk-DRYFGARD.mjs +289 -0
- package/dist/chunk-DRYFGARD.mjs.map +1 -0
- package/dist/chunk-DUN6XU3N.mjs +72 -0
- package/dist/{chunk-XD7WGXHZ.mjs.map → chunk-DUN6XU3N.mjs.map} +1 -1
- package/dist/chunk-ESLHNWWE.mjs +387 -0
- package/dist/chunk-ESLHNWWE.mjs.map +1 -0
- package/dist/chunk-JUQEW6ON.mjs +256 -0
- package/dist/chunk-JUQEW6ON.mjs.map +1 -0
- package/dist/chunk-KUO2BVFY.mjs +90 -0
- package/dist/{chunk-DE7I3VHO.mjs.map → chunk-KUO2BVFY.mjs.map} +1 -1
- package/dist/chunk-R5J3WAUI.mjs +645 -0
- package/dist/chunk-R5J3WAUI.mjs.map +1 -0
- package/dist/chunk-XZ6ZMXXU.mjs +39 -0
- package/dist/{chunk-K36R6HWY.mjs.map → chunk-XZ6ZMXXU.mjs.map} +1 -1
- package/dist/chunk-YPU4WTXZ.mjs +269 -0
- package/dist/chunk-YPU4WTXZ.mjs.map +1 -0
- package/dist/chunk-YUCD2TFH.mjs +242 -0
- package/dist/chunk-YUCD2TFH.mjs.map +1 -0
- package/dist/chunk-ZTJENCFC.mjs +379 -0
- package/dist/chunk-ZTJENCFC.mjs.map +1 -0
- package/dist/chunk-ZWYXXCXP.mjs +248 -0
- package/dist/chunk-ZWYXXCXP.mjs.map +1 -0
- package/dist/index.d.mts +183 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.js +3930 -4449
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +250 -2698
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-2YMA6QOV.mjs +0 -193
- package/dist/chunk-2YMA6QOV.mjs.map +0 -1
- package/dist/chunk-3IIFBJCD.mjs +0 -45
- package/dist/chunk-BS4DKYGN.mjs +0 -48
- package/dist/chunk-DE7I3VHO.mjs +0 -106
- package/dist/chunk-F6D2OZ36.mjs +0 -89
- package/dist/chunk-F6D2OZ36.mjs.map +0 -1
- package/dist/chunk-GAOIFQDX.mjs +0 -59
- package/dist/chunk-GAOIFQDX.mjs.map +0 -1
- package/dist/chunk-HUIX2XVR.mjs +0 -159
- package/dist/chunk-HUIX2XVR.mjs.map +0 -1
- package/dist/chunk-K36R6HWY.mjs +0 -51
- package/dist/chunk-MBTAI3WE.mjs +0 -78
- package/dist/chunk-MBTAI3WE.mjs.map +0 -1
- package/dist/chunk-MGT6RUVX.mjs +0 -423
- package/dist/chunk-PIFS7AIT.mjs +0 -446
- package/dist/chunk-WG66JYYW.mjs +0 -116
- package/dist/chunk-XD7WGXHZ.mjs +0 -82
- package/dist/chunk-YYDM6V2F.mjs +0 -113
- package/dist/chunk-ZK7P3TZN.mjs +0 -286
- package/dist/chunk-ZK7P3TZN.mjs.map +0 -1
- /package/dist/{chunk-3IIFBJCD.mjs.map → chunk-BJTO5JO5.mjs.map} +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MLPACKAGE_FILES
|
|
3
|
+
} from "./chunk-BP7H4NFS.mjs";
|
|
4
|
+
import {
|
|
5
|
+
cropRegion,
|
|
6
|
+
resizeAndNormalize
|
|
7
|
+
} from "./chunk-22BHCDT5.mjs";
|
|
8
|
+
import {
|
|
9
|
+
resolveEngine
|
|
10
|
+
} from "./chunk-2IOKI4ES.mjs";
|
|
11
|
+
|
|
12
|
+
// src/catalogs/face-recognition-models.ts
|
|
13
|
+
import { hfModelUrl } from "@camstack/types";
|
|
14
|
+
var HF_REPO = "camstack/camstack-models";
|
|
15
|
+
var FACE_EMBEDDING_LABELS = [
|
|
16
|
+
{ id: "embedding", name: "Face Embedding" }
|
|
17
|
+
];
|
|
18
|
+
var FACE_RECOGNITION_MODELS = [
|
|
19
|
+
{
|
|
20
|
+
id: "arcface-r100",
|
|
21
|
+
name: "ArcFace R100",
|
|
22
|
+
description: "ArcFace ResNet-100 \u2014 high-accuracy face recognition embeddings",
|
|
23
|
+
inputSize: { width: 112, height: 112 },
|
|
24
|
+
inputLayout: "nhwc",
|
|
25
|
+
labels: FACE_EMBEDDING_LABELS,
|
|
26
|
+
formats: {
|
|
27
|
+
onnx: {
|
|
28
|
+
url: hfModelUrl(HF_REPO, "faceRecognition/arcface/onnx/camstack-arcface-arcface.onnx"),
|
|
29
|
+
sizeMB: 130
|
|
30
|
+
},
|
|
31
|
+
coreml: {
|
|
32
|
+
url: hfModelUrl(HF_REPO, "faceRecognition/arcface/coreml/camstack-arcface-r100.mlpackage"),
|
|
33
|
+
sizeMB: 65,
|
|
34
|
+
isDirectory: true,
|
|
35
|
+
files: MLPACKAGE_FILES,
|
|
36
|
+
runtimes: ["python"]
|
|
37
|
+
},
|
|
38
|
+
openvino: {
|
|
39
|
+
url: hfModelUrl(HF_REPO, "faceRecognition/arcface/openvino/camstack-arcface-r100.xml"),
|
|
40
|
+
sizeMB: 65,
|
|
41
|
+
runtimes: ["python"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
// src/shared/postprocess/arcface.ts
|
|
48
|
+
function l2Normalize(vec) {
|
|
49
|
+
let sumSq = 0;
|
|
50
|
+
for (let i = 0; i < vec.length; i++) {
|
|
51
|
+
sumSq += vec[i] * vec[i];
|
|
52
|
+
}
|
|
53
|
+
const norm = Math.sqrt(sumSq);
|
|
54
|
+
if (norm === 0) return new Float32Array(vec.length);
|
|
55
|
+
const out = new Float32Array(vec.length);
|
|
56
|
+
for (let i = 0; i < vec.length; i++) {
|
|
57
|
+
out[i] = vec[i] / norm;
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
function cosineSimilarity(a, b) {
|
|
62
|
+
if (a.length !== b.length) throw new Error("Embedding length mismatch");
|
|
63
|
+
let dot = 0;
|
|
64
|
+
for (let i = 0; i < a.length; i++) {
|
|
65
|
+
dot += a[i] * b[i];
|
|
66
|
+
}
|
|
67
|
+
return dot;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/addons/face-recognition/index.ts
|
|
71
|
+
var IDENTITY_LABEL = { id: "identity", name: "Identity" };
|
|
72
|
+
var IDENTITY_LABELS = [IDENTITY_LABEL];
|
|
73
|
+
var FACE_REC_CLASS_MAP = { mapping: {}, preserveOriginal: true };
|
|
74
|
+
var REQUIRED_STEPS = [
|
|
75
|
+
{ slot: "cropper", outputClasses: ["face"], description: "Requires a face detector" }
|
|
76
|
+
];
|
|
77
|
+
var FaceRecognitionAddon = class {
|
|
78
|
+
id = "face-recognition";
|
|
79
|
+
slot = "classifier";
|
|
80
|
+
inputClasses = ["face"];
|
|
81
|
+
outputClasses = ["identity:*"];
|
|
82
|
+
slotPriority = 0;
|
|
83
|
+
requiredSteps = REQUIRED_STEPS;
|
|
84
|
+
manifest = {
|
|
85
|
+
id: "face-recognition",
|
|
86
|
+
name: "Face Recognition",
|
|
87
|
+
version: "0.1.0",
|
|
88
|
+
description: "ArcFace-based face recognition \u2014 produces 512-d identity embeddings",
|
|
89
|
+
slot: "classifier",
|
|
90
|
+
labelOutputType: "face",
|
|
91
|
+
inputClasses: ["face"],
|
|
92
|
+
outputClasses: ["identity:*"],
|
|
93
|
+
requiredSteps: REQUIRED_STEPS,
|
|
94
|
+
supportsCustomModels: false,
|
|
95
|
+
mayRequirePython: false,
|
|
96
|
+
defaultConfig: {
|
|
97
|
+
modelId: "arcface-r100",
|
|
98
|
+
runtime: "node",
|
|
99
|
+
backend: "cpu"
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
engine = null;
|
|
103
|
+
modelEntry;
|
|
104
|
+
resolvedConfig = null;
|
|
105
|
+
ctx = null;
|
|
106
|
+
getModelRequirements() {
|
|
107
|
+
return FACE_RECOGNITION_MODELS.map((m) => ({
|
|
108
|
+
modelId: m.id,
|
|
109
|
+
name: m.name,
|
|
110
|
+
minRAM_MB: 400,
|
|
111
|
+
accuracyScore: 90,
|
|
112
|
+
formats: Object.keys(m.formats)
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
configure(config) {
|
|
116
|
+
this.resolvedConfig = config;
|
|
117
|
+
}
|
|
118
|
+
async initialize(ctx) {
|
|
119
|
+
this.ctx = ctx;
|
|
120
|
+
const cfg = ctx.addonConfig;
|
|
121
|
+
const modelId = cfg["modelId"] ?? this.resolvedConfig?.modelId ?? "arcface-r100";
|
|
122
|
+
const entry = FACE_RECOGNITION_MODELS.find((m) => m.id === modelId);
|
|
123
|
+
if (!entry) {
|
|
124
|
+
throw new Error(`FaceRecognitionAddon: unknown modelId "${modelId}"`);
|
|
125
|
+
}
|
|
126
|
+
this.modelEntry = entry;
|
|
127
|
+
}
|
|
128
|
+
async classify(input) {
|
|
129
|
+
if (!this.engine) await this.ensureEngine();
|
|
130
|
+
const start = Date.now();
|
|
131
|
+
const { width: inputW, height: inputH } = this.modelEntry.inputSize;
|
|
132
|
+
const faceCrop = await cropRegion(input.frame.data, input.roi);
|
|
133
|
+
const layout = this.modelEntry.inputLayout ?? "nhwc";
|
|
134
|
+
const normalization = this.modelEntry.inputNormalization ?? "zero-one";
|
|
135
|
+
const normalized = await resizeAndNormalize(faceCrop, inputW, inputH, normalization, layout);
|
|
136
|
+
const rawEmbedding = await this.engine.run(normalized, [1, inputH, inputW, 3]);
|
|
137
|
+
const embedding = l2Normalize(rawEmbedding);
|
|
138
|
+
return {
|
|
139
|
+
classifications: [
|
|
140
|
+
{
|
|
141
|
+
class: "identity",
|
|
142
|
+
score: 1,
|
|
143
|
+
embedding
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
inferenceMs: Date.now() - start,
|
|
147
|
+
modelId: this.modelEntry.id
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
async ensureEngine() {
|
|
151
|
+
const config = this.resolvedConfig;
|
|
152
|
+
const modelId = config?.modelId ?? this.modelEntry.id;
|
|
153
|
+
const runtime = config?.runtime === "python" ? "coreml" : config?.runtime === "node" ? "onnx" : "auto";
|
|
154
|
+
const backend = config?.backend ?? "cpu";
|
|
155
|
+
const format = config?.format ?? "onnx";
|
|
156
|
+
const entry = FACE_RECOGNITION_MODELS.find((m) => m.id === modelId) ?? this.modelEntry;
|
|
157
|
+
this.modelEntry = entry;
|
|
158
|
+
const modelsDir = this.ctx.models?.getModelsDir() ?? this.ctx.locationPaths.models;
|
|
159
|
+
if (this.ctx.models) {
|
|
160
|
+
await this.ctx.models.ensure(modelId, format);
|
|
161
|
+
}
|
|
162
|
+
const resolved = await resolveEngine({
|
|
163
|
+
runtime,
|
|
164
|
+
backend,
|
|
165
|
+
modelEntry: entry,
|
|
166
|
+
modelsDir,
|
|
167
|
+
models: this.ctx.models
|
|
168
|
+
});
|
|
169
|
+
this.engine = resolved.engine;
|
|
170
|
+
}
|
|
171
|
+
async shutdown() {
|
|
172
|
+
await this.engine?.dispose();
|
|
173
|
+
}
|
|
174
|
+
getConfigSchema() {
|
|
175
|
+
return {
|
|
176
|
+
sections: [
|
|
177
|
+
{
|
|
178
|
+
id: "model",
|
|
179
|
+
title: "Model",
|
|
180
|
+
columns: 1,
|
|
181
|
+
fields: [
|
|
182
|
+
{
|
|
183
|
+
key: "modelId",
|
|
184
|
+
label: "Model",
|
|
185
|
+
type: "model-selector",
|
|
186
|
+
catalog: [...FACE_RECOGNITION_MODELS],
|
|
187
|
+
allowCustom: false,
|
|
188
|
+
allowConversion: false,
|
|
189
|
+
acceptFormats: ["onnx", "coreml", "openvino"],
|
|
190
|
+
requiredMetadata: ["inputSize", "labels", "outputFormat"],
|
|
191
|
+
outputFormatHint: "embedding"
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "runtime",
|
|
197
|
+
title: "Runtime",
|
|
198
|
+
columns: 2,
|
|
199
|
+
fields: [
|
|
200
|
+
{
|
|
201
|
+
key: "runtime",
|
|
202
|
+
label: "Runtime",
|
|
203
|
+
type: "select",
|
|
204
|
+
options: [
|
|
205
|
+
{ value: "auto", label: "Auto" },
|
|
206
|
+
{ value: "onnx", label: "ONNX Runtime" },
|
|
207
|
+
{ value: "coreml", label: "CoreML (Apple)" },
|
|
208
|
+
{ value: "openvino", label: "OpenVINO (Intel)" }
|
|
209
|
+
]
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
key: "backend",
|
|
213
|
+
label: "Backend",
|
|
214
|
+
type: "select",
|
|
215
|
+
showWhen: { field: "runtime", equals: "onnx" },
|
|
216
|
+
options: [
|
|
217
|
+
{ value: "auto", label: "Auto" },
|
|
218
|
+
{ value: "cpu", label: "CPU" },
|
|
219
|
+
{ value: "coreml", label: "CoreML" },
|
|
220
|
+
{ value: "cuda", label: "CUDA (NVIDIA)" }
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
getClassMap() {
|
|
229
|
+
return FACE_REC_CLASS_MAP;
|
|
230
|
+
}
|
|
231
|
+
getModelCatalog() {
|
|
232
|
+
return [...FACE_RECOGNITION_MODELS];
|
|
233
|
+
}
|
|
234
|
+
getAvailableModels() {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
getActiveLabels() {
|
|
238
|
+
return IDENTITY_LABELS;
|
|
239
|
+
}
|
|
240
|
+
async probe() {
|
|
241
|
+
return {
|
|
242
|
+
available: true,
|
|
243
|
+
runtime: this.engine?.runtime ?? "onnx",
|
|
244
|
+
device: this.engine?.device ?? "cpu",
|
|
245
|
+
capabilities: ["fp32"]
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export {
|
|
251
|
+
l2Normalize,
|
|
252
|
+
cosineSimilarity,
|
|
253
|
+
FACE_RECOGNITION_MODELS,
|
|
254
|
+
FaceRecognitionAddon
|
|
255
|
+
};
|
|
256
|
+
//# sourceMappingURL=chunk-JUQEW6ON.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/catalogs/face-recognition-models.ts","../src/shared/postprocess/arcface.ts","../src/addons/face-recognition/index.ts"],"sourcesContent":["import type { ModelCatalogEntry, LabelDefinition } from '@camstack/types'\nimport { hfModelUrl } from '@camstack/types'\nimport { MLPACKAGE_FILES } from './object-detection-models.js'\n\nconst HF_REPO = 'camstack/camstack-models'\n\nconst FACE_EMBEDDING_LABELS: readonly LabelDefinition[] = [\n { id: 'embedding', name: 'Face Embedding' },\n] as const\n\nexport const FACE_RECOGNITION_MODELS: readonly ModelCatalogEntry[] = [\n {\n id: 'arcface-r100',\n name: 'ArcFace R100',\n description: 'ArcFace ResNet-100 — high-accuracy face recognition embeddings',\n inputSize: { width: 112, height: 112 },\n inputLayout: 'nhwc',\n labels: FACE_EMBEDDING_LABELS,\n formats: {\n onnx: {\n url: hfModelUrl(HF_REPO, 'faceRecognition/arcface/onnx/camstack-arcface-arcface.onnx'),\n sizeMB: 130,\n },\n coreml: {\n url: hfModelUrl(HF_REPO, 'faceRecognition/arcface/coreml/camstack-arcface-r100.mlpackage'),\n sizeMB: 65,\n isDirectory: true,\n files: MLPACKAGE_FILES,\n runtimes: ['python'],\n },\n openvino: {\n url: hfModelUrl(HF_REPO, 'faceRecognition/arcface/openvino/camstack-arcface-r100.xml'),\n sizeMB: 65,\n runtimes: ['python'],\n },\n },\n },\n] as const\n","/** L2 normalize a vector in-place, returning a new Float32Array */\nexport function l2Normalize(vec: Float32Array): Float32Array {\n let sumSq = 0\n for (let i = 0; i < vec.length; i++) {\n sumSq += vec[i]! * vec[i]!\n }\n const norm = Math.sqrt(sumSq)\n if (norm === 0) return new Float32Array(vec.length)\n\n const out = new Float32Array(vec.length)\n for (let i = 0; i < vec.length; i++) {\n out[i] = vec[i]! / norm\n }\n return out\n}\n\n/** Cosine similarity between two embeddings (assumes they are already L2-normalized) */\nexport function cosineSimilarity(a: Float32Array, b: Float32Array): number {\n if (a.length !== b.length) throw new Error('Embedding length mismatch')\n let dot = 0\n for (let i = 0; i < a.length; i++) {\n dot += a[i]! * b[i]!\n }\n return dot\n}\n","import type {\n IClassifierProvider,\n IDetectionAddon,\n AddonManifest,\n AddonContext,\n CropInput,\n ClassifierOutput,\n ConfigUISchema,\n ClassMapDefinition,\n ProbeResult,\n ModelCatalogEntry,\n DetectionModel,\n LabelDefinition,\n IInferenceEngine,\n RequiredStep,\n ModelRequirement,\n ResolvedInferenceConfig,\n} from '@camstack/types'\nimport { FACE_RECOGNITION_MODELS } from '../../catalogs/face-recognition-models.js'\nimport { cropRegion, resizeAndNormalize } from '../../shared/image-utils.js'\nimport { l2Normalize } from '../../shared/postprocess/arcface.js'\nimport { resolveEngine } from '../../shared/engine-resolver.js'\n\nconst IDENTITY_LABEL: LabelDefinition = { id: 'identity', name: 'Identity' }\nconst IDENTITY_LABELS: readonly LabelDefinition[] = [IDENTITY_LABEL]\nconst FACE_REC_CLASS_MAP: ClassMapDefinition = { mapping: {}, preserveOriginal: true }\n\nconst REQUIRED_STEPS: readonly RequiredStep[] = [\n { slot: 'cropper', outputClasses: ['face'], description: 'Requires a face detector' },\n]\n\nexport default class FaceRecognitionAddon implements IClassifierProvider, IDetectionAddon {\n readonly id = 'face-recognition'\n readonly slot = 'classifier' as const\n readonly inputClasses = ['face'] as const\n readonly outputClasses = ['identity:*'] as const\n readonly slotPriority = 0\n readonly requiredSteps = REQUIRED_STEPS\n readonly manifest: AddonManifest = {\n id: 'face-recognition',\n name: 'Face Recognition',\n version: '0.1.0',\n\n description: 'ArcFace-based face recognition — produces 512-d identity embeddings',\n\n slot: 'classifier',\n labelOutputType: 'face',\n inputClasses: ['face'],\n outputClasses: ['identity:*'],\n requiredSteps: REQUIRED_STEPS,\n supportsCustomModels: false,\n mayRequirePython: false,\n defaultConfig: {\n modelId: 'arcface-r100',\n runtime: 'node',\n backend: 'cpu',\n },\n }\n\n private engine: IInferenceEngine | null = null\n private modelEntry!: ModelCatalogEntry\n private resolvedConfig: ResolvedInferenceConfig | null = null\n private ctx: AddonContext | null = null\n\n getModelRequirements(): ModelRequirement[] {\n return FACE_RECOGNITION_MODELS.map((m) => ({\n modelId: m.id,\n name: m.name,\n minRAM_MB: 400,\n accuracyScore: 90,\n formats: Object.keys(m.formats) as readonly string[],\n }))\n }\n\n configure(config: ResolvedInferenceConfig): void {\n this.resolvedConfig = config\n }\n\n async initialize(ctx: AddonContext): Promise<void> {\n this.ctx = ctx\n const cfg = ctx.addonConfig\n const modelId = (cfg['modelId'] as string | undefined) ?? this.resolvedConfig?.modelId ?? 'arcface-r100'\n\n const entry = FACE_RECOGNITION_MODELS.find((m) => m.id === modelId)\n if (!entry) {\n throw new Error(`FaceRecognitionAddon: unknown modelId \"${modelId}\"`)\n }\n this.modelEntry = entry\n }\n\n async classify(input: CropInput): Promise<ClassifierOutput> {\n if (!this.engine) await this.ensureEngine()\n const start = Date.now()\n const { width: inputW, height: inputH } = this.modelEntry.inputSize\n\n // Crop the face region\n const faceCrop = await cropRegion(input.frame.data, input.roi)\n\n // Resize to 112x112, NHWC layout (ArcFace expects NHWC)\n const layout = this.modelEntry.inputLayout ?? 'nhwc'\n const normalization = this.modelEntry.inputNormalization ?? 'zero-one'\n const normalized = await resizeAndNormalize(faceCrop, inputW, inputH, normalization, layout)\n\n // Run inference — output is 512-d embedding\n const rawEmbedding = await this.engine!.run(normalized, [1, inputH, inputW, 3])\n\n // L2 normalize the embedding\n const embedding = l2Normalize(rawEmbedding)\n\n return {\n classifications: [\n {\n class: 'identity',\n score: 1.0,\n embedding,\n },\n ],\n inferenceMs: Date.now() - start,\n modelId: this.modelEntry.id,\n }\n }\n\n private async ensureEngine(): Promise<void> {\n const config = this.resolvedConfig\n const modelId = config?.modelId ?? this.modelEntry.id\n const runtime = config?.runtime === 'python' ? 'coreml' : (config?.runtime === 'node' ? 'onnx' : 'auto')\n const backend = config?.backend ?? 'cpu'\n const format = config?.format ?? 'onnx'\n\n const entry = FACE_RECOGNITION_MODELS.find((m) => m.id === modelId) ?? this.modelEntry\n this.modelEntry = entry\n\n const modelsDir = this.ctx!.models?.getModelsDir() ?? this.ctx!.locationPaths.models\n\n if (this.ctx!.models) {\n await this.ctx!.models.ensure(modelId, format as any)\n }\n\n const resolved = await resolveEngine({\n runtime: runtime as 'auto',\n backend,\n modelEntry: entry,\n modelsDir,\n models: this.ctx!.models,\n })\n this.engine = resolved.engine\n }\n\n async shutdown(): Promise<void> {\n await this.engine?.dispose()\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'model',\n title: 'Model',\n columns: 1,\n fields: [\n {\n key: 'modelId',\n label: 'Model',\n type: 'model-selector',\n catalog: [...FACE_RECOGNITION_MODELS],\n allowCustom: false,\n allowConversion: false,\n acceptFormats: ['onnx', 'coreml', 'openvino'],\n requiredMetadata: ['inputSize', 'labels', 'outputFormat'],\n outputFormatHint: 'embedding',\n },\n ],\n },\n {\n id: 'runtime',\n title: 'Runtime',\n columns: 2,\n fields: [\n {\n key: 'runtime',\n label: 'Runtime',\n type: 'select',\n options: [\n { value: 'auto', label: 'Auto' },\n { value: 'onnx', label: 'ONNX Runtime' },\n { value: 'coreml', label: 'CoreML (Apple)' },\n { value: 'openvino', label: 'OpenVINO (Intel)' },\n ],\n },\n {\n key: 'backend',\n label: 'Backend',\n type: 'select',\n showWhen: { field: 'runtime', equals: 'onnx' },\n options: [\n { value: 'auto', label: 'Auto' },\n { value: 'cpu', label: 'CPU' },\n { value: 'coreml', label: 'CoreML' },\n { value: 'cuda', label: 'CUDA (NVIDIA)' },\n ],\n },\n ],\n },\n ],\n }\n }\n\n getClassMap(): ClassMapDefinition {\n return FACE_REC_CLASS_MAP\n }\n\n getModelCatalog(): ModelCatalogEntry[] {\n return [...FACE_RECOGNITION_MODELS]\n }\n\n getAvailableModels(): DetectionModel[] {\n return []\n }\n\n getActiveLabels(): readonly LabelDefinition[] {\n return IDENTITY_LABELS\n }\n\n async probe(): Promise<ProbeResult> {\n return {\n available: true,\n runtime: this.engine?.runtime ?? 'onnx',\n device: this.engine?.device ?? 'cpu',\n capabilities: ['fp32'],\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;AACA,SAAS,kBAAkB;AAG3B,IAAM,UAAU;AAEhB,IAAM,wBAAoD;AAAA,EACxD,EAAE,IAAI,aAAa,MAAM,iBAAiB;AAC5C;AAEO,IAAM,0BAAwD;AAAA,EACnE;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,WAAW,EAAE,OAAO,KAAK,QAAQ,IAAI;AAAA,IACrC,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,MAAM;AAAA,QACJ,KAAK,WAAW,SAAS,4DAA4D;AAAA,QACrF,QAAQ;AAAA,MACV;AAAA,MACA,QAAQ;AAAA,QACN,KAAK,WAAW,SAAS,gEAAgE;AAAA,QACzF,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,OAAO;AAAA,QACP,UAAU,CAAC,QAAQ;AAAA,MACrB;AAAA,MACA,UAAU;AAAA,QACR,KAAK,WAAW,SAAS,4DAA4D;AAAA,QACrF,QAAQ;AAAA,QACR,UAAU,CAAC,QAAQ;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AACF;;;ACpCO,SAAS,YAAY,KAAiC;AAC3D,MAAI,QAAQ;AACZ,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,aAAS,IAAI,CAAC,IAAK,IAAI,CAAC;AAAA,EAC1B;AACA,QAAM,OAAO,KAAK,KAAK,KAAK;AAC5B,MAAI,SAAS,EAAG,QAAO,IAAI,aAAa,IAAI,MAAM;AAElD,QAAM,MAAM,IAAI,aAAa,IAAI,MAAM;AACvC,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,QAAI,CAAC,IAAI,IAAI,CAAC,IAAK;AAAA,EACrB;AACA,SAAO;AACT;AAGO,SAAS,iBAAiB,GAAiB,GAAyB;AACzE,MAAI,EAAE,WAAW,EAAE,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AACtE,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,WAAO,EAAE,CAAC,IAAK,EAAE,CAAC;AAAA,EACpB;AACA,SAAO;AACT;;;ACDA,IAAM,iBAAkC,EAAE,IAAI,YAAY,MAAM,WAAW;AAC3E,IAAM,kBAA8C,CAAC,cAAc;AACnE,IAAM,qBAAyC,EAAE,SAAS,CAAC,GAAG,kBAAkB,KAAK;AAErF,IAAM,iBAA0C;AAAA,EAC9C,EAAE,MAAM,WAAW,eAAe,CAAC,MAAM,GAAG,aAAa,2BAA2B;AACtF;AAEA,IAAqB,uBAArB,MAA0F;AAAA,EAC/E,KAAK;AAAA,EACL,OAAO;AAAA,EACP,eAAe,CAAC,MAAM;AAAA,EACtB,gBAAgB,CAAC,YAAY;AAAA,EAC7B,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,WAA0B;AAAA,IACjC,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IAET,aAAa;AAAA,IAEb,MAAM;AAAA,IACN,iBAAiB;AAAA,IACjB,cAAc,CAAC,MAAM;AAAA,IACrB,eAAe,CAAC,YAAY;AAAA,IAC5B,eAAe;AAAA,IACf,sBAAsB;AAAA,IACtB,kBAAkB;AAAA,IAClB,eAAe;AAAA,MACb,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,SAAkC;AAAA,EAClC;AAAA,EACA,iBAAiD;AAAA,EACjD,MAA2B;AAAA,EAEnC,uBAA2C;AACzC,WAAO,wBAAwB,IAAI,CAAC,OAAO;AAAA,MACzC,SAAS,EAAE;AAAA,MACX,MAAM,EAAE;AAAA,MACR,WAAW;AAAA,MACX,eAAe;AAAA,MACf,SAAS,OAAO,KAAK,EAAE,OAAO;AAAA,IAChC,EAAE;AAAA,EACJ;AAAA,EAEA,UAAU,QAAuC;AAC/C,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,MAAM,WAAW,KAAkC;AACjD,SAAK,MAAM;AACX,UAAM,MAAM,IAAI;AAChB,UAAM,UAAW,IAAI,SAAS,KAA4B,KAAK,gBAAgB,WAAW;AAE1F,UAAM,QAAQ,wBAAwB,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO;AAClE,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,0CAA0C,OAAO,GAAG;AAAA,IACtE;AACA,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,MAAM,SAAS,OAA6C;AAC1D,QAAI,CAAC,KAAK,OAAQ,OAAM,KAAK,aAAa;AAC1C,UAAM,QAAQ,KAAK,IAAI;AACvB,UAAM,EAAE,OAAO,QAAQ,QAAQ,OAAO,IAAI,KAAK,WAAW;AAG1D,UAAM,WAAW,MAAM,WAAW,MAAM,MAAM,MAAM,MAAM,GAAG;AAG7D,UAAM,SAAS,KAAK,WAAW,eAAe;AAC9C,UAAM,gBAAgB,KAAK,WAAW,sBAAsB;AAC5D,UAAM,aAAa,MAAM,mBAAmB,UAAU,QAAQ,QAAQ,eAAe,MAAM;AAG3F,UAAM,eAAe,MAAM,KAAK,OAAQ,IAAI,YAAY,CAAC,GAAG,QAAQ,QAAQ,CAAC,CAAC;AAG9E,UAAM,YAAY,YAAY,YAAY;AAE1C,WAAO;AAAA,MACL,iBAAiB;AAAA,QACf;AAAA,UACE,OAAO;AAAA,UACP,OAAO;AAAA,UACP;AAAA,QACF;AAAA,MACF;AAAA,MACA,aAAa,KAAK,IAAI,IAAI;AAAA,MAC1B,SAAS,KAAK,WAAW;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,MAAc,eAA8B;AAC1C,UAAM,SAAS,KAAK;AACpB,UAAM,UAAU,QAAQ,WAAW,KAAK,WAAW;AACnD,UAAM,UAAU,QAAQ,YAAY,WAAW,WAAY,QAAQ,YAAY,SAAS,SAAS;AACjG,UAAM,UAAU,QAAQ,WAAW;AACnC,UAAM,SAAS,QAAQ,UAAU;AAEjC,UAAM,QAAQ,wBAAwB,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO,KAAK,KAAK;AAC5E,SAAK,aAAa;AAElB,UAAM,YAAY,KAAK,IAAK,QAAQ,aAAa,KAAK,KAAK,IAAK,cAAc;AAE9E,QAAI,KAAK,IAAK,QAAQ;AACpB,YAAM,KAAK,IAAK,OAAO,OAAO,SAAS,MAAa;AAAA,IACtD;AAEA,UAAM,WAAW,MAAM,cAAc;AAAA,MACnC;AAAA,MACA;AAAA,MACA,YAAY;AAAA,MACZ;AAAA,MACA,QAAQ,KAAK,IAAK;AAAA,IACpB,CAAC;AACD,SAAK,SAAS,SAAS;AAAA,EACzB;AAAA,EAEA,MAAM,WAA0B;AAC9B,UAAM,KAAK,QAAQ,QAAQ;AAAA,EAC7B;AAAA,EAEA,kBAAkC;AAChC,WAAO;AAAA,MACL,UAAU;AAAA,QACR;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,KAAK;AAAA,cACL,OAAO;AAAA,cACP,MAAM;AAAA,cACN,SAAS,CAAC,GAAG,uBAAuB;AAAA,cACpC,aAAa;AAAA,cACb,iBAAiB;AAAA,cACjB,eAAe,CAAC,QAAQ,UAAU,UAAU;AAAA,cAC5C,kBAAkB,CAAC,aAAa,UAAU,cAAc;AAAA,cACxD,kBAAkB;AAAA,YACpB;AAAA,UACF;AAAA,QACF;AAAA,QACA;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,KAAK;AAAA,cACL,OAAO;AAAA,cACP,MAAM;AAAA,cACN,SAAS;AAAA,gBACP,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,gBAC/B,EAAE,OAAO,QAAQ,OAAO,eAAe;AAAA,gBACvC,EAAE,OAAO,UAAU,OAAO,iBAAiB;AAAA,gBAC3C,EAAE,OAAO,YAAY,OAAO,mBAAmB;AAAA,cACjD;AAAA,YACF;AAAA,YACA;AAAA,cACE,KAAK;AAAA,cACL,OAAO;AAAA,cACP,MAAM;AAAA,cACN,UAAU,EAAE,OAAO,WAAW,QAAQ,OAAO;AAAA,cAC7C,SAAS;AAAA,gBACP,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,gBAC/B,EAAE,OAAO,OAAO,OAAO,MAAM;AAAA,gBAC7B,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,gBACnC,EAAE,OAAO,QAAQ,OAAO,gBAAgB;AAAA,cAC1C;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,cAAkC;AAChC,WAAO;AAAA,EACT;AAAA,EAEA,kBAAuC;AACrC,WAAO,CAAC,GAAG,uBAAuB;AAAA,EACpC;AAAA,EAEA,qBAAuC;AACrC,WAAO,CAAC;AAAA,EACV;AAAA,EAEA,kBAA8C;AAC5C,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAA8B;AAClC,WAAO;AAAA,MACL,WAAW;AAAA,MACX,SAAS,KAAK,QAAQ,WAAW;AAAA,MACjC,QAAQ,KAAK,QAAQ,UAAU;AAAA,MAC/B,cAAc,CAAC,MAAM;AAAA,IACvB;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/shared/postprocess/yolo.ts
|
|
2
|
+
function iou(a, b) {
|
|
3
|
+
const ax1 = a.x;
|
|
4
|
+
const ay1 = a.y;
|
|
5
|
+
const ax2 = a.x + a.w;
|
|
6
|
+
const ay2 = a.y + a.h;
|
|
7
|
+
const bx1 = b.x;
|
|
8
|
+
const by1 = b.y;
|
|
9
|
+
const bx2 = b.x + b.w;
|
|
10
|
+
const by2 = b.y + b.h;
|
|
11
|
+
const interX1 = Math.max(ax1, bx1);
|
|
12
|
+
const interY1 = Math.max(ay1, by1);
|
|
13
|
+
const interX2 = Math.min(ax2, bx2);
|
|
14
|
+
const interY2 = Math.min(ay2, by2);
|
|
15
|
+
const interW = Math.max(0, interX2 - interX1);
|
|
16
|
+
const interH = Math.max(0, interY2 - interY1);
|
|
17
|
+
const interArea = interW * interH;
|
|
18
|
+
if (interArea === 0) return 0;
|
|
19
|
+
const areaA = a.w * a.h;
|
|
20
|
+
const areaB = b.w * b.h;
|
|
21
|
+
const unionArea = areaA + areaB - interArea;
|
|
22
|
+
return unionArea === 0 ? 0 : interArea / unionArea;
|
|
23
|
+
}
|
|
24
|
+
function nms(boxes, iouThreshold) {
|
|
25
|
+
const indices = boxes.map((_, i) => i).sort((a, b) => boxes[b].score - boxes[a].score);
|
|
26
|
+
const kept = [];
|
|
27
|
+
const suppressed = /* @__PURE__ */ new Set();
|
|
28
|
+
for (const idx of indices) {
|
|
29
|
+
if (suppressed.has(idx)) continue;
|
|
30
|
+
kept.push(idx);
|
|
31
|
+
for (const other of indices) {
|
|
32
|
+
if (other === idx || suppressed.has(other)) continue;
|
|
33
|
+
if (iou(boxes[idx].bbox, boxes[other].bbox) > iouThreshold) {
|
|
34
|
+
suppressed.add(other);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return kept;
|
|
39
|
+
}
|
|
40
|
+
function yoloPostprocess(output, numClasses, numBoxes, options) {
|
|
41
|
+
const { confidence, iouThreshold, labels, scale, padX, padY, originalWidth, originalHeight } = options;
|
|
42
|
+
const candidates = [];
|
|
43
|
+
for (let i = 0; i < numBoxes; i++) {
|
|
44
|
+
const cx = output[0 * numBoxes + i];
|
|
45
|
+
const cy = output[1 * numBoxes + i];
|
|
46
|
+
const w = output[2 * numBoxes + i];
|
|
47
|
+
const h = output[3 * numBoxes + i];
|
|
48
|
+
let bestScore = -Infinity;
|
|
49
|
+
let bestClass = 0;
|
|
50
|
+
for (let j = 0; j < numClasses; j++) {
|
|
51
|
+
const score = output[(4 + j) * numBoxes + i];
|
|
52
|
+
if (score > bestScore) {
|
|
53
|
+
bestScore = score;
|
|
54
|
+
bestClass = j;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (bestScore < confidence) continue;
|
|
58
|
+
const bbox = {
|
|
59
|
+
x: cx - w / 2,
|
|
60
|
+
y: cy - h / 2,
|
|
61
|
+
w,
|
|
62
|
+
h
|
|
63
|
+
};
|
|
64
|
+
candidates.push({ bbox, score: bestScore, classIdx: bestClass });
|
|
65
|
+
}
|
|
66
|
+
if (candidates.length === 0) return [];
|
|
67
|
+
const keptIndices = nms(candidates, iouThreshold);
|
|
68
|
+
return keptIndices.map((idx) => {
|
|
69
|
+
const { bbox, score, classIdx } = candidates[idx];
|
|
70
|
+
const label = labels[classIdx] ?? String(classIdx);
|
|
71
|
+
const x = Math.max(0, Math.min(originalWidth, (bbox.x - padX) / scale));
|
|
72
|
+
const y = Math.max(0, Math.min(originalHeight, (bbox.y - padY) / scale));
|
|
73
|
+
const x2 = Math.max(0, Math.min(originalWidth, (bbox.x + bbox.w - padX) / scale));
|
|
74
|
+
const y2 = Math.max(0, Math.min(originalHeight, (bbox.y + bbox.h - padY) / scale));
|
|
75
|
+
const finalBbox = { x, y, w: x2 - x, h: y2 - y };
|
|
76
|
+
return {
|
|
77
|
+
class: label,
|
|
78
|
+
originalClass: label,
|
|
79
|
+
score,
|
|
80
|
+
bbox: finalBbox
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
iou,
|
|
87
|
+
nms,
|
|
88
|
+
yoloPostprocess
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=chunk-KUO2BVFY.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/postprocess/yolo.ts"],"sourcesContent":["import type { SpatialDetection, BoundingBox } from '@camstack/types'\n\nexport interface YoloPostprocessOptions {\n readonly confidence: number\n readonly iouThreshold: number\n readonly labels: readonly string[]\n readonly scale: number\n readonly padX: number\n readonly padY: number\n readonly originalWidth: number\n readonly originalHeight: number\n}\n\n/** Calculate IoU between two bounding boxes */\nexport function iou(a: BoundingBox, b: BoundingBox): number {\n const ax1 = a.x\n const ay1 = a.y\n const ax2 = a.x + a.w\n const ay2 = a.y + a.h\n\n const bx1 = b.x\n const by1 = b.y\n const bx2 = b.x + b.w\n const by2 = b.y + b.h\n\n const interX1 = Math.max(ax1, bx1)\n const interY1 = Math.max(ay1, by1)\n const interX2 = Math.min(ax2, bx2)\n const interY2 = Math.min(ay2, by2)\n\n const interW = Math.max(0, interX2 - interX1)\n const interH = Math.max(0, interY2 - interY1)\n const interArea = interW * interH\n\n if (interArea === 0) return 0\n\n const areaA = a.w * a.h\n const areaB = b.w * b.h\n const unionArea = areaA + areaB - interArea\n\n return unionArea === 0 ? 0 : interArea / unionArea\n}\n\n/** Non-maximum suppression — returns indices of kept boxes (sorted by score desc) */\nexport function nms(\n boxes: ReadonlyArray<{ readonly bbox: BoundingBox; readonly score: number }>,\n iouThreshold: number,\n): number[] {\n const indices = boxes\n .map((_, i) => i)\n .sort((a, b) => (boxes[b]!.score) - (boxes[a]!.score))\n\n const kept: number[] = []\n const suppressed = new Set<number>()\n\n for (const idx of indices) {\n if (suppressed.has(idx)) continue\n kept.push(idx)\n for (const other of indices) {\n if (other === idx || suppressed.has(other)) continue\n if (iou(boxes[idx]!.bbox, boxes[other]!.bbox) > iouThreshold) {\n suppressed.add(other)\n }\n }\n }\n\n return kept\n}\n\n/** Full YOLO v8/v9 postprocessing: filter → NMS → scale back to original coords */\nexport function yoloPostprocess(\n output: Float32Array,\n numClasses: number,\n numBoxes: number,\n options: YoloPostprocessOptions,\n): SpatialDetection[] {\n const { confidence, iouThreshold, labels, scale, padX, padY, originalWidth, originalHeight } = options\n\n interface Candidate {\n readonly bbox: BoundingBox\n readonly score: number\n readonly classIdx: number\n }\n\n const candidates: Candidate[] = []\n\n for (let i = 0; i < numBoxes; i++) {\n // YOLO v8/v9 output layout: [1, 4+numClasses, numBoxes] stored row-major\n const cx = output[0 * numBoxes + i]!\n const cy = output[1 * numBoxes + i]!\n const w = output[2 * numBoxes + i]!\n const h = output[3 * numBoxes + i]!\n\n let bestScore = -Infinity\n let bestClass = 0\n\n for (let j = 0; j < numClasses; j++) {\n const score = output[(4 + j) * numBoxes + i]!\n if (score > bestScore) {\n bestScore = score\n bestClass = j\n }\n }\n\n if (bestScore < confidence) continue\n\n // Convert cx,cy,w,h to x,y,w,h (top-left origin)\n const bbox: BoundingBox = {\n x: cx - w / 2,\n y: cy - h / 2,\n w,\n h,\n }\n\n candidates.push({ bbox, score: bestScore, classIdx: bestClass })\n }\n\n if (candidates.length === 0) return []\n\n const keptIndices = nms(candidates, iouThreshold)\n\n return keptIndices.map((idx) => {\n const { bbox, score, classIdx } = candidates[idx]!\n const label = labels[classIdx] ?? String(classIdx)\n\n // Transform from letterbox coords back to original image coords\n const x = Math.max(0, Math.min(originalWidth, (bbox.x - padX) / scale))\n const y = Math.max(0, Math.min(originalHeight, (bbox.y - padY) / scale))\n const x2 = Math.max(0, Math.min(originalWidth, (bbox.x + bbox.w - padX) / scale))\n const y2 = Math.max(0, Math.min(originalHeight, (bbox.y + bbox.h - padY) / scale))\n\n const finalBbox: BoundingBox = { x, y, w: x2 - x, h: y2 - y }\n\n return {\n class: label,\n originalClass: label,\n score,\n bbox: finalBbox,\n } satisfies SpatialDetection\n })\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/shared/postprocess/yolo.ts"],"sourcesContent":["import type { SpatialDetection, BoundingBox } from '@camstack/types'\n\nexport interface YoloPostprocessOptions {\n readonly confidence: number\n readonly iouThreshold: number\n readonly labels: readonly string[]\n readonly scale: number\n readonly padX: number\n readonly padY: number\n readonly originalWidth: number\n readonly originalHeight: number\n}\n\n/** Calculate IoU between two bounding boxes */\nexport function iou(a: BoundingBox, b: BoundingBox): number {\n const ax1 = a.x\n const ay1 = a.y\n const ax2 = a.x + a.w\n const ay2 = a.y + a.h\n\n const bx1 = b.x\n const by1 = b.y\n const bx2 = b.x + b.w\n const by2 = b.y + b.h\n\n const interX1 = Math.max(ax1, bx1)\n const interY1 = Math.max(ay1, by1)\n const interX2 = Math.min(ax2, bx2)\n const interY2 = Math.min(ay2, by2)\n\n const interW = Math.max(0, interX2 - interX1)\n const interH = Math.max(0, interY2 - interY1)\n const interArea = interW * interH\n\n if (interArea === 0) return 0\n\n const areaA = a.w * a.h\n const areaB = b.w * b.h\n const unionArea = areaA + areaB - interArea\n\n return unionArea === 0 ? 0 : interArea / unionArea\n}\n\n/** Non-maximum suppression — returns indices of kept boxes (sorted by score desc) */\nexport function nms(\n boxes: ReadonlyArray<{ readonly bbox: BoundingBox; readonly score: number }>,\n iouThreshold: number,\n): number[] {\n const indices = boxes\n .map((_, i) => i)\n .sort((a, b) => (boxes[b]!.score) - (boxes[a]!.score))\n\n const kept: number[] = []\n const suppressed = new Set<number>()\n\n for (const idx of indices) {\n if (suppressed.has(idx)) continue\n kept.push(idx)\n for (const other of indices) {\n if (other === idx || suppressed.has(other)) continue\n if (iou(boxes[idx]!.bbox, boxes[other]!.bbox) > iouThreshold) {\n suppressed.add(other)\n }\n }\n }\n\n return kept\n}\n\n/** Full YOLO v8/v9 postprocessing: filter → NMS → scale back to original coords */\nexport function yoloPostprocess(\n output: Float32Array,\n numClasses: number,\n numBoxes: number,\n options: YoloPostprocessOptions,\n): SpatialDetection[] {\n const { confidence, iouThreshold, labels, scale, padX, padY, originalWidth, originalHeight } = options\n\n interface Candidate {\n readonly bbox: BoundingBox\n readonly score: number\n readonly classIdx: number\n }\n\n const candidates: Candidate[] = []\n\n for (let i = 0; i < numBoxes; i++) {\n // YOLO v8/v9 output layout: [1, 4+numClasses, numBoxes] stored row-major\n const cx = output[0 * numBoxes + i]!\n const cy = output[1 * numBoxes + i]!\n const w = output[2 * numBoxes + i]!\n const h = output[3 * numBoxes + i]!\n\n let bestScore = -Infinity\n let bestClass = 0\n\n for (let j = 0; j < numClasses; j++) {\n const score = output[(4 + j) * numBoxes + i]!\n if (score > bestScore) {\n bestScore = score\n bestClass = j\n }\n }\n\n if (bestScore < confidence) continue\n\n // Convert cx,cy,w,h to x,y,w,h (top-left origin)\n const bbox: BoundingBox = {\n x: cx - w / 2,\n y: cy - h / 2,\n w,\n h,\n }\n\n candidates.push({ bbox, score: bestScore, classIdx: bestClass })\n }\n\n if (candidates.length === 0) return []\n\n const keptIndices = nms(candidates, iouThreshold)\n\n return keptIndices.map((idx) => {\n const { bbox, score, classIdx } = candidates[idx]!\n const label = labels[classIdx] ?? String(classIdx)\n\n // Transform from letterbox coords back to original image coords\n const x = Math.max(0, Math.min(originalWidth, (bbox.x - padX) / scale))\n const y = Math.max(0, Math.min(originalHeight, (bbox.y - padY) / scale))\n const x2 = Math.max(0, Math.min(originalWidth, (bbox.x + bbox.w - padX) / scale))\n const y2 = Math.max(0, Math.min(originalHeight, (bbox.y + bbox.h - padY) / scale))\n\n const finalBbox: BoundingBox = { x, y, w: x2 - x, h: y2 - y }\n\n return {\n class: label,\n originalClass: label,\n score,\n bbox: finalBbox,\n } satisfies SpatialDetection\n })\n}\n"],"mappings":"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","names":[]}
|