LBP算法

    • 原始LBP算法
    • 圆形LBP
    • 旋转不变LBP
    • 等价LBP
    • 旋转不变等价LBP

局部二值模式(Local Binary Pattern,LBP)的基本思想是将中心像素点的灰度值作为阈值,将其邻域内的像素点灰度值与阈值进行比较,从而得到二进制编码用以表述局部纹理特征。

  • 优势

LBP表示方法不易受图像整体灰度线性变化的影响,当图像的灰度值发生线性均匀变化时,其LBP特征编码是不变的。LBP特征计算简单,表征能力强,在纹理特征描述上具有较好的效果。

  • 发展历史
  1. 原始的LPB
  2. 圆形LPB

为了解决不能自由更改尺寸的缺陷。

  1. 旋转不变的LPB

上面的LBP特征具有灰度不变性,但还不具备旋转不变性,此改进为了解决旋转不变性。

原始LBP算法

基本的LBP算子:3×3的矩形块,有1个中心像素和8个邻域像素分别对应9个灰度值。特征值:以中心像素的灰度值为阈值,将其邻域的8个灰度值与阈值比较,大于中心灰度值的像素用1表示,反之用0表示。然后根据顺时针方向读出8个二进制值。经阈值化后的二值矩阵可看成一个二值纹理模式,用来刻画邻域内像素点的灰度相对中心点的变化情况。

  • 手写代码
import matplotlib.pyplot as plt
import cv2 as cv
from skimage import data
img = data.coffee()
def lbp_basic(img):
    basic_array = np.zeros(img.shape,np.uint8)
    for i in range(basic_array.shape[0]-1):
        for j in range(basic_array.shape[1]-1):
            basic_array[i,j] = bin_to_decimal(cal_basic_lbp(img,i,j))
    return basic_array
def cal_basic_lbp(img,i,j):#比中心像素大的点赋值为1,比中心像素小的赋值为0,返回得到的二进制序列
    sum = []
    if img[i - 1, j ] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i - 1, j+1 ] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i , j + 1] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i + 1, j+1 ] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i + 1, j ] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i + 1, j - 1] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i , j - 1] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i - 1, j - 1] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    return sum
def bin_to_decimal(bin):#二进制转十进制
    res = 0
    bit_num = 0 #左移位数
    for i in bin[::-1]:
        res += i << bit_num   # 左移n位相当于乘以2的n次方
        bit_num += 1
    return res
def show_basic_hist(a): #画原始lbp的直方图      
    hist = cv.calcHist([a],[0],None,[256],[0,256])
    hist = cv.normalize(hist,hist)
    plt.figure(figsize = (8,4))
    plt.plot(hist, color='r')
    plt.xlim([0,256])
    plt.show()
img1 = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
basic_array = lbp_basic(img1)
show_basic_hist(basic_array)
plt.figure(figsize=(11,11))
plt.subplot(1,2,1)
plt.imshow(img1)
plt.subplot(1,2,2)
plt.imshow(basic_array,cmap='Greys_r')
plt.show()  


  • 调库
import skimage.feature
import skimage.segmentation
img_ku = skimage.feature.local_binary_pattern(img1,8,1.0,method='default')
img_ku = img_ku.astype(np.uint8)
hist = cv.calcHist([img_ku],[0],None,[256],[0,256])
hist = cv.normalize(hist,hist)
plt.plot(hist, color='r')
plt.xlim([0,256])
plt.show()
plt.imshow(img_ku,cmap='Greys_r')
plt.show()


  • 方法说明 skimage.feature.local_binary_pattern

skimage.feature.local_binary_pattern(image, P, R, method=‘default’)
1、image:灰度图像的像素矩阵
2、P:选取中心像素周围的像素点的个数
3、R:选取的区域的半径
4、method : {‘default’, ‘ror’, ‘uniform’, ‘var’}

default:原始的局部二值模式
参考地址

圆形LBP

圆形邻域的像素采样方式会比8-邻域的方式要更灵活,可以通过改变圆形的半径R来调整邻域大小。但半径R越大,采样点的数目P也越多。对于没有落到像素格子中央的点的灰度值,一般采用插值法得到。
除此之外,通过对采样角度设置更高的量化等级,可以得到更精细的角度分辨率。

LBP算子为:

旋转不变LBP

我们总是选择最右中间点作为起始点g0,所以当LBP算子旋转的时候,g0会发生变化,这样即使是同一个模板、同一个位置、同样的P、R,计算得到的LBP特征值都是不同的。为了消除这种旋转差异,重新定义了LBP计算方式:

其中ROR(x,i)指的是对p位数字x进行i次循环右移。也就是说,从各个旋转的LBP二进制串中,找到最小的值,作为这个模板的LBP特征。举个例子,假设P=8,R=1(8个点,半径1),那么对于4个连续的1,4个连续的0(00001111)来说,可以旋转的有:

显然最小的是15,所以这个模板的值就是15。

