Skip to content
←Return to Projects
Case Study

AR Smart Mirror

Real-time interactive AR virtual try-on system designed for retail fashion.

The Problem

In-store clothing fitting is slow, creates long queues in fitting rooms, and increases garment wear. Retailers need an instant, hygienic, and engaging digital mirror that lets shoppers visualize outfits without undressing.

My Role

Built solo with AI-assisted development — computer vision pipeline design, MediaPipe pose detection integration, Canvas AR overlay engine, FastAPI backend services, Docker multi-container topology, and Cloudflare Zero Trust ingress.

Highlights

  • Sub-30ms real-time body tracking and landmark extraction using MediaPipe Pose
  • Dynamic Canvas AR garment warping and affine transformation for realistic clothing drape
  • FastAPI backend with PostgreSQL catalog and Redis session pub/sub synchronization
  • Mobile controller pairing allowing customers to browse catalogs and trigger try-ons from their smartphones
  • Production-grade Docker Compose architecture with Nginx reverse proxy and Cloudflare Tunnel

Stack

Python 3.11FastAPIMediaPipeHTML5 CanvasPostgreSQL 15Redis 7DockerNginx

Constraints

  • Hard real-time latency target (<33ms per frame / 30 FPS) to avoid motion sickness and visual lag.
  • Zero customer biometric persistence — video feeds must process in memory without saving raw footage to disk.
  • Compatibility with standard consumer hardware (e.g. RTX 3060) without requiring specialized depth cameras.

System Architecture

Capture & Client
Webcam Feed
MediaPipe Pose (Client A)
Mobile Controller (Client B)
Edge & Ingress
Cloudflare Tunnel
Nginx Reverse Proxy
SSL Termination
Application Services
FastAPI Gateway (:8000)
Garment Overlay Service
WebSocket Pub/Sub
Data & Storage
PostgreSQL 15 (Catalog/Users)
Redis 7 (Session Sync)
Local Volume Storage
Telemetry
Prometheus
Grafana Monitoring (:3000)

Key Trade-offs

The decisions worth defending — what I chose, what I turned down, and why.

Pose tracking execution model

Chose

Client-side MediaPipe Pose on Canvas API with GPU acceleration

Rejected

Server-side video streaming and frame-by-frame inference

Server inference introduces network jitter, high bandwidth overhead, and privacy concerns streaming raw camera feeds over the network.

Garment deformation approach

Chose

Real-time 2D affine mesh deformation keyed to skeletal anchor landmarks

Rejected

Full 3D physics-based volumetric cloth simulation

3D cloth simulation cannot sustain 30+ FPS on edge kiosk hardware; 2D mesh triangulation provides fluid response with low computational cost.

Remote store interconnect

Chose

Cloudflare Tunnel over outbound HTTPS

Rejected

Direct port forwarding and dynamic DNS on store routers

Retail store Wi-Fi and ISP firewalls prevent inbound port forwarding; Cloudflare Tunnels punch through NAT securely without public IPs.

What I'd Do Differently

An honest retrospective — the stuff I'd change with more time, more users, or a second pass.

  1. 1Implement WebGPU shaders for cloth lighting and shadow blending to improve realism under dynamic store lighting.
  2. 2Add an offline fallback queue for store analytics so telemetry buffers locally during internet interruptions.
  3. 3Optimize landmark smoothing filters (OneEuro filter) to eliminate micro-jitter when shoppers stand at edge distances.

Computer Vision & Pose Tracking Shell

Benchmark the real-time AR try-on pipeline (status, camera init, pose detect, mesh align -shirt, latency bench).

icelegends-interactive-shell ~ [armirror]
History: ↑ / ↓
cv-engine@ar-mirror:~/pipeline$

Technical Deep-Dive

Architecture, specifications, and implementation details.

19 — Try-On Pipeline Architecture

#Overview

The AR Mirror try-on system processes three independent inputs in parallel and fuses them into a single photorealistic try-on result. The three streams are:

