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

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

人臉識(shí)別說明

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

API

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

  1. FaceDetector:通過傳遞Bitmap檢測(cè)圖中的人臉璧瞬,同時(shí)返回眼睛部位,識(shí)別人數(shù)渐夸,未詳細(xì)測(cè)試嗤锉;
  2. Camera.FaceDetectionListener:通過打開攝像頭(不是相機(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中涡相,過程耗時(shí)
            int findFaceCount = faceDet.findFaces(bitmap, faceArray);
            // 獲取傳回的臉部數(shù)組中的第一張臉的信息
            FaceDetector.Face face1 = faceArray[0];
            // 獲取雙眼的中心點(diǎn),用一個(gè)PointF來接收其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;
}

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

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

通過調(diào)用攝像頭,提供一個(gè)實(shí)時(shí)檢測(cè)臉部的方案
流程:打開攝像頭→給Camera類傳遞回調(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()中打開相機(jī)遍尺,設(shè)置監(jiān)聽
    /**
     * 把攝像頭的圖像顯示到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)聽
                        mCamera.setFaceDetectionListener(new FaceDetectorListener());
                        mCamera.setPreviewDisplay(holder);
                        // 開始臉部檢測(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();
        // /**/注釋的地方非必須截酷,參考來源  [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說明不支持臉部檢測(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】類的方法:【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】類的方法:【onFaceDetection】: ");
                Matrix matrix = updateFaceRect();
                facesView.updateFaces(matrix, faces);
            } else {
                // 只會(huì)執(zhí)行一次
                Log.e("tag", "【FaceDetectorListener】類的方法:【onFaceDetection】: " + "沒有臉部");
                facesView.removeRect();
            }
        }
    }
    /**
     * 因?yàn)閷?duì)攝像頭進(jìn)行了旋轉(zhuǎn),所以同時(shí)也旋轉(zhuǎn)畫板矩陣
     * 詳細(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()解釋:在臉部檢測(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)了畫布矩陣,所以字體也跟著旋轉(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)畫布的矩陣
     * @param faces 臉部信息數(shù)組
     */
    public void updateFaces(Matrix matrix, Camera.Face[] faces) {
        this.matrix = matrix;
        this.faces = faces;
        invalidate();
    }

    /**
     * 清除已經(jīng)畫上去的框
     */
    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)系作者
  • 序言:七十年代末摆出,一起剝皮案震驚了整個(gè)濱河市朗徊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌偎漫,老刑警劉巖爷恳,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異象踊,居然都是意外死亡温亲,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門杯矩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栈虚,“玉大人,你說我怎么就攤上這事史隆』晡瘢” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵泌射,是天一觀的道長头镊。 經(jīng)常有香客問我,道長魄幕,這世上最難降的妖魔是什么相艇? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮纯陨,結(jié)果婚禮上坛芽,老公的妹妹穿的比我還像新娘留储。我一直安慰自己,他們只是感情好咙轩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布获讳。 她就那樣靜靜地躺著,像睡著了一般活喊。 火紅的嫁衣襯著肌膚如雪丐膝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天钾菊,我揣著相機(jī)與錄音帅矗,去河邊找鬼。 笑死煞烫,一個(gè)胖子當(dāng)著我的面吹牛浑此,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滞详,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼凛俱,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了料饥?” 一聲冷哼從身側(cè)響起蒲犬,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岸啡,沒想到半個(gè)月后原叮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凰狞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年篇裁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沛慢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赡若。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖团甲,靈堂內(nèi)的尸體忽然破棺而出逾冬,到底是詐尸還是另有隱情,我是刑警寧澤躺苦,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布身腻,位于F島的核電站,受9級(jí)特大地震影響匹厘,放射性物質(zhì)發(fā)生泄漏嘀趟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一愈诚、第九天 我趴在偏房一處隱蔽的房頂上張望她按。 院中可真熱鬧牛隅,春花似錦、人聲如沸酌泰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陵刹。三九已至默伍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間衰琐,已是汗流浹背也糊。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碘耳,地道東北人显设。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像辛辨,于是被迫代替她去往敵國和親捕捂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

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