自己写了一下eigenface的代码,读取at.txt文件中的图片路径,然后进行pca降维,再进行识别。
原理网上很多,这里就不写了。
主要是为了学习思路,所以矩阵变换,取行列计算之类的都是手写的,代码比较繁杂。
imread读取灰度图片是CV_8UC1,而矩阵计算时不能直接计算,要转换成CV_64FC1。8UC1的范围时0~255,64FC1范围是0~1,要注意。
这里训练用的图片是orl训练集中的s10~s19 中的1.bmp,共十张,记为第一到第十张图片,所以在识别图片的时候,输出的是他是第几张图片,可以按自己的需求更改代码。
如果有问题或错误欢迎联系,一定及时更改。
#include <iostream>
#include <opencv.hpp>
#include <vector>
#include <string>
#include <opencv2/highgui/highgui_c.h>//CV_Error的头文件
#include <math.h>
#define k_weidu 5//降维后的维度
#define image_col 92
#define image_row 112
using namespace std;
using namespace cv;
string file_name = "H:\\ORL\\at.txt";
string test_file = "H:/ORL/s15/3.bmp";
//输入矩阵 每列为一张图片 如n*m即有m张图片 每张图片的像素为n
void PCA_test(Mat& item);
//读取txt文件中的所有图片路径
Mat read_image(string file_name);
void sort_mat(Mat value, vector<int>& ij);
//将图片矩阵变为一个向量
vector<uchar> mattovec(string image_name);
//
Mat vectomat(vector<uchar> vec);
int main(void)
{
//读取图片
Mat item = read_image(file_name);
//进行降维与训练
PCA_test(item);
//输入需要识别的图片
waitKey(0);
return 0;
}
//原图片是112*92 输入十张人脸
//item 行数为92*112 列数为照片个数
//返回特征向量
void PCA_test(Mat& item)
{
//算出m个向量的平均向量 avg
//初始化赋值0
Mat item_s = item.clone();
vector<uchar> avg(item.rows);
for (int i, j = 0; j < item.rows; j++)
{
int sum = 0;
for (i = 0; i < item.cols; i++)
{
sum += item.at<uchar>(j, i);
}
avg[j] = sum / item.cols;
}
//显示平均脸
Mat avg_mat = vectomat(avg);
namedWindow("avg");
imshow("avg", avg_mat);
//用原矩阵的每个向量减去平均向量
//含义:每个人脸向量减去平均向量的向量差
for (int i, j = 0; j < item.cols; j++)
for (i = 0; i < item.rows; i++)
{
item.at<uchar>(i, j) -= avg[i];
}
//原矩阵的转置矩阵
Mat item_t = item.t();
//灰度或rgb图像的颜色分量都在0~255之间。
//CV_8UC1的取值范围正好,因此直接imshow就可以显示图像了。
//CV_64FC1取值范围远远不止0~255,需要先归一化成0~1.0
//imshow的时候会把图像x255后再显示。
item.convertTo(item, CV_64FC1, 1.0 / 255.0);
//求协方差 协方差矩阵为n*n
Mat item_cov;
Mat means1;//协方差,均值
calcCovarMatrix(item, item_cov, means1, COVAR_NORMAL | COVAR_ROWS);
//求协方差矩阵的特征值和特征向量
//计算后 特征值是一个一维列向量
//特征向量是一个n*n矩阵 每一列对应一个特征值
//eigenvalue 是Mat<double>
Mat eigenvalue, eigenvector;
eigen(item_cov, eigenvalue, eigenvector);
//n个特征向量
Mat eigen_mat(item_s.rows, eigenvector.cols, CV_64FC1, Scalar(0));
//n*m m*m = eigen_mat 特征向量为n*m n维 即为特征脸
eigen_mat = item * eigenvector;
eigen_mat.convertTo(eigen_mat, CV_8UC1, 255.0);
//输出特征脸(每一个特征向量转化成图像)
for (int i, j = 0; j < eigenvector.cols; j++)
{
vector<uchar> eigen_face;
Mat eigen_face_mat;
for (i = 0; i < eigen_mat.rows; i++)
{
eigen_face.push_back(eigen_mat.at<uchar>(i, j));
}
eigen_face_mat = vectomat(eigen_face);
string a = "eigen_face" + to_string(j);
namedWindow(a);
imshow(a, eigen_face_mat);
}
vector<int> ij;
//将特征值排序 大的在前
Mat v = eigenvalue.clone();
sort_mat(v, ij);
//将矩阵降维为N*K
Mat PCA_k(eigen_mat.rows, k_weidu, CV_8UC1, Scalar(0));
for (int i, j = 0; j < k_weidu; j++)
{
for (i = 0; i < eigen_mat.rows; i++)
{
PCA_k.at<uchar>(i, j) = eigen_mat.at<uchar>(i, ij[j]);
}
}
//读入要识别的图片
Mat im = imread(test_file, 0);
namedWindow("scr");
imshow("scr", im);
Mat im_sub_avg(im.rows * im.cols, 1, CV_8UC1, Scalar(0));
//将图片转为向量 92*112 1 并计算与平均脸的差值
for (int i, j = 0; j < im.cols; j++)
{
for (i = 0; i < im.rows; i++)
{
im_sub_avg.at<uchar>(j * im.rows + i, 0) = im.at<uchar>(i, j) - avg[(int)(j * im.rows + i)];
}
}
//转换成float类型进行计算
im_sub_avg.convertTo(im_sub_avg, CV_64FC1, 1.0 / 255.0);
PCA_k.convertTo(PCA_k, CV_64FC1, 1.0 / 255.0);
Mat PCA_k_t = PCA_k.t();
//用特征向量表示该脸 K*N N*1 = K*1
Mat im_eigen = PCA_k_t * im_sub_avg;
//对最初的M每个图片同上求K维的权重 并求其与它的欧氏距离
vector<int> euclidean_metric;
item.convertTo(item, CV_8UC1,255.0);
for (int i, j = 0; j < item.cols; j++)
{
Mat temp(im.rows * im.cols, 1, CV_8UC1, Scalar(0));
for (int ii = 0; ii < temp.rows; ii++)
temp.at<uchar>(ii, 0) = item.at<uchar>(ii, j);
temp.convertTo(temp, CV_64FC1, 1.0 / 255.0);
Mat temp_eigen = PCA_k_t * temp;
//temp_eigen.convertTo(temp_eigen, CV_8UC1,255.0);
double em = 0;
for (i = 0; i < temp_eigen.rows; i++)
{
//为了防止越界,除个1000,对后边数值比较没有影响
double x = temp_eigen.at<double>(i, 0);
double y = im_eigen.at<double>(i, 0);
double t = y - x;
em += pow(t, 2);
}
euclidean_metric.push_back(em);
cout << "与第" << j + 1 << "张照片的欧氏距离: " << em << endl;
}
int minPosition = min_element(euclidean_metric.begin(), euclidean_metric.end()) - euclidean_metric.begin() + 1;
cout << "识别人脸的图片为第" << minPosition << endl;
}
Mat read_image(string file_name)
{
ifstream file(file_name.c_str(), ifstream::in);
if (!file)
{
string error_message = "No valid input file was given, please check the given filename.";
CV_Error(CV_StsBadArg, error_message);
}
string line;
vector<vector<uchar>> vec;
while (getline(file, line)) //从文本文件中读取一行字符,未指定限定符默认限定符为“/n”
{
vec.push_back(mattovec(line));
}
Mat item((vec[0]).size(), vec.size(), CV_8UC1, Scalar(0));
for (int i, j = 0; j < item.cols; j++)
{
for (i = 0; i < item.rows; i++)
{
item.at<uchar>(i, j) = vec[j][i];
}
}
return item;
}
//选择排序 大的在前
void sort_mat(Mat value, vector<int>& ij)
{
for (int i, j = 0; j < value.rows; j++)
{
int d = 0;//实时记录最大值的坐标
//temp实时记录最大值
double temp = value.at<double>(0, 0);
for (i = 1; i < value.rows - 1; i++)
{
if (value.at<double>(i, 0) > temp)
{
temp = value.at<double>(i, 0);
d = i;
}
}
//遍历后得到最大的 将坐标压入队列
ij.push_back(d);
value.at<double>(d, 0) = 0;
}
}
vector<uchar> mattovec(string image_name)
{
Mat image = imread(image_name, 0);
if (image.empty())
{
string a = "could not load " + image_name;
printf(a.c_str());
exit(0);
}
vector<uchar> vec;
//一列接一列
for (int i, j = 0; j < image.cols; j++)
{
for (i = 0; i < image.rows; i++)
{
vec.push_back(image.at<uchar>(i, j));
}
}
return vec;
}
Mat vectomat(vector<uchar> vec)
{
Mat mat(image_row, image_col, CV_8UC1, Scalar(0));
for (int i, j = 0; j < image_col; j++)
{
for (i = 0; i < image_row; i++)
{
mat.at<uchar>(i, j) = vec[j * (int)image_row + i];
}
}
return mat;
}