Skip to content

Repository files navigation

GlowCheck

GlowCheck is a research/demo tool for face and skin metrics. It analyzes a single facial photo and reports age, gender and emotion estimates (DeepFace/TensorFlow) together with objective skin measurements — ITA° skin tone with an approximate Fitzpatrick category, relative erythema (redness), and a geometric facial-asymmetry index. The same Python pipeline runs two ways: as a local CLI (detectarpiel.py, single image or whole folder with CSV export) and as a Firebase Cloud Function behind a mobile-first web frontend. It is aimed at developers and researchers exploring computer-vision skin/face metrics — it is not a medical device and its outputs are estimates, not diagnoses.

Key features

  • Demographics (DeepFace): age, gender (with confidence), dominant emotion and race estimates, using the RetinaFace detector by default.
  • Skin tone (ITA°): computed on segmented skin only (forehead + both cheeks, YCrCb skin mask), with median/IQR, per-region values, an approximate Fitzpatrick category (I–VI), and an honest low-confidence flag when lighting is uneven.
  • Erythema: relative redness as median CIELAB a* on the same segmented skin, per region.
  • Facial asymmetry: coarse geometric index from RetinaFace's 5 landmarks (nasal deviation, mouth offset, mouth cant), normalized by interocular distance, roll-invariant, with a frontality check that flags head-pose contamination.
  • Robust preprocessing: EXIF-aware loading; CLAHE lighting normalization is applied in memory for the detection models only, while color metrics always use the original pixels.
  • Batch mode + CSV: point the CLI at a folder to process every image with per-image error isolation and a consolidated skin_report.csv.
  • Web app: public/index.html (Spanish UI, mobile-first) sends the photo as base64 to the Cloud Function at /api/detect-age and renders the results. glowcheck.html is the standalone static UI mockup.

Ethics and limitations

  • Estimates, not facts. Age, gender, emotion and race outputs are model guesses with real error rates; treat them as approximate signals only. Inferring attributes like gender, race or emotion from a face is inherently sensitive — do not use this project for decisions about people (hiring, access, profiling, moderation, etc.).
  • Not medical. ITA/Fitzpatrick and erythema values are relative, uncalibrated screen-side metrics — not clinical instruments and not a substitute for dermatological assessment. Erythema (a*) is less reliable on darker skin; skin-tone readings need diffuse lighting (ideally a grey/color card) to be trustworthy, and the pipeline flags low-confidence cases.
  • Asymmetry is coarse. The 5-landmark index conflates head pose with true asymmetry on non-frontal faces (flagged via the frontality score); a dense-landmark model (e.g. MediaPipe 468 points) would be the upgrade.
  • No server-side image storage by design. The Cloud Function decodes the base64 image in memory, analyzes it, and returns JSON — it never writes the photo to storage or a database. The local CLI likewise processes images in place (debug overlays are written only when you pass --debug). Get informed consent before analyzing anyone else's photo.

Tech stack

  • Python 3.10 — analysis pipeline
  • DeepFace 0.0.96 / TensorFlow 2.20 — age, gender, emotion, race models
  • RetinaFace — face detection + 5-point landmarks
  • OpenCV + NumPy + Pillow — skin segmentation (YCrCb), CIELAB/ITA math, CLAHE, EXIF handling
  • Firebase — Cloud Functions (Python runtime) + Hosting, with a /api/detect-age rewrite
  • Vanilla HTML/CSS/JS — mobile-first frontend, no framework

Project structure

detectarpiel.py        # Local CLI pipeline (single image or folder batch -> CSV)
imagen1.jpg            # Sample input image (AI-generated face)
skin_report.csv        # Sample CSV output for imagen1.jpg
skin_debug/            # Sample --debug overlay (face box, ROIs, skin mask)
functions/             # Firebase Cloud Function (main.py = HTTP adaptation of the pipeline)
public/                # Deployed web frontend (calls /api/detect-age)
glowcheck.html         # Standalone static UI mockup
firebase.json          # Hosting + functions config (python312, /api/detect-age rewrite)

Quickstart — local CLI

python -m venv .venv
.venv\Scripts\activate        # Windows  (Linux/macOS: source .venv/bin/activate)
pip install -r requirements.txt