StreamInputPurpose
AReference body modelBuild a 3D cloth template
BTarget cloth imageProvide the garment texture
CTarget human (live camera)Provide the actual body to dress
┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│ Reference Body   │     │  Target Cloth    │     │  Target Human    │
│ Model (3D mesh)  │     │  (product photo) │     │  (live camera)   │
└────────┬─────────┘     └────────┬─────────┘     └────────┬─────────┘
         │                        │                         │
         ▼                        │                         ▼
┌──────────────────┐              │              ┌──────────────────────┐
│ Reference Mask   │◄─────────────┘              │ Semantic Segmentation│
│ Generation       │                             │ (body-part labelling)│
└────────┬─────────┘                             └──────────┬───────────┘
         │ Ref. Mask                                        │ Human
         ▼                                                  │ Segmentation
┌──────────────────┐                                        │
│  2D Cloth        │◄── Target Cloth ──────────────────────►│
│  Matching        │                                        │
└────────┬─────────┘                             ┌──────────▼───────────┐
         │ Cloth (2D matched)                    │ 2D Pose Estimation   │
         ▼                                       │ (landmark detection) │
┌──────────────────┐                             └──────────┬───────────┘
│  3D Cloth        │                                        │ 2D joints
│  Reconstruction  │                             ┌──────────▼───────────┐
└────────┬─────────┘                             │ 3D Pose & Shape      │
         │ 3D Cloth Model                        │ Estimation           │
         │                                       └──────────┬───────────┘
         │                                                  │ 3D pose & shape
         │                              ┌───────────────────┤
         ▼                              ▼                   ▼
┌──────────────────────────┐  ┌──────────────────┐  ┌──────────────────────┐
│  3D Shape Transfer &     │  │ Target Segment.  │  │ Target Skin Parts    │
│  Re-posing               │  │ Generation       │  │ Generation           │
│  (cloth warped to body)  │  └────────┬─────────┘  └──────────┬───────────┘
└────────────┬─────────────┘           │ Target seg.            │ Skin parts
             │ 3D warped cloth         │                        │
             └─────────────────────────┴────────────────────────┘
                                       │
                             ┌─────────▼─────────┐
                             │   Try-On Fusion    │
                             │   (compositing)    │
                             └─────────┬─────────┘
                                       │
                             ┌─────────▼─────────┐
                             │   Try-On Result   │
                             └───────────────────┘

#Stage-by-Stage Breakdown

##Stream A — 3D Cloth Model

Stage A1: Reference Mask Generation

  • Input: Reference body model (parametric 3D mesh, e.g. SMPL)
  • Output: Ref. Mask — a silhouette image encoding body-part regions
  • Purpose: Provides a canonical shape template so the cloth matching stage knows where shoulders, torso, and hips are in normalised body space.

Stage A2: 2D Cloth Matching

  • Input: Ref. Mask + Target Cloth photo (product image)
  • Output: Cloth (2D matched) — the garment warped onto the reference silhouette
  • Purpose: Aligns the flat garment photo with the reference body outline so that seams, collar, and hem land on the correct body-part boundaries.

Stage A3: 3D Cloth Reconstruction

  • Input: 2D matched cloth + reference body geometry
  • Output: 3D Cloth Model — a textured mesh of the garment draped on the reference body
  • Purpose: Lifts the 2D aligned garment into 3D so it can be re-posed to match the target person's actual body pose and shape.

##Stream B — Target Human Analysis

Stage B1: 2D Pose Estimation

  • Input: Target human frame (live camera or static image)
  • Output: 2D joints — a set of 2D keypoints (skeleton landmarks)
  • Current implementation: MediaPipe Pose, 33-point model, 30 fps
  • Key landmarks used: shoulders [11,12], hips [23,24], nose [0]

Stage B2: 3D Pose & Shape Estimation

  • Input: 2D joints + human image context
  • Output: 3D pose & shape — a full SMPL-family body mesh fitted to the person
  • Purpose: Recovers the person's body volume (not just 2D silhouette), enabling the cloth to wrap correctly around shoulders, chest depth, and waist.
  • Current implementation: Approximated by bilinear quad-mesh deformation anchored to 4 body-derived control points (collar-left, collar-right, hem-left, hem-right).

