内容来源于网络,这里记录下自己学习的过程以及其中遇到的坑,方便以后自己查阅。
这篇博客中有关人脸识别模块的API的一些变化,早点看到就好了,当时弄得我头大
https://blog.csdn.net/xingchenbingbuyu/article/details/78573983
一.环境要求
我使用的是vs2019和已经扩展的opencv4.2库
只要在opencv文件中的lib文件夹中有
二.基本的知识
均值、标准差、方差的公式
关于方差和协方差
API介绍:meanStdDev函数是用来计算矩阵的均值和标准偏差
C++: void meanStdDev(InputArray src,OutputArray mean, OutputArray stddev, InputArray mask=noArray())
parameter:
src:输入矩阵,这个矩阵应该是1-4通道的,这可以将计算结果存在Scalar_ ‘s中
mean:输出参数,计算均值
stddev:输出参数,计算标准差
mask:可选参数
calcCovarMatrix 函数是用来求取向量集的协方差矩阵
calcCovarMatrix(const Mat*samples, int nsamples, Mat& covar, Mat& mean, int flags, int ctype=CV_64F)
samples: 输入的向量集,它们可以是若干个同样形式的向量组成,也可以是一个矩阵的若干行组成。
nsamples: 输入的向量的数目。
covar: 输出的协方差矩阵。
mean: 输出的均值矩阵。
flags: 操作标志,(PS:opencv4.2的版本前面没有CV_)
分别有:COVAR_SCRAMBLED(高速PCA”Scrambled”协方差),COVAR_NORMAL(计算均值和协方差),COVAR_USE_AVG(输入均值而不是计算均值),COVAR_SCALE(又一次缩放输出的协方差矩阵),
COVAR_ROWS,COVAR_COLS(COVAR_ROWS,COVAR_COLS是指当samples 是由一个矩阵时,用来指用单个向量是由其中行向量或者列向量组成)
代码演示:计算均值、方差、协方差
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("E:\\pictures\\10.jpg");
if (image.empty())
{
printf("could not load image...\n");
return -1;
}
imshow("input image", image);
Mat means, stddev; //均值、标准差
meanStdDev(image, means, stddev);
printf("均值行数 : %d, 均值列数 %d\n", means.rows, means.cols); //RGB三通道,所以均值结果是3行1列
printf("标准差行数 : %d, 标准差列数 %d\n", stddev.rows, stddev.cols);//标准差结果也是3行1列
for (int row = 0; row < means.rows; row++) //打印RGB三个通道的均值和标准差
{
printf("mean %d = %.3f\n", row, means.at<double>(row));
printf("stddev %d = %.3f\n", row, stddev.at<double>(row));
}
//定义个5*3的矩阵
Mat samples = (Mat_<double>(5, 3) << 90, 60, 90, 90, 90, 30, 60, 60, 60, 60, 60, 90, 30, 30, 30);
Mat cov, mu;
calcCovarMatrix(samples, cov, mu, COVAR_NORMAL | COVAR_ROWS);
cout << "=============================" << endl;
cout << "cov : " << endl;
cout << cov / 5 << endl;
cout << "means : " << endl;
cout << mu << endl;
waitKey(0);
return 0;
}
输出结果:
三.关于Opencv中的特征值与特征向量
eigen函数用来计算矩阵的特征值与特征向量,且必须是对称矩阵
#include
#include
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat data = (Mat_<double>(2, 2) << 2, 4, 4, 2); //定义个两行两列的矩阵
Mat eigenvalues, eigenvector; //定义特征值、特征向量
eigen(data, eigenvalues, eigenvector);
for (int i = 0; i < eigenvalues.rows; i++)
{
printf("eigen value %d : %.3f \n", i, eigenvalues.at<double>(i));
}
cout << " eigen vector : " << endl;
cout << eigenvector << endl;
waitKey(0);
return 0;
}
四.关于人脸识别算法的简介
OpenCV中PCA分析
原理:①通过对高维数据分析发现他们的相同与不同表达为 一个低维数据模式
②主成分不变③细微损失④高维数据到低维数据
API:
PCA::PCA(InputArray data, InputArray mean, int flags, int maxComponents=0)
该构造函数的参数1为要进行PCA变换的输入Mat;
参数2为该Mat的均值向量;
参数3为输入矩阵数据的存储方式,如果其值为CV_PCA_DATA_AS_ROW则说明输入Mat的每一行代表一个样本,同理当其值为CV_PCA_DATA_AS_COL时,代表输入矩阵的每一列为一个样本;
参数4为该PCA计算时保留的最大主成分的个数。如果是缺省值,则表示所有的成分都保留。
代码演示:
#include
#include
using namespace cv;
using namespace std;
double calcPCAOrientation(vector<Point>& pts, Mat& image);
int main(int argc, char** argv)
{
Mat src = imread("E:\\pictures\\49.png");
if (src.empty())
{
printf("could not load image...\n");
return -1;
}
namedWindow("【1】原图", WINDOW_AUTOSIZE);
imshow("【1】原图", src);
Mat gray, binary; //灰度图、二值化图
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU); //阈值判定
imshow("【2】二值化后", binary);
//寻找图像中的轮廓
vector<Vec4i> hireachy;
vector<vector<Point>> contours;
findContours(binary, contours, hireachy, RETR_LIST, CHAIN_APPROX_NONE);
Mat result = src.clone();
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
if (area > 1e5 || area < 1e2) continue; //去掉不需要的轮廓,去掉大于10^5和小于10^2像素的轮廓
drawContours(result, contours, i, Scalar(0, 0, 255), 2, 8); //绘制图像中的轮廓
double theta = calcPCAOrientation(contours[i], result); //求轮廓点的方向
}
imshow("【3】寻找轮廓后", result);
waitKey(0);
return 0;
}
double calcPCAOrientation(vector<Point>& pts, Mat& image)
{
int size = static_cast<int>(pts.size()); //获取数据点个数 size=901
Mat data_pts = Mat(size, 2, CV_64FC1);//定义个 n行2列的矩阵
for (int i = 0; i < size; i++)
{
data_pts.at<double>(i, 0) = pts[i].x;//矩阵的第1列数据
data_pts.at<double>(i, 1) = pts[i].y;//矩阵的第2列数据
}
// PCA处理
PCA pca_analysis(data_pts, Mat(), 0);
Point cnt = Point(static_cast<int>(pca_analysis.mean.at<double>(0, 0)),//求均值,第1列1行
static_cast<int>(pca_analysis.mean.at<double>(0, 1)));//求均值,第1列2行
circle(image, cnt, 2, Scalar(0, 255, 0), 2, 8, 0);
vector<Point2d> vecs(2); //定义两个特征向量
vector<double> vals(2); //定义两个特征值
for (int i = 0; i < 2; i++)
{
vals[i] = pca_analysis.eigenvalues.at<double>(i, 0);
cout << "第" << i << "个特征值为:" << vals[i] << endl;
vecs[i] = Point2d(pca_analysis.eigenvectors.at<double>(i, 0),
pca_analysis.eigenvectors.at<double>(i, 1));
}
Point p1 = cnt + 0.02 * Point(static_cast<int>(vecs[0].x * vals[0]), static_cast<int>(vecs[0].y * vals[0]));
Point p2 = cnt - 0.05 * Point(static_cast<int>(vecs[1].x * vals[1]), static_cast<int>(vecs[1].y * vals[1]));
line(image, cnt, p1, Scalar(255, 0, 0), 2, 8, 0);
line(image, cnt, p2, Scalar(255, 255, 0), 2, 8, 0);
double angle = atan2(vecs[0].y, vecs[0].x);
printf("角度 : %.2f\n", 180 * (angle / CV_PI));
return angle;
}
输出结果:
人脸识别算法之EigenFace算法
简介:人脸数据 平均脸 特征脸
API:
Ptr<BasicFaceRecognizer> model = EigenFaceRecognizer::create();
参数 –numComponents PCA降维 参数 –threshold 预测分类时候用的值DBL_MAX
关于FisherFace、LBPH算法算法,这里不在赘述,网上有很多详细的资料
PCA:取两个对象的相似之处
LDA:取两个对象的差异之处
五.人脸识别案例
第一步:数据的采集
原则:采集的数据图像尺寸一致,人脸面对摄像头,头不要动,做各种表情,我调用的是笔记本的摄像头进行采集。
若需要识别多个人脸,采集一个人的图像,就放到一个文件夹中,另一个人,换一个文件夹。一般一个人15-30张。
在Opencv模块中有以上的文件:D:\opencv4.2_201964 world\etc\haarcascades\haarcascade_frontalface_alt_tree.xml
下面的代码中要用到这个文件:目的是你摄像头捕捉的是人脸,而不是其他的事物。
//调用笔记本摄像头进行人脸的数据采集
#include
#include
using namespace cv;
using namespace std;
//下载人脸的特征数据
string haar_face_datapath = "D:\\opencv4.2_201964 world\\etc\\haarcascades\\haarcascade_frontalface_alt_tree.xml";
int main(int argc, char** argv)
{
VideoCapture capture(0); //打开摄像头
if (!capture.isOpened())
{
printf("could not open camera...\n");
return -1;
}
Size S = Size((int)capture.get(CAP_PROP_FRAME_WIDTH), (int)capture.get(CAP_PROP_FRAME_HEIGHT));
int fps = capture.get(CAP_PROP_FPS);
CascadeClassifier faceDetector;
faceDetector.load(haar_face_datapath);
Mat frame;
namedWindow("camera-demo", WINDOW_AUTOSIZE);
vector<Rect> faces;
int count = 0;
while (capture.read(frame))
{
flip(frame, frame, 1);
faceDetector.detectMultiScale(frame, faces, 1.1, 1, 0, Size(100, 120), Size(380, 400));
for (int i = 0; i < faces.size(); i++)
{
if (count % 10 == 0)
{
Mat dst;
resize(frame(faces[i]), dst, Size(100, 100)); //将采集到的人脸图片尺寸统一
imwrite(format("E:/pictures/my_face/face_%d.jpg", count), dst);
}
rectangle(frame, faces[i], Scalar(0, 0, 255), 2, 8, 0); //用矩形框选出人脸
}
imshow("camera-demo", frame);
char c = waitKey(10);
if (c == 27)
{
break;
}
count++;
}
capture.release();
waitKey(0);
return 0;
}
进行以上数据采集的操作中,指定个你要选择存放图像的文件夹,在采集中,头尽量不要动,做其他的表情,采集完成后:
①选出15-30张图像作为数据集
②将这些数据集的路径写成进csv文件里,方便下面程序的路径读取,用你熟悉的编程语言写一个,可以先保存为txt格式后转为csv格式,如下:
第二步:开始检测人脸
#include
#include
#include
using namespace cv;
using namespace cv::face;
using namespace std;
string haar_face_datapath = "D:\\opencv4.2_201964 world\\etc\\haarcascades\\haarcascade_frontalface_alt_tree.xml";
int main(int argc, char** argv)
{
string filename = string("E:\\pictures\\666.csv");
ifstream file(filename.c_str(), ifstream::in);
if (!file)
{
printf("could not load file correctly...\n");
return -1;
}
string line, path, classlabel;
vector<Mat> images;
vector<int> labels;
char separator = ';';
while (getline(file, line))
{
stringstream liness(line);
getline(liness, path, separator);
getline(liness, classlabel);
if (!path.empty() && !classlabel.empty())
{
//printf("path : %s\n", path.c_str());
images.push_back(imread(path, 0));
labels.push_back(atoi(classlabel.c_str()));
}
}
if (images.size() < 1 || labels.size() < 1)
{
printf("invalid image path...\n");
return -1;
}
int height = images[0].rows;
int width = images[0].cols;
printf("height : %d, width : %d\n", height, width);
Mat testSample = images[images.size() - 1];
int testLabel = labels[labels.size() - 1];
images.pop_back();
labels.pop_back();
// train it
Ptr<BasicFaceRecognizer> model = EigenFaceRecognizer::create();
model->train(images, labels);
// recognition face
int predictedLabel = model->predict(testSample);
printf("actual label : %d, predict label : %d\n", testLabel, predictedLabel);
CascadeClassifier faceDetector;
faceDetector.load(haar_face_datapath);
VideoCapture capture(0);
if (!capture.isOpened())
{
printf("could not open camera...\n");
return -1;
}
Mat frame;
namedWindow("face-recognition", WINDOW_AUTOSIZE);
vector<Rect> faces;
Mat dst;
while (capture.read(frame))
{
flip(frame, frame, 1);
faceDetector.detectMultiScale(frame, faces, 1.1, 1, 0, Size(80, 100), Size(380, 400));
for (int i = 0; i < faces.size(); i++)
{
Mat roi = frame(faces[i]);
cvtColor(roi, dst, COLOR_BGR2GRAY);
resize(dst, testSample, testSample.size());
int label = model->predict(testSample); //测试样本
rectangle(frame, faces[i], Scalar(255, 0, 0), 2, 8, 0);
putText(frame, format("i'm %s", (label == 19 ? "zzw!" : "what")), faces[i].tl(), FONT_HERSHEY_PLAIN, 1.0, Scalar(0, 0, 255), 2, 8);
}
imshow("face-recognition", frame);
char c = waitKey(10);
if (c == 27) //ESC
{
break;
}
}
waitKey(0);
return 0;
}
输出结果:
将预测的标签与实际的相比较,一致,达到要求
注意点:
PS:在读取数据集的时候,遇到个问题,
第一个路径怎么读取都是乱码,排查了好久,发现txt文件保存为csv格式的时候,编码格式选的不对