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阈值则识别失败。