1. 程式人生 > >android opencv實現人臉檢測 以及 年齡和性別識別

android opencv實現人臉檢測 以及 年齡和性別識別

支援技術分享,轉載或複製,請指出文章來源此部落格作者為Jack__0023

1、背景

因為面對的場景不同,所以我上次使用 android 提供的人臉檢測 已經不能滿足我的使用,因為長時間開啟,他會出現各種小狀況,而且接下來要做識別(不在這篇部落格講,等我整理好資料後寫出來測試沒問題再發部落格),所以投向opencv的懷抱。

bak:人臉檢測 不是 人臉識別,檢測的意思是 是不是人, 識別的意思是 你是誰。還有 年齡和性別識別 要實現的話,必須是opencv3.3版本或以上,因為只有這個版本或以上才支援CNN。

2、opencv 環境安裝(如果沒有的可以看一下)

2-1、準備工具
2-1-1、opencv 資源包,你可以去opencv官網下載,我使用的是3.4.0版本,因為我這個專案是19年初的時候做的,只是我沒有釋出出來,所以你有更好的選擇,你可以選擇4.0以上的版本,因為4.0的版本對於記憶體好像是有優化的。
這個是opencv官網資源下載地址
2-2、opencv資源引入android
2-2-1、當你下載完成的時候,你會的得到一個資源壓縮包,對資源包進行解壓
2-2-2、在android 進行引入module(module是解壓的檔案的 opencv-3.4.0-android-sdk\OpenCV-android-sdk\sdk\java 這個資料夾),一直next 就行,如圖

在這裡插入圖片描述

2-2-3、如果在引入的時候遇見這個異常

解決方案就是開啟引入的 opencv 模組的 AndroidManifest.xml和build.gradle,AndroidManifest中註釋掉這一行 程式碼

build.gradle 中的話,就修改成和你主app中build.gradle一樣的sdk版本配置就好了

在這裡插入圖片描述

然後 try again 就可以解決了,如果遇到其他問題可以私信我

3、程式碼區(人臉檢測和性別年齡識別分兩個部分來說)

3-1、人臉檢測 程式碼簡介
3-1-1、檢測模組選擇
我選用的是正臉和側臉檢測,當然你根據你的業務需求來選擇,opencv 很人體,眼睛之類等諸多選擇;所以我選擇的是opencv如圖的兩個訓練模組
選擇這兩個 haar 模組的原因:雖然haar不是最快的檢測的模組(例如相對來說lbp會快一點),但是是相對來說比較準確的模組,haar是Adaboost演算法,利用明暗變化來進行分類的,具體的haar,lbp,hog這三個的區分你們可以查詢資料,有很多大神在解釋,在我看來,圖裡面兩個模組是比較符合我的想法的。
選擇這兩個 haar 模組資源在解壓的壓縮包的 opencv-3.4.0-android-sdk\OpenCV-android-sdk\sdk\etc\haarcascades 路徑下,也有lbp的

3-1-2、檢測程式碼區域
備註:我這裡做了個攔截,變成了沒人的時候,一秒檢測兩次,有人的時候一秒檢測一次(在onCameraFrame方法實現了攔截)
原因:這樣可以避免不必要的計算,如果不做攔截,一秒可以檢測五次到二十幾次(具體看攝像頭還有初始化攝像頭的幀數配置),這種我嘗試這樣開幾天也沒有因為過熱導致app退出,具體的我都有做註釋,有什麼問題可以私聊我
package com.yxm.opencvface.activity;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.hardware.Camera;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.TextView;

import com.yxm.opencvface.R;

import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.core.Size;
import org.opencv.dnn.Net;
import org.opencv.objdetect.CascadeClassifier;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;

public class TestActivity extends Activity implements CameraBridgeViewBase.CvCameraViewListener2 {
    protected static int LIMIT_TIME = 3;//臨界的時間,超過3秒儲存圖片一次
    protected static Bitmap mFaceBitmap;//包含有人臉的影象
    private CameraBridgeViewBase openCvCameraView;

    private static final String TAG = "yaoxuminTest";

    private Handler mHandler;

