訓練好caffemodel後,提取人臉圖片的特徵向量
這篇部落格主要介紹下用C++程式碼,提取出一張人臉圖片的特徵向量,這裡的網路,是上文中用到了center loss的網路,根據論文裡介紹的,我們提取出人臉圖片以及該圖片的上下翻轉圖各自經過網路在fc5層輸出的特徵向量,然後將兩者拼起來,形成一個2倍維數的特徵向量,該特徵向量表徵了這個人臉。這樣子,我們計算出一對待驗證的人臉圖片各自的特徵向量後,就可以計算相似度了,這裡用餘弦相似度。
同樣的,作為C++愛好者,我還是將程式碼組織成一個人臉識別類 class FaceRecognition ,該類只有一個共有的介面
輸入兩張待識別的人臉圖片(注意,這個函式不會對人臉圖片進行對齊操作),計算出其餘弦相似度並返回。float getSimilarity(const Mat& lhs,const Mat& rhs,bool useFlipImg=true); //比較兩個影象的餘弦相似度
#include <caffe/caffe.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <algorithm> #include <iosfwd> #include <memory> #include <string> #include <utility> #include <vector> #include <cmath> using namespace caffe; // NOLINT(build/namespaces) using namespace cv; using namespace std; //人臉驗證類 class FaceRecognition{ //提取網路最後一層特徵 public: //建構函式,需要一個deploy.prototxt檔案和一個caffemodel FaceRecognition(const string& model_file,const string& trained_file ){ _net.reset(new Net<float>(model_file,TEST)); //定義一個網路 _net->CopyTrainedLayersFrom(trained_file); //載入權重 } float getSimilarity(const Mat& lhs,const Mat& rhs,bool useFlipImg=true){ //比較兩個影象的餘弦相似度 //計算餘弦相似度 vector<float> feat1,feat2; if(useFlipImg){ feat1=getLastLayerFeaturesFlip(lhs); feat2=getLastLayerFeaturesFlip(rhs); } else{ feat1=getLastLayerFeatures(lhs); feat2=getLastLayerFeatures(rhs); } return std::max<float>(0,getSimilarity(feat1,feat2)); } private: shared_ptr<Net<float> > _net; //CNN網路 float getMold(const vector<float>& vec){ //求向量的模長 int n = vec.size(); float sum = 0.0; for (int i = 0; i<n; ++i) sum += vec[i] * vec[i]; return sqrt(sum); } //求兩個向量的餘弦相似度 float getSimilarity(const vector<float>& lhs, const vector<float>& rhs){ int n = lhs.size(); assert(n == rhs.size()); float tmp = 0.0; //內積 for (int i = 0; i<n; ++i) tmp += lhs[i] * rhs[i]; return tmp / (getMold(lhs)*getMold(rhs)); } //center loss中,是將原圖的特徵向量和上下翻轉圖的特徵向量拼接起來,返回最終的特徵向量 vector<float> getLastLayerFeaturesFlip(const Mat& _img){ vector<float> result1 = getLastLayerFeatures(_img); //原圖的特徵向量,需要再計算翻轉圖的特徵向量 Mat flipImg; flip(_img, flipImg, 0); //上下翻轉 vector<float> result2 = getLastLayerFeatures(flipImg); for (int i = 0; i<result2.size(); ++i) result1.push_back(result2[i]); return result1; } vector<float> getLastLayerFeatures(const Mat& _img){ //求一張圖片經過最後一層(fc5)的特徵向量 Mat img = _img.clone(); img.convertTo(img, CV_32FC3); //轉為浮點圖 Blob<float>* inputBlob = _net->input_blobs()[0]; int width = inputBlob->width(); //網路規定的輸入圖片的寬度和高度 int height = inputBlob->height(); resize(img, img, Size(width, height)); //將測試圖片進行調整大小 img = (img - 127.5)*0.0078125; //減均值,再縮放到-1 到 1 float* data = inputBlob->mutable_cpu_data(); //將圖片的畫素值,複製進網路的輸入Blob for (int k = 0; k<3; ++k){ for (int i = 0; i<height; ++i){ for (int j = 0; j<width; ++j){ int index = (k*height + i)*width + j; //獲取偏移量 data[index] = img.at<Vec3f>(i, j)[k]; } } } vector<Blob<float>* > inputs(1, inputBlob); const vector<Blob<float>* >& outputBlobs = _net->Forward(inputs); //進行前向傳播,並返回最後一層的blob Blob<float>* outputBlob = outputBlobs[0]; //輸出blob const float* value = outputBlob->cpu_data(); vector<float> result; for (int i = 0; i<outputBlob->count(); ++i) //將輸出blob裡的特徵數值,拷貝到vector裡並返回 result.push_back(value[i]); return result; } };
程式碼進行了大量的註釋,且大量中間過程,都在私有函式裡,最終直接呼叫getSimilarity函式就能得到兩張圖片的相似度。
還有一點需要注意的是,因為程式碼中,強調的是輸出最後一層的輸出值,所以我們的deploy.prototxt檔案,要把softmax層以及accuracy層什麼的都註釋掉,直到最後一個我們想輸出的層,比如我們這裡是要得到fc5的輸出值,則要把fc5層的後面的都註釋掉。具體檔案可以參看作者的GitHub。
部分效果如下
我們這裡使用的人臉檢測以及人臉對齊的方法是上上篇部落格中的方法,MTCNN,論文是
然後,訓練人臉圖片用到神經網路以及方法(center loss)以及GitHub分別是
ECCV16 《A Discriminative Feature Learning Approach for Deep Face Recognition》