Skip to content
All work

2025· mechatronics· embedded· frontend· ai

Face-Recognition Door Access

A door lock that opens to a face, controlled from a browser anywhere — recognition running on the device, not in a cloud.

Scope

Sole engineer. Component selection, embedded Python on the Raspberry Pi, the Flask control API, the React control app, and the 3D-printed enclosure. Final-year B.Eng project, Afe Babalola University, supervised by Engr. Dr. A. Akinwumi.

Screens

  • The finished rig, mounted and running. One fixed camera at one height in a real doorway — the conditions every number further down this page was measured in.
  • Idle. The display and the three LEDs report what the Pi is doing without anyone reaching for a phone, and the button under them is the local trigger — the path that still works with the internet down.
  • The remote half. Recognition, enrolment, lock, alarm and the MJPEG feed on one screen, talking to the Flask API on the Pi through a tunnel.
  • The relay in the middle is the reason this diagram exists: a 12V solenoid cannot be driven from a 3.3V GPIO pin, so the Pi switches it rather than powering it.
  • Fusion 360, before printing. The cutouts are sized to parts the budget had already fixed — the camera, the display and the board were bought before the box was drawn.

Problem

Keys and passcodes can be copied, shared or stolen, and none of them leave a record of who came through the door. The published Raspberry Pi attempts at solving this split into two camps: those running recognition on hardware too weak for it — ESP32-CAM boards that could not hold real time — and those with adequate compute but no remote control path at all. Nobody had both, and both were the requirement.

Constraints

  • A single Raspberry Pi 4 as the only compute. Recognition had to run on-device — no cloud inference budget and no appetite for shipping faces off the box.
  • ₦350,000 total budget, which fixed the camera, the lock and the board before any code was written.
  • The door had to work with the internet down. A house whose lock depends on connectivity is worse than a key.
  • 12V/0.35A solenoid against 3.3V GPIO — the actuator could not be driven directly and needed relay isolation.
  • One fixed camera, no controlled lighting: the same doorway in Nigerian afternoon sun and in a dim hallway.
  • Frontend on Vercel, backend on a Raspberry Pi behind a tunnel — a cross-origin split with a device that has no stable public address.

Decisions

Two trigger paths into one recognition function

A physical button on the enclosure and a remote API endpoint both call the same internal routine. The local path needs no network at all, so a dropped connection degrades the system to exactly what it was before — a door you walk up to — rather than to a locked box.

Recognition on-device, at a 0.40 distance threshold

128-dimensional encodings compared locally rather than sent to a hosted model. That removes network round-trip from the critical path, removes a privacy problem, and removes an availability dependency. The threshold is deliberately tight — the consequences of the two error directions are not symmetric.

Enrollment averages several images into one encoding

Rather than storing every enrollment photo and matching against all of them, the encodings are averaged into a single vector per person. It costs some per-image precision and buys robustness across the lighting the doorway actually sees.

MJPEG in a plain <img> tag for the live feed

WebRTC would have been lower latency and would also have required a signalling server, TURN fallback and a pile of client state. A multipart JPEG stream renders in an ordinary image tag with no client-side code at all. For deciding whether to open a door, the latency difference does not matter; the complexity difference does.

Auto-lock as a deadline watched by a background thread

The unlock handler sets an expiry timestamp and returns immediately; a separate thread re-engages the lock when the deadline passes. Sleeping inside the request would have held the response open for five seconds and, worse, would have left the door open permanently if the request died mid-sleep.

Trade-offs

Gave up

20% of authorized attempts fail in low light and have to be retried

Bought

Recognition that never leaves the device — no cloud latency, no per-request cost, and no face data in anyone else's datacentre

Gave up

One in four authorized attempts rejected on first try at the 0.40 threshold

Bought

A bias toward refusing the right person over admitting the wrong one. For a lock, a retry is an annoyance and a false accept is a break-in.

Gave up

Bandwidth, and no audio channel

Bought

A live video feed that worked in a single <img> tag with zero client-side streaming code

Gave up

GPIO interrupt handlers had to be torn down and re-registered around every actuation

Bought

A button that could not double-fire mid-unlock. This was the bug that took longest to find: two threads, one hardware resource, and a failure that only appeared under the multi-worker API setup.

Results

Face match

1.3 s

on-device, Raspberry Pi 4

Remote unlock

800 ms

end-to-end, browser to bolt

Recognition — daylight

47 / 50

Recognition — low light

40 / 50

Unknown correctly refused

29 / 30

False accepts

2 of 20

Small sample and a prototype threshold. This is the number that says the system is not production-ready, and it is the first thing I would attack.

False rejects

5 of 20

Deliberate — the direction the threshold was tuned toward

Lock actuation

50+ cycles, 0 failures

Total project cost

₦350,000

Ratios are given as raw counts rather than percentages. A percentage implies a precision that twenty attempts do not have.

What I got wrong

  • Credentials were written to Firestore in plaintext alongside Firebase Auth, which was already hashing and storing them properly. Two sources of truth for a password is one too many, and the weaker one sets the security of the system. It should never have been written.
  • There is no liveness check. A printed photograph held up to the camera passes. Single-camera face recognition without depth or blink detection is an access convenience, not an access control, and the report should have said so more plainly than it did.

Stack

  • React
  • Tailwind CSS
  • Framer Motion
  • Firebase Auth
  • Cloud Firestore
  • Vercel
  • Python
  • Flask
  • OpenCV
  • face_recognition
  • Raspberry Pi 4
  • Fusion 360