Back to all projects

DeepFace Access Control System

January 5, 2026
python·raspberry-pi·deepface·opencv·facial-recognition·iot·security·prd

Note: This is a documented technical plan and Product Requirements Document (PRD) - not an implemented project. It serves as a comprehensive blueprint for anyone looking to build a DIY facial recognition access control system.

Product Requirements Document

Facial Recognition Access Control System

DIY Implementation for 50-1000 Users


| Field | Value | |-------|-------| | Document Version | 1.0 | | Date | January 2026 | | Status | Draft | | Target Deployment | Q1-Q2 2026 |


Table of Contents

  1. Executive Summary
  2. Problem Statement
  3. Goals and Objectives
  4. User Stories and Use Cases
  5. Functional Requirements
  6. Non-Functional Requirements
  7. System Architecture
  8. Hardware Requirements
  9. Software Stack
  10. Implementation Plan
  11. Sample Code
  12. Project Directory Structure
  13. Project Timeline
  14. Budget Summary
  15. Risks and Mitigations
  16. Success Metrics
  17. Future Enhancements

1. Executive Summary

This document outlines the product requirements for a DIY facial recognition access control system designed for small to medium deployments (50-1,000 users). The system prioritizes cost-effectiveness, customizability, privacy (all processing on-premises), and integration flexibility.

1.1 Key Highlights

| Metric | Value | |--------|-------| | Hardware Cost | $150-400 total (one-time) | | Software Cost | $0 (open-source libraries) | | Recurring Cost | $0 (no cloud APIs) | | Recognition Accuracy | 97-99.8% (depending on model choice) | | Recognition Speed | 50-500ms per face | | Privacy | 100% on-premises, no data leaves the device |


2. Problem Statement

Organizations need secure, touchless access control that is affordable, customizable, and respects user privacy. Existing solutions present significant challenges:

2.1 Current Market Gaps

  1. Commercial systems are expensive: $1,000-2,500 per door plus ongoing fees
  2. Cloud-based APIs create dependencies: Recurring costs ($30-300/month), latency issues, internet dependency
  3. Privacy concerns: Biometric data sent to third-party servers
  4. Limited customization: Vendor lock-in, inability to integrate with custom workflows
  5. Physical credentials are insecure: Keys, cards, and PINs can be lost, stolen, or shared

3. Goals and Objectives

3.1 Primary Goals

  1. Deploy a functional facial recognition access control system for under $400
  2. Support 50-1,000 registered users with sub-second recognition
  3. Achieve 97%+ recognition accuracy under normal conditions
  4. Maintain 100% on-premises data processing
  5. Provide full customization through open-source software

3.2 Success Criteria

  • System operational within 2 weeks of project start
  • False acceptance rate (FAR) less than 0.1%
  • False rejection rate (FRR) less than 3%
  • System uptime greater than 99% during business hours
  • User enrollment time less than 30 seconds per person

4. User Stories and Use Cases

4.1 Primary Users

End Users (Employees/Members)

  • As an employee, I want to unlock the door by looking at the camera so that I don't need to carry keys or cards
  • As a member, I want the system to recognize me quickly so that I'm not delayed at entry points
  • As a user, I want a backup entry method (PIN/card) in case facial recognition fails

Administrators

  • As an admin, I want to enroll new users by capturing their face photo so that they can access the facility
  • As an admin, I want to revoke access instantly so that terminated employees cannot enter
  • As an admin, I want to view access logs so that I can audit who entered and when
  • As an admin, I want to set access schedules so that users can only enter during authorized hours

4.2 Use Cases

| Use Case | Description | Priority | |----------|-------------|----------| | UC-001 | User approaches door, face is recognized, door unlocks | P0 - Critical | | UC-002 | Admin enrolls new user with photo capture | P0 - Critical | | UC-003 | Unknown face detected, access denied, event logged | P0 - Critical | | UC-004 | Admin views access logs and generates reports | P1 - High | | UC-005 | User uses backup PIN when face recognition fails | P1 - High | | UC-006 | System sends alert on repeated failed attempts | P2 - Medium |


5. Functional Requirements

5.1 Face Detection and Recognition

  1. System shall detect faces in real-time video stream (minimum 15 FPS)
  2. System shall generate 128-512 dimensional face embeddings for each detected face
  3. System shall compare face embeddings against enrolled database using cosine similarity
  4. System shall support configurable similarity threshold (default: 0.6)
  5. System shall handle multiple detection backends: OpenCV, MTCNN, RetinaFace
  6. System shall support multiple recognition models: ArcFace, FaceNet, VGG-Face

5.2 User Management

  • Enroll users via photo capture (webcam) or image upload
  • Support multiple photos per user for improved accuracy
  • Store user metadata: name, employee ID, department, access level
  • Enable/disable user access without deleting profile
  • Bulk import users via CSV
  • Set access schedules per user or group

