1. HOG 特征
int DataPrepare::getImageHogFeature(Mat &img, vector<float> & descriptors, Size & size)
{
if (img.data == NULL)
{
cout << "No exist" << endl;
return -1;
}
resize(img, img, size);
HOGDescriptor *hog = new HOGDescriptor(size, Size(16, 16), Size(4, 4), Size(8, 8), 9);
hog->compute(img, descriptors, Size(1, 1), Size(0, 0));
cout << "descriptors size is :" << descriptors.size() << endl;
return 0;
}
2.Sift 特征
int DataPrepare::getImageSiftFeature(cv::Mat &img, std::vector<float> & descriptors, cv::Size & size)
{
if (img.data == NULL)
{
cout << "No exist" << endl;
return -1;
}
resize(img, img, size);
Ptr<Feature2D> sift = xfeatures2d::SIFT::create(SIFT_NUM, 3, 0.01, 80);
std::vector<KeyPoint> keypointsa;
keypointsa.clear();
Mat a;//特征点描述
//sift->detectAndCompute(src, mask, keypointsa, a);//得到特征点和特征点描述
sift->detectAndCompute(img, Mat(), keypointsa, a);
cout << "a length is :" << a.rows << " " << a.cols << " " << a.channels() << endl;
a.reshape(1, SIFT_NUM * 128);
//cout << "a length is :" << a.rows << " " << a.cols << " " << a.channels() << endl;
cout << "descriptors shape is :" << descriptors.size() << endl;
}
3.LBP 特征
(多种 LBP特征, lbp_normal, lbp_circle, 旋转不变, 计算旋转不变 + 等价LBP特征)
int DataPrepare::getImageLBPFeature(Mat &img, vector<float> & descriptors, Size & size, int radias)
{
if (img.data == NULL)
{
cout << "No exist" << endl;
return -1;
}
resize(img, img, Size(size.width + radias * 2, size.height + radias * 2));
Mat dst = Mat(img.rows - 2 * radias, img.cols - 2 * radias, CV_8UC1, Scalar(0));;
//lbp_normal(img, dst);
int neighbors = 8;
int range = pow(2, neighbors);
lbp_circle(img, dst, radias, neighbors);
//int * map = (int *)malloc(sizeof(int)* range);
////rotation_invariant_mapping(dst, range, neighbors, map);
//rotation_uniform_invariant_mapping(dst, range, neighbors, map);
lbp_to_feature(dst, descriptors, 256);
//cout << "descriptors is :" << descriptors.size() << " "<< descriptors[0] << endl;
return 0;
}
void lbp_circle(Mat& src, Mat &dst, int radius, int neighbors)
{
for (int n = 0; n < neighbors; n++)
{
// 采样点的计算
float x = static_cast<float>(-radius * sin(2.0*CV_PI*n / static_cast<float>(neighbors)));
float y = static_cast<float>(radius * cos(2.0*CV_PI*n / static_cast<float>(neighbors)));
// 上取整和下取整的值
int fx = static_cast<int>(floor(x));
int fy = static_cast<int>(floor(y));
int cx = static_cast<int>(ceil(x));
int cy = static_cast<int>(ceil(y));
// 小数部分
float ty = y - fy;
float tx = x - fx;
// 设置插值权重
float w1 = (1 - tx) * (1 - ty);
float w2 = tx * (1 - ty);
float w3 = (1 - tx) * ty;
float w4 = tx * ty;
// 循环处理图像数据
for (int i = radius; i < src.rows - radius; i++)
{
for (int j = radius; j < src.cols - radius; j++)
{
// 计算插值
float t = static_cast<float>(w1*src.at<uchar>(i + fy, j + fx) + w2*src.at<uchar>(i + fy, j + cx) + w3*src.at<uchar>(i + cy, j + fx) + w4*src.at<uchar>(i + cy, j + cx));
// 进行编码 当t>=src(i,j)的时候取1,并进行相应的移位 避免 精度 损失:std::abs(t - src.at<uchar>(i, j)) < std::numeric_limits<float>::epsilon())
dst.at<uchar>(i - radius, j - radius) += ((t > src.at<uchar>(i, j)) || (std::abs(t - src.at<uchar>(i, j)) < std::numeric_limits<float>::epsilon())) << n;
}
}
}
}
//旋转不变 Mapping range = 2^neighbors
void rotation_invariant_mapping(Mat & src, int range, int neighbors, int *Mapping)
{
int newMax, rm, r;
int *tmpMap;
newMax = 0;
tmpMap = (int *)malloc(sizeof(int)*range);
memset(tmpMap, -1, sizeof(int)*range);
for (int i = 0; i < range; i++)
{
rm = i;
r = i;
for (int j = 0; j < neighbors - 1; j++)
{
//将r向左循环移动一位,当r超过num_sp位时,舍弃
r = r << 1;
if (r > range - 1)
{
r = r - (range - 1);
}
if (r < rm)
{
rm = r;
}
}
if (tmpMap[rm] < 0)
{
tmpMap[rm] = newMax;
newMax++;
}
Mapping[i] = tmpMap[rm];
}
for (int i = 0; i < src.cols; i++)
{
for (int j = 0; j < src.rows; j++)
{
src.at<uchar>(i, j) = Mapping[src.at<uchar>(i, j)];
}
}
free(tmpMap);
}
int calc_sum(int r)
{
int res_sum;
res_sum = 0;
while (r)
{
res_sum = res_sum + r % 2;
r /= 2;
}
return res_sum;
}
//计算旋转不变 + 等价LBP特征
void rotation_uniform_invariant_mapping(Mat & src, int range, int num_sp, int *Mapping)
{
int numt, i, j, tem_xor;
numt = 0;
tem_xor = 0;
for (i = 0; i < range; i++)
{
j = i << 1;
if (j > range - 1)
{
j = j - (range - 1);
}
tem_xor = i ^ j; // 异或
numt = calc_sum(tem_xor);//计算异或结果中1的个数,即跳变个数
if (numt <= 2)
{
Mapping[i] = calc_sum(i);
}
else {
Mapping[i] = num_sp + 1;
}
}
for (int i = 0; i < src.cols; i++)
{
for (int j = 0; j < src.rows; j++)
{
src.at<uchar>(i, j) = Mapping[src.at<uchar>(i, j)];
}
}
}
void lbp_normal(Mat& src, Mat &dst)
{
// 循环处理图像数据
for (int i = 1; i < src.rows - 1; i++)
{
for (int j = 1; j < src.cols - 1; j++)
{
uchar tt = 0;
int tt1 = 0;
uchar u = src.at<uchar>(i, j);
if (src.at<uchar>(i - 1, j - 1) > u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i - 1, j) > u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i - 1, j + 1) > u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i, j + 1) > u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i + 1, j + 1) > u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i + 1, j) > u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i + 1, j - 1) > u) { tt += 1 << tt1; }
tt1++;
if (src.at<uchar>(i - 1, j) > u) { tt += 1 << tt1; }
tt1++;
dst.at<uchar>(i - 1, j - 1) = tt;
}
}
}
void lbp_to_feature(Mat & src, vector<float> &descriptors, int num)
{
int rows = src.rows;
int cols = src.cols;
Rect block = Rect(0, 0, rows / 3, cols / 3);
Mat roi;
int fea[256] = { 0 };
for (int k = 0; k < 3; k++)
{
for (int n = 0; n < 3; n++)
{
block.x = k * rows / 3;
block.y = n * cols / 3;
roi = src(block);
fea[256] = { 0 };
for (int i = 0; i < roi.rows; i++)
{
for (int j = 0; j < roi.cols; j++)
{
fea[roi.at<uchar>(i, j)]++;
}
}
for (int i = 0; i < num; i++)
{
descriptors.push_back(fea[i] / (16 * 16.0));
}
}
}
}
更多 LBP特征获得方法
原始LBP
//原始LBP特征计算
template <typename _tp>
void getOriginLBPFeature(InputArray _src,OutputArray _dst)
{
Mat src = _src.getMat();
_dst.create(src.rows-2,src.cols-2,CV_8UC1);
Mat dst = _dst.getMat();
dst.setTo(0);
for(int i=1;i<src.rows-1;i++)
{
for(int j=1;j<src.cols-1;j++)
{
_tp center = src.at<_tp>(i,j);
unsigned char lbpCode = 0;
lbpCode |= (src.at<_tp>(i-1,j-1) > center) << 7;
lbpCode |= (src.at<_tp>(i-1,j ) > center) << 6;
lbpCode |= (src.at<_tp>(i-1,j+1) > center) << 5;
lbpCode |= (src.at<_tp>(i ,j+1) > center) << 4;
lbpCode |= (src.at<_tp>(i+1,j+1) > center) << 3;
lbpCode |= (src.at<_tp>(i+1,j ) > center) << 2;
lbpCode |= (src.at<_tp>(i+1,j-1) > center) << 1;
lbpCode |= (src.at<_tp>(i ,j-1) > center) << 0;
dst.at<uchar>(i-1,j-1) = lbpCode;
}
}
}
圆形LBP特征(Circular LBP or Extended LBP)
由于原始LBP特征使用的是固定邻域内的灰度值,因此当图像的尺度发生变化时,LBP特征的编码将会发生错误,LBP特征将不能正确的反映像素点周围的纹理信息,因此研究人员对其进行了改进[3]。基本的 LBP 算子的最大缺陷在于它只覆盖了一个固定半径范围内的小区域,这显然不能满足不同尺寸和频率纹理的需要。为了适应不同尺度的纹理特征,并达到灰度和旋转不变性的要求,Ojala 等对 LBP 算子进行了改进,将 3×3 邻域扩展到任意邻域,并用圆形邻域代替了正方形邻域,改进后的 LBP 算子允许在半径为 R 的圆形邻域内有任意多个像素点。
//圆形LBP特征计算,这种方法适于理解,但在效率上存在问题,声明时默认neighbors=8
template <typename _tp>
void getCircularLBPFeature(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
Mat src = _src.getMat();
//LBP特征图像的行数和列数的计算要准确
_dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
Mat dst = _dst.getMat();
dst.setTo(0);
//循环处理每个像素
for(int i=radius;i<src.rows-radius;i++)
{
for(int j=radius;j<src.cols-radius;j++)
{
//获得中心像素点的灰度值
_tp center = src.at<_tp>(i,j);
unsigned char lbpCode = 0;
for(int k=0;k<neighbors;k++)
{
//根据公式计算第k个采样点的坐标,这个地方可以优化,不必每次都进行计算radius*cos,radius*sin
float x = i + static_cast<float>(radius * \
cos(2.0 * CV_PI * k / neighbors));
float y = j - static_cast<float>(radius * \
sin(2.0 * CV_PI * k / neighbors));
//根据取整结果进行双线性插值,得到第k个采样点的灰度值
//1.分别对x,y进行上下取整
int x1 = static_cast<int>(floor(x));
int x2 = static_cast<int>(ceil(x));
int y1 = static_cast<int>(floor(y));
int y2 = static_cast<int>(ceil(y));
//2.计算四个点(x1,y1),(x1,y2),(x2,y1),(x2,y2)的权重
//下面的权重计算方式有个问题,如果四个点都相等,则权重全为0,计算出来的插值为0
//float w1 = (x2-x)*(y2-y); //(x1,y1)
//float w2 = (x2-x)*(y-y1); //(x1,y2)
//float w3 = (x-x1)*(y2-y); //(x2,y1)
//float w4 = (x-x1)*(y-y1); //(x2,y2)
//将坐标映射到0-1之间
float tx = x - x1;
float ty = y - y1;
//根据0-1之间的x,y的权重计算公式计算权重
float w1 = (1-tx) * (1-ty);
float w2 = tx * (1-ty);
float w3 = (1-tx) * ty;
float w4 = tx * ty;
//3.根据双线性插值公式计算第k个采样点的灰度值
float neighbor = src.at<_tp>(x1,y1) * w1 + src.at<_tp>(x1,y2) *w2 \
+ src.at<_tp>(x2,y1) * w3 +src.at<_tp>(x2,y2) *w4;
//通过比较获得LBP值,并按顺序排列起来
lbpCode |= (neighbor>center) <<(neighbors-k-1);
}
dst.at<uchar>(i-radius,j-radius) = lbpCode;
}
}
}
//圆形LBP特征计算,效率优化版本,声明时默认neighbors=8
template <typename _tp>
void getCircularLBPFeatureOptimization(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
Mat src = _src.getMat();
//LBP特征图像的行数和列数的计算要准确
_dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
Mat dst = _dst.getMat();
dst.setTo(0);
for(int k=0;k<neighbors;k++)
{
//计算采样点对于中心点坐标的偏移量rx,ry
float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
//为双线性插值做准备
//对采样点偏移量分别进行上下取整
int x1 = static_cast<int>(floor(rx));
int x2 = static_cast<int>(ceil(rx));
int y1 = static_cast<int>(floor(ry));
int y2 = static_cast<int>(ceil(ry));
//将坐标偏移量映射到0-1之间
float tx = rx - x1;
float ty = ry - y1;
//根据0-1之间的x,y的权重计算公式计算权重,权重与坐标具体位置无关,与坐标间的差值有关
float w1 = (1-tx) * (1-ty);
float w2 = tx * (1-ty);
float w3 = (1-tx) * ty;
float w4 = tx * ty;
//循环处理每个像素
for(int i=radius;i<src.rows-radius;i++)
{
for(int j=radius;j<src.cols-radius;j++)
{
//获得中心像素点的灰度值
_tp center = src.at<_tp>(i,j);
//根据双线性插值公式计算第k个采样点的灰度值
float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
+ src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
//LBP特征图像的每个邻居的LBP值累加,累加通过与操作完成,对应的LBP值通过移位取得
dst.at<uchar>(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
}
}
}
}
旋转不变LBP特征
从上面可以看出,上面的LBP特征具有灰度不变性,但还不具备旋转不变性,因此研究人员又在上面的基础上进行了扩展,提出了具有旋转不变性的LBP特征。
首先不断的旋转圆形邻域内的LBP特征,根据选择得到一系列的LBP特征值,从这些LBP特征值选择LBP特征值最小的作为中心像素点的LBP特征。具体做法如下图所示
//旋转不变圆形LBP特征计算,声明时默认neighbors=8
template <typename _tp>
void getRotationInvariantLBPFeature(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
Mat src = _src.getMat();
//LBP特征图像的行数和列数的计算要准确
_dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
Mat dst = _dst.getMat();
dst.setTo(0);
for(int k=0;k<neighbors;k++)
{
//计算采样点对于中心点坐标的偏移量rx,ry
float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
//为双线性插值做准备
//对采样点偏移量分别进行上下取整
int x1 = static_cast<int>(floor(rx));
int x2 = static_cast<int>(ceil(rx));
int y1 = static_cast<int>(floor(ry));
int y2 = static_cast<int>(ceil(ry));
//将坐标偏移量映射到0-1之间
float tx = rx - x1;
float ty = ry - y1;
//根据0-1之间的x,y的权重计算公式计算权重,权重与坐标具体位置无关,与坐标间的差值有关
float w1 = (1-tx) * (1-ty);
float w2 = tx * (1-ty);
float w3 = (1-tx) * ty;
float w4 = tx * ty;
//循环处理每个像素
for(int i=radius;i<src.rows-radius;i++)
{
for(int j=radius;j<src.cols-radius;j++)
{
//获得中心像素点的灰度值
_tp center = src.at<_tp>(i,j);
//根据双线性插值公式计算第k个采样点的灰度值
float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
+ src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
//LBP特征图像的每个邻居的LBP值累加,累加通过与操作完成,对应的LBP值通过移位取得
dst.at<uchar>(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
}
}
}
//进行旋转不变处理
for(int i=0;i<dst.rows;i++)
{
for(int j=0;j<dst.cols;j++)
{
unsigned char currentValue = dst.at<uchar>(i,j);
unsigned char minValue = currentValue;
for(int k=1;k<neighbors;k++)
{
//循环左移
unsigned char temp = (currentValue>>(neighbors-k)) | (currentValue<<k);
if(temp < minValue)
{
minValue = temp;
}
}
dst.at<uchar>(i,j) = minValue;
}
}
}
Uniform Pattern LBP特征
Uniform Pattern,也被称为等价模式或均匀模式,由于一个LBP特征有多种不同的二进制形式,对于半径为R的圆形区域内含有P个采样点的LBP算子将会产生 2P2P 种模式。很显然,随着邻域集内采样点数的增加,二进制模式的种类是以指数形式增加的。例如:5×5邻域内20个采样点,有 220220 =1,048,576种二进制模式。这么多的二进制模式不利于纹理的提取、分类、识别及存取。例如,将LBP算子用于纹理分类或人脸识别时,常采用LBP模式的统计直方图来表达图像的信息,而较多的模式种类将使得数据量过大,且直方图过于稀疏。因此,需要对原始的LBP模式进行降维,使得数据量减少的情况下能最好的表示图像的信息。为了解决二进制模式过多的问题,提高统计性,Ojala提出了采用一种“等价模式”(Uniform Pattern)来对LBP算子的模式种类进行降维。Ojala等认为,在实际图像中,绝大多数LBP模式最多只包含两次从1到0或从0到1的跳变。因此,Ojala将“等价模式”定义为:当某个LBP所对应的循环二进制数从0到1或从1到0最多有两次跳变时,该LBP所对应的二进制就称为一个等价模式类。如00000000(0次跳变),00000111(只含一次从0到1的跳变),10001111(先由1跳到0,再由0跳到1,共两次跳变)都是等价模式类。除等价模式类以外的模式都归为另一类,称为混合模式类,例如10010111(共四次跳变)。通过这样的改进,二进制模式的种类大大减少,而不会丢失任何信息。模式数量由原来的 2P2P 种减少为 P ( P-1)+2种,其中P表示邻域集内的采样点数。对于3×3邻域内8个采样点来说,二进制模式由原始的256种减少为58种,即:它把值分为59类,58个uniform pattern为一类,其它的所有值为第59类。这样直方图从原来的256维变成59维。这使得特征向量的维数更少,并且可以减少高频噪声带来的影响。
具体实现:采样点数目为8个,即LBP特征值有 28
28种,共256个值,正好对应灰度图像的0-255,因此原始的LBP特征图像是一幅正常的灰度图像,而等价模式LBP特征,根据0-1跳变次数,将这256个LBP特征值分为了59类,从跳变次数上划分:跳变0次—2个,跳变1次—0个,跳变2次—56个,跳变3次—0个,跳变4次—140个,跳变5次—0个,跳变6次—56个,跳变7次—0个,跳变8次—2个。共9种跳变情况,将这256个值进行分配,跳变小于2次的为等价模式类,共58个,他们对应的值按照从小到大分别编码为1—58,即它们在LBP特征图像中的灰度值为1—58,而除了等价模式类之外的混合模式类被编码为0,即它们在LBP特征中的灰度值为0,因此等价模式LBP特征图像整体偏暗。
//等价模式LBP特征计算
template <typename _tp>
void getUniformPatternLBPFeature(InputArray _src,OutputArray _dst,int radius,int neighbors)
{
Mat src = _src.getMat();
//LBP特征图像的行数和列数的计算要准确
_dst.create(src.rows-2*radius,src.cols-2*radius,CV_8UC1);
Mat dst = _dst.getMat();
dst.setTo(0);
//LBP特征值对应图像灰度编码表,直接默认采样点为8位
uchar temp = 1;
uchar table[256] = {0};
for(int i=0;i<256;i++)
{
if(getHopTimes(i)<3)
{
table[i] = temp;
temp++;
}
}
//是否进行UniformPattern编码的标志
bool flag = false;
//计算LBP特征图
for(int k=0;k<neighbors;k++)
{
if(k==neighbors-1)
{
flag = true;
}
//计算采样点对于中心点坐标的偏移量rx,ry
float rx = static_cast<float>(radius * cos(2.0 * CV_PI * k / neighbors));
float ry = -static_cast<float>(radius * sin(2.0 * CV_PI * k / neighbors));
//为双线性插值做准备
//对采样点偏移量分别进行上下取整
int x1 = static_cast<int>(floor(rx));
int x2 = static_cast<int>(ceil(rx));
int y1 = static_cast<int>(floor(ry));
int y2 = static_cast<int>(ceil(ry));
//将坐标偏移量映射到0-1之间
float tx = rx - x1;
float ty = ry - y1;
//根据0-1之间的x,y的权重计算公式计算权重,权重与坐标具体位置无关,与坐标间的差值有关
float w1 = (1-tx) * (1-ty);
float w2 = tx * (1-ty);
float w3 = (1-tx) * ty;
float w4 = tx * ty;
//循环处理每个像素
for(int i=radius;i<src.rows-radius;i++)
{
for(int j=radius;j<src.cols-radius;j++)
{
//获得中心像素点的灰度值
_tp center = src.at<_tp>(i,j);
//根据双线性插值公式计算第k个采样点的灰度值
float neighbor = src.at<_tp>(i+x1,j+y1) * w1 + src.at<_tp>(i+x1,j+y2) *w2 \
+ src.at<_tp>(i+x2,j+y1) * w3 +src.at<_tp>(i+x2,j+y2) *w4;
//LBP特征图像的每个邻居的LBP值累加,累加通过与操作完成,对应的LBP值通过移位取得
dst.at<uchar>(i-radius,j-radius) |= (neighbor>center) <<(neighbors-k-1);
//进行LBP特征的UniformPattern编码
if(flag)
{
dst.at<uchar>(i-radius,j-radius) = table[dst.at<uchar>(i-radius,j-radius)];
}
}
}
}
}
//计算跳变次数
int getHopTimes(int n)
{
int count = 0;
bitset<8> binaryCode = n;
for(int i=0;i<8;i++)
{
if(binaryCode[i] != binaryCode[(i+1)%8])
{
count++;
}
}
return count;
}
MB-LBP特征
MB-LBP特征,全称为Multiscale Block LBP,来源于论文[9],中科院的人发明的,在Traincascade级联目标训练检测中的LBP特征使用的就是MB-LBP。
MB-LBP的原理:
将图像分成一个个小块(Block),每个小块再分为一个个的小区域(类似于HOG中的cell),小区域内的灰度平均值作为当前小区域的灰度值,与周围小区域灰度进行比较形成LBP特征,生成的特征称为MB-LBP,Block大小为3*3,则小区域的大小为1,就是原始的LBP特征,上图的Block大小为9*9,小区域的大小为3*3。
不同Block提取的MB-LBP特征如图所示:
//MB-LBP特征的计算
void getMultiScaleBlockLBPFeature(InputArray _src,OutputArray _dst,int scale)
{
Mat src = _src.getMat();
Mat dst = _dst.getMat();
//定义并计算积分图像
int cellSize = scale / 3;
int offset = cellSize / 2;
Mat cellImage(src.rows-2*offset,src.cols-2*offset,CV_8UC1);
for(int i=offset;i<src.rows-offset;i++)
{
for(int j=offset;j<src.cols-offset;j++)
{
int temp = 0;
for(int m=-offset;m<offset+1;m++)
{
for(int n=-offset;n<offset+1;n++)
{
temp += src.at<uchar>(i+n,j+m);
}
}
temp /= (cellSize*cellSize);
cellImage.at<uchar>(i-cellSize/2,j-cellSize/2) = uchar(temp);
}
}
getOriginLBPFeature<uchar>(cellImage,dst);
}
到此为止,还没有结束,作者对得到LBP特征又进行了均值模式编码,通过对得到的特征图求直方图,得到了LBP特征值0-255之间(0-255即直方图中的bin)的特征数量,通过对bin中的数值进行排序,通过权衡,将排序在前63位的特征值看作是等价模式类,其他的为混合模式类,总共64类,作者在论文中称之为SEMB-LBP(Statistically Effective MB-LBP )。类似于等价模式LBP,等价模式的LBP的等价模式类为58种,混合模式类1种,共59种。二者除了等价模式类的数量不同之外,主要区别在于:对等价模式类的定义不同,等价模式LBP是根据0-1的跳变次数定义的,而SEMB-LBP是通过对直方图排序得到的。当然下一步要做的就是将SEMB-LBP变为LBPH进行使用。
计算SEMB-LBP的代码
//求SEMB-LBP
void SEMB_LBPFeature(InputArray _src,OutputArray _dst,int scale)
{
Mat dst=_dst.getMat();
Mat MB_LBPImage;
getMultiScaleBlockLBPFeature(_src,MB_LBPImage,scale);
//imshow("dst",dst);
Mat histMat;
int histSize = 256;
float range[] = {float(0),float(255)};
const float* ranges = {range};
//计算LBP特征值0-255的直方图
calcHist(&MB_LBPImage,1,0,Mat(),histMat,1,&histSize,&ranges,true,false);
histMat.reshape(1,1);
vector<float> histVector(histMat.rows*histMat.cols);
uchar table[256];
memset(table,64,256);
if(histMat.isContinuous())
{
//histVector = (int *)(histMat.data);
//将直方图histMat变为vector向量histVector
histVector.assign((float*)histMat.datastart,(float*)histMat.dataend);
vector<float> histVectorCopy(histVector);
//对histVector进行排序,即对LBP特征值的数量进行排序,降序排列
sort(histVector.begin(),histVector.end(),greater<float>());
for(int i=0;i<63;i++)
{
for(int j=0;j<histVectorCopy.size();j++)
{
if(histVectorCopy[j]==histVector[i])
{
//得到类似于Uniform的编码表
table[j]=i;
}
}
}
}
dst = MB_LBPImage;
//根据编码表得到SEMB-LBP
for(int i=0;i<dst.rows;i++)
{
for(int j=0;j<dst.cols;j++)
{
dst.at<uchar>(i,j) = table[dst.at<uchar>(i,j)];
}
}
}
LBPH——图像的LBP特征向量 (把LBP特征转化成特征向量)
LBPH,Local Binary Patterns Histograms,即LBP特征的统计直方图,LBPH将LBP特征与图像的空间信息结合在一起。这种表示方法由Ahonen等人在论文[3]中提出,他们将LBP特征图像分成m个局部块,并提取每个局部块的直方图,然后将这些直方图依次连接在一起形成LBP特征的统计直方图,即LBPH。
一幅图像具体的计算LBPH的过程(以Opencv中的人脸识别为例):
1. 计算图像的LBP特征图像,在上面已经讲过了。
2. 将LBP特征图像进行分块,Opencv中默认将LBP特征图像分成8行8列64块区域
3. 计算每块区域特征图像的直方图cell_LBPH,将直方图进行归一化,直方图大小为1*numPatterns
4. 将上面计算的每块区域特征图像的直方图按分块的空间顺序依次排列成一行,形成LBP特征向量,大小为1*(numPatterns*64)
5. 用机器学习的方法对LBP特征向量进行训练,用来检测和识别目标
举例说明LBPH的维度:
采样点为8个,如果用的是原始的LBP或Extended LBP特征,其LBP特征值的模式为256种,则一幅图像的LBP特征向量维度为:64*256=16384维,
而如果使用的UniformPatternLBP特征,其LBP值的模式为59种,其特征向量维度为:64*59=3776维,可以看出,使用等价模式特征,其特征向量的维度大大减少,
这意味着使用机器学习方法进行学习的时间将大大减少,而性能上没有受到很大影响。
Opencv的人脸识别使用的是Extended LBP
//计算LBP特征图像的直方图LBPH
Mat getLBPH(InputArray _src,int numPatterns,int grid_x,int grid_y,bool normed)
{
Mat src = _src.getMat();
int width = src.cols / grid_x;
int height = src.rows / grid_y;
//定义LBPH的行和列,grid_x*grid_y表示将图像分割成这么些块,numPatterns表示LBP值的模式种类
Mat result = Mat::zeros(grid_x * grid_y,numPatterns,CV_32FC1);
if(src.empty())
{
return result.reshape(1,1);
}
int resultRowIndex = 0;
//对图像进行分割,分割成grid_x*grid_y块,grid_x,grid_y默认为8
for(int i=0;i<grid_x;i++)
{
for(int j=0;j<grid_y;j++)
{
//图像分块
Mat src_cell = Mat(src,Range(i*height,(i+1)*height),Range(j*width,(j+1)*width));
//计算直方图
Mat hist_cell = getLocalRegionLBPH(src_cell,0,(numPattern-1),true);
//将直方图放到result中
Mat rowResult = result.row(resultRowIndex);
hist_cell.reshape(1,1).convertTo(rowResult,CV_32FC1);
resultRowIndex++;
}
}
return result.reshape(1,1);
}
//计算一个LBP特征图像块的直方图
Mat getLocalRegionLBPH(const Mat& src,int minValue,int maxValue,bool normed)
{
//定义存储直方图的矩阵
Mat result;
//计算得到直方图bin的数目,直方图数组的大小
int histSize = maxValue - minValue + 1;
//定义直方图每一维的bin的变化范围
float range[] = { static_cast<float>(minValue),static_cast<float>(maxValue + 1) };
//定义直方图所有bin的变化范围
const float* ranges = { range };
//计算直方图,src是要计算直方图的图像,1是要计算直方图的图像数目,0是计算直方图所用的图像的通道序号,从0索引
//Mat()是要用的掩模,result为输出的直方图,1为输出的直方图的维度,histSize直方图在每一维的变化范围
//ranges,所有直方图的变化范围(起点和终点)
calcHist(&src,1,0,Mat(),result,1,&histSize,&ranges,true,false);
//归一化
if(normed)
{
result /= (int)src.total();
}
//结果表示成只有1行的矩阵
return result.reshape(1,1);
}