Buckets:

immErfanrajabee/bd / face-memory.ts
imerfanrajabee's picture
download
raw
6.32 kB
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import {StoredUserProfile} from './voice-memory';
export interface PixelFaceBox {
x: number;
y: number;
width: number;
height: number;
}
export interface FaceAnalysis {
box: PixelFaceBox;
fingerprint: number[];
brightness: number;
sharpness: number;
warmth: number;
}
export interface FaceMatchResult {
profile: StoredUserProfile;
similarity: number;
}
function clamp(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
function cosineSimilarity(a: number[], b: number[]): number {
if (!a.length || a.length !== b.length) return 0;
let dot = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
if (normA === 0 || normB === 0) return 0;
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
export function faceFingerprintSimilarity(a: number[], b: number[]): number {
return cosineSimilarity(a, b);
}
function normalizeVector(vector: number[]): number[] {
let norm = 0;
for (const value of vector) {
norm += value * value;
}
norm = Math.sqrt(norm) || 1;
return vector.map((value) => value / norm);
}
function sampleGridLuminance(
data: Uint8ClampedArray,
imageWidth: number,
imageHeight: number,
box: PixelFaceBox,
): number[] {
const grid = 6;
const result: number[] = [];
const startX = clamp(Math.floor(box.x), 0, imageWidth - 1);
const startY = clamp(Math.floor(box.y), 0, imageHeight - 1);
const width = clamp(Math.floor(box.width), 1, imageWidth - startX);
const height = clamp(Math.floor(box.height), 1, imageHeight - startY);
for (let gy = 0; gy < grid; gy++) {
for (let gx = 0; gx < grid; gx++) {
const cellStartX = startX + Math.floor((gx * width) / grid);
const cellEndX = startX + Math.floor(((gx + 1) * width) / grid);
const cellStartY = startY + Math.floor((gy * height) / grid);
const cellEndY = startY + Math.floor(((gy + 1) * height) / grid);
let lumSum = 0;
let samples = 0;
const step = 2;
for (let y = cellStartY; y < cellEndY; y += step) {
for (let x = cellStartX; x < cellEndX; x += step) {
const idx = (y * imageWidth + x) * 4;
const r = data[idx] / 255;
const g = data[idx + 1] / 255;
const b = data[idx + 2] / 255;
lumSum += 0.2126 * r + 0.7152 * g + 0.0722 * b;
samples++;
}
}
result.push(samples ? lumSum / samples : 0);
}
}
return result;
}
export function extractFaceAnalysis(
imageData: ImageData,
box: PixelFaceBox,
): FaceAnalysis {
const data = imageData.data;
const imageWidth = imageData.width;
const imageHeight = imageData.height;
const startX = clamp(Math.floor(box.x), 0, imageWidth - 1);
const startY = clamp(Math.floor(box.y), 0, imageHeight - 1);
const width = clamp(Math.floor(box.width), 1, imageWidth - startX);
const height = clamp(Math.floor(box.height), 1, imageHeight - startY);
let rSum = 0;
let gSum = 0;
let bSum = 0;
let lSum = 0;
let lSquared = 0;
let samples = 0;
let edgeX = 0;
let edgeY = 0;
for (let y = startY; y < startY + height; y += 2) {
for (let x = startX; x < startX + width; x += 2) {
const idx = (y * imageWidth + x) * 4;
const r = data[idx] / 255;
const g = data[idx + 1] / 255;
const b = data[idx + 2] / 255;
const l = 0.2126 * r + 0.7152 * g + 0.0722 * b;
rSum += r;
gSum += g;
bSum += b;
lSum += l;
lSquared += l * l;
samples++;
if (x + 2 < startX + width) {
const idxX = (y * imageWidth + (x + 2)) * 4;
const lX =
0.2126 * (data[idxX] / 255) +
0.7152 * (data[idxX + 1] / 255) +
0.0722 * (data[idxX + 2] / 255);
edgeX += Math.abs(lX - l);
}
if (y + 2 < startY + height) {
const idxY = ((y + 2) * imageWidth + x) * 4;
const lY =
0.2126 * (data[idxY] / 255) +
0.7152 * (data[idxY + 1] / 255) +
0.0722 * (data[idxY + 2] / 255);
edgeY += Math.abs(lY - l);
}
}
}
const count = Math.max(samples, 1);
const meanR = rSum / count;
const meanG = gSum / count;
const meanB = bSum / count;
const meanL = lSum / count;
const varianceL = Math.max(0, lSquared / count - meanL * meanL);
const stdL = Math.sqrt(varianceL);
const gridLuma = sampleGridLuminance(data, imageWidth, imageHeight, {
x: startX,
y: startY,
width,
height,
});
const ratio = width / Math.max(height, 1);
const rawFingerprint = [
...gridLuma,
meanR,
meanG,
meanB,
stdL,
edgeX / count,
edgeY / count,
ratio,
clamp(width / imageWidth, 0, 1),
clamp(height / imageHeight, 0, 1),
];
return {
box: {x: startX, y: startY, width, height},
fingerprint: normalizeVector(rawFingerprint),
brightness: meanL,
sharpness: (edgeX + edgeY) / count,
warmth: meanR - meanB,
};
}
export function identifyFaceMatch(
fingerprint: number[],
profiles: StoredUserProfile[],
threshold = 0.94,
): FaceMatchResult | null {
let best: FaceMatchResult | null = null;
for (const profile of profiles) {
const prints = profile.facePrints ?? [];
for (const print of prints) {
if (!print.length || print.length !== fingerprint.length) continue;
const similarity = cosineSimilarity(print, fingerprint);
if (!best || similarity > best.similarity) {
best = {profile, similarity};
}
}
}
if (!best || best.similarity < threshold) {
return null;
}
return best;
}
export function describeFaceAnalysis(analysis: FaceAnalysis): string {
const lighting =
analysis.brightness > 0.62
? 'well-lit face'
: analysis.brightness > 0.38
? 'balanced lighting'
: 'low-light face';
const detail =
analysis.sharpness > 0.18
? 'high detail'
: analysis.sharpness > 0.1
? 'normal detail'
: 'soft detail';
const tone =
analysis.warmth > 0.05
? 'warm color profile'
: analysis.warmth < -0.05
? 'cool color profile'
: 'neutral color profile';
return `${lighting}, ${detail}, ${tone}`;
}

Xet Storage Details

Size:
6.32 kB
·
Xet hash:
8df80b93ca835882837f468de56fde8f0aa46f847affab656be5874420585db9

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.