fixed some errors

This commit is contained in:
2026-05-10 08:23:15 +08:00
parent 37530a0309
commit db8bc18c64
6 changed files with 44 additions and 53 deletions

View File

@@ -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

View File

@@ -1,7 +1,6 @@
package com.example.studentfaceregistry.face
import com.example.studentfaceregistry.data.Student
import kotlin.math acos
import kotlin.math.sqrt
/**

View File

@@ -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,

View File

@@ -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,

View File

@@ -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<Bitmap>,
minFrames: Int = 5,
maxFrames: Int = 20
): EnrollmentResult {
): VideoEnrollmentResult {
val faceFrames = mutableListOf<FaceFrame>()
// 检测所有帧中的人脸
@@ -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<FaceFrame>): Map<AngleBucket, List<FaceFrame>> {
val buckets = mutableMapOf<AngleBucket, MutableList<FaceFrame>>()
private fun groupFramesByAngle(frames: List<FaceFrame>): Map<VideoAngleBucket, List<FaceFrame>> {
val buckets = mutableMapOf<VideoAngleBucket, MutableList<FaceFrame>>()
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<AngleBucket, List<FaceFrame>>,
groups: Map<VideoAngleBucket, List<FaceFrame>>,
maxFrames: Int
): List<FaceFrame> {
val selected = mutableListOf<FaceFrame>()
@@ -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()
}

View File

@@ -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)