版本:opencv3.4.1
平台:win10
#<io.h>是windows专有库,在linux中不能使用
本文参考github训练的ssd模型(在本文后悔给出链接)
,然后结合LBP实现人脸检测,之所以选择ssd+lbp其原因在于快,相比于其他浮点计算人脸检测方法,如EigenFaceRecognizer,FisherFaceRecognizer,在速度上,因为lbp采用整数进行计算,因此,在速度上有很大的提示,这对于在一些羸弱的计算机如树莓派也能良好的工作。
下面是演示代码:
使用方法:
在第一次使用的时候,将Flag 设为1,他会采集人脸,并进行训练
在后面的执行中,就可以将flag设为0,他会自动去加载上一下训练的模型
该代码使用了两个自实现函数:getfiles和SplitString,主要用来获取目录下的所有文件和用来进行字符串分割,在使用时,可不比纠结于这两个算法的实现,知道其使用方法即可:
void getFiles(string path, vector<string>& files)
第一个参数为文件路径,第二个是一个vector向量,用于存放该文件夹下所有文件的路径
void SplitString(const string& s, vector<string>& v, const string& c)
第一个参数为要进行分割的字符串,第二个是一个vector向量,用于存放分割的字符串,第三个参数为要使用什么字符串进行风格,如“,”、"_"等。
#include<opencv2/opencv.hpp>
#include <iostream>
#include<opencv2/face.hpp>
#include<opencv2/dnn.hpp>
#include <string>
#include<io.h>
#include <fstream>
using namespace std;
using namespace cv;
using namespace cv::face;
using namespace cv::dnn;
int Flag = 1; //1表示需需要进行添加新人脸,进行训练,2表示加载模型直接运行
String image_base = "F:\\VS\\opencv\\data\\orl_faces";
String modelFile = "E:\\opencv_install\\model\\caffe_ssd_face_detactor\\res10_300x300_ssd_iter_140000.caffemodel"; //caffe模型
String model_text_file = "E:\\opencv_install\\model\\caffe_ssd_face_detactor\\deploy.prototxt";
String train_data_dir = "F:\\VS\\opencv\\边角检测\\边角检测\\data";
void getFiles(string path, vector<string>& files);
void SplitString(const string& s, vector<string>& v, const string& c);
map<int, string> ID_NAME;
int main(int argc, char** argv)
{
//namedWindow("image", WINDOW_AUTOSIZE);
// import Caffe SSD model
Net net = readNetFromCaffe(model_text_file, modelFile);
if (net.empty()) {
printf("read caffe model data failure...\n");
return -1;
}
VideoCapture capture(0);
if (!capture.isOpened()) {
printf("could not open camera...\n");
return -1;
}
//采集人脸数据进行训练
if (Flag == 1) //添加人脸,进行训练
{
/*getFiles(train_data_dir, face_image_list);*/
//使用摄像头进行采集10张需要添加的人脸
int cnt = 10;
int ID = 1;
cout << "请输入姓名" << endl;
string name = "";
cin >> name;
//循环遍历文件夹,为了避免ID重复,需要先获取当前已有ID;
vector<string> face_image_list;
getFiles(train_data_dir, face_image_list);
if (face_image_list.size() == 0)
ID = 1;
else
{
//循环遍历,获取最大ID值
for (int i = 0; i < face_image_list.size(); i++)
{
//存储格式为"F:\\VS\\opencv\\边角检测\\边角检测\\data\\name_ID_1.jpg"
//获取ID方式,使用_进行分割
vector<string> ID_list;
SplitString(face_image_list[i], ID_list, "_");
if (atoi(ID_list[1].c_str()) > ID)
ID = atoi(ID_list[1].c_str());
}
}
//新用户ID+1
ID++;
namedWindow("image", WINDOW_AUTOSIZE);
while (true)
{
Mat tem_frame;
capture.read(tem_frame);
Mat inputBlob = blobFromImage(tem_frame, 1.0, Size(300, 300), Scalar(104, 117, 123), false, false);
//crop一定要设置成false,让他进行缩放,而不能从中心进行裁剪
net.setInput(inputBlob, "data");
Mat prob = net.forward("detection_out");
Mat detectionMat(prob.size[2], prob.size[3], CV_32F, prob.ptr<float>());//size[2]存放宽度,3存放高度
//设置置信区间
float confidence_threshold = 0.8;
float confidence = detectionMat.at<float>(0, 2); //第二列存放的是置信度
float tl_x = -1;
float tl_y = -1;
float br_x = -1;
float br_y = -1;
if (confidence > confidence_threshold)
{
size_t objIndex = (size_t)(detectionMat.at<float>(0, 1)); //第1列存放的是标签索引
tl_x = detectionMat.at<float>(0, 3) * tem_frame.cols; //后面依次为x,y的坐标
tl_y = detectionMat.at<float>(0, 4) * tem_frame.rows;
br_x = detectionMat.at<float>(0, 5) * tem_frame.cols;
br_y = detectionMat.at<float>(0, 6) * tem_frame.rows;
Rect object_box((int)tl_x, (int)tl_y, (int)(br_x - tl_x), (int)(br_y - tl_y)); //绘制矩形,这里坐标需要转换为int
rectangle(tem_frame, object_box, Scalar(0, 0, 255), 2, 8, 0); //绘制
}
imshow("image", tem_frame);
char c = cv::waitKey(10);
if (c == 115) //调整好姿势,按s进行采集数据
{
if (tl_x != -1)
{
Rect object_box((int)tl_x, (int)tl_y, (int)(br_x - tl_x), (int)(br_y - tl_y)); //绘制矩形,这里坐标需要转换为int
Mat test_roi = tem_frame(object_box);
imwrite(format((train_data_dir + "\\" + name + "_ % d_%d.jpg").c_str(), ID, cnt--), test_roi);
cout << "已采集" << 10 - cnt << "张" << endl;
cv::waitKey(500);//放在误触,设定1s采集一张
}
else
{
cout << "调整角度进行人脸采集" << endl;
}
}
if (cnt == 0)
break;
}
destroyWindow("image");
//获取训练集,
vector<string> train_image_list;
getFiles(train_data_dir, train_image_list);
vector<int> train_label;
vector<Mat> tran_data;
for (int j = 0; j < train_image_list.size(); j++)
{
//存储格式为"F:\\VS\\opencv\\边角检测\\边角检测\\data\\name_ID_1.jpg"
tran_data.push_back(imread(train_image_list[j], 0));//以灰度模式进行读取
vector<string> label_tem;
SplitString(train_image_list[j], label_tem, "_");
train_label.push_back(atoi(label_tem[1].c_str()));
vector<string> name_tem;
SplitString(label_tem[0], name_tem, "\\");
ID_NAME[atoi(label_tem[1].c_str())] = name_tem[name_tem.size() - 1];
}
//train it
int height = 300;
int width = 300;
//确保每张图片宽高一样
for (int i = 0; i < tran_data.size(); i++)
{
resize(tran_data[i], tran_data[i], Size(width, height));
}
Ptr<LBPHFaceRecognizer> model = LBPHFaceRecognizer::create();
cout << "train it....." << endl;
model->train(tran_data, train_label);
//模型保存
model->save("F:\\VS\\opencv\\边角检测\\边角检测\\model\\face.xml");
cout << "save it....." << endl;
}
// 创建并加载模型
Ptr<LBPHFaceRecognizer> model = LBPHFaceRecognizer::create();
cout << "load it....." << endl;
model->read("F:\\VS\\opencv\\边角检测\\边角检测\\model\\face.xml");
//获取ID_NMAE
if (ID_NAME.size() == 0)
{
vector<string> train_image_list;
getFiles(train_data_dir, train_image_list);
for (int j = 0; j < train_image_list.size(); j++)
{
//存储格式为"F:\\VS\\opencv\\边角检测\\边角检测\\data\\name_ID_1.jpg"
vector<string> label_tem;
SplitString(train_image_list[j], label_tem, "_");
vector<string> name_tem;
SplitString(label_tem[0], name_tem, "\\");
ID_NAME[atoi(label_tem[1].c_str())] = name_tem[name_tem.size() - 1];
}
}
Mat frame;
while (true)
{
capture.read(frame);
if (frame.empty())
{
continue;
}
flip(frame, frame, 1);
Mat inputBlob = blobFromImage(frame, 1.0, Size(300, 300), Scalar(104, 117, 123), false, false);
//crop一定要设置成false,让他进行缩放,而不能从中心进行裁剪
net.setInput(inputBlob, "data");
Mat prob = net.forward("detection_out");
Mat detectionMat(prob.size[2], prob.size[3], CV_32F, prob.ptr<float>());//size[2]存放宽度,3存放高度
//设置置信区间
float confidence_threshold = 0.8;
for (int i = 0; i < detectionMat.rows; i++)
{
float confidence = detectionMat.at<float>(i, 2); //第二列存放的是置信度
if (confidence > confidence_threshold)
{
size_t objIndex = (size_t)(detectionMat.at<float>(i, 1)); //第1列存放的是标签索引
float tl_x = detectionMat.at<float>(i, 3) * frame.cols; //后面依次为x,y的坐标
float tl_y = detectionMat.at<float>(i, 4) * frame.rows;
float br_x = detectionMat.at<float>(i, 5) * frame.cols;
float br_y = detectionMat.at<float>(i, 6) * frame.rows;
Rect object_box((int)tl_x, (int)tl_y, (int)(br_x - tl_x), (int)(br_y - tl_y)); //绘制矩形,这里坐标需要转换为int
rectangle(frame, object_box, Scalar(0, 0, 255), 2, 8, 0); //绘制
//预测是谁
Mat testSample_roi;
try {
testSample_roi = frame(object_box);
}
catch (Exception e)
{
testSample_roi = frame;
}
Mat gray;
cvtColor(testSample_roi, gray, CV_BGR2GRAY);//因为训练的时候采用的是灰度
resize(gray, gray, Size(300, 300));
//int predictedLabel = model->predict(gray);
int predictedLabel;
double conf;
model->predict(gray, predictedLabel,conf);
cout << predictedLabel <<" " << conf << endl;
if(conf<40)
putText(frame, ID_NAME[predictedLabel], Point(tl_x, br_y), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255, 0, 0), 2);
else
putText(frame, "Unknow", Point(tl_x, br_y), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 255, 0), 2);
}
}
imshow("image", frame);
cv::waitKey(1);
}
return 0;
}
void SplitString(const string& s, vector<string>& v, const string& c)
{
string::size_type pos1, pos2;
pos2 = s.find(c);
pos1 = 0;
while (string::npos != pos2)
{
v.push_back(s.substr(pos1, pos2 - pos1));
pos1 = pos2 + c.size();
pos2 = s.find(c, pos1);
}
if (pos1 != s.length())
v.push_back(s.substr(pos1));
}
void getFiles(string path, vector<string>& files)
{
//文件句柄
long long hFile = 0;
//文件信息,声明一个存储文件信息的结构体
struct _finddata_t fileinfo;
string p;//字符串,存放路径
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)//若查找成功,则进入
{
do
{
//如果是目录,迭代之(即文件夹内还有文件夹)
if ((fileinfo.attrib & _A_SUBDIR))
{
//文件名不等于"."&&文件名不等于".."
//.表示当前目录
//..表示当前目录的父目录
//判断时,两者都要忽略,不然就无限递归跳不出去了!
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
}
//如果不是,加入列表
else
{
files.push_back(p.assign(path).append("\\").append(fileinfo.name));
}
} while (_findnext(hFile, &fileinfo) == 0);
//_findclose函数结束查找
_findclose(hFile);
}
}
不训练该人脸:
经测试发现,LBPHFaceRecognizer的置信度阈值为60,当然该阈值的实际参数还是需要根据自己的数据及进行适当调整才行。
模型github链接