##Stream C — Semantic Understanding

Stage C1: Semantic Segmentation

  • Input: Target human frame
  • Output: Human Segmentation — per-pixel body-part labels (head, torso, left-arm, right-arm, legs, background, etc.)
  • Current implementation: MediaPipe segmentation mask (binary person/background)

Stage C2: Target Segmentation Generation

  • Input: Human Segmentation + 3D pose & shape
  • Output: Target Segmentation — refined pixel mask aligned to the predicted 3D body
  • Purpose: Produces precise garment region boundaries (where cloth should start/end) accounting for body pose, so the fusion stage doesn't bleed cloth onto the face or hands.

Stage C3: Target Skin Parts Generation

  • Input: Target Segmentation
  • Output: Target Skin Parts — isolated face, neck, hand regions (RGBA patches)
  • Purpose: These skin patches are composited on top of the warped cloth in the fusion stage so that hands and face always appear in front of the garment.

##Fusion — Try-On Fusion

Stage F1: 3D Shape Transfer & Re-posing

  • Input: 3D Cloth Model (Stream A) + 3D pose & shape (Stream B)
  • Output: 3D Warped Cloth — the reference cloth re-posed to the target person's body
  • Purpose: Physically warps the cloth mesh to match the target's skeletal pose and body shape, producing correct fold deformations, sleeve length, and torso width.

Stage F2: Try-On Fusion

  • Input: 3D Warped Cloth + Target Segmentation + Target Skin Parts
  • Output: Try-On Result — final composite frame
  • Steps:
    1. Render 3D warped cloth into the camera view using the estimated camera parameters
    2. Mask the rendered cloth to the torso segmentation region
    3. Composite target skin parts (face, hands) on top of the cloth layer
    4. Blend with the original background

#Current vs. Target Implementation

Pipeline StageTarget (full pipeline)Current Implementation
Pose estimation2D → 3D SMPL fittingMediaPipe 33-pt 2D landmarks
Cloth matchingReference mask + 2D warpingrembg + 800×860px canvas normalisation
3D reconstructionParametric mesh (SMPL+D)None — 2D quad only
Body segmentationSemantic (14 body parts)MediaPipe binary person mask (enableSegmentation:true) — used per-frame
Cloth warping3D mesh re-posing2D canvas overlay clipped to body silhouette via mask
Skin compositingPer-region RGBA patchLandmark capsules (face, neck, forearms) intersected with segmentation mask
FusionNeural rendererLayered canvases: video → garment (mask-clipped) → skin (mask-clipped)

#Garment Image Requirements

For the current pipeline to produce good results:

PropertyRequirement
FormatPNG with transparent background
Resolution800 × 860 px (enforced by upload pipeline)
Aspect ratio~0.93 : 1 (portrait, shoulder-to-hem)
BackgroundFully transparent (alpha = 0)
SubjectFlat-lay, ghost mannequin, or product shot
Shoulder positionTop 15–25 % of image height

Images uploaded via /admin.html are automatically processed:

  1. Background removed by rembg (U2Net model)
  2. Transparent margins trimmed (2 % padding kept)
  3. Resized and centred on an 800 × 860 transparent canvas

#Key Constants (three_engine.js v61)

ConstantValueMeaning
SEGMENTS16Grid subdivisions → 289 vertices
COLLAR_FRAC0.65Collar lifted 65 % of nose-to-shoulder above shoulders
COLLAR_EXTRA_W0.25Collar extends 25 % of shoulder span beyond each shoulder
HEM_EXTRA_W0.40Hem extends 40 % of hip span beyond each hip
HEM_EXTRA_H0.30Hem drops 30 % of body length below hip landmarks
LERP_POS0.18Collar corner tracking smoothing factor
LERP_SIZE0.10Hem corner tracking smoothing factor
minDetectionConfidence0.5MediaPipe pose detection threshold
minTrackingConfidence0.5MediaPipe pose tracking threshold

#Comparison with nawodyaishan/ar-fashion-tryon

