Building an AI-Powered Squat Counter App with QuickPose iOS SDK
Are you interested in developing an AI-powered squat counter for your fitness app? Squats are a cornerstone exercise in strength training, known for their ability to target multiple muscle groups, including the quadriceps, hamstrings, glutes, and lower back. This compound movement not only builds strength and endurance in the lower body but also engages the core for stability.
In this guide, you’ll learn how to create a squat counter app using the QuickPose iOS SDK. We’ll walk you through the process of implementing real-time squat counting, providing form corrections, and customising feedback to enhance user engagement. Whether you’re building a new fitness app or adding advanced features to an existing one, this tutorial will help you leverage AI to deliver an accurate and user-friendly squat counter that sets your app apart from the competition.
Steps to integrate an AI Squat Counter into your app:
Register an SDK Key with QuickPose
Get your free SDK key on https://dev.quickpose.ai, usage limits may apply. SDK Keys are linked to your bundle ID, please check Key before distributing to the App Store.
This is a quick look to integrate the squat counter using the QuickPose iOS SDK. You can see the full documentation here: QuickPose iOS SDK Squat Counter installation.
Activate Squats Feature
feature = .fitness(.squats)
feature = .fitness(.squats, style: customOrConditionalStyle)
Give your users feedback on their squat form
We recommend using the Feedback Feature to provide form feedback to users.
quickPose.start(features: [.fitness(.squats)], onFrame: { status, image, features, feedback, landmarks in
switch status {
case .success:
overlayImage = image
if let result = features.values.first {
feedbackText = "Squats: \(Int(result.value * 100))%"
} else if let feedback = feedback.values.first, feedback.isRequired {
feedbackText = feedback.displayString
} else {
feedbackText = nil
}
case .noPersonFound:
feedbackText = "Stand in view";
case .sdkValidationError:
feedbackText = "Be back soon";
}
})
Conditional Styling
To give user feedback consider using conditional styling so that when the user’s measurement goes above a threshold, here 0.8, a green highlight is shown.
let greenHighlightStyle = QuickPose.Style(conditionalColors: [QuickPose.Style.ConditionalColor(min: 0.8, max: nil, color: UIColor.green)])
quickPose.start(features: [.fitness(.squats, style: customOrConditionalStyle)],
onFrame: { status, image, features, feedback, landmarks in ...
})
How to Count Squats
Real-time squat counting with AI
To count the Squats declare a configurable threshold counter, which can be used to turn lots of our features into counts.
@State private var counter = QuickPoseThresholdCounter()
Then pass QuickPose’s squat result to the counter, and display in the feedback text declared above.
quickPose.start(features: [.fitness(.squats)], onFrame: { status, image, features, feedback, landmarks in
switch status {
case .success:
overlayImage = image
if let result = features.values.first {
let counterState = counter.count(result.value)
feedbackText = "\(counterState.count) Squats"
} else {
feedbackText = nil
}
case .noPersonFound:
feedbackText = "Stand in view";
case .sdkValidationError:
feedbackText = "Be back soon";
}
})
Squat Timer
To time the Squats declare a configurable threshold timer, which can be used to turn lots of our features into timers. For Squats, we suggest modifying the default threshold, taking account of expected camera positioning and tilt.
@State private var timer = QuickPoseThresholdTimer(threshold: 0.2)
Then pass the result’s raw value to the timer, and display in the feedback text declared above.
quickPose.start(features: [.fitness(.squats)], onFrame: { status, image, features, feedback, landmarks in
switch status {
case .success:
overlayImage = image
if let result = features.values.first {
let timerState = timer.time(result.value)
feedbackText = String(format: "%.1f", timerState.time) + "secs"
} else {
feedbackText = nil
}
case .noPersonFound:
feedbackText = "Stand in view";
case .sdkValidationError:
feedbackText = "Be back soon";
}
})