关于人脸检测和识别,应用的范围是非常广的,其实之前的 也有提到,只是那时并未具体的去分析算法和实现原理,这里笔者打算一步一步来分析和实现人脸识别,首先我们得要明确人脸检测和人脸识别是两个不同的概念,人脸检测是检测有人脸,人脸识别是匹配你是你,他们所采用的算法也是不一样的,这篇文章是基于人脸检测来实现人脸识别。我们先来看下已经实现了的人脸检测效果:
人脸马赛克大家可以忽略,并不是这里要关注的内容,是因为长得太帅了怕大家嫉妒。马赛克效果实现大家可以参考。首先我们不妨来思考一下,要实现像支付宝的人脸识别和员工刷脸签到等等,这样的一些应用功能,我们需要用到哪些知识?需要经过哪些步骤呢?其实这里分为两步,第一步是样本数据采集,第二步是检测和匹配。不过我们得先来看几个概念:均值,标准差,协方差矩阵,特征值,特征向量,PCA降维。
1. 均值,标准差,协方差矩阵
这几个都是概率论中的概念,我们随便举一个例子来算下即可,假设我的 Mat 数据如下:
Mat src = (Mat_ (3, 3) << 50, 50, 50, 60, 60, 60, 70, 70, 70);复制代码
均值:[60]
标准差:[8.164965809277252]
协方差矩阵:[200, 200, 200; 200, 200, 200; 200, 200, 200]
2. 特征值,特征向量
关于特征值与特征向量这是线性代数的概念,还是老套路拿个例子过来算下,能算出来就可以了,同时大家也可以参考这篇文章:
3. PCA降维
人脸识别肯定需要采集人脸样本,也就是相机采集过来的 Mat 数据,那么大量的数据怎么处理呢?这里就需要 PCA 降维了:
- 把原始数据中每个样本用一个向量表示,然后把所有样本组合起来构成一个矩阵。当然了,为了避免样本的单位的影响,样本集需要标准化。
- 求该矩阵的协方差矩阵
- 求步骤2中得到的协方差矩阵的特征值和特征向量。
- 将求出的特征向量按照特征值的大小进行组合形成一个映射矩阵,并根据指定的 PCA 保留的特征个数取出映射矩阵的前n行或者前n列作为最终的映射矩阵。
- 用步骤4的映射矩阵对原始数据进行映射,达到数据降维的目的。
4. 样本训练
接下来就是代码层面的东西了,代码是很简单的就那么几句话,但关键其实还是在于理解里面的原理:
FaceDetection_trainingPattern(JNIEnv *env, jobject instance) { // 训练样本,这一步是在数据采集做的 // train it vectorfaces; vector labels; // 样本比较少 for (int i = 1; i <= 5; ++i) { for (int j = 1; j <= 5; ++j) { Mat face = imread(format("/storage/emulated/0/s%d/%d.pgm", i, j), 0); if (face.empty()) { LOGE("face mat is empty"); continue; } // 确保大小一致 resize(face, face, Size(128, 128)); faces.push_back(face); labels.push_back(i); } } for (int i = 1; i <= 8; ++i) { Mat face = imread(format("/storage/emulated/0/face_%d.png", i), 0); if (face.empty()) { LOGE("face mat is empty"); continue; } resize(face, face, Size(128, 128)); faces.push_back(face); labels.push_back(11); } // 训练方法 Ptr model = EigenFaceRecognizer::create(); // 采集了八张,同一个人 label 一样 model->train(faces, labels); // 训练样本是 xml ,本地 model->save("/storage/emulated/0/face_darren_pattern.xml");// 存的是处理的特征数据 LOGE("樣本訓練成功");}复制代码
5. 匹配识别
FaceDetection_faceDetection(JNIEnv *env, jobject instance, jlong nativeObj) { Mat *src = reinterpret_cast(nativeObj); int width = src->rows; int height = src->cols; Mat grayMat; // 2. 转成灰度图,提升运算速度,灰度图所对应的 CV_8UC1 单颜色通道,信息量少 0-255 1u cvtColor(*src, grayMat, COLOR_BGRA2GRAY); // 4. 检测人脸,这是个大问题 // 参数 1.1 会采取上采样和降采样 ,缩放比例 // 参数 3 检测多少次 // 参数 Size(width / 2, height / 2) 最小脸的大小 std::vector faces; cascadeClassifier.detectMultiScale(grayMat, faces, 1.1, 3, 0, Size(width / 2, height / 2)); if (faces.size() != 1) { mosaicFace(*src); return; } // 把脸框出来 Rect faceRect = faces[0]; rectangle(*src, faceRect, Scalar(255, 0, 0, 255), 4, LINE_AA); // 不断检测,录入 10 张,张张嘴巴,眨眨眼睛 ,保证准确率 // 还需要注意一点,确保人脸大小一致,reSize(128,128) ,确保收集到的人脸眼睛尽量在一条线上 // 与服务端进行比对,是不是我 // 用一个计数器,这里我们做及时的 Mat face = (*src)(faceRect).clone(); resize(face, face, Size(128, 128)); cvtColor(face, face, COLOR_BGRA2GRAY); // 直方均衡,harr 检测人脸 int label = model->predict(face); // 训练的时候存的是 11 if (label == 11) { // 识别到了自己 LOGE("识别到了自己"); putText(*src, "Darren", Point(faceRect.x + 20, faceRect.y - 20), HersheyFonts::FONT_HERSHEY_COMPLEX, 1, Scalar(255, 0, 0, 255),2,LINE_AA); } else { // 不是自己 LOGE("不是自己"); putText(*src, "UnKnow", Point(faceRect.x + 20, faceRect.y - 20), HersheyFonts::FONT_HERSHEY_COMPLEX, 1, Scalar(255, 0, 0, 255),2,LINE_AA); } // 速度, 准确率, 人脸尽量正常 mosaicFace((*src)(faceRect));}复制代码
视频地址:
视频密码:jc4k