手机版
你好,游客 登录 注册
背景:
阅读新闻

OpenCV人脸识别LBPH算法源码分析

[日期:2016-07-13] 来源:Linux社区  作者:zhaoweiwei [字体: ]

3 LBPH人脸识别关键部分源码

以OpenCV2.4.9为例,LBPH类源码该文件——opencv2.4.9\sources\modules\contrib\src\facerec.cpp中,如LBPH类创建函数的声明及实现如下:

1 CV_EXPORTS_W Ptr<FaceRecognizer> createLBPHFaceRecognizer(int radius=1, int neighbors=8,
2                                                             int grid_x=8, int grid_y=8, double threshold = DBL_MAX);
3 
4 Ptr<FaceRecognizer> createLBPHFaceRecognizer(int radius, int neighbors,
5                                              int grid_x, int grid_y, double threshold)
6 {
7     return new LBPH(radius, neighbors, grid_x, grid_y, threshold);
8 }

由代码可见LBPH使用圆形LBP算子,默认情况下,圆的半径是1,采样点P为8,x方向和y方向上的分区个数都为8,即有8*8=64个分区,最后一个参数为相似度阈值,待识别图像也图像库中图像相似度小于该值时才会产生匹配结果。对于LBPH类我们首先看一下其训练过程函数train:

 1 void LBPH::train(InputArrayOfArrays _in_src, InputArray _in_labels, bool preserveData) {
 2     if(_in_src.kind() != _InputArray::STD_VECTOR_MAT && _in_src.kind() != _InputArray::STD_VECTOR_VECTOR) {
 3         string error_message = "The images are expected as InputArray::STD_VECTOR_MAT (a std::vector<Mat>) or _InputArray::STD_VECTOR_VECTOR (a std::vector< vector<...> >).";
 4         CV_Error(CV_StsBadArg, error_message);
 5     }
 6     if(_in_src.total() == 0) {
 7         string error_message = format("Empty training data was given. You'll need more than one sample to learn a model.");
 8         CV_Error(CV_StsUnsupportedFormat, error_message);
 9     } else if(_in_labels.getMat().type() != CV_32SC1) {
10         string error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _in_labels.type());
11         CV_Error(CV_StsUnsupportedFormat, error_message);
12     }
13     // get the vector of matrices
14     vector<Mat> src;
15     _in_src.getMatVector(src);
16     // get the label matrix
17     Mat labels = _in_labels.getMat();
18     // check if data is well- aligned
19     if(labels.total() != src.size()) {
20         string error_message = format("The number of samples (src) must equal the number of labels (labels). Was len(samples)=%d, len(labels)=%d.", src.size(), _labels.total());
21         CV_Error(CV_StsBadArg, error_message);
22     }
23     // if this model should be trained without preserving old data, delete old model data
24     if(!preserveData) {
25         _labels.release();
26         _histograms.clear();
27     }
28     // append labels to _labels matrix
29     for(size_t labelIdx = 0; labelIdx < labels.total(); labelIdx++) {
30         _labels.push_back(labels.at<int>((int)labelIdx));
31     }
32     // store the spatial histograms of the original data
33     for(size_t sampleIdx = 0; sampleIdx < src.size(); sampleIdx++) {
34         // calculate lbp image
35         Mat lbp_image = elbp(src[sampleIdx], _radius, _neighbors);
36         // get spatial histogram from this lbp image
37         Mat p = spatial_histogram(
38                 lbp_image, /* lbp_image */
39                 static_cast<int>(std::pow(2.0, static_cast<double>(_neighbors))), /* number of possible patterns */
40                 _grid_x, /* grid size x */
41                 _grid_y, /* grid size y */
42                 true);
43         // add to templates
44         _histograms.push_back(p);
45     }
46 }

