Opencv+C++之人臉識別二


這兩天課比較多,上次的兩步法人臉識別代碼一直沒有補充完整,今天將整個實驗代碼show一下,同時將該方法的主要思想介紹下:

上一節我們已經將圖片進行降維處理,這樣做的目的就是要在保持對象間差異的同時降低處理數據量。除了PCA外,LDA也是一種比較簡單實用的降維方法,大家可以對比兩種降維方法;基於PCA降維后的數據,我們接着要做的是用訓練數據將測試數據表示出來

接着通過以下的誤差判別式來找到M近鄰(誤差值越小說明該訓練樣本跟測試樣本的相似度越大)

       

以上就完成了兩步法中的第一步,第二步中用M近鄰樣本將測試樣本再次標出(實際上這里的本質還是稀疏表示的方法,但是改進之處是單純的稀疏法中稀疏項不確定,兩步法中通過第一步的誤差篩選確定了貢獻度較大的訓練樣本)

在M近鄰中包含多個類的訓練樣本,我們要將每個類的訓練樣本累加起來,分別同測試樣本做誤差對比,將測試樣本判定給誤差最下的類

       

OK,主要思想介紹了,下面就看代碼實現

/************************************************************************/
/* ZhaoChaofeng
*/ 2013.4.16
/************************************************************************/

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

#include <fstream>
#include <sstream>
#include <iostream>
#include <string>

using namespace cv;
using namespace std;

const double u=0.01f;
const double v=0.01f;//the global parameter
const int MNeighbor=40;//the M nearest neighbors
// Number of components to keep for the PCA
const int num_components = 100;
//the M neighbor mats
vector<Mat> MneighborMat;
//the class index of M neighbor mats
vector<int> MneighborIndex;
//the number of object which used to training
const int Training_ObjectNum=40;
//the number of image that each object used
const int Training_ImageNum=7;
//the number of object used to testing
const int Test_ObjectNum=40;
//the image number
const int Test_ImageNum=3;

