Counting repetitions sounds simple, but breaks down on edge cases. A user performs a squat slowly — one rep. Pulsates at the top — the model counts two. The phone tilts — coordinates shift, counter misreads. In our practice, we faced a fitness app project where counting accuracy was only 60% due to camera shake and varying user builds. After implementing adaptive calibration and time-series smoothing, accuracy rose to 92%. This algorithm now processes up to 30 frames per second on five-year-old devices. Apple Vision documentation recommends a side camera view for better push-up accuracy, but we adapted the solution to the front camera — more convenient for the user.
How AI Rep Counting Works
The approach is the same as in posture analysis: VNDetectHumanBodyPoseRequest (iOS) or ML Kit Pose Detection (Android) outputs skeleton keypoints on each frame. But for rep counting, we are interested not in static pose but in the movement of a keypoint over time.
For each exercise, we select a tracking point:
| Exercise | Tracking Point | Axis |
|---|---|---|
| Squat | Hip (leftHip / rightHip) | Y |
| Push-up | Wrist or shoulder | Y |
| Bicep curl | Wrist | Y |
| Burpee | Wrist + head | Y complex |
| Lunge | Knee | Y |
class RepCounter {
private var positionHistory: [Double] = []
private var repCount: Int = 0
private var state: MovementState = .neutral
private let minAmplitude: Double = 0.08 // 8% of screen height
enum MovementState { case neutral, goingDown, bottom, goingUp, top }
func update(normalizedY: Double) {
positionHistory.append(normalizedY)
if positionHistory.count > 30 { positionHistory.removeFirst() }
let smoothed = positionHistory.suffix(5).reduce(0, +) / 5
detectRep(smoothedY: smoothed)
}
private func detectRep(smoothedY: Double) {
let baseline = positionHistory.prefix(10).reduce(0, +) / 10
let deviation = smoothedY - baseline
switch state {
case .neutral where deviation > minAmplitude:
state = .goingDown
case .goingDown where deviation < minAmplitude * 0.3:
state = .bottom
case .bottom where deviation > minAmplitude * 0.7:
state = .goingUp
case .goingUp where deviation < 0:
state = .top
repCount += 1
onRepCompleted?(repCount)
state = .neutral
default: break
}
}
}
A moving average over 5 frames (suffix(5)) smooths pose estimation noise. Without it, the counter jitters from keypoint fluctuations between frames.
Why Calibration Matters
minAmplitude = 0.08 is a percentage of screen height. It works for squats, but for bicep curls a larger value is needed (wider range), and for push-ups a smaller value with a different axis.
We perform calibration either analytically (train on labeled videos) or via an adaptive baseline: the first 3 seconds of exercise measure the movement amplitude and adjust thresholds.
class AdaptiveRepCounter {
private var calibrationPhase = true
private var calibrationSamples: [Double] = []
private var dynamicAmplitude: Double = 0.05
func calibrate(y: Double) {
calibrationSamples.append(y)
if calibrationSamples.count >= 75 { // ~3 seconds at 25fps
let range = calibrationSamples.max()! - calibrationSamples.min()!
dynamicAmplitude = range * 0.4 // 40% of observed amplitude
calibrationPhase = false
}
}
}
Additional: false positive filtering
Pulsating movements (e.g., shaking) can erroneously trigger a rep. To counter this, we have introduced a minimum time delay between phases — at least 200 ms. This filters fast fluctuations without losing accuracy at normal speed.
How to Choose Tracking Point for an Exercise
Point selection depends on movement amplitude. For squats, the hip is best because it moves significantly up and down. For push-ups, wrist or shoulder works since the body stays straight. For bicep curls, the wrist suffices. For complex exercises (burpees), we use two points and analyze their combination. Our engineers will select the optimal configuration for your exercise set — contact us.
What's Included in the Work
- Requirements analysis: define exercise set and recording conditions
- Pose estimation integration: VNDetectHumanBodyPoseRequest for iOS or ML Kit for Android
- Rep counting algorithm implementation: smoothing, phase detection, adaptive calibration
- Skeleton and counter UI: display of points and rep animation
- Real-user testing: accuracy verification on different phones and lighting conditions
- Documentation and training: code delivery and calibration instructions
Camera orientation: side vs front view. The same task requires different solutions depending on camera position:
| Position | Advantages | Disadvantages |
|---|---|---|
| Front view (camera in front of user) | Both shoulders, hips, head visible. Good for squats, lunges, burpees. | Less accurate for push-ups — wrists sometimes occlude. |
| Side view | Better for knee angle analysis (squat technique), more accurate for push-ups. | Requires tripod/stand, inconvenient in real conditions. |
Most applications optimize for front view with instructions "place the phone 2 meters in front of you at waist level." Contact us for project evaluation — we will analyze your requirements and offer an optimal solution.
Work Process
- Analytics: discuss target exercises and usage scenarios
- Design: choose approach (front-end/back-end) and stack
- Implementation: integrate pose estimation, write counting algorithm, adaptive calibration, UI overlay
- Testing: verify on real devices with different users
- Deployment: deliver code, documentation, support
Duration Estimates
Counting 3-5 basic exercises — from 1 to 2 weeks. Automatic exercise recognition + full workout tracking — from 3 to 5 weeks. Development budget varies depending on complexity and number of exercises. We work with major fitness platforms and guarantee at least 90% accuracy after calibration. Over 10 years of mobile development experience, over 50 successful projects. Contact us for a consultation on implementing AI rep counting in your app.
Feedback and UI
A large, high-contrast animated counter, UIImpactFeedbackGenerator(style: .rigid), and a visual flash on each rep. Overlay skeleton lines between keypoints on the camera feed. Red skeleton when confidence is low.