参数_in_src为训练的人脸图像组数据(人脸库),_in_labels是对应的标签数组,这两个数组长度应保持一致,_in_src中两个人脸图像对应下标处的标签值如果相同则说明两个人脸是同一个人的人脸。函数最后的for循环实现训练的核心功能,elbp计算人脸库每一个人脸的lbp图像,spatial_histogram求每一个人脸lbp图像的分区直方图,_histograms保存相应的分区直方图,以上两个函数的实现如下:

 
  1 template <typename _Tp> static
  2 inline void elbp_(InputArray _src, OutputArray _dst, int radius, int neighbors) {
  3     //get matrices
  4     Mat src = _src.getMat();
  5     // allocate memory for result
  6     _dst.create(src.rows-2*radius, src.cols-2*radius, CV_32SC1);
  7     Mat dst = _dst.getMat();
  8     // zero
  9     dst.setTo(0);
 10     for(int n=0; n<neighbors; n++) {
 11         // sample points
 12         float x = static_cast<float>(radius * cos(2.0*CV_PI*n/static_cast<float>(neighbors)));
 13         float y = static_cast<float>(-radius * sin(2.0*CV_PI*n/static_cast<float>(neighbors)));
 14         // relative indices
 15         int fx = static_cast<int>(floor(x));
 16         int fy = static_cast<int>(floor(y));
 17         int cx = static_cast<int>(ceil(x));
 18         int cy = static_cast<int>(ceil(y));
 19         // fractional part
 20         float ty = y - fy;
 21         float tx = x - fx;
 22         // set interpolation weights
 23         float w1 = (1 - tx) * (1 - ty);
 24         float w2 =      tx  * (1 - ty);
 25         float w3 = (1 - tx) *      ty;
 26         float w4 =      tx  *      ty;
 27         // iterate through your data
 28         for(int i=radius; i < src.rows-radius;i++) {
 29             for(int j=radius;j < src.cols-radius;j++) {
 30                 // calculate interpolated value
 31                 float t = static_cast<float>(w1*src.at<_Tp>(i+fy,j+fx) + w2*src.at<_Tp>(i+fy,j+cx) + w3*src.at<_Tp>(i+cy,j+fx) + w4*src.at<_Tp>(i+cy,j+cx));
 32                 // floating point precision, so check some machine-dependent epsilon
 33                 dst.at<int>(i-radius,j-radius) += ((t > src.at<_Tp>(i,j)) || (std::abs(t-src.at<_Tp>(i,j)) < std::numeric_limits<float>::epsilon())) << n;
 34             }
 35         }
 36     }
 37 }
 38 
 39 static void elbp(InputArray src, OutputArray dst, int radius, int neighbors)
 40 {
 41     int type = src.type();
 42     switch (type) {
 43     case CV_8SC1:   elbp_<char>(src,dst, radius, neighbors); break;
 44     case CV_8UC1:   elbp_<unsigned char>(src, dst, radius, neighbors); break;
 45     case CV_16SC1:  elbp_<short>(src,dst, radius, neighbors); break;
 46     case CV_16UC1:  elbp_<unsigned short>(src,dst, radius, neighbors); break;
 47     case CV_32SC1:  elbp_<int>(src,dst, radius, neighbors); break;
 48     case CV_32FC1:  elbp_<float>(src,dst, radius, neighbors); break;
 49     case CV_64FC1:  elbp_<double>(src,dst, radius, neighbors); break;
 50     default:
 51         string error_msg = format("Using Original Local Binary Patterns for feature extraction only works on single-channel images (given %d). Please pass the image data as a grayscale image!", type);
 52         CV_Error(CV_StsNotImplemented, error_msg);
 53         break;
 54     }
 55 }
 56 
 57 static Mat
 58 histc_(const Mat& src, int minVal=0, int maxVal=255, bool normed=false)
 59 {
 60     Mat result;
 61     // Establish the number of bins.
 62     int histSize = maxVal-minVal+1;
 63     // Set the ranges.
 64     float range[] = { static_cast<float>(minVal), static_cast<float>(maxVal+1) };
 65     const float* histRange = { range };
 66     // calc histogram
 67     calcHist(&src, 1, 0, Mat(), result, 1, &histSize, &histRange, true, false);
 68     // normalize
 69     if(normed) {
 70         result /= (int)src.total();
 71     }
 72     return result.reshape(1,1);
 73 }
 74 
 75 static Mat histc(InputArray _src, int minVal, int maxVal, bool normed)
 76 {
 77     Mat src = _src.getMat();
 78     switch (src.type()) {
 79         case CV_8SC1:
 80             return histc_(Mat_<float>(src), minVal, maxVal, normed);
 81             break;
 82         case CV_8UC1:
 83             return histc_(src, minVal, maxVal, normed);
 84             break;
 85         case CV_16SC1:
 86             return histc_(Mat_<float>(src), minVal, maxVal, normed);
 87             break;
 88         case CV_16UC1:
 89             return histc_(src, minVal, maxVal, normed);
 90             break;
 91         case CV_32SC1:
 92             return histc_(Mat_<float>(src), minVal, maxVal, normed);
 93             break;
 94         case CV_32FC1:
 95             return histc_(src, minVal, maxVal, normed);
 96             break;
 97         default:
 98             CV_Error(CV_StsUnmatchedFormats, "This type is not implemented yet."); break;
 99     }
100     return Mat();
101 }
102 
103 
104 static Mat spatial_histogram(InputArray _src, int numPatterns,
105                              int grid_x, int grid_y, bool /*normed*/)
106 {
107     Mat src = _src.getMat();
108     // calculate LBP patch size
109     int width = src.cols/grid_x;
110     int height = src.rows/grid_y;
111     // allocate memory for the spatial histogram
112     Mat result = Mat::zeros(grid_x * grid_y, numPatterns, CV_32FC1);
113     // return matrix with zeros if no data was given
114     if(src.empty())
115         return result.reshape(1,1);
116     // initial result_row
117     int resultRowIdx = 0;
118     // iterate through grid
119     for(int i = 0; i < grid_y; i++) {
120         for(int j = 0; j < grid_x; j++) {
121             Mat src_cell = Mat(src, Range(i*height,(i+1)*height), Range(j*width,(j+1)*width));
122             Mat cell_hist = histc(src_cell, 0, (numPatterns-1), true);
123             // copy to the result matrix
124             Mat result_row = result.row(resultRowIdx);
125             cell_hist.reshape(1,1).convertTo(result_row, CV_32FC1);
126             // increase row count in result matrix
127             resultRowIdx++;
128         }
129     }
130     // return result as reshaped feature vector
131     return result.reshape(1,1);
132 }
133 
134 //------------------------------------------------------------------------------
135 // wrapper to cv::elbp (extended local binary patterns)
136 //------------------------------------------------------------------------------
137 
138 static Mat elbp(InputArray src, int radius, int neighbors) {
139     Mat dst;
140     elbp(src, dst, radius, neighbors);
141     return dst;
142 }

 需要注意的是在求图像中每个位置的8个采样点的值时,是使用的采样点四个角上相应位置的加权平均值才作为采样点的值(见上面函数elbp_中12~35行处代码),这样做能降低噪音点对LBP值的影响。而spatial_histogram函数把最后的分区直方图结果reshape成一行,这样做能方便识别时的相似度计算。识别函数有predict函数实现,源代码如下:

 1 void LBPH::predict(InputArray _src, int &minClass, double &minDist) const {
 2     if(_histograms.empty()) {
 3         // throw error if no data (or simply return -1?)
 4         string error_message = "This LBPH model is not computed yet. Did you call the train method?";
 5         CV_Error(CV_StsBadArg, error_message);
 6     }
 7     Mat src = _src.getMat();
 8     // get the spatial histogram from input image
 9     Mat lbp_image = elbp(src, _radius, _neighbors);
10     Mat query = spatial_histogram(
11             lbp_image, /* lbp_image */
12             static_cast<int>(std::pow(2.0, static_cast<double>(_neighbors))), /* number of possible patterns */
13             _grid_x, /* grid size x */
14             _grid_y, /* grid size y */
15             true /* normed histograms */);
16     // find 1-nearest neighbor
17     minDist = DBL_MAX;
18     minClass = -1;
19     for(size_t sampleIdx = 0; sampleIdx < _histograms.size(); sampleIdx++) {
20         double dist = compareHist(_histograms[sampleIdx], query, CV_COMP_CHISQR);
21         if((dist < minDist) && (dist < _threshold)) {
22             minDist = dist;
23             minClass = _labels.at<int>((int) sampleIdx);
24         }
25     }
26 }

函数中7~15行是计算带预测图片_src的分区直方图query,19~25行的for循环分别比较query和人脸库直方图数组_histograms中每一个直方图的相似度(比较方法正是CV_COMP_CHISQR),并把相似度最小的作为最终结果,该部分也可以看成创建LBPH类时threshold的作用,即相似度都不小于threshold阈值则识别失败。

linux
相关资讯       OpenCV人脸识别  LBPH算法源码