Android 人臉檢測(cè) 非人臉識(shí)別

Note:文章以Google Android API提供的人臉探測(cè)技術(shù)進(jìn)行講解,并且使用的Camera class進(jìn)行開(kāi)發(fā),而不是Google推薦的camera2,如果你開(kāi)發(fā)中需要camera2可移步Detecting camera features with Camera2(需要梯子)
未引用OpenCV

人臉識(shí)別說(shuō)明

人臉檢測(cè):檢測(cè)并定位圖片中的人臉,返回人臉框坐標(biāo)。
人臉比對(duì):比對(duì)兩張臉為同一個(gè)人的可信度。
人臉關(guān)鍵點(diǎn):定位返回人臉關(guān)鍵部位和四官坐標(biāo)(眉、眼、鼻、口)
人臉屬性:通過(guò)算法獲取人臉屬性,如:年齡、 性別、微笑程度、眼睛狀態(tài)、人種等
說(shuō)這么多是不是很激動(dòng),但別忘了題目是人臉檢測(cè),我們要講的只有一個(gè)功能,人臉檢測(cè)

API

Google官方給出的有兩種方法:

  1. FaceDetector:通過(guò)傳遞Bitmap檢測(cè)圖中的人臉,同時(shí)返回眼睛部位,識(shí)別人數(shù),未詳細(xì)測(cè)試;
  2. Camera.FaceDetectionListener:通過(guò)打開(kāi)攝像頭(不是相機(jī)),實(shí)時(shí)獲取人臉定位,最大5人。

講解

FaceDetector傳圖識(shí)臉
 
    /**
     * Creates a FaceDetector, configured with the size of the images to
     * be analysed and the maximum number of faces that can be detected.
     * These parameters cannot be changed once the object is constructed.
     * Note that the width of the image must be even.
     * 
     * @param width  the width of the image
     * @param height the height of the image
     * @param maxFaces the maximum number of faces to identify
     *
     */
    public FaceDetector(int width, int height, int maxFaces){}

翻譯:創(chuàng)建一個(gè)FaceDetector,配置要分析的圖像的大小以及可以檢測(cè)到的最大面孔數(shù)。一旦構(gòu)建對(duì)象,這些參數(shù)就不能被更改。請(qǐng)注意,圖像的寬度必須均勻。

用法:

public Bitmap detectionFace(Bitmap b) {        
            // 檢測(cè)前必須轉(zhuǎn)化為RGB_565格式。文末有詳述連接
            Bitmap bitmap = b.copy(Bitmap.Config.RGB_565, true);
            b.recycle();
            // 設(shè)定最大可查的人臉數(shù)量
            int MAX_FACES = 5;
            FaceDetector faceDet = new FaceDetector(bitmap.getWidth(), bitmap.getHeight(), MAX_FACES);
            // 將人臉數(shù)據(jù)存儲(chǔ)到faceArray 中
            FaceDetector.Face[] faceArray = new FaceDetector.Face[MAX_FACES];
            // 返回找到圖片中人臉的數(shù)量,同時(shí)把返回的臉部位置信息放到faceArray中,過(guò)程耗時(shí)
            int findFaceCount = faceDet.findFaces(bitmap, faceArray);
            // 獲取傳回的臉部數(shù)組中的第一張臉的信息
            FaceDetector.Face face1 = faceArray[0];
            // 獲取雙眼的中心點(diǎn),用一個(gè)PointF來(lái)接收其x、y坐標(biāo)
            PointF point = new PointF();
            face1.getMidPoint(point);
            // 獲取該部位為人臉的可信度,0~1
            float confidence = face1.confidence();
            // 獲取雙眼間距
            float eyesDistance = face1.eyesDistance();
            // 獲取面部姿勢(shì)
            // 傳入X則獲取到x方向上的角度,傳入Y則獲取到y(tǒng)方向上的角度,傳入Z則獲取到z方向上的角度
            float angle = face1.pose(FaceDetector.Face.EULER_X);

            // todo 在bitmap上繪制一個(gè)Rect框住臉,因?yàn)榉祷氐氖茄劬ξ恢?,所以還要做一些處理

            return bitmap;
}

