關於人臉檢測和識別,應用的範圍是非常廣的,其實之前的《NDK開發前奏 - 實現支付寶人臉識別功能》 也有提到,只是那時並未具體的去分析算法和實現原理,這裏筆者打算一步一步來分析和實現人臉識別,首先我們得要明確人臉檢測和人臉識別是兩個不同的概念,人臉檢測是檢測有人臉,人臉識別是匹配你是你,他們所採用的算法也是不一樣的,這篇文章是基於人臉檢測來實現人臉識別。我們先來看下已經實現了的人臉檢測效果:
人臉馬賽克大家可以忽略,並不是這裏要關注的內容,是因爲長得太帥了怕大家嫉妒。馬賽克效果實現大家可以參考《圖形圖像處理 - 手寫 QQ 說說圖片處理效果》。首先我們不妨來思考一下,要實現像支付寶的人臉識別和員工刷臉簽到等等,這樣的一些應用功能,我們需要用到哪些知識?需要經過哪些步驟呢?其實這裏分爲兩步,第一步是樣本數據採集,第二步是檢測和匹配。不過我們得先來看幾個概念:均值,標準差,協方差矩陣,特徵值,特徵向量,PCA降維。
1. 均值,標準差,協方差矩陣
這幾個都是概率論中的概念,我們隨便舉一個例子來算下即可,假設我的 Mat 數據如下:
Mat src = (Mat_<int>(3, 3) << 50, 50, 50, 60, 60, 60, 70, 70, 70);
均值:[60]
標準差:[8.164965809277252]
協方差矩陣:[200, 200, 200; 200, 200, 200; 200, 200, 200]
2. 特徵值,特徵向量
關於特徵值與特徵向量這是線性代數的概念,還是老套路拿個例子過來算下,能算出來就可以了,同時大家也可以參考這篇文章:
https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors
3. PCA降維
人臉識別肯定需要採集人臉樣本,也就是相機採集過來的 Mat 數據,那麼大量的數據怎麼處理呢?這裏就需要 PCA 降維了:
- 把原始數據中每個樣本用一個向量表示,然後把所有樣本組合起來構成一個矩陣。當然了,爲了避免樣本的單位的影響,樣本集需要標準化。
- 求該矩陣的協方差矩陣
- 求步驟2中得到的協方差矩陣的特徵值和特徵向量。
- 將求出的特徵向量按照特徵值的大小進行組合形成一個映射矩陣,並根據指定的 PCA 保留的特徵個數取出映射矩陣的前n行或者前n列作爲最終的映射矩陣。
- 用步驟4的映射矩陣對原始數據進行映射,達到數據降維的目的。
4. 樣本訓練
接下來就是代碼層面的東西了,代碼是很簡單的就那麼幾句話,但關鍵其實還是在於理解裏面的原理:
FaceDetection_trainingPattern(JNIEnv *env, jobject instance) {
// 訓練樣本,這一步是在數據採集做的
// train it
vector<Mat> faces;
vector<int> 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<BasicFaceRecognizer> model = EigenFaceRecognizer::create();
// 採集了八張,同一個人 label 一樣
model->train(faces, labels);
// 訓練樣本是 xml ,本地
model->save("/storage/emulated/0/face_darren_pattern.xml");// 存的是處理的特徵數據
LOGE("樣本訓練成功");
}
4. 匹配識別
FaceDetection_faceDetection(JNIEnv *env, jobject instance,
jlong nativeObj) {
Mat *src = reinterpret_cast<Mat *>(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<Rect> 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));
}
視頻地址:https://pan.baidu.com/s/1lF92ev7_SqVNRNMih9eHkQ
視頻密碼:jc4k