5.3 Access Control

  • Control door lock via GPIO relay module
  • Configurable unlock duration (default: 5 seconds)
  • Support backup authentication: PIN code, RFID card
  • Anti-passback protection (optional)
  • Manual override for emergency situations

5.4 Logging and Monitoring

  • Log all access attempts with timestamp, user ID, result
  • Store snapshots of unrecognized faces for review
  • Generate daily/weekly access reports
  • Alert on suspicious activity (multiple failed attempts)
  • Export logs to CSV/JSON

5.5 Administration Interface

  • Web-based dashboard accessible on local network
  • User management: add, edit, delete, search
  • Real-time recognition feed preview
  • System configuration: thresholds, timing, models
  • Access log viewer with filtering

6. Non-Functional Requirements

6.1 Performance

| Metric | Target | |--------|--------| | Face detection | Less than 100ms per frame | | Face recognition | Less than 500ms total (detection + embedding + matching) | | Database search | Less than 50ms for 1,000 users | | Door unlock response | Less than 1 second from face detection | | Web interface response | Less than 2 seconds |

6.2 Reliability

  • System uptime: greater than 99% during operating hours
  • Automatic recovery after power failure
  • Graceful degradation if camera fails (allow PIN/card fallback)
  • Database backup: daily automatic backup

6.3 Security

  • All data stored locally (no cloud transmission)
  • Face embeddings stored, not raw images (optional)
  • Admin interface requires authentication
  • HTTPS for web interface
  • Anti-spoofing: liveness detection (Phase 2)

6.4 Scalability

  • Support 50-1,000 users on single device
  • Support multiple doors with networked devices
  • Centralized user database for multi-door deployments

6.5 Environmental

  • Operating temperature: 0C to 40C
  • Indoor use only (or with weatherproof enclosure)
  • Adequate lighting: 200-1000 lux recommended

7. System Architecture

7.1 High-Level Architecture

+----------------------------------------------------------------+
|                        HARDWARE LAYER                           |
+---------------+---------------+---------------+-----------------+
|   Camera      |  Raspberry    |    Relay      |   Solenoid Lock |
|   Module      |    Pi 5       |   Module      |   (12V DC)      |
+---------------+---------------+---------------+-----------------+
                        |
+----------------------------------------------------------------+
|                      SOFTWARE LAYER                             |
+----------------------------------------------------------------+
|  +--------------+  +--------------+  +------------------------+ |
|  |   OpenCV     |  |  DeepFace    |  |    Flask/FastAPI       | |
|  |  (Capture)   |  | (Recognition)|  |    (Web Interface)     | |
|  +--------------+  +--------------+  +------------------------+ |
+----------------------------------------------------------------+
|  +--------------+  +--------------+  +------------------------+ |
|  |   SQLite     |  |   Pickle     |  |    RPi.GPIO            | |
|  |  (Metadata)  |  | (Embeddings) |  |    (Lock Control)      | |
|  +--------------+  +--------------+  +------------------------+ |
+----------------------------------------------------------------+

7.2 Data Flow

  1. Step 1: Camera captures video frame
  2. Step 2: Face detection identifies face region
  3. Step 3: Face alignment normalizes face orientation
  4. Step 4: Recognition model generates embedding vector
  5. Step 5: Embedding compared against database
  6. Step 6: If match found and authorized, GPIO triggers relay
  7. Step 7: Relay activates solenoid lock
  8. Step 8: Event logged to database

7.3 Database Schema

Users Table

CREATE TABLE users (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    employee_id     VARCHAR(50) UNIQUE NOT NULL,
    name            VARCHAR(100) NOT NULL,
    department      VARCHAR(100),
    access_level    INTEGER DEFAULT 1,
    is_active       BOOLEAN DEFAULT TRUE,
    pin_code        VARCHAR(6),
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME DEFAULT CURRENT_TIMESTAMP
);

Access Logs Table

CREATE TABLE access_logs (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER REFERENCES users(id),
    timestamp       DATETIME DEFAULT CURRENT_TIMESTAMP,
    door_id         VARCHAR(50),
    access_granted  BOOLEAN,
    method          VARCHAR(20),  -- 'face', 'pin', 'card', 'manual'
    confidence      FLOAT,
    snapshot_path   VARCHAR(255)
);

Access Schedules Table

CREATE TABLE access_schedules (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER REFERENCES users(id),
    day_of_week     INTEGER,  -- 0=Monday, 6=Sunday
    start_time      TIME,
    end_time        TIME
);

8. Hardware Requirements

8.1 Bill of Materials