以上就是通過(guò)傳遞一張Bitmap然后找出其中面部的具體方法
識(shí)別率:低

Camera.FaceDetectionListener實(shí)時(shí)檢測(cè)臉部

通過(guò)調(diào)用攝像頭,提供一個(gè)實(shí)時(shí)檢測(cè)臉部的方案
流程:打開(kāi)攝像頭→給Camera類(lèi)傳遞回調(diào)接口→從接口獲取臉部位置信息

    /**
     * Callback interface for face detected in the preview frame.
     *
     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
     *             applications.
     */
    @Deprecated
    public interface FaceDetectionListener
    {
        /**
         * Notify the listener of the detected faces in the preview frame.
         *
         * @param faces The detected faces in a list
         * @param camera  The {@link Camera} service object
         */
        void onFaceDetection(Face[] faces, Camera camera);
    }

這是一個(gè)回調(diào)接口,供Camera使用。返回的數(shù)據(jù)包含臉部集合和攝像頭對(duì)象。
用法:

  1. 在Activity的onCreate中提供一個(gè)SurfaceView給Camera顯示圖像
    private void initViews() {
        surfaceView = new SurfaceView(this);
        rectView = new DrawFacesView(this);
        addContentView(surfaceView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
        addContentView(rectView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
    }

這是Activity布局

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
  1. 在onCreate()中打開(kāi)相機(jī),設(shè)置監(jiān)聽(tīng)
    /**
     * 把攝像頭的圖像顯示到SurfaceView
     */
    private void openSurfaceView() {
        mHolder = surfaceView.getHolder();
        mHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                if (mCamera == null) {
                    mCamera = Camera.open();
                    try {
                        // 設(shè)置臉部檢測(cè)監(jiān)聽(tīng)
                        mCamera.setFaceDetectionListener(new FaceDetectorListener());
                        mCamera.setPreviewDisplay(holder);
                        // 開(kāi)始臉部檢測(cè)
                        startFaceDetection();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                if (mHolder.getSurface() == null) {
                    // preview surface does not exist
                    Log.e(TAG, "mHolder.getSurface() == null");
                    return;
                }

                try {
                    mCamera.stopPreview();

                } catch (Exception e) {
                    // ignore: tried to stop a non-existent preview
                    Log.e(TAG, "Error stopping camera preview: " + e.getMessage());
                }

                try {
                    mCamera.setPreviewDisplay(mHolder);
                    int measuredWidth = surfaceView.getMeasuredWidth();
                    int measuredHeight = surfaceView.getMeasuredHeight();
                    setCameraParms(mCamera, measuredWidth, measuredHeight);
                    mCamera.startPreview();

                    startFaceDetection(); // re-start face detection feature

                } catch (Exception e) {
                    // ignore: tried to stop a non-existent preview
                    Log.d(TAG, "Error starting camera preview: " + e.getMessage());
                }
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                mCamera.stopPreview();
                mCamera.release();
                mCamera = null;
                holder = null;
            }
        });
    }

    /**
     * 在攝像頭啟動(dòng)前設(shè)置參數(shù)
     *
     * @param camera
     * @param width
     * @param height
     */
    private void setCameraParms(Camera camera, int width, int height) {
        // 獲取攝像頭支持的pictureSize列表
        Camera.Parameters parameters = camera.getParameters();
        // /**/注釋的地方非必須,參考來(lái)源  [Android 手把手帶你玩轉(zhuǎn)自定義相機(jī)](http://blog.csdn.net/qq_17250009/article/details/52795530)
        /*List<Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes();
        // 從列表中選擇合適的分辨率
        Camera.Size pictureSize = getProperSize(pictureSizeList, (float) height / width);
        if (null == pictureSize) {
            pictureSize = parameters.getPictureSize();
        }
        // 根據(jù)選出的PictureSize重新設(shè)置SurfaceView大小
        float w = pictureSize.width;
        float h = pictureSize.height;
        parameters.setPictureSize(pictureSize.width, pictureSize.height);

        surfaceView.setLayoutParams(new FrameLayout.LayoutParams((int) (height * (h / w)), height));

        // 獲取攝像頭支持的PreviewSize列表
        List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();
        Camera.Size preSize = getProperSize(previewSizeList, (float) height / width);
        if (null != preSize) {
            parameters.setPreviewSize(preSize.width, preSize.height);
        }
*/
        parameters.setJpegQuality(100);
        // 不對(duì)焦,拍攝電腦上的圖片都模糊
        if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            // 連續(xù)對(duì)焦
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        }
        camera.cancelAutoFocus();
        // 攝像頭圖像旋轉(zhuǎn)90度,此處不嚴(yán)謹(jǐn)
        camera.setDisplayOrientation(90);// 可注釋該行代碼看看效果
        camera.setParameters(parameters);
    }

    /**
    * 啟動(dòng)臉部檢測(cè),如果getMaxNumDetectedFaces()!=0說(shuō)明不支持臉部檢測(cè)
    */
    public void startFaceDetection() {
        // Try starting Face Detection
        Camera.Parameters params = mCamera.getParameters();
        // start face detection only *after* preview has started
        if (params.getMaxNumDetectedFaces() > 0) {
            // mCamera supports face detection, so can start it:
            mCamera.startFaceDetection();
        } else {
            Log.e("tag", "【FaceDetectorActivity】類(lèi)的方法:【startFaceDetection】: " + "不支持");
        }
    }
  1. 臉部回調(diào)接口
    /**
     * 臉部檢測(cè)接口
     */
    private class FaceDetectorListener implements Camera.FaceDetectionListener {
        @Override
        public void onFaceDetection(Camera.Face[] faces, Camera camera) {
            if (faces.length > 0) {
                Camera.Face face = faces[0];
                Rect rect = face.rect;
                Log.d("FaceDetection", "可信度:" + face.score + "face detected: " + faces.length +
                        " Face 1 Location X: " + rect.centerX() +
                        "Y: " + rect.centerY() + "   " + rect.left + " " + rect.top + " " + rect.right + " " + rect.bottom);
                Log.e("tag", "【FaceDetectorListener】類(lèi)的方法:【onFaceDetection】: ");
                Matrix matrix = updateFaceRect();
                facesView.updateFaces(matrix, faces);
            } else {
                // 只會(huì)執(zhí)行一次
                Log.e("tag", "【FaceDetectorListener】類(lèi)的方法:【onFaceDetection】: " + "沒(méi)有臉部");
                facesView.removeRect();
            }
        }
    }
    /**
     * 因?yàn)閷?duì)攝像頭進(jìn)行了旋轉(zhuǎn),所以同時(shí)也旋轉(zhuǎn)畫(huà)板矩陣
     * 詳細(xì)請(qǐng)查看{@link android.hardware.Camera.Face#rect}
     * @return 旋轉(zhuǎn)后的矩陣
     */
    private Matrix updateFaceRect() {
        Matrix matrix = new Matrix();
        Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
        // Need mirror for front camera.
        boolean mirror = (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT);
        matrix.setScale(mirror ? -1 : 1, 1);
        // This is the value for android.hardware.Camera.setDisplayOrientation.
        // 剛才我們?cè)O(shè)置了camera的旋轉(zhuǎn)參數(shù),所以這里也要設(shè)置一下
        matrix.postRotate(90);
        // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
        // UI coordinates range from (0, 0) to (width, height).
        matrix.postScale(surfaceView.getWidth() / 2000f, surfaceView.getHeight() / 2000f);
        matrix.postTranslate(surfaceView.getWidth() / 2f, surfaceView.getHeight() / 2f);
        return matrix;
    }
  1. 現(xiàn)在插播一條消息:
    updateFaceRect()解釋?zhuān)涸谀槻繖z測(cè)時(shí),獲得的Rect并不是View的坐標(biāo),而是這樣的一個(gè)坐標(biāo)系,中心為原點(diǎn),所以我們需要轉(zhuǎn)換一下


    camera-area-coordinates.png
  2. 繪制臉部方框的View

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.view.View;
public class DrawFacesView extends View {

