优化性能第一版
This commit is contained in:
@@ -86,6 +86,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private var cosineThreshold = DEFAULT_COSINE_THRESHOLD
|
private var cosineThreshold = DEFAULT_COSINE_THRESHOLD
|
||||||
@Volatile private var matcher = FaceMatcher(DEFAULT_EUCLIDEAN_THRESHOLD, DEFAULT_COSINE_THRESHOLD)
|
@Volatile private var matcher = FaceMatcher(DEFAULT_EUCLIDEAN_THRESHOLD, DEFAULT_COSINE_THRESHOLD)
|
||||||
private val recognitionHistory = mutableMapOf<Int, ArrayDeque<RecognitionResult>>()
|
private val recognitionHistory = mutableMapOf<Int, ArrayDeque<RecognitionResult>>()
|
||||||
|
private val recognitionCache = mutableMapOf<Int, CachedRecognition>()
|
||||||
|
|
||||||
private val permissionLauncher = registerForActivityResult(
|
private val permissionLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.RequestMultiplePermissions()
|
ActivityResultContracts.RequestMultiplePermissions()
|
||||||
@@ -415,6 +416,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
matcherConfigSummaryText.text = matcherConfigSummary()
|
matcherConfigSummaryText.text = matcherConfigSummary()
|
||||||
euclideanThresholdInput.setText(formatThreshold(euclideanThreshold))
|
euclideanThresholdInput.setText(formatThreshold(euclideanThreshold))
|
||||||
cosineThresholdInput.setText(formatThreshold(cosineThreshold))
|
cosineThresholdInput.setText(formatThreshold(cosineThreshold))
|
||||||
|
recognitionHistory.clear()
|
||||||
|
recognitionCache.clear()
|
||||||
overlayView.update(emptyList())
|
overlayView.update(emptyList())
|
||||||
|
|
||||||
if (hasCameraPermission()) {
|
if (hasCameraPermission()) {
|
||||||
@@ -473,6 +476,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
analysisUseCase = null
|
analysisUseCase = null
|
||||||
cameraProvider?.unbindAll()
|
cameraProvider?.unbindAll()
|
||||||
cameraProvider = null
|
cameraProvider = null
|
||||||
|
recognitionHistory.clear()
|
||||||
|
recognitionCache.clear()
|
||||||
overlayView.update(emptyList())
|
overlayView.update(emptyList())
|
||||||
processor?.close()
|
processor?.close()
|
||||||
processor = null
|
processor = null
|
||||||
@@ -513,6 +518,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (currentMode == AppMode.RECOGNITION) {
|
if (currentMode == AppMode.RECOGNITION) {
|
||||||
recognitionHistory.clear()
|
recognitionHistory.clear()
|
||||||
|
recognitionCache.clear()
|
||||||
overlayView.update(emptyList())
|
overlayView.update(emptyList())
|
||||||
updateRecognitionStatus(emptyList())
|
updateRecognitionStatus(emptyList())
|
||||||
}
|
}
|
||||||
@@ -520,11 +526,26 @@ class MainActivity : AppCompatActivity() {
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val bitmap = ImageUtils.imageProxyToBitmap(image)
|
|
||||||
val activeTrackIds = faces.mapNotNull { it.trackingId }.toSet()
|
val activeTrackIds = faces.mapNotNull { it.trackingId }.toSet()
|
||||||
val rawDetections = faces.map { face ->
|
val facesNeedingRefresh = faces.mapIndexedNotNull { index, face ->
|
||||||
val embedding = faceProcessor.embedFace(bitmap, face)
|
if (shouldRefreshRecognition(face.trackingId, now)) index to face else null
|
||||||
val result = stabilizeRecognition(face, matcher.findNearest(embedding, students))
|
}
|
||||||
|
val refreshedResults = mutableMapOf<Int, RecognitionResult>()
|
||||||
|
|
||||||
|
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(
|
RawDetection(
|
||||||
bounds = face.boundingBox,
|
bounds = face.boundingBox,
|
||||||
result = result,
|
result = result,
|
||||||
@@ -532,6 +553,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
recognitionHistory.keys.retainAll(activeTrackIds)
|
recognitionHistory.keys.retainAll(activeTrackIds)
|
||||||
|
recognitionCache.keys.retainAll(activeTrackIds)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (currentMode == AppMode.RECOGNITION) {
|
if (currentMode == AppMode.RECOGNITION) {
|
||||||
@@ -710,12 +732,37 @@ class MainActivity : AppCompatActivity() {
|
|||||||
return if (best.value.size >= 2) best.value.last() else result
|
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 {
|
companion object {
|
||||||
private const val MATCHER_PREFS_NAME = "face_matcher_config"
|
private const val MATCHER_PREFS_NAME = "face_matcher_config"
|
||||||
private const val KEY_EUCLIDEAN_THRESHOLD = "euclidean_threshold"
|
private const val KEY_EUCLIDEAN_THRESHOLD = "euclidean_threshold"
|
||||||
private const val KEY_COSINE_THRESHOLD = "cosine_threshold"
|
private const val KEY_COSINE_THRESHOLD = "cosine_threshold"
|
||||||
private const val DEFAULT_EUCLIDEAN_THRESHOLD = 1.0f
|
private const val DEFAULT_EUCLIDEAN_THRESHOLD = 1.0f
|
||||||
private const val DEFAULT_COSINE_THRESHOLD = 0.6f
|
private const val DEFAULT_COSINE_THRESHOLD = 0.6f
|
||||||
|
private const val RECOGNITION_REFRESH_MS = 400L
|
||||||
private const val ANALYSIS_INTERVAL_MS = 120L
|
private const val ANALYSIS_INTERVAL_MS = 120L
|
||||||
private val ANALYSIS_SIZE = Size(640, 480)
|
private val ANALYSIS_SIZE = Size(640, 480)
|
||||||
}
|
}
|
||||||
@@ -732,3 +779,8 @@ private data class RawDetection(
|
|||||||
val result: RecognitionResult,
|
val result: RecognitionResult,
|
||||||
val confidence: Float?
|
val confidence: Float?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private data class CachedRecognition(
|
||||||
|
val result: RecognitionResult,
|
||||||
|
val updatedAtMs: Long
|
||||||
|
)
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class FaceProcessor(context: Context) : AutoCloseable {
|
|||||||
fun embedFace(bitmap: Bitmap, face: Face): FloatArray {
|
fun embedFace(bitmap: Bitmap, face: Face): FloatArray {
|
||||||
val cropped = cropFace(bitmap, face.boundingBox)
|
val cropped = cropFace(bitmap, face.boundingBox)
|
||||||
val aligned = alignFaceWithLandmarks(cropped, face, 0, 0)
|
val aligned = alignFaceWithLandmarks(cropped, face, 0, 0)
|
||||||
val preprocessed = preprocessor.preprocess(aligned)
|
val preprocessed = preprocessor.preprocessRealtime(aligned)
|
||||||
return embedder.embed(preprocessed)
|
return embedder.embed(preprocessed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ class ImagePreprocessor {
|
|||||||
return histogramEqualize(enhanced)
|
return histogramEqualize(enhanced)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实时识别场景使用轻量预处理,减少每帧 CPU 开销。
|
||||||
|
*/
|
||||||
|
fun preprocessRealtime(bitmap: Bitmap): Bitmap {
|
||||||
|
return enhanceBrightnessContrast(bitmap, brightness = 0.06f, contrast = 1.08f)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 亮度对比度增强
|
* 亮度对比度增强
|
||||||
* @param brightness 亮度调整 (-1 到 1,0 为不调整)
|
* @param brightness 亮度调整 (-1 到 1,0 为不调整)
|
||||||
|
|||||||
Reference in New Issue
Block a user