    private CascadeClassifier mFrontalFaceClassifier = null; //正臉 級聯分類器
    private CascadeClassifier mProfileFaceClassifier = null; //側臉 級聯分類器

    private Rect[] mFrontalFacesArray;
    private Rect[] mProfileFacesArray;
    //保留正臉資料用於分析性別和年齡
    private Rect[] mTempFrontalFacesArray;

    private Mat mRgba; //影象容器
    private Mat mGray;
    private TextView mFrontalFaceNumber;
    private TextView mProfileFaceNumber;
    private TextView mCurrentNumber;
    private TextView mWaitTime;

    private int mFrontFaces = 0;
    private int mProfileFaces = 0;
    //記錄之前觀看的人的數量
    private int mRecodeFaceSize;
    //當前觀看的人的數量
    private int mCurrentFaceSize;

    //儲存最大的觀看人數峰值
    private int mMaxFaceSize;
    //記錄的時間,秒級別
    private long mRecodeTime;
    //記錄的時間,毫秒級別,空閒時間,用來計數,實現0.5秒一次檢查
    private long mRecodeFreeTime;
    //當前的時間,秒級別
    private long mCurrentTime = 0;
    //當前的時間,毫秒級別
    private long mMilliCurrentTime = 0;
    //觀看時間
    private int mWatchTheTime = 0;
    //離開的人數
    private int mLeftFaces = 0;