    private Matrix matrix;
    private Paint paint;
    private Camera.Face[] faces;
    private boolean isClear;

    public DrawFacesView(Context context) {
        this(context, null);
    }

    public DrawFacesView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DrawFacesView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setColor(Color.GREEN);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        faces = new Camera.Face[]{};
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.setMatrix(matrix);
//        canvas.drawRect(rect, paint);
        for (Camera.Face face : faces) {
            if (face == null) break;
            canvas.drawRect(face.rect, paint);
            if (face.leftEye != null)
                canvas.drawPoint(face.leftEye.x, face.leftEye.y, paint);
            if (face.rightEye != null)
                canvas.drawPoint(face.rightEye.x, face.rightEye.y, paint);
            if (face.mouth != null)
                canvas.drawPoint(face.mouth.x, face.mouth.y, paint);
            // 因?yàn)樾D(zhuǎn)了畫(huà)布矩陣,所以字體也跟著旋轉(zhuǎn)
//            canvas.drawText(String.valueOf("id:" + face.id + "\n置信度:" + face.score), face.rect.left, face.rect.bottom + 10, paint);
        }
        if (isClear) {
            canvas.drawColor(Color.WHITE, PorterDuff.Mode.CLEAR);
            isClear = false;
        }
    }
    /**
     * 繪制臉部方框
     *
     * @param matrix 旋轉(zhuǎn)畫(huà)布的矩陣
     * @param faces 臉部信息數(shù)組
     */
    public void updateFaces(Matrix matrix, Camera.Face[] faces) {
        this.matrix = matrix;
        this.faces = faces;
        invalidate();
    }

    /**
     * 清除已經(jīng)畫(huà)上去的框
     */
    public void removeRect() {
        isClear = true;
        invalidate();
    }
}

