Sythra

Article

Count fingers with MediaPipe in Python

Hold up three fingers and Python says three. Learn the landmark logic behind finger counting, including the thumb rule that trips everyone up.

vaibhavkothari· Aug 26, 2026· 6 min readIntermediate
ShareXLinkedIn
Count fingers with MediaPipe in Python cover

Finger counting is the "hello world" of hand tracking. It takes about forty lines, it runs on any laptop webcam, and once you understand the trick behind it you can build gesture controls for anything.

The idea is small: MediaPipe gives you 21 points on the hand. For each finger, compare the tip to the joint below it. If the tip is higher up the screen, the finger is extended.

The thumb is the exception, and that exception is why most tutorials give wrong counts.

What you need

python -m pip install opencv-python mediapipe

Python 3.8–3.12. MediaPipe usually does not support the newest release for a few months.

Step 1 — Understand the landmark numbers

MediaPipe numbers 21 landmarksKey points on the hand — the wrist is 0, fingertips are 4, 8, 12, 16, 20 per hand. You need eight of them:

FingerTipJoint below (PIP)
Thumb42
Index86
Middle1210
Ring1614
Pinky2018

Landmark 0 is the wrist. The pattern is regular: each finger's tip is the last of its four points, counting outward from the wrist.

Step 2 — Get landmarks on screen

import cv2
import mediapipe as mp

mp_hands = mp.solutions.hands
mp_draw = mp.solutions.drawing_utils

hands = mp_hands.Hands(
    max_num_hands=2,
    min_detection_confidence=0.7,
    min_tracking_confidence=0.6,
)

cap = cv2.VideoCapture(0)

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

    frame = cv2.flip(frame, 1)
    result = hands.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))

    if result.multi_hand_landmarks:
        for hand in result.multi_hand_landmarks:
            mp_draw.draw_landmarks(frame, hand, mp_hands.HAND_CONNECTIONS)

    cv2.imshow("Finger Counter", frame)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

cap.release()
cv2.destroyAllWindows()

Step 3 — Count the four straight fingers

Screen coordinates run downwards: y = 0 is the top of the frame. So a raised fingertip has a smaller y than the joint beneath it.

FINGER_TIPS = [8, 12, 16, 20]     # index, middle, ring, pinky

def count_straight_fingers(hand):
    count = 0
    for tip in FINGER_TIPS:
        tip_y = hand.landmark[tip].y
        pip_y = hand.landmark[tip - 2].y      # the joint two points below
        if tip_y < pip_y:                     # tip is higher on screen
            count += 1
    return count

tip - 2 works for all four because the landmarks are evenly spaced: tip 8 → joint 6, tip 12 → joint 10, and so on.

This assumes the hand is upright. Point your fingers sideways and it breaks — handled in Step 6.

Step 4 — Handle the thumb correctly

The thumb does not fold down; it folds sideways across the palm. Comparing y values gives nonsense. Compare x instead — and the direction depends on which hand you are looking at.

MediaPipe tells you the handedness, so use it:

def thumb_is_out(hand, handedness_label):
    tip_x = hand.landmark[4].x
    joint_x = hand.landmark[2].x

    if handedness_label == "Right":
        return tip_x > joint_x
    return tip_x < joint_x

One important gotcha: because you mirrored the frame with cv2.flip, MediaPipe's "Right" is your left hand on screen. Either flip the label or accept the mirrored naming — just be consistent, or your thumb count silently inverts for one hand.

Step 5 — Put the counter together

import cv2
import mediapipe as mp

mp_hands = mp.solutions.hands
mp_draw = mp.solutions.drawing_utils
hands = mp_hands.Hands(max_num_hands=2, min_detection_confidence=0.7)

FINGER_TIPS = [8, 12, 16, 20]