// Normalizes a given image into a value range between 0 and 255.
Mat norm_0_255(const Mat& src) {
    // Create and return normalized image:
    Mat dst;
    switch(src.channels()) {
    case 1:
        cv::normalize(src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
        break;
    case 3:
        cv::normalize(src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
        break;
    default:
        src.copyTo(dst);
        break;
    }
    return dst;
}

// Converts the images given in src into a row matrix.
Mat asRowMatrix(const vector<Mat>& src, int rtype, double alpha = 1, double beta = 0) {
    // Number of samples:
    size_t n = src.size();
    // Return empty matrix if no matrices given:
    if(n == 0)
        return Mat();
    // dimensionality of (reshaped) samples
    size_t d = src[0].total();
    // Create resulting data matrix:
    Mat data(n, d, rtype);
    // Now copy data:
    for(int i = 0; i < n; i++) {
        //
        if(src[i].empty()) {
            string error_message = format("Image number %d was empty, please check your input data.", i);
            CV_Error(CV_StsBadArg, error_message);
        }
        // Make sure data can be reshaped, throw a meaningful exception if not!
        if(src[i].total() != d) {
            string error_message = format("Wrong number of elements in matrix #%d! Expected %d was %d.", i, d, src[i].total());
            CV_Error(CV_StsBadArg, error_message);
        }
        // Get a hold of the current row:
        Mat xi = data.row(i);
        // Make reshape happy by cloning for non-continuous matrices:
        if(src[i].isContinuous()) {
            src[i].reshape(1, 1).convertTo(xi, rtype, alpha, beta);
        } else {
            src[i].clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta);
        }
    }
    return data;
}

//convert int to string
string Int_String(int index)
{
    stringstream ss;
    ss<<index;
    return ss.str();
}

////show the element of mat(used to test code)
//void showMat(Mat RainMat)
//{
//    for (int i=0;i<RainMat.rows;i++)
//    {
//        for (int j=0;j<RainMat.cols;j++)
//        {
//            cout<<RainMat.at<float>(i,j)<<"  ";
//        }
//        cout<<endl;
//    }
//}
//
////show the element of vector
//void showVector(vector<int> index)
//{
//    for (int i=0;i<index.size();i++)
//    {
//        cout<<index[i]<<endl;
//    }
//}
//
//void showMatVector(vector<Mat> neighbor)
//{
//    for (int e=0;e<neighbor.size();e++)
//    {
//        showMat(neighbor[e]);
//    }
//}


//Training function


void Trainging()
{
    // Holds some training images:
    vector<Mat> db;

    // This is the path to where I stored the images, yours is different!
    for (int i=1;i<=Training_ObjectNum;i++)
    {
        for (int j=1;j<=Training_ImageNum;j++)
        {
            string filename="s"+Int_String(i)+"/"+Int_String(j)+".pgm";
            db.push_back(imread(filename,IMREAD_GRAYSCALE));
        }
    }

    // Build a matrix with the observations in row:
    Mat data = asRowMatrix(db, CV_32FC1);

    // Perform a PCA:
    PCA pca(data, Mat(), CV_PCA_DATA_AS_ROW, num_components);

    // And copy the PCA results:
    Mat mean = pca.mean.clone();
    Mat eigenvalues = pca.eigenvalues.clone();
    Mat eigenvectors = pca.eigenvectors.clone();

    // The mean face:
    //imshow("avg", norm_0_255(mean.reshape(1, db[0].rows)));

    // The first three eigenfaces:
    //imshow("pc1", norm_0_255(pca.eigenvectors.row(0)).reshape(1, db[0].rows));
    //imshow("pc2", norm_0_255(pca.eigenvectors.row(1)).reshape(1, db[0].rows));
    //imshow("pc3", norm_0_255(pca.eigenvectors.row(2)).reshape(1, db[0].rows));

    ////get and save the training image information which decreased on dimensionality
    Mat mat_trans_eigen;
    Mat temp_data=data.clone();
    Mat temp_eigenvector=pca.eigenvectors.clone();
    gemm(temp_data,temp_eigenvector,1,NULL,0,mat_trans_eigen,CV_GEMM_B_T);

    //save the eigenvectors
    FileStorage fs(".\\eigenvector.xml", FileStorage::WRITE);
    fs<<"eigenvector"<<eigenvectors;
    fs<<"TrainingSamples"<<mat_trans_eigen;
    fs.release();
}

//Line combination of test sample used by training samples 
//parameter:y stand for the test sample column vector;
//x stand for the training samples matrix
Mat LineCombination(Mat y,Mat x)
{
    //the number of training samples
    size_t col=x.cols;
    //the result mat 
    Mat result=cvCreateMat(col,1,CV_32FC1);
    //the transposition of x and also work as a temp matrix
    Mat trans_x_mat=cvCreateMat(col,col,CV_32FC1);
    //construct the identity matrix
    Mat I=Mat::ones(col,col,CV_32FC1);

    //solve the Y=XA
    //result=x.inv(DECOMP_SVD);
        //result*=y;
    Mat temp=(x.t()*x+u*I);
    
    Mat temp_one=temp.inv(DECOMP_SVD);
    Mat temp_two=x.t()*y;
    result=temp_one*temp_two;

    return result;
}

//Error test
//parameter:y stand for the test sample column vector;
//x stand for the training samples matrix
//coeff stand for the coefficient of training samples 
void  ErrorTest(Mat y,Mat x,Mat coeff)
{
    //the array store the coefficient
    map<double,int> Efficient;

    //compute the error
    for (int i=0;i<x.cols;i++)
    {
        Mat temp=x.col(i);
        double coefficient=coeff.at<float>(i,0);
        temp=coefficient*temp;
        double e=norm((y-temp),NORM_L2);
        Efficient[e]=i;//insert a new element
    }

    //select the minimum w col as the w nearest neighbors
    map<double,int>::const_iterator map_it=Efficient.begin();
    int num=0;
    //the map could sorted by the key one
    while (map_it!=Efficient.end() && num<MNeighbor)
    {
        MneighborMat.push_back(x.col(map_it->second));
        MneighborIndex.push_back(map_it->second);
        ++map_it;
        ++num;
    }

    //return MneighborMat;
}

//error test of two step
//parameter:MneighborMat store the class information of M nearest neighbor samples
int ErrorTest_Two(Mat y,Mat x,Mat coeff)
{
    int result;
    bool flag=true;
    double minimumerror;
    //
    map<int,vector<Mat>> ErrorResult;

    //count the class of M neighbor
    for (int i=0;i<x.cols;i++)
    {
        //compare
        //Mat temp=x.col(i)==MneighborMat[i];
          //showMat(temp);
        //if (temp.at<float>(0,0)==255)
        //{
            int classinf=MneighborIndex[i];
            double coefficient=coeff.at<float>(i,0);
            Mat temp=x.col(i);
            temp=coefficient*temp;
            ErrorResult[classinf/Training_ImageNum].push_back(temp);
        //}
        
    }

    //
    map<int,vector<Mat>>::const_iterator map_it=ErrorResult.begin();
    while(map_it!=ErrorResult.end())
    {
        vector<Mat> temp_mat=map_it->second;
        int num=temp_mat.size();
        Mat temp_one;
        temp_one=Mat::zeros(temp_mat[0].rows,temp_mat[0].cols,CV_32FC1);
        while (num>0)
        {
            temp_one+=temp_mat[num-1];
            num--;
        }
        double e=norm((y-temp_one),NORM_L2);
        if (flag)
        {
            minimumerror=e;
            result=map_it->first+1;
            flag=false;
        }
        if (e<minimumerror)
        {
            minimumerror=e;
            result=map_it->first+1;
        }
        ++map_it;
    }
    return result;
}

//testing function
//parameter:y stand for the test sample column vector;
//x stand for the training samples matrix
int testing(Mat x,Mat y)
{
    // the class that test sample belongs to
    int classNum;

    //the first step: get the M nearest neighbors
    Mat coffecient=LineCombination(y.t(),x.t());

    //cout<<"the first step coffecient"<<endl;
    //showMat(coffecient);

    //map<Mat,int> MneighborMat=ErrorTest(y,x,coffecient);
    ErrorTest(y.t(),x.t(),coffecient);

    //cout<<"the M neighbor index"<<endl;
    //showVector(MneighborIndex);
    //cout<<"the M neighbor mats"<<endl;
    //showMatVector(MneighborMat);

    //the second step:
    //construct the W nearest neighbors mat
    int row=x.cols;//should be careful 
    Mat temp(row,MNeighbor,CV_32FC1);
    for (int i=0;i<MneighborMat.size();i++)
    {
        Mat temp_x=temp.col(i);
        if (MneighborMat[i].isContinuous())
        {
            MneighborMat[i].convertTo(temp_x,CV_32FC1,1,0);
        }
        else
        {
            MneighborMat[i].clone().convertTo(temp_x,CV_32FC1,1,0);
        }
    }

    //cout<<"the second step mat"<<endl;
    //showMat(temp);

    Mat coffecient_two=LineCombination(y.t(),temp);

    //cout<<"the second step coffecient"<<endl;
    //showMat(coffecient_two);

    classNum=ErrorTest_Two(y.t(),temp,coffecient_two);
    return classNum;
}

int main(int argc, const char *argv[]) {
    //the number which test true
    int TrueNum=0;
    //the Total sample which be tested
    int TotalNum=Test_ObjectNum*Test_ImageNum;

    //if there is the eigenvector.xml, it means we have got the training data and go to the testing stage directly;
    FileStorage fs(".\\eigenvector.xml", FileStorage::READ);
    if (fs.isOpened())
    {
        //if the eigenvector.xml file exist,read the mat data
        Mat mat_eigenvector;
        fs["eigenvector"] >> mat_eigenvector;    
        Mat mat_Training;
        fs["TrainingSamples"]>>mat_Training;

        for (int i=1;i<=Test_ObjectNum;i++)
        {
            int ClassTestNum=0;
            for (int j=Training_ImageNum+1;j<=Training_ImageNum+Test_ImageNum;j++)
            {
                string filename="s"+Int_String(i)+"/"+Int_String(j)+".pgm";
                Mat TestSample=imread(filename,IMREAD_GRAYSCALE);
                Mat TestSample_Row;
                TestSample.reshape(1,1).convertTo(TestSample_Row,CV_32FC1,1,0);//convert to row mat
                Mat De_deminsion_test;
                gemm(TestSample_Row,mat_eigenvector,1,NULL,0,De_deminsion_test,CV_GEMM_B_T);// get the test sample which decrease the dimensionality

                //cout<<"the test sample"<<endl;
                //showMat(De_deminsion_test.t());
                //cout<<"the training samples"<<endl;
                //showMat(mat_Training);

                int result=testing(mat_Training,De_deminsion_test);
                //cout<<"the result is"<<result<<endl;
                if (result==i)
                {
                    TrueNum++;
                    ClassTestNum++;
                }
                MneighborIndex.clear();
                MneighborMat.clear();//及時清除空間
            }
            cout<<""<<Int_String(i)<<"類測試正確的圖片數:  "<<Int_String(ClassTestNum)<<endl;
        }
        fs.release();
    }
    else
    {
        Trainging();
    }
    // Show the images:
    waitKey(0);

    // Success!
    return 0;
}

在以上的實現中,有些opencv的實現需要特別注意一下:

(1)坑爹的Mat類型,它雖然可以方便的讓我們實現圖像數據的矩陣化,並給出了一系列的操作方法,但是,在調試中,它卻不能像一般變量一樣,讓我們直觀的看到;我用一個比較笨的方法:自己寫一個方法,在調試中調用,呈現關鍵矩陣的數據

(2)另外一個就是將訓練數據做一個保存,用到了opencv中的FileStorage類;有關對中間數據的存儲通常會用到.xml或者.yml文件,以下對其做個簡單介紹

  新版本的OpenCV的C++接口中,imwrite()和imread()只能保存整數數據,且需要以圖像格式。當需要保存浮點數據或者XML/YML文件時,之前的C語言接口cvSave()函數已經在C++接口中被刪除,代替它的是    FileStorage類。這個類非常的方便,封裝了很多數據結構的細節,編程的時候可以根據統一的接口對數據結構進行保存。

    1. FileStorage類寫XML/YML文件

         •   新建一個FileStorage對象,以FileStorage::WRITE的方式打開一個文件。

         •   使用 << 操作對該文件進行操作。

         •   釋放該對象,對文件進行關閉。

        例子如下:

FileStorage fs("test.yml", FileStorage::WRITE);
    fs << "frameCount" << 5;
    time_t rawtime; time(&rawtime);
    fs << "calibrationDate" << asctime(localtime(&rawtime));
    Mat cameraMatrix = (Mat_<double>(3,3) << 1000, 0, 320, 0, 1000, 240, 0, 0, 1); //又一種Mat初始化方式
    Mat distCoeffs = (Mat_<double>(5,1) << 0.1, 0.01, -0.001, 0, 0);
    fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;
    
    //features為一個大小為3的向量,其中每個元素由隨機數x,y和大小為8的uchar數組組成
    fs << "features" << "[";
    for( int i = 0; i < 3; i++ )
    {
        int x = rand() % 640;
        int y = rand() % 480;
        uchar lbp = rand() % 256;
        fs << "{:" << "x" << x << "y" << y << "lbp" << "[:";
        for( int j = 0; j < 8; j++ )
            fs << ((lbp >> j) & 1);
        fs << "]" << "}";
    }
    fs << "]";
    fs.release();

2. FileStorage類讀XML/YML文件

       FileStorage對存儲內容在內存中是以層次的節點組成的,每個節點類型為FileNode,FileNode可以使單個的數值、數組或者一系列FileNode的集合。FileNode又可以看做是一個容器,使用iterator接口可以對該節點內更小單位的內容進行訪問,例如訪問到上面存儲的文件中"features"的內容。步驟與寫文件類似:

         •   新建FileStorage對象,以FileStorage::READ 方式打開一個已經存在的文件

         •   使用FileStorage::operator []()函數對文件進行讀取,或者使用FileNode和FileNodeIterator

         •   使用FileStorage::release()對文件進行關閉

         例子如下:

FileStorage fs("test.yml", FileStorage::READ);

    //方式一: []操作符
    int frameCount = (int)fs["frameCount"];
    
    //方式二: FileNode::operator >>()
    string date;
    fs["calibrationDate"] >> date;
    
    Mat cameraMatrix2, distCoeffs2;
    fs["cameraMatrix"] >> cameraMatrix2;
    fs["distCoeffs"] >> distCoeffs2;
    
    //注意FileNodeIterator的使用,似乎只能用一維數組去讀取里面所有的數據
    FileNode features = fs["features"];
    FileNodeIterator it = features.begin(), it_end = features.end();
    int idx = 0;
    std::vector<uchar> lbpval;
    for( ; it != it_end; ++it, idx++ )
    {
        cout << "feature #" << idx << ": ";
        cout << "x=" << (int)(*it)["x"] << ", y=" << (int)(*it)["y"] << ", lbp: (";
        (*it)["lbp"] >> lbpval;  //直接讀出一維向量

        for( int i = 0; i < (int)lbpval.size(); i++ )
            cout << " " << (int)lbpval[i];
        cout << ")" << endl;
    }
    fs.release();

另外,注意在新建FileStorage對象之后,並以READ或WRITE模式打開文件之后,可以用FileStorage::isOpened()查看文件狀態,判斷是否成功打開了文件。

有關FileStorage類的相關內容引用自:http://www.cnblogs.com/summerRQ/articles/2524560.html

我使用的是opencv2.4.0版本實現的方法,opencv有個欠缺的地方就是版本間的兼容性,雖然做了些工作,但是使用起來還是有些不流暢。不過,值得稱贊的是其將OOP的思想應用到庫的開發中,很多核心對象和相關操作被封裝起來,方便使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM