参考https://blog.csdn.net/q1007729991/article/details/52995734 和https://blog.csdn.net/qq_37577735/article/details/80041586 这两篇博文,由这两篇描述知道,要了解LBP就要先学习插值,以双线性插值为例。

插值

首先看最简单的线性插值如下:
图像的LBP特征-LMLPHP
接下来就是将线性插值用在双线性插值中,如下:
图像的LBP特征-LMLPHP
图像的LBP特征-LMLPHP
图像的LBP特征-LMLPHP 由这个推导的最后结果就可与下面所述f(i+u,j+v)对上,说明推导正确。可以明显看到对某个像素点的插值结果其实就是附近4个相邻像素点的加权。图像的LBP特征-LMLPHP
到此,我们已明白双线性插值的意思。

LBP特征

接下来我们看LBP的原理,由文章开头发的链接中作者介绍可知:像素3*3的邻域内,以邻域中心像素为阈值,相邻的8个像素的灰度值与邻域中心的像素值进行比较,若周围像素大于中心像素值,则该像素点的位置被标记为1,否则为0。那么对于某个中心周围的8个邻域点,就有pow(2,8)=256种可能,取值范围就是[0,255],所以出来的LBP特征图就是一个灰度图。
图像的LBP特征-LMLPHP
可以由上图看到此时采样点组成了一个正方形,我觉得原始LBP可以叫做正方形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;
        }
    }
}

可以看到左上角的那个点放在最高位上,顺时针以此类推。这个原始LBP很好理解,不用赘述。但作者后面说的圆形LBP中说了两个词:半径radius和采样点neighbors:
图像的LBP特征-LMLPHP
可以看到上图的LBP的右上角值就是radius,右下角值就是neighbors。可以看到不一定要求neighbors=8*radius,而是**<=**即可。以radius=1,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-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那样使用正方形,这里是圆形就必须插值,如下图所示0、2、4、6位置是插值所得:
图像的LBP特征-LMLPHP
由上图还可以看到,原始LBP与圆形LBP的二进制放的起始位置变了,比如最高位原始LBP在左上角,但圆形LBP最高位在正下方。但没关系,8个采样点组成了圆形所以是符合原理的。
突然看到https://blog.csdn.net/xhdmtx_hcf/article/details/80060963 这里也介绍了圆形LBP,但这个作者给的elbp_函数中,却不对,以中心点(1,1)为例,radius=1,neighbors=8:

from function elbp_():
1,1 bitpos:0  biliear:2,1 - 2,1 - 2,1 - 2,1
1,1 bitpos:1  biliear:1,0 - 2,0 - 1,1 - 2,1
1,1 bitpos:2  biliear:1,0 - 2,0 - 1,0 - 2,0
1,1 bitpos:3  biliear:0,0 - 1,0 - 0,1 - 1,1
1,1 bitpos:4  biliear:0,0 - 0,0 - 0,1 - 0,1
1,1 bitpos:5  biliear:0,1 - 1,1 - 0,2 - 1,2
1,1 bitpos:6  biliear:0,2 - 1,2 - 0,2 - 1,2
1,1 bitpos:7  biliear:1,1 - 2,1 - 1,2 - 2,2

from function getCircularLBPFeature():
1,1 bitpos:7  biliear:1,2 - 1,2 - 1,2 - 1,2
1,1 bitpos:6  biliear:0,1 - 1,1 - 0,2 - 1,2
1,1 bitpos:5  biliear:0,1 - 0,1 - 0,1 - 0,1
1,1 bitpos:4  biliear:0,0 - 1,0 - 0,1 - 1,1
1,1 bitpos:3  biliear:1,0 - 1,0 - 1,0 - 1,0
1,1 bitpos:2  biliear:1,0 - 2,0 - 1,1 - 2,1
1,1 bitpos:1  biliear:2,1 - 2,1 - 2,1 - 2,1
1,1 bitpos:0  biliear:1,1 - 2,1 - 1,2 - 2,2

为什么elbp_函数中二进制位2的时候,怎么是由(1,0)、(2,0)这两个点插值的结果作为bitpos2的结果呢?!明显不对啊,然后这几个点画出来也不是圆形。再看getCircularLBPFeature()中二进制位2上的结果是由(1,0)、(2,0)、(1,1)、(2,1)插值得到的,就是符合我上面画的“圆形LBP”的图,的确组合成了圆形。

10-26 08:25