def count_fingers(hand, label):
    total = 0

    tip_x, joint_x = hand.landmark[4].x, hand.landmark[2].x
    if (label == "Right" and tip_x > joint_x) or (label == "Left" and tip_x < joint_x):
        total += 1

    for tip in FINGER_TIPS:
        if hand.landmark[tip].y < hand.landmark[tip - 2].y:
            total += 1

    return total

cap = cv2.VideoCapture(0)

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

    frame = cv2.flip(frame, 1)
    result = hands.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))

    total = 0
    if result.multi_hand_landmarks and result.multi_handedness:
        pairs = zip(result.multi_hand_landmarks, result.multi_handedness)
        for hand, handedness in pairs:
            label = handedness.classification[0].label
            total += count_fingers(hand, label)
            mp_draw.draw_landmarks(frame, hand, mp_hands.HAND_CONNECTIONS)

    cv2.rectangle(frame, (20, 20), (170, 140), (255, 120, 40), cv2.FILLED)
    cv2.putText(frame, str(total), (55, 115),
                cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 255, 255), 5)

    cv2.imshow("Finger Counter", frame)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

cap.release()
cv2.destroyAllWindows()

Hold up both hands and it counts up to ten.

Step 6 — Make it robust

Stop the flicker. A count that wobbles between 2 and 3 is annoying. Keep the last few readings and report the most common one:

from collections import deque, Counter

history = deque(maxlen=7)

def stable_count(current):
    history.append(current)
    return Counter(history).most_common(1)[0][0]

Seven frames is about a quarter of a second — long enough to smooth noise, short enough to feel instant.

Handle rotated hands. Instead of comparing to the joint, compare each tip's distance from the wrist against the joint's distance from the wrist. This works at any angle:

import math

def finger_extended(hand, tip):
    wrist = hand.landmark[0]
    tip_pt = hand.landmark[tip]
    pip_pt = hand.landmark[tip - 2]

    d_tip = math.dist((tip_pt.x, tip_pt.y), (wrist.x, wrist.y))
    d_pip = math.dist((pip_pt.x, pip_pt.y), (wrist.x, wrist.y))
    return d_tip > d_pip

Step 7 — Turn counts into commands

Once counting is stable, gestures are trivial:

ACTIONS = {
    0: "pause",
    1: "play",
    2: "next track",
    3: "previous track",
    5: "stop",
}

action = ACTIONS.get(stable_count(total))
if action:
    print("Action:", action)

Add a short cooldown so one gesture does not fire thirty times per second, and you have a working gesture remote. The same landmark data drives the gesture volume controller and the virtual mouse.

Common problems (and fixes)

ProblemFix
Thumb always countedYou are using the y comparison; use x with handedness
Count flickersUse the deque + Counter majority vote
Wrong when hand is sidewaysUse the wrist-distance method from Step 6
Left/right swappedcv2.flip mirrors the frame; flip the label too
Nothing detectedBetter lighting, hand 40–60 cm from the camera
mediapipe will not installUse Python 3.8–3.12

FAQ

How do I count fingers with MediaPipe in Python?

Compare each fingertip landmark to the joint two positions below it. If the tip's y value is smaller, the finger is up. Handle the thumb separately by comparing x values, using the reported handedness.

Why does the thumb break finger counting?

The thumb folds sideways across the palm rather than downwards, so a vertical comparison gives the wrong answer. Compare the thumb tip's x coordinate to the joint below it and pick the direction based on which hand it is.

Which MediaPipe landmarks are the fingertips?

Landmarks 4, 8, 12, 16 and 20 are the thumb, index, middle, ring and pinky tips. The wrist is landmark 0.

How do I stop the finger count from flickering?

Store the last seven counts in a deque and report the most common value with collections.Counter. This majority vote removes single-frame noise without noticeable lag.

Can MediaPipe count fingers on both hands?

Yes. Set max_num_hands=2, loop over multi_hand_landmarks, and add the counts together for a total up to ten.

Next reading on Sythra Articles

Keep reading

Related articles

Newsletter

Get new articles

Free essays on learning, Python, and ML — no account required. We’ll only email when there’s something worth reading.