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

阅读: 评论:0

OpenCV⼈脸识别LBPH算法源码分析
1 背景及理论基础
⼈脸识别是指将⼀个需要识别的⼈脸和⼈脸库中的某个⼈脸对应起来(类似于指纹识别),⽬的是完成识别功能,该术语需要和⼈脸检测进⾏区分,⼈脸检测是在⼀张图⽚中把⼈脸定位出来,完成的是搜寻的功能。从OpenCV2.4开始,加⼊了新的类FaceRecognizer,该类⽤于⼈脸识别,使⽤它可以⽅便地进⾏相关识别实验。
原始的LBP算⼦定义为在3*3的窗⼝内,以窗⼝中⼼像素为阈值,将相邻的8个像素的灰度值与其进⾏⽐较,若周围像素值⼤于或等于中⼼像素值,则该像素点的位置被标记为1,否则为0。这样,3*3邻域内的8个点经⽐较可产⽣8位⼆进制数(通常转换为⼗进制数即LBP码,共256种),即得到该窗⼝中⼼像素点的LBP值,并⽤这个值来反映该区域的纹理特征。如下图所⽰:
原始的LBP提出后,研究⼈员不断对其提出了各种改进和优化。
1.1 圆形LBP算⼦
基本的 LBP算⼦的最⼤缺陷在于它只覆盖了⼀个固定半径范围内的⼩区域,这显然不能满⾜不同尺⼨和频率纹理的需要。为了适应不同尺度的纹理特征,Ojala等对LBP算⼦进⾏了改进,将3×3邻域扩展到任意邻域,并⽤圆形邻域代替了正⽅形邻域,改进后的LBP算⼦允许在半径为R的圆形邻域内有任意多个像素点,从⽽得到了诸如半径为R的圆形区域内含有P个采样点的LBP算⼦,OpenCV中正是使⽤圆形LBP算⼦,下图⽰意了圆形LBP算⼦:
1.2 旋转不变模式
从LBP的定义可以看出,LBP算⼦是灰度不变的,但却不是旋转不变的,图像的旋转就会得到不同的LBP值。Maenpaa等⼈⼜将LBP算⼦进⾏了扩展,提出了具有旋转不变性的LBP算⼦,即不断旋转圆形邻域得到⼀系列初始定义的LBP值,取其最⼩值作为该邻域的LBP值。下图给出了求取旋转不变LBP的过程⽰意图,图中算⼦下⽅的数字表⽰该算⼦对应的LBP值,图中所⽰的8种LBP模式,经过旋转不变的处理,最终得到的具有旋转不变性的LBP值为15。也就是说,图中的8种LBP模式对应的旋转不变的LBP码值都是00001111。
1.3 等价模式
⼀个LBP算⼦可以产⽣不同的⼆进制模式,对于半径为R的圆形区域内含有P个采样点的LBP算⼦将会产⽣P2种模式。很显然,随着邻域集内采样点数的增加,⼆进制模式的种类是急剧增加的。例如:5×5邻域内20个采样点,有220=1,048,576种⼆进制模式。如此多的⼆值模式⽆论对于纹理的提取还是对于纹理的识别、分类及信息的存取都是不利的。为了解决⼆进制模式过多的问题,提⾼统计性,Ojala提出了采⽤⼀种“等价模式”(Uniform Pattern)来对LBP算⼦的模式种类进⾏降维。Ojala等认为,在实际图像中,绝⼤多数LBP模式最多只包含两次从1到0或从0到1的跳变。因此,Ojala将“等价模式”定义为:当某个局部⼆进制模式所对应的循环⼆进制数从0到1或从1到0最多有两次跳变时,该局部⼆进制模式所对应的⼆进制就成为⼀个等价模式类。如00000000(0次跳变),00000111(含⼀次从0到1的跳变和⼀次1到0的跳变),10001111(先由1跳到0,再由0跳到1,共两次跳变)都是等价模式类。除等价模式类以外的模式都归为另⼀类,称为混合模式类,例如10010111(共四次跳变)。
通过这样的改进,⼆进制模式的种类⼤⼤减少,模式数量由原来的2P种减少为P(P-1)+2+1种,其中P表⽰邻域集内的采样点数,等价模式类包含P(P-1)+2种模式,混合模式类只有1种模式。对于3×3邻域内8个采样点来说,⼆进制模式由原始的256种减少为59种,这使得特征向量的维数更少,并且可以减少⾼频噪声带来的影响。
2 LBP特征⽤于检测的原理
显⽽易见的是,上述提取的LBP算⼦在每个像素点都可以得到⼀个LBP“编码”,那么,对⼀幅图像(记录的是每个像素点的灰度值)提取其原始的LBP算⼦之后,得到的原始LBP特征依然是“⼀幅图⽚”(记录的是每个像素点的LBP值),如图所⽰:
如果将以上得到的LBP图直接⽤于⼈脸识别,其实和不提取LBP特征没什么区别,在实际的LBP应⽤中⼀般采⽤LBP特征谱的统计直⽅图作为特征向量进⾏分类识别,并且可以将⼀幅图⽚划分为若⼲的⼦区域,对每个⼦区域内的每个像素点都提取LBP特征,然后,在每个⼦区域内建⽴LBP特征的统计直⽅图。如此⼀来,每个⼦区域,就可以⽤⼀个统计直⽅图来进⾏描述,整个图⽚就由若⼲个统计直⽅图组成,这样做的好处是在⼀定范围内减⼩图像没完全对准⽽产⽣的误差,分区的另外⼀个意义在
于我们可以根据不同的⼦区域给予不同的权重,⽐如说我们认为中⼼部分分区的权重⼤于边缘部分分区的权重,意思就是说中⼼部分在进⾏图⽚匹配识别时的意义更为重⼤。例如:⼀幅100*100像素⼤⼩的图⽚,划分为10*10=100个⼦区域(可以通过多种⽅式来划分区域),每个⼦区域的⼤⼩为10*10像素;在每个⼦区域内的每个像素点,提取其LBP特征,然后,建⽴统计直⽅图;这样,这幅图⽚就有10*10个⼦区域,也就有了10*10个统计直⽅图,利⽤这10*10个统计直⽅图,就可以描述这幅图⽚了。之后,我们利⽤各种相似性度量函数,就可以判断两幅图像之间的相似性了,OpenCV在LBP⼈脸识别中使⽤的是如下相似度公式:
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,
2int grid_x=8, int grid_y=8, double threshold = DBL_MAX);
3
4 Ptr<FaceRecognizer> createLBPHFaceRecognizer(int radius, int neighbors,
5int grid_x, int grid_y, double threshold)
6 {
7return new LBPH(radius, neighbors, grid_x, grid_y, threshold);
8 }
由代码可见LBPH使⽤圆形LBP算⼦,默认情况下,圆的半径是1,采样点P为8,x⽅向和y⽅向上的分区个数都为8,即有8*8=64个分区,最后⼀个参数为相似度阈值,待识别图像也图像库中图像相似度⼩于该值时才会产⽣匹配结果。对于LBPH类我们⾸先看⼀下其训练过程函数train:
1void LBPH::train(InputArrayOfArrays _in_src, InputArray _in_labels, bool preserveData) {
2if(_in_src.kind() != _InputArray::STD_VECTOR_MAT && _in_src.kind() != _InputArray::STD_VECT
OR_VECTOR) {
3string 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    }
6if(_al() == 0) {
7string 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(_Mat().type() != CV_32SC1) {
10string error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _pe());
11        CV_Error(CV_StsUnsupportedFormat, error_message);
12    }
13// get the vector of matrices
14    vector<Mat> src;
15    _MatVector(src);
16// get the label matrix
17    Mat labels = _Mat();
18// check if data is well- aligned
al() != src.size()) {
20string error_message = format("The number of samples (src) must equal the number of labels (labels). Was len(samples)=%d, len(labels)=%d.", src.size(), _al());
21        CV_Error(CV_StsBadArg, error_message);
22    }
23// if this model should be trained without preserving old data, delete old model data
24if(!preserveData) {
25        _lease();
26        _histograms.clear();
27    }
28// append labels to _labels matrix
29for(size_t labelIdx = 0; labelIdx < al(); labelIdx++) {
30        _labels.push_back(labels.at<int>((int)labelIdx));
31    }
32// store the spatial histograms of the original data
33for(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 */
42true);
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 = _Mat();
5// allocate memory for result
6    _ws-2*radius, ls-2*radius, CV_32SC1);
7    Mat dst = _Mat();
8// zero
9    dst.setTo(0);
10for(int n=0; n<neighbors; n++) {
11// sample points
12float x = static_cast<float>(radius * cos(2.0*CV_PI*n/static_cast<float>(neighbors)));
13float y = static_cast<float>(-radius * sin(2.0*CV_PI*n/static_cast<float>(neighbors)));
14// relative indices
15int fx = static_cast<int>(floor(x));
16int fy = static_cast<int>(floor(y));
17int cx = static_cast<int>(ceil(x));
18int cy = static_cast<int>(ceil(y));
19// fractional part
20float ty = y - fy;
21float tx = x - fx;
22// set interpolation weights
23float w1 = (1 - tx) * (1 - ty);
24float w2 =      tx  * (1 - ty);
25float w3 = (1 - tx) *      ty;
26float w4 =      tx  *      ty;
27// iterate through your data
28for(int i=radius; i < ws-radius;i++) {
29for(int j=radius;j < ls-radius;j++) {
30// calculate interpolated value
31float 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
39static void elbp(InputArray src, OutputArray dst, int radius, int neighbors)
40 {
41int type = pe();
42switch (type) {
43case CV_8SC1:  elbp_<char>(src,dst, radius, neighbors); break;
44case CV_8UC1:  elbp_<unsigned char>(src, dst, radius, neighbors); break;
45case CV_16SC1:  elbp_<short>(src,dst, radius, neighbors); break;
46case CV_16UC1:  elbp_<unsigned short>(src,dst, radius, neighbors); break;
47case CV_32SC1:  elbp_<int>(src,dst, radius, neighbors); break;
48case CV_32FC1:  elbp_<float>(src,dst, radius, neighbors); break;
49case CV_64FC1:  elbp_<double>(src,dst, radius, neighbors); break;
50default:
51string 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);水压力传感器
53break;
54    }
55 }
56
57static 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.
62int histSize = maxVal-minVal+1;
63// Set the ranges.
64float range[] = { static_cast<float>(minVal), static_cast<float>(maxVal+1) };
65const float* histRange = { range };
66// calc histogram
67    calcHist(&src, 1, 0, Mat(), result, 1, &histSize, &histRange, true, false);
68// normalize
69if(normed) {
70        result /= (al();
71    }
shape(1,1);
73 }
74
75static Mat histc(InputArray _src, int minVal, int maxVal, bool normed)
76 {
77    Mat src = _Mat();
78switch (pe()) {
79case CV_8SC1:
80return histc_(Mat_<float>(src), minVal, maxVal, normed);
81break;
82case CV_8UC1:
83return histc_(src, minVal, maxVal, normed);
84break;
85case CV_16SC1:
86return histc_(Mat_<float>(src), minVal, maxVal, normed);
87break;
88case CV_16UC1:
89return histc_(src, minVal, maxVal, normed);
90break;
91case CV_32SC1:
92return histc_(Mat_<float>(src), minVal, maxVal, normed);
93break;
94case CV_32FC1:
95return histc_(src, minVal, maxVal, normed);
96break;
97default:
98            CV_Error(CV_StsUnmatchedFormats, "This type is not implemented yet."); break;
99    }
100return Mat();
101 }
102
103
104static Mat spatial_histogram(InputArray _src, int numPatterns,
105int grid_x, int grid_y, bool/*normed*/)
106 {
107    Mat src = _Mat();
108// calculate LBP patch size
109int width = ls/grid_x;
110int height = ws/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
pty())
shape(1,1);
116// initial result_row
117int resultRowIdx = 0;
毒死蜱颗粒剂118// iterate through grid
119for(int i = 0; i < grid_y; i++) {
120for(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 = w(resultRowIdx);
125            shape(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
shape(1,1);
132 }
133
134//------------------------------------------------------------------------------电玉粉
135// wrapper to cv::elbp (extended local binary patterns)
136//------------------------------------------------------------------------------
137
138static Mat elbp(InputArray src, int radius, int neighbors) {
139    Mat dst;
140    elbp(src, dst, radius, neighbors);
141return dst;
142 }
elbp和spatial_histogram
需要注意的是在求图像中每个位置的8个采样点的值时,是使⽤的采样点四个⾓上相应位置的加权平均值才作为采样点的值(见上⾯函数elbp_中12~35⾏处代码),这样做能降低噪⾳点对LBP值的影响。⽽spatial_histogram函数把最后的分区直⽅图结果reshape成⼀⾏,这样做能⽅便识别时的相似度计算。识别函数有predict函数实现,源代码如下:
1void LBPH::predict(InputArray _src, int &minClass, double &minDist) const {
2if(_pty()) {
3// throw error if no data (or simply return -1?)
4string 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 = _Mat();
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 */
15true/* normed histograms */);
16// find 1-nearest neighbor
17    minDist = DBL_MAX;
18    minClass = -1;
19for(size_t sampleIdx = 0; sampleIdx < _histograms.size(); sampleIdx++) {
20double dist = compareHist(_histograms[sampleIdx], query, CV_COMP_CHISQR);
21if((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阈值则识别失败。
4 LBP⼈脸识别⽰例
最后给出LBP⼈脸识别的⽰例代码,代码中使⽤的⼈脸库是AT&T⼈脸库(⼜称ORL⼈脸数据库),库中有40个⼈,每⼈10张照⽚,共400张⼈脸照⽚。⽰例代码如下:
1 #include "opencv2/core/core.hpp"
2 #include "opencv2/highgui/highgui.hpp"
3 #include "opencv2/contrib/contrib.hpp"
4
5#define CV_VERSION_ID      CVAUX_STR(CV_MAJOR_VERSION) CVAUX_STR(CV_MINOR_VERSION) CVAUX_STR(CV_SUBMINOR_VERSION)
6
7 #ifdef _DEBUG
8#define cvLIB(name) "opencv_" name CV_VERSION_ID "d"
9#else
10#define cvLIB(name) "opencv_" name CV_VERSION_ID
11#endif
12
13#pragma comment( lib, cvLIB("core") )
14#pragma comment( lib, cvLIB("imgproc") )
15#pragma comment( lib, cvLIB("highgui") )
16#pragma comment( lib, cvLIB("flann") )
17#pragma comment( lib, cvLIB("features2d") )
18#pragma comment( lib, cvLIB("calib3d") )
19#pragma comment( lib, cvLIB("gpu") )
20#pragma comment( lib, cvLIB("legacy") )
21#pragma comment( lib, cvLIB("ml") )
22#pragma comment( lib, cvLIB("objdetect") )
nbva23#pragma comment( lib, cvLIB("ts") )
24#pragma comment( lib, cvLIB("video") )
25#pragma comment( lib, cvLIB("contrib") )
26#pragma comment( lib, cvLIB("nonfree") )
27
28 #include <iostream>
29 #include <fstream>
30 #include <sstream>
31
32using namespace cv;
33using namespace std;
34
35static void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator =';') {
36    std::ifstream file(filename.c_str(), ifstream::in);
37if (!file) {
38string error_message ="No valid input file was given, please check the given filename.";
39        CV_Error(CV_StsBadArg, error_message);
40    }
41string line, path, classlabel;
42while (getline(file, line)) {
43        stringstream liness(line);
44        getline(liness, path, separator);
45        getline(liness, classlabel);
46if(!pty()&&!pty()) {
47            images.push_back(imread(path, 0));
48            labels.push_back(atoi(classlabel.c_str()));
49        }
50    }
51 }
52
53int main(int argc, const char *argv[]) {
54if (argc !=2) {
55        cout <<"usage: "<< argv[0]<<" &>"<< endl;
56        exit(1);
57    }
58string fn_csv = string(argv[1]);
59    vector<Mat> images;
60    vector<int> labels;
61try {
62        read_csv(fn_csv, images, labels);
63    } catch (cv::Exception& e) {
64        cerr <<"Error opening file "<< fn_csv <<". Reason: "<< e.msg << endl;
65// nothing more we can do
66        exit(1);
67    }
68if(images.size()<=1) {
69string error_message ="This demo needs at least 2 images to work. Please add more images to your data set!";
70        CV_Error(CV_StsError, error_message);
71    }
72int height = images[0].rows;
73    Mat testSample = images[images.size() -1];
74int testLabel = labels[labels.size() -1];
75    images.pop_back();
76    labels.pop_back();
77// TLBPHFaceRecognizer 使⽤了扩展的LBP
78// 在其他的算⼦中他可能很容易被扩展
79// 下⾯是默认参数
80//      radius = 1
81//      neighbors = 8
82//      grid_x = 8
83//      grid_y = 8
84//
85// 如果你要创建 LBPH FaceRecognizer 半径是2,16个邻域
86//      cv::createLBPHFaceRecognizer(2, 16);
87//
88// 如果你需要⼀个阈值,并且使⽤默认参数:
89//      cv::createLBPHFaceRecognizer(1,8,8,8,123.0)
90//
91    Ptr<FaceRecognizer> model = createLBPHFaceRecognizer();
92    model->train(images, labels);
93int predictedLabel = model->predict(testSample);
94//      int predictedLabel = -1;
95//      double confidence = 0.0;
96//      model->predict(testSample, predictedLabel, confidence);
97//
98string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);
99    cout << result_message << endl;
100// 有时你需要设置或者获取内部数据模型,
101// 他不能被暴露在 cv::FaceRecognizer类中.
102//
103// ⾸先我们对FaceRecognizer的阈值设置到0.0,⽽不是重写训练模型
104// 当你重新估计模型时很重要
105//
106    model->set("threshold",0.0);
107    predictedLabel = model->predict(testSample);
108    cout <<"Predicted class = "<< predictedLabel << endl;
109// 由于确保⾼效率,LBP图没有被存储在模型⾥⾯。
110    cout <<"Model Information:"<< endl;
111string model_info = format("tLBPH(radius=%i, neighbors=%i, grid_x=%i, grid_y=%i, threshold=%.2f)",
112        model->getInt("radius"),
U盘笔113        model->getInt("neighbors"),

本文发布于:2023-07-22 08:14:35,感谢您对本站的认可!

本文链接:https://patent.en369.cn/patent/3/187639.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:模式   识别   图像   旋转   进制   邻域   函数   区域
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 369专利查询检索平台 豫ICP备2021025688号-20 网站地图