Skip to content

Plumerai Familiar Face Identification Python API

This document describes the Python API for the Plumerai Familiar Face Identification software for videos.

The API

The Python API consists of a single class with a constructor that needs to be ran once, and a process_frame function that needs to be executed on each input frame. Furthermore, the API provides functions to 'enroll' or 'register' new people by generating and storing a 'face embedding'.

FaceIdentification

init

FaceIdentification.__init__(height: int, width: int)

Initializes a new face identification object.

This needs to be called only once at the start of the application.

Arguments:

  • height:: The height of the input image in pixels.
  • width:: The width of the input image in pixels.

Returns:

  • Nothing.

process_frame

FaceIdentification.process_frame(
    image, delta_t: float = 0.0
) -> tuple[ErrorCodeFamiliarFaceID, tuple[BoxPrediction, ...]]

Process a single frame from a video sequence with RGB input.

The image must have the height and width that was specified when the FaceIdentification object was created.

Arguments:

  • image: A tensor of shape (video_height, video_width, 3) with RGB image data. It can be a Numpy array or TensorFlow, PyTorch or Jax tensor.
  • delta_t: The time in seconds it took between this and the previous video frame (1/fps). If left to the default of 0, then the system clock will be used to compute this value.

Returns:

single_image

FaceIdentification.single_image(
    image
) -> tuple[ErrorCodeFamiliarFaceID, tuple[BoxPrediction, ...]]

Process a single image not part of a video sequence.

This should not be used for video data. It can be used for face enrollments from a set of images. The returned box id values are not related to those returned by process_frame or other calls to single_frame.

Arguments:

  • image: A tensor of shape (*, *, 3) with RGB image data. It can be a Numpy array, or TensorFlow, PyTorch or Jax tensor.

Returns:

add_face_embedding

FaceIdentification.add_face_embedding(
    face_embedding: np.ndarray, face_id: int
) -> ErrorCodeFamiliarFaceID

Add a face embedding to the face library for any process_frame calls that follow.

A second embedding for the same face id will overwrite the previous one.

When process_frame detects a person that matches a face, it will use the face_id provided here in the returned BoxPrediction.

Arguments:

  • face_embedding: A NumPy face embedding as returned by finish_face_enrollment. If an empty NumPy array is given, the specified face_id is removed.
  • face_id: A non-negative integer face-ID, which is not allowed to be equal to FACE_ID_UNIDENTIFIED or FACE_ID_NOT_IN_LIBRARY.

Returns:

  • Returns the error code "SUCCESS" on success, "INVALID_FACE_ID" or "INVALID_EMBEDDING" on failure.

remove_face_embedding

FaceIdentification.remove_face_embedding(face_id: int) -> ErrorCodeFamiliarFaceID

Remove a face embedding from the face library for any process_frame calls that follow.

Arguments:

Returns:

  • Returns the error code "SUCCESS" on success.

start_face_enrollment

FaceIdentification.start_face_enrollment(
    region_of_interest: tuple[float, float, float, float] = (0, 0, 1, 1),
) -> ErrorCodeFamiliarFaceID

This starts the enrollment procedure for a new face.

During subsequent calls to process_frame or single_image, face embeddings will be computed. During enrollment there should be a single face in the image, or in the optional region specified by region_of_interest, ideally clearly and completely visible. The enrollment procedure can be finalized by calling finish_face_enrollment.

Arguments:

  • region_of_interest: This can be set to a region of interest in the image as y_min, x_min, y_max, x_max in relative coordinates. Only faces that have overlap with this region will be used for enrollment. The default of 0, 0, 1, 1 includes the entire image.

Returns:

  • Returns the error code "ENROLLMENT_IN_PROGRESS" if the enrollment procedure was started successfully, otherwise returns "ALREADY_ENROLLING".

finish_face_enrollment

FaceIdentification.finish_face_enrollment() -> tuple[ErrorCodeFamiliarFaceID, np.ndarray]

Finalize the face enrollment procedure started by start_face_enrollment and obtain the face embedding.

The application is responsible for storing this embedding on persistent storage and passing it to add_face_embedding every time an instance of this class is created. The face enrollment procedure does not automatically call add_face_embedding.

Arguments:

  • None.

Returns:

  • A tuple with an error code of type ErrorCodeFamiliarFaceID and the resulting face embedding vector

get_face_id

FaceIdentification.get_face_id(person_box: BoxPrediction) -> int

Retrieve the face ID that belongs to a person box.

Retrieves the face ID given a person box returned by process_frame. This function has three possible return values:

  • FACE_ID_UNIDENTIFIED when the provided box is not of CLASS_PERSON or when a face is not yet identified, e.g. the face is not fully visible.
  • FACE_ID_NOT_IN_LIBRARY when the face is visible but not found in the library, e.g. when a stranger is in view.
  • Any integer face-ID provided previously to add_face_embedding when a familiar face is identified.

This function should not be called directly after restoring from a previous state.

Arguments:

  • person_box: A box returned by process_frame with class_id equal to DetectionClass::CLASS_PERSON.

Returns:

  • Returns an integer face-ID as provided when calling add_face_embedding when a face is identified, FACE_ID_UNIDENTIFIED or FACE_ID_NOT_IN_LIBRARY when it is not identified successfully, or FACE_ID_UNIDENTIFIED when the provided box is not a valid person box.

BoxPrediction

A structure representing a single resulting bounding box. Coordinates are between 0 and 1, the origin is at the top-left.

class BoxPrediction(NamedTuple):
    y_min: float  # top coordinate between 0 and 1 in height dimension
    x_min: float  # left coordinate between 0 and 1 in width dimension
    y_max: float  # bottom coordinate between 0 and 1 in height dimension
    x_max: float  # right coordinate between 0 and 1 in width dimension
    confidence: float  # between 0 and 1, higher means more confident
    id: int = 0  # the tracked identifier of this box
    class_id: int = DetectionClass.CLASS_UNKNOWN  # the class of the object

