This tutorial uses the insightface library for face recognition, which is developed based on the ONNX framework and runs in an Anaconda environment.

Code Address: Download Here

Installation Environment

  1. Install insightface with the following command:
python -m pip install Cython insightface==0.6.2 -i https://mirror.baidu.com/pypi/simple
  1. Install onnxruntime-gpu:
python -m pip install onnxruntime-gpu -i https://mirror.baidu.com/pypi/simple
  1. Install cudatoolkit:
conda install cudatoolkit=11.3 cudnn --channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/Paddle/

Face Recognition and Registration

To simplify the process, a class is created to encapsulate the entire recognition workflow. The insightface.app.FaceAnalysis() function is used to obtain the model object, which includes three models: face detection, feature extraction, and gender/age recognition. The model.prepare() method configures the ctx_id to specify which GPU to use (negative values indicate CPU usage), sets the face detection threshold with det_thresh, and configures the image size for the detection model with det_size. The load_faces() function loads faces from the face database for subsequent comparison.

import os

import cv2
import insightface
import numpy as np
from sklearn import preprocessing


class FaceRecognition:
    def __init__(self, gpu_id=0, face_db='face_db', threshold=1.24, det_thresh=0.50, det_size=(640, 640)):
        """
        Face recognition tool class
        :param gpu_id: Positive number for GPU ID, negative for CPU
        :param face_db: Face database folder
        :param threshold: Face recognition threshold
        :param det_thresh: Detection threshold
        :param det_size: Detection model image size
        """
        self.gpu_id = gpu_id
        self.face_db = face_db
        self.threshold = threshold
        self.det_thresh = det_thresh
        self.det_size = det_size

        # Load face recognition model (only detection and recognition when allowed_modules=['detection', 'recognition'])
        self.model = insightface.app.FaceAnalysis(root='./',
                                                  allowed_modules=None,
                                                  providers=['CUDAExecutionProvider'])
        self.model.prepare(ctx_id=self.gpu_id, det_thresh=self.det_thresh, det_size=self.det_size)
        # Face features from the database
        self.faces_embedding = list()
        # Load faces from the database
        self.load_faces(self.face_db)

    # Load faces from the database
    def load_faces(self, face_db_path):
        if not os.path.exists(face_db_path):
            os.makedirs(face_db_path)
        for root, dirs, files in os.walk(face_db_path):
            for file in files:
                input_image = cv2.imdecode(np.fromfile(os.path.join(root, file), dtype=np.uint8), 1)
                user_name = file.split(".")[0]
                face = self.model.get(input_image)[0]
                embedding = np.array(face.embedding).reshape((1, -1))
                embedding = preprocessing.normalize(embedding)
                self.faces_embedding.append({
                    "user_name": user_name,
                    "feature": embedding
                })

Next, the recognition() function is implemented to compare each face’s feature embedding with those in the database using Euclidean distance. If the distance is less than 1.24, the faces are considered the same person.

    def recognition(self, image):
        faces = self.model.get(image)
        results = list()
        for face in faces:
            # Extract face embedding
            embedding = np.array(face.embedding).reshape((1, -1))
            embedding = preprocessing.normalize(embedding)
            user_name = "unknown"
            # Compare with all faces in the database
            for com_face in self.faces_embedding:
                r = self.feature_compare(embedding, com_face["feature"], self.threshold)
                if r:
                    user_name = com_face["user_name"]
            results.append(user_name)
        return results

The Euclidean distance calculation method is defined as follows:

    @staticmethod
    def feature_compare(feature1, feature2, threshold):
        diff = np.subtract(feature1, feature2)
        dist = np.sum(np.square(diff), 1)
        return dist < threshold

