DaisyKit: AI for Everyone
3 / 10
Lesson 3 of 10

Lesson 03: Face Detection & Landmarks

4 min readViet-Anh NguyenViet-Anh Nguyen

FaceDetectorFlow is DaisyKit's most full-featured flow. It chains two models together:

  1. YOLO Fastest — a lightweight face detector that also predicts whether the person is wearing a face mask
  2. PFLD (Practical Facial Landmark Detector) — a 98-keypoint landmark regressor that runs on each detected face crop

Face detection with landmarks and mask detection output

Configuration

import json
from daisykit.utils import get_asset_file

config = {
    "face_detection_model": {
        "model": get_asset_file(
            "models/face_detection/yolo_fastest_with_mask/yolo-fastest-opt.param"
        ),
        "weights": get_asset_file(
            "models/face_detection/yolo_fastest_with_mask/yolo-fastest-opt.bin"
        ),
        "input_width": 320,
        "input_height": 320,
        "score_threshold": 0.7,   # lower = detect more faces (more false positives)
        "iou_threshold": 0.5,     # NMS overlap threshold
        "use_gpu": False,
    },
    "with_landmark": True,        # set False to skip landmark regression (faster)
    "facial_landmark_model": {
        "model": get_asset_file("models/facial_landmark/pfld-sim.param"),
        "weights": get_asset_file("models/facial_landmark/pfld-sim.bin"),
        "input_width": 112,
        "input_height": 112,
        "use_gpu": False,
    },
}

Key parameters:

ParameterEffect
score_thresholdMin confidence to accept a detection. Lower = more detections.
iou_thresholdNMS threshold. Lower = suppress more overlapping boxes.
with_landmarkWhether to run PFLD landmark regression on each face crop.
input_width/heightDetector input size. Larger = more accurate, slower.

Real-Time Webcam Demo

import cv2
import json
import daisykit
from daisykit.utils import get_asset_file, to_py_type

config = { ... }  # as above

flow = daisykit.FaceDetectorFlow(json.dumps(config))

cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break

    # DaisyKit expects RGB
    rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

    faces = flow.Process(rgb)
    flow.DrawResult(rgb, faces)

    display = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
    cv2.imshow("Face Detection + Landmarks", display)

    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

cap.release()
cv2.destroyAllWindows()

Real-time face detection with 68 landmark dots and mask/no-mask labels

Reading Face Data

from daisykit.utils import to_py_type

rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
faces = flow.Process(rgb)
faces_py = to_py_type(faces)

for i, face in enumerate(faces_py):
    x, y, w, h = face["x"], face["y"], face["w"], face["h"]
    conf = face["confidence"]
    mask_prob = face["wearing_mask_prob"]

    print(f"Face {i}: bbox=({x},{y},{w},{h})  conf={conf:.2f}  mask={mask_prob:.2f}")

    if "landmark" in face:
        landmarks = face["landmark"]  # list of {"x": float, "y": float}
        print(f"  {len(landmarks)} landmark points")
        # Example: landmarks[30] is approximately the nose tip
        nose = landmarks[30]
        print(f"  Nose tip: ({nose['x']:.1f}, {nose['y']:.1f})")

Running on a Static Image (no webcam)

import cv2
import json
import urllib.request
import daisykit
from daisykit.utils import get_asset_file, to_py_type

urllib.request.urlretrieve("https://ultralytics.com/images/zidane.jpg", "zidane.jpg")

config = { ... }  # as above
flow = daisykit.FaceDetectorFlow(json.dumps(config))

img = cv2.imread("zidane.jpg")
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

faces = flow.Process(rgb)
print(f"Detected {len(faces)} face(s)")

flow.DrawResult(rgb, faces)
result = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
cv2.imwrite("face_result.jpg", result)
# Expected: image with green bounding boxes, landmark dots, and mask/no-mask label

Disabling Landmarks for Speed

When you only need bounding boxes (e.g., counting faces, access control), skip landmark regression:

config = {
    "face_detection_model": { ... },
    "with_landmark": False,   # skip PFLD — ~2x faster
}
flow = daisykit.FaceDetectorFlow(json.dumps(config))

Applications

  • Smart attendance systems — detect and log faces without storing biometrics
  • COVID-19 safety cameras — alert when a person is not wearing a mask
  • AR filters — use landmark positions (eyes, nose, mouth) to place virtual objects
  • Head pose estimation — derive 3D head orientation from 2D landmark positions

Conclusion

FaceDetectorFlow chains two models into a single, concurrent pipeline — you call Process() once and get both face bounding boxes and 68 landmark points. In the next lesson we switch to body analysis with human pose estimation using MoveNet.