| Component | Model | Est. Cost | Notes | |-----------|-------|-----------|-------| | Single Board Computer | Raspberry Pi 5 (8GB) | $80 | 4GB also works | | Camera Module | RPi Camera V3 | $25 | Or USB webcam | | MicroSD Card | 64GB Class 10 | $12 | 32GB minimum | | Relay Module | 5V Single Channel | $5 | Optocoupler isolated | | Solenoid Lock | 12V DC Electric | $15 | Or magnetic lock | | 12V Power Supply | 12V 2A Adapter | $10 | For solenoid lock | | 5V USB-C Power Supply | 5V 5A USB-C | $15 | For Raspberry Pi | | Enclosure + Misc | Case, wires, etc. | $30 | Jumper wires, box | | TOTAL | | $192 | |

8.2 Optional Components

| Component | Model | Est. Cost | Purpose | |-----------|-------|-----------|---------| | IR Illuminator | 850nm LED Array | $15 | Low-light operation | | Touchscreen | 7" HDMI Display | $50 | Local UI/enrollment | | RFID Reader | RC522 Module | $5 | Backup authentication | | Keypad | 4x4 Matrix | $5 | PIN backup | | UPS | Mini UPS for Pi | $25 | Power backup |

8.3 Wiring Diagram

Raspberry Pi GPIO Connections:
-----------------------------------------
  Pi GPIO 17 (Pin 11) ------> Relay IN
  Pi 5V      (Pin 2)  ------> Relay VCC
  Pi GND     (Pin 6)  ------> Relay GND

Relay to Solenoid Lock:
-----------------------------------------
  12V Adapter (+) ----------> Solenoid (+)
  12V Adapter (-) ----------> Relay COM
  Relay NO        ----------> Solenoid (-)

Camera Connection:
-----------------------------------------
  Camera ribbon cable ------> Pi CSI port
  (or USB webcam to USB port)

8.4 GPIO Pin Reference

| GPIO | Pin | Function | |------|-----|----------| | GPIO 17 | 11 | Relay Control (Lock) | | GPIO 27 | 13 | Status LED (Green) | | GPIO 22 | 15 | Status LED (Red) | | GPIO 23 | 16 | Door Sensor (Optional) | | GPIO 24 | 18 | Exit Button (Optional) |


9. Software Stack

9.1 Operating System

  • Raspberry Pi OS (64-bit) - Debian Bookworm based
  • Python 3.11+

9.2 Core Libraries

| Library | Version | Purpose | |---------|---------|---------| | deepface | 0.0.93+ | Face detection, recognition, embedding | | opencv-python | 4.9+ | Video capture, image processing | | Flask / FastAPI | 3.0+ / 0.110+ | Web interface and REST API | | RPi.GPIO / gpiozero | Latest | GPIO control for relay | | SQLite3 | Built-in | User metadata and access logs | | numpy | 1.26+ | Numerical operations, embedding storage | | pandas | 2.0+ | Data manipulation, log analysis | | Pillow | 10.0+ | Image processing |

9.3 Recommended Recognition Models

| Model | Accuracy (LFW) | Speed | Embedding Size | Recommendation | |-------|----------------|-------|----------------|----------------| | ArcFace | 99.41% | Medium | 512 | Best overall | | FaceNet512 | 99.65% | Slower | 512 | Highest accuracy | | FaceNet | 99.20% | Medium | 128 | Good balance | | VGG-Face | 98.78% | Fast | 4096 | Good for low-power | | Dlib | 99.38% | Fast | 128 | Good balance | | SFace | 99.60% | Fast | 128 | Lightweight |

9.4 Detection Backends

| Backend | Speed | Accuracy | Notes | |---------|-------|----------|-------| | RetinaFace | Slower | Excellent | Best for accuracy | | MTCNN | Medium | Very Good | Good balance | | OpenCV | Fast | Good | Best for speed | | SSD | Fast | Good | Lightweight | | Dlib | Medium | Good | CPU-friendly |


10. Implementation Plan

10.1 Phase 1: Hardware Setup (Days 1-2)

  1. Flash Raspberry Pi OS (64-bit) to SD card using Raspberry Pi Imager
  2. Enable SSH and configure WiFi in imager settings
  3. Boot Raspberry Pi and perform initial setup (updates, locale)
  4. Connect and test camera module (libcamera-hello)
  5. Wire relay module to GPIO (Pin 17 to IN, 5V to VCC, GND to GND)
  6. Connect solenoid lock through relay (12V supply to COM, solenoid to NO)
  7. Test relay switching with simple GPIO script

10.2 Phase 2: Software Environment (Days 2-3)

  1. Create Python virtual environment
  2. Install system dependencies (cmake, libatlas, libhdf5)
  3. Install Python packages: deepface, opencv-python, flask, RPi.GPIO
  4. Verify DeepFace installation by running test recognition
  5. Create project directory structure
  6. Initialize SQLite database with schema