缺点:看起来圆形LBP很完美,但实际使用发现LBPROT并不具有很好地辨别力,因为随着采样点数的增加,二进制模式会急剧增多,会使得数据量过大,直方图过于稀疏,不能很好地反映图像特征。

  • python代码实现
from skimage import data
import cv2 as cv
import matplotlib.pyplot as plt
# revolve_map为旋转不变模式的36种特征值从小到大进行序列化编号得到的字典
revolve_map = {0: 0, 1: 1, 3: 2, 5: 3, 7: 4, 9: 5, 11: 6, 13: 7, 15: 8, 17: 9, 19: 10, 21: 11, 23: 12, 25: 13, 27: 14, 29: 15, 31: 16, 37: 17, 
39: 18, 43: 19, 45: 20, 47: 21, 51: 22, 53: 23,55: 24,59: 25, 61: 26, 63: 27, 85: 28, 87: 29, 91: 30, 95: 31, 111: 32, 119: 33, 127: 34, 
255: 35}
    
def lbp_revolve(img): #图像旋转不变LBP特征
    revolve_array = np.zeros(img.shape,np.uint8)
    width = img.shape[0]
    height = img.shape[1]
    for i in range(1,width-1):
        for j in range(1,height-1):
            sum = cal_basic_lbp(img,i,j)
            revolve_key = get_min_for_revolve(sum)  #得到各个旋转的LBP二进制串中的最小值
            revolve_array[i, j] = revolve_map[revolve_key]  #将值范围映射到0~35
    return revolve_array

def cal_basic_lbp(img,i,j):#比中心像素大的点赋值为1,比中心像素小的赋值为0,返回得到的二进制序列
    sum = []
    if img[i - 1, j ] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i - 1, j+1 ] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i , j + 1] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i + 1, j+1 ] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i + 1, j ] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i + 1, j - 1] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i , j - 1] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    if img[i - 1, j - 1] > img[i, j]:
        sum.append(1)
    else:
        sum.append(0)
    return sum    
def get_min_for_revolve(arr): # 获取二进制序列进行不断环形旋转得到新的二进制序列的最小十进制值
    values = [] #存放每次移位后的值,最后选择值最小那个
    circle = arr*2  # 用于循环移位,分别计算其对应的十进制
    for i in range(0,8):
        j = 0
        sum = 0
        bit_sum = 0
        while j < 8:
            sum += circle[i+j] << bit_sum
            bit_sum += 1
            j += 1
        values.append(sum)
    return min(values)
 # 绘制图像旋转不变LBP特征的归一化统计直方图
def show_revolve_hist(img_array):
    show_hist(img_array, [36], [0, 36])
def show_hist(img_array,im_bins,im_range):
    hist = cv.calcHist([img_array], [0], None, im_bins, im_range)
    hist = cv.normalize(hist, hist).flatten()
    plt.plot(hist, color='r')
    plt.xlim(im_range)
    plt.show()
img = data.coffee()
img1 = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
re_arr = lbp_revolve(img1)
show_revolve_hist(re_arr)
plt.imshow(re_arr, cmap='Greys_r')
plt.show()


等价LBP

针对圆形LBP缺点,有人进一步提出等价LBP特征。利用等价模式来对LBP模板种类进行降维。我们首先定义“跳变”为二进制串中"01"、"10"这样的变化,定义度量准则为二进制串中的跳变次数。人们发现图像中大部分包含的都是至多两种跳变,且这些囊括了“亮点”、‘暗点’、‘平坦区域’、‘变化的边缘’等等,基本包含了绝大部分主要信息。

定义:当某个LBP所对应的循环二进制数从0到1或从1到0最多有两次跳变时,该LBP所对应的二进制就称为一个等价模式类。如00000000(0次跳变),00000111(只含一次从0到1的跳变),10001111(先由1跳到0,再由0跳到1,共两次跳变)都是等价模式类。除等价模式类以外的模式都归为另一类,称为混合模式类。

通过这样的改进,二进制模式的种类大大减少,而不会丢失任何信息。模式数量由原来的2^P种减少为 P ( P-1)+2种,其中P表示邻域集内的采样点数。对于3×3邻域内8个采样点来说,二进制模式由原始的256种减少为58种,这使得特征向量的维数更少,并且可以减少高频噪声带来的影响。

跳变0次有两个、跳变1次有

  • 步骤
  1. 输入图像若为彩色图像,则将其灰度化,转化为仅含单通道像素的灰度图像;
  2. 按照从左到右、从上到下的顺序依次遍历图像中的所有像素。对于每个像素而言,选取其为中心的3 * 3 邻域;
  3. 于每个邻域,以该邻域中心的像素值作为阈值,其四周的8个像素值与之进行比较,若值大于阈值像素的值,则该位置处被置为1,否则置为0;
  4. 经过对比后,在3 * 3邻域内,其四周8个点可产生8位二进制数,将这8位二进制数依次排列组成二进制序列,依次计算每个8位二进制数的“01”,“10”跳变次数,若跳变次数小于等于2,则将该二进制序列对应的十进制值就是邻域中心的LBP值;若跳变次数大于2,则直接将邻域中心点的LBP值置为 P+1, 也就是9。但在下边代码中,我们不这么做,我们将跳变次数小于等于2的十进制值映射到0~57,跳变次数大于2的,LBP值置为58.
  5. 对图像内的所有像素遍历后,可得到整幅图像中的LBP值,即最终产生的ULBP特征。
  • python 实现
