Skip to content

Analyzing video

Dynaface analyzes a video one frame at a time, each frame just like a photo. Read the frames with OpenCV, convert each one from BGR to RGB, and give it to load_image():

import cv2
from dynaface import facial, measures, models
from dynaface.measures_frontal import AnalyzeLandmarks

models.init_models(models.download_models(), models.detect_device())
chosen = [
    m
    for m in measures.all_measures()
    if m.is_frontal and not isinstance(m, AnalyzeLandmarks)
]

video = cv2.VideoCapture("smile.mp4")
fps = video.get(cv2.CAP_PROP_FPS)
rows = []
while True:
    ok, frame = video.read()
    if not ok:
        break
    face = facial.AnalyzeFace(measures=chosen)
    face.load_image(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB), crop=True)
    row = {"frame": len(rows), "time": len(rows) / fps}
    results = face.analyze()
    if results is not None:  # None for a frame without a face
        row.update({name: results.get(name) for name in face.get_all_items()})
    rows.append(row)
video.release()

A frame without a face, such as one where the subject turns away, gets a row with only its frame number and time. Write the rows to a CSV file as in Measuring a folder of photos, or load them into pandas with pandas.DataFrame(rows).

Finding a moment

The rows make it easy to find a moment in the video, such as the peak of a smile, where the dental display is largest, and to look at the other measures at that moment:

with_face = [row for row in rows if "dental_area" in row]
smile = max(with_face, key=lambda row: row["dental_area"])
print(f"Widest smile at {smile['time']:.2f} s, eye.ratio = {smile['eye.ratio']:.2f}")

Comparing the eyes at the widest smile with the mouth at the widest eye opening is the kind of comparison used in studies of oral-ocular synkinesis. The Evaluation report in the Dynaface apps makes it for you.

Keeping the analysis

Finding the face and its landmarks is the slow part. dump_state() captures a frame's analysis, including its standard image, landmarks and scale, and load_state() restores it without running the models again. To keep every frame's state, create a list before the loop, states = [], and add states.append(face.dump_state()) after load_image(). You can then calculate other measures later:

for state in states:
    face = facial.AnalyzeFace(measures=[measures.AnalyzeEyeArea()])
    face.load_state(state)
    results = face.analyze()

Restore each state into a new AnalyzeFace, as above, so that every frame is drawn on its own image. Each state holds its frame's 1024 by 1024 image, about 3 MB, so the states of a long video take a lot of memory. The desktop app keeps them in its .dyfc project files.

Speed

Every frame goes through the face detector and the landmark model, so analyzing a video takes much longer than playing it, especially on a CPU. Trim videos to the parts you need, and use a GPU if you have one (see Models and devices).

The example script

examples/process_media.py measures every frame of a video and writes an annotated copy of the video, a chart of each measure over time and a CSV file of the values. The examples page shows how to run it.