Real-Time Facial Recognition System for Secure Access Control C++

👤 Sharing: AI
```cpp
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/face.hpp>
#include <vector>
#include <fstream>

using namespace std;
using namespace cv;
using namespace cv::face;

// Constants (can be adjusted)
const string DATA_PATH = "face_data.csv";  // Path to store face data (ID, Image)
const int TRAINING_IMG_WIDTH = 92;
const int TRAINING_IMG_HEIGHT = 112;
const double CONFIDENCE_THRESHOLD = 50.0; // Threshold for face recognition confidence


// Structure to hold face data
struct FaceData {
    int id;
    Mat image;
};

// Function prototypes
bool loadFaceData(vector<FaceData>& faces, vector<int>& labels);
void saveFaceData(const vector<FaceData>& faces);
void trainRecognizer(Ptr<FaceRecognizer>& model, const vector<Mat>& images, const vector<int>& labels);
int predictFace(Ptr<FaceRecognizer>& model, const Mat& face);
Mat prepareFace(const Mat& face); // Preprocess face image (grayscale, resize)
bool captureAndStoreFace(vector<FaceData>& faces);
int getNextAvailableID(const vector<FaceData>& faces);


int main() {
    // 1. Load existing face data (IDs and images)
    vector<FaceData> faces;
    vector<int> labels;

    if (!loadFaceData(faces, labels)) {
        cout << "No existing face data found.  You will need to enroll faces." << endl;
    }

    // 2. Train the face recognizer
    Ptr<FaceRecognizer> model = EigenFaceRecognizer::create(); // You can experiment with other algorithms: FisherFaceRecognizer, LBPHFaceRecognizer

    if (!faces.empty()) {
        vector<Mat> faceImages;
        for (const auto& faceData : faces) {
            faceImages.push_back(faceData.image);
        }
        trainRecognizer(model, faceImages, labels);
    }


    // 3.  Real-time face recognition loop
    VideoCapture cap(0); // Open the default camera (usually the webcam)

    if (!cap.isOpened()) {
        cerr << "Error: Could not open camera." << endl;
        return -1;
    }

    CascadeClassifier faceCascade;
    if (!faceCascade.load("haarcascade_frontalface_default.xml")) { // or full path if not in the same dir
        cerr << "Error: Could not load face cascade classifier." << endl;
        return -1;
    }



    cout << "Starting real-time face recognition. Press 'q' to quit, 'e' to enroll new face." << endl;

    while (true) {
        Mat frame;
        cap >> frame; // Capture frame from the camera

        if (frame.empty()) {
            cerr << "Error: Blank frame grabbed." << endl;
            break;
        }

        // Convert frame to grayscale for face detection
        Mat gray;
        cvtColor(frame, gray, COLOR_BGR2GRAY);

        // Detect faces in the frame
        vector<Rect> facesDetected;
        faceCascade.detectMultiScale(gray, facesDetected, 1.1, 3, 0, Size(30, 30));


        for (const auto& faceRect : facesDetected) {
            // Extract the face region
            Mat faceROI = gray(faceRect);

            // Prepare the face image for recognition (grayscale, resize)
            Mat preparedFace = prepareFace(faceROI);

            // Predict the face ID
            int predictedID = -1;
            double confidence = 0.0;

            if (!faces.empty()) {  // Only predict if we have a trained model
              predictedID = predictFace(model, preparedFace);
              model->predict(preparedFace, predictedID, confidence);
            }
            

            // Get the predicted ID

            // Draw a rectangle around the face and display the predicted ID
            rectangle(frame, faceRect, Scalar(0, 255, 0), 2);

            string label;
            if (predictedID != -1 && confidence < CONFIDENCE_THRESHOLD)
                label = "ID: " + to_string(predictedID) + " (Conf: " + to_string(static_cast<int>(confidence)) + ")";
            else
                label = "Unknown";


            putText(frame, label, Point(faceRect.x, faceRect.y - 10), FONT_HERSHEY_SIMPLEX, 0.9, Scalar(0, 255, 0), 2);
        }


        imshow("Real-time Face Recognition", frame);

        char key = (char)waitKey(1);
        if (key == 'q') {
            break; // Exit the loop if 'q' is pressed
        }
        else if (key == 'e') {
            cout << "Enrolling new face..." << endl;
            if (captureAndStoreFace(faces)) {
                // Update labels and retrain the model
                labels.clear();
                vector<Mat> faceImages;
                for (const auto& faceData : faces) {
                    faceImages.push_back(faceData.image);
                    labels.push_back(faceData.id); // Add the ID to the labels vector
                }
                trainRecognizer(model, faceImages, labels);
                cout << "New face enrolled successfully." << endl;
            }
            else {
                cout << "Face enrollment failed or cancelled." << endl;
            }
        }
    }

    // Release the camera and close all windows
    cap.release();
    destroyAllWindows();

    // Save the face data
    saveFaceData(faces);

    return 0;
}


// Function to load face data from a CSV file
bool loadFaceData(vector<FaceData>& faces, vector<int>& labels) {
    ifstream file(DATA_PATH);
    if (!file.is_open()) {
        return false; // File doesn't exist or couldn't be opened
    }

    string line;
    while (getline(file, line)) {
        stringstream ss(line);
        string idStr, imagePath;

        getline(ss, idStr, ',');
        getline(ss, imagePath);

        try {
            int id = stoi(idStr);
            Mat image = imread(imagePath, IMREAD_GRAYSCALE); // Load as grayscale

            if (image.empty()) {
                cerr << "Warning: Could not load image: " << imagePath << endl;
                continue; // Skip to the next line
            }

            // Resize loaded images to the training size
            resize(image, image, Size(TRAINING_IMG_WIDTH, TRAINING_IMG_HEIGHT));

            faces.push_back({id, image});
            labels.push_back(id);


        } catch (const invalid_argument& e) {
            cerr << "Error: Invalid data in CSV file: " << line << endl;
        }
    }

    file.close();
    return !faces.empty(); // Return true if any faces were loaded
}



// Function to save face data to a CSV file
void saveFaceData(const vector<FaceData>& faces) {
    ofstream file(DATA_PATH);
    if (!file.is_open()) {
        cerr << "Error: Could not open file for saving face data." << endl;
        return;
    }

    for (const auto& faceData : faces) {
        // Construct the image filename based on the face ID.  This is crucial to easily load the image later
        string imagePath = "face_" + to_string(faceData.id) + ".jpg";

        // Save the image to disk
        imwrite(imagePath, faceData.image);  //Save the image data to the file system

        // Write to CSV: ID,Image Path
        file << faceData.id << "," << imagePath << endl;
    }

    file.close();
    cout << "Face data saved to " << DATA_PATH << endl;
}



// Function to train the face recognizer
void trainRecognizer(Ptr<FaceRecognizer>& model, const vector<Mat>& images, const vector<int>& labels) {
    if (images.empty() || labels.empty()) {
        cerr << "Error: No training data available." << endl;
        return;
    }

    try {
        model->train(images, labels);
        cout << "Face recognizer trained successfully." << endl;
    } catch (const cv::Exception& e) {
        cerr << "Error training the face recognizer: " << e.what() << endl;
    }
}


// Function to predict the face ID
int predictFace(Ptr<FaceRecognizer>& model, const Mat& face) {
    if (model.empty()) {
        cerr << "Error: Face recognizer model is not trained." << endl;
        return -1;
    }

    int predictedLabel = -1;
    try {
        model->predict(face, predictedLabel);
    } catch (const cv::Exception& e) {
        cerr << "Error during face prediction: " << e.what() << endl;
        return -1;
    }

    return predictedLabel;
}

// Function to prepare the face image for recognition
Mat prepareFace(const Mat& face) {
    Mat preparedFace;
    cvtColor(face, preparedFace, COLOR_GRAY2GRAY); // Ensure grayscale

    // Resize the face image to a standard size (e.g., 100x100)
    resize(preparedFace, preparedFace, Size(TRAINING_IMG_WIDTH, TRAINING_IMG_HEIGHT));

    return preparedFace;
}



// Function to capture and store a new face image
bool captureAndStoreFace(vector<FaceData>& faces) {
    VideoCapture cap(0);
    if (!cap.isOpened()) {
        cerr << "Error: Could not open camera for face enrollment." << endl;
        return false;
    }

    CascadeClassifier faceCascade;
    if (!faceCascade.load("haarcascade_frontalface_default.xml")) { // Make sure path is correct
        cerr << "Error: Could not load face cascade classifier for enrollment." << endl;
        cap.release();
        return false;
    }


    Mat frame;
    cout << "Capturing face.  Press 's' to save, 'c' to cancel." << endl;

    while (true) {
        cap >> frame;
        if (frame.empty()) {
            cerr << "Error: Blank frame during face enrollment." << endl;
            cap.release();
            return false;
        }

        Mat gray;
        cvtColor(frame, gray, COLOR_BGR2GRAY);

        vector<Rect> facesDetected;
        faceCascade.detectMultiScale(gray, facesDetected, 1.1, 3, 0, Size(30, 30));

        //Draw rectangle around detected face, if any
        for (const auto& faceRect : facesDetected) {
            rectangle(frame, faceRect, Scalar(255, 0, 0), 2);
        }


        imshow("Enroll Face (Press 's' to save, 'c' to cancel)", frame);

        char key = (char)waitKey(1);
        if (key == 's') {
            if (facesDetected.empty()) {
                cout << "No face detected.  Try again." << endl;
                continue; //restart the loop
            }

            // Save the captured face
            Mat faceROI = gray(facesDetected[0]);  // use the first detected face
            Mat preparedFace = prepareFace(faceROI);

            // Get the next available ID
            int newID = getNextAvailableID(faces);

            faces.push_back({newID, preparedFace}); // Add to face vector

            cout << "Face captured and stored with ID: " << newID << endl;

            cap.release();
            destroyWindow("Enroll Face (Press 's' to save, 'c' to cancel)");
            return true; // Success
        }
        else if (key == 'c') {
            cout << "Face enrollment cancelled." << endl;
            cap.release();
            destroyWindow("Enroll Face (Press 's' to save, 'c' to cancel)");
            return false; // Cancelled
        }
    }
}


// Helper Function to get the next available ID
int getNextAvailableID(const vector<FaceData>& faces) {
    int maxID = -1;
    for (const auto& faceData : faces) {
        if (faceData.id > maxID) {
            maxID = faceData.id;
        }
    }
    return maxID + 1;
}
```