For face registration, the register() function checks if the face exists in the database before adding it. If the face count is not 1, registration fails. The feature is normalized and stored, along with the image saved to the face database.

    def register(self, image, user_name):
        faces = self.model.get(image)
        if len(faces) != 1:
            return '图片检测不到人脸'  # No face detected in the image
        # Check if the face already exists
        embedding = np.array(faces[0].embedding).reshape((1, -1))
        embedding = preprocessing.normalize(embedding)
        is_exits = False
        for com_face in self.faces_embedding:
            if self.feature_compare(embedding, com_face["feature"], self.threshold):
                is_exits = True
        if is_exits:
            return '该用户已存在'  # User already exists
        # Save image and add to database
        cv2.imencode('.png', image)[1].tofile(os.path.join(self.face_db, '%s.png' % user_name))
        self.faces_embedding.append({
            "user_name": user_name,
            "feature": embedding
        })
        return "success"

A general face detection function is also provided, returning coordinates, key points, 3D/2D landmarks, pose, age, gender, and feature embedding for each detected face.

    def detect(self, image):
        faces = self.model.get(image)
        results = list()
        for face in faces:
            result = dict()
            # Face bounding box
            result["bbox"] = np.array(face.bbox).astype(np.int32).tolist()
            # Key points
            result["kps"] = np.array(face.kps).astype(np.int32).tolist()
            result["landmark_3d_68"] = np.array(face.landmark_3d_68).astype(np.int32).tolist()
            result["landmark_2d_106"] = np.array(face.landmark_2d_106).astype(np.int32).tolist()
            result["pose"] = np.array(face.pose).astype(np.int32).tolist()
            result["age"] = face.age
            # Determine gender
            result["gender"] = '女' if face.gender == 0 else '男'
            # Extract feature
            embedding = np.array(face.embedding).reshape((1, -1))
            embedding = preprocessing.normalize(embedding)
            result["embedding"] = embedding
            results.append(result)
        return results

Usage

Face Registration:

if __name__ == '__main__':
    img = cv2.imdecode(np.fromfile('迪丽热巴.jpg', dtype=np.uint8), -1)
    face_recognitio = FaceRecognition()
    result = face_recognitio.register(img, user_name='迪丽热巴')
    print(result)

Face Recognition:

if __name__ == '__main__':
    img = cv2.imdecode(np.fromfile('迪丽热巴.jpg', dtype=np.uint8), -1)
    face_recognitio = FaceRecognition()
    results = face_recognitio.recognition(img)
    for result in results:
        print("识别结果:{}".format(result))

General Face Detection:

if __name__ == '__main__':
    img = cv2.imdecode(np.fromfile('迪丽热巴.jpg', dtype=np.uint8), -1)
    face_recognitio = FaceRecognition()
    results = face_recognitio.detect(img)
    for result in results:
        print('人脸框坐标:{}'.format(result["bbox"]))
        print('人脸五个关键点:{}'.format(result["kps"]))
        print('人脸3D关键点:{}'.format(result["landmark_3d_68"]))
        print('人脸2D关键点:{}'.format(result["landmark_2d_106"]))
        print('人脸姿态:{}'.format(result["pose"]))
        print('年龄:{}'.format(result["age"]))
        print('性别:{}'.format(result["gender"]))

Complete Code

import os

import cv2
import insightface
import numpy as np
from sklearn import preprocessing


