edited the values
This commit is contained in:
@@ -36,12 +36,16 @@ enum class BowlingPhase {
|
|||||||
* completely independent state, and neither calls into the other.
|
* completely independent state, and neither calls into the other.
|
||||||
*
|
*
|
||||||
* [update] is fed one frame's landmarks/angles at a time, in recording (or
|
* [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
|
* live-preview) order. A posture only "counts" once a decaying progress
|
||||||
* [requiredConsecutiveFrames] frames in a row -- this filters out a
|
* counter (see [validFrameProgress]) climbs to [requiredConsecutiveFrames]
|
||||||
* momentary, correct-looking pose caught mid-transition (e.g. a fleeting
|
* -- this filters out a momentary, correct-looking pose caught mid-transition
|
||||||
* instant during the approach where the knee angle briefly passes through
|
* (e.g. a fleeting instant during the approach where the knee angle briefly
|
||||||
* the starting-stance range) -- and keeps reporting that phase for as long
|
* passes through the starting-stance range) while still tolerating the
|
||||||
* as the posture keeps validating, reverting to null the instant it doesn't.
|
* 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
|
* @param torsoTiltMinDegrees Minimum forward torso lean from vertical
|
||||||
* (shoulder-midpoint-to-hip-midpoint vector vs. vertical) still
|
* (shoulder-midpoint-to-hip-midpoint vector vs. vertical) still
|
||||||
@@ -56,19 +60,37 @@ enum class BowlingPhase {
|
|||||||
* considered "holding the ball in front", in degrees.
|
* considered "holding the ball in front", in degrees.
|
||||||
* @param elbowAngleMaxDegrees Maximum shoulder-elbow-wrist angle still
|
* @param elbowAngleMaxDegrees Maximum shoulder-elbow-wrist angle still
|
||||||
* considered "holding the ball in front", in degrees.
|
* considered "holding the ball in front", in degrees.
|
||||||
* @param requiredConsecutiveFrames How many consecutive frames the posture
|
* @param requiredConsecutiveFrames Target value for [validFrameProgress]
|
||||||
* must validate before [update] starts reporting [BowlingPhase.STARTING_STANCE].
|
* (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(
|
class PosePhaseDetector(
|
||||||
private val torsoTiltMinDegrees: Float = 10f,
|
private val torsoTiltMinDegrees: Float = 2f,
|
||||||
private val torsoTiltMaxDegrees: Float = 15f,
|
private val torsoTiltMaxDegrees: Float = 15f,
|
||||||
private val kneeAngleMinDegrees: Float = 160f,
|
private val kneeAngleMinDegrees: Float = 150f,
|
||||||
private val kneeAngleMaxDegrees: Float = 175f,
|
private val kneeAngleMaxDegrees: Float = 180f,
|
||||||
private val elbowAngleMinDegrees: Float = 70f,
|
private val elbowAngleMinDegrees: Float = 70f,
|
||||||
private val elbowAngleMaxDegrees: Float = 110f,
|
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
|
private var currentPhase: BowlingPhase? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,9 +131,9 @@ class PosePhaseDetector(
|
|||||||
* [PoseAngleCalculator]) -- elbow angles are reused from here
|
* [PoseAngleCalculator]) -- elbow angles are reused from here
|
||||||
* rather than recomputed, so this detector doesn't duplicate that math.
|
* rather than recomputed, so this detector doesn't duplicate that math.
|
||||||
* @return This frame's [Metrics] alongside [BowlingPhase.STARTING_STANCE]
|
* @return This frame's [Metrics] alongside [BowlingPhase.STARTING_STANCE]
|
||||||
* once the posture has validated for [requiredConsecutiveFrames]
|
* once [validFrameProgress] has climbed to [requiredConsecutiveFrames],
|
||||||
* frames in a row and keeps validating on this frame too,
|
* continuing to report it through brief invalid streaks shorter
|
||||||
* otherwise alongside a null phase.
|
* than [requiredInvalidFramesToExit], otherwise alongside a null phase.
|
||||||
*/
|
*/
|
||||||
fun update(landmarks: Map<Int, SmoothedLandmark>, angles: PoseAngles): Result {
|
fun update(landmarks: Map<Int, SmoothedLandmark>, angles: PoseAngles): Result {
|
||||||
val metrics = Metrics(
|
val metrics = Metrics(
|
||||||
@@ -122,14 +144,39 @@ class PosePhaseDetector(
|
|||||||
rightElbowAngleDegrees = angles.rightElbow
|
rightElbowAngleDegrees = angles.rightElbow
|
||||||
)
|
)
|
||||||
val isValid = isStartingStanceValid(metrics)
|
val isValid = isStartingStanceValid(metrics)
|
||||||
consecutiveValidFrames = if (isValid) consecutiveValidFrames + 1 else 0
|
if (isValid) {
|
||||||
currentPhase = if (consecutiveValidFrames >= requiredConsecutiveFrames) BowlingPhase.STARTING_STANCE else null
|
// 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)
|
return Result(currentPhase, metrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Clears all detection state. Call at the start of a new session/attempt. */
|
/** @brief Clears all detection state. Call at the start of a new session/attempt. */
|
||||||
fun reset() {
|
fun reset() {
|
||||||
consecutiveValidFrames = 0
|
validFrameProgress = 0
|
||||||
|
consecutiveInvalidFrames = 0
|
||||||
currentPhase = null
|
currentPhase = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<string name="error_camera_unavailable">Camera unavailable: %1$s</string>
|
<string name="error_camera_unavailable">Camera unavailable: %1$s</string>
|
||||||
<string name="error_recording_failed">Recording failed: %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="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_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>
|
<string name="pose_metrics_format">Torso %1$s · Knee L%2$s R%3$s · Elbow L%4$s R%5$s</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user