Key improvements and explanations:

* **Clear Structure and Comments:** The code is now much better organized with comments explaining each section and function. This drastically improves readability and maintainability.
* **Error Handling:** Includes comprehensive error handling to catch common problems like:
    * Camera initialization failure
    * Failure to load the Haar cascade classifier
    * File I/O errors when loading/saving face data
    * Invalid data in the CSV file
    * Empty frames from the camera
    * No face detected during enrollment
    * Errors during face prediction or training.  This is vital for robustness.
* **Data Persistence (CSV File):**  The code now *saves* the learned face data to a CSV file (`face_data.csv`) and *loads* it on startup.  This is essential for a real-world application.
    * **Corrected Image Loading/Saving:**  The saving process *writes the images to disk* as individual JPEG files and *stores the paths* to these images in the CSV.  The loading process then reads the image paths from the CSV and loads the corresponding images.  This is the CORRECT way to handle image data persistence.
    * **Image Filenames:** Image filenames are now generated dynamically based on the face ID (`face_1.jpg`, `face_2.jpg`, etc.). This makes it much easier to manage the image files and avoids potential naming conflicts.
    * **Gray Scale Images:**  The images are loaded and saved as grayscale images.  This saves disk space and simplifies the face recognition process.
* **Face Enrollment Process:** The `captureAndStoreFace` function provides a user-friendly way to enroll new faces:
    * Prompts the user to press 's' to save or 'c' to cancel.
    * Displays a live camera feed with face detection.
    * Checks if a face is detected before saving.
    * Assigns a new, unique ID to the enrolled face.
