From db8bc18c64b9ccfcbe348fed92725a6674cbba2e Mon Sep 17 00:00:00 2001 From: Shuming Liu Date: Sun, 10 May 2026 08:23:15 +0800 Subject: [PATCH] fixed some errors --- .../studentfaceregistry/face/FaceAligner.kt | 1 + .../studentfaceregistry/face/FaceMatcher.kt | 1 - .../studentfaceregistry/face/FaceProcessor.kt | 7 +-- .../face/SmartEnrollment.kt | 25 ++++----- .../face/VideoEnrollment.kt | 56 +++++++++---------- .../studentfaceregistry/ui/OverlayView.kt | 7 +-- 6 files changed, 44 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/com/example/studentfaceregistry/face/FaceAligner.kt b/app/src/main/java/com/example/studentfaceregistry/face/FaceAligner.kt index 0f91d1b..65d3671 100644 --- a/app/src/main/java/com/example/studentfaceregistry/face/FaceAligner.kt +++ b/app/src/main/java/com/example/studentfaceregistry/face/FaceAligner.kt @@ -2,6 +2,7 @@ package com.example.studentfaceregistry.face import android.graphics.Bitmap import android.graphics.Matrix +import kotlin.math.absoluteValue import kotlin.math.cos import kotlin.math.sin import kotlin.math.sqrt diff --git a/app/src/main/java/com/example/studentfaceregistry/face/FaceMatcher.kt b/app/src/main/java/com/example/studentfaceregistry/face/FaceMatcher.kt index d079565..d2324c7 100644 --- a/app/src/main/java/com/example/studentfaceregistry/face/FaceMatcher.kt +++ b/app/src/main/java/com/example/studentfaceregistry/face/FaceMatcher.kt @@ -1,7 +1,6 @@ package com.example.studentfaceregistry.face import com.example.studentfaceregistry.data.Student -import kotlin.math acos import kotlin.math.sqrt /** 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 7d2fa94..1a66cb7 100644 --- a/app/src/main/java/com/example/studentfaceregistry/face/FaceProcessor.kt +++ b/app/src/main/java/com/example/studentfaceregistry/face/FaceProcessor.kt @@ -7,6 +7,7 @@ import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.face.Face import com.google.mlkit.vision.face.FaceDetection import com.google.mlkit.vision.face.FaceDetectorOptions +import com.google.mlkit.vision.face.FaceLandmark import kotlinx.coroutines.tasks.await /** @@ -84,10 +85,8 @@ class FaceProcessor(context: Context) : AutoCloseable { faceLeft: Int, faceTop: Int ): Bitmap { - val leftEye = face.leftEye ?: return faceBitmap - val rightEye = face.rightEye ?: return faceBitmap - - if (!leftEye.visible || !rightEye.visible) return faceBitmap + val leftEye = face.getLandmark(FaceLandmark.LEFT_EYE) ?: return faceBitmap + val rightEye = face.getLandmark(FaceLandmark.RIGHT_EYE) ?: return faceBitmap return aligner.align( bitmap = faceBitmap, diff --git a/app/src/main/java/com/example/studentfaceregistry/face/SmartEnrollment.kt b/app/src/main/java/com/example/studentfaceregistry/face/SmartEnrollment.kt index d7dea12..151a231 100644 --- a/app/src/main/java/com/example/studentfaceregistry/face/SmartEnrollment.kt +++ b/app/src/main/java/com/example/studentfaceregistry/face/SmartEnrollment.kt @@ -10,7 +10,9 @@ import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.face.Face import com.google.mlkit.vision.face.FaceDetection import com.google.mlkit.vision.face.FaceDetectorOptions +import com.google.mlkit.vision.face.FaceLandmark import kotlinx.coroutines.tasks.await +import kotlin.math.PI import kotlin.math.atan2 import kotlin.math.sqrt @@ -23,7 +25,7 @@ import kotlin.math.sqrt * 2. 自动检测并选择最大的人脸 * 3. 视频注册时自动提取多角度帧并融合特征 */ -class SmartEnrollment(context: Context) { +class SmartEnrollment(context: Context) : AutoCloseable { // 高精度人脸检测器(用于注册) private val detector = FaceDetection.getClient( @@ -207,28 +209,26 @@ class SmartEnrollment(context: Context) { * 计算偏航角(左右转头) */ private fun yawAngle(face: Face): Float { - val leftCheek = face.leftCheek ?: return 0f - val rightCheek = face.rightCheek ?: return 0f - if (!leftCheek.visible || !rightCheek.visible) return 0f + val leftCheek = face.getLandmark(FaceLandmark.LEFT_CHEEK) ?: return 0f + val rightCheek = face.getLandmark(FaceLandmark.RIGHT_CHEEK) ?: return 0f val dx = rightCheek.position.x - leftCheek.position.x val dy = rightCheek.position.y - leftCheek.position.y - return atan2(dy.toDouble(), dx.toDouble()).toFloat() * 180f / 3.14159 + return atan2(dy.toDouble(), dx.toDouble()).toFloat() * 180f / PI.toFloat() } /** * 计算俯仰角(上下点头) */ private fun pitchAngle(face: Face): Float { - val nose = face.nose ?: return 0f - val leftEye = face.leftEye ?: return 0f - val rightEye = face.rightEye ?: return 0f - if (!nose.visible || !leftEye.visible || !rightEye.visible) return 0f + val nose = face.getLandmark(FaceLandmark.NOSE_BASE) ?: return 0f + val leftEye = face.getLandmark(FaceLandmark.LEFT_EYE) ?: return 0f + val rightEye = face.getLandmark(FaceLandmark.RIGHT_EYE) ?: return 0f val eyeCenterY = (leftEye.position.y + rightEye.position.y) / 2f val dx = nose.position.x - (leftEye.position.x + rightEye.position.x) / 2f val dy = nose.position.y - eyeCenterY - return atan2(dy.toDouble(), dx.toDouble()).toFloat() * 180f / 3.14159 + return atan2(dy.toDouble(), dx.toDouble()).toFloat() * 180f / PI.toFloat() } /** @@ -360,9 +360,8 @@ class SmartEnrollment(context: Context) { * 对齐人脸 */ private fun alignFace(bitmap: Bitmap, face: Face, cropRect: Rect): Bitmap { - val leftEye = face.leftEye ?: return bitmap - val rightEye = face.rightEye ?: return bitmap - if (!leftEye.visible || !rightEye.visible) return bitmap + val leftEye = face.getLandmark(FaceLandmark.LEFT_EYE) ?: return bitmap + val rightEye = face.getLandmark(FaceLandmark.RIGHT_EYE) ?: return bitmap return aligner.align( bitmap = bitmap, diff --git a/app/src/main/java/com/example/studentfaceregistry/face/VideoEnrollment.kt b/app/src/main/java/com/example/studentfaceregistry/face/VideoEnrollment.kt index 9906826..dcc1cb3 100644 --- a/app/src/main/java/com/example/studentfaceregistry/face/VideoEnrollment.kt +++ b/app/src/main/java/com/example/studentfaceregistry/face/VideoEnrollment.kt @@ -6,7 +6,9 @@ import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.face.Face import com.google.mlkit.vision.face.FaceDetection import com.google.mlkit.vision.face.FaceDetectorOptions +import com.google.mlkit.vision.face.FaceLandmark import kotlinx.coroutines.tasks.await +import kotlin.math.PI import kotlin.math.abs import kotlin.math.atan2 import kotlin.math.cos @@ -22,7 +24,7 @@ import kotlin.math.sqrt * - 自动选择质量好的帧 * - 融合多帧特征,提升识别准确率 */ -class VideoEnrollment(private val context: android.content.Context) { +class VideoEnrollment(private val context: android.content.Context) : AutoCloseable { private val detector = FaceDetection.getClient( FaceDetectorOptions.Builder() @@ -45,7 +47,7 @@ class VideoEnrollment(private val context: android.content.Context) { frames: List, minFrames: Int = 5, maxFrames: Int = 20 - ): EnrollmentResult { + ): VideoEnrollmentResult { val faceFrames = mutableListOf() // 检测所有帧中的人脸 @@ -71,7 +73,7 @@ class VideoEnrollment(private val context: android.content.Context) { } if (faceFrames.size < minFrames) { - return EnrollmentResult.Failure( + return VideoEnrollmentResult.Failure( reason = "检测到 ${faceFrames.size} 帧有效人脸,需要至少 $minFrames 帧。请确保视频中人脸清晰且有多角度展示。" ) } @@ -90,7 +92,7 @@ class VideoEnrollment(private val context: android.content.Context) { // 融合特征(平均 + L2 归一化) val fusedEmbedding = fuseEmbeddings(embeddings) - return EnrollmentResult.Success( + return VideoEnrollmentResult.Success( embedding = fusedEmbedding, frameCount = selectedFrames.size, totalDetected = faceFrames.size, @@ -105,8 +107,8 @@ class VideoEnrollment(private val context: android.content.Context) { var score = 1.0f // 可见度分数 - val leftEyeVisible = face.leftEye?.visible ?: false - val rightEyeVisible = face.rightEye?.visible ?: false + val leftEyeVisible = face.getLandmark(FaceLandmark.LEFT_EYE) != null + val rightEyeVisible = face.getLandmark(FaceLandmark.RIGHT_EYE) != null if (!leftEyeVisible || !rightEyeVisible) score *= 0.7f // 置信度分数 @@ -125,16 +127,14 @@ class VideoEnrollment(private val context: android.content.Context) { * 计算人脸偏航角(左右转头,-90 到 90 度) */ private fun calculateYawAngle(face: Face): Float { - val leftCheek = face.leftCheek ?: return 0f - val rightCheek = face.rightCheek ?: return 0f - - if (!leftCheek.visible || !rightCheek.visible) return 0f + val leftCheek = face.getLandmark(FaceLandmark.LEFT_CHEEK) ?: return 0f + val rightCheek = face.getLandmark(FaceLandmark.RIGHT_CHEEK) ?: return 0f val dx = rightCheek.position.x - leftCheek.position.x val dy = rightCheek.position.y - leftCheek.position.y // 计算角度 - val angle = atan2(dy.toDouble(), dx.toDouble()).toFloat() * 180f / 3.14159 + val angle = atan2(dy.toDouble(), dx.toDouble()).toFloat() * 180f / PI.toFloat() return angle.coerceIn(-90f, 90f) } @@ -143,11 +143,9 @@ class VideoEnrollment(private val context: android.content.Context) { * 计算人脸俯仰角(上下点头,-90 到 90 度) */ private fun calculatePitchAngle(face: Face): Float { - val nose = face.nose ?: return 0f - val leftEye = face.leftEye ?: return 0f - val rightEye = face.rightEye ?: return 0f - - if (!nose.visible || !leftEye.visible || !rightEye.visible) return 0f + val nose = face.getLandmark(FaceLandmark.NOSE_BASE) ?: return 0f + val leftEye = face.getLandmark(FaceLandmark.LEFT_EYE) ?: return 0f + val rightEye = face.getLandmark(FaceLandmark.RIGHT_EYE) ?: return 0f // 眼睛中心点 val eyeCenterY = (leftEye.position.y + rightEye.position.y) / 2f @@ -155,7 +153,7 @@ class VideoEnrollment(private val context: android.content.Context) { val dx = nose.position.x - (leftEye.position.x + rightEye.position.x) / 2f val dy = nose.position.y - eyeCenterY - val angle = atan2(dy.toDouble(), dx.toDouble()).toFloat() * 180f / 3.14159 + val angle = atan2(dy.toDouble(), dx.toDouble()).toFloat() * 180f / PI.toFloat() return angle.coerceIn(-90f, 90f) } @@ -163,8 +161,8 @@ class VideoEnrollment(private val context: android.content.Context) { /** * 按角度分组人脸帧 */ - private fun groupFramesByAngle(frames: List): Map> { - val buckets = mutableMapOf>() + private fun groupFramesByAngle(frames: List): Map> { + val buckets = mutableMapOf>() for (frame in frames) { val bucket = getAngleBucket(frame.yaw, frame.pitch) @@ -180,7 +178,7 @@ class VideoEnrollment(private val context: android.content.Context) { /** * 获取角度分组 */ - private fun getAngleBucket(yaw: Float, pitch: Float): AngleBucket { + private fun getAngleBucket(yaw: Float, pitch: Float): VideoAngleBucket { val yawBucket = when { yaw < -30 -> -2 // 左 yaw < -10 -> -1 // 左前 @@ -195,14 +193,14 @@ class VideoEnrollment(private val context: android.content.Context) { else -> 1 // 下 } - return AngleBucket(yawBucket, pitchBucket) + return VideoAngleBucket(yawBucket, pitchBucket) } /** * 从每组中选择质量最好的帧 */ private fun selectBestFramesFromGroups( - groups: Map>, + groups: Map>, maxFrames: Int ): List { val selected = mutableListOf() @@ -314,10 +312,8 @@ class VideoEnrollment(private val context: android.content.Context) { * 对齐人脸 */ private fun alignFace(bitmap: Bitmap, face: Face): Bitmap { - val leftEye = face.leftEye ?: return bitmap - val rightEye = face.rightEye ?: return bitmap - - if (!leftEye.visible || !rightEye.visible) return bitmap + val leftEye = face.getLandmark(FaceLandmark.LEFT_EYE) ?: return bitmap + val rightEye = face.getLandmark(FaceLandmark.RIGHT_EYE) ?: return bitmap val aligner = FaceAligner() return aligner.align( @@ -363,7 +359,7 @@ data class FaceFrame( /** * 角度分组 */ -data class AngleBucket(val yaw: Int, val pitch: Int) +data class VideoAngleBucket(val yaw: Int, val pitch: Int) /** * 角度覆盖范围 @@ -380,13 +376,13 @@ data class AngleCoverage( /** * 注册结果 */ -sealed class EnrollmentResult { +sealed class VideoEnrollmentResult { data class Success( val embedding: FloatArray, val frameCount: Int, val totalDetected: Int, val angleCoverage: AngleCoverage - ) : EnrollmentResult() + ) : VideoEnrollmentResult() - data class Failure(val reason: String) : EnrollmentResult() + data class Failure(val reason: String) : VideoEnrollmentResult() } diff --git a/app/src/main/java/com/example/studentfaceregistry/ui/OverlayView.kt b/app/src/main/java/com/example/studentfaceregistry/ui/OverlayView.kt index cbe3753..e078893 100644 --- a/app/src/main/java/com/example/studentfaceregistry/ui/OverlayView.kt +++ b/app/src/main/java/com/example/studentfaceregistry/ui/OverlayView.kt @@ -5,10 +5,7 @@ import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Rect -import android.graphics.RippleDrawable import android.graphics.Typeface -import android.graphics.drawable.ShapeDrawable -import android.graphics.drawable.shapes.RoundRectShape import android.util.AttributeSet import android.view.View import com.example.studentfaceregistry.face.MatchType @@ -96,7 +93,7 @@ class OverlayView @JvmOverloads constructor( // 标签背景颜色与框颜色一致 labelBackgroundPaint.color = boxColor - canvas.drawRoundRect(labelLeft, labelTop, labelRight, labelBottom, 8f, labelBackgroundPaint) + canvas.drawRoundRect(labelLeft, labelTop, labelRight, labelBottom, 8f, 8f, labelBackgroundPaint) // 绘制标签文字 canvas.drawText(label, labelLeft + padding, labelTop + 38f, labelPaint) @@ -112,7 +109,7 @@ class OverlayView @JvmOverloads constructor( // 置信度背景(半透明黑色) labelBackgroundPaint.color = Color.argb(180, 0, 0, 0) - canvas.drawRoundRect(confLeft, confTop, confRight, confBottom, 6f, labelBackgroundPaint) + canvas.drawRoundRect(confLeft, confTop, confRight, confBottom, 6f, 6f, labelBackgroundPaint) // 置信度文字 canvas.drawText(confidenceText, confLeft + 8f, confTop + 30f, confidencePaint)