LBP指局部二值模式(Local Binary Pattern),是一种用来描述图像局部特征的算子,具有灰度不变性和旋转不变性等显著优点。由于该特征的简单易算性,虽然其总体效果不如Haar特征,但速度则快于Haar,所以也得到了广泛的使用。
原始的LBP算子定义在像素 3 ∗ 3 3*3 3∗3的邻域内,以邻域中心像素为阈值,相邻的8个像素的灰度值与邻域中心的像素值进行比较,若周围像素大于中心像素值,则该像素点的位置被标记为1,否则为0。这样, 3 ∗ 3 3*3 3∗3邻域内的8个点经过比较可产生8为二进制数,将这8位二进制数依次排列形成一个二进制数字,这个二进制数字就是中心像素的LBP值,LBP值共有 2 8 2^{8} 28种可能,因此LBP值有256种可能。中心像素的LBP值反映了该像素周围区域的纹理信息。
注意:计算LBP特征的图像必须是灰度图,如果是彩色图,需要先转换成灰度图,而且8位二进制数的读法应该以顺时针的方向读取。
如下图所示,左上角的1比中间的5小,所以在右边的3*3表格中标记为0,左边的9比中间的5大标记为1。其它的都依次类推。
基本的 LBP算子的最大缺陷在于它只覆盖了一个固定半径范围内的小区域,这显然不能满足不同尺寸和频率纹理的需要。为了适应不同尺度的纹理特征,并达到灰度级和旋转不变性的要求,Ojala等对 LBP算子进行了改进,将 3×3邻域扩展到任意邻域,并用圆形邻域代替了正方形邻域,改进后的 LBP算子允许在半径为 R的圆形邻域内有任意多个像素点。从而得到了诸如半径为R的圆形区域内含有P个采样点的LBP算子,表示为 L B P P R LBP^{R}_P LBPPR。
挑一个小的,5*5的邻域的拿出来分析。
上图内有8个黑色的采样点,每个采样点的坐标值可以通过下式计算:
x p = x c + Rcos ( 2 π p P ) y p = y c + Rsin ( 2 π p P ) \begin{array}{l} x_{p}=x_{c}+\operatorname{Rcos}\left(\frac{2 \pi p}{P}\right) \\ y_{p}=y_{c}+\operatorname{Rsin}\left(\frac{2 \pi p}{P}\right) \end{array} xp=xc+Rcos(P2πp)yp=yc+Rsin(P2πp)
其中中心的白色点位 ( x c , y c ) (x_c,y_c) (xc,yc),R是采样半径,p是第p个采样点,P是采样数目。
现在来解释一下上式的坐标是怎么得到的。下面的图中简单的解释了一下上式的公式。
如果近邻点不在整数位置上,就需要进行插值运算了。插值的方法有很多,这边使用双线性插值来得到该采样点的的像素值:
上图是几种不同半径,不同采样点的LBP算子。
插值运算可以参考这边博客:图像插值运算总结
LBP特征是灰度不变,但不是旋转不变的,同一幅图像,进行旋转以后,其特征将会有很大的差别,影响匹配的精度。Ojala在LBP算法上,进行改进,实现了具有旋转不变性的LBP的特征。
实现方法:不断旋转圆形邻域得到一系列初始定义的LBP值,取最小值作为该邻域的值。
如下图,从左上角开始按顺时针读数,下图旋转8次,有8个不同的LBP值,最小的一个位15,取最小值作为该邻域的值。
一个LBP算子可以产生不同的二进制模式,对于半径为R的圆形区域内含有P个采样点的LBP算子将会产生种 2 P 2^{P} 2P模式。为了解决二进制模式过多的问题,提高统计性,Ojala提出了采用一种“等价模式”来对LBP算子的模式种类进行降维。在实际图像中,绝大多数LPB模式最多只包含两次从1到0或从0到1的跳变。
等价模式:当某个局部二进制模式所对应的循环二进制数从0到1或从1到0最多有两次跳变时,该局部二进制模式所对应的二进制就称为一个等价模式。
二进制序列中存在从1到0或者0到1的转变,可以称作是一次跳变。下面我们将举例说明跳变次数的计算:
二进制序列 | 跳变次数 |
---|---|
00000000、11111111 | 0 |
01000000 | 2 |
01001000 | 4 |
因为01001000这个序列跳变了4次,因此就不属于等价模式,而00000000、11111111、01000000跳变的次数小于等于2,因此属于等价模式。
除了等价模式之外的称为混合模式。也就是跳变次数大于2的。
改进后的LPB模式数由2 p ^{p} p(p为邻域集内的采集点数 ) 降维为 p ∗ ( p − 1 ) + 2 p*(p-1)+2 p∗(p−1)+2 。维数减少,可以降低高频噪声的影响。举一个8个采集点的例子。
0次跳变的有: 00000000、11111111 共计2个
2次跳变的有:
0次和两次的加起来是2+56=58种。公式是P*(P-1)种,只有具体怎么推导的看这:LBP, Uniform LBP 等价模式个数的推导
上述提取的LBP算子在每个像素点都可以得到一个LBP“编码”,那么,对一幅图像(记录的是每个像素点的灰度值)提取其原始的LBP算子之后,得到的原始LBP特征依然是“一幅图片”(记录的是每个像素点的LBP值)。BP的应用中,如纹理分类、人脸分析等,一般都不将LBP图谱作为特征向量用于分类识别,而是采用LBP特征谱的统计直方图作为特征向量用于分类识别。
例如:一幅100∗100100100100∗100像素大小的图片,划分为10∗10=1001010=10010∗10=100个子区域,也就有了10∗10101010∗10个统计直方图,利用这10∗10101010∗10个统计直方图,以及各种相似性度量函数,就可以判断两幅图像之间的相似性了。
读取图片并显示出来
#coding:utf-8
import cv2 as cv
# 读取原始图像
img= cv.imread('F:\\DataWhale\\timg.jpg')
cv.imshow("原始图片", img)
cv.waitKey(0)
face_detect = cv.CascadeClassifier("F:\\DataWhale\\lbpcascade_frontalface_improved.xml")
# 检测人脸
# 灰度处理
gray = cv.cvtColor(img, code=cv.COLOR_BGR2GRAY)
# 检查人脸 按照1.1倍放到 周围最小像素为5
face_zone = face_detect.detectMultiScale(gray, scaleFactor = 2, minNeighbors = 2) # maxSize = (55,55)
print ('识别人脸的信息:\n',face_zone)
for x, y, w, h in face_zone:
# 绘制矩形人脸区域
cv.rectangle(img, pt1 = (x, y), pt2 = (x+w, y+h), color = [0,0,255], thickness=2)
# 绘制圆形人脸区域 radius表示半径
cv.circle(img, center = (x + w//2, y + h//2), radius = w//2, color = [0,255,0], thickness = 2)
完整代码:
#coding:utf-8
import cv2 as cv
# 读取原始图像
img= cv.imread('F:\\DataWhale\\timg.jpg')
face_detect = cv.CascadeClassifier("F:\\DataWhale\\lbpcascade_frontalface_improved.xml")
# 检测人脸
# 灰度处理
gray = cv.cvtColor(img, code=cv.COLOR_BGR2GRAY)
# 检查人脸 按照1.1倍放到 周围最小像素为5
face_zone = face_detect.detectMultiScale(gray, scaleFactor = 2, minNeighbors = 2) # maxSize = (55,55)
print ('识别人脸的信息:\n',face_zone)
# 绘制矩形和圆形检测人脸
for x, y, w, h in face_zone:
# 绘制矩形人脸区域
cv.rectangle(img, pt1 = (x, y), pt2 = (x+w, y+h), color = [0,0,255], thickness=2)
# 绘制圆形人脸区域 radius表示半径
cv.circle(img, center = (x + w//2, y + h//2), radius = w//2, color = [0,255,0], thickness = 2)
# 设置图片可以手动调节大小
cv.namedWindow("Easmount-CSDN", 0)
# 显示图片
cv.imshow("Easmount-CSDN", img)
# 等待显示 设置任意键退出程序
cv.waitKey(0)
cv.destroyAllWindows()
uchar GetMinBinary(uchar *binary)
{
// 计算8个二进制
uchar LBPValue[8] = { 0 };
for (int i = 0; i <= 7; ++i)
{
LBPValue[0] += binary[i] << (7 - i);
LBPValue[1] += binary[(i + 7) % 8] << (7 - i);
LBPValue[2] += binary[(i + 6) % 8] << (7 - i);
LBPValue[3] += binary[(i + 5) % 8] << (7 - i);
LBPValue[4] += binary[(i + 4) % 8] << (7 - i);
LBPValue[5] += binary[(i + 3) % 8] << (7 - i);
LBPValue[6] += binary[(i + 2) % 8] << (7 - i);
LBPValue[7] += binary[(i + 1) % 8] << (7 - i);
}
// 选择最小的
uchar minValue = LBPValue[0];
for (int i = 1; i <= 7; ++i)
{
if (LBPValue[i] < minValue)
{
minValue = LBPValue[i];
}
}
return minValue;
}
//计算9种等价模式
int ComputeValue9(int value58)
{
int value9 = 0;
switch (value58)
{
case 1:
value9 = 1;
break;
case 2:
value9 = 2;
break;
case 4:
value9 = 3;
break;
case 7:
value9 = 4;
break;
case 11:
value9 = 5;
break;
case 16:
value9 = 6;
break;
case 22:
value9 = 7;
break;
case 29:
value9 = 8;
break;
case 58:
value9 = 9;
break;
}
return value9;
}
//灰度不变常规LBP(256)
void NormalLBPImage(const Mat &srcImage, Mat &LBPImage)
{
CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
LBPImage.create(srcImage.size(), srcImage.type());
Mat extendedImage;
copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_DEFAULT);
// 计算LBP特征图
int heightOfExtendedImage = extendedImage.rows;
int widthOfExtendedImage = extendedImage.cols;
int widthOfLBP = LBPImage.cols;
uchar *rowOfExtendedImage = extendedImage.data + widthOfExtendedImage + 1;
uchar *rowOfLBPImage = LBPImage.data;
for (int y = 1; y <= heightOfExtendedImage - 2; ++y, rowOfExtendedImage += widthOfExtendedImage, rowOfLBPImage += widthOfLBP)
{
// 列
uchar *colOfExtendedImage = rowOfExtendedImage;
uchar *colOfLBPImage = rowOfLBPImage;
for (int x = 1; x <= widthOfExtendedImage - 2; ++x, ++colOfExtendedImage, ++colOfLBPImage)
{
// 计算LBP值
int LBPValue = 0;
if (colOfExtendedImage[0 - widthOfExtendedImage - 1] >= colOfExtendedImage[0])
LBPValue += 128;
if (colOfExtendedImage[0 - widthOfExtendedImage] >= colOfExtendedImage[0])
LBPValue += 64;
if (colOfExtendedImage[0 - widthOfExtendedImage + 1] >= colOfExtendedImage[0])
LBPValue += 32;
if (colOfExtendedImage[0 + 1] >= colOfExtendedImage[0])
LBPValue += 16;
if (colOfExtendedImage[0 + widthOfExtendedImage + 1] >= colOfExtendedImage[0])
LBPValue += 8;
if (colOfExtendedImage[0 + widthOfExtendedImage] >= colOfExtendedImage[0])
LBPValue += 4;
if (colOfExtendedImage[0 + widthOfExtendedImage - 1] >= colOfExtendedImage[0])
LBPValue += 2;
if (colOfExtendedImage[0 - 1] >= colOfExtendedImage[0])
LBPValue += 1;
colOfLBPImage[0] = LBPValue;
}
}
}
// 等价灰度不变LBP(58)
void UniformNormalLBPImage(const Mat &srcImage, Mat &LBPImage)// 计算等价模式LBP特征图
{
// 参数检查,内存分配
CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
LBPImage.create(srcImage.size(), srcImage.type());
// 计算LBP图
// 扩充原图像边界,便于边界处理
Mat extendedImage;
copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_DEFAULT);
// 构建LBP 等价模式查找表
//int table[256];
//BuildUniformPatternTable(table);
// LUT(256种每一种模式对应的等价模式)
static const int table[256] = { 1, 2, 3, 4, 5, 0, 6, 7, 8, 0, 0, 0, 9, 0, 10, 11, 12, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 14, 0, 15, 16, 17, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 20, 0, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25,
0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 27, 0, 28, 29, 30, 31, 0, 32, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0
, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 36, 37, 38, 0, 39, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42
, 43, 44, 0, 45, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 47, 48, 49, 0, 50, 0, 0, 0, 51, 52, 53, 0, 54, 55, 56, 57, 58 };
// 计算LBP
int heightOfExtendedImage = extendedImage.rows;
int widthOfExtendedImage = extendedImage.cols;
int widthOfLBP = LBPImage.cols;
uchar *rowOfExtendedImage = extendedImage.data + widthOfExtendedImage + 1;
uchar *rowOfLBPImage = LBPImage.data;
for (int y = 1; y <= heightOfExtendedImage - 2; ++y, rowOfExtendedImage += widthOfExtendedImage, rowOfLBPImage += widthOfLBP)
{
// 列
uchar *colOfExtendedImage = rowOfExtendedImage;
uchar *colOfLBPImage = rowOfLBPImage;
for (int x = 1; x <= widthOfExtendedImage - 2; ++x, ++colOfExtendedImage, ++colOfLBPImage)
{
// 计算LBP值
int LBPValue = 0;
if (colOfExtendedImage[0 - widthOfExtendedImage - 1] >= colOfExtendedImage[0])
LBPValue += 128;
if (colOfExtendedImage[0 - widthOfExtendedImage] >= colOfExtendedImage[0])
LBPValue += 64;
if (colOfExtendedImage[0 - widthOfExtendedImage + 1] >= colOfExtendedImage[0])
LBPValue += 32;
if (colOfExtendedImage[0 + 1] >= colOfExtendedImage[0])
LBPValue += 16;
if (colOfExtendedImage[0 + widthOfExtendedImage + 1] >= colOfExtendedImage[0])
LBPValue += 8;
if (colOfExtendedImage[0 + widthOfExtendedImage] >= colOfExtendedImage[0])
LBPValue += 4;
if (colOfExtendedImage[0 + widthOfExtendedImage - 1] >= colOfExtendedImage[0])
LBPValue += 2;
if (colOfExtendedImage[0 - 1] >= colOfExtendedImage[0])
LBPValue += 1;
colOfLBPImage[0] = table[LBPValue];
}
}
}
// 等价旋转不变LBP(9)
void UniformRotInvLBPImage(const Mat &srcImage, Mat &LBPImage)
{
// 参数检查,内存分配
CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
LBPImage.create(srcImage.size(), srcImage.type());
// 扩充图像,处理边界情况
Mat extendedImage;
copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_DEFAULT);
// 构建LBP 等价模式查找表
//int table[256];
//BuildUniformPatternTable(table);
// 通过查找表
static const int table[256] = { 1, 2, 3, 4, 5, 0, 6, 7, 8, 0, 0, 0, 9, 0, 10, 11, 12, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 14, 0, 15, 16, 17, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 20, 0, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25,
0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 27, 0, 28, 29, 30, 31, 0, 32, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0
, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 36, 37, 38, 0, 39, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42
, 43, 44, 0, 45, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 47, 48, 49, 0, 50, 0, 0, 0, 51, 52, 53, 0, 54, 55, 56, 57, 58 };
uchar binary[8] = { 0 };// 记录每个像素的LBP值
int heigthOfExtendedImage = extendedImage.rows;
int widthOfExtendedImage = extendedImage.cols;
int widthOfLBPImage = LBPImage.cols;
uchar *rowOfExtendedImage = extendedImage.data + widthOfExtendedImage + 1;
uchar *rowOfLBPImage = LBPImage.data;
for (int y = 1; y <= heigthOfExtendedImage - 2; ++y, rowOfExtendedImage += widthOfExtendedImage, rowOfLBPImage += widthOfLBPImage)
{
// 列
uchar *colOfExtendedImage = rowOfExtendedImage;
uchar *colOfLBPImage = rowOfLBPImage;
for (int x = 1; x <= widthOfExtendedImage - 2; ++x, ++colOfExtendedImage, ++colOfLBPImage)
{
// 计算旋转不变LBP(最小的二进制模式)
binary[0] = colOfExtendedImage[0 - widthOfExtendedImage - 1] >= colOfExtendedImage[0] ? 1 : 0;
binary[1] = colOfExtendedImage[0 - widthOfExtendedImage] >= colOfExtendedImage[0] ? 1 : 0;
binary[2] = colOfExtendedImage[0 - widthOfExtendedImage + 1] >= colOfExtendedImage[0] ? 1 : 0;
binary[3] = colOfExtendedImage[0 + 1] >= colOfExtendedImage[0] ? 1 : 0;
binary[4] = colOfExtendedImage[0 + widthOfExtendedImage + 1] >= colOfExtendedImage[0] ? 1 : 0;
binary[5] = colOfExtendedImage[0 + widthOfExtendedImage] >= colOfExtendedImage[0] ? 1 : 0;
binary[6] = colOfExtendedImage[0 + widthOfExtendedImage - 1] >= colOfExtendedImage[0] ? 1 : 0;
binary[7] = colOfExtendedImage[0 - 1] >= colOfExtendedImage[0] ? 1 : 0;
int minValue = GetMinBinary(binary);
// 计算58种等价模式LBP
int value58 = table[minValue];
// 计算9种等价模式
colOfLBPImage[0] = ComputeValue9(value58);
}
}
}
//灰度不变常规LBP(256)特征
void NormalLBPFeature(const Mat &srcImage, Size cellSize, Mat &featureVector)
{
// 参数检查,内存分配
CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
Mat LBPImage;
NormalLBPImage(srcImage, LBPImage);
// 计算cell个数
int widthOfCell = cellSize.width;
int heightOfCell = cellSize.height;
int numberOfCell_X = srcImage.cols / widthOfCell;// X方向cell的个数
int numberOfCell_Y = srcImage.rows / heightOfCell;
// 特征向量的个数
int numberOfDimension = 256 * numberOfCell_X*numberOfCell_Y;
featureVector.create(1, numberOfDimension, CV_32FC1);
featureVector.setTo(Scalar(0));
// 计算LBP特征向量
int stepOfCell = srcImage.cols;
int pixelCount = cellSize.width*cellSize.height;
float *dataOfFeatureVector = (float *)featureVector.data;
// cell的特征向量在最终特征向量中的起始位置
int index = -256;
for (int y = 0; y <= numberOfCell_Y - 1; ++y)
{
for (int x = 0; x <= numberOfCell_X - 1; ++x)
{
index += 256;
// 计算每个cell的LBP直方图
Mat cell = LBPImage(Rect(x * widthOfCell, y * heightOfCell, widthOfCell, heightOfCell));
uchar *rowOfCell = cell.data;
for (int y_Cell = 0; y_Cell <= cell.rows - 1; ++y_Cell, rowOfCell += stepOfCell)
{
uchar *colOfCell = rowOfCell;
for (int x_Cell = 0; x_Cell <= cell.cols - 1; ++x_Cell, ++colOfCell)
{
++dataOfFeatureVector[index + colOfCell[0]];
}
}
// 一定要归一化!否则分类器计算误差很大
for (int i = 0; i <= 255; ++i)
dataOfFeatureVector[index + i] /= pixelCount;
}
}
}
// 等价灰度不变LBP(58)特征
void UniformNormalLBPFeature(const Mat &srcImage, Size cellSize, Mat &featureVector)
{
// 参数检查,内存分配
CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
Mat LBPImage;
UniformNormalLBPImage(srcImage, LBPImage);
// 计算cell个数
int widthOfCell = cellSize.width;
int heightOfCell = cellSize.height;
int numberOfCell_X = srcImage.cols / widthOfCell;// X方向cell的个数
int numberOfCell_Y = srcImage.rows / heightOfCell;
// 特征向量的个数
int numberOfDimension = 58 * numberOfCell_X*numberOfCell_Y;
featureVector.create(1, numberOfDimension, CV_32FC1);
featureVector.setTo(Scalar(0));
// 计算LBP特征向量
int stepOfCell = srcImage.cols;
int index = -58;// cell的特征向量在最终特征向量中的起始位置
float *dataOfFeatureVector = (float *)featureVector.data;
for (int y = 0; y <= numberOfCell_Y - 1; ++y)
{
for (int x = 0; x <= numberOfCell_X - 1; ++x)
{
index += 58;
// 计算每个cell的LBP直方图
Mat cell = LBPImage(Rect(x * widthOfCell, y * heightOfCell, widthOfCell, heightOfCell));
uchar *rowOfCell = cell.data;
int sum = 0; // 每个cell的等价模式总数
for (int y_Cell = 0; y_Cell <= cell.rows - 1; ++y_Cell, rowOfCell += stepOfCell)
{
uchar *colOfCell = rowOfCell;
for (int x_Cell = 0; x_Cell <= cell.cols - 1; ++x_Cell, ++colOfCell)
{
if (colOfCell[0] != 0)
{
// 在直方图中转化为0~57,所以是colOfCell[0] - 1
++dataOfFeatureVector[index + colOfCell[0] - 1];
++sum;
}
}
}
// 一定要归一化!否则分类器计算误差很大
for (int i = 0; i <= 57; ++i)
dataOfFeatureVector[index + i] /= sum;
}
}
}
// 等价旋转不变LBP(9)特征
void UniformRotInvLBPFeature(const Mat &srcImage, Size cellSize, Mat &featureVector)
{
// 参数检查,内存分配
CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
Mat LBPImage;
UniformRotInvLBPImage(srcImage, LBPImage);
// 计算cell个数
int widthOfCell = cellSize.width;
int heightOfCell = cellSize.height;
int numberOfCell_X = srcImage.cols / widthOfCell;// X方向cell的个数
int numberOfCell_Y = srcImage.rows / heightOfCell;
// 特征向量的个数
int numberOfDimension = 9 * numberOfCell_X*numberOfCell_Y;
featureVector.create(1, numberOfDimension, CV_32FC1);
featureVector.setTo(Scalar(0));
// 计算LBP特征向量
int stepOfCell = srcImage.cols;
int index = -9;// cell的特征向量在最终特征向量中的起始位置
float *dataOfFeatureVector = (float *)featureVector.data;
for (int y = 0; y <= numberOfCell_Y - 1; ++y)
{
for (int x = 0; x <= numberOfCell_X - 1; ++x)
{
index += 9;
// 计算每个cell的LBP直方图
Mat cell = LBPImage(Rect(x * widthOfCell, y * heightOfCell, widthOfCell, heightOfCell));
uchar *rowOfCell = cell.data;
int sum = 0; // 每个cell的等价模式总数
for (int y_Cell = 0; y_Cell <= cell.rows - 1; ++y_Cell, rowOfCell += stepOfCell)
{
uchar *colOfCell = rowOfCell;
for (int x_Cell = 0; x_Cell <= cell.cols - 1; ++x_Cell, ++colOfCell)
{
if (colOfCell[0] != 0)
{
// 在直方图中转化为0~8,所以是colOfCell[0] - 1
++dataOfFeatureVector[index + colOfCell[0] - 1];
++sum;
}
}
}
// 直方图归一化
for (int i = 0; i <= 8; ++i)
dataOfFeatureVector[index + i] /= sum;
}
}
}
参考文档:
LBP特征
LBP特征描述算子-人脸检测