← All posts

Pull-Up Counter: Counting Reps With Shoulders and Elbows

2023CV · Python

The push-up counter proved that elbow angles could track reps reliably. Pull-ups use the same joints but a different motion. I wanted to see if the same approach worked when the camera faces your back instead of your front.

It does. The pull-up counter is 57 lines of Python. A webcam pointed at your back, MediaPipe Pose tracking your shoulders and elbows, and a state machine that counts each rep exactly once.

The Detection

The check is simple. If both elbows are above both shoulders, you are at the top of a pull-up. The function reads the landmark coordinates from MediaPipe and compares the y values. Shoulders lower than elbows means full arm extension. That is the entire detection logic.

The camera faces your back so the landmarks are visible even when you are directly under the bar. MediaPipe handles the mirrored geometry without any special configuration.

One Detail

Counting every frame where elbows are above shoulders would overflow the counter in seconds. The fix is a boolean flag called has_reached_top. It tracks whether the current rep has already been counted. Once elbows drop below shoulders, the flag resets. One rep per cycle.

That flag is the only state in the whole program. No arrays. No history. No calibration. It is enough.

Where It Stops

This is a companion to the push-up counter, not a replacement. It has the same limitations. Bad lighting breaks the landmark detection. Fast reps get missed when the camera frame rate cannot keep up. It assumes the camera is positioned correctly behind the user. There is no calibration for different body proportions or arm lengths.

Fifty-seven lines cannot solve every edge case. They do not need to. The project taught me that the hardest part of exercise tracking is not the pose detection. It is knowing when a rep starts and ends. That is a state problem, not a vision problem.

A boolean flag and a shoulder check. That is all it took.

View all projects →View on GitHub ↗