10.3 Phase 3: Core Recognition System (Days 4-7)

  1. Implement video capture module (OpenCV VideoCapture)
  2. Implement face detection with configurable backend
  3. Implement face embedding generation using DeepFace.represent()
  4. Implement database search using DeepFace.find()
  5. Implement access decision logic with configurable threshold
  6. Implement GPIO lock control with timing
  7. Implement access logging to SQLite
  8. Create main recognition loop with threading

10.4 Phase 4: Administration Interface (Days 8-10)

  1. Set up Flask application structure
  2. Implement user enrollment endpoint (photo capture/upload)
  3. Implement user management CRUD endpoints
  4. Implement access log viewing endpoint
  5. Implement system configuration endpoint
  6. Build HTML templates for dashboard
  7. Add basic authentication for admin interface
  8. Implement live video feed preview

10.5 Phase 5: Testing and Deployment (Days 11-14)

  1. Enroll test users (minimum 10)
  2. Test recognition accuracy under various lighting conditions
  3. Test false rejection rate with enrolled users
  4. Test false acceptance rate with unknown faces
  5. Configure system as systemd service for auto-start
  6. Set up log rotation
  7. Configure automatic database backup
  8. Install and secure enclosure at entry point
  9. Document system and create user guide

11. Sample Code

11.1 Main Recognition Loop

# main.py - Core facial recognition access control
import cv2
import time
import sqlite3
from deepface import DeepFace
from gpiozero import OutputDevice
from datetime import datetime
import threading

# Configuration
CONFIG = {
    "database_path": "./face_database",
    "model_name": "ArcFace",
    "detector_backend": "retinaface",
    "distance_metric": "cosine",
    "threshold": 0.6,
    "unlock_duration": 5,
    "relay_pin": 17,
    "camera_id": 0
}

# Initialize GPIO
relay = OutputDevice(CONFIG["relay_pin"], active_high=False)

def unlock_door(duration=5):
    """Activate relay to unlock door for specified duration."""
    relay.on()
    time.sleep(duration)
    relay.off()

def log_access(user_id, granted, confidence, method="face"):
    """Log access attempt to SQLite database."""
    conn = sqlite3.connect("access_control.db")
    cursor = conn.cursor()
    cursor.execute("""
        INSERT INTO access_logs
        (user_id, timestamp, access_granted, method, confidence)
        VALUES (?, ?, ?, ?, ?)
    """, (user_id, datetime.now(), granted, method, confidence))
    conn.commit()
    conn.close()

def recognize_face(frame):
    """Attempt to recognize face in frame against database."""
    try:
        results = DeepFace.find(
            img_path=frame,
            db_path=CONFIG["database_path"],
            model_name=CONFIG["model_name"],
            detector_backend=CONFIG["detector_backend"],
            distance_metric=CONFIG["distance_metric"],
            enforce_detection=True,
            silent=True
        )

        if len(results) > 0 and len(results[0]) > 0:
            best_match = results[0].iloc[0]
            distance = best_match["distance"]
            identity = best_match["identity"]

            if distance < CONFIG["threshold"]:
                confidence = 1 - distance
                return True, identity, confidence

        return False, None, 0.0

    except Exception as e:
        print(f"Recognition error: {e}")
        return False, None, 0.0

def main_loop():
    """Main recognition loop."""
    cap = cv2.VideoCapture(CONFIG["camera_id"])
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

    last_recognition_time = 0
    cooldown_period = 3  # Prevent rapid re-recognition

    print("Facial Recognition Access Control Started")
    print("Press Ctrl+C to exit")

    try:
        while True:
            ret, frame = cap.read()
            if not ret:
                continue

            current_time = time.time()

            # Only attempt recognition after cooldown
            if current_time - last_recognition_time > cooldown_period:
                recognized, identity, confidence = recognize_face(frame)

                if recognized:
                    print(f"Access GRANTED: {identity} ({confidence:.2%})")
                    log_access(identity, True, confidence)

                    # Unlock door in separate thread
                    unlock_thread = threading.Thread(
                        target=unlock_door,
                        args=(CONFIG["unlock_duration"],)
                    )
                    unlock_thread.start()

                    last_recognition_time = current_time

            # Display frame (optional, for debugging)
            cv2.imshow("Access Control", frame)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

    except KeyboardInterrupt:
        print("\nShutting down...")
    finally:
        cap.release()
        cv2.destroyAllWindows()
        relay.close()

if __name__ == "__main__":
    main_loop()

11.2 User Enrollment Script

# enroll.py - User enrollment script
import cv2
import os
import sqlite3
from datetime import datetime