# Analyze the bundled sample image (writes a debug overlay to ./skin_debug/)
python detectarpiel.py imagen1.jpg --debug

# Analyze a whole folder -> <folder>/skin_report.csv
python detectarpiel.py path/to/folder

# Optional: choose a detector backend and JSON/CSV output
python detectarpiel.py imagen1.jpg retinaface --json --csv

Notes:

  • Python 3.10 is recommended (matches the Cloud Function runtime and the pinned TensorFlow build).
  • On first run DeepFace downloads its model weights (a few hundred MB) to ~/.deepface/ — the first analysis is slow, later runs are fast.
  • Facial asymmetry needs the default retinaface backend and a roughly frontal face.

Quickstart — Firebase deploy

npm install -g firebase-tools
firebase login

# Bind this folder to YOUR Firebase project (creates .firebaserc, see Configuration)
firebase use --add

# Deploy the Python Cloud Function + hosting
firebase deploy

After deploy, the hosted site (public/index.html) posts photos to /api/detect-age, which firebase.json rewrites to the detect_age function (2 GB memory, 60 s timeout, us-central1). You can also deploy pieces separately with firebase deploy --only functions or --only hosting.

Sample output

Running python detectarpiel.py imagen1.jpg --debug on the bundled sample (an AI-generated face) produces:

  • Console report per face: age/gender/emotion estimates, ITA° with IQR and per-region values, Fitzpatrick approximation, erythema a*, and the asymmetry breakdown with confidence flags.
  • skin_report.csv — the flat CSV row for imagen1.jpg (age 39, tone "tan" / Fitzpatrick ~IV, ITA 21.1°, flagged low-confidence due to uneven lighting).
  • skin_debug/imagen1_skin.png — debug overlay showing the face box (blue), the forehead/cheek ROIs (yellow) and the accepted skin pixels (green).

Configuration

The local CLI needs no configuration at all beyond pip install -r requirements.txt.

The deployed Cloud Function reads two optional environment variables — both are access controls, not secrets-to-obtain-from-a-vendor:

Variable What it is How to set it
APP_TOKEN Shared secret a caller must send in the X-App-Token header. If unset, the function accepts unauthenticated calls and logs a warning — fine locally, not for a public deployment. Pick a long random string; set it under Cloud Run → your function → Variables, or firebase functions:secrets:set. Put the same value in public/index.html (APP_TOKEN).
ALLOWED_ORIGINS Comma-separated CORS allowlist. Defaults to localhost dev ports — never *. Set it to your site origin, e.g. https://your-project.web.app.

Because public/index.html is served to the browser, the token in it is not a secret — it raises the cost of casual abuse, and App Check (below) is the real control. The only value you must supply to deploy is your Firebase project:

Value What it is Where to get it
Firebase project ID The project hosting the function + site. Stored in .firebaserc (gitignored here). Create a project at console.firebase.google.com, then run firebase use --add in the repo root.

Additional one-time setup:

  1. Enable billing (Blaze plan). Python Cloud Functions with 2 GB memory require it.
  2. firebase use --add — recreates .firebaserc bound to your project (this repo intentionally does not ship one).
  3. firebase deploy — deploys functions/ (runtime python312) and public/.

Cost controls

This function loads TensorFlow into 2 GB of memory on every cold start. An unauthenticated, uncapped version of it is a standing invitation to run up someone else's Cloud bill, so the following limits are enforced in code (functions/main.py) and ship enabled by default:

