โšกToolZip
๋ธ”๋กœ๊ทธ๐Ÿ› ๏ธ ๋„๊ตฌ ์‚ฌ์šฉํ•˜๊ธฐ
โ† Blog
๐Ÿ”ง utilOctober 20, 2026

How We Run AI Face Detection Entirely in the Browser (No Server Required)

#javascript#ai#webdev#privacy

How We Run AI Face Detection Entirely in the Browser (No Server Required)

When we built the face blur tool for ToolZip, we made a deliberate choice: no image ever leaves the user's device. Everything runs client-side, including the AI inference.

Here's how we pulled it off.


The Problem with Server-Side Processing

The conventional approach for image AI features is:

  1. Upload image to server
  2. Run inference
  3. Return result

This works fine technically, but it comes with trade-offs:

  • Privacy: users must trust you with their photos
  • Cost: inference compute isn't free at scale
  • Latency: upload + process + download round-trip
  • Compliance: GDPR, data residency concerns

For a face blur tool specifically, the privacy angle matters a lot. People blur faces when they don't want the content to be seen โ€” making them upload to a server feels contradictory.


Enter TensorFlow.js

TensorFlow.js lets you run ML models directly in the browser using WebGL for GPU acceleration. The models we care about:

  • MediaPipe Face Detection โ€” lightweight, optimized for real-time use
  • BlazeFace โ€” ultra-fast face detection (sub-millisecond on modern hardware)

Both are available as npm packages (@mediapipe/face_detection, @tensorflow-models/blazeface) and can run entirely client-side.


Implementation Overview

1. Load the Model

import * as faceDetection from '@tensorflow-models/face-detection';

const model = faceDetection.SupportedModels.MediaPipeFaceDetector;
const detectorConfig = {
  runtime: 'mediapipe',
  solutionPath: 'https://cdn.jsdelivr.net/npm/@mediapipe/face_detection'
};

const detector = await faceDetection.createDetector(model, detectorConfig);

The first load downloads the model weights (~3MB). After that, it's cached by the browser.

2. Detect Faces

const image = document.getElementById('input-image');
const faces = await detector.detectFaces(image);

// Each face includes:
// { box: { xMin, yMin, width, height }, keypoints: [...] }

3. Apply Blur to Detected Regions

function blurFaceRegions(canvas, faces, blurIntensity = 15) {
  const ctx = canvas.getContext('2d');
  
  faces.forEach(face => {
    const { xMin, yMin, width, height } = face.box;
    
    // Add padding around detected face
    const padding = Math.floor(width * 0.15);
    const x = Math.max(0, xMin - padding);
    const y = Math.max(0, yMin - padding);
    const w = Math.min(canvas.width - x, width + padding * 2);
    const h = Math.min(canvas.height - y, height + padding * 2);
    
    // Save current state
    ctx.save();
    
    // Clip to face region
    ctx.beginPath();
    ctx.rect(x, y, w, h);
    ctx.clip();
    
    // Apply blur via CSS filter
    ctx.filter = `blur(${blurIntensity}px)`;
    ctx.drawImage(canvas, 0, 0);
    
    ctx.restore();
  });
}

The CSS filter: blur() approach works well for rectangular regions. For a more sophisticated oval blur (following face shape), you'd use a radial gradient mask instead.


Performance Considerations

MediaPipe Face Detector is designed for real-time video (30fps+), so for static images it's practically instant. But there are a few gotchas:

Model initialization time: The first inference takes ~300-500ms while TF.js warms up WebGL. Subsequent calls are much faster.

// Warm up the model with a small dummy tensor
const warmupTensor = tf.zeros([1, 100, 100, 3]);
await detector.estimateFaces(warmupTensor);
warmupTensor.dispose();

Large images: Performance degrades with very large images. We resize inputs to max 1920px before detection, then scale the bounding boxes back up.

const MAX_DETECTION_SIZE = 1920;

function prepareForDetection(img) {
  const scale = Math.min(1, MAX_DETECTION_SIZE / Math.max(img.width, img.height));
  
  const canvas = document.createElement('canvas');
  canvas.width = img.width * scale;
  canvas.height = img.height * scale;
  canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
  
  return { canvas, scale };
}

What About False Positives and Misses?

No face detector is perfect. Common failure cases:

  • Partial faces: faces at the edge of frame, heavily occluded
  • Extreme angles: faces turned more than ~45 degrees from frontal
  • Small faces: tiny in the frame (less than ~60px)
  • Unusual lighting: very dark, backlit, strong shadows

For a blur tool, false negatives (missed faces) are more problematic than false positives (blurring something that isn't a face). So we err on the side of a slightly larger bounding box and offer manual adjustment.


The Privacy Architecture

The full data flow for our tool:

User selects image โ†’ FileReader API (local) โ†’ 
Canvas element (local) โ†’ TF.js inference (local GPU) โ†’ 
Canvas manipulation (local) โ†’ Download result (local)

Zero network requests involved in the actual processing. The only external call is the one-time model download, which gets cached.


Browser Support

WebGL (required for TF.js GPU acceleration) is supported by 97%+ of browsers as of 2024. For the ~3% without WebGL, TF.js falls back to CPU via WASM or pure JS โ€” slower but functional.


Try It

If you want to see this in action without setting anything up: ToolZip Image Face Blur โ€” runs entirely in your browser, no account needed.

The complete source approach described here is what we use in production. Give it a try and let me know if you have questions about any part of the implementation.


Tags: #javascript #ai #webdev #privacy

๐Ÿ› ๏ธ
Try the tool now
No install ยท Free ยท No server upload
Open tool โ†’
How We Run AI Face Detection Entirely in the Browser (No Server Required) โ€” ToolZip | ToolZip