    //設定檢測區域
    private Size m55Size = new Size(55, 55);
    private Size m65Size = new Size(65, 65);
    private Size mDefault = new Size();

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_opencv);
        mHandler = new Handler();
        //初始化控制元件
        initComponent();
        //初始化攝像頭
        initCamera();
        //opencv資源的初始化在父類的 onResume 方法中
    }

    @Override
    public void onResume() {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.e(TAG, "OpenCV init error");
        }
        //初始化opencv資源
        initOpencv();
    }

 /**
     * @Description 初始化元件
     * @author 姚旭民
     * @date 2019/7/24 12:12
     */
    protected void initComponent() {
        openCvCameraView = findViewById(R.id.javaCameraView);
        mFrontalFaceNumber = findViewById(R.id.tv_frontal_face_number);
        mProfileFaceNumber = findViewById(R.id.tv_profile_face_number);
        mCurrentNumber = findViewById(R.id.tv_current_number);
        mWaitTime = findViewById(R.id.tv_wait_time);
    }

 /**
     * @Description 初始化攝像頭
     * @author 姚旭民
     * @date 2019/7/24 12:12
     */
    protected void initCamera() {
        int camerId = 0;
        Camera.CameraInfo info = new Camera.CameraInfo();
        int numCameras = Camera.getNumberOfCameras();
        for (int i = 0; i < numCameras; i++) {
            Camera.getCameraInfo(i, info);
            Log.v("yaoxumin", "在 CameraRenderer 類的 openCamera 方法 中執行了開啟攝像 Camera.open 方法,cameraId:" + i);
            camerId = i;
            break;
        }
        openCvCameraView.setCameraIndex(camerId); //攝像頭索引        -1/0:後置雙攝     1:前置
        openCvCameraView.enableFpsMeter(); //顯示FPS
        openCvCameraView.setCvCameraViewListener(this);//監聽
        openCvCameraView.setMaxFrameSize(640, 480);//設定幀大小
    }

    /**
     * @Description 初始化opencv資源
     * @author 姚旭民
     * @date 2019/7/24 12:12
     */
    protected void initOpencv() {
        initFrontalFace();
        initProfileFace();
        // 顯示
        openCvCameraView.enableView();
    }

    /**
     * @Description 初始化正臉分類器
     * @author 姚旭民
     * @date 2019/7/24 12:12
     */
    public void initFrontalFace() {
        try {
            //這個模型是我覺得來說相對不錯的
            InputStream is = getResources().openRawResource(R.raw.haarcascade_frontalface_alt); //OpenCV的人臉模型檔案: lbpcascade_frontalface_improved
            File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
            File mCascadeFile = new File(cascadeDir, "haarcascade_frontalface_alt.xml");
            FileOutputStream os = new FileOutputStream(mCascadeFile);
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            is.close();
            os.close();
            // 載入 正臉分類器
            mFrontalFaceClassifier = new CascadeClassifier(mCascadeFile.getAbsolutePath());
        } catch (Exception e) {
           Log.e(TAG, e.toString());
        }
    }

    /**
     * @Description 初始化側臉分類器
     * @author 姚旭民
     * @date 2019/7/24 12:12
     */
    public void initProfileFace() {
        try {
            //這個模型是我覺得來說相對不錯的
            InputStream is = getResources().openRawResource(R.raw.haarcascade_profileface); //OpenCV的人臉模型檔案: lbpcascade_frontalface_improved
            File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
            File mCascadeFile = new File(cascadeDir, "haarcascade_profileface.xml");
            FileOutputStream os = new FileOutputStream(mCascadeFile);
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            is.close();
            os.close();
            // 載入 側臉分類器
            mProfileFaceClassifier = new CascadeClassifier(mCascadeFile.getAbsolutePath());
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }

    @Override
    public void onCameraViewStarted(int width, int height) {
        mRgba = new Mat();
        mGray = new Mat();
    }

    @Override
    public void onCameraViewStopped() {
        mRgba.release();
        mGray.release();
    }

    /**
     * @Description 在這裡實現人臉檢測和性別年齡識別
     * @author 姚旭民
     * @date 2019/7/24 12:16
     */
    @Override
    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
        mRgba = inputFrame.rgba(); //RGBA
        mGray = inputFrame.gray(); //單通道灰度圖
        //解決  前置攝像頭旋轉顯示問題
        Core.flip(mRgba, mRgba, 1); //旋轉,變成映象

        mMilliCurrentTime = System.currentTimeMillis() / 100;//獲取當前時間毫秒級別
        mCurrentTime = mMilliCurrentTime / 10;//獲取當前時間,秒級別

        //每0.5秒檢查一次,如果你一直開測,不做這個過濾,那麼大概一秒可能是起碼5次計算檢測是否有人臉,這樣的計算對於機器有一定的壓力,
        //而且這些計算起始可以不用那麼多的,我個人覺得合適的是,
        //沒有檢測到人的時候,1秒2次檢測
        //檢測到人的時候,修改為1秒1次檢測,這樣你開一天甚至幾天也不會出現過熱導致app退出
        if (mRecodeFreeTime + 5 <= mMilliCurrentTime) {
            mRecodeFreeTime = mMilliCurrentTime;
            if (mRecodeTime == 0 || mCurrentFaceSize == 0 || mRecodeTime < mCurrentTime) {//識別到人之後,1秒做一次檢測
                mRecodeTime = mCurrentTime;//記錄當前時間
                //檢測並顯示
                MatOfRect frontalFaces = new MatOfRect();
                MatOfRect profileFaces = new MatOfRect();

                if (mFrontalFaceClassifier != null) {//這裡2個 Size 是用於檢測人臉的,越小,檢測距離越遠,1.1, 5, 2, m65Size, mDefault著四個引數可以提高檢測的準確率,5表示確認五次,具體百度 detectMultiScale 這個方法
                    mFrontalFaceClassifier.detectMultiScale(mGray, frontalFaces, 1.1, 5, 2, m65Size, mDefault);
                    mFrontalFacesArray = frontalFaces.toArray();
                    if (mFrontalFacesArray.length > 0) {
                        Log.i(TAG, "正臉人數為 : " + mFrontalFacesArray.length);
                    }
                    mCurrentFaceSize = mFrontFaces = mFrontalFacesArray.length;
                }

                if (mProfileFaceClassifier != null) {//這裡2個 Size 是用於檢測人臉的,越小,檢測距離越遠
                    mProfileFaceClassifier.detectMultiScale(mGray, profileFaces, 1.1, 6, 0, m55Size, mDefault);
                    mProfileFacesArray = profileFaces.toArray();
                    if (mProfileFacesArray.length > 0)
                        Log.i(TAG, "側臉人數為 : " + mProfileFacesArray.length);
                    mProfileFaces = mProfileFacesArray.length;
                }

                mProfileFacesArray = profileFaces.toArray();
                mCurrentFaceSize += mProfileFaces;

           /* if (mProfileFacesArray.length > 0){
                for (int i = 0; i < mProfileFacesArray.length; i++) {    //用框標記
                    Imgproc.rectangle(mRgba, mProfileFacesArray[i].tl(), mProfileFacesArray[i].br(), new Scalar(0, 255, 0, 255), 3);
                }
            }*/


                if (mCurrentFaceSize > 0) {//檢測到有人
                    mRecodeFaceSize = mCurrentFaceSize;//記錄有人看的時候的數量
                    if (mRecodeFaceSize > mMaxFaceSize)//記錄最大人數
                        mMaxFaceSize = mRecodeFaceSize;
                    mWatchTheTime++;//疊加觀看時間
                    if (mWatchTheTime == LIMIT_TIME) {//達到臨界值,進行圖片儲存
                        Log.v(TAG, "當前識別到的人臉數量為:" + mCurrentFaceSize);
                   /* Bitmap bitmap = Bitmap.createBitmap(mRgba.width(), mRgba.height(), Bitmap.Config.RGB_565);
                    Utils.matToBitmap(mRgba, bitmap);
                    mFaceBitmap = bitmap;*/
                    }
                } else {//沒有人觀看
                    if (mWatchTheTime > 0) {//之前是否有人觀看
                        if (mWatchTheTime < LIMIT_TIME) {//如果小於臨界值,進行丟資料包操作,暫時沒有介面
                            Log.v(TAG, "沒有超過臨界值,當前觀看人數為:" + mRecodeFaceSize + ",當前觀看的海報時間為:" + mWatchTheTime);
                        } else {//大於等於臨界值,進行圖片傳送
                            Log.v(TAG, "超過臨界值,當前觀看人數為:" + mRecodeFaceSize + ",當前觀看的海報時間為:" + mWatchTheTime);
                        }

                        Log.i(TAG, "mTempFrontalFacesArray : " + mTempFrontalFacesArray);
                    }
                }

                //顯示檢測到的人數
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mFrontalFaceNumber.setText(mFrontFaces + "");
                        mProfileFaceNumber.setText(mProfileFaces + "");
                        mCurrentNumber.setText(mCurrentFaceSize + "");
                        mWaitTime.setText(mWatchTheTime + "");
                    }
                }, 0);
            }
        }
        return mRgba;
    }
}

