Android原生人脸识别Camera2示例

景嘉志
2023-12-01

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);
    }

    /**
     * 初始化并获取相机开启回调对象。当准备就绪后,发起预览请求
     */
    @SuppressLint("NewApi")
    private CameraDevice.StateCallback getCDeviceOpenCallback() {
        if (cDeviceOpenCallback == null) {
            cDeviceOpenCallback = new CameraDevice.StateCallback() {
                @Override
                public void onOpened(CameraDevice camera) {//打开摄像头
                    cDevice = camera;
                    try {

                        //创建Session,需先完成画面呈现目标(此处为预览和拍照Surface)的初始化
                        camera.createCaptureSession(Arrays.asList(getPreviewSurface(), getCaptureSurface()), new
                                CameraCaptureSession.StateCallback() {
                                    @Override
                                    public void onConfigured(CameraCaptureSession session) {
                                        cSession = session;
                                        //构建预览请求,并发起请求
                                        Log.i(TAG, "[发出预览请求]");

                                        try {
                                            session.setRepeatingRequest(getPreviewRequest(), getPreviewCallback(),
                                                    getCHandler());
                                        } catch (CameraAccessException e) {
                                            Log.i(TAG, "--" + e.getMessage());
                                        }
                                    }

                                    @Override
                                    public void onConfigureFailed(CameraCaptureSession session) {
                                        session.close();
                                    }
                                }, getCHandler());
                    } catch (CameraAccessException e) {
                        Log.i(TAG, "--" + e.getMessage());
                    }
                }

                @Override
                public void onDisconnected(CameraDevice camera) {
                    //关闭摄像头
                    camera.close();
                }

                @Override
                public void onError(CameraDevice camera, int error) {
                    //发生错误
                    camera.close();
                }
            };
        }
        return cDeviceOpenCallback;
    }

    /**
     * 初始化并获取相机线程处理
     *
     * @return
     */
    private Handler getCHandler() {
        if (cHandler == null) {
            //单独开一个线程给相机使用
            cHandlerThread = new HandlerThread("cHandlerThread");
            cHandlerThread.start();
            cHandler = new Handler(cHandlerThread.getLooper());
        }
        return cHandler;
    }


    /**
     * 获取支持的最高人脸检测级别
     *
     * @return
     */
    private int getFaceDetectMode() {
        if (faceDetectModes == null) {
            Log.i(TAG, "getFaceDetectMode: ----");
            return CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL;
        } else {
            Log.i(TAG, "getFaceDetectMode: --2--" + faceDetectModes[faceDetectModes.length - 1]);
            return faceDetectModes[faceDetectModes.length - 1];
        }

    }

    /**
     * 初始化并获取预览回调对象
     *
     * @return
     */
    @SuppressLint("NewApi")
    private CameraCaptureSession.CaptureCallback getPreviewCallback() {
        if (previewCallback == null) {
            previewCallback = new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest
                        request, TotalCaptureResult result) {
                    onCameraImagePreviewed(result);
                }
            };
        }
        return previewCallback;
    }

    /**
     * 生成并获取预览请求
     *
     * @return
     */
    @SuppressLint("NewApi")
    private CaptureRequest getPreviewRequest() {
        previewRequest = getPreviewRequestBuilder().build();
        return previewRequest;
    }

    /**
     * 初始化并获取预览请求构建对象,进行通用配置,并每次获取时进行人脸检测级别配置
     *
     * @return
     */
    @SuppressLint("NewApi")
    private CaptureRequest.Builder getPreviewRequestBuilder() {
        if (previewRequestBuilder == null) {
            try {
                previewRequestBuilder = cSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                previewRequestBuilder.addTarget(getPreviewSurface());
                previewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);//自动曝光、白平衡、对焦
            } catch (CameraAccessException e) {
                Log.i(TAG, "--" + e.getMessage());
            }
        }
