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
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
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.
- 1Implement WebGPU shaders for cloth lighting and shadow blending to improve realism under dynamic store lighting.
- 2Add an offline fallback queue for store analytics so telemetry buffers locally during internet interruptions.
- 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).
Technical Deep-Dive
Architecture, specifications, and implementation details.
02 — API Contracts
#Base URL
http://localhost:8000/api/v1
https://ar-mirror.yourdomain.com/api/v1 (via Cloudflare Tunnel)
#Authentication
All REST endpoints (except /health) require:
Authorization: Bearer <jwt_token>
WebSocket endpoints use token or session parameters in the URL.
#WebSocket Contracts (Real-Time Control)
##WS /ws/session/{session_id}?role={mirror|mobile}
Establishes a persistent bidirectional connection for real-time control.
- role=mirror: The physical display receiving commands.
- role=mobile: The user's smartphone sending commands.
Mobile to Mirror Messages:
// User taps a garment on their phone
{ "action": "select_garment", "garment_id": "uuid" }
// User clears the current try-on
{ "action": "clear_garment" }
Server to Mirror Broadcasts:
// Instantly updates the AR display when mobile sends a command
{ "action": "render_garment", "garment_id": "uuid", "asset_url": "/assets/..." }
#Endpoints
##GET /health
Returns system status.
Response 200:
{ "status": "ok", "version": "1.0.0", "gpu": true }
##POST /session/start
Creates a new pose tracking session and generates pairing URL.
Request: { "device_id": "mirror-01", "store_id": "uuid" }
Response 201: {
"session_id": "uuid",
"pairing_url": "https://ar-mirror.yourdomain.com/mobile?session=uuid",
"expires_at": "ISO8601"
}
##DELETE /session/{id}
Ends session and frees resources.
Response 204: No Content
##GET /garments
Returns full garment catalog.
Response 200:
{ "garments": [{ "id":"uuid","name":"Urban Hoodie","type":"hoodie",
"color":"black","size_range":"S-XL","asset_url":"/assets/..." }] }
##GET /garments/{id}
Single garment details + PNG asset URL.
Response 200:
{ "id":"uuid","name":"Bomber Jacket","type":"jacket",
"asset_url":"/assets/garments/bomber-black.png","active":true }
##POST /garments (admin only)
Upload a new garment.
Content-Type: multipart/form-data
Fields: name, type, color, size_range, file (PNG)
Response 201: { "id": "uuid", "asset_url": "..." }
##POST /pose/process
Processes a video frame and returns pose landmarks.
Request:
{ "session_id":"uuid","frame_b64":"<base64>","timestamp":1704067200 }
Response 200:
{ "pose_detected": true,
"landmarks": [{"x":0.48,"y":0.31,"z":-0.12,"visibility":0.99}, ...] }
##POST /analytics/event
Logs a try-on interaction event.
Request: { "session_id":"uuid","garment_id":"uuid","duration_ms":4200 }
Response 202: { "logged": true }
##GET /analytics/summary
Returns try-on stats per garment.
Response 200:
{ "garments": [{ "id":"uuid","name":"...","try_ons":142,"conversions":38 }] }
##GET /admin/metrics
Prometheus-compatible metrics endpoint.
Response 200: text/plain (Prometheus format)
#Error Format
{ "error": "description", "code": "ERROR_CODE", "status": 400 }
#Rate Limits
| Endpoint | Limit |
|---|---|
| /pose/process | 30 req/s per session |
| /garments | 60 req/min per device |
| /analytics/event | 120 req/min per session |
| /admin/* | 10 req/min per API key |