cd /news/computer-vision/dia-de-los-muertos-photo-booth · home › topics › computer-vision › article
[ARTICLE · art-159999] src=dev.to ↗ pub= topic=computer-vision verified=true sentiment=↑ positive

Día de los Muertos Photo Booth

A developer built Día de los Muertos Photo Booth, a small self-hosted Flask web app that detects faces locally with MediaPipe Face Mesh and alpha-composites themed Día de los Muertos adornments — sugar skulls, sombreros, marigold crowns, La Catrina veils and monarch butterflies — onto each face using facial landmarks. Themes are data-driven, defined by folders of transparent PNGs plus a theme.json describing anchoring, scaling, offset and rotation, so new themes can be added without code changes. The app processes uploads entirely in memory with no external API and nothing written to disk, and ships as a single-service Docker stack.

by read6 min views1 publishedOct 29, 2026

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Ghost in the Machine

Día de los Muertos Photo Booth is a tiny Flask web app that decorates every face in a photo with festive Día de los Muertos adornments — all on your own machine, with no cloud, no account, and no photo ever leaving your computer.

You upload one JPEG or PNG. The app detects the faces in the picture locally, randomly assigns each face one of eight Día de los Muertos themes — calavera (sugar-skull decoration + forehead marigold), sombrero (hat + mustache), flower_crown (cempasúchil marigold crown), catrina (La Catrina veil + stitched lips), papel_picado (decorative banner), marigold_path (petal overlay + crown), mariachi (mustache + bow tie + sombrero), and monarch (monarch butterfly) — and alpha-composites transparent PNG adornments onto each face using its facial landmarks. Each adornment is anchored to the face: hats, crowns, veils, the monarch butterfly, and the forehead marigold sit above the head; the calavera decoration frames the eyes; mustaches and stitched lips land on the mouth; the bow tie sits at the chin; and the papel picado and marigold-petal overlays cover the whole frame. Several themes layer multiple pieces (for example, mariachi combines a mustache, a bow tie, and a sombrero). Every adornment scales with each face and rotates to follow a tilted head, so the result tracks the real geometry of the photo. You then see the composed image in the browser and can download it in the same format you uploaded.

Who it's for: anyone who wants a fun, instant, private way to celebrate Día de los Muertos with a photo effect — families, classrooms, cultural events, or a self-hosted photo-booth kiosk. Because everything runs locally and in memory, it's a good fit for anyone who cares that their faces and family photos are never uploaded to a third-party service.

The privacy guarantee is structural, not a promise: there is no external API, nothing is written to disk, and every buffer is released in a try/finally block on every request path. Processing is fully in-memory and ephemeral.

The adornment catalog is fully data-driven: each theme is a folder of transparent PNGs plus a theme.json describing how each piece is anchored, scaled, offset, and rotated, so themes can be added, enriched, or swapped without touching a line of code.

Run it locally with Docker (see the run steps below) and open http://localhost:5000.

Pick a JPEG or PNG with one or more faces, click Create portrait, and the composed, adorned image appears in the result panel ready to download. No setup, seeding, or model download is required on first start — the app is ready the moment the container is up.

All code lives in desafio5/ and is deliberately small: a thin Flask layer over pure, independently testable domain modules, a vanilla-JS frontend, and a single-service Docker stack.

app.py — Flask app factory and routes. GET / renders the single-page upload UI; POST /process runs the full in-memory pipeline (validate → detect → assign themes → compose → encode) and streams the result back. Loads the adornment set once at startup, warms up the detection model, and enforces a 30 s processing budget.validation.py — decodes and validates the upload entirely in memory (10 MB limit, JPEG/PNG only, decodability, non-zero dimensions). Never touches disk.detection.py — the Detection_Engine wrapper around MediaPipe Face Mesh; locates up to 20 faces and derives the eye/mouth/head/chin landmarks used to anchor adornments.composition.py — the Composition_Engine: assigns a random theme per face and scales, rotates, and alpha-composites each PNG onto the photo while preserving the input dimensions.adornments.py — loads the Adornment_Set (PNG assets + per-theme theme.json) once at startup and encodes the result image back to bytes.templates/index.html — single-page upload interface (all labels in English).static/app.js + static/style.css — the fetch-based up and the responsive stylesheet.static/adornments/<theme>/ — the transparent PNG assets and placement metadata per theme.Dockerfile + docker-compose.yml — a single web service served by gunicorn.README.md — project documentation (features, themes, architecture, run and usage instructions). The whole image-processing pipeline runs on an open-source, fully local AI/vision stack. No request ever leaves the machine, and no model weights are downloaded at runtime — they are bundled into the Docker image at build time. Here is each open-source tool and the core image-processing job it does:

requirements.txt, and the app runs one throwaway inference at startup to warm the model graph so the first real upload isn't penalized by lazy initialization.opencv-python-headless, the server-friendly build with no GUI dependencies) provides the image-array handling and color/format conventions that MediaPipe builds on, so the decoded photo is presented to the detector in the array layout its inference graph expects. On top of that vision stack, Flask serves the single page and the processing endpoint, run under gunicorn inside the container, and the frontend is plain vanilla JavaScript that posts the file with fetch and renders the returned image blob.

The design decision I care most about is local-and-ephemeral by construction. Validation decodes the bytes in a memory buffer and never writes them; detection and composition operate on in-memory images; and the /process route releases every upload buffer and decoded image in a finally block on success, failure, and timeout alike. A face whose landmarks can't be computed is simply skipped while the other faces are still adorned, so a hard photo degrades gracefully instead of erroring out.

Building this on open-source local models instead of a hosted vision API changed what the project could be:

Open weights and on-device inference are what let a genuinely useful, face-aware AI effect live on cheap, private, offline hardware that anyone can own.

Before running the Docker commands below, make sure you have:

docker compose v2 No other local setup is needed. In particular, there is no model-pull step: unlike an Ollama-based app, the MediaPipe models are bundled into the image at build time, so nothing is downloaded at runtime.

From the desafio5/ directory, run these commands in order:

docker compose up --build

That's the complete sequence: docker compose up --build builds the image and starts the web service, which gunicorn serves on 0.0.0.0:5000 inside the container, published to the host as 5000:5000. The app is then reachable at http://localhost:5000.

To stop the app, press Ctrl+C in that terminal (or run docker compose down from the desafio5/ directory).

If no face is detected, or the file is not a valid JPEG/PNG within the size limit, the app shows a clear message and stays ready for another upload without re the page.

── more in #computer-vision 4 stories · sorted by recency
── more on @mediapipe 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/dia-de-los-muertos-p…] indexed:0 read:6min 2026-10-29 · —