LBP特征原理

Posted --Allen--

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LBP特征原理相关的知识,希望对你有一定的参考价值。

来源:http://blog.csdn.net/quincuntial/article/details/50541815

一、LBP特征的背景介绍

LBP指局部二值模式,英文全称:Local Binary Pattern,是一种用来描述图像局部特征的算子,LBP特征具有灰度不变性和旋转不变性等显著优点。它是由T. Ojala, M.Pietikäinen, 和 D. Harwood [1][2]在1994年提出,由于LBP特征计算简单、效果较好,因此LBP特征在计算机视觉的许多领域都得到了广泛的应用,LBP特征比较出名的应用是用在人脸识别和目标检测中,在计算机视觉开源库OpenCV中有使用LBP特征进行人脸识别的接口,也有用LBP特征训练目标检测分类器的方法,Opencv实现了LBP特征的计算,但没有提供一个单独的计算LBP特征的接口。

二、LBP特征的原理

1、原始LBP特征描述及计算方法

原始的LBP算子定义在像素3*3的邻域内,以邻域中心像素为阈值,相邻的8个像素的灰度值与邻域中心的像素值进行比较,若周围像素大于中心像素值,则该像素点的位置被标记为1,否则为0。这样,3*3邻域内的8个点经过比较可产生8位二进制数,将这8位二进制数依次排列形成一个二进制数字,这个二进制数字就是中心像素的LBP值,LBP值共有 28 种可能,因此LBP值有256种。中心像素的LBP值反映了该像素周围区域的纹理信息。
备注:计算LBP特征的图像必须是灰度图,如果是彩色图,需要先转换成灰度图。
上述过程用图像表示为:


将上述过程用公式表示为:

(xc,yc) 为中心像素的坐标, p 为邻域的第p个像素, ip 为邻域像素的灰度值, ic 为中心像素的灰度值, s(x) 为符号函数

原始LBP特征计算代码(Opencv下):

//原始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;
        
    

测试结果:

2、LBP特征的改进版本

在原始的LBP特征提出以后,研究人员对LBP特征进行了很多的改进,因此产生了许多LBP的改进版本。

2.1 圆形LBP特征(Circular LBP or Extended LBP)

由于原始LBP特征使用的是固定邻域内的灰度值,因此当图像的尺度发生变化时,LBP特征的编码将会发生错误,LBP特征将不能正确的反映像素点周围的纹理信息,因此研究人员对其进行了改进[3]。基本的 LBP 算子的最大缺陷在于它只覆盖了一个固定半径范围内的小区域,这显然不能满足不同尺寸和频率纹理的需要。为了适应不同尺度的纹理特征,并达到灰度和旋转不变性的要求,Ojala 等对 LBP 算子进行了改进,将 3×3 邻域扩展到任意邻域,并用圆形邻域代替了正方形邻域,改进后的 LBP 算子允许在半径为 R 的圆形邻域内有任意多个像素点。从而得到了诸如半径为R的圆形区域内含有P个采样点的LBP算子:

这种LBP特征叫做Extended LBP,也叫Circular LBP。使用可变半径的圆对近邻像素进行编码,可以得到如下的近邻:

对于给定中心点 (xc,yc) ,其邻域像素位置为 (xp,yp) pP ,其采样点 (xp,yp) 用如下公式计算:

R 是采样半径,p是第 p 个采样点,P是采样数目。由于计算的值可能不是整数,即计算出来的点不在图像上,我们使用计算出来的点的插值点。目的的插值方法有很多,Opencv使用的是双线性插值,双线性插值的公式如下:

通过LBP特征的定义可以看出,LBP特征对光照变化是鲁棒的,其效果如下图所示:

//圆形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);
            
        
    

测试结果:
radius = 3,neighbors = 8

第三幅图像为radius = 3,neighbors = 8,第四幅图像为radius = 1,neighbors = 8,从实验结果可以看出,半径越小,图像纹理越精细

第三幅图像为radius = 3,neighbors = 8,第四幅图像为radius = 3,neighbors = 4,从实验结果可以看出,邻域数目越小,图像亮度越低,合理,因此4位的灰度值很小
由于我代码的问题,不能使neighbors >8,可改进

2.2 旋转不变LBP特征

从上面可以看出,上面的LBP特征具有灰度不变性,但还不具备旋转不变性,因此研究人员又在上面的基础上进行了扩展,提出了具有旋转不变性的LBP特征。

首先不断的旋转圆形邻域内的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;
        
    

测试结果:
radius = 3,neighbors = 8,最后一幅是旋转不变LBP特征

2.3 Uniform Pattern LBP特征

Uniform Pattern,也被称为等价模式或均匀模式,由于一个LBP特征有多种不同的二进制形式,对于半径为R的圆形区域内含有P个采样点的LBP算子将会产生2P种模式。很显然,随着邻域集内采样点数的增加,二进制模式的种类是以指数形式增加的。例如:5×5邻域内20个采样点,有220=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(共四次跳变)。通过这样的改进,二进制模式的种类大大减少,而不会丢失任何信息。模式数量由原来的2P种减少为 P ( P-1)+2种,其中P表示邻域集内的采样点数。对于3×3邻域内8个采样点来说,二进制模式由原始的256种减少为58种,即:它把值分为59类,58个uniform pattern为一类,其它的所有值为第59类。这样直方图从原来的256维变成59维。这使得特征向量的维数更少,并且可以减少高频噪声带来的影响。

具体实现:采样点数目为8个,即LBP特征值有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;

测试结果:
radius = 3,neighbors = 8,最后一幅是等价模式LBP特征

2.4 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代码:

//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);

效果图:
Block=3,即原始的LBP特征

Block=9

Block=15

到此为止,还没有结束,作者对得到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<fl

以上是关于LBP特征原理的主要内容,如果未能解决你的问题,请参考以下文章

LBP特征原理

Uniform Circular LBP人脸识别实现

LBP特征提取原理及代码实现

LBP等价模式

PCL:RadiusOutlierRemoval ❤️ 半径滤波

数字图像处理OpenCV3 扩展模块学习笔记