DATABASE_PATH = "./face_database"
DB_FILE = "access_control.db"

def init_database():
    """Initialize SQLite database with schema."""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            employee_id VARCHAR(50) UNIQUE NOT NULL,
            name VARCHAR(100) NOT NULL,
            department VARCHAR(100),
            access_level INTEGER DEFAULT 1,
            is_active BOOLEAN DEFAULT TRUE,
            pin_code VARCHAR(6),
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )
    """)

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS access_logs (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER REFERENCES users(id),
            timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
            door_id VARCHAR(50),
            access_granted BOOLEAN,
            method VARCHAR(20),
            confidence FLOAT,
            snapshot_path VARCHAR(255)
        )
    """)

    conn.commit()
    conn.close()

def enroll_user(employee_id, name, department="General"):
    """Capture photos and enroll a new user."""

    # Create user directory
    user_dir = f"{DATABASE_PATH}/{employee_id}_{name.replace(' ', '_')}"
    os.makedirs(user_dir, exist_ok=True)

    # Add to database
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()

    try:
        cursor.execute("""
            INSERT INTO users (employee_id, name, department)
            VALUES (?, ?, ?)
        """, (employee_id, name, department))
        conn.commit()
    except sqlite3.IntegrityError:
        print(f"User {employee_id} already exists!")
        conn.close()
        return False

    conn.close()

    # Capture photos
    cap = cv2.VideoCapture(0)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

    photo_count = 0

    print(f"\n{'='*50}")
    print(f"Enrolling: {name} ({employee_id})")
    print(f"{'='*50}")
    print("Instructions:")
    print("  - Press SPACE to capture a photo")
    print("  - Capture 3-5 photos from different angles")
    print("  - Press Q when done")
    print(f"{'='*50}\n")

    while True:
        ret, frame = cap.read()
        if not ret:
            continue

        # Draw instructions on frame
        display_frame = frame.copy()
        cv2.putText(display_frame, f"Photos: {photo_count}", (10, 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
        cv2.putText(display_frame, "SPACE=Capture, Q=Done", (10, 60),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1)

        cv2.imshow("Enrollment", display_frame)

        key = cv2.waitKey(1) & 0xFF

        if key == ord(' '):  # Space to capture
            photo_path = f"{user_dir}/{name.replace(' ', '_')}_{photo_count}.jpg"
            cv2.imwrite(photo_path, frame)
            photo_count += 1
            print(f"Captured photo {photo_count}: {photo_path}")

        elif key == ord('q'):  # Q to quit
            break

    cap.release()
    cv2.destroyAllWindows()

    if photo_count > 0:
        print(f"\nSuccessfully enrolled {name} with {photo_count} photos")
        print(f"  Directory: {user_dir}")
        return True
    else:
        print(f"\nNo photos captured for {name}")
        # Remove empty directory and database entry
        os.rmdir(user_dir)
        conn = sqlite3.connect(DB_FILE)
        cursor = conn.cursor()
        cursor.execute("DELETE FROM users WHERE employee_id = ?", (employee_id,))
        conn.commit()
        conn.close()
        return False

def list_users():
    """List all enrolled users."""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("SELECT employee_id, name, department, is_active FROM users")
    users = cursor.fetchall()
    conn.close()

    print(f"\n{'='*60}")
    print(f"{'Employee ID':<15} {'Name':<20} {'Department':<15} {'Active'}")
    print(f"{'='*60}")
    for user in users:
        status = "Yes" if user[3] else "No"
        print(f"{user[0]:<15} {user[1]:<20} {user[2]:<15} {status}")
    print(f"{'='*60}")
    print(f"Total users: {len(users)}\n")

def delete_user(employee_id):
    """Delete a user and their photos."""
    import shutil

    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()

    # Get user info
    cursor.execute("SELECT name FROM users WHERE employee_id = ?", (employee_id,))
    result = cursor.fetchone()

    if not result:
        print(f"User {employee_id} not found!")
        conn.close()
        return False

    name = result[0]
    user_dir = f"{DATABASE_PATH}/{employee_id}_{name.replace(' ', '_')}"

    # Delete from database
    cursor.execute("DELETE FROM users WHERE employee_id = ?", (employee_id,))
    conn.commit()
    conn.close()

    # Delete photos directory
    if os.path.exists(user_dir):
        shutil.rmtree(user_dir)

    print(f"Deleted user {name} ({employee_id})")
    return True

# Main CLI
if __name__ == "__main__":
    import sys

    init_database()
    os.makedirs(DATABASE_PATH, exist_ok=True)

    if len(sys.argv) < 2:
        print("Usage:")
        print("  python enroll.py add <employee_id> <name> [department]")
        print("  python enroll.py list")
        print("  python enroll.py delete <employee_id>")
        sys.exit(1)

    command = sys.argv[1]

    if command == "add" and len(sys.argv) >= 4:
        emp_id = sys.argv[2]
        name = sys.argv[3]
        dept = sys.argv[4] if len(sys.argv) > 4 else "General"
        enroll_user(emp_id, name, dept)

    elif command == "list":
        list_users()

    elif command == "delete" and len(sys.argv) >= 3:
        delete_user(sys.argv[2])

    else:
        print("Invalid command or missing arguments")

11.3 Flask Web Interface

# web/app.py - Flask admin interface
from flask import Flask, render_template, request, jsonify, Response
import cv2
import sqlite3
import os
from datetime import datetime, timedelta

app = Flask(__name__)
DATABASE = "../access_control.db"
FACE_DB = "../face_database"

def get_db():
    conn = sqlite3.connect(DATABASE)
    conn.row_factory = sqlite3.Row
    return conn

@app.route('/')
def dashboard():
    """Main dashboard with statistics."""
    conn = get_db()

    # Get stats
    total_users = conn.execute("SELECT COUNT(*) FROM users WHERE is_active=1").fetchone()[0]
    today = datetime.now().date()
    today_access = conn.execute(
        "SELECT COUNT(*) FROM access_logs WHERE DATE(timestamp) = ?",
        (today,)
    ).fetchone()[0]

    # Recent access logs
    recent_logs = conn.execute("""
        SELECT al.*, u.name
        FROM access_logs al
        LEFT JOIN users u ON al.user_id = u.employee_id
        ORDER BY al.timestamp DESC
        LIMIT 10
    """).fetchall()

    conn.close()

    return render_template('dashboard.html',
                          total_users=total_users,
                          today_access=today_access,
                          recent_logs=recent_logs)

@app.route('/users')
def users():
    """User management page."""
    conn = get_db()
    users = conn.execute("SELECT * FROM users ORDER BY name").fetchall()
    conn.close()
    return render_template('users.html', users=users)

@app.route('/api/users', methods=['GET', 'POST'])
def api_users():
    """REST API for user management."""
    conn = get_db()

    if request.method == 'GET':
        users = conn.execute("SELECT * FROM users").fetchall()
        conn.close()
        return jsonify([dict(u) for u in users])

    elif request.method == 'POST':
        data = request.json
        try:
            conn.execute(
                "INSERT INTO users (employee_id, name, department) VALUES (?, ?, ?)",
                (data['employee_id'], data['name'], data.get('department', 'General'))
            )
            conn.commit()
            conn.close()
            return jsonify({"status": "success"})
        except Exception as e:
            conn.close()
            return jsonify({"status": "error", "message": str(e)}), 400

@app.route('/api/users/<employee_id>', methods=['DELETE'])
def api_delete_user(employee_id):
    """Delete a user."""
    conn = get_db()
    conn.execute("DELETE FROM users WHERE employee_id = ?", (employee_id,))
    conn.commit()
    conn.close()
    return jsonify({"status": "success"})

@app.route('/logs')
def logs():
    """Access logs page."""
    conn = get_db()
    logs = conn.execute("""
        SELECT al.*, u.name
        FROM access_logs al
        LEFT JOIN users u ON al.user_id = u.employee_id
        ORDER BY al.timestamp DESC
        LIMIT 100
    """).fetchall()
    conn.close()
    return render_template('logs.html', logs=logs)

@app.route('/api/logs')
def api_logs():
    """REST API for access logs."""
    conn = get_db()

    # Get query parameters
    start_date = request.args.get('start')
    end_date = request.args.get('end')
    user_id = request.args.get('user_id')

    query = """
        SELECT al.*, u.name
        FROM access_logs al
        LEFT JOIN users u ON al.user_id = u.employee_id
        WHERE 1=1
    """
    params = []

    if start_date:
        query += " AND DATE(al.timestamp) >= ?"
        params.append(start_date)
    if end_date:
        query += " AND DATE(al.timestamp) <= ?"
        params.append(end_date)
    if user_id:
        query += " AND al.user_id = ?"
        params.append(user_id)

    query += " ORDER BY al.timestamp DESC LIMIT 1000"

    logs = conn.execute(query, params).fetchall()
    conn.close()

    return jsonify([dict(log) for log in logs])

def generate_frames():
    """Generator for video streaming."""
    camera = cv2.VideoCapture(0)
    while True:
        success, frame = camera.read()
        if not success:
            break

        ret, buffer = cv2.imencode('.jpg', frame)
        frame = buffer.tobytes()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

@app.route('/video_feed')
def video_feed():
    """Video streaming route."""
    return Response(generate_frames(),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

12. Project Directory Structure

face-access-control/
+-- main.py                    # Main recognition loop
+-- enroll.py                  # User enrollment script
+-- config.py                  # Configuration settings
+-- requirements.txt           # Python dependencies
+-- README.md                  # Project documentation
|
+-- face_database/             # Face embeddings storage
|   +-- EMP001_John_Smith/
|   |   +-- John_0.jpg
|   |   +-- John_1.jpg
|   |   +-- John_2.jpg
|   +-- EMP002_Jane_Doe/
|   |   +-- Jane_0.jpg
|   +-- representations_arcface.pkl  # Cached embeddings
|
+-- web/                       # Flask web interface
|   +-- app.py                 # Flask application
|   +-- templates/
|   |   +-- base.html
|   |   +-- dashboard.html
|   |   +-- users.html
|   |   +-- enroll.html
|   |   +-- logs.html
|   +-- static/
|       +-- css/
|       |   +-- style.css
|       +-- js/
|           +-- app.js
|
+-- database/
|   +-- schema.sql             # Database schema
|   +-- access_control.db      # SQLite database
|
+-- logs/
|   +-- access.log             # Access log file
|   +-- error.log              # Error log file
|   +-- snapshots/             # Unrecognized face images
|
+-- scripts/
|   +-- setup.sh               # Initial setup script
|   +-- backup.sh              # Database backup script
|   +-- test_hardware.py       # Hardware testing
|   +-- test_recognition.py    # Recognition testing
|
+-- systemd/
|   +-- face-access.service    # Systemd service file
|
+-- docs/
    +-- user_guide.md          # End user documentation
    +-- admin_guide.md         # Administrator guide
    +-- troubleshooting.md     # Common issues and solutions

13. Project Timeline

| Phase | Tasks | Duration | Deliverable | |-------|-------|----------|-------------| | 1 | Hardware setup, OS install, wiring | Days 1-2 | Working Pi + relay | | 2 | Software environment, dependencies | Day 3 | DeepFace verified | | 3 | Core recognition system | Days 4-7 | Working recognition | | 4 | Admin web interface | Days 8-10 | Web dashboard | | 5 | Testing, optimization, deployment | Days 11-14 | Production ready | | Total | | 2 Weeks | |


14. Budget Summary

| Category | One-Time Cost | Recurring (Monthly) | |----------|---------------|---------------------| | Hardware (BOM) | $192 | $0 | | Software (Open Source) | $0 | $0 | | Cloud/API Fees | $0 | $0 | | Electricity (~10W device) | $0 | ~$1 | | TOTAL | $192 | ~$1 |

Cost Comparison with Alternatives

| Solution | One-Time | Monthly | 3-Year Total | |----------|----------|---------|--------------| | This DIY Solution | $192 | $1 | $228 | | Commercial System | $1,500 | $50 | $3,300 | | Cloud API (AWS/Google) | $100 | $100 | $3,700 |

Savings: $3,000+ over 3 years


15. Risks and Mitigations

| Risk | Severity | Mitigation | |------|----------|------------| | Poor lighting affects accuracy | High | Add IR illumination, use RetinaFace detector, adjust threshold | | Photo spoofing attacks | High | Implement liveness detection (Phase 2), require blink/head movement | | System downtime | Medium | Provide PIN/card backup, auto-restart with systemd, UPS for power | | Data breach | Medium | Store embeddings only (not photos), local network only, encryption | | Hardware failure | Low | Keep spare components, daily database backup, manual override | | Slow recognition | Low | Use faster model (VGG-Face), enable embedding cache, optimize code | | User resistance | Low | Provide training, backup entry methods, clear communication |


16. Success Metrics

| Metric | Target | Measurement Method | |--------|--------|-------------------| | Recognition Accuracy | >97% | Test with enrolled users | | False Acceptance Rate | less than 0.1% | Test with unknown faces | | False Rejection Rate | less than 3% | Track failed attempts for enrolled users | | Recognition Speed | less than 1 second | Measure door unlock latency | | System Uptime | >99% | Monitor with systemd | | User Enrollment Time | less than 30 seconds | Time new enrollments | | Total Cost | less than $250/door | Track actual expenses |


17. Future Enhancements (Phase 2)

Priority 1: Security Enhancements

  • Liveness Detection: Require blink or head movement to prevent photo spoofing
  • Anti-Spoofing: Detect printed photos, screens, and masks
  • Encryption: Encrypt stored embeddings and database

Priority 2: Scalability

  • Multi-Door Support: Centralized database with networked devices
  • Load Balancing: Distribute recognition across multiple processors
  • Cloud Backup: Optional encrypted backup to cloud storage

Priority 3: User Experience

  • Mobile App: Remote access log viewing, user enrollment via phone
  • Voice Feedback: Audio confirmation of access granted/denied
  • Touchscreen UI: Local enrollment and status display

Priority 4: Integration

  • Visitor Management: Temporary access codes, visitor photo capture
  • HR System Integration: Sync users with HR database
  • Time and Attendance: Track work hours based on access logs
  • Security Alerts: Integration with alarm systems

Priority 5: Analytics

  • Traffic Analytics: Peak hours, busiest entry points
  • Attendance Reports: Daily/weekly/monthly summaries
  • Anomaly Detection: Alert on unusual access patterns

Appendix A: Installation Commands

System Setup

# System Updates
sudo apt update && sudo apt upgrade -y

# Install System Dependencies
sudo apt install -y python3-pip python3-venv cmake libatlas-base-dev \
    libhdf5-dev libhdf5-serial-dev libopenblas-dev libjpeg-dev \
    libpng-dev libtiff-dev libavcodec-dev libavformat-dev \
    libswscale-dev libv4l-dev libxvidcore-dev libx264-dev \
    libgtk-3-dev libcanberra-gtk3-module

# Create Project Directory
mkdir -p ~/face-access-control
cd ~/face-access-control

# Create Virtual Environment
python3 -m venv venv
source venv/bin/activate

# Install Python Packages
pip install --upgrade pip
pip install deepface opencv-python flask numpy pandas pillow
pip install RPi.GPIO gpiozero  # For Raspberry Pi

# Verify Installation
python -c "from deepface import DeepFace; print('DeepFace OK')"
python -c "import cv2; print(f'OpenCV {cv2.__version__} OK')"

Database Initialization

# Create database directory
mkdir -p database

# Initialize database
sqlite3 database/access_control.db < database/schema.sql

Camera Testing

# Test Pi Camera
libcamera-hello

# Test USB Camera
python -c "import cv2; cap=cv2.VideoCapture(0); print('Camera:', cap.isOpened())"

GPIO Testing

# Test relay (will click on/off)
python -c "
from gpiozero import OutputDevice
import time
relay = OutputDevice(17)
print('Relay ON')
relay.on()
time.sleep(1)
print('Relay OFF')
relay.off()
relay.close()
"

Appendix B: Systemd Service File

Service Configuration

# /etc/systemd/system/face-access.service
[Unit]
Description=Facial Recognition Access Control
After=network.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/face-access-control
Environment=PATH=/home/pi/face-access-control/venv/bin
ExecStart=/home/pi/face-access-control/venv/bin/python main.py
Restart=always
RestartSec=10
StandardOutput=append:/home/pi/face-access-control/logs/access.log
StandardError=append:/home/pi/face-access-control/logs/error.log

[Install]
WantedBy=multi-user.target

Service Management

# Copy service file
sudo cp systemd/face-access.service /etc/systemd/system/

# Reload systemd
sudo systemctl daemon-reload

# Enable service (start on boot)
sudo systemctl enable face-access

# Start service
sudo systemctl start face-access

# Check status
sudo systemctl status face-access

# View logs
journalctl -u face-access -f

# Stop service
sudo systemctl stop face-access

# Restart service
sudo systemctl restart face-access

Appendix C: Troubleshooting

Common Issues

| Issue | Possible Cause | Solution | |-------|---------------|----------| | Camera not detected | Wrong device ID | Try camera_id: 1 or check /dev/video* | | Low accuracy | Poor lighting | Add lighting, use RetinaFace backend | | Slow recognition | Large model | Switch to VGG-Face or enable caching | | Relay not clicking | Wrong GPIO pin | Verify wiring, test with simple script | | Permission denied | GPIO access | Add user to gpio group | | Import errors | Missing dependencies | Reinstall in virtual environment |

Debug Mode

# Add to main.py for debugging
import logging
logging.basicConfig(level=logging.DEBUG)

# Enable DeepFace verbose output
results = DeepFace.find(..., silent=False)

Performance Optimization

# Use embedding cache (auto-generated on first run)
# Delete to rebuild: rm face_database/representations_*.pkl

# Use faster detector for real-time
CONFIG["detector_backend"] = "opencv"  # Fastest
CONFIG["detector_backend"] = "ssd"     # Fast + accurate

# Reduce frame size
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)

Appendix D: requirements.txt

# Core dependencies
deepface>=0.0.93
opencv-python>=4.9.0
numpy>=1.26.0
pandas>=2.0.0
Pillow>=10.0.0

# Web interface
Flask>=3.0.0
gunicorn>=21.0.0

# Raspberry Pi GPIO (install only on Pi)
# RPi.GPIO>=0.7.0
# gpiozero>=2.0.0

# Optional: Better face detection
# tensorflow>=2.15.0  # For RetinaFace
# torch>=2.0.0        # Alternative backend

# Development
pytest>=7.0.0
black>=23.0.0
flake8>=6.0.0