Android实现人脸识别检测(FaceDetector)

发布时间 2023-05-16 22:37:33作者: 又一岁荣枯

前言

目前网上常规解决这块问题的方案总结了一下有以下五种,当然有更多的暂时还未了解过~

1、OpenCV (API level 8 +)

2、Camera内部的 API (API level 14+)

3、android.media.FaceDetector 静态检测 (API level 1 +)

4、Google Play Service 的 Vision API (API 9,在 API 17 增加了一些功能)

5、Face++ Android SDK

正文

1、效果图

本文重点介绍FaceDetector基于静态人脸的检测实现,先上效果图~
image

2、检测说明

由于FaceDetector是只接收bitmap的静态图片的人脸检测方案,因此我们在做检测的时候需要将每一帧预览帧拿去做检验。

但是你以为这样就完了,太天真了,你每一帧是一个完整的手机屏幕里面的人脸可能检测的时候根本就没有出现在圆圈内。因此我们需要用前景遮挡图+背景预览帧画面组合合成一张待检测图片进行送检。

3、FaceDetector检测核心代码

FaceDetector face_detector = new FaceDetector(newBP.getWidth(), newBP.getHeight(), faceNum);
FaceDetector.Face[] faces = new FaceDetector.Face[faceNum];

image

image

4、注意事项

FaceDetector检测的bitmap要求为RGB_565格式(The bitmap must be in 565 format)

实例

1、相机实时预览

使用相机实时预览这边使用的是SurfaceView

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
</layout>

2、定义圆形透明遮挡层

2.1 使用Canvas首先绘制整屏幕的白色遮挡层

canvas.drawARGB(255, 255, 255, 255);

2.2 使用Canvas在白色遮挡层上抠出一个透明的原型显示预览画面

mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAlpha(0);
// android.graphics.PorterDuff.Mode.CLEAR 显示挖空canvas为透明
mPaint.setXfermode(new PorterDuffXfermode(android.graphics.PorterDuff.Mode.CLEAR));

canvas.drawCircle(宽度, 高度, 大小, mPaint);

3、开始进行相机预览

使用SurfaceHolder对象增加回调监听,在监听中预览和处理响应识别业务

/**
     * 开始预览
     */
    private void startPreview() {
        SurfaceHolder holder = binding.surfaceView.getHolder();
        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {
                mCamera = getCustomCamera();
                mCamera.setPreviewCallback((data, camera) -> {
                    // 是否已经检测成功,如果是则不继续检测
                    if (isIdentify) {
                        checkFaces(data, camera, surfaceHolder, this);
                    }
                });
            }

            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
                if (mCamera != null) {
                    Camera.Parameters parameters = mCamera.getParameters();
                    // 选择合适的图片尺寸,必须是手机支持的尺寸
                    List<Camera.Size> sizeList = parameters.getSupportedPictureSizes();
                    // 如果sizeList只有一个我们也没有必要做什么了,因为就他一个别无选择
                    if (sizeList.size() > 1) {
                        for (int j = 0; j < sizeList.size(); j++) {
                            Camera.Size size = sizeList.get(j);
                            previewWidth = size.width;
                            previewHeight = size.height;
                        }
                    }
                    //设置照片的大小
                    parameters.setPictureSize(previewWidth, previewHeight);
                    mCamera.setParameters(parameters);
                    try {
                        mCamera.setPreviewDisplay(holder);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    CameraUtils.takePicture((data, camera) -> {
                        CameraUtils.startPreview();
                        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                        if (bitmap != null) {
                            bitmap = ImageUtils.getRotatedBitmap(bitmap, 180);
                        }
                        CameraUtils.startPreview();
                    });
                    //调用相机预览功能
                    mCamera.startPreview();
                }
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
                if (null != mCamera) {
                    holder.removeCallback(this);
                    mCamera.setPreviewCallback(null);
                    //停止预览
                    mCamera.stopPreview();
                    mCamera.lock();
                    //释放相机资源
                    mCamera.release();
                    mCamera = null;
                }
            }
        });
    }

4、人脸识别检测

4.1 预览图片

通过回调方法我们可以实时获取相机预览的图片字节数据,但是需要注意的是这个图片的格式是Yuv格式的,如果要在Android中正常的显示和操作则需要进行常规的转换,这里Yuv转bitmap的方法如下:

  public Bitmap convertYUVtoRGB(byte[] yuvData, int width, int height) {
        if (yuvType == null) {
            yuvType = new Type.Builder(rs, Element.U8(rs)).setX(yuvData.length);
            in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

            rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
            out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
        }
        in.copyFrom(yuvData);
        yuvToRgbIntrinsic.setInput(in);
        yuvToRgbIntrinsic.forEach(out);
        Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        out.copyTo(bmpout);
        return bmpout;
    }

4.2 构建检测图片

其中核心的如前面说的我们需要将整个画面截屏出来进行识别,但是SurfaceView由于他的缓存机制是无法通过常规的getDrawableCache获取其截图的,就算你获取了也是空白图片,所以这里需要获取非SurfaceView部分和相机预览部分进行人为的叠拼。

/**
     * 合并两张bitmap图片
     *
     * @param firstBitmap
     * @param secondBitmap
     * @return
     */
    private Bitmap mergeBitmap(Bitmap firstBitmap, Bitmap secondBitmap) {
        Bitmap bitmap = Bitmap.createBitmap(firstBitmap.getWidth(), firstBitmap.getHeight(), Bitmap.Config.RGB_565);
        Canvas canvas = new Canvas(bitmap);
        canvas.drawBitmap(firstBitmap, new Matrix(), null);
        canvas.drawBitmap(secondBitmap, new Matrix(), null);
        return bitmap;
    }

4.3 进行人脸检测

进行检测时直接调用上述的系统API即可完成操作,需要注意的是根据测试,当设置的检测数量大于人脸数量时,有时会直接返回最大数量而非实际数量。

Bitmap bitmap565 = bp.copy(Bitmap.Config.RGB_565, true);
Bitmap newBP = mergeBitmap(bitmap565, getViewBitmap());
FaceDetector face_detector = new FaceDetector(newBP.getWidth(), newBP.getHeight(), 1);
FaceDetector.Face[] faces = new FaceDetector.Face[1];
int face_count = face_detector.findFaces(newBP, faces);