Home | History | Annotate | Download | only in cpp
      1 /*
      2 * pca.cpp
      3 *
      4 *  Author:
      5 *  Kevin Hughes <kevinhughes27[at]gmail[dot]com>
      6 *
      7 *  Special Thanks to:
      8 *  Philipp Wagner <bytefish[at]gmx[dot]de>
      9 *
     10 * This program demonstrates how to use OpenCV PCA with a
     11 * specified amount of variance to retain. The effect
     12 * is illustrated further by using a trackbar to
     13 * change the value for retained varaince.
     14 *
     15 * The program takes as input a text file with each line
     16 * begin the full path to an image. PCA will be performed
     17 * on this list of images. The author recommends using
     18 * the first 15 faces of the AT&T face data set:
     19 * http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html
     20 *
     21 * so for example your input text file would look like this:
     22 *
     23 *        <path_to_at&t_faces>/orl_faces/s1/1.pgm
     24 *        <path_to_at&t_faces>/orl_faces/s2/1.pgm
     25 *        <path_to_at&t_faces>/orl_faces/s3/1.pgm
     26 *        <path_to_at&t_faces>/orl_faces/s4/1.pgm
     27 *        <path_to_at&t_faces>/orl_faces/s5/1.pgm
     28 *        <path_to_at&t_faces>/orl_faces/s6/1.pgm
     29 *        <path_to_at&t_faces>/orl_faces/s7/1.pgm
     30 *        <path_to_at&t_faces>/orl_faces/s8/1.pgm
     31 *        <path_to_at&t_faces>/orl_faces/s9/1.pgm
     32 *        <path_to_at&t_faces>/orl_faces/s10/1.pgm
     33 *        <path_to_at&t_faces>/orl_faces/s11/1.pgm
     34 *        <path_to_at&t_faces>/orl_faces/s12/1.pgm
     35 *        <path_to_at&t_faces>/orl_faces/s13/1.pgm
     36 *        <path_to_at&t_faces>/orl_faces/s14/1.pgm
     37 *        <path_to_at&t_faces>/orl_faces/s15/1.pgm
     38 *
     39 */
     40 
     41 #include <iostream>
     42 #include <fstream>
     43 #include <sstream>
     44 
     45 #include <opencv2/core/core.hpp>
     46 #include "opencv2/imgcodecs.hpp"
     47 #include <opencv2/highgui/highgui.hpp>
     48 
     49 using namespace cv;
     50 using namespace std;
     51 
     52 ///////////////////////
     53 // Functions
     54 static void read_imgList(const string& filename, vector<Mat>& images) {
     55     std::ifstream file(filename.c_str(), ifstream::in);
     56     if (!file) {
     57         string error_message = "No valid input file was given, please check the given filename.";
     58         CV_Error(Error::StsBadArg, error_message);
     59     }
     60     string line;
     61     while (getline(file, line)) {
     62         images.push_back(imread(line, 0));
     63     }
     64 }
     65 
     66 static  Mat formatImagesForPCA(const vector<Mat> &data)
     67 {
     68     Mat dst(static_cast<int>(data.size()), data[0].rows*data[0].cols, CV_32F);
     69     for(unsigned int i = 0; i < data.size(); i++)
     70     {
     71         Mat image_row = data[i].clone().reshape(1,1);
     72         Mat row_i = dst.row(i);
     73         image_row.convertTo(row_i,CV_32F);
     74     }
     75     return dst;
     76 }
     77 
     78 static Mat toGrayscale(InputArray _src) {
     79     Mat src = _src.getMat();
     80     // only allow one channel
     81     if(src.channels() != 1) {
     82         CV_Error(Error::StsBadArg, "Only Matrices with one channel are supported");
     83     }
     84     // create and return normalized image
     85     Mat dst;
     86     cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
     87     return dst;
     88 }
     89 
     90 struct params
     91 {
     92     Mat data;
     93     int ch;
     94     int rows;
     95     PCA pca;
     96     string winName;
     97 };
     98 
     99 static void onTrackbar(int pos, void* ptr)
    100 {
    101     cout << "Retained Variance = " << pos << "%   ";
    102     cout << "re-calculating PCA..." << std::flush;
    103 
    104     double var = pos / 100.0;
    105 
    106     struct params *p = (struct params *)ptr;
    107 
    108     p->pca = PCA(p->data, cv::Mat(), PCA::DATA_AS_ROW, var);
    109 
    110     Mat point = p->pca.project(p->data.row(0));
    111     Mat reconstruction = p->pca.backProject(point);
    112     reconstruction = reconstruction.reshape(p->ch, p->rows);
    113     reconstruction = toGrayscale(reconstruction);
    114 
    115     imshow(p->winName, reconstruction);
    116     cout << "done!   # of principal components: " << p->pca.eigenvectors.rows << endl;
    117 }
    118 
    119 
    120 ///////////////////////
    121 // Main
    122 int main(int argc, char** argv)
    123 {
    124     if (argc != 2) {
    125         cout << "usage: " << argv[0] << " <image_list.txt>" << endl;
    126         exit(1);
    127     }
    128 
    129     // Get the path to your CSV.
    130     string imgList = string(argv[1]);
    131 
    132     // vector to hold the images
    133     vector<Mat> images;
    134 
    135     // Read in the data. This can fail if not valid
    136     try {
    137         read_imgList(imgList, images);
    138     } catch (cv::Exception& e) {
    139         cerr << "Error opening file \"" << imgList << "\". Reason: " << e.msg << endl;
    140         exit(1);
    141     }
    142 
    143     // Quit if there are not enough images for this demo.
    144     if(images.size() <= 1) {
    145         string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!";
    146         CV_Error(Error::StsError, error_message);
    147     }
    148 
    149     // Reshape and stack images into a rowMatrix
    150     Mat data = formatImagesForPCA(images);
    151 
    152     // perform PCA
    153     PCA pca(data, cv::Mat(), PCA::DATA_AS_ROW, 0.95); // trackbar is initially set here, also this is a common value for retainedVariance
    154 
    155     // Demonstration of the effect of retainedVariance on the first image
    156     Mat point = pca.project(data.row(0)); // project into the eigenspace, thus the image becomes a "point"
    157     Mat reconstruction = pca.backProject(point); // re-create the image from the "point"
    158     reconstruction = reconstruction.reshape(images[0].channels(), images[0].rows); // reshape from a row vector into image shape
    159     reconstruction = toGrayscale(reconstruction); // re-scale for displaying purposes
    160 
    161     // init highgui window
    162     string winName = "Reconstruction | press 'q' to quit";
    163     namedWindow(winName, WINDOW_NORMAL);
    164 
    165     // params struct to pass to the trackbar handler
    166     params p;
    167     p.data = data;
    168     p.ch = images[0].channels();
    169     p.rows = images[0].rows;
    170     p.pca = pca;
    171     p.winName = winName;
    172 
    173     // create the tracbar
    174     int pos = 95;
    175     createTrackbar("Retained Variance (%)", winName, &pos, 100, onTrackbar, (void*)&p);
    176 
    177     // display until user presses q
    178     imshow(winName, reconstruction);
    179 
    180     int key = 0;
    181     while(key != 'q')
    182         key = waitKey();
    183 
    184    return 0;
    185 }
    186