From a26eed922a5b90007e39a7f33abac9df1d467c17 Mon Sep 17 00:00:00 2001 From: Shuming Liu Date: Sun, 10 May 2026 19:21:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=80=A7=E8=83=BD=E7=AC=AC?= =?UTF-8?q?=E4=B8=80=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../studentfaceregistry/MainActivity.kt | 60 +++++++++++++++++-- .../studentfaceregistry/face/FaceProcessor.kt | 2 +- .../face/ImagePreprocessor.kt | 7 +++ 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/example/studentfaceregistry/MainActivity.kt b/app/src/main/java/com/example/studentfaceregistry/MainActivity.kt index 98119d0..68aa4c8 100644 --- a/app/src/main/java/com/example/studentfaceregistry/MainActivity.kt +++ b/app/src/main/java/com/example/studentfaceregistry/MainActivity.kt @@ -86,6 +86,7 @@ class MainActivity : AppCompatActivity() { private var cosineThreshold = DEFAULT_COSINE_THRESHOLD @Volatile private var matcher = FaceMatcher(DEFAULT_EUCLIDEAN_THRESHOLD, DEFAULT_COSINE_THRESHOLD) private val recognitionHistory = mutableMapOf>() + private val recognitionCache = mutableMapOf() private val permissionLauncher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() @@ -415,6 +416,8 @@ class MainActivity : AppCompatActivity() { matcherConfigSummaryText.text = matcherConfigSummary() euclideanThresholdInput.setText(formatThreshold(euclideanThreshold)) cosineThresholdInput.setText(formatThreshold(cosineThreshold)) + recognitionHistory.clear() + recognitionCache.clear() overlayView.update(emptyList()) if (hasCameraPermission()) { @@ -473,6 +476,8 @@ class MainActivity : AppCompatActivity() { analysisUseCase = null cameraProvider?.unbindAll() cameraProvider = null + recognitionHistory.clear() + recognitionCache.clear() overlayView.update(emptyList()) processor?.close() processor = null @@ -513,6 +518,7 @@ class MainActivity : AppCompatActivity() { withContext(Dispatchers.Main) { if (currentMode == AppMode.RECOGNITION) { recognitionHistory.clear() + recognitionCache.clear() overlayView.update(emptyList()) updateRecognitionStatus(emptyList()) } @@ -520,11 +526,26 @@ class MainActivity : AppCompatActivity() { return@launch } - val bitmap = ImageUtils.imageProxyToBitmap(image) val activeTrackIds = faces.mapNotNull { it.trackingId }.toSet() - val rawDetections = faces.map { face -> - val embedding = faceProcessor.embedFace(bitmap, face) - val result = stabilizeRecognition(face, matcher.findNearest(embedding, students)) + val facesNeedingRefresh = faces.mapIndexedNotNull { index, face -> + if (shouldRefreshRecognition(face.trackingId, now)) index to face else null + } + val refreshedResults = mutableMapOf() + + if (facesNeedingRefresh.isNotEmpty()) { + val bitmap = ImageUtils.imageProxyToBitmap(image) + facesNeedingRefresh.forEach { (index, face) -> + val embedding = faceProcessor.embedFace(bitmap, face) + val refreshed = matcher.findNearest(embedding, students) + refreshedResults[index] = refreshed + face.trackingId?.let { trackId -> + recognitionCache[trackId] = CachedRecognition(refreshed, now) + } + } + } + + val rawDetections = faces.mapIndexed { index, face -> + val result = resolveRecognitionResult(face, refreshedResults[index], now) RawDetection( bounds = face.boundingBox, result = result, @@ -532,6 +553,7 @@ class MainActivity : AppCompatActivity() { ) } recognitionHistory.keys.retainAll(activeTrackIds) + recognitionCache.keys.retainAll(activeTrackIds) withContext(Dispatchers.Main) { if (currentMode == AppMode.RECOGNITION) { @@ -710,12 +732,37 @@ class MainActivity : AppCompatActivity() { return if (best.value.size >= 2) best.value.last() else result } + private fun shouldRefreshRecognition(trackId: Int?, now: Long): Boolean { + if (trackId == null) return true + val cached = recognitionCache[trackId] ?: return true + return now - cached.updatedAtMs >= RECOGNITION_REFRESH_MS + } + + private fun resolveRecognitionResult( + face: Face, + refreshedResult: RecognitionResult?, + now: Long + ): RecognitionResult { + val trackId = face.trackingId + val directResult = when { + refreshedResult != null -> refreshedResult + trackId != null && recognitionCache.containsKey(trackId) -> recognitionCache.getValue(trackId).result + else -> RecognitionResult(null, Float.MAX_VALUE, 0f, MatchType.NO_DATA) + } + val stabilized = stabilizeRecognition(face, directResult) + if (trackId != null) { + recognitionCache[trackId] = CachedRecognition(stabilized, now) + } + return stabilized + } + companion object { private const val MATCHER_PREFS_NAME = "face_matcher_config" private const val KEY_EUCLIDEAN_THRESHOLD = "euclidean_threshold" private const val KEY_COSINE_THRESHOLD = "cosine_threshold" private const val DEFAULT_EUCLIDEAN_THRESHOLD = 1.0f private const val DEFAULT_COSINE_THRESHOLD = 0.6f + private const val RECOGNITION_REFRESH_MS = 400L private const val ANALYSIS_INTERVAL_MS = 120L private val ANALYSIS_SIZE = Size(640, 480) } @@ -732,3 +779,8 @@ private data class RawDetection( val result: RecognitionResult, val confidence: Float? ) + +private data class CachedRecognition( + val result: RecognitionResult, + val updatedAtMs: Long +) diff --git a/app/src/main/java/com/example/studentfaceregistry/face/FaceProcessor.kt b/app/src/main/java/com/example/studentfaceregistry/face/FaceProcessor.kt index d7f3cd6..1710f3f 100644 --- a/app/src/main/java/com/example/studentfaceregistry/face/FaceProcessor.kt +++ b/app/src/main/java/com/example/studentfaceregistry/face/FaceProcessor.kt @@ -128,7 +128,7 @@ class FaceProcessor(context: Context) : AutoCloseable { fun embedFace(bitmap: Bitmap, face: Face): FloatArray { val cropped = cropFace(bitmap, face.boundingBox) val aligned = alignFaceWithLandmarks(cropped, face, 0, 0) - val preprocessed = preprocessor.preprocess(aligned) + val preprocessed = preprocessor.preprocessRealtime(aligned) return embedder.embed(preprocessed) } diff --git a/app/src/main/java/com/example/studentfaceregistry/face/ImagePreprocessor.kt b/app/src/main/java/com/example/studentfaceregistry/face/ImagePreprocessor.kt index 7f5b1a5..8850123 100644 --- a/app/src/main/java/com/example/studentfaceregistry/face/ImagePreprocessor.kt +++ b/app/src/main/java/com/example/studentfaceregistry/face/ImagePreprocessor.kt @@ -28,6 +28,13 @@ class ImagePreprocessor { return histogramEqualize(enhanced) } + /** + * 实时识别场景使用轻量预处理,减少每帧 CPU 开销。 + */ + fun preprocessRealtime(bitmap: Bitmap): Bitmap { + return enhanceBrightnessContrast(bitmap, brightness = 0.06f, contrast = 1.08f) + } + /** * 亮度对比度增强 * @param brightness 亮度调整 (-1 到 1,0 为不调整)