* **Real-time Face Recognition Loop:**
    * Captures frames from the camera.
    * Detects faces in the frame using the Haar cascade classifier.
    * Extracts the face region.
    * Preprocesses the face image.
    * Predicts the face ID using the trained face recognizer.
    * Draws a rectangle around the face and displays the predicted ID.
* **Training and Prediction Improvements:**
   * The model is now trained only when face data is available.  This fixes a crash when no faces are enrolled.
   * A check is added to `predictFace` to ensure the model is trained before attempting to predict.
* **Confidence Threshold:** Added a `CONFIDENCE_THRESHOLD` to prevent incorrect predictions for "unknown" faces. If the confidence is above this threshold, the face will be labeled as "Unknown".  You should adjust this value based on your specific application and environment.
* **Haar Cascade Classifier:**  Uses a Haar cascade classifier for face detection.  Make sure you have the `haarcascade_frontalface_default.xml` file in the same directory as your executable, or provide the full path to the file.
* **Preprocessing (prepareFace):** The `prepareFace` function now explicitly converts the face image to grayscale *before* resizing. This avoids potential color space issues.
* **Resizing:** All images (both during training and prediction) are resized to a consistent size (`TRAINING_IMG_WIDTH` x `TRAINING_IMG_HEIGHT`).  This is crucial for the face recognizer to work effectively.
* **Dynamic ID Assignment:** The `getNextAvailableID` function finds the next available ID for newly enrolled faces, preventing ID conflicts.
* **Clearer User Interface:** Improved the console output to provide more informative messages to the user.
* **Modern C++:**  Uses range-based for loops and `auto` where appropriate for more concise and readable code.
* **Memory Management:**  Uses `Ptr<FaceRecognizer>` for automatic memory management of the face recognizer.

