支持技术分享,转载或复制,请指出文章来源此博客作者为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 "";
}
}