建议在阅读完LBP学习笔记(1)后再阅读下面的内容更容易上手
一.简述LBP等价模式
LBP算子作为一种用于图像纹理分析的算法,在处理过程中会产生大量的二进制模式,严重影响了模式分类和识别的效率。为了解决这个问题,Ojala提出了采用一种"等价模式"(Uniform Pattern)来对LBP算子的模式种类进行降维。
具体而言,LBP等价模式指的是在LBP计算中,将二进制模式编码,使相邻的二进制模式在满足某些条件的情况下被归到同一个等价类中。通过这种方式,可以将原先过多的二进制模式数量压缩为比较少的等价模式数量,从而提高了算法的效率和可靠性。这一策略在实际应用中得到了广泛的应用,并被应用于许多计算机视觉领域中,如人脸识别、手写数字识别、角膜纹理识别等。
二.代码实现(关键函数及完整代码)
1.获取等价模式特征
# 获取图像的LBP原始模式特征
def lbp_basic(self, image_array):
basic_array = np.zeros(image_array.shape, np.uint8)
width = image_array.shape[0]
height = image_array.shape[1]
for i in range(1, width - 1):
for j in range(1, height - 1):
sum = self.calute_basic_lbp(image_array, i, j)[1]
basic_array[i, j] = sum
return basic_array
# 获取图像的LBP等价模式特征
def lbp_uniform(self, image_array):
uniform_array = np.zeros(image_array.shape, np.uint8)
basic_array = self.lbp_basic(image_array)
width = image_array.shape[0]
height = image_array.shape[1]
for i in range(1, width - 1):
for j in range(1, height - 1):
k = basic_array[i, j] << 1
if k > 255:
k = k - 255
xor = basic_array[i, j] ^ k #^是异或运算符,用于按位比较两个二进制数字,如果对应位上的数字不同,则该位的运算结果为1,否则为0
num = self.calc_sum(xor)
if num <= 2:
uniform_array[i, j] = self.uniform_map[basic_array[i, j]]
else:
uniform_array[i, j] = 58
return uniform_array
获取原始模式特征在笔记(1)已经介绍过,这里不再重复。
在获取等价模式特征前,先获取原始模式特征,把它传入进lbp_uniform函数中的basic_array。
遍历basic_array每个特征值,把特征值往左移1位,(这里就是把特征 x2),然后和原来没有移位的特征值进行异或运算,按位比较,得到新的二进制序列,然后计算新的二进制序列中1的位数,如果大于二,则把他们都归为一类模式,让他们的像素值都等于58,若小于等于2,则他们的像素值就根据原始数据的特征值作为uniform_map字典里的键,把键对应的值赋予。
异或:异或(XOR)是一种逻辑运算符,通常用于比较两个二进制数字的对应位,若这些位不同则结果为1,否则结果为0。它的符号通常是“^”。
这里 if k > 255 的原因:
假设传入的原始模式特征值矩阵中有最大特征值255,它对应的二进制序列为 [1,1,1,1,1,1,1,1] ,此时如果不进行判断,让它左移一位,那么它就会变成510,即[1,1,1,1,1,1,1,1,0],这样数据有效长度(1作为最高位往右数)变成了9,我们的二进制序列只有8位,所以让 510-255 =255,这样就可以避免当中心像素周围的8个像素都大于中心像素时的造成的计算溢出。
basic_array[i,j] ^ k的原因:
因为我们用等价模式的主要目的的要提取出图片中的轮廓条纹,取舍其他不重要的点,那么这种纹理特征必然是在一段区域内是连续的,(可以理解成一根线,那么一根线它必然是连续的,不可能是一个点一个点这样的),抽象出来就是[0,0,1,1,1,1,0,0],[1,1,1,1,1,1,1,1],[0,0,0,0,0,0,0,0],[1,1,0,0,0,0,0,1],这里就是1要连续或者0要连续,这样0到1或者1到0就跳变1次,而这种连续的二进制序列就只能跳变0次或者2次(是环形排列,头尾相连),我们就把这样的跳变次数不超过2次的二进制对应的原始模式特征值做为uniform_map字典里的键(有58个)。
那么想要得到这种可以判断图片线条纹理是否连续的二进制序列,最简单的方法就是原始模式特征值和它左移一位得到的值做异或运算。
连续:
不连续:
2.获取二进制序列的十进制数
# 图像的LBP原始特征计算算法:将图像指定位置的像素与周围8个像素比较
# 比中心像素大的点赋值为1,比中心像素小的赋值为0,返回得到的二进制序列
def calute_basic_lbp(self, image_array, i, j):
sum = []
num = 0
if image_array[i - 1, j - 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 0
else:
sum.append(0)
if image_array[i - 1, j] > image_array[i, j]:
sum.append(1)
num += 2 ** 1
else:
sum.append(0)
if image_array[i - 1, j + 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 2
else:
sum.append(0)
if image_array[i, j - 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 3
else:
sum.append(0)
if image_array[i, j + 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 4
else:
sum.append(0)
if image_array[i + 1, j - 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 5
else:
sum.append(0)
if image_array[i + 1, j] > image_array[i, j]:
sum.append(1)
num += 2 ** 6
else:
sum.append(0)
if image_array[i + 1, j + 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 7
else:
sum.append(0)
return sum, num
3.获取异或之后1的位数
# 获取值r的二进制中1的位数
def calc_sum(self, r):
num = 0
while (r):
r &= (r - 1)
num += 1
return num
4.创建等价模式特征字典
def __init__(self):
# uniform_map为等价模式的58种特征值从小到大进行序列化编号得到的字典 58种
self.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}
字典里的键就是跳变次数不超过2的原始模式特征值,有58个,而等价不变总共有59种模式,跳变次数超过2的都统一成一种模式,键对应的值就是这些模式下的像素值。
5.完整代码
import numpy as np
import cv2
from PIL import Image
from pylab import *
class LBP:
def __init__(self):
# uniform_map为等价模式的58种特征值从小到大进行序列化编号得到的字典 58种
self.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}
# 图像的LBP原始特征计算算法:将图像指定位置的像素与周围8个像素比较
# 比中心像素大的点赋值为1,比中心像素小的赋值为0,返回得到的二进制序列
def calute_basic_lbp(self, image_array, i, j):
sum = []
num = 0
if image_array[i - 1, j - 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 0
else:
sum.append(0)
if image_array[i - 1, j] > image_array[i, j]:
sum.append(1)
num += 2 ** 1
else:
sum.append(0)
if image_array[i - 1, j + 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 2
else:
sum.append(0)
if image_array[i, j - 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 3
else:
sum.append(0)
if image_array[i, j + 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 4
else:
sum.append(0)
if image_array[i + 1, j - 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 5
else:
sum.append(0)
if image_array[i + 1, j] > image_array[i, j]:
sum.append(1)
num += 2 ** 6
else:
sum.append(0)
if image_array[i + 1, j + 1] > image_array[i, j]:
sum.append(1)
num += 2 ** 7
else:
sum.append(0)
return sum, num
# 获取值r的二进制中1的位数
def calc_sum(self, r):
num = 0
while (r):
r &= (r - 1)
num += 1
return num
# 获取图像的LBP原始模式特征
def lbp_basic(self, image_array):
basic_array = np.zeros(image_array.shape, np.uint8)
width = image_array.shape[0]
height = image_array.shape[1]
for i in range(1, width - 1):
for j in range(1, height - 1):
sum = self.calute_basic_lbp(image_array, i, j)[1]
basic_array[i, j] = sum
return basic_array
# 获取图像的LBP等价模式特征
def lbp_uniform(self, image_array):
uniform_array = np.zeros(image_array.shape, np.uint8)
basic_array = self.lbp_basic(image_array)
width = image_array.shape[0]
height = image_array.shape[1]
for i in range(1, width - 1):
for j in range(1, height - 1):
k = basic_array[i, j] << 1
if k > 255:
k = k - 255
xor = basic_array[i, j] ^ k #^是异或运算符,用于按位比较两个二进制数字,如果对应位上的数字不同,则该位的运算结果为1,否则为0
num = self.calc_sum(xor)
if num <= 2:
uniform_array[i, j] = self.uniform_map[basic_array[i, j]]
else:
uniform_array[i, j] = 58
return uniform_array
# 绘制指定维数和范围的图像灰度归一化统计直方图
def show_hist(self, img_array, im_bins, im_range):
#直方图的x轴是灰度值,y轴是图片中具有同一个灰度值的点的数目, [img_array]原图像图像格式为uint8或float32,[0]0表示灰度,None无掩膜
hist = cv2.calcHist([img_array], [0], None, im_bins, im_range)
hist = cv2.normalize(hist, hist).flatten() #对计算出来的直方图数据进行归一化处理,并将结果扁平化为1D数组
# print(hist)
plt.plot(hist, color='r')
plt.xlim(im_range) #设置X轴的取值范围
plt.show()
# 绘制图像原始LBP特征的归一化统计直方图
def show_basic_hist(self, img_array):
self.show_hist(img_array, [256], [0, 256])
# 绘制图像等价模式LBP特征的归一化统计直方图
def show_uniform_hist(self, img_array):
self.show_hist(img_array, [60], [0, 60]) #[60]把像素值区间分成60部分,[0, 60]像素值区间
# 显示图像
def show_image(self, image_array):
plt.imshow(image_array, cmap='Greys_r')
plt.show()
if __name__ == '__main__':
image = cv2.imread('F:/in_picture/001.png')
image_array = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) #BGR格式图像转灰度图像
lbp = LBP()
plt.imshow(image_array, cmap='Greys_r') # 去掉参数就是热量图了
plt.title('Original')
plt.show()
# 获取图像原始LBP特征,并显示其统计直方图与特征图像
basic_array = lbp.lbp_basic(image_array)
lbp.show_basic_hist(basic_array)
lbp.show_image(basic_array)
#获取图像等价模式LBP特征,并显示其统计直方图与特征图像
uniform_array=lbp.lbp_uniform(image_array)
lbp.show_uniform_hist(uniform_array)
lbp.show_image(uniform_array)