|
@@ -0,0 +1,232 @@
|
|
|
+package com.example.chemical.ui.verify.fragment;
|
|
|
+
|
|
|
+import android.annotation.SuppressLint;
|
|
|
+import android.graphics.Bitmap;
|
|
|
+import android.graphics.BitmapFactory;
|
|
|
+import android.graphics.Matrix;
|
|
|
+import android.os.Bundle;
|
|
|
+import android.util.Log;
|
|
|
+import android.util.Size;
|
|
|
+import android.view.LayoutInflater;
|
|
|
+import android.view.View;
|
|
|
+import android.view.ViewGroup;
|
|
|
+
|
|
|
+import androidx.annotation.NonNull;
|
|
|
+import androidx.annotation.Nullable;
|
|
|
+import androidx.camera.core.CameraSelector;
|
|
|
+import androidx.camera.core.ImageAnalysis;
|
|
|
+import androidx.camera.core.ImageCapture;
|
|
|
+import androidx.camera.core.ImageCaptureException;
|
|
|
+import androidx.camera.core.ImageProxy;
|
|
|
+import androidx.camera.core.Preview;
|
|
|
+import androidx.camera.lifecycle.ProcessCameraProvider;
|
|
|
+import androidx.core.content.ContextCompat;
|
|
|
+import androidx.fragment.app.Fragment;
|
|
|
+
|
|
|
+import com.blankj.utilcode.util.LogUtils;
|
|
|
+import com.blankj.utilcode.util.ThreadUtils;
|
|
|
+import com.example.chemical.databinding.FragmentFaceDetectBinding;
|
|
|
+import com.example.chemical.ui.verify.DoubleVerifyActivity;
|
|
|
+import com.google.android.gms.tasks.OnSuccessListener;
|
|
|
+import com.google.common.util.concurrent.ListenableFuture;
|
|
|
+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.FaceDetector;
|
|
|
+import com.google.mlkit.vision.face.FaceDetectorOptions;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+import java.nio.ByteBuffer;
|
|
|
+import java.util.List;
|
|
|
+import java.util.concurrent.ExecutorService;
|
|
|
+import java.util.concurrent.Executors;
|
|
|
+
|
|
|
+public class FaceDetectFragment extends Fragment {
|
|
|
+ private static FaceDetectFragment instance; // 静态实例
|
|
|
+ private FragmentFaceDetectBinding binding;
|
|
|
+ private FaceDetector faceDetector;
|
|
|
+ private ExecutorService cameraExecutor;
|
|
|
+
|
|
|
+ private ProcessCameraProvider cameraProvider;
|
|
|
+
|
|
|
+ private ImageAnalysis imageAnalyzer;
|
|
|
+
|
|
|
+ private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
|
|
|
+
|
|
|
+ private int lastSize = 0;
|
|
|
+
|
|
|
+ private boolean isPause = false;
|
|
|
+
|
|
|
+ private DoubleVerifyActivity.DoubleVerifyListener doubleVerifyListener;
|
|
|
+
|
|
|
+ private Runnable providerFutureRunnable;
|
|
|
+ private Preview preview;
|
|
|
+
|
|
|
+ private FaceDetectFragment() {
|
|
|
+ }
|
|
|
+
|
|
|
+ // 静态方法,获取实例
|
|
|
+ public static FaceDetectFragment getInstance() {
|
|
|
+ if (instance == null) {
|
|
|
+ instance = new FaceDetectFragment();
|
|
|
+ }
|
|
|
+ return instance;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Nullable
|
|
|
+ @Override
|
|
|
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
|
+ binding = FragmentFaceDetectBinding.inflate(inflater, container, false);
|
|
|
+ return binding.getRoot();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
|
+ super.onCreate(savedInstanceState);
|
|
|
+ if (instance != null && instance != this) {
|
|
|
+ getParentFragmentManager().beginTransaction()
|
|
|
+ .replace(getId(), instance)
|
|
|
+ .commit();
|
|
|
+ } else {
|
|
|
+ instance = this;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void startFaceDetect() {
|
|
|
+ cameraExecutor = Executors.newSingleThreadExecutor();
|
|
|
+ FaceDetectorOptions options = new FaceDetectorOptions.Builder()
|
|
|
+ .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
|
|
|
+ .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
|
|
|
+ .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
|
|
|
+ .build();
|
|
|
+ faceDetector = FaceDetection.getClient(options);
|
|
|
+ startCamera();
|
|
|
+ isPause = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void stopFaceDetect() {
|
|
|
+ isPause = true;
|
|
|
+ recycleFaceDetect();
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressLint("RestrictedApi")
|
|
|
+ private void recycleFaceDetect() {
|
|
|
+ // 释放相机资源
|
|
|
+ if (cameraProvider != null) {
|
|
|
+ cameraProvider.unbindAll();
|
|
|
+ cameraProvider = null;
|
|
|
+
|
|
|
+ // 释放 FaceAnalyzer
|
|
|
+ if (imageAnalyzer != null) {
|
|
|
+ imageAnalyzer.clearAnalyzer(); // 清除 Analyzer
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (null != cameraExecutor) {
|
|
|
+ // 关闭线程池
|
|
|
+ cameraExecutor.shutdown();
|
|
|
+ }
|
|
|
+ if (null != faceDetector) {
|
|
|
+ // 释放人脸检测器
|
|
|
+ faceDetector.close();
|
|
|
+ }
|
|
|
+ if (null != providerFutureRunnable) {
|
|
|
+ preview.onDetached();
|
|
|
+ cameraProviderFuture.cancel(true);
|
|
|
+ providerFutureRunnable = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onDestroyView() {
|
|
|
+ super.onDestroyView();
|
|
|
+ recycleFaceDetect();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void startCamera() {
|
|
|
+ cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext());
|
|
|
+ if (null == providerFutureRunnable) {
|
|
|
+ providerFutureRunnable = new Runnable() {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ try {
|
|
|
+ cameraProvider = cameraProviderFuture.get();
|
|
|
+ // 预览用例
|
|
|
+ preview = new Preview.Builder().build();
|
|
|
+ preview.setSurfaceProvider(binding.previewView.getSurfaceProvider());
|
|
|
+ // 图像分析用例
|
|
|
+ imageAnalyzer =
|
|
|
+ new ImageAnalysis.Builder()
|
|
|
+ .setTargetResolution(new Size(640, 480))
|
|
|
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
|
|
+ .build();
|
|
|
+ imageAnalyzer.setAnalyzer(cameraExecutor, new ImageAnalysis.Analyzer() {
|
|
|
+ @Override
|
|
|
+ public void analyze(@NonNull ImageProxy imageProxy) {
|
|
|
+ ImageProxy.PlaneProxy[] planes = imageProxy.getPlanes();
|
|
|
+ int rotationDegrees = imageProxy.getImageInfo().getRotationDegrees();
|
|
|
+ if (planes.length == 0 || planes[0] == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ InputImage image = InputImage.fromByteBuffer(
|
|
|
+ planes[0].getBuffer(),
|
|
|
+ imageProxy.getWidth(),
|
|
|
+ imageProxy.getHeight(),
|
|
|
+ rotationDegrees,
|
|
|
+ InputImage.IMAGE_FORMAT_NV21 // 或其他格式
|
|
|
+ );
|
|
|
+ if (isPause) {
|
|
|
+ imageProxy.close();
|
|
|
+ if (binding.hintTV.getVisibility() == View.VISIBLE) {
|
|
|
+ ThreadUtils.runOnUiThread(() -> binding.hintTV.setVisibility(View.GONE));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ faceDetector.process(image)
|
|
|
+ .addOnSuccessListener(
|
|
|
+ new OnSuccessListener<List<Face>>() {
|
|
|
+ @Override
|
|
|
+ public void onSuccess(List<Face> faces) {
|
|
|
+
|
|
|
+ if (binding.hintTV.getVisibility() == View.GONE) {
|
|
|
+ binding.hintTV.setVisibility(View.VISIBLE);
|
|
|
+ }
|
|
|
+ if (null == faces || faces.isEmpty() || lastSize == faces.size()) {
|
|
|
+ binding.hintTV.setText("未检测到人脸");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ lastSize = faces.size();
|
|
|
+ if (faces.size() > 1) {
|
|
|
+ binding.hintTV.setText("请确保相机区域内只有一个人脸");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ isPause = true;
|
|
|
+ if (null != doubleVerifyListener) {
|
|
|
+ doubleVerifyListener.detect(DetectType.FACE_DETECT, null, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .addOnFailureListener(
|
|
|
+ e -> LogUtils.e("人脸检测失败", Log.getStackTraceString(e)))
|
|
|
+ .addOnCompleteListener(task -> imageProxy.close());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // 选择摄像头
|
|
|
+ CameraSelector cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA;
|
|
|
+ // 解绑之前的用例
|
|
|
+ cameraProvider.unbindAll();
|
|
|
+ // 绑定用例到相机
|
|
|
+ cameraProvider.bindToLifecycle(getViewLifecycleOwner(), cameraSelector, preview, imageAnalyzer);
|
|
|
+ } catch (Exception e) {
|
|
|
+ LogUtils.e(Log.getStackTraceString(e));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ cameraProviderFuture.addListener(providerFutureRunnable, ContextCompat.getMainExecutor(requireContext()));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setActivityListener(DoubleVerifyActivity.DoubleVerifyListener doubleVerifyListener) {
|
|
|
+ this.doubleVerifyListener = doubleVerifyListener;
|
|
|
+ }
|
|
|
+}
|