Control Value Why it bounds spend
max_instances 3 The hard ceiling. Sustained abuse can occupy at most 3 instances instead of fanning out to Cloud Run's default limit.
concurrency 1 One 2 GB analysis per instance; the gen2 default of 80 would OOM and multiply cost.
min_instances 0 Never pay for idle warm instances.
timeout_sec 60 A warm analysis takes seconds; a hung request cannot burn 120 s.
Payload cap 5 MB base64 Checked against Content-Length before the body is parsed, and again on the field before base64-decoding or touching a model.
Pixel budget 40 MP A byte cap is not a memory cap — a ~20 KB PNG can declare 30000×30000 and expand to gigabytes. Dimensions are read from the image header and rejected (413) before any pixels are materialised; PIL's own DecompressionBombError guard backs it up.
APP_TOKEN gate 401 Rejected before any model work happens.
Rate limit 5 burst, 1 per 6 s per IP Per-instance token bucket → 429, applied before the token check so token-guessing is throttled too. Keyed on the load balancer's appended peer, never the caller-supplied leftmost X-Forwarded-For (which an attacker can rotate per request). Trims scripted abuse only — the state is per-instance and dies on cold start.
CORS allowlist Never *. Note this is a browser control — it does nothing against curl; App Check below is the server-side one.
App Check enforced in code enforce_app_check=True on the function (override with ENFORCE_APP_CHECK=0 for local testing). The platform rejects callers without a valid attestation from your registered app before any billed work runs — the strongest control here, and it ships on by default.

Worst-case concurrent billed execution is therefore max_instances × concurrency = 3, each capped at 60 s.

Do these two things before you expose a real deployment:

  1. Set a budget alertconsole.cloud.google.com/billing → your billing account → Budgets & alertsCreate budget. Scope it to this project and set email alerts at 50 / 90 / 100 %. A budget alert notifies; it does not cap spend, which is exactly why max_instances is set in code. To hard-stop, add a Pub/Sub budget notification that disables billing.
  2. Register the app for Firebase App Check — enforcement is already ON in code, so you must complete the registration or your own site will be rejected too: console.firebase.google.comApp Check → register the web app with reCAPTCHA Enterprise, and initialise App Check in public/index.html. For local testing without it, set ENFORCE_APP_CHECK=0.

Engineering highlights

The computer-vision / ML project in this portfolio: one Python pipeline, shipped two ways.

Languages Python 3.12, JavaScript, HTML/CSS
ML / CV DeepFace + TensorFlow (age, gender, emotion), RetinaFace detection and 5-point landmarks, OpenCV and NumPy for all pixel work
Original algorithms Skin segmentation in YCrCb, ITA skin-tone with median/IQR and Fitzpatrick mapping, relative erythema as CIELAB a-star, and a roll-invariant facial-asymmetry index normalised by interocular distance
Signal honesty CLAHE normalisation is applied to the detector's copy only, never to the pixels used for colour metrics; low-confidence cases (uneven lighting, non-frontal pose) are flagged rather than silently reported
Deployment The same pipeline runs as a local CLI (single image or batch folder to CSV) and as a Firebase Cloud Function behind a mobile-first web frontend
Production hardening Instance caps, a pixel budget that rejects decompression bombs from the image header before decoding, request-size limits, token gate, rate limiting keyed on a non-spoofable peer, and App Check enforced in code

Skills demonstrated: computer vision, applied ML, colour-science maths, serverless deployment, cost and abuse modelling for expensive endpoints, and writing honestly about model limitations.

Disclaimer and terms of use

Read the Ethics and limitations section first — it is the important one. In addition:

  • Faces are sensitive personal data. In many jurisdictions facial analysis is regulated biometric processing (Chile's Ley 19.628; the GDPR treats biometric identification as a special category). Get informed, documented consent before analysing anyone's photo, and do not deploy this publicly without a privacy notice and a lawful basis.
  • Never use it to make decisions about people. Age, gender, emotion and especially race outputs are error-prone model guesses with known demographic bias. Using them for hiring, access control, pricing, profiling, moderation, or law enforcement is out of scope and actively discouraged.
  • Not a medical device. Skin-tone, erythema and asymmetry values are uncalibrated, screen-side metrics. They diagnose nothing and are not a substitute for a dermatologist.
  • Deploying it costs money. The function loads TensorFlow into 2 GB of memory on a pay-as-you-go plan. The controls in Cost controls ship enabled, but the bill is yours — set a budget alert and complete App Check registration before exposing a public URL.
  • Not affiliated with Google, Firebase, or the DeepFace / RetinaFace projects; those are used under their own licences.
  • No warranty. Provided "as is", without warranty of any kind (see LICENSE).

License

MIT — see LICENSE. Copyright (c) 2026 Diego Ostertag.

About

Computer-vision face and skin analysis: DeepFace/TensorFlow demographics plus original ITA skin-tone, erythema and facial-asymmetry metrics.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages