From bc2e32dc7510d74e0d12987d36bef76b3e8755f4 Mon Sep 17 00:00:00 2001 From: SaiD Date: Fri, 5 Dec 2025 21:20:13 +0530 Subject: [PATCH] video commit first feedbacks --- .kotlin/errors/errors-1764909759880.log | 4 + ...tlin-compiler-18065896741356929687.salive} | 0 .../livingai/domain/ml/FeedbackAnalyzer.kt | 118 ++++++++++-------- .../livingai/pages/camera/VideoViewModel.kt | 8 +- .../com/example/livingai/utils/Constants.kt | 1 + 5 files changed, 77 insertions(+), 54 deletions(-) create mode 100644 .kotlin/errors/errors-1764909759880.log rename .kotlin/sessions/{kotlin-compiler-2318937954061801521.salive => kotlin-compiler-18065896741356929687.salive} (100%) diff --git a/.kotlin/errors/errors-1764909759880.log b/.kotlin/errors/errors-1764909759880.log new file mode 100644 index 0000000..1219b50 --- /dev/null +++ b/.kotlin/errors/errors-1764909759880.log @@ -0,0 +1,4 @@ +kotlin version: 2.0.21 +error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output: + 1. Kotlin compile daemon is ready + diff --git a/.kotlin/sessions/kotlin-compiler-2318937954061801521.salive b/.kotlin/sessions/kotlin-compiler-18065896741356929687.salive similarity index 100% rename from .kotlin/sessions/kotlin-compiler-2318937954061801521.salive rename to .kotlin/sessions/kotlin-compiler-18065896741356929687.salive diff --git a/app/src/main/java/com/example/livingai/domain/ml/FeedbackAnalyzer.kt b/app/src/main/java/com/example/livingai/domain/ml/FeedbackAnalyzer.kt index be7b184..ddc6624 100644 --- a/app/src/main/java/com/example/livingai/domain/ml/FeedbackAnalyzer.kt +++ b/app/src/main/java/com/example/livingai/domain/ml/FeedbackAnalyzer.kt @@ -1,6 +1,8 @@ package com.example.livingai.domain.ml import android.graphics.RectF +import android.util.Log +import com.example.livingai.utils.Constants import kotlin.math.abs import kotlin.math.max import kotlin.math.sin @@ -9,24 +11,16 @@ import kotlin.math.sin // CONFIG CLASS // ------------------------------------------------------------ data class AnalyzerThresholds( - // Size thresholds - val minCoverage: Float = 0.15f, - val maxCoverage: Float = 0.70f, + val toleranceRatio: Float = 0.02f, - // Vertical centering thresholds - val topMarginRatio: Float = 0.05f, - val bottomMarginRatio: Float = 0.05f, - val centerToleranceRatio: Float = 0.10f, + // Height estimation + val minTargetHeightMeters: Float = 0.60f, + val maxTargetHeightMeters: Float = 0.70f, - // Height estimation thresholds - val targetHeightMeters: Float = 0.70f, - val heightToleranceMeters: Float = 0.05f, // ±5 cm allowed - - // Real height of subject (for estimating camera distance) - val subjectRealHeightMeters: Float = 1.70f // default human height or silhouette height + // Real physical height of subject + val subjectRealHeightMeters: Float = 1.55f ) - // ------------------------------------------------------------ // STATES // ------------------------------------------------------------ @@ -41,21 +35,20 @@ sealed class FeedbackState(val message: String) { object Optimal : FeedbackState("Hold still") } - // ------------------------------------------------------------ -// ANALYZER +// ANALYZER INTERFACE // ------------------------------------------------------------ interface FeedbackAnalyzer { fun analyze( detection: ObjectDetector.DetectionResult?, frameWidth: Int, frameHeight: Int, - tiltDegrees: Float, // phone pitch for height estimation - focalLengthPx: Float // camera intrinsics for height estimation + screenHeight: Int, + tiltDegrees: Float, + focalLengthPx: Float ): FeedbackState } - // ------------------------------------------------------------ // IMPLEMENTATION // ------------------------------------------------------------ @@ -67,6 +60,7 @@ class FeedbackAnalyzerImpl( detection: ObjectDetector.DetectionResult?, frameWidth: Int, frameHeight: Int, + screenHeight: Int, tiltDegrees: Float, focalLengthPx: Float ): FeedbackState { @@ -74,77 +68,97 @@ class FeedbackAnalyzerImpl( if (detection == null) return FeedbackState.Searching if (frameWidth <= 0 || frameHeight <= 0) return FeedbackState.Idle - val box = detection.boundingBox - - val pc = Precomputed(box, frameWidth, frameHeight, thresholds) + val pc = Precomputed( + detection.boundingBox, + frameWidth, + frameHeight, + screenHeight, + thresholds + ) val cameraHeight = estimateCameraHeight(pc, tiltDegrees, focalLengthPx) + Log.d("FeedbackAnalyzer", "Camera Height: $cameraHeight, frame height: ${pc.frameHeight}, " + + "frame top: ${pc.frameTop}, frame bottom: ${pc.frameBottom}," + + " detection top: ${pc.detectionTop}, detection bottom: ${pc.detectionBottom}, " + + "isTopInside: ${pc.isTopInside}, isBottomInside: ${pc.isBottomInside}," + + " detection height: ${pc.detectionHeight}, tolerance: ${pc.tolerance}") + return when { - isTooFar(pc) -> FeedbackState.TooFar + // ORDER MATTERS — evaluate alignment first + isTooHigh(pc) -> FeedbackState.TooHigh + isTooLow(pc) -> FeedbackState.TooLow isTooClose(pc) -> FeedbackState.TooClose - isNotCentered(pc) -> FeedbackState.NotCentered + isTooFar(pc) -> FeedbackState.TooFar + + // Height estimation last isHeightTooLow(cameraHeight) -> FeedbackState.TooLow isHeightTooHigh(cameraHeight) -> FeedbackState.TooHigh + else -> FeedbackState.Optimal } } - private fun isTooFar(pc: Precomputed) = - pc.verticalCoverage < thresholds.minCoverage + private fun isTooLow(pc: Precomputed): Boolean = + pc.isBottomInside && !pc.isTopInside && ((pc.frameTop - pc.detectionTop) > pc.tolerance) - private fun isTooClose(pc: Precomputed) = - pc.verticalCoverage > thresholds.maxCoverage + private fun isTooHigh(pc: Precomputed): Boolean = + !pc.isBottomInside && pc.isTopInside && ((pc.detectionBottom - pc.frameBottom) > pc.tolerance) - private fun isNotCentered(pc: Precomputed) = - !pc.topWithinMargin || !pc.bottomWithinMargin || !pc.centeredVertically + // OBJECT TOO CLOSE (bigger than allowed) + private fun isTooClose(pc: Precomputed): Boolean = + !pc.isTopInside && !pc.isBottomInside && ((pc.detectionHeight - pc.frameHeight) > pc.tolerance) - private fun isHeightTooLow(camHeight: Float) = - camHeight < (thresholds.targetHeightMeters - thresholds.heightToleranceMeters) + // OBJECT TOO FAR (too small) + private fun isTooFar(pc: Precomputed): Boolean = + pc.isTopInside && pc.isBottomInside && + ((pc.frameHeight - pc.detectionHeight) > pc.tolerance) - private fun isHeightTooHigh(camHeight: Float) = - camHeight > (thresholds.targetHeightMeters + thresholds.heightToleranceMeters) + private fun isHeightTooLow(heightMeters: Float): Boolean = + heightMeters > 0 && + (thresholds.minTargetHeightMeters > heightMeters) + + private fun isHeightTooHigh(heightMeters: Float): Boolean = + heightMeters > (thresholds.maxTargetHeightMeters) private fun estimateCameraHeight( pc: Precomputed, tiltDegrees: Float, focalLengthPx: Float ): Float { - val tiltRad = Math.toRadians(tiltDegrees.toDouble()) val realHeight = thresholds.subjectRealHeightMeters - val pixelHeight = pc.heightPx + val pixelHeight = pc.detectionHeight if (pixelHeight <= 0f || focalLengthPx <= 0f) return -1f val distance = (realHeight * focalLengthPx) / pixelHeight - - val height = distance * sin(tiltRad) - - return height.toFloat() // in meters + return (distance * sin(tiltRad)).toFloat() } private data class Precomputed( val box: RectF, val frameWidth: Int, val frameHeight: Int, + val screenHeight: Int, val t: AnalyzerThresholds ) { - val top = box.top - val bottom = box.bottom - val heightPx = max(0f, bottom - top) - val verticalCoverage: Float = heightPx / frameHeight + private val modelFrameHeight = Constants.MODEL_HEIGHT + private val scaleSlip = ((screenHeight - modelFrameHeight) * screenHeight) / (2F * modelFrameHeight) + val detectionTop = (box.top * screenHeight / modelFrameHeight) - scaleSlip + val detectionBottom = (box.bottom * screenHeight / modelFrameHeight) - scaleSlip + val detectionHeight = max(0f, detectionBottom - detectionTop) - private val topMarginPx = frameHeight * t.topMarginRatio - private val bottomMarginPx = frameHeight * t.bottomMarginRatio - private val centerTolerancePx = frameHeight * t.centerToleranceRatio + // Frame centered vertically + val frameTop = (screenHeight - frameHeight) / 2f + val frameBottom = frameTop + frameHeight - val topWithinMargin = top >= topMarginPx - val bottomWithinMargin = bottom <= (frameHeight - bottomMarginPx) + val tolerance = t.toleranceRatio * screenHeight + + // Inside checks with tolerance + val isTopInside = detectionTop >= frameTop + val isBottomInside = detectionBottom <= frameBottom - val centerY = box.centerY() - val frameCenterY = frameHeight / 2f - val centeredVertically = abs(centerY - frameCenterY) <= centerTolerancePx } } diff --git a/app/src/main/java/com/example/livingai/pages/camera/VideoViewModel.kt b/app/src/main/java/com/example/livingai/pages/camera/VideoViewModel.kt index 3122aae..25aeedd 100644 --- a/app/src/main/java/com/example/livingai/pages/camera/VideoViewModel.kt +++ b/app/src/main/java/com/example/livingai/pages/camera/VideoViewModel.kt @@ -1,5 +1,6 @@ package com.example.livingai.pages.camera +import android.content.res.Resources import android.graphics.Bitmap import android.net.Uri import android.util.Log @@ -8,6 +9,7 @@ import androidx.lifecycle.viewModelScope import com.example.livingai.domain.ml.FeedbackAnalyzer import com.example.livingai.domain.ml.FeedbackState import com.example.livingai.domain.ml.ObjectDetector +import com.example.livingai.utils.Constants import com.example.livingai.utils.TiltSensorManager import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -44,6 +46,7 @@ class VideoViewModel( detection = cow, frameWidth = state.value.frameWidth, frameHeight = state.value.frameHeight, + screenHeight = state.value.screenHeight, tiltDegrees = state.value.tilt, focalLengthPx = state.value.focalLength ) @@ -78,10 +81,10 @@ class VideoViewModel( _state.value = _state.value.copy(recordedVideoUri = null) } is VideoEvent.FrameReceived -> { - // Update frame dimensions and focal length, but let TiltSensorManager handle tilt _state.value = _state.value.copy( frameWidth = event.bitmap.width, - frameHeight = event.bitmap.height, + frameHeight = Constants.VIDEO_SILHOETTE_FRAME_HEIGHT.value.toInt(), + screenHeight = event.bitmap.height, focalLength = event.focalLength ) objectDetector.detect(event.bitmap, event.rotation) @@ -97,6 +100,7 @@ data class VideoState( val feedback: FeedbackState = FeedbackState.Idle, val frameWidth: Int = 0, val frameHeight: Int = 0, + val screenHeight: Int = 0, val tilt: Float = 0f, val focalLength: Float = 0f ) diff --git a/app/src/main/java/com/example/livingai/utils/Constants.kt b/app/src/main/java/com/example/livingai/utils/Constants.kt index 04d8d14..d694245 100644 --- a/app/src/main/java/com/example/livingai/utils/Constants.kt +++ b/app/src/main/java/com/example/livingai/utils/Constants.kt @@ -19,4 +19,5 @@ object Constants { val VIDEO_SILHOETTE_FRAME_HEIGHT = 300.dp const val JACCARD_THRESHOLD = 85 + const val MODEL_HEIGHT = 320F } \ No newline at end of file