Reference repo: https://github.com/nawodyaishan/ar-fashion-tryon (audited at commit on main, 2026-05-15)

This section documents why the current garment looks the way it does and how the reference repo approaches the same problem, based on direct code inspection — not just the README. The repo is a final-year project with five separate services in one monorepo; AR Mirror is a single product. The comparison below maps service to service.

##Why the garment looks "wrong" today

Every garment in this project is a flat 2D PNG warped onto a 16×16 quad mesh between four MediaPipe-derived anchors (collar-left, collar-right, hem-left, hem-right). Direct consequences:

Visible artefactRoot cause in the data model
Sleeves missing / arms clip through clothGarment region is a torso rectangle, no sleeve geometry
No realistic folds, drape, or shadingBilinear interpolation cannot model cloth physics
Flat chest, no shoulder roundnessNo depth — quad lives at z = 0
Hands appear behind garment when crossing itNo per-pixel skin-part compositing (Stage C3 not implemented)
Side / 3⁄4 turns shear the textureSingle front-only PNG, no view-dependent texture
Slim-fit vs oversized look identicalGarment width is derived from body span, not cloth shape

The 800 × 860 px upload canvas (see 09_Garment_Processing_Pipeline.md) bakes the "front-facing 2D PNG" assumption into the DB. Changing the visible structure of the garment therefore requires changing the asset structure first.

##Repository layout of ar-fashion-tryon (what's actually in it)

FolderRoleStack
web-frontend/Next.js 15 client (try-on UI, gallery, settings)Next.js, TS, Tailwind, shadcn/ui, react-rnd
web-backend/NestJS scaffoldTS, Nest — mostly placeholder controllers
ml-backend/Pose & garment ML APIFastAPI, MediaPipe, YOLO, abstract pipeline framework
image-extraction-backend/Upload + classify + bg-removalFastAPI, TensorFlow/Keras, rembg, Cloudinary
catvton-gradio/Photo-realistic VTONGradio + GPU + Stable Diffusion + CatVTON + DensePose (Detectron2)
vton-api-notebook/Jupyter notebook server for VTONOne-off

The densepose/ and detectron2/ folders inside catvton-gradio/ are vendored in full (~150 source files) — this is not a slim dependency, it's the whole Facebook AI library copy-pasted in.

##Architecture comparison

AspectAR Mirror (this project)nawodyaishan/ar-fashion-tryon
Form factorRetail mirror + phone controllerPersonal browser try-on (single user, single device)
DeploymentOn-premises (Docker Compose, 6 services, no cloud)Cloud — Railway for FastAPI, HuggingFace Space for Gradio, Cloudinary for assets
FrontendVanilla JS + HTML5 Canvas + Three.jsNext.js 15 + TypeScript + Tailwind + shadcn/ui
Backend(s)One FastAPI serviceFour services (Nest, two FastAPIs, one Gradio)
DatabasePostgreSQL + RedisNone — Cloudinary is the store
Session modelMirror ↔ phone over WebSocket, paired by QRNone — single browser session, local zustand store
Real-time engineMediaPipe Pose (33 pts) on the mirror, in-browser, WebGL quad meshMediaPipe Pose on backend, static_image_mode=True, model_complexity=2, one snapshot at a time
Offline try-onNot presentCatVTON diffusion (50 steps, 768×1024) on GPU
Body understanding5 landmarks (nose, shoulders, hips)33 landmarks + DensePose (per-pixel body parts) + SCHP (clothing segmentation)
Garment classificationManual type field at admin uploadTensorFlow/Keras CNN (clothing_model_final.h5) → upper / lower / full + confidence
Latency target< 100 ms render, 30 fps"Capture-then-process" — seconds per frame for CatVTON, no fps target for AR mode
Failed earlier approachn/aml-backend/previous codes/3D approch 1.py — PyTorch3D + DepthAnythingV2 attempt to generate a 3D garment mesh from a 2D photo; abandoned

##Garment structure comparison — the key finding