OK,人臉檢測這裡完畢,開始年齡和性別識別
3-2、年齡和性別識別區域
3-2-1、簡介
其實也是在人臉檢測上繼續做識別,但是要注意的是識別的時機選擇,當然這個識別我還沒做微調,所以誤差還是有點大,你可以百度資料繼續微調;
我這裡是測試,所以我在有人到沒檢測到人的時候,進行識別男女,我後面會調整一下,有需要的私聊我。
模組資源,這兩個模組是我找了好幾個東西翻出來的,你也可以翻牆去國外網站下載(如果不知道我後面提供下載地址),年齡和性別識別是基於CNN模型上實現的,你具體要問CNN是什麼,你可以找一下資料,也有國外的大神的論文,我只是粗略看了,就不賣弄了,幾個模型如下圖
模組資源下載我發給你吧。

3-2-2、程式碼區域
3-2-2-1、資源載入還是在 onCameraViewStarted 方法中進行,改成
 @Override
    public void onCameraViewStarted(int width, int height) {
        mRgba = new Mat();
        mGray = new Mat();

        //載入年齡模組
        String proto = getPath("deploy_age.prototxt");
        String weights = getPath("age_net.caffemodel");
        Log.i(TAG, "onCameraViewStarted| ageProto : " + proto + ",ageWeights : " + weights);
        mAgeNet = Dnn.readNetFromCaffe(proto, weights);

        //載入性別模組
        proto = getPath("deploy_gender.prototxt");
        weights = getPath("gender_net.caffemodel");
        Log.i(TAG, "onCameraViewStarted| genderProto : " + proto + ",genderWeights : " + weights);
        mGenderNet = Dnn.readNetFromCaffe(proto, weights);

        if (mAgeNet.empty()) {
            Log.i(TAG, "Network loading failed");
        } else {
            Log.i(TAG, "Network loading success");
        }
    }
完整人臉檢測和年齡性別識別的程式碼如下
package com.yxm.opencvface.activity;