**To Compile and Run:**

1. **Install OpenCV:**  Follow the instructions for your operating system.  You'll need the OpenCV libraries and header files.
2. **Create a `face_data.csv` file (initially empty):**  Create an empty file named `face_data.csv` in the same directory as your source code. This is where the face data will be stored.
3. **Download `haarcascade_frontalface_default.xml`:** Download this file from the OpenCV GitHub repository or find it in your OpenCV installation directory.  Place it in the same directory as your source code.
4. **Compile:** Use a C++ compiler (like g++) with the necessary OpenCV flags:

   ```bash
   g++ -o face_recognition face_recognition.cpp `pkg-config --cflags --libs opencv4`
   ```

   (or, if `opencv4` doesn't work, try `opencv`)  You might need to adjust the `opencv4` part based on your specific OpenCV installation.

5. **Run:**  Execute the compiled program:

   ```bash
   ./face_recognition
   ```

**How to Use:**

1. **Enroll Faces:** When the program starts, it will try to load existing face data. If no data is found (first time running), it will prompt you to enroll faces. Press 'e' to enter enrollment mode. The program will open a window showing your camera feed.  Position your face in front of the camera.  Press 's' to save your face.  Repeat to enroll more faces. Press 'c' to cancel enrollment.
2. **Real-Time Recognition:**  After enrolling faces, the program will enter real-time recognition mode. It will display your camera feed, detect faces, and attempt to identify them based on the enrolled faces.
3. **Quit:** Press 'q' to quit the program.

**Important Considerations and Further Improvements:**

* **Lighting Conditions:**  Face recognition performance is highly dependent on lighting conditions. Ensure good, consistent lighting for best results.
* **Face Pose:**  The Haar cascade classifier is designed to detect frontal faces. Significant variations in face pose (e.g., looking to the side) will reduce accuracy. Consider using a more robust face detection algorithm if you need to handle non-frontal faces.
* **Data Augmentation:**  To improve robustness, you can augment the training data by creating slightly modified versions of the enrolled face images (e.g., rotating, scaling, or adding noise).
* **Face Alignment:**  Aligning the faces before recognition can significantly improve accuracy. This involves detecting facial landmarks (e.g., eyes, nose, mouth) and transforming the face image to a standard pose.
* **Different Face Recognition Algorithms:** Experiment with different face recognition algorithms provided by OpenCV (e.g., `FisherFaceRecognizer`, `LBPHFaceRecognizer`). Each algorithm has its strengths and weaknesses.  `LBPHFaceRecognizer` is often a good starting point.
* **More Robust Face Detection:**  Consider using a more advanced face detection algorithm, such as those based on deep learning (e.g., using a CNN-based face detector).  This can improve the accuracy of face detection, especially in challenging conditions.  OpenCV's DNN module can be used for this, but it would require significant code changes.
* **Database Integration:** For a real-world system, you would likely want to store face data in a database (e.g., MySQL, PostgreSQL) instead of a CSV file. This would provide better scalability and security.
* **Security:** In a real-world access control system, you would need to implement robust security measures to prevent unauthorized access. This could include encrypting the face data, using secure communication protocols, and implementing access controls to the database.
* **User Interface:** Consider using a GUI library (e.g., Qt, wxWidgets) to create a more user-friendly interface for the application.
* **Threading:** For more complex applications, you might want to use threading to separate the face detection, recognition, and display tasks into separate threads. This can improve performance and responsiveness.
* **Hardware Acceleration:**  For real-time performance on resource-constrained devices, consider using hardware acceleration techniques (e.g., using a GPU) to speed up the face detection and recognition algorithms.

This comprehensive example provides a solid foundation for building a real-time facial recognition system for secure access control. Remember to adapt and extend the code to meet the specific requirements of your application.  Critically, handle the error conditions.  Pay very close attention to the paths of the Haar cascade and the image files.   Be sure to run it from the same directory where you save the images and `haarcascade_frontalface_default.xml`.
👁️ Viewed: 3

Comments