Both projects represent the garment as a 2D RGBA PNG with a removed background. Neither uses a true 3D mesh, neither rigs the garment to a skeleton, neither stores per-vertex weights or cloth-physics parameters.

Garment attributeAR Mirrorar-fashion-tryon
Asset formatPNG, 800 × 860 RGBAPNG RGBA, no fixed canvas; Cloudinary auto-formats
Background removalrembg / U2Net (one pass)rembg / U2NET (in image-extraction-backend/services/image_processing.py)
ClassificationManual at admin upload (DB field)TF/Keras CNN at upload, returns label + confidence
StorageLocal Docker volume /app/garments/Cloudinary folders: /originals/, /cutouts/, /tryon_results/, /outfits/
Real-time geometry16 × 16 bilinear quad mesh, 4 pose-derived corners, auto-tracking 30 fpsNone — react-rnd widget; the garment is a plain <img> inside a draggable / resizable / rotatable box; user manually positions it (web-frontend/components/tryon/GarmentOverlay.tsx)
Pose driving the overlayYes (every frame)No — pose only provides a shoulderY value used as a magnetic snap target when the user drags the image near it (15-px threshold)
Photo-realistic modeNot presentCatVTON diffusion re-renders the whole person — the garment is never positioned at all, it's regenerated as part of the output image
Multi-viewFront onlyFront only
Cloth-physics simulationNoNo in AR mode; faked by the diffusion model in CatVTON mode (no actual physics, but the network has learned to produce plausible folds)

The reference repo's better-looking screenshots come not from a different garment structure, but from a second, offline pipeline. In CatVTON mode the flat PNG plus the person photo plus DensePose + SCHP masks are fed to a Stable-Diffusion-inpainting model, which synthesises a photo-realistic image of the person wearing the garment. That is image generation, not garment rendering — it takes seconds per frame on a GPU and is not real-time.

In its AR (real-time) mode, ar-fashion-tryon is actually less automatic than AR Mirror: the user must drag the garment onto themselves with a mouse, because there is no pose-driven mesh deformation; the only "smart" behaviour is a 15-pixel magnetic snap to the detected shoulder line.

##What this means for "I want a different garment structure"

If by "different structure" you mean the cloth wraps the body better — that is solvable inside the existing 2D PNG asset format. If you mean the asset itself is no longer a flat picture — that requires replacing both the upload pipeline and the renderer.

Four concrete paths, in increasing cost:

  1. Stay 2D, add semantic regions to the existing PNG asset. Render sleeves, collar, hem, side seams as separate rules anchored to elbow [13,14], nose [0], knee [25,26] respectively. Asset stays one PNG; only the renderer changes. Fixes: sleeves, hem fall, collar tracking. Does not fix: drape, occlusion, depth. Cost: renderer rewrite only.

  2. Add segmentation-based skin compositing. Implement Stage C3 (Target Skin Parts) from section above using MediaPipe Selfie Segmentation (already shipped with MediaPipe Pose, just turn enable_segmentation=True on). Composite hands/face over the garment. Asset format unchanged. Fixes: hands no longer disappear behind cloth. Cost: one extra mask blit per frame.

  3. Adopt the reference repo's approach for hero shots only — run a CatVTON-style diffusion model server-side for a "save my outfit" button, keeping the real-time quad-mesh for live preview. Requires a GPU on-prem (incompatible with the current on-premises CPU constraint) or accepting a cloud dependency. The reference repo demonstrates that this is the only path to photo-realistic output today.

  4. True 3D garment per item — glTF/GLB mesh with a skeleton rig per SKU, driven by MediaPipe pose. The reference repo tried this in ml-backend/previous codes/3D approch 1.py (PyTorch3D + DepthAnythingV2 to lift a single product photo into a 3D mesh) and abandoned it — the file sits in a previous codes/ quarantine. The realistic version of this requires per-garment 3D authoring, which breaks the "admin uploads a product photo" workflow.

Option 1 is the cheapest visible improvement to garment shape. Option 2 is the cheapest fix for the "hand-behind-cloth" artefact. Option 3 is the only path to photo-realism, at the cost of GPU and latency. Option 4 is structurally the "real" answer but is unproven — the reference repo's own attempt did not ship.

