Opencv人脸识别案例记录

内容来源于网络,这里记录下自己学习的过程以及其中遇到的坑,方便以后自己查阅。
这篇博客中有关人脸识别模块的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 <opencv2/opencv.hpp>
#include <iostream>

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 <opencv2/opencv.hpp>
#include <iostream>

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 <opencv2/opencv.hpp>
#include <iostream>

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 <opencv2/opencv.hpp>
#include <iostream>

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 <opencv2/opencv.hpp>
#include <opencv2/face.hpp>
#include <iostream>

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格式的时候,编码格式选的不对

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

boss-dog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值