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.
On this page0%
- What you need
- Step 1 — Understand the landmark numbers
- Step 2 — Get landmarks on screen
- Step 3 — Count the four straight fingers
- Step 4 — Handle the thumb correctly
- Step 5 — Put the counter together
- Step 6 — Make it robust
- Step 7 — Turn counts into commands
- Common problems (and fixes)
- FAQ
- How do I count fingers with MediaPipe in Python?
- Why does the thumb break finger counting?
- Which MediaPipe landmarks are the fingertips?
- How do I stop the finger count from flickering?
- Can MediaPipe count fingers on both hands?
- Next reading on Sythra Articles
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:
| Finger | Tip | Joint below (PIP) |
|---|---|---|
| Thumb | 4 | 2 |
| Index | 8 | 6 |
| Middle | 12 | 10 |
| Ring | 16 | 14 |
| Pinky | 20 | 18 |
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)
| Problem | Fix |
|---|---|
| Thumb always counted | You are using the y comparison; use x with handedness |
| Count flickers | Use the deque + Counter majority vote |
| Wrong when hand is sideways | Use the wrist-distance method from Step 6 |
| Left/right swapped | cv2.flip mirrors the frame; flip the label too |
| Nothing detected | Better lighting, hand 40–60 cm from the camera |
mediapipe will not install | Use 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.