DetectionClass

class DetectionClass(Enum):
    CLASS_UNKNOWN = 0
    CLASS_PERSON = 1
    CLASS_HEAD = 2
    CLASS_FACE = 3

ErrorCodeFamiliarFaceID

Error codes returned which can be returned by the API.

class ErrorCodeFamiliarFaceID(Enum):
    # All went well
    SUCCESS = 0

    # Should not occur, contact Plumerai if this happens
    INTERNAL_ERROR = -1

    # The `delta_t` parameter should be >= 0
    INVALID_DELTA_T = -2

    # Everything OK, enrollment is in progress.
    ENROLLMENT_IN_PROGRESS = -1000

    # This library is not built with support for face identification.
    FACE_IDENTIFICATION_DISABLED = -1001

    # Called `start_face_enrollment` but enrollment is already started.
    ALREADY_ENROLLING = -1002

    # Last frame was ignored because multiple faces were detected within the
    # specified region of interest. Try again with one face visible.
    MULTIPLE_FACES_IN_VIEW = -1003

    # Last frame was ignored because no face was detected, or it was not clearly
    # visible, or the lighting was not good enough for enrollment. Try again with
    # one face clearly visible.
    NO_FACE_IN_VIEW = -1004

    # Last frame was ignored because the face detection was too close to the
    # edge. Too close to the edge means that the border of the face bounding-box
    # is within 2% of the edge of the image. In theory, it is OK if a face is
    # close to edge, however, it can also mean that a face is only partially
    # visible. Thus, this check is added to prevent partial faces from being
    # enrolled.
    FACE_CLOSE_TO_EDGE = -1005

    # Called `finish_face_enrollment` but enrollment was not started.
    NOT_ENROLLING = -1006

    # This is returned by `finish_face_enrollment` when the enrollment failed
    # because no faces could be detected in any of the images.
    ENROLLMENT_FAILED = -1007

    # This is returned by `finish_face_enrollment` when a face embedding vector
    # is returned but the quality of the enrollment is very low. In this case
    # re-enrolllment is recommended.
    LOW_QUALITY_ENROLLMENT = -1008

    # Returned by `add_face_embedding` or `remove_face_embedding` when the
    # provided face ID is invalid.
    INVALID_FACE_ID = -1009

    # Returned by `add_face_embedding` when the provided face embedding is
    # invalid.
    INVALID_EMBEDDING = -1010

Constants

FACE_ID_UNIDENTIFIED = -1
FACE_ID_NOT_IN_LIBRARY = -2

If the face-identification model is not confident about a face, it will output FACE_ID_UNIDENTIFIED. If the model is confident that a face is not in the face library, it will output FACE_ID_NOT_IN_LIBRARY.

Example usage

Below is an example of using the Python API shown above.

import numpy as np
import plumerai_people_detection as ppd_api

# Settings, to be changed as needed
width = 1600  # camera image width in pixels
height = 1200  # camera image height in pixels

# Initialize the `FaceIdentification` object
ffid = ppd_api.FaceIdentification(height, width)

# ---------------------- Enrollment starting ------------------------------

error_code = ffid.start_face_enrollment()
if error_code != ppd_api.ErrorCodeFamiliarFaceID.ENROLLMENT_IN_PROGRESS:
    raise RuntimeError(f"Error in 'start_face_enrollment': {error_code}")

# Enroll for 10 frames (just an example, more frames is better)
for t in range(10):
    # Some example input here, normally this is where camera data is acquired
    image = np.zeros((height, width, 3), dtype=np.uint8)

    # Process the frame. If the enrollment frames come from a video source,
    # then use 'process_frame' instead.
    error_code, predictions = ffid.single_image(image)
    if error_code != ppd_api.ErrorCodeFamiliarFaceID.SUCCESS:
        raise RuntimeError(f"Error in 'single_image': {error_code}")

# Finish enrollment
error_code, embedding = ffid.finish_face_enrollment()
if error_code != ppd_api.ErrorCodeFamiliarFaceID.SUCCESS:
    raise RuntimeError(f"Error in 'finish_face_enrollment': {error_code}")

# Add the embedding to the library with face id '1'.
error_code = ffid.add_face_embedding(embedding, face_id=1)
if error_code != ppd_api.ErrorCodeFamiliarFaceID.SUCCESS:
    raise RuntimeError(f"Error in 'add_face_embedding': {error_code}")

# ---------------------- Enrollment finished ------------------------------

# Loop over frames in a video stream (example: 10 frames)
for t in range(10):
    # Some example input here, normally this is where camera data is acquired
    image = np.zeros((height, width, 3), dtype=np.uint8)

    # The time between two video frames in seconds. In this example we assume
    # a constant frame rate of 30 fps, but variable rates are supported.
    delta_t = 1. / 30.

    # Process the frame
    error_code, predictions = ffid.process_frame(image, delta_t)
    if error_code != ppd_api.ErrorCodeFamiliarFaceID.SUCCESS:
      raise RuntimeError(f"Error in 'process_frame': {error_code}")

    # Display the results
    for p in predictions:
        if p.class_id != ppd_api.DetectionClass.CLASS_PERSON:
          continue
        face_id = ffid.get_face_id(p)
        print(
            f"Box #{p.id} with face ID {face_id} with confidence {p.confidence} "
            f"@(x,y)->({p.x_min:.2f},{p.y_min:.2f})-({p.x_max:.2f},{p.y_max:.2f})"
        )
    if len(predictions) == 0:
        print("No bounding boxes found in this frame")