A face-aware halftone art generator built for Raspberry Pi 5.
Operator controls the Pi camera from a local console. Users scan a QR code, photograph themselves, and receive stunning halftone art on the main screen.
| Feature | Detail |
|---|---|
| User Upload Page | Clean mobile-friendly page — upload a photo or take one with the device camera |
| Live Camera Capture | Real-time camera preview in browser; capture → review → send in one tap |
| QR Code Sharing | Operator info panel shows a permanent QR code — phones scan it to open the upload page instantly |
| Pi Camera Integration | Operator can trigger the Raspberry Pi Camera Module 3 directly from the console |
| Face-Aware Processing | OpenCV detects faces; crops + optimises halftone settings automatically |
| Halftone Engine | Circle, Square, Diamond, and Line dot shapes with style presets |
| Fullscreen Output | Generated halftone fills the screen; click anywhere to return |
| Inbox | All user uploads and Pi camera shots appear in the operator inbox |
| LAN Access | Server binds to 0.0.0.0 — any device on the same Wi-Fi can access it |
| Output Resolution | Up to 6 000 × 6 000 px PNG + JPEG saved to Desktop |
- Python 3.11+
- A modern browser (Chrome / Firefox / Safari — required for
getUserMedia) - Raspberry Pi 5 with Camera Module 3 (for Pi Camera capture)
flask>=3.0.0
opencv-python>=4.9.0
Pillow>=10.3.0
numpy>=1.26.0
Werkzeug>=3.0.0
git clone <repo-url> pidots
cd pidotspython -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux / Raspberry Pi
source .venv/bin/activatepip install -r requirements.txtpython app.pyStartup output:
============================================================
[*] Halftone Studio – Face-Aware Halftone Generator
============================================================
Local : http://127.0.0.1:5001
Network: http://192.168.1.42:5001
Output: ~/Desktop/Halftone Studio Output
============================================================
| URL | Purpose |
|---|---|
http://127.0.0.1:5001/ |
User upload page (local) |
http://127.0.0.1:5001/operator |
Operator console |
http://<LAN-IP>:5001/ |
User page for phones on same Wi-Fi |
- Open
/operatoron the Raspberry Pi monitor. - The "How it works" info panel on the right shows a QR code auto-generated from the server's LAN IP.
- Any phone on the same Wi-Fi scans it → opens the user upload page.
- User takes a photo (live camera) or uploads a file → photo lands in the Inbox.
- Operator selects it → clicks Generate Halftone → result fills the screen.
The QR code is rendered once on page load and never changes while the server is running.
Scan QR → User uploads photo → Photo appears in Inbox
→ Operator selects photo → Generate Halftone → Fullscreen art display
- Camera button in the topbar opens the Pi Camera drawer to capture directly.
- Inbox button shows all received photos (filter: All / Uploads / Camera).
- Click anywhere on the fullscreen result to return to the console.
pidots/
│
├── app.py Flask routes + face pipeline + _get_local_ip()
├── config.py Central config (HOST, PORT, paths, defaults)
├── requirements.txt Python dependencies
├── README.md This file
│
├── halftone/
│ ├── __init__.py Public API
│ ├── engine.py Halftone rendering (OpenCV + NumPy)
│ ├── auto_settings.py Face-aware parameter computation
│ ├── filters.py Style pre-filters & CMYK splitter
│ └── utils.py Image I/O, validation, helpers
│
├── templates/
│ ├── index.html User upload page (camera + file upload tabs)
│ └── operator.html Operator console (QR panel, inbox, camera drawer)
│
├── static/
│ ├── css/
│ │ ├── style.css Shared scrapbook design system
│ │ └── operator.css Operator-specific styles
│ └── js/
│ ├── app.js User page: live camera, capture, upload logic
│ └── operator.js Operator: inbox polling, generation, fullscreen
│
├── uploads/ Received photos (auto-created)
├── temp/ Processing scratch space (auto-created)
└── Halftone Studio Output/ Generated artwork (on Desktop, auto-created)
| Setting | Default | Description |
|---|---|---|
HOST |
0.0.0.0 |
Bind to all interfaces (LAN visible) |
PORT |
5001 |
Server port |
DEBUG |
True |
Flask debug mode |
MAX_CONTENT_LENGTH |
50 MB |
Max upload size |
DEFAULT_DOT_SIZE |
8 |
Halftone dot radius |
DEFAULT_SPACING |
10 |
Grid cell size |
MAX_OUTPUT_RESOLUTION |
6000 |
Max output px (long axis) |
ANTIALIAS_SCALE |
2 |
Super-sampling factor |
JPEG_QUALITY |
92 |
JPEG encode quality |
sudo apt update && sudo apt install -y python3-pip python3-venv libopencv-dev rpicam-apps
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtpython app.pyOpen Chromium → http://127.0.0.1:5001/operator
Click Camera in the topbar → Take Photo to capture with Pi Camera Module 3.
# /etc/systemd/system/pidots.service
[Unit]
Description=pidots Halftone Studio
After=network.target
[Service]
WorkingDirectory=/home/pi/pidots
ExecStart=/home/pi/pidots/.venv/bin/python app.py
Restart=always
User=pi
[Install]
WantedBy=multi-user.targetsudo systemctl enable pidots
sudo systemctl start pidots| Problem | Solution |
|---|---|
ModuleNotFoundError: cv2 |
pip install opencv-python |
ModuleNotFoundError: flask |
pip install flask |
| Port 5001 already in use | Change PORT in config.py |
| Other devices can't connect | Allow Python through Windows Firewall |
| Camera says "permission denied" | Allow camera access in browser settings |
| Camera says "already in use" | Close other apps using the camera |
| QR code shows "unavailable" | Check server is running on 0.0.0.0 |
| Upload fails | Check uploads/ folder exists and is writable |
MIT — free to use, modify, and distribute.


