Smoother skeleton

This commit is contained in:
2026-08-13 19:54:48 +08:00
parent e05aca3312
commit 5d07c8c8bd
7 changed files with 110 additions and 47 deletions
@@ -5,7 +5,6 @@ import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.pose.Pose
import com.google.mlkit.vision.pose.PoseDetection
import com.google.mlkit.vision.pose.PoseDetector
import com.google.mlkit.vision.pose.accurate.AccuratePoseDetectorOptions
@@ -39,10 +38,13 @@ class PoseAnalyzer(
* map it from analysis-image pixels to view pixels, plus the joint
* angles derived from that same pose (see [PoseAngleCalculator]) so
* downstream consumers (overlay text, future frame-by-frame logging)
* don't need to recompute them from [pose] themselves.
* don't need to recompute them from [landmarks] themselves. [landmarks]
* is already smoothed across frames (see [PoseLandmarkSmoother]) rather
* than raw ML Kit output, so anything drawn straight from it doesn't
* need to smooth it again.
*/
data class PoseFrameResult(
val pose: Pose,
val landmarks: Map<Int, SmoothedLandmark>,
val imageWidth: Int,
val imageHeight: Int,
val rotationDegrees: Int,
@@ -56,6 +58,12 @@ class PoseAnalyzer(
.build()
)
// One smoother per analyzer instance -- a fresh PoseAnalyzer (see
// CameraXController.bindToLifecycle) means a fresh detection stream, so
// its smoothing state should start clean rather than lerping in from
// whatever pose the previous stream last saw.
private val landmarkSmoother = PoseLandmarkSmoother()
// STRATEGY_KEEP_ONLY_LATEST on the ImageAnalysis use case (see
// CameraXController) already ensures we're never handed a backlog, but
// this guards against overlapping calls if the detector ever falls
@@ -85,16 +93,17 @@ class PoseAnalyzer(
detector.process(inputImage)
.addOnSuccessListener { pose ->
val landmarks = landmarkSmoother.smooth(pose)
onResult(
PoseFrameResult(
pose = pose,
landmarks = landmarks,
imageWidth = width,
imageHeight = height,
rotationDegrees = rotationDegrees,
isFrontCamera = frontCamera,
// Cheap (four atan2 pairs at most), safe to compute
// on every frame right alongside the detection result.
angles = PoseAngleCalculator.compute(pose)
angles = PoseAngleCalculator.compute(landmarks)
)
)
}