Retrospective · 2026

VISION Studio

A computer vision suite running eleven analysis modes (objects, faces, poses, silhouette, hands, style transfer…) entirely in the browser. This page walks through the technical decisions, the problems I ran into, and what I took away from building it.

Starting point

The site positions me as an AI engineer. I wanted a recruiter to be able to open a real demonstration in one click, without installing anything, and form an opinion of the actual work rather than a list of skills.

The constraints I set for myself:

  • No backend. The hosting is shared IONOS, no GPU. Everything runs client-side.
  • No data transmitted. Uploaded videos and images stay in the user's tab.
  • No cookies, no trackers, no analytics.
  • Total budget below 300 MB for the heaviest models, loaded on demand.
  • Correct behaviour on a consumer laptop without a dedicated GPU.

Technical choices

Three runtimes rather than one

Each model tends to run better in one environment than another. I ended up using three runtimes in parallel:

  • TensorFlow.js for COCO-SSD (object detection), MoveNet (poses), MobileNet (scene classification), BodyPix (body segmentation) and face-api (faces and emotions). WebGL backend by default, WASM as a fallback.
  • ONNX Runtime Web for the four style transfer models (fast_neural_style: mosaic, pointillism, udnie) and AnimeGANv3_PortraitSketch. These models are more widely available as ONNX than as TF.js.
  • MediaPipe (Google) for hand tracking with 21 keypoints per hand. It's the only runtime that sustains 60 fps on mobile in my testing.

Downside: three APIs to know, three release cycles to follow. The upside is that each model runs on the runtime that suits it best.

WebGPU when available, WebGL by default

WebGPU speeds inference up by a factor of two to four on the larger models, but its support is uneven (recent Safari only, occasionally unstable on mobile). The implementation picks the best available backend and displays an indicator in the interface, so the user knows what they're running on.

Model hosting

Models were initially loaded from tfhub.dev. The service was deprecated in 2026 and its URLs became unstable. I migrated MoveNet and MobileNet to self-hosted copies on my own domain, and pulled the remaining ones from Kaggle Models. When Kaggle then blocked hotlinking, everything had to be re-hosted locally again. The main takeaway: on a solo project, it's safer to archive a copy of each model with its hash than to depend on any third-party CDN, even an official one.

Content Security Policy

Every new model adds a domain to whitelist in the Content Security Policy. Rather than a permissive global policy, I scoped the exceptions with an <If REQUEST_URI ~ m#/jeux/vision/#> block in the IONOS .htaccess. This scope only works if a Header unset Content-Security-Policy precedes the new rule, otherwise Apache concatenates the headers and the browser applies the intersection, blocking everything.

Grouping the eleven modes into five families

Eleven modes laid out flat made the interface unreadable. I grouped them by function:

  • Detection: objects, faces, poses, scenes, hands.
  • Segmentation: anatomy, silhouette with automatic background removal.
  • Extraction: OCR.
  • Transformation: style transfer, anonymising blur.
  • Comparison: two synchronised videos, or before/after split-screen.

What worked

  • The Silhouette mode (automatic greenscreen) produces a very clean result. BodyPix outputs a raw mask that becomes usable after temporal smoothing (moving majority over five frames) and morphological operations (dilate/erode). Four post-processing effects (cyan halo, glow, red neon, subject isolation) are then available.
  • Hand tracking with 21 keypoints per hand holds 60 fps thanks to MediaPipe. Multi-scale detection lets it follow a close or distant hand without adjustment. Up to four hands simultaneously.
  • The before/after split-screen with a draggable divider allows comparison of two synchronised instances of the same model. It's used, among other things, to compare COCO-SSD v1 and v2 side by side.
  • The category filter via checkboxes keeps the interface usable despite the 80 COCO-SSD classes and 1,000 ImageNet classes. ImageNet labels are translated to French.

What didn't work

Not every attempt succeeded. Mentioning them seems more useful than a purely positive account.

  • License plate detection and blurring. The goal was automatic anonymisation of videos for a GDPR-oriented use case. I went through around thirty approaches: YOLOv8n-plate in ONNX, full-image Tesseract OCR, classical vision with Sobel and morphology, candidate scoring, global learning from manually drawn zones. No approach reached the reliability required for automatic blurring. False positives (windshields, road signs) and false negatives (worn plates, sharp angles) stayed too frequent. I removed the mode rather than leave an unreliable feature in place that a user might rely on before publishing a video.
  • Semantic colorisation of black-and-white images. The model I tested produced visually unconvincing results on real images. The mode was removed before investing in the interface.
  • Style transfer via Magenta. TensorFlow's Magenta library relied on tfhub.dev, which was deprecated. I migrated to fast_neural_style in ONNX format, hosted on GitHub via media.githubusercontent.com, then AnimeGANv3 on HuggingFace. HuggingFace subsequently migrated its storage to Xet (cas-bridge.xethub.hf.co), which required another CSP adjustment.
  • face-api in production. The SsdMobilenetv1 detector calls eval() internally in TF.js, which the strict CSP was blocking. I added 'unsafe-eval' only for /jeux/vision/ and audited possible input paths to check that no injection could take advantage of that local allowance.

Takeaways

  1. Working on the model is a small part of the implementation. Loading COCO-SSD takes three lines; tracking an object across frames (IoU + light Kalman filter), filtering false positives and displaying twenty detections legibly without saturating the screen takes far more attention.
  2. Security, performance and compatibility are coupled. A strict CSP forbids eval(), which breaks some TF.js loading paths. WebGPU accelerates inference but stays unstable on mobile Safari. Every technical decision has a counterpart on the server-configuration side.
  3. The model → CDN → server → CSP chain is fragile. A migrating CDN, a forgotten Header unset, a mis-scoped CSP key: each link can interrupt the application. On a solo project, it pays to document each model URL with its hash and keep a local copy.
  4. Removing a feature that doesn't deliver on its promise is sometimes the best call. The plates mode represented about thirty hours of work spread across roughly thirty iterations. Keeping it in production would have hurt the credibility of the whole.
  5. Local execution is a genuine differentiator, especially on the French and European market. No video leaves the browser, which simplifies GDPR compliance and reassures users on a topic as sensitive as face recognition.

Numbers

  • Eleven modes shipped, one removed.
  • Six TF.js models, four ONNX models, three MediaPipe estimators, one Tesseract pipeline.
  • About 4,000 lines of JavaScript in the current version (vision-v18.js).
  • No backend, no cookies, no trackers.
  • About 300 MB of models loadable at most, on demand.
  • UI contrast meets WCAG AA (verified via in-browser measurement).
  • A/B split-screen synchronisation within ±33 ms.
Open VISION Contact me All projects