v2.0 introduces Docker containerization, a bridge/container architecture, browser-based live stream, and production-grade code quality. The original bare-metal version is preserved as v1.0.
- What changed in v2.0
- Architecture
- Repository structure
- Quick start — build directly on the Pi
- Cross-build on PC then deploy to Pi
- PC development mode
- Viewing the live stream
- Hardware test
- Configuration reference
- Troubleshooting
| Area | v1.0 | v2.0 |
|---|---|---|
| Dependencies | Installed globally on host OS | Isolated in Docker container |
| Camera access | Direct in main script | bridge.py on host, streamed over HTTP |
| GPIO | Direct in main script | bridge.py on host, commanded via REST |
| Display | cv2.imshow() — requires monitor |
MJPEG stream at http://<pi-ip>:5001/ |
| Firebase | Initialized every call — crashes | Initialized once, background thread pool |
| ThingSpeak | Called every frame — rate-limit failures | Rate-limit guard, fire-and-forget |
| Blocking | sleep() on main thread |
Non-blocking everywhere |
| Config | Magic strings scattered in files | Single config.py |
| PC dev | Not possible | main_pc.py + USB webcam mock |
The core insight: picamera2, gpiozero, and libcamera are compiled against the host OS Python 3.13 and cannot run inside a Python 3.11 container. Rather than fighting this, we split responsibilities cleanly:
┌────────────────────────────────────────────────────────────────┐
│ Raspberry Pi 4 │
│ │
│ HOST (Python 3.13) DOCKER (Python 3.11) │
│ ┌─────────────────────┐ ┌──────────────────────────┐ │
│ │ bridge.py │ │ main.py │ │
│ │ │ MJPEG │ │ │
│ │ picamera2 ─────────┼────────▶│ stream reader │ │
│ │ IR sensor ─────────┼────────▶│ face detection │ │
│ │ │ │ face verification │ │
│ │ LED ◀──────────────┼─────────┤ emotion analysis │ │
│ │ Buzzer ◀───────────┼─────────┤ eye state model │ │
│ │ LCD ◀──────────────┼─ JSON ──┤ OpenCV overlay │ │
│ │ lightLED ◀─────────┼─────────┤ Firebase upload │ │
│ │ blueLED ◀──────────┼─────────┤ ThingSpeak logging │ │
│ └─────────────────────┘ └──────────┬───────────────┘ │
│ │ MJPEG :5001 │
└─────────────────────────────────────────────┼──────────────────┘
▼
Browser / Phone
http://<pi-ip>:5001/
| Direction | Protocol | Endpoint | Content |
|---|---|---|---|
| bridge → container | HTTP MJPEG | GET :5000/stream |
Raw camera frames |
| bridge → container | HTTP JSON | GET :5000/ir_status |
IR sensor state |
| container → bridge | HTTP JSON | POST :5000/command |
AI results + LCD text |
| container → browser | HTTP MJPEG | GET :5001/stream |
Annotated frames |
Driver-Monitoring-System/
│
├── host/ ← runs on Pi HOST (Python 3.13, no Docker)
│ ├── bridge.py
│ ├── LCD.py
│ ├── buzzer.py
│ └── test_hardware.py
│
├── container/ ← runs INSIDE Docker container (Python 3.11)
│ ├── main.py
│ ├── debugmain.py
│ ├── connect.py
│ └── config.py
│
├── pc/ ← PC development mock (no Pi hardware)
│ ├── main_pc.py
│ └── Dockerfile.pc
│
├── docker/ ← Docker build files
│ ├── Dockerfile.pi
│ └── requirements_pi.txt
│ └── requirements.txt
│
├── models/ ← AI models and data files
│ ├── eye_state_model.h5
│ ├── embeddings.txt
│ ├── haarcascade_frontalface_default.xml
│ └── shape_predictor_68_face_landmarks.dat
│
├── .gitignore
└── README.md
Recommended if you have a Pi 4 with 4 GB RAM. We confirmed this works with 2 GB RAM too — it just takes longer.
# On the Pi
sudo apt-get update
sudo apt-get install -y docker.io python3-flask python3-picamera2 \
python3-gpiozero python3-smbus i2c-tools
sudo usermod -aG docker $USER
# Log out and back in, then verify:
docker run hello-worldgit clone https://github.com/AzizHrz/Driver-Monitoring-System.git
cd Driver-Monitoring-System# Place your Firebase service account file:
cp /path/to/serviceAccountKey.json .
# Edit config.py and set your ThingSpeak keys:
nano config.py
# → THINGSPEAK_CHANNEL_ID, THINGSPEAK_WRITE_KEY, THINGSPEAK_READ_KEYcp /path/to/your/photo1.jpg owner1.jpeg
cp /path/to/your/photo2.jpg owner2.jpeg
cp /path/to/your/photo3.jpg owner3.jpegdocker build -f Dockerfile.pi -t driver-monitor:pi .This installs TensorFlow 2.15, DeepFace, dlib-bin, and OpenCV. Expect 20–40 minutes on a Pi 4. Subsequent rebuilds are fast (Docker layer cache).
pip3 install flask --break-system-packagesTerminal 1 — bridge (host):
python3 bridge.pyTerminal 2 — container:
docker run --rm -it --privileged \
--device=/dev/i2c-1:/dev/i2c-1 \
-v $(pwd):/app \
-p 5001:5001 \
--network host \
driver-monitor:pi python3 main.pyOn any device on the same network:
http://<your-pi-ip>:5001/
Find your Pi IP:
hostname -IUse this if your Pi has 2 GB RAM and builds are too slow or fail, or if you want to iterate quickly on the AI code from your PC.
Building TensorFlow and dlib on a Pi 4 with 2 GB RAM takes 30–60 minutes and can run out of memory. Building on a PC with QEMU ARM64 emulation takes the same time but uses your PC's RAM (8–16 GB typically) — much more reliable.
# Install Docker with buildx support
docker buildx version # should print buildx version
# Enable QEMU ARM64 emulation
docker run --privileged --rm tonistiigi/binfmt --install arm64
# Verify:
ls /proc/sys/fs/binfmt_misc/ | grep aarch64docker buildx create --use --name pi-builder --platform linux/arm64
docker buildx inspect --bootstrap# Clone on PC
git clone https://github.com/AzizHrz/Driver-Monitoring-System.git
cd Driver-Monitoring-System
# Build — output to local Docker daemon as a .tar
docker buildx build \
--platform linux/arm64 \
-f Dockerfile.pi \
-t driver-monitor:pi \
--output type=docker \
.Expect 30–60 minutes on first build. The dlib C++ compilation is the longest step. Important: use
dlib-bininrequirements_pi.txt, notdlib— see Troubleshooting.
docker save driver-monitor:pi | gzip > driver-monitor-pi.tar.gz
ls -lh driver-monitor-pi.tar.gz # expect ~3–5 GB# Replace with your Pi's IP
rsync -avz --progress driver-monitor-pi.tar.gz rahma-pi@172.20.10.14:~/Or with scp:
scp driver-monitor-pi.tar.gz rahma-pi@172.20.10.14:~/# SSH into Pi
ssh rahma-pi@172.20.10.14
# Load image
docker load < driver-monitor-pi.tar.gz
# Verify
docker images | grep driver-monitorFollow Steps 2–7 from Quick start.
Develop and test the AI pipeline on your PC — no Pi hardware needed.
# Build PC image (x86_64)
docker build -f Dockerfile.pc -t driver-monitor:pc .
# Run with USB webcam
docker run --rm -it \
--device=/dev/video0:/dev/video0 \
-v $(pwd):/app \
-p 5001:5001 \
driver-monitor:pc python3 main_pc.pymain_pc.py replaces:
picamera2→cv2.VideoCapture(0)(USB webcam)gpiozero→ stub classes that print to console instead of driving GPIObridge.py→ not needed, runs as a single process
Once the system is running, open in any browser:
http://<pi-ip>:5001/ ← full page with annotated video
http://<pi-ip>:5001/stream ← raw MJPEG (works in VLC too)
http://<pi-ip>:5001/status ← JSON health check
The annotated stream shows:
- Coloured face bounding box: green=verified, orange=verifying, red=unknown, blue=no face
- State label above the face box
- Emotion label (happy, sad, angry, neutral…)
- Drowsy / eyes closed badge at the bottom
- Translucent status bar at the top with state + emotion
- Debug counters (frames received / annotated) — disable by setting
DEBUG=false
Test all GPIO components independently — no Docker needed:
python3 test_hardware.py
# Skip LCD if not wired:
python3 test_hardware.py --skip-lcdTests: LED blink × 5, lightLED blink × 5, blueLED blink × 5, buzzer beep × 3, LCD two-line message, IR sensor state.
All constants live in config.py. Key ones to change for your setup:
# ThingSpeak (or set as env vars)
THINGSPEAK_CHANNEL_ID = "YOUR_CHANNEL_ID"
THINGSPEAK_WRITE_KEY = "YOUR_WRITE_KEY"
THINGSPEAK_READ_KEY = "YOUR_READ_KEY"
# Firebase (or set FIREBASE_CRED env var)
FIREBASE_CRED_PATH = "/app/serviceAccountKey.json"
FIREBASE_DB_URL = "https://your-project.firebaseio.com"
# GPIO pins (BCM)
PIN_LED = 14
PIN_BUZZER = 18
PIN_LIGHT_LED = 23
PIN_BLUE_LED = 24
PIN_IR = 25
# AI tuning
VERIFY_THRESHOLD = 0.5 # cosine similarity for face verification
DROWSY_FRAME_THRESHOLD = 3 # consecutive closed-eye frames → drowsy alert
AI_EVERY_N_FRAMES = 5 # run DeepFace every N frames (CPU throttle)docker run ... \
-e FIREBASE_CRED=/app/serviceAccountKey.json \
-e FIREBASE_DB_URL=https://your-project.firebaseio.com \
-e TS_CHANNEL_ID=123456 \
-e TS_WRITE_KEY=XXXXXXXX \
-e TS_READ_KEY=YYYYYYYY \
-e DEBUG=false \
driver-monitor:pi python3 main.pyCause: _libcamera.cpython-313-aarch64-linux-gnu.so is compiled for Python 3.13 (host) but the container runs Python 3.11. The .so binary is ABI-incompatible — renaming it does not work.
Fix: This is exactly why v2.0 uses bridge.py on the host. The container never imports libcamera or picamera2. Make sure you are running bridge.py on the host and main.py inside the container.
Cause: Building dlib from source under QEMU ARM64 emulation fails at the LTO linker step — it spawns 128 parallel jobs that exhaust emulated memory/stack.
Fix: Use dlib-bin==19.24.6 in requirements_pi.txt instead of dlib. It is a pre-compiled wheel that skips all C++ compilation.
# requirements_pi.txt — use this:
dlib-bin==19.24.6
# NOT this:
dlib==19.24.4
Cause: numpy C extensions need BLAS. The system library is missing on the Pi host.
Fix:
sudo apt-get install -y libopenblas0-pthread libblas3Cause: ThingSpeak free tier enforces a minimum 15-second interval between writes per channel. Writing every frame causes all writes after the first to fail silently.
Fix: Already handled in v2.0 connect.py via _last_write rate-limit guard. If you still see failures, check that THINGSPEAK_WRITE_KEY is correct in config.py.
Cause: Inside the container, the working directory is /app. The credential file must exist at /app/serviceAccountKey.json — which means it must be present in the folder you mount as -v $(pwd):/app.
Fix:
ls $(pwd)/serviceAccountKey.json # must exist before running docker runChecklist:
- Is
bridge.pyrunning on the host?curl http://localhost:5000/health - Are frames arriving in the container?
curl http://localhost:5001/status→ checkframes_received - Is the annotation loop running? → check
frames_annotatedin the same response - Is the Pi camera enabled?
sudo raspi-config→ Interface Options → Camera → Enable
Cause: cv2.imshow() was called inside the container — it needs an X11 display which doesn't exist in a headless container.
Fix: v2.0 removes all cv2.imshow() calls. The annotated output is served via MJPEG on port 5001. If you see this error, you are running an old version of main.py.
