|
@@ -0,0 +1,241 @@
|
|
|
|
|
+package com.grkj.iscs
|
|
|
|
|
+
|
|
|
|
|
+import android.annotation.SuppressLint
|
|
|
|
|
+import android.graphics.Bitmap
|
|
|
|
|
+import android.graphics.BitmapFactory
|
|
|
|
|
+import android.graphics.ImageFormat
|
|
|
|
|
+import android.graphics.Matrix
|
|
|
|
|
+import android.graphics.Rect
|
|
|
|
|
+import android.graphics.YuvImage
|
|
|
|
|
+import android.media.FaceDetector
|
|
|
|
|
+import android.media.Image
|
|
|
|
|
+import android.util.Log
|
|
|
|
|
+import androidx.activity.ComponentActivity
|
|
|
|
|
+import androidx.camera.core.CameraSelector
|
|
|
|
|
+import androidx.camera.core.ImageAnalysis
|
|
|
|
|
+import androidx.camera.core.ImageAnalysis.Analyzer
|
|
|
|
|
+import androidx.camera.core.ImageProxy
|
|
|
|
|
+import androidx.camera.core.Preview
|
|
|
|
|
+import androidx.camera.lifecycle.ProcessCameraProvider
|
|
|
|
|
+import androidx.camera.view.PreviewView
|
|
|
|
|
+import androidx.core.content.ContextCompat
|
|
|
|
|
+import androidx.core.util.Consumer
|
|
|
|
|
+import java.io.ByteArrayOutputStream
|
|
|
|
|
+import java.util.concurrent.ExecutorService
|
|
|
|
|
+import java.util.concurrent.Executors
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 使用cameraX和FaceDetector进行人脸识别
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author fdk
|
|
|
|
|
+ * @date 2024-04-22
|
|
|
|
|
+ */
|
|
|
|
|
+class FaceDetectorHelper(
|
|
|
|
|
+ private var mCameraExecutor: ExecutorService = Executors.newSingleThreadExecutor(),
|
|
|
|
|
+ private var mSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
|
|
|
|
+) {
|
|
|
|
|
+ // 提供相机服务
|
|
|
|
|
+ private lateinit var mCameraProvider: ProcessCameraProvider
|
|
|
|
|
+
|
|
|
|
|
+ private lateinit var imagePreview: Preview
|
|
|
|
|
+
|
|
|
|
|
+ private lateinit var imageAnalysis: ImageAnalysis
|
|
|
|
|
+
|
|
|
|
|
+ // 人脸识别控制
|
|
|
|
|
+ var isOpenDetector = false
|
|
|
|
|
+
|
|
|
|
|
+ // 摄像头方向
|
|
|
|
|
+ private var currentLensFacing = CameraSelector.LENS_FACING_BACK
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 使用CameraX API进行预览
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param activity 带lifecycle的activity,提供context,并且便于使用协程
|
|
|
|
|
+ * @param view Camera API使用的 PreviewView
|
|
|
|
|
+ */
|
|
|
|
|
+ @SuppressLint("RestrictedApi")
|
|
|
|
|
+ fun startPreview(
|
|
|
|
|
+ activity: ComponentActivity,
|
|
|
|
|
+ view: PreviewView,
|
|
|
|
|
+ callback: Consumer<Bitmap>
|
|
|
|
|
+ ) {
|
|
|
|
|
+ val cameraProviderFuture = ProcessCameraProvider.getInstance(activity)
|
|
|
|
|
+ cameraProviderFuture.addListener({
|
|
|
|
|
+ // 用于将相机的生命周期绑定到生命周期所有者
|
|
|
|
|
+ // 消除了打开和关闭相机的任务,因为 CameraX 具有生命周期感知能力
|
|
|
|
|
+ mCameraProvider = cameraProviderFuture.get()
|
|
|
|
|
+
|
|
|
|
|
+ // 预览
|
|
|
|
|
+ imagePreview = Preview.Builder()
|
|
|
|
|
+ .build()
|
|
|
|
|
+ .also {
|
|
|
|
|
+ it.setSurfaceProvider(view.surfaceProvider)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 配置图像分析
|
|
|
|
|
+ imageAnalysis = ImageAnalysis.Builder()
|
|
|
|
|
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
|
|
|
|
+ .build()
|
|
|
|
|
+ imageAnalysis.setAnalyzer(mCameraExecutor, FaceAnalyzer(callback))
|
|
|
|
|
+
|
|
|
|
|
+ // 选择摄像头,省去了去判断摄像头ID
|
|
|
|
|
+ val cameraSelector = mSelector
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Unbind use cases before rebinding
|
|
|
|
|
+ mCameraProvider.unbindAll()
|
|
|
|
|
+
|
|
|
|
|
+ // 将相机绑定到 lifecycleOwner,就不用手动关闭了
|
|
|
|
|
+ mCameraProvider.bindToLifecycle(
|
|
|
|
|
+ activity, cameraSelector, imagePreview, imageAnalysis)
|
|
|
|
|
+
|
|
|
|
|
+ } catch(exc: Exception) {
|
|
|
|
|
+ Log.e("TAG", "Use case binding failed", exc)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 回调代码在主线程处理
|
|
|
|
|
+ }, ContextCompat.getMainExecutor(activity))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fun switchCamera(activity: ComponentActivity) {
|
|
|
|
|
+ currentLensFacing = if (currentLensFacing == CameraSelector.LENS_FACING_BACK) {
|
|
|
|
|
+ CameraSelector.LENS_FACING_FRONT
|
|
|
|
|
+ } else {
|
|
|
|
|
+ CameraSelector.LENS_FACING_BACK
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ val newCameraSelector = CameraSelector.Builder()
|
|
|
|
|
+ .requireLensFacing(currentLensFacing)
|
|
|
|
|
+ .build()
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ mCameraProvider.unbindAll()
|
|
|
|
|
+ mCameraProvider.bindToLifecycle(activity, newCameraSelector, imagePreview, imageAnalysis)
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
|
+ e.printStackTrace()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 开启人脸识别
|
|
|
|
|
+ */
|
|
|
|
|
+ fun startDetector() {
|
|
|
|
|
+ isOpenDetector = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 停止人脸识别
|
|
|
|
|
+ */
|
|
|
|
|
+ fun stopDetector() {
|
|
|
|
|
+ isOpenDetector = false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 释放资源
|
|
|
|
|
+ */
|
|
|
|
|
+ fun release() {
|
|
|
|
|
+ // 取消绑定生命周期观察者
|
|
|
|
|
+ mCameraProvider.unbindAll()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private inner class FaceAnalyzer(
|
|
|
|
|
+ private val callback: Consumer<Bitmap>
|
|
|
|
|
+ ) : Analyzer {
|
|
|
|
|
+
|
|
|
|
|
+ private val maxFaces = 1
|
|
|
|
|
+ private var faceDetector: FaceDetector? = null
|
|
|
|
|
+
|
|
|
|
|
+ @SuppressLint("UnsafeOptInUsageError")
|
|
|
|
|
+ override fun analyze(image: ImageProxy) {
|
|
|
|
|
+ // Log.d("TAG", "analyze: ${image.format}")
|
|
|
|
|
+
|
|
|
|
|
+ // 控制人脸检测
|
|
|
|
|
+ if (!isOpenDetector) {
|
|
|
|
|
+ image.close()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ val bitmap = if (image.format == ImageFormat.YUV_420_888) {
|
|
|
|
|
+ val rotation = image.imageInfo.rotationDegrees
|
|
|
|
|
+ imageProxyToBitmap(image.image!!, rotation)
|
|
|
|
|
+ }else {
|
|
|
|
|
+ imageProxyToBitmap(image)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 创建FaceDetector
|
|
|
|
|
+ if (faceDetector == null) {
|
|
|
|
|
+ faceDetector = FaceDetector(bitmap.width, bitmap.height, maxFaces)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 进行人脸检测
|
|
|
|
|
+ val faces = arrayOfNulls<FaceDetector.Face>(maxFaces)
|
|
|
|
|
+ val faceCount = faceDetector!!.findFaces(bitmap, faces)
|
|
|
|
|
+
|
|
|
|
|
+ // 处理人脸检测结果
|
|
|
|
|
+ if (faceCount > 0) {
|
|
|
|
|
+
|
|
|
|
|
+ // 第一张脸信息
|
|
|
|
|
+ val face1 = faces[0]!!
|
|
|
|
|
+ // 人脸的可信度,0 - 1
|
|
|
|
|
+ val confidence = face1.confidence()
|
|
|
|
|
+ // 双眼的间距
|
|
|
|
|
+ // val eyesDistance = face1.eyesDistance()
|
|
|
|
|
+ // 角度
|
|
|
|
|
+ // val angle = face1.pose(FaceDetector.Face.EULER_X)
|
|
|
|
|
+
|
|
|
|
|
+ Log.d("TAG", "analyze: confidence = $confidence")
|
|
|
|
|
+
|
|
|
|
|
+ // 加点判断,传出结果
|
|
|
|
|
+ if (confidence > 0.5){
|
|
|
|
|
+ callback.accept(bitmap)
|
|
|
|
|
+ }else {
|
|
|
|
|
+ bitmap.recycle()
|
|
|
|
|
+ }
|
|
|
|
|
+ }else {
|
|
|
|
|
+ bitmap.recycle()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ image.close()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun imageProxyToBitmap(image: Image, rotationDegrees: Int): Bitmap {
|
|
|
|
|
+ val yBuffer = image.planes[0].buffer
|
|
|
|
|
+ val uBuffer = image.planes[1].buffer
|
|
|
|
|
+ val vBuffer = image.planes[2].buffer
|
|
|
|
|
+
|
|
|
|
|
+ val ySize = yBuffer.remaining()
|
|
|
|
|
+ val uSize = uBuffer.remaining()
|
|
|
|
|
+ val vSize = vBuffer.remaining()
|
|
|
|
|
+
|
|
|
|
|
+ val nv21 = ByteArray(ySize + uSize + vSize)
|
|
|
|
|
+ yBuffer.get(nv21, 0, ySize)
|
|
|
|
|
+ vBuffer.get(nv21, ySize, vSize)
|
|
|
|
|
+ uBuffer.get(nv21, ySize + vSize, uSize)
|
|
|
|
|
+
|
|
|
|
|
+ val yuvImage = YuvImage(nv21, ImageFormat.NV21, image.width, image.height, null)
|
|
|
|
|
+ val outputStream = ByteArrayOutputStream()
|
|
|
|
|
+ yuvImage.compressToJpeg(Rect(0, 0, image.width, image.height), 100, outputStream)
|
|
|
|
|
+ val jpegArray = outputStream.toByteArray()
|
|
|
|
|
+
|
|
|
|
|
+ val options = BitmapFactory.Options()
|
|
|
|
|
+ options.inPreferredConfig = Bitmap.Config.RGB_565
|
|
|
|
|
+ val bitmap = BitmapFactory.decodeByteArray(jpegArray, 0, jpegArray.size, options)
|
|
|
|
|
+
|
|
|
|
|
+ val matrix = Matrix()
|
|
|
|
|
+ matrix.postRotate(rotationDegrees.toFloat())
|
|
|
|
|
+
|
|
|
|
|
+ return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun imageProxyToBitmap(image: ImageProxy): Bitmap {
|
|
|
|
|
+ val planeProxy = image.planes[0]
|
|
|
|
|
+ val buffer = planeProxy.buffer
|
|
|
|
|
+
|
|
|
|
|
+ val bytes = ByteArray(buffer.remaining())
|
|
|
|
|
+ buffer.get(bytes)
|
|
|
|
|
+
|
|
|
|
|
+ return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|