OpenCV人脸校验,人脸识别
年初来深圳的时候,好久都没有体验坐高铁我,看到了以前的人工检票换成了机器检票,让我对人脸识别的技术产生了极大的好奇心,于是就想着去了解人脸识别的技术。高铁站的智能检票就是对比你的人与身份证是否是同一个人(从技术上来看是属于人脸校验的范畴)在网上找了很久的高铁检票的实现机制 ,它的大概流程可以分为这样的,
1)证件读取 (拉取公安系统 个人信息 姓名 身份证号 人脸图像)
2)车票验证 (是否购票 购票信息 站点)
3)人脸检测 (摄像头,检测人脸位置,抓取图像)
4)特征提取 (抓取摄像头中的人脸 ,提取特征 特征值 特征向量)
5)基于人 证 的对比识别 (公安拉取图像信息与摄像头采取图像对比)
人脸校验(人脸校验是要给你两张脸比较是不是同一个人)人脸识别(人脸识别是给你一张脸和一个人脸库比较这张脸是库里的谁)
接下来我们在Android中实现,人脸识别功能。具体用到了OpenCV 和它的扩展模块的so库编译,这里不具体去讲了,以后有时间也会陆续去写一些关于Linux内核的文章,里面就会包含有so库的编译知识,人脸识别要用到这个库https://github.com/opencv/opencv_contrib,至于摄像头采取数据转化为Mat这里我采用了OpenCV中的代码做了一些修改。
接下来我们看看具体是使用OpenCV Android Sdk 代码
public class MainActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener, View.OnClickListener {
JavaCameraView cameraView;
//主要的逻辑写在这个方法里面
......
@Override
public Mat onCameraFrame(Mat inputFrame) {
//输入的没
if (this.getResources().getConfiguration().orientation
== ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
&& cameraView.getCameraIndex() == Camera.CameraInfo.CAMERA_FACING_FRONT) {
Core.rotate(inputFrame, inputFrame, Core.ROTATE_90_COUNTERCLOCKWISE);
BitmapUtils.remap(inputFrame, inputFrame);
} else if (this.getResources().getConfiguration().orientation
== ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
&& cameraView.getCameraIndex() == Camera.CameraInfo.CAMERA_FACING_BACK) {
Core.rotate(inputFrame, inputFrame, Core.ROTATE_90_CLOCKWISE); //90
}
if(isStart){
if(isDetection) {
//人脸识别 脸部数据只加载一次即可
// BitmapUtils.loadPattern("/storage/emulated/0/face_pattern"+faceId+".xml");
BitmapUtils.faceDetection(inputFrame);
}else {
//数据采集
BitmapUtils.collection(inputFrame,faceId);
}
}
return inputFrame;
}
......
这里我们要解决的两个问题 一个是摄像机采集的数据不能够全屏,二个是前置摄像头画面左右互换了。至于全屏的问题留给大家自己去修改OpenCV 安卓sdk 的源码,左右调换画面怎么去调整呢,有两种方法:方法一:修改OpenCV源代码重新编译so库;方法二:自己写方法去解决。我是利用OpenCV 的重映射去把画面镜像过来的
BitmapUtils.remap(inputFrame, inputFrame);
/**
* 镜像 调整画面
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_nRemap(JNIEnv *env, jclass type, jlong nativeObj,
jlong dstnativeObj) {
Mat *src = reinterpret_cast<Mat *>(nativeObj);
Mat *dst = reinterpret_cast<Mat *>(dstnativeObj);
Mat map_x(src->size(), CV_32FC1);
Mat map_y(src->size(), CV_32FC1);
for (int row = 0; row < src->rows; ++row) {
for (int col = 0; col < src->cols; ++col) {
map_x.at<float>(row, col) = src->cols - col;
map_y.at<float>(row, col) = row;
}
}
remap(*src, *dst, map_x, map_y, INTER_AREA);
}
准备工作做好,并且相机能够正常的显示采集数据。要实现人脸的识别就来先了解一下人脸的检测。从摄像头中采取帧数据传入ndk 交个OpenCV做处理,主要是级联分类器的使用,
加载级联分类器:
@Override
protected void onCreate(Bundle savedInstanceState) {
try {
// load cascade file from application resources
InputStream is = getResources().openRawResource(R.raw.lbpcascade_frontalface);
File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface.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();
BitmapUtils.load(mCascadeFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
CascadeClassifier cascadeClassifier;
/**
* 加载 lbpcascade_frontalface.xml
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_load(JNIEnv *env, jclass type, jstring absolutePath_) {
const char *filePath = env->GetStringUTFChars(absolutePath_, 0);
cascadeClassifier.load(filePath);
env->ReleaseStringUTFChars(absolutePath_, filePath);
}
人脸检测,框出人脸
/**
* 框出人脸区域
*/
int frame_face(Mat *src, Rect &rect, bool isDrawRect) {
Mat gary;
//将输入的帧图像转为灰度图 ,(转灰度)减小算法复杂度提升处理速度
cvtColor(*src, gary, COLOR_BGRA2GRAY);
vector<Rect> faces;
//检测人脸
cascadeClassifier.detectMultiScale(gary, faces, 1.1, 3, 0, Size(src->cols / 3, src->rows / 3),
src->size());
if (faces.size() <= 0) {
return 0;
}
Rect face = faces[0];
if (isDrawRect) {
//将人脸区域画上矩形
rectangle(*src, face, Scalar(255, 0, 0, 255), 5, LINE_AA);
}
rect = face;
return 1;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_nFaceDetection(JNIEnv *env, jclass type,
jlong nativeMat) {
Mat *src = reinterpret_cast<Mat *>(nativeMat);
Rect face_rect;
//检测并且框出人脸
frame_face(src, face_rect, true)
}
cascadeClassifier.detectMultiScale(gary, faces, 1.1, 3, 0, Size(src->cols / 3, src->rows / 3),src->size());重点关注一下检测代码。gary输入图像,faces输出图像,第三个参数缩放比,内部会检多次检测是否是人脸,他会把图像缩放去多次比较是否是人脸,提高检测的准确度,第四个参数是检测的次数 ,这里是3次确认人脸,就确定他是人脸,参数五指定 Haar,后面指定最小脸和最大脸。具体效果
人脸检测.png
数据采集,样本的训练:
对于人脸的数据我们保存的并不是人脸的图片,而是人脸的特征。有特征脸方法 LDA线性判别分析等。特征脸方法大概流程是 读取训练数据,计算平均脸,计算协方差矩阵,计算特征值,特征矩阵,PCA降维,子控件模型。PCA降维有效的减少了数据量。准备ORL人脸数据库样本用来训练 人脸数据.png里面包含了40个人的人脸,接下就利用 EigenFaceRecognizer 对人脸数据的训练及其识别
Ptr<BasicFaceRecognizer> model = EigenFaceRecognizer::create();
vector<Mat> faces_c;
vector<int> labels;
//添加人到集合中 每个人的数据对应一个label(标签) 识别的时候返回改标签
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_initCollection(JNIEnv *env, jclass type) {
for (int i = 1; i <= 10; ++i) {
for (int j = 1; j <= 5; ++j) {
Mat face = imread(format("/storage/emulated/0/orl_faces/s%d/%d.pgm", i, j), 0);
if (!face.data) {
LOGE("不能读取文件");
continue;
}
// 确保大小一致
resize(face, face, Size(128, 128));
faces_c.push_back(face);
labels.push_back(i);
}
}
}
/**
* 从摄像机采集 脸部数据
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_nCollection(JNIEnv *env, jclass type,
jlong nativeMat, jint labe) {
Mat *src = reinterpret_cast<Mat *>(nativeMat);
m_label = labe;
Rect rect;
if (frame_face(src, rect, false)) {
//克隆出脸部数据 其余的丢弃
Mat face = (*src)(rect).clone();
cvtColor(face,face,COLOR_BGRA2GRAY);
//保证图片大小
resize(face, face, Size(128, 128));
faces_c.push_back(face);
labels.push_back(labe);
}
}
/**
* 采集完毕 训练模板
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_collectionComplete(JNIEnv *env, jclass type) {
LOGE("%d---%d", faces_c.size(), labels.size());
model->train(faces_c, labels);
char file_name[100];
sprintf(file_name, "/storage/emulated/0/face_pattern%d.xml", m_label);
LOGE("%s", file_name);
model->save(file_name);
labels.clear();
faces_c.clear();
LOGE("人脸采集完毕");
}
上面的数据采集,样本的训练包括下面识别过程最好放在子线程,这个步骤比较耗时。
样本数据的加载,人脸的识别:
在识别的时候,要去加载我们刚才采集的数据,并将摄像机的帧传入ndk层去时时识别
//人脸识别
BitmapUtils.loadPattern("/storage/emulated/0/face_pattern"+faceId+".xml");
BitmapUtils.faceDetection(inputFrame);
/**
* 加载模板数据
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_loadPattern(JNIEnv *env, jclass type,
jstring pattern_) {
const char *pattern = env->GetStringUTFChars(pattern_, 0);
FileStorage fs(pattern, FileStorage::READ);
const FileNode fn = fs.getFirstTopLevelNode();
model->read(fn);
env->ReleaseStringUTFChars(pattern_, pattern);
}
/**
* 人脸识别
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_facedetection_BitmapUtils_nFaceDetection(JNIEnv *env, jclass type,
jlong nativeMat) {
Mat *src = reinterpret_cast<Mat *>(nativeMat);
Rect face_rect;
if (frame_face(src, face_rect, false)) {
Mat face = (*src)(face_rect).clone();
resize(*src, face, Size(128, 128));
cvtColor(face, face, COLOR_BGRA2GRAY);
//返回的 label 对应你存储特征时的label 标签 一个label标签就对应一个人
int label = model->predict(face);
LOGE("label = %d",label);
}
}
在网上拿angelababy 图片,利用摄像头采集到她的样本数据,进行训练,下面是识别的效果。
识别.gif
angelababy几乎都能够识别出来,换到其他人就不认识了(没有采集他们的样本数据所以不认识),但是有时会把其他人识别成angelababy ,从识别的效果来看识别率不是太准高,这个跟训练样本的数据量有关,程序的效果还是能够达到人脸的识别的。