edited the values
This commit is contained in:
@@ -36,12 +36,16 @@ enum class BowlingPhase {
|
||||
* completely independent state, and neither calls into the other.
|
||||
*
|
||||
* [update] is fed one frame's landmarks/angles at a time, in recording (or
|
||||
* live-preview) order. A posture only "counts" once it holds for
|
||||
* [requiredConsecutiveFrames] frames in a row -- this filters out a
|
||||
* momentary, correct-looking pose caught mid-transition (e.g. a fleeting
|
||||
* instant during the approach where the knee angle briefly passes through
|
||||
* the starting-stance range) -- and keeps reporting that phase for as long
|
||||
* as the posture keeps validating, reverting to null the instant it doesn't.
|
||||
* live-preview) order. A posture only "counts" once a decaying progress
|
||||
* counter (see [validFrameProgress]) climbs to [requiredConsecutiveFrames]
|
||||
* -- this filters out a momentary, correct-looking pose caught mid-transition
|
||||
* (e.g. a fleeting instant during the approach where the knee angle briefly
|
||||
* passes through the starting-stance range) while still tolerating the
|
||||
* occasional single-frame jitter a held stance sees in practice (see
|
||||
* [update]'s doc for why this decays rather than resets outright). Once
|
||||
* confirmed, it keeps reporting that phase through any invalid streak
|
||||
* shorter than [requiredInvalidFramesToExit], reverting to null only once
|
||||
* that streak runs longer.
|
||||
*
|
||||
* @param torsoTiltMinDegrees Minimum forward torso lean from vertical
|
||||
* (shoulder-midpoint-to-hip-midpoint vector vs. vertical) still
|
||||
@@ -56,19 +60,37 @@ enum class BowlingPhase {
|
||||
* considered "holding the ball in front", in degrees.
|
||||
* @param elbowAngleMaxDegrees Maximum shoulder-elbow-wrist angle still
|
||||
* considered "holding the ball in front", in degrees.
|
||||
* @param requiredConsecutiveFrames How many consecutive frames the posture
|
||||
* must validate before [update] starts reporting [BowlingPhase.STARTING_STANCE].
|
||||
* @param requiredConsecutiveFrames Target value for [validFrameProgress]
|
||||
* (which increments by 1 on a valid frame, decrements by 1 -- not
|
||||
* reset to 0 -- on an invalid one) before [update] starts reporting
|
||||
* [BowlingPhase.STARTING_STANCE]. Despite the name, this is no
|
||||
* longer a strict run of consecutive valid frames; see [update]'s doc.
|
||||
* @param requiredInvalidFramesToExit How many consecutive frames the
|
||||
* posture must fail to validate before [update] stops reporting
|
||||
* [BowlingPhase.STARTING_STANCE] once it's already been confirmed.
|
||||
* Deliberately separate from [requiredConsecutiveFrames] -- angle
|
||||
* readings jitter a couple of degrees frame-to-frame even when the
|
||||
* bowler is genuinely holding still, so reverting to null on the very
|
||||
* first out-of-range frame (as opposed to requiring several in a
|
||||
* row, same as confirming the stance in the first place) makes the
|
||||
* label flicker between "confirmed" and "waiting" on that jitter
|
||||
* alone rather than on an actual change of posture.
|
||||
*/
|
||||
class PosePhaseDetector(
|
||||
private val torsoTiltMinDegrees: Float = 10f,
|
||||
private val torsoTiltMinDegrees: Float = 2f,
|
||||
private val torsoTiltMaxDegrees: Float = 15f,
|
||||
private val kneeAngleMinDegrees: Float = 160f,
|
||||
private val kneeAngleMaxDegrees: Float = 175f,
|
||||
private val kneeAngleMinDegrees: Float = 150f,
|
||||
private val kneeAngleMaxDegrees: Float = 180f,
|
||||
private val elbowAngleMinDegrees: Float = 70f,
|
||||
private val elbowAngleMaxDegrees: Float = 110f,
|
||||
private val requiredConsecutiveFrames: Int = 8
|
||||
private val requiredConsecutiveFrames: Int = 8,
|
||||
private val requiredInvalidFramesToExit: Int = 5
|
||||
) {
|
||||
private var consecutiveValidFrames = 0
|
||||
// Decaying progress toward requiredConsecutiveFrames -- see update()'s
|
||||
// doc for why this decays by one on an invalid frame rather than
|
||||
// resetting to 0 outright.
|
||||
private var validFrameProgress = 0
|
||||
private var consecutiveInvalidFrames = 0
|
||||
private var currentPhase: BowlingPhase? = null
|
||||
|
||||
/**
|
||||
@@ -109,9 +131,9 @@ class PosePhaseDetector(
|
||||
* [PoseAngleCalculator]) -- elbow angles are reused from here
|
||||
* rather than recomputed, so this detector doesn't duplicate that math.
|
||||
* @return This frame's [Metrics] alongside [BowlingPhase.STARTING_STANCE]
|
||||
* once the posture has validated for [requiredConsecutiveFrames]
|
||||
* frames in a row and keeps validating on this frame too,
|
||||
* otherwise alongside a null phase.
|
||||
* once [validFrameProgress] has climbed to [requiredConsecutiveFrames],
|
||||
* continuing to report it through brief invalid streaks shorter
|
||||
* than [requiredInvalidFramesToExit], otherwise alongside a null phase.
|
||||
*/
|
||||
fun update(landmarks: Map<Int, SmoothedLandmark>, angles: PoseAngles): Result {
|
||||
val metrics = Metrics(
|
||||
@@ -122,14 +144,39 @@ class PosePhaseDetector(
|
||||
rightElbowAngleDegrees = angles.rightElbow
|
||||
)
|
||||
val isValid = isStartingStanceValid(metrics)
|
||||
consecutiveValidFrames = if (isValid) consecutiveValidFrames + 1 else 0
|
||||
currentPhase = if (consecutiveValidFrames >= requiredConsecutiveFrames) BowlingPhase.STARTING_STANCE else null
|
||||
if (isValid) {
|
||||
// Capped at the threshold rather than left to grow unbounded, so
|
||||
// a long-held stance doesn't need an equally long invalid streak
|
||||
// to ever start climbing back down once it's fully confirmed.
|
||||
validFrameProgress = (validFrameProgress + 1).coerceAtMost(requiredConsecutiveFrames)
|
||||
consecutiveInvalidFrames = 0
|
||||
} else {
|
||||
// A step back, not a hard reset to 0 -- torso/knee/elbow angles
|
||||
// all have to validate *simultaneously* every frame, and with
|
||||
// five independent noisy readings it's easy for one to blip out
|
||||
// of range for a single frame even while the bowler holds
|
||||
// genuinely still. Resetting to 0 on that alone meant progress
|
||||
// could almost never reach requiredConsecutiveFrames; decaying
|
||||
// by one instead still requires a mostly-valid run to confirm,
|
||||
// just without one blip erasing everything before it.
|
||||
validFrameProgress = (validFrameProgress - 1).coerceAtLeast(0)
|
||||
consecutiveInvalidFrames++
|
||||
}
|
||||
|
||||
currentPhase = when {
|
||||
validFrameProgress >= requiredConsecutiveFrames -> BowlingPhase.STARTING_STANCE
|
||||
// Already confirmed -- a short invalid streak alone (jitter,
|
||||
// not necessarily a real change of posture) doesn't clear it.
|
||||
currentPhase == BowlingPhase.STARTING_STANCE && consecutiveInvalidFrames < requiredInvalidFramesToExit -> BowlingPhase.STARTING_STANCE
|
||||
else -> null
|
||||
}
|
||||
return Result(currentPhase, metrics)
|
||||
}
|
||||
|
||||
/** @brief Clears all detection state. Call at the start of a new session/attempt. */
|
||||
fun reset() {
|
||||
consecutiveValidFrames = 0
|
||||
validFrameProgress = 0
|
||||
consecutiveInvalidFrames = 0
|
||||
currentPhase = null
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<string name="error_camera_unavailable">Camera unavailable: %1$s</string>
|
||||
<string name="error_recording_failed">Recording failed: %1$s</string>
|
||||
<string name="error_pose_detector">Pose detector error: %1$s</string>
|
||||
<string name="pose_phase_starting_stance">✓ Starting pose</string>
|
||||
<string name="pose_phase_starting_stance">Starting pose</string>
|
||||
<string name="pose_phase_waiting">Get into starting pose</string>
|
||||
<string name="pose_metrics_format">Torso %1$s · Knee L%2$s R%3$s · Elbow L%4$s R%5$s</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user