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