class FaceRecognition:
    def __init__(self, gpu_id=0, face_db='face_db', threshold=1.24, det_thresh=0.50, det_size=(640, 640)):
        """
        Face recognition tool class
        :param gpu_id: Positive number for GPU ID, negative for CPU
        :param face_db: Face database folder
        :param threshold: Face recognition threshold
        :param det_thresh: Detection threshold
        :param det_size: Detection model image size
        """
        self.gpu_id = gpu_id
        self.face_db = face_db
        self.threshold = threshold
        self.det_thresh = det_thresh
        self.det_size = det_size

        # Load face recognition model
        self.model = insightface.app.FaceAnalysis(root='./',
                                                  allowed_modules=None,
                                                  providers=['CUDAExecutionProvider'])
        self.model.prepare(ctx_id=self.gpu_id, det_thresh=self.det_thresh, det_size=self.det_size)
        # Store face features from database
        self.faces_embedding = list()
        self.load_faces(self.face_db)

    def load_faces(self, face_db_path):
        if not os.path.exists(face_db_path):
            os.makedirs(face_db_path)
        for root, dirs, files in os.walk(face_db_path):
            for file in files:
                input_image = cv2.imdecode(np.fromfile(os.path.join(root, file), dtype=np.uint8), 1)
                user_name = file.split(".")[0]
                face = self.model.get(input_image)[0]
                embedding = np.array(face.embedding).reshape((1, -1))
                embedding = preprocessing.normalize(embedding)
                self.faces_embedding.append({
                    "user_name": user_name,
                    "feature": embedding
                })

    def recognition(self, image):
        faces = self.model.get(image)
        results = list()
        for face in faces:
            embedding = np.array(face.embedding).reshape((1, -1))
            embedding = preprocessing.normalize(embedding)
            user_name = "unknown"
            for com_face in self.faces_embedding:
                if self.feature_compare(embedding, com_face["feature"], self.threshold):
                    user_name = com_face["user_name"]
            results.append(user_name)
        return results

    @staticmethod
    def feature_compare(feature1, feature2, threshold):
        diff = np.subtract(feature1, feature2)
        dist = np.sum(np.square(diff), 1)
        return dist < threshold

    def register(self, image, user_name):
        faces = self.model.get(image)
        if len(faces) != 1:
            return '图片检测不到人脸'
        embedding = np.array(faces[0].embedding).reshape((1, -1))
        embedding = preprocessing.normalize(embedding)
        is_exits = False
        for com_face in self.faces_embedding:
            if self.feature_compare(embedding, com_face["feature"], self.threshold):
                is_exits = True
        if is_exits:
            return '该用户已存在'
        cv2.imencode('.png', image)[1].tofile(os.path.join(self.face_db, '%s.png' % user_name))
        self.faces_embedding.append({
            "user_name": user_name,
            "feature": embedding
        })
        return "success"

    def detect(self, image):
        faces = self.model.get(image)
        results = list()
        for face in faces:
            result = dict()
            result["bbox"] = np.array(face.bbox).astype(np.int32).tolist()
            result["kps"] = np.array(face.kps).astype(np.int32).tolist()
            result["landmark_3d_68"] = np.array(face.landmark_3d_68).astype(np.int32).tolist()
            result["landmark_2d_106"] = np.array(face.landmark_2d_106).astype(np.int32).tolist()
            result["pose"] = np.array(face.pose).astype(np.int32).tolist()
            result["age"] = face.age
            result["gender"] = '女' if face.gender == 0 else '男'
            embedding = np.array(face.embedding).reshape((1, -1))
            embedding = preprocessing.normalize(embedding)
            result["embedding"] = embedding
            results.append(result)
        return results


if __name__ == '__main__':
    img = cv2.imdecode(np.fromfile('迪丽热巴.jpg', dtype=np.uint8), -1)
    face_recognitio = FaceRecognition()
    # Register face
    result = face_recognitio.register(img, user_name='迪丽热巴')
    print(result)

    # Recognize face
    results = face_recognitio.recognition(img)
    for result in results:
        print("识别结果:{}".format(result))

    # Detect faces
    results = face_recognitio.detect(img)
    for result in results:
        print('人脸框坐标:{}'.format(result["bbox"]))
        print('人脸五个关键点:{}'.format(result["kps"]))
        print('人脸3D关键点:{}'.format(result["landmark_3d_68"]))
        print('人脸2D关键点:{}'.format(result["landmark_2d_106"]))
        print('人脸姿态:{}'.format(result["pose"]))
        print('年龄:{}'.format(result["age"]))
        print('性别:{}'.format(result["gender"]))
Xiaoye