##Implementation log

DateChangeFilesNotes
2026-05-15Option 2 shipped — segmentation-mask compositingfrontend/AR-mirror/app.jsenableSegmentation:true was already on but results.segmentationMask was unused. Added maskBuffer + updatePersonMask(). Garment is now painted into garmentBuffer, clipped via destination-in against the mask, then composited onto cvOv. Skin patches in drawSkinParts() are intersected with the same mask at the end of the function so capsule edges follow real arm silhouette. Cost: one extra drawImage of the mask per frame, ~0.3 ms on the target hardware.
2026-05-15Realism stack on top of Option 2 — five cheap passes layered into drawGarmentCanvas2D and drawSkinPartsfrontend/AR-mirror/app.jsApproximates the cues a diffusion model produces, in real time on CPU. See "Realism stack" below for the per-pass breakdown.
2026-05-15Temporal mask EMA — silhouette no longer shimmersfrontend/AR-mirror/app.jsMediaPipe's per-frame segmentation jitters by 1-2 px at the edges. updatePersonMask now blends current + previous smoothed mask using globalCompositeOperation = 'lighter' with globalAlpha = MASK_EMA_CURR (0.6) + 1 - MASK_EMA_CURR (0.4), producing a true weighted average. Adds currMaskBuffer and prevMaskBuffer (the smoothed result feeds back as prev next frame). Settles in ~3 frames (~100 ms at 30 fps), imperceptible lag, kills the cloth-edge shimmer.

##Realism stack (added 2026-05-15)

Five passes layered on top of the segmentation-mask base. None require new dependencies or a GPU; total added cost is ~1 ms per frame.

#PassWhereWhat it fakesHow
1Edge featherdrawGarmentCanvas2D step 5Soft fabric edge instead of pixel-binary mask cutctx.filter = 'blur(2px)' applied only at the moment of the destination-in mask draw
2Collar shadowdrawGarmentCanvas2D step 2Natural occlusion shadow at necklineDark-to-clear vertical gradient over the top 12 % of the garment, composited with source-atop
3Body-lean shadingdrawGarmentCanvas2D step 3Side-lighting that responds to body rotationHorizontal gradient driven by (ls.z - rs.z). Smoothed (LERP 0.15) in gSmooth.leanZ to avoid flicker. Max opacity 0.32
4Hem fadedrawGarmentCanvas2D step 4Cloth bottom blending with body / bottoms instead of a hard linedestination-out alpha gradient over bottom 8 % of garment
5Z-aware skin visibilitydrawSkinParts armInFront()Arms behind the body don't get a skin patch drawn over the garmentSkip a forearm capsule when wrist.visibility < 0.4 or ((elbow.z + wrist.z)/2 - shoulder.z) > 0.18

Why this combination matters: the reference repo nawodyaishan/ar-fashion-tryon produces realistic results only via the offline CatVTON diffusion pipeline (seconds per frame, GPU-bound). The five passes above approximate the most-visible cues that pipeline learns — silhouette-aware edges, collar occlusion, side shading, hem blending, correct front/back depth ordering — without any ML inference beyond the already-running MediaPipe Pose. They are not photo-realistic, but together they cross the threshold from "PNG taped to a body" to "cloth that responds to lighting and pose."

Tuning constants (current values, in drawGarmentCanvas2D)

ConstantValuePass
collarShadowHdH * 0.12Collar shadow band height
Collar gradient max alpha0.38Collar shadow darkness
Lean Z threshold0.04Below this, no body-lean shading
Lean intensity cap0.32Max darkness of body-lean gradient
Lean LERP factor0.15Smoothing on gSmooth.leanZ
hemFadeHdH * 0.08Hem fade band height
Hem fade max alpha0.55Hem fade strength
Mask blur2 pxEdge feather radius
Z-behind threshold0.18Hide forearm if (avg(elbow,wrist).z - shoulder.z) > this
Wrist visibility floor0.4Hide forearm if MediaPipe is unsure
~ End of Document ~