import android.app.Activity;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.hardware.Camera;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.TextView;

import com.yxm.opencvface.R;

import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.dnn.Dnn;
import org.opencv.dnn.Net;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class TestActivity extends Activity implements CameraBridgeViewBase.CvCameraViewListener2 {
    protected static int LIMIT_TIME = 3;//臨界的時間,超過5秒儲存圖片一次
    protected static Bitmap mFaceBitmap;//包含有人臉的影象
    private CameraBridgeViewBase openCvCameraView;

    private static final String TAG = "yaoxuminTest";

    private Handler mHandler;

    private CascadeClassifier mFrontalFaceClassifier = null; //正臉 級聯分類器
    private CascadeClassifier mProfileFaceClassifier = null; //側臉 級聯分類器

    private Rect[] mFrontalFacesArray;
    private Rect[] mProfileFacesArray;

    private Mat mRgba; //影象容器
    private Mat mGray;
    private TextView mFrontalFaceNumber;
    private TextView mProfileFaceNumber;
    private TextView mCurrentNumber;
    private TextView mWaitTime;

    private int mFrontFaces = 0;
    private int mProfileFaces = 0;
    //記錄之前觀看的人的數量
    private int mRecodeFaceSize;
    //當前觀看的人的數量
    private int mCurrentFaceSize;

    //儲存最大的觀看人數峰值
    private int mMaxFaceSize;
    //記錄的時間,秒級別
    private long mRecodeTime;
    //記錄的時間,毫秒級別,空閒時間,用來計數,實現0.5秒一次檢查
    private long mRecodeFreeTime;
    //當前的時間,秒級別
    private long mCurrentTime = 0;
    //當前的時間,毫秒級別
    private long mMilliCurrentTime = 0;
    //觀看時間
    private int mWatchTheTime = 0;
    //離開的人數
    private int mLeftFaces = 0;

    //設定檢測區域
    private Size m55Size = new Size(55, 55);
    private Size m65Size = new Size(65, 65);
    private Size mDefault = new Size();

    //性別和年齡識別區域
    private Net mAgeNet;
    private static final String[] AGES = new String[]{"0-2", "4-6", "8-13", "15-20", "25-32", "38-43", "48-53", "60+"};

    private Net mGenderNet;
    private static final String[] GENDERS = new String[]{"男", "女"};
    //保留正臉資料用於分析性別和年齡
    private Rect[] mTempFrontalFacesArray;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_opencv);
        mHandler = new Handler();
        //初始化控制元件
        initComponent();
        //初始化攝像頭
        initCamera();
        //opencv資源的初始化在父類的 onResume 方法中
    }

    @Override
    public void onResume() {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.e(TAG, "OpenCV init error");
        }
        //初始化opencv資源
        initOpencv();
    }

 /**
     * @Description 初始化元件
     * @author 姚旭民
     * @date 2019/7/24 12:12
     */
    protected void initComponent() {
        openCvCameraView = findViewById(R.id.javaCameraView);
        mFrontalFaceNumber = findViewById(R.id.tv_frontal_face_number);
        mProfileFaceNumber = findViewById(R.id.tv_profile_face_number);
        mCurrentNumber = findViewById(R.id.tv_current_number);
        mWaitTime = findViewById(R.id.tv_wait_time);
    }

 /**
     * @Description 初始化攝像頭
     * @author 姚旭民
     * @date 2019/7/24 12:12
     */
    protected void initCamera() {
        int camerId = 0;
        Camera.CameraInfo info = new Camera.CameraInfo();
        int numCameras = Camera.getNumberOfCameras();
        for (int i = 0; i < numCameras; i++) {
            Camera.getCameraInfo(i, info);
            Log.v("yaoxumin", "在 CameraRenderer 類的 openCamera 方法 中執行了開啟攝像 Camera.open 方法,cameraId:" + i);
            camerId = i;
            break;
        }
        openCvCameraView.setCameraIndex(camerId); //攝像頭索引        -1/0:後置雙攝     1:前置
        openCvCameraView.enableFpsMeter(); //顯示FPS
        openCvCameraView.setCvCameraViewListener(this);//監聽
        openCvCameraView.setMaxFrameSize(640, 480);//設定幀大小
    }

    /**
     * @Description 初始化opencv資源
     * @author 姚旭民
     * @date 2019/7/24 12:12
     */
    protected void initOpencv() {
        initFrontalFace();
        initProfileFace();
        // 顯示
        openCvCameraView.enableView();
    }

    /**
     * @Description 初始化正臉分類器
     * @author 姚旭民
     * @date 2019/7/24 12:12
     */
    public void initFrontalFace() {
        try {
            //這個模型是我覺得來說相對不錯的
            InputStream is = getResources().openRawResource(R.raw.haarcascade_frontalface_alt); //OpenCV的人臉模型檔案: lbpcascade_frontalface_improved
            File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
            File mCascadeFile = new File(cascadeDir, "haarcascade_frontalface_alt.xml");
            FileOutputStream os = new FileOutputStream(mCascadeFile);
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            is.close();
            os.close();
            // 載入 正臉分類器
            mFrontalFaceClassifier = new CascadeClassifier(mCascadeFile.getAbsolutePath());
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }

    /**
     * @Description 初始化側臉分類器
     * @author 姚旭民
     * @date 2019/7/24 12:12
     */
    public void initProfileFace() {
        try {
            //這個模型是我覺得來說相對不錯的
            InputStream is = getResources().openRawResource(R.raw.haarcascade_profileface); //OpenCV的人臉模型檔案: lbpcascade_frontalface_improved
            File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
            File mCascadeFile = new File(cascadeDir, "haarcascade_profileface.xml");
            FileOutputStream os = new FileOutputStream(mCascadeFile);
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            is.close();
            os.close();
            // 載入 側臉分類器
            mProfileFaceClassifier = new CascadeClassifier(mCascadeFile.getAbsolutePath());
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }

    @Override
    public void onCameraViewStarted(int width, int height) {
        mRgba = new Mat();
        mGray = new Mat();

        //載入年齡模組
        String proto = getPath("deploy_age.prototxt");
        String weights = getPath("age_net.caffemodel");
        Log.i(TAG, "onCameraViewStarted| ageProto : " + proto + ",ageWeights : " + weights);
        mAgeNet = Dnn.readNetFromCaffe(proto, weights);

        //載入性別模組
        proto = getPath("deploy_gender.prototxt");
        weights = getPath("gender_net.caffemodel");
        Log.i(TAG, "onCameraViewStarted| genderProto : " + proto + ",genderWeights : " + weights);
        mGenderNet = Dnn.readNetFromCaffe(proto, weights);

        if (mAgeNet.empty()) {
            Log.i(TAG, "Network loading failed");
        } else {
            Log.i(TAG, "Network loading success");
        }
    }

    @Override
    public void onCameraViewStopped() {
        mRgba.release();
        mGray.release();
    }

    /**
     * @Description 在這裡實現人臉檢測和性別年齡識別
     * @author 姚旭民
     * @date 2019/7/24 12:16
     */
    @Override
    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
        mRgba = inputFrame.rgba(); //RGBA
        mGray = inputFrame.gray(); //單通道灰度圖
        //解決  前置攝像頭旋轉顯示問題
        Core.flip(mRgba, mRgba, 1); //旋轉,變成映象

        mMilliCurrentTime = System.currentTimeMillis() / 100;//獲取當前時間毫秒級別
        mCurrentTime = mMilliCurrentTime / 10;//獲取當前時間,秒級別

        //每0.5秒檢查一次,如果你一直開測,不做這個過濾,那麼大概一秒可能是起碼5次計算檢測是否有人臉,這樣的計算對於機器有一定的壓力,
        //而且這些計算起始可以不用那麼多的,我個人覺得合適的是,
        //沒有檢測到人的時候,1秒2次檢測
        //檢測到人的時候,修改為1秒1次檢測,這樣你開一天甚至幾天也不會出現過熱導致app退出
        if (mRecodeFreeTime + 5 <= mMilliCurrentTime) {
            mRecodeFreeTime = mMilliCurrentTime;
            if (mRecodeTime == 0 || mCurrentFaceSize == 0 || mRecodeTime < mCurrentTime) {//識別到人之後,1秒做一次檢測
                mRecodeTime = mCurrentTime;//記錄當前時間
                //檢測並顯示
                MatOfRect frontalFaces = new MatOfRect();
                MatOfRect profileFaces = new MatOfRect();

                if (mFrontalFaceClassifier != null) {//這裡2個 Size 是用於檢測人臉的,越小,檢測距離越遠,1.1, 5, 2, m65Size, mDefault著四個引數可以提高檢測的準確率,5表示確認五次,具體百度 detectMultiScale 這個方法
                    mFrontalFaceClassifier.detectMultiScale(mGray, frontalFaces, 1.1, 5, 2, m65Size, mDefault);
                    mFrontalFacesArray = frontalFaces.toArray();
                    if (mFrontalFacesArray.length > 0) {
                        Log.i(TAG, "正臉人數為 : " + mFrontalFacesArray.length);
                    }
                    mCurrentFaceSize = mFrontFaces = mFrontalFacesArray.length;
                }

                if (mProfileFaceClassifier != null) {//這裡2個 110 是用於檢測人臉的,越小,檢測距離越遠
                    mProfileFaceClassifier.detectMultiScale(mGray, profileFaces, 1.1, 6, 0, m55Size, mDefault);
                    mProfileFacesArray = profileFaces.toArray();
                    if (mProfileFacesArray.length > 0)
                        Log.i(TAG, "側臉人數為 : " + mProfileFacesArray.length);
                    mProfileFaces = mProfileFacesArray.length;
                }

                mProfileFacesArray = profileFaces.toArray();
                mCurrentFaceSize += mProfileFaces;

           /* if (mProfileFacesArray.length > 0){
                for (int i = 0; i < mProfileFacesArray.length; i++) {    //用框標記
                    Imgproc.rectangle(mRgba, mProfileFacesArray[i].tl(), mProfileFacesArray[i].br(), new Scalar(0, 255, 0, 255), 3);
                }
            }*/


                if (mCurrentFaceSize > 0) {//檢測到有人
                    mRecodeFaceSize = mCurrentFaceSize;//記錄有人看的時候的數量
                    if (mRecodeFaceSize > mMaxFaceSize)//記錄最大人數
                        mMaxFaceSize = mRecodeFaceSize;
                    mWatchTheTime++;//疊加觀看時間
                    //性別識別的需要
                    mTempFrontalFacesArray = mFrontalFacesArray;
                    if (mWatchTheTime == LIMIT_TIME) {//達到臨界值,進行圖片儲存
                        Log.v(TAG, "當前識別到的人臉數量為:" + mCurrentFaceSize);
                   /* Bitmap bitmap = Bitmap.createBitmap(mRgba.width(), mRgba.height(), Bitmap.Config.RGB_565);
                    Utils.matToBitmap(mRgba, bitmap);
                    mFaceBitmap = bitmap;*/
                    }
                } else {//沒有人觀看
                    if (mWatchTheTime > 0) {//之前是否有人觀看
                        if (mWatchTheTime < LIMIT_TIME) {//如果小於臨界值,進行丟資料包操作,暫時沒有介面
                            Log.v(TAG, "沒有超過臨界值,當前觀看人數為:" + mRecodeFaceSize + ",當前觀看的海報時間為:" + mWatchTheTime);
                        } else {//大於等於臨界值,進行圖片傳送
                            Log.v(TAG, "超過臨界值,當前觀看人數為:" + mRecodeFaceSize + ",當前觀看的海報時間為:" + mWatchTheTime);
                        }

                        //資料重置
                        mWatchTheTime = mMaxFaceSize = mRecodeFaceSize = 0;

                        Log.i(TAG, "mTempFrontalFacesArray : " + mTempFrontalFacesArray);
                        if (mTempFrontalFacesArray != null) {
                            String age;
                            Log.i(TAG, "mTempFrontalFacesArray.length : " + mTempFrontalFacesArray.length);
                            for (int i = 0; i < mTempFrontalFacesArray.length; i++) {
                                Log.i(TAG, "年齡為 : " + analyseAge(mRgba, mTempFrontalFacesArray[i]));
                                Log.i(TAG, "性別為 : " + analyseGender(mRgba, mTempFrontalFacesArray[i]));
                            }
                            mTempFrontalFacesArray = null;
                        }
                    }
                }


                //顯示檢測到的人數
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mFrontalFaceNumber.setText(mFrontFaces + "");
                        mProfileFaceNumber.setText(mProfileFaces + "");
                        mCurrentNumber.setText(mCurrentFaceSize + "");
                        mWaitTime.setText(mWatchTheTime + "");
                    }
                }, 0);
            }
        }
        return mRgba;
    }

    /**
     * @param mRgba 圖片資源
     * @param face  人臉資源
     * @return 返回年齡區間
     * @Description 分析年齡
     * @author 姚旭民
     * @date 2019/7/24 12:54
     */
    private String analyseAge(Mat mRgba, Rect face) {
        try {
            Mat capturedFace = new Mat(mRgba, face);
            //Resizing pictures to resolution of Caffe model
            Imgproc.resize(capturedFace, capturedFace, new Size(227, 227));
            //Converting RGBA to BGR
            Imgproc.cvtColor(capturedFace, capturedFace, Imgproc.COLOR_RGBA2BGR);

            //Forwarding picture through Dnn
            Mat inputBlob = Dnn.blobFromImage(capturedFace, 1.0f, new Size(227, 227),
                    new Scalar(78.4263377603, 87.7689143744, 114.895847746), false, false);
            mAgeNet.setInput(inputBlob, "data");
            Mat probs = mAgeNet.forward("prob").reshape(1, 1);
            Core.MinMaxLocResult mm = Core.minMaxLoc(probs); //Getting largest softmax output

            double result = mm.maxLoc.x; //Result of age recognition prediction
            Log.i(TAG, "Result is: " + result);
            return AGES[(int) result];
        } catch (Exception e) {
            Log.e(TAG, "Error processing age", e);
        }
        return null;
    }

    /**
     * @param mRgba 圖片資源
     * @param face  人臉資源
     * @return 返回分析結果性別
     * @Description 分析性別
     * @author 姚旭民
     * @date 2019/7/24 13:04
     */
    private String analyseGender(Mat mRgba, Rect face) {
        try {
            Mat capturedFace = new Mat(mRgba, face);
            //Resizing pictures to resolution of Caffe model
            Imgproc.resize(capturedFace, capturedFace, new Size(227, 227));
            //Converting RGBA to BGR
            Imgproc.cvtColor(capturedFace, capturedFace, Imgproc.COLOR_RGBA2BGR);

            //Forwarding picture through Dnn
            Mat inputBlob = Dnn.blobFromImage(capturedFace, 1.0f, new Size(227, 227),
                    new Scalar(78.4263377603, 87.7689143744, 114.895847746), false, false);
            mGenderNet.setInput(inputBlob, "data");
            Mat probs = mGenderNet.forward("prob").reshape(1, 1);
            Core.MinMaxLocResult mm = Core.minMaxLoc(probs); //Getting largest softmax output

            double result = mm.maxLoc.x; //Result of gender recognition prediction. 1 = FEMALE, 0 = MALE
            Log.i(TAG, "Result is: " + result);
            return GENDERS[(int) result];
        } catch (Exception e) {
            Log.e(TAG, "Error processing gender", e);
        }
        return null;
    }

    /**
     * @param file 檔名稱
     * @return 返回檔案路徑
     * @Description 獲取檔案的路徑
     * @author 姚旭民
     * @date 2019/7/24 13:05
     */
    private String getPath(String file) {
        AssetManager assetManager = getApplicationContext().getAssets();
        BufferedInputStream inputStream;

        try {
            //Reading data from app/src/main/assets
            inputStream = new BufferedInputStream(assetManager.open(file));
            byte[] data = new byte[inputStream.available()];
            inputStream.read(data);
            inputStream.close();

            File outputFile = new File(getApplicationContext().getFilesDir(), file);
            FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
            fileOutputStream.write(data);
            fileOutputStream.close();
            return outputFile.getAbsolutePath();
        } catch (IOException ex) {
            Log.e(TAG, ex.toString());
        }
        return "";
    }
}

識別結果如下圖

如果覺得有問題可以私信我一起討論一下,我也希望瞭