# uniform_map为等价模式的58种特征值从小到大进行序列化编号得到的字典
uniform_map = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 6: 5, 7: 6, 8: 7, 12: 8,14: 9, 15: 10, 16: 11, 24: 12, 28: 13, 30: 14, 31: 15, 32: 16, 48: 17,
 56: 18, 60: 19, 62: 20, 63: 21, 64: 22, 96: 23, 112: 24,120: 25, 124: 26, 126: 27, 127: 28, 128: 29, 129: 30, 131: 31, 135: 32,143: 33,
 159: 34, 191: 35, 192: 36, 193: 37, 195: 38, 199: 39, 207: 40,223: 41, 224: 42, 225: 43, 227: 44, 231: 45, 239: 46, 240: 47, 241: 48,
243: 49, 247: 50, 248: 51, 249: 52, 251: 53, 252: 54, 253: 55, 254: 56,255: 57}
def lbp_uniform(img):
    revolve_array = np.zeros(img.shape,np.uint8)
    width = img.shape[0]
    height = img.shape[1]
    for i in range(1,width-1):
        for j in range(1,height-1):
            sum_ = cal_basic_lbp(img,i,j) #获得二进制
            num_ = calc_sum(sum_)  #获得跳变次数
            if num_ <= 2:
                revolve_array[i,j] = uniform_map[bin_to_decimal(sum_)] #若跳变次数小于等于2,则将该二进制序列对应的十进制值就是邻域中心的LBP值,因为只有58种可能的值,但值得最大值可以是255,所以这里进行映射。
            else:
                revolve_array[i,j] = 58
    return revolve_array
def calc_sum(r):  # 获取值r的二进制中跳变次数
    sum_ = 0
    for i in range(0,len(r)-1):
        if(r[i] != r[i+1]):
            sum_ += 1
    return sum_
def show_uniform_hist(img_array):
    show_hist(img_array, [60], [0, 60])
def show_hist(img_array,im_bins,im_range):
    hist = cv.calcHist([img_array], [0], None, im_bins, im_range)
    hist = cv.normalize(hist, hist).flatten()
    plt.plot(hist, color='r')
    plt.xlim(im_range)
    plt.show()

uniform_array = lbp_uniform(img1)
show_revolve_hist(uniform_array)
plt.imshow(uniform_array,cmap='Greys_r')
plt.show()

旋转不变等价LBP

与等价LBP类似,区别在于若跳变次数小于等于2,则将该二进制序列1的位数就是邻域中心的LBP值;若跳变次数大于2,则直接将邻域中心点的LBP值置为 P+1, 也就是9。

def lbp_uniform(img):
    revolve_array = np.zeros(img.shape,np.uint8)
    width = img.shape[0]
    height = img.shape[1]
    for i in range(1,width-1):
        for j in range(1,height-1):
            sum_ = cal_basic_lbp(img,i,j) #获得二进制
            num_ = calc_sum(sum_)  #获得跳变次数
            if num_ <= 2:
                revolve_array[i,j] = count(bin_to_decimal(sum_)) #若跳变次数小于等于2,则将该二进制序列1的位数作为LBP值
            else:
                revolve_array[i,j] = 9  # P + 1 = 8 + 1 = 9
    return revolve_array
def calc_sum(r):  # 获取值r的二进制中跳变次数
    sum_ = 0
    for i in range(0,len(r)-1):
        if(r[i] != r[i+1]):
            sum_ += 1
    return sum_
def show_uniform_hist(img_array):
    show_hist(img_array, [10], [0,10])
def show_hist(img_array,im_bins,im_range):
    hist = cv.calcHist([img_array], [0], None, im_bins, im_range)
    hist = cv.normalize(hist, hist).flatten()
    plt.plot(hist, color='r')
    plt.xlim(im_range)
    plt.show()
def count(num):
    cnt = 0
    while num:
        if num & 1 == 1:
            cnt += 1
        num = num >> 1
    return cnt
def bin_to_decimal(bin):#二进制转十进制
    res = 0
    bit_num = 0 #左移位数
    for i in bin[::-1]:
        res += i << bit_num   # 左移n位相当于乘以2的n次方
        bit_num += 1
    return res
uniform_array = lbp_uniform(img1)
show_revolve_hist(uniform_array)
plt.imshow(uniform_array,cmap='Greys_r')
plt.show()


更多推荐

Python实现LBP算法