通过人脸判断性别(补充材料)

时间:2022-09-16 05:19:19

 考虑到一些朋友对鄙人的性别判断方法感兴趣,而自己的代码又没妥善保存,因此特在这里进行弥补,将作业报告PPT内容公布,并进行简单地代码说明,希望对感兴趣的童鞋能有所帮助。

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

通过人脸判断性别(补充材料)

代码:

        代码主要根据opencv\modules\contrib\doc\facerec\src\facerec_fisherfaces.cpp来讲解,源代码内容如下:

[cpp]  view plain copy
  1. /* 
  2.  * Copyright (c) 2011. Philipp Wagner <bytefish[at]gmx[dot]de>. 
  3.  * Released to public domain under terms of the BSD Simplified license. 
  4.  * 
  5.  * Redistribution and use in source and binary forms, with or without 
  6.  * modification, are permitted provided that the following conditions are met: 
  7.  *   * Redistributions of source code must retain the above copyright 
  8.  *     notice, this list of conditions and the following disclaimer. 
  9.  *   * Redistributions in binary form must reproduce the above copyright 
  10.  *     notice, this list of conditions and the following disclaimer in the 
  11.  *     documentation and/or other materials provided with the distribution. 
  12.  *   * Neither the name of the organization nor the names of its contributors 
  13.  *     may be used to endorse or promote products derived from this software 
  14.  *     without specific prior written permission. 
  15.  * 
  16.  *   See <http://www.opensource.org/licenses/bsd-license> 
  17.  */  
  18.   
  19. #include "opencv2/core/core.hpp"  
  20. #include "opencv2/contrib/contrib.hpp"  
  21. #include "opencv2/highgui/highgui.hpp"  
  22.   
  23. #include <iostream>  
  24. #include <fstream>  
  25. #include <sstream>  
  26.   
  27. using namespace cv;  
  28. using namespace std;  
  29.   
  30. static Mat norm_0_255(InputArray _src) {  
  31.     Mat src = _src.getMat();  
  32.     // Create and return normalized image:  
  33.     Mat dst;  
  34.     switch(src.channels()) {  
  35.     case 1:  
  36.         cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);  
  37.         break;  
  38.     case 3:  
  39.         cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3);  
  40.         break;  
  41.     default:  
  42.         src.copyTo(dst);  
  43.         break;  
  44.     }  
  45.     return dst;  
  46. }  
  47.   
  48. static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';') {  
  49.     std::ifstream file(filename.c_str(), ifstream::in);  
  50.     if (!file) {  
  51.         string error_message = "No valid input file was given, please check the given filename.";  
  52.         CV_Error(CV_StsBadArg, error_message);  
  53.     }  
  54.     string line, path, classlabel;  
  55.     while (getline(file, line)) {  
  56.         stringstream liness(line);  
  57.         getline(liness, path, separator);  
  58.         getline(liness, classlabel);  
  59.         if(!path.empty() && !classlabel.empty()) {  
  60.             images.push_back(imread(path, 0));  
  61.             labels.push_back(atoi(classlabel.c_str()));  
  62.         }  
  63.     }  
  64. }  
  65.   
  66. int main(int argc, const char *argv[]) {  
  67.     // Check for valid command line arguments, print usage  
  68.     // if no arguments were given.  
  69.     if (argc < 2) {  
  70.         cout << "usage: " << argv[0] << " <csv.ext> <output_folder> " << endl;  
  71.         exit(1);  
  72.     }  
  73.     string output_folder;  
  74.     if (argc == 3) {  
  75.         output_folder = string(argv[2]);  
  76.     }  
  77.     // Get the path to your CSV.  
  78.     string fn_csv = string(argv[1]);  
  79.     // These vectors hold the images and corresponding labels.  
  80.     vector<Mat> images;  
  81.     vector<int> labels;  
  82.     // Read in the data. This can fail if no valid  
  83.     // input filename is given.  
  84.     try {  
  85.         read_csv(fn_csv, images, labels);  
  86.     } catch (cv::Exception& e) {  
  87.         cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;  
  88.         // nothing more we can do  
  89.         exit(1);  
  90.     }  
  91.     // Quit if there are not enough images for this demo.  
  92.     if(images.size() <= 1) {  
  93.         string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!";  
  94.         CV_Error(CV_StsError, error_message);  
  95.     }  
  96.     // Get the height from the first image. We'll need this  
  97.     // later in code to reshape the images to their original  
  98.     // size:  
  99.     int height = images[0].rows;  
  100.     // The following lines simply get the last images from  
  101.     // your dataset and remove it from the vector. This is  
  102.     // done, so that the training data (which we learn the  
  103.     // cv::FaceRecognizer on) and the test data we test  
  104.     // the model with, do not overlap.  
  105.     Mat testSample = images[images.size() - 1];  
  106.     int testLabel = labels[labels.size() - 1];  
  107.     images.pop_back();  
  108.     labels.pop_back();  
  109.     // The following lines create an Fisherfaces model for  
  110.     // face recognition and train it with the images and  
  111.     // labels read from the given CSV file.  
  112.     // If you just want to keep 10 Fisherfaces, then call  
  113.     // the factory method like this:  
  114.     //  
  115.     //      cv::createFisherFaceRecognizer(10);  
  116.     //  
  117.     // However it is not useful to discard Fisherfaces! Please  
  118.     // always try to use _all_ available Fisherfaces for  
  119.     // classification.  
  120.     //  
  121.     // If you want to create a FaceRecognizer with a  
  122.     // confidence threshold (e.g. 123.0) and use _all_  
  123.     // Fisherfaces, then call it with:  
  124.     //  
  125.     //      cv::createFisherFaceRecognizer(0, 123.0);  
  126.     //  
  127.     Ptr<FaceRecognizer> model = createFisherFaceRecognizer();  
  128.     model->train(images, labels);  
  129.     // The following line predicts the label of a given  
  130.     // test image:  
  131.     int predictedLabel = model->predict(testSample);  
  132.     //  
  133.     // To get the confidence of a prediction call the model with:  
  134.     //  
  135.     //      int predictedLabel = -1;  
  136.     //      double confidence = 0.0;  
  137.     //      model->predict(testSample, predictedLabel, confidence);  
  138.     //  
  139.     string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);  
  140.     cout << result_message << endl;  
  141.     // Here is how to get the eigenvalues of this Eigenfaces model:  
  142.     Mat eigenvalues = model->getMat("eigenvalues");  
  143.     // And we can do the same to display the Eigenvectors (read Eigenfaces):  
  144.     Mat W = model->getMat("eigenvectors");  
  145.     // Get the sample mean from the training data  
  146.     Mat mean = model->getMat("mean");  
  147.     // Display or save:  
  148.     if(argc == 2) {  
  149.         imshow("mean", norm_0_255(mean.reshape(1, images[0].rows)));  
  150.     } else {  
  151.         imwrite(format("%s/mean.png", output_folder.c_str()), norm_0_255(mean.reshape(1, images[0].rows)));  
  152.     }  
  153.     // Display or save the first, at most 16 Fisherfaces:  
  154.     for (int i = 0; i < min(16, W.cols); i++) {  
  155.         string msg = format("Eigenvalue #%d = %.5f", i, eigenvalues.at<double>(i));  
  156.         cout << msg << endl;  
  157.         // get eigenvector #i  
  158.         Mat ev = W.col(i).clone();  
  159.         // Reshape to original size & normalize to [0...255] for imshow.  
  160.         Mat grayscale = norm_0_255(ev.reshape(1, height));  
  161.         // Show the image & apply a Bone colormap for better sensing.  
  162.         Mat cgrayscale;  
  163.         applyColorMap(grayscale, cgrayscale, COLORMAP_BONE);  
  164.         // Display or save:  
  165.         if(argc == 2) {  
  166.             imshow(format("fisherface_%d", i), cgrayscale);  
  167.         } else {  
  168.             imwrite(format("%s/fisherface_%d.png", output_folder.c_str(), i), norm_0_255(cgrayscale));  
  169.         }  
  170.     }  
  171.     // Display or save the image reconstruction at some predefined steps:  
  172.     for(int num_component = 0; num_component < min(16, W.cols); num_component++) {  
  173.         // Slice the Fisherface from the model:  
  174.         Mat ev = W.col(num_component);  
  175.         Mat projection = subspaceProject(ev, mean, images[0].reshape(1,1));  
  176.         Mat reconstruction = subspaceReconstruct(ev, mean, projection);  
  177.         // Normalize the result:  
  178.         reconstruction = norm_0_255(reconstruction.reshape(1, images[0].rows));  
  179.         // Display or save:  
  180.         if(argc == 2) {  
  181.             imshow(format("fisherface_reconstruction_%d", num_component), reconstruction);  
  182.         } else {  
  183.             imwrite(format("%s/fisherface_reconstruction_%d.png", output_folder.c_str(), num_component), reconstruction);  
  184.         }  
  185.     }  
  186.     // Display if we are not writing to an output folder:  
  187.     if(argc == 2) {  
  188.         waitKey(0);  
  189.     }  
  190.     return 0;  
  191. }  
         上面这段代码实现了用fisherface判断人脸性别的算法,但效果不好,因此我打算在fisherface之前插入一个pca降维过程,提高fisherface的精度。代码如下:
[cpp]  view plain copy
  1. ...   
  2.     // If you want to use _all_ Eigenfaces and have a threshold,  
  3.     // then call the method like this:  
  4.     //  
  5.     //      cv::createEigenFaceRecognizer(0, 123.0);  
  6.     //  
  7.   
  8.     Ptr<FaceRecognizer> model = createEigenFaceRecognizer();//定义pca模型  
  9.     model->train(images, labels);//训练pca模型,这里的model包含了所有特征值和特征向量,没有损失  
  10.     model->save("eigenface.yml");//保存训练结果,供检测时使用  
  11.     Mat eigenvalues = model->getMat("eigenvalues");//提取model中的特征值,该特征值默认由大到小排列  
  12.     Mat W = model->getMat("eigenvectors");//提取model中的特征向量,特征向量的排列方式与特征值排列顺序一一对应  
  13.     int xth = 121;//打算保留前121个特征向量,代码中没有体现原因,但选择121是经过斟酌的,首先,在我的实验中,"前121个特征值之和/所有特征值总和>0.97";其次,121=11^2,可以将结果表示成一个11*11的2维图像方阵,交给fisherface去计算。  
  14.     vector<Mat> reduceDemensionimages;//降维后的图像矩阵  
  15.     Mat evs = Mat(W, Range::all(), Range(0, xth));//选择前xth个特征向量,其余舍弃  
  16.     for(int i=0;i<images.size();i++)  
  17.     {  
  18.         Mat projection = subspaceProject(evs, mean, images[i].reshape(1,1));//做子空间投影  
  19.         reduceDemensionimages.push_back(projection.reshape(1,sqrt(xth));//将获得的子空间系数表示映射成2维图像,并保存起来  
  20.     }  
  21.     Ptr<FaceRecognizer> fishermodel = createFisherFaceRecognizer();  
  22.     fishermodel->train(reduceDemensionimages,labels);//用保存的降维后的图片来训练fishermodel,后面的内容与原始代码就没什么变化了  
  23. ...  

       这样就实现了pca+lda的训练过程,将训练好的pca和lda模型保存起来,在识别时用训练好的pca和lda模型对检测样本提取特征,根据其最终结果判断图片性别,就大功告成了。 

        pca+lda不但可以做性别识别,还可以做“年轻/年老”、“人种”、“表情”等多种识别,效果也不错,但这类识别都有一个特点:每类的样本个数大致相等,且分类应该是完备的,否则就不适合用pca+lda的方法来做,例如:在一大堆的图片中识别出梅西,这种事情就不太适合用pca+lda方法来做。

        pca+lda方法在原理上和当前很火的深度学习存在着相似性,hinton大牛提出的多层结构:每层用无监督的方法提取特征,提取的特征又是下一层的输入,这一点和pca+lda很相似:pca是一种无监督的学习方法,提出的特征又作为lda方法的输入,大量实验证明多层深度学习会比浅层学习效果更好,这里的pca+lda就比单纯的lda或单纯的pca更好,大家也可以试试用多层pca+lda(即:pca+pca+...+pca+lda)会取的什么样的结果。