做人臉識別的步驟:數據收集和預處理、訓練模型、人臉識別三個部分
最基本的第一步:認識並理解人臉識別,知道人臉識別需要的函數:
在OpenCV中主要使用了兩種特徵(即兩種方法)進行人臉檢測,Haar特徵和LBP特徵。使用已經訓練好的XML格式的分類器進行人臉檢測。我的電腦在OpenCV的安裝目錄下的install文件夾裏的etc文件夾(opencv\install\etc\haarcascades )。
文件夾的名字“haarcascades”、“hogcascades”和“lbpcascades”分別表示通過“haar”、“hog”和“lbp”三種不同的特徵而訓練出的分類器:"haar"特徵主要用於人臉檢測,“hog”特徵主要用於行人檢測,“lbp”特徵主要用於人臉識別,“eye”特徵主要用於眼睛的檢測識別。
實現人臉檢測主要依賴於detectMultiScale()函數,下面是函數的原型及參數的含義:
CV_WRAP virtual void detectMultiScale( const Mat& image,
CV_OUT vector<Rect>& objects,
double scaleFactor=1.1,
int minNeighbors=3, int flags=0,
Size minSize=Size(),
Size maxSize=Size() );
各參數含義:
const Mat& image: 需要被檢測的圖像(灰度圖)
vector
double scaleFactor: 每次圖片縮放的比例
int minNeighbors: 每一個人臉至少要檢測到多少次纔算是真的人臉
int flags: 決定是縮放分類器來檢測,還是縮放圖像
Size(): 表示人臉的最大最小尺寸
接着:
通過調用函數來識別圖片
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/calib3d/calib3d.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat image, image_gray; //定義兩個Mat變量,用於存儲每一幀的圖像
image = imread("C:/Users/asus/Desktop/timg (1).jpg");
imshow("原圖", image);
cvtColor(image, image_gray, CV_BGR2GRAY);//轉爲灰度圖
equalizeHist(image_gray, image_gray);//直方圖均衡化,增加對比度方便處理
CascadeClassifier eye_Classifier; //載入分類器
CascadeClassifier face_cascade; //載入分類器
//加載分類訓練器,OpenCv官方文檔提供的xml文檔,可以直接調用
//xml文檔路徑 opencv\install\etc\haarcascades
if (!eye_Classifier.load("D:/opencv/build/etc/haarcascades/haarcascade_eye.xml")) //需要將xml文檔放在自己指定的路徑下
{
cout << "Load haarcascade_eye.xml failed!" << endl;
return 0;
}
if (!face_cascade.load("D:/opencv/build/etc/haarcascades/haarcascade_frontalface_alt.xml"))
{
cout << "Load haarcascade_frontalface_alt f ailed!" << endl;
return 0;
}
//vector 是個類模板 需要提供明確的模板實參 vector<Rect>則是個確定的類 模板的實例化
vector<Rect> eyeRect;
vector<Rect> faceRect;
//檢測關於眼睛部位位置
eye_Classifier.detectMultiScale(image_gray, eyeRect, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30));
for (size_t eyeIdx = 0; eyeIdx < eyeRect.size(); eyeIdx++)
{
rectangle(image, eyeRect[eyeIdx], Scalar(0, 0, 255)); //用矩形畫出檢測到的位置
}
//檢測關於臉部位置
face_cascade.detectMultiScale(image_gray, faceRect, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30));
for (size_t i = 0; i < faceRect.size(); i++)
{
rectangle(image, faceRect[i], Scalar(0, 0, 255)); //用矩形畫出檢測到的位置
}
imshow("人臉識別圖", image); //顯示當前幀
waitKey(0);
return 0;
}
不過中間出現了一點點小插曲
上午還好好的下午就不能用了一直出現了這個情況
然後我百度了,試了很多方法,1.重新配了環境變量2.把庫又重新配了一遍,3.把vc14裏面的dll格式的文件放進c盤system32和syswow64裏面4.我把opencv刪了,又重新安裝了一遍,還是不行,通過1天的不屑百度,終於突破了這一難題…
然後開始調用攝像頭實時檢測人臉
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/calib3d/calib3d.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat image, image_gray; //定義兩個Mat變量,用於存儲每一幀的圖像
VideoCapture capture(0); //從攝像頭讀入視頻
while (1) //循環顯示每一幀
{
capture >> image; //讀取當前幀
cvtColor(image, image_gray, CV_BGR2GRAY);//轉爲灰度圖
equalizeHist(image_gray, image_gray);//直方圖均衡化,增加對比度方便處理
CascadeClassifier face_cascade; //載入分類器
//加載分類訓練器,OpenCv官方文檔提供的xml文檔,可以直接調用
//xml文檔路徑 opencv\installl\etc\haarcascades
if (!face_cascade.load("D:/opencv/build/etc/haarcascades/haarcascade_frontalface_alt.xml"))
{
cout << "Load haarcascade_frontalface_alt failed!" << endl;
return 0;
}
//vector 是個類模板 需要提供明確的模板實參 vector<Rect>則是個確定的類 模板的實例化
vector<Rect> faceRect;
//檢測關於臉部位置
face_cascade.detectMultiScale(image_gray, faceRect, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30));//檢測
for (size_t i = 0; i < faceRect.size(); i++)
{
rectangle(image, faceRect[i], Scalar(0, 0, 255)); //用矩形畫出檢測到的位置
}
imshow("人臉識別圖", image); //顯示當前幀
char c = waitKey(30); //延時30ms,即每秒播放33幀圖像
if (c == 27) break;
}
return 0;
}
PS:雖然做出來了,但是中間真的有很多小插曲,阻礙着我們,比如
後來詢問到告訴我在附加依賴項裏把這個刪了,
一:數據收集和預處理
opencv給出了40個人臉照片,我可以利用這些,也可以自己去構建屬於自己團隊的人臉數據庫
人臉數據庫的收集涉及到人臉檢測和人臉拍照(把前面2個結合在一起)
#include <opencv2\opencv.hpp>
#include <vector>
#include <iostream>
#include<stdio.h>
//#include <stdio.h>
//#include <cv.h>
using namespace std;
using namespace cv;
int main()
{
CascadeClassifier cascada;
cascada.load("E:/opencv/build/etc/haarcascades/haarcascade_frontalface_alt2.xml");
VideoCapture cap(0);
Mat frame, myFace;
int pic_num = 1;
while (1) {
//攝像頭讀圖像
cap >> frame;
vector<Rect> faces;//vector容器存檢測到的faces
Mat frame_gray;
cvtColor(frame, frame_gray, COLOR_BGR2GRAY);//轉灰度化,減少運算
cascada.detectMultiScale(frame_gray, faces, 1.1, 4, CV_HAAR_DO_ROUGH_SEARCH, Size(70, 70), Size(1000, 1000));
printf("檢測到人臉個數:%d\n", faces.size());
//1.frame_gray表示的是要檢測的輸入圖像 2.faces表示檢測到的人臉目標序列,3. 1.1表示每次圖像尺寸減小的比例
//4. 4表示每一個目標至少要被檢測到3次纔算是真的目標(因爲周圍的像素和不同的窗口大小都可以檢測到人臉表示每一個目標至少要被檢測到3次纔算是真的目標(因爲周圍的像素和不同的窗口大小都可以檢測到人臉
/*5.flags–要麼使用默認值,要麼使用CV_HAAR_DO_CANNY_PRUNING,
函數將會使用Canny邊緣檢測來排除邊緣過多或過少的區域,
因爲這些區域通常不會是人臉所在區域;opencv3 以後都不用這個參數了*/
//6. Size(100, 100)爲目標的最小尺寸 一般爲30*30 是最小的了 也夠了
//7. Size(500, 500)爲目標的最大尺寸 其實可以不用這個,opencv會自動去找這個最大尺寸
//適當調整5,6,7兩個參數可以用來排除檢測結果中的干擾項。
//識別到的臉用矩形圈出
for (int i = 0; i < faces.size(); i++)
{
rectangle(frame, faces[i], Scalar(255, 0, 0), 2, 8, 0);
}
//當只有一個人臉時,開始拍照
if (faces.size() == 1)
{
Mat faceROI = frame_gray(faces[0]);//在灰度圖中將圈出的臉所在區域裁剪出
//cout << faces[0].x << endl;//測試下face[0].x
resize(faceROI, myFace, Size(92, 112));//將興趣域size爲92*112
putText(frame, to_string(pic_num), faces[0].tl(), 3, 1.2, (0, 0, 225), 2, 0);//在 faces[0].tl()的左上角上面寫序號
string filename = format("E:/人臉識別/jiangjun/lxw/%d.jpg", pic_num); //存放在當前項目文件夾以1-10.jpg 命名,format就是轉爲字符串
imwrite(filename, myFace);//存在當前目錄下
imshow(filename, myFace);//顯示下size後的臉
waitKey(500);//等待500us
destroyWindow(filename);//:銷燬指定的窗口
pic_num++;//序號加1
if (pic_num == 11)
{
return 0;//當序號爲11時退出循環
}
}
int c = waitKey(10);
if ((char)c == 27) { break; } //10us內輸入esc則退出循環
imshow("frame", frame);//顯示視頻流
waitKey(100);//等待100us
}
return 0;
}
然後在寫人臉模型的訓練程序時,需要讀取人臉和人臉對應的標籤。直接在數據庫中讀取時低效的,所以要用csv文件讀取。csv文件中包含兩方面的內容,一是每一張圖片的位置所在,二是每一個人臉對應的標籤,就是爲每一個人編號。下面這個at.txt文件就是我所需要的csv文件。(博客中說用python來生成csv,但由於沒有python基礎,只能通過最笨的方法自己手動輸入。)
在配cmake時不斷出現這個對話框,最終我刪了原來的opencv,終於配完成了
二:人臉訓練
#include<opencv2\face\facerec.hpp> //opencv3需要
#include<opencv2\core.hpp>
#include<opencv2\face.hpp>
#include<opencv2\highgui.hpp>
#include<opencv2\imgproc.hpp>
#include <math.h>
//使用void read_csv()這個函數必須的三個頭文件
#include <iostream>
#include <fstream>
#include <sstream>
using namespace cv;
using namespace cv::face;
using namespace std;
static Mat norm_0_255(InputArray _src) {
Mat src = _src.getMat();
// 創建和返回一個歸一化後的圖像矩陣:
Mat dst;
switch (src.channels()) {
case 1:
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
break;
case 3:
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
break;
default:
src.copyTo(dst);
break;
}
return dst;
}
//使用CSV文件去讀圖像和標籤,主要使用stringstream和getline方法
static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {
std::ifstream file(filename.c_str(), ifstream::in);//c_str()函數可用可不用,無需返回一個標準C類型的字符串
if (!file)
{
string error_message = "No valid input file was given, please check the given filename.";
CV_Error(CV_StsBadArg, error_message);
}
string line, path, classlabel;
while (getline(file, line)) //從文本文件中讀取一行字符,未指定限定符默認限定符爲“/n”
{
stringstream liness(line);//這裏採用stringstream主要作用是做字符串的分割
getline(liness, path, separator);//讀入圖片文件路徑以分好作爲限定符
getline(liness, classlabel);//讀入圖片標籤,默認限定符
if (!path.empty() && !classlabel.empty()) //如果讀取成功,則將圖片和對應標籤壓入對應容器中
{
images.push_back(imread(path, 0));
labels.push_back(atoi(classlabel.c_str()));
}
}
}
int main()
{
//讀取你的CSV文件路徑.
//string fn_csv = string(argv[1]);
string fn_csv = "at.txt";
// 2個容器來存放圖像數據和對應的標籤
vector<Mat> images;
vector<int> labels;
// 讀取數據. 如果文件不合法就會出錯
// 輸入的文件名已經有了.
try
{
read_csv(fn_csv, i
mages, labels); //從csv文件中批量讀取訓練數據
}
catch (cv::Exception& e)
{
cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;
// 文件有問題,我們啥也做不了了,退出了
exit(1);
}
// 如果沒有讀取到足夠圖片,也退出.
if (images.size() <= 1) {
string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!";
CV_Error(CV_StsError, error_message);
}
for (int i = 0; i < images.size(); i++)
{
if (images[i].size() != Size(92, 112))
{
cout << i << endl;
cout << images[i].size() << endl;
}
}
// 下面的幾行代碼僅僅是從你的數據集中移除最後一張圖片,作爲測試圖片
//[gm:自然這裏需要根據自己的需要修改,他這裏簡化了很多問題]
Mat testSample = images[images.size() - 1];
int testLabel = labels[labels.size() - 1];
images.pop_back();//刪除最後一張照片,此照片作爲測試圖片
labels.pop_back();//刪除最有一張照片的labels
// 下面幾行創建了一個特徵臉模型用於人臉識別,
// 通過CSV文件讀取的圖像和標籤訓練它。
// T這裏是一個完整的PCA變換
//如果你只想保留10個主成分,使用如下代碼
// cv::createEigenFaceRecognizer(10);
//
// 如果你還希望使用置信度閾值來初始化,使用以下語句:
// cv::createEigenFaceRecognizer(10, 123.0);
//
// 如果你使用所有特徵並且使用一個閾值,使用以下語句:
// cv::createEigenFaceRecognizer(0, 123.0);
//創建一個PCA人臉分類器,暫時命名爲model吧,創建完成後
//調用其中的成員函數train()來完成分類器的訓練
Ptr<BasicFaceRecognizer> model = EigenFaceRecognizer::create();
model->train(images, labels);
model->save("MyFacePCAModel.xml");//保存路徑可自己設置,但注意用“\\”
Ptr<BasicFaceRecognizer> model1 = FisherFaceRecognizer::create();
model1->train(images, labels);
model1->save("MyFaceFisherModel.xml");
Ptr<LBPHFaceRecognizer> model2 = LBPHFaceRecognizer::create();
model2->train(images, labels);
model2->save("MyFaceLBPHModel.xml");
// 下面對測試圖像進行預測,predictedLabel是預測標籤結果
//注意predict()入口參數必須爲單通道灰度圖像,如果圖像類型不符,需要先進行轉換
//predict()函數返回一個整形變量作爲識別標籤
int predictedLabel = model->predict(testSample);//加載分類器
int predictedLabel1 = model1->predict(testSample);
int predictedLabel2 = model2->predict(testSample);
// 還有一種調用方式,可以獲取結果同時得到閾值:
// int predictedLabel = -1;
// double confidence = 0.0;
// model->predict(testSample, predictedLabel, confidence);
string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);
string result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel);
string result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel);
cout << result_message << endl;
cout << result_message1 << endl;
cout << result_message2 << endl;
getchar();
//waitKey(0);
return 0;
}
我的照片在第40個標籤,識別到我在40
三:人臉識別
#include<opencv2\opencv.hpp>
#include<opencv2\face.hpp>
#include<opencv2\core\core.hpp>
#include<opencv2\face\facerec.hpp>
#include <fstream>
#include <sstream>
#include<math.h>
using namespace std;
using namespace cv;
using namespace cv::face;
RNG g_rng(12345);
Ptr<FaceRecognizer> model;
int Predict(Mat src_image) //識別圖片
{
Mat face_test;
int predict = 0;
//截取的ROI人臉尺寸調整
if (src_image.rows >= 120)
{
//改變圖像大小,使用雙線性差值
resize(src_image, face_test, Size(92, 112));
}
//判斷是否正確檢測ROI
if (!face_test.empty())
{
//測試圖像應該是灰度圖
predict = model->predict(face_test);
}
cout << predict << endl;
return predict;
}
int main()
{
VideoCapture cap(0); //打開默認攝像頭
if (!cap.isOpened())
{
return -1;
}
Mat frame;
Mat gray;
//這個分類器是人臉檢測所用
CascadeClassifier cascade;
bool stop = false;
//訓練好的文件名稱,放置在可執行文件同目錄下
cascade.load("E:/opencv/new_build/install/etc/haarcascades/haarcascade_frontalface_alt2.xml");//感覺用lbpcascade_frontalface效果沒有它好,注意哈!要是正臉
model = createFisherFaceRecognizer();
//1.加載訓練好的分類器
model->load("E:/opencvrlsb/rl/rl/MyFaceFisherModel.xml");// opencv2用load (OpenCV3.1.0也保留着load方法)
//3.利用攝像頭採集人臉並識別
while (1)
{
cap >> frame;
vector<Rect> faces(0);//建立用於存放人臉的向量容器
cvtColor(frame, gray, CV_RGB2GRAY);//測試圖像必須爲灰度圖
equalizeHist(gray, gray); //變換後的圖像進行直方圖均值化處理
//檢測人臉
cascade.detectMultiScale(gray, faces,
1.1, 4, 0
//|CV_HAAR_FIND_BIGGEST_OBJECT
| CV_HAAR_DO_ROUGH_SEARCH,
//| CV_HAAR_SCALE_IMAGE,
Size(30, 30), Size(500, 500));
Mat* pImage_roi = new Mat[faces.size()]; //定以數組
Mat face;
Point text_lb;//文本寫在的位置
//框出人臉
string str;
for (int i = 0; i < faces.size(); i++)
{
pImage_roi[i] = gray(faces[i]); //將所有的臉部保存起來
text_lb = Point(faces[i].x, faces[i].y);
if (pImage_roi[i].empty())
continue;
switch (Predict(pImage_roi[i])) //對每張臉都識別(返回label標籤值)
{
case 41:str = "HuangHaiNa"; break;
case 40:str = "jiangjun"; break;
case 43:str = "HuangLuYao"; break;
default: str = "Error"; break;
}
Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));//所取的顏色任意值
rectangle(frame, Point(faces[i].x, faces[i].y), Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height), color, 1, 8);//放入緩存
putText(frame, str, text_lb, FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255));//添加文字
}
delete[]pImage_roi;
imshow("face", frame);
waitKey(200);
}
return 0;
}