權(quán)限

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

效果:

  1. 傳入Bitmap識(shí)別
檢測(cè)Bitmap2.jpg
  1. 攝像頭實(shí)時(shí)檢測(cè)
動(dòng)態(tài)識(shí)別.png
動(dòng)態(tài)識(shí)別多人.png

參考

Google Android Camera API
Android 手把手帶你玩轉(zhuǎn)自定義相機(jī)

體驗(yàn)

體驗(yàn)APK
源碼Github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評(píng)論 25 709
  • 語(yǔ)音識(shí)別,語(yǔ)義理解一站式解決之智能照相機(jī)(人臉識(shí)別,olami) 轉(zhuǎn)載請(qǐng)注明CSDN博文地址:http://blo...
    ls0609閱讀 1,900評(píng)論 0 1
  • 1. Outline 本文主要從以下三個(gè)大的方面來(lái)說(shuō)明一下2D Graphic 繪圖的一些相關(guān)函數(shù)及應(yīng)用。 Col...
    lee_3do閱讀 3,344評(píng)論 0 11
  • 介紹自己負(fù)責(zé)的部分,如何實(shí)現(xiàn)的。 框架的搭建排查問(wèn)題以及結(jié)解決方式兼容性保證性能優(yōu)化上線(xiàn)之后模塊導(dǎo)致crash的比...
    黃海佳閱讀 13,384評(píng)論 6 350
  • 《安藤忠雄論建筑/建築を語(yǔ)る》 建筑工業(yè)出版社“事實(shí)上,我認(rèn)為安藤的作品還不能表現(xiàn)出他更高的理論境界?!?第一次接...
    日本建筑譯介閱讀 1,291評(píng)論 0 5

友情鏈接更多精彩內(nèi)容