Android原生人脸识别Camera2示例

Posted zhang106209

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android原生人脸识别Camera2示例相关的知识,希望对你有一定的参考价值。

1. 简介

Camera2 是最新的低级 android 相机包,取代了已弃用的Camera类。Camera2 为复杂的用例提供了深入的控制,但需要您管理特定于设备的配置。源码地址

2. Camera2相机开发流程总结

1.检测并访问相机资源:检查手机是否存在相机资源,如果存在则请求访问相机资源。

2.创建预览界面:创建继承自SurfaceView并实现SurfaceHolder接口的拍摄预览类。有了拍摄预览类,即可创建一个布局文件,将预览画面与设计好的用户界面控件融合在一起,实时显示相机的预览图像。

3.设置拍照监听器:给用户界面控件绑定监听器,使其能响应用户操作, 开始拍照过程。

4.拍照并保存文件:将拍摄获得的图像转换成位图文件,最终输出保存成各种常用格式的图片。

5.释放相机资源:相机是一个共享资源,当相机使用完毕后,必须正确地将其释放,以免其它程序访问使用时发生冲突。

3. Camera2消息流转

1.由SystemService创建CameraManager;
2.整个camera2由CameraManager来进行统一管理;
3.CameraManager通过CameraDevice、CameraCharacteristic和CameraCaptureSession对Camera进行操作;
4.CameraDevice和CameraCaptureSession都有回调函数完成相关操作,包括相机打开,capture过程处理,capture完成处理等。
5.Android设备发送CameraCaptureRequest给Camera,而Camera处理完后发送元数据CameraMetaData给Android设备。

4. 示例类

4.1 Camera2Utils

package com.zw.camera2test.camera2;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.WindowManager;

import androidx.annotation.RequiresApi;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;


public class Camera2Utils 

    private static final String TAG = "Camera2Utils";
    private static Camera2Utils mCameraUtils;
    private CameraManager cManager;
    private Size cPixelSize;//相机成像尺寸
    private int cOrientation;
    private Size captureSize;
    private int[] faceDetectModes;
    private TextureView cView;//用于相机预览
    private Surface previewSurface;//预览Surface
    private ImageReader cImageReader;
    private Surface captureSurface;//拍照Surface
    HandlerThread cHandlerThread;//相机处理线程
    Handler cHandler;//相机处理
    CameraDevice cDevice;
    CameraCaptureSession cSession;
    CameraDevice.StateCallback cDeviceOpenCallback = null;//相机开启回调
    CaptureRequest.Builder previewRequestBuilder;//预览请求构建
    CaptureRequest previewRequest;//预览请求
    CameraCaptureSession.CaptureCallback previewCallback;//预览回调
    CaptureRequest.Builder captureRequestBuilder;
    CaptureRequest captureRequest;
    CameraCaptureSession.CaptureCallback captureCallback;

    private Context mContext;
    WindowManager mWindowManager;
    //为了使照片竖直显示
    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

    static 
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    

    public static Camera2Utils getInstance() 
        if (mCameraUtils == null) 
            synchronized (Camera2Utils.class) 
                if (mCameraUtils == null) 
                    mCameraUtils = new Camera2Utils();
                
            
        
        return mCameraUtils;
    

    public void init(WindowManager windowManager, Context context, TextureView textureView) 
        this.mWindowManager = windowManager;
        this.mContext = context;
        this.cView = textureView;
    


    @SuppressLint("MissingPermission")
    public void startPreview() 
        boolean isFront = true;
        //前置摄像头
        String cId = "";
        if (isFront) 
            cId = CameraCharacteristics.LENS_FACING_BACK + "";
         else 
            cId = CameraCharacteristics.LENS_FACING_FRONT + "";
        

        cManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
        //根据摄像头ID,开启摄像头
        try 

            //获取开启相机的相关参数
            CameraCharacteristics characteristics = cManager.getCameraCharacteristics(cId);
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] previewSizes = map.getOutputSizes(SurfaceTexture.class);//获取预览尺寸
            Size[] captureSizes = map.getOutputSizes(ImageFormat.JPEG);//获取拍照尺寸
            cOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//获取相机角度
            Rect cRect = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);//获取成像区域
            cPixelSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);//获取成像尺寸,同上
            Log.i(TAG, "获取相机角度 : " + cOrientation);
            //可用于判断是否支持人脸检测,以及支持到哪种程度
            faceDetectModes = characteristics.get(CameraCharacteristics
                    .STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES);//支持的人脸检测模式
            int maxFaceCount = characteristics.get(CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT);
            int mFaceDetectMode = CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF;
            for (int i = 0; i < faceDetectModes.length; i++) 
                int face = faceDetectModes[i];
                if (face == CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL || face == CaptureRequest.STATISTICS_FACE_DETECT_MODE_SIMPLE) 
                    Log.i(TAG, "相机硬件不支持人脸检测---" + face);
                    mFaceDetectMode = CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL;
                    break;
                
            
            if (mFaceDetectMode == CaptureRequest.STATISTICS_FACE_DETECT_MODE_OFF) 
                Log.i(TAG, "相机硬件不支持人脸检测");
                return;
            
            //支持的最大检测人脸数量
            //此处写死640*480,实际从预览尺寸列表选择
//            previewSizes[2];
            Size sSize = new Size(640, 480);
            //设置预览尺寸(避免控件尺寸与预览画面尺寸不一致时画面变形)
            transformImage(previewSizes, cView.getWidth(), cView.getHeight());
            cView.getSurfaceTexture().setDefaultBufferSize(sSize.getWidth(), sSize.getHeight());
            cManager.openCamera(cId, getCDeviceOpenCallback(), getCHandler());


         catch (CameraAccessException e) 
        
    

    private void transformImage(Size[] previewSizes, int width, int height) 
        Size mPreviewSize = getOptimalSize(previewSizes, width, height);
        if (mPreviewSize == null || cView == null) 
            return;
        
        Matrix matrix = new Matrix();
        int rotation = mWindowManager.getDefaultDisplay().getRotation();
        RectF textureRectF = new RectF(0, 0, width, height);
        RectF previewRectF = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
        float centerX = textureRectF.centerX();
        float centery = textureRectF.centerY();
        if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_270) 
         else if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) 
            previewRectF.offset(centerX - previewRectF.centerX(), centery - previewRectF.centerY());
            matrix.setRectToRect(textureRectF, previewRectF, Matrix.ScaleToFit.FILL);
            float scale = Math.max((float) width / mPreviewSize.getWidth(), (float) height / mPreviewSize.getHeight());
            matrix.postScale(scale, scale, centerX, centery);
            matrix.postRotate(90 * (rotation - 2), centerX, centery);
            cView.setTransform(matrix);
        
    


    /**
     * 解决预览变形问题
     *
     * @param sizeMap
     * @param width
     * @param height
     * @return
     */
    //选择sizeMap中大于并且最接近width和height的size
    private Size getOptimalSize(Size[] sizeMap, int width, int height) 
        List<Size> sizeList = new ArrayList<>();
        for (Size option : sizeMap) 
            if (width > height) 
                if (option.getWidth() > width && option.getHeight() > height) 
                    sizeList.add(option);
                
             else 
                if (option.getWidth() > height && option.getHeight() > width) 
                    sizeList.add(option);
                
            
        
        if (sizeList.size() > 0) 
            return Collections.min(sizeList, new Comparator<Size>() 
                @Override
                public int compare(Size lhs, Size rhs) 
                    return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight());
                
            );
        
        return sizeMap[0];
    

    private Size getOptimalPreviewSize(Size[] sizes, int w, int h) 
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) w / h;
        if (sizes == null) return null;

        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        // Try to find an size match aspect ratio and size
        Size size = null;
        for (int i = 0; i < sizes.length; i++) 
            size = sizes[i];
            double ratio = (double) size.getWidth() / size.getHeight();
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.getHeight() - targetHeight) < minDiff) 
                optimalSize = size;
                minDiff = Math.abs(size.getHeight() - targetHeight);
            
        

        // Cannot find the one match the aspect ratio, ignore the requirement
        if (optimalSize == null) 
            minDiff = Double.MAX_VALUE;
            for (int i = 0; i < sizes.length; i++) 
                size = sizes[i];
                if (Math.abs(size.getHeight() - targetHeight) < minDiff) 
                    optimalSize = size;
                    minDiff = Math.abs(size.getHeight() - targetHeight);
                
            
        
        return optimalSize;
    

    private void configureTransform(int viewWidth, int viewHeight) 
        int rotation = 1;
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
        float centerX = viewRect.centerX();
        float centerY = viewRect.centerY();
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) 
//            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
         else if (Surface.ROTATION_180 == rotation) 
            matrix.postRotate(180, centerX, centerY);
        
        cView.setTransform(matrix);
    

    /**

以上是关于Android原生人脸识别Camera2示例的主要内容,如果未能解决你的问题,请参考以下文章

我无法用 camera2 检测到人脸

使用 Android Camera2 API 进行人脸检测和画圆

基于OpenCV和C++原生(JNI)的Android数字图像处理+人脸识别demo

神目人脸识别Android SDK Demo说明

Android 11.0 Camera2 默认选择拍照尺寸修改及流程分析

Dlib android实时人脸识别问题