android opencv實現人臉檢測 以及 年齡和性別識別
阿新 • • 發佈:2019-08-11
支援技術分享,轉載或複製,請指出文章來源此部落格作者為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 "";
}
}