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

Lesson 04: Human Pose Estimation

4 min readViet-Anh NguyenViet-Anh Nguyen

HumanPoseMoveNetFlow chains two models:

  1. SSD-MobileNetV2 — a fast person detector that crops each person from the frame
  2. MoveNet Lightning — Google's lightweight keypoint regressor, ported to NCNN

The result is 17 body keypoints per person: nose, eyes, ears, shoulders, elbows, wrists, hips, knees, ankles.

Human pose estimation output: skeleton overlay on detected people

Configuration

import json
from daisykit.utils import get_asset_file

config = {
    "person_detection_model": {
        "model": get_asset_file(
            "models/human_detection/ssd_mobilenetv2.param"
        ),
        "weights": get_asset_file(
            "models/human_detection/ssd_mobilenetv2.bin"
        ),
        "input_width": 320,
        "input_height": 320,
        "use_gpu": False,
    },
    "human_pose_model": {
        "model": get_asset_file(
            "models/human_pose_detection/movenet/lightning.param"
        ),
        "weights": get_asset_file(
            "models/human_pose_detection/movenet/lightning.bin"
        ),
        "input_width": 192,
        "input_height": 192,
        "use_gpu": False,
    },
}

MoveNet variants:

VariantInput SizeSpeedAccuracyBest For
Lightning192×192FasterLowerReal-time webcam, mobile
Thunder256×256SlowerHigherFitness tracking, sports

To use Thunder, replace lightning with thunder in the model paths.

Real-Time Webcam Demo

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

config = { ... }  # as above

flow = HumanPoseMoveNetFlow(json.dumps(config))
cap = cv2.VideoCapture(0)

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

    rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

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

    display = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
    cv2.imshow("Human Pose Estimation", display)

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

cap.release()
cv2.destroyAllWindows()

Human pose estimation in action — skeleton connects 17 keypoints across the body

Reading Pose Keypoints

MoveNet outputs 17 keypoints indexed 0–16. The order follows the COCO keypoint convention:

KEYPOINT_NAMES = [
    "nose", "left_eye", "right_eye", "left_ear", "right_ear",
    "left_shoulder", "right_shoulder", "left_elbow", "right_elbow",
    "left_wrist", "right_wrist", "left_hip", "right_hip",
    "left_knee", "right_knee", "left_ankle", "right_ankle",
]

rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
poses = flow.Process(rgb)
poses_py = to_py_type(poses)

for person in poses_py:
    keypoints = person.get("keypoints", [])
    for i, kp in enumerate(keypoints):
        if kp.get("score", 0) > 0.3:   # filter low-confidence points
            x, y, score = kp["x"], kp["y"], kp["score"]
            print(f"  {KEYPOINT_NAMES[i]:15s}: ({x:.0f}, {y:.0f})  score={score:.2f}")

Building a Rep Counter (Push-ups)

A practical example using elbow angle to count push-up repetitions:

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

def angle(a, b, c):
    """Angle at point b formed by a-b-c."""
    ab = (a[0]-b[0], a[1]-b[1])
    cb = (c[0]-b[0], c[1]-b[1])
    dot = ab[0]*cb[0] + ab[1]*cb[1]
    mag = math.sqrt(ab[0]**2+ab[1]**2) * math.sqrt(cb[0]**2+cb[1]**2)
    if mag == 0:
        return 0
    return math.degrees(math.acos(max(-1, min(1, dot/mag))))

config = { ... }
flow = HumanPoseMoveNetFlow(json.dumps(config))
cap = cv2.VideoCapture(0)
reps = 0
down = False

while True:
    ret, frame = cap.read()
    if not ret:
        break
    rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    poses = flow.Process(rgb)
    flow.DrawResult(rgb, poses)

    poses_py = to_py_type(poses)
    for person in poses_py:
        kps = person.get("keypoints", [])
        if len(kps) >= 11 and all(kps[i]["score"] > 0.3 for i in [5, 7, 9]):
            shoulder = (kps[5]["x"], kps[5]["y"])
            elbow    = (kps[7]["x"], kps[7]["y"])
            wrist    = (kps[9]["x"], kps[9]["y"])
            elbow_angle = angle(shoulder, elbow, wrist)

            if elbow_angle < 90 and not down:
                down = True
            elif elbow_angle > 160 and down:
                reps += 1
                down = False

    display = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
    cv2.putText(display, f"Reps: {reps}", (20, 50),
                cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 3)
    cv2.imshow("Push-up Counter", display)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

cap.release()
cv2.destroyAllWindows()
# Expected: live skeleton with rep counter that increments each push-up

Applications

  • Fitness apps — count reps, measure range of motion, detect bad form
  • Sports analytics — track athlete movement and joint angles
  • AR games — use body position to control game characters
  • Fall detection — trigger alert when a person's keypoints indicate a fall

Conclusion

HumanPoseMoveNetFlow gives you 17-keypoint body skeletons in real time with just a few lines of configuration. Combined with simple geometry (joint angles, distances), it powers fitness counters, pose classifiers, and interactive applications. Next, we use AI to replace video backgrounds.