//        previewRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, getFaceDetectMode());//设置人脸检测级别
        previewRequestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, CameraCharacteristics.STATISTICS_FACE_DETECT_MODE_SIMPLE);//设置人脸检测级别
        previewRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, 0);
        return previewRequestBuilder;
    }


    /**
     * 获取预览Surface
     *
     * @return
     */
    private Surface getPreviewSurface() {
        if (previewSurface == null) {
            previewSurface = new Surface(cView.getSurfaceTexture());
        }

        return previewSurface;
    }

    /**
     * 处理相机画面处理完成事件,获取检测到的人脸坐标,换算并绘制方框
     *
     * @param result
     */
    @SuppressLint({"NewApi", "LocalSuppress"})
    private void onCameraImagePreviewed(CaptureResult result) {
        Face faces[] = result.get(CaptureResult.STATISTICS_FACES);
        Log.i(TAG, "检测到有人脸,进行拍照操作:faceLength=" + faces.length);
        if (faces.length > 0) {
            Log.i(TAG, "检测到有人脸,-----------------------------------");
            //检测到有人脸,控制相机进行拍照操作
            executeCapture();
        }
    }

    /**
     * 初始化拍照相关
     */
    @SuppressLint("NewApi")
    private Surface getCaptureSurface() {
        if (cImageReader == null) {
            cImageReader = ImageReader.newInstance(getCaptureSize().getWidth(), getCaptureSize().getHeight(),
                    ImageFormat.JPEG, 2);
            cImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
                @Override
                public void onImageAvailable(ImageReader reader) {
                    //拍照最终回调
                    onCaptureFinished(reader);
                }
            }, getCHandler());
            captureSurface = cImageReader.getSurface();
        }
        return captureSurface;
    }

    /**
     * 获取拍照尺寸
     *
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private Size getCaptureSize() {
        if (captureSize != null) {
            return captureSize;
        } else {
            return new Size(cView.getWidth(), cView.getHeight());
        }
    }

    /**
     * 执行拍照
     */
    @SuppressLint("NewApi")
    private void executeCapture() {
        try {
            Log.i(TAG, "发出请求");
            cSession.capture(getCaptureRequest(), getCaptureCallback(), getCHandler());
        } catch (CameraAccessException e) {
            Log.i(TAG, "--" + e.getMessage());
        }
    }

    @SuppressLint("NewApi")
    private CaptureRequest getCaptureRequest() {
        captureRequest = getCaptureRequestBuilder().build();
        return captureRequest;
    }

    @SuppressLint("NewApi")
    private CaptureRequest.Builder getCaptureRequestBuilder() {
        if (captureRequestBuilder == null) {
            try {
                captureRequestBuilder = cSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
                //设置拍照回调接口
                captureRequestBuilder.addTarget(getCaptureSurface());
                //TODO 1 照片旋转
//                int rotation =getWindowManager().getDefaultDisplay().getRotation();
                int rotation = 0;
                int rotationTo = getOrientation(rotation);

                captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, rotationTo);
            } catch (CameraAccessException e) {
                Log.i(TAG, "--" + e.getMessage());
            }
        }
        return captureRequestBuilder;
    }

    @SuppressLint("NewApi")
    private CameraCaptureSession.CaptureCallback getCaptureCallback() {
        if (captureCallback == null) {
            captureCallback = new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest
                        request, TotalCaptureResult result) {
                }
            };
        }
        return captureCallback;
    }

    /**
     * Retrieves the JPEG orientation from the specified screen rotation.
     *
     * @param rotation The screen rotation.
     * @return The JPEG orientation (one of 0, 90, 270, and 360)
     */
    private int getOrientation(int rotation) {
        return (ORIENTATIONS.get(rotation) + cOrientation + 270) % 360;
    }

    /**
     * 处理相机拍照完成的数据
     *
     * @param reader
     */

    Bitmap takeBitmap = null;
    Bitmap takeBitmap2 = null;
    @SuppressLint("NewApi")
    private void onCaptureFinished(ImageReader reader) {
        Image image = reader.acquireLatestImage();
        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);
        image.close();
        buffer.clear();
        takeBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);

        /**
         * 为了解决预览和拍照左右颠倒问题
         */
        Matrix m = new Matrix();
        m.postScale(-1, 1); // 镜像水平翻转
        takeBitmap2 = Bitmap.createBitmap(takeBitmap,0,0,takeBitmap.getWidth(),takeBitmap.getHeight(),m,true);
        if (ioShowBitmapListener != null) {
            ioShowBitmapListener.showBitmap(takeBitmap2);
        }
//        Runtime.getRuntime()
//                .gc();
    }


    @SuppressLint("NewApi")
    public void closeCamera() {
        if (cSession != null) {
            try {
                cSession.stopRepeating();
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
            cSession.close();
            cSession = null;
        }

        if (cDevice != null) {
            cDevice.close();
            cDevice = null;
        }
        if (cImageReader != null) {
            cImageReader.close();
            cImageReader = null;
            captureRequestBuilder = null;
        }
        if (cHandlerThread != null) {
            cHandlerThread.quitSafely();
            try {
                cHandlerThread.join();
                cHandlerThread = null;
                cHandler = null;
            } catch (InterruptedException e) {
                Log.i(TAG, "--" + e.getMessage());
            }
        }

        if (captureRequestBuilder != null) {
            captureRequestBuilder.removeTarget(captureSurface);
            captureRequestBuilder = null;
        }
        if (captureSurface != null) {
            captureSurface.release();
            captureSurface = null;
        }
        if (previewRequestBuilder != null) {
            previewRequestBuilder.removeTarget(previewSurface);
            previewRequestBuilder = null;
        }
        if (previewSurface != null) {
            previewSurface.release();
            previewSurface = null;
        }

        if (takeBitmap!=null){
            takeBitmap.recycle();
            takeBitmap=null;
        }

    }


    private IOShowBitmapListener ioShowBitmapListener;

    public void setIoShowBitmapListener(IOShowBitmapListener ioShowBitmapListener) {
        this.ioShowBitmapListener = ioShowBitmapListener;
    }

    public interface IOShowBitmapListener {
        void showBitmap(Bitmap bitmap);
    }

}

5. 参考文献

https://blog.csdn.net/wangzhicheng1983/article/details/122660654

6. 下载地址

Demo下载地址

 类似资料: