【OpenCV 例程200篇】227. 特征描述之 LBP 纹理特征算子

『youcans 的 OpenCV 例程200篇 - 总目录』

【youcans 的 OpenCV 例程200篇】227. 特征描述之 LBP 纹理特征算子


特征通常是针对于图像中的某个目标而言的。

针对目标所在区域的特征描述符(Region descriptors),称为区域特征描述子。


4.2 纹理特征之统计直方图

纹理体现了物体表面的具有缓慢变化或者周期性变化的表面结构组织排列属性。纹理特征描述了图像或图像区域所对应景物的表面性质。

纹理与尺度密切相关,一般仅呈现在特定尺度上,对纹理的识别要在恰当的尺度上进行。 纹理特征不是基于像素点的特征,而是一种基于像素区域的统计特性。因此,纹理能用来描述不同的图像区域。

纹理特征具有三个性质:

  • 某种局部序列性不断重复;
  • 非随机排列;
  • 纹理区域内大致为均匀的统一体。

纹理特征通常具有旋转不变性,在模式匹配中对噪声有较强的抵抗能力。

描述图像中的纹理区域,要基于区域尺度、可分辨灰度元素的数目以及这些灰度元素的相互关系等。


4.2.1 LBP 纹理特征算子

局部二值模式(LBP,Local binary patterns)是一种用来描述图像局部纹理特征的算子,它具有旋转不变性和灰度不变性的优点 。 LBP 特征计算简单、效果较好,在计算机视觉领域得到了广泛的应用。

LBP 通过对邻域像素进行阈值处理并以二进制数表示来标记像素,所得到的灰度图像反映了图像中的纹理。

原始的 LBP 算子定义在 3×3 的窗口内,以窗口中心像素为阈值,与相邻的 8 个像素的灰度值比较,大于阈值则标记为 1,否则标记为 0。从右上角开始顺时针旋转,排列 8 个 0/1标记值,得到一个 8 位二进制数,就是窗口中心像素点的 LBP 值。
L B P P , R ( x c , y c ) = ∑ p = 0 P − 1 S ( g p − g c ) ∗ 2 p S ( g p − g c ) = { 1 , g p ≥ g c 0 , g p < g c LBP_{P,R} (x_c,y_c) = \sum_{p=0}^{P-1} S(g_p-g_c)*2^p\\ S(g_p-g_c) = \begin{cases} 1, \quad g_p \ge g_c\\ 0, \quad g_p \lt g_c \end{cases} LBPP,R(xc,yc)=p=0P1S(gpgc)2pS(gpgc)={ 1,gpgc0,gp<gc

LBP 算子利用了邻域点的量化关系,可以有效地消除光照对图像的影响。只要光照变化不足以改变相邻像素点的像素值的大小关系,LBP 算子的值就不会发生变化。


例程 14.7:特征描述之 LBP 纹理特征算子

OpenCV 中实现了 LBP 特征的计算,但没有提供单独的 API。skimage 特征检测与提取对各种 LBP 的方法提供了丰富的封装函数。

本例程比较不同方法实现 LBP 的性能,同时考察 LBP 排列顺序的影响。

    #  14.7 特征描述之 LBP 纹理特征算子 (Local binary patterns)
    def getLBP1(gray):
        height, width = gray.shape
        dst = np.zeros((height, width), np.uint8)
        kernel = np.array([1, 2, 4, 128, 0, 8, 64, 32, 16]).reshape(3,3)  # 从左上角开始顺时针旋转
        # kernel = np.array([64,128,1,32,0,2,16,8,4]).reshape(3,3)  # 从右上角开始顺时针旋转
        for h in range(1, height-1):
            for w in range(1, width-1):
                LBPMat = (gray[h-1:h+2, w-1:w+2] >= gray[h, w])  # 阈值比较
                dst[h, w] = np.sum(LBPMat * kernel)  # 二维矩阵相乘
        return dst

    def getLBP2(gray):
        height, width = gray.shape
        dst = np.zeros((height, width), np.uint8)
        # kernelFlatten = np.array([1, 2, 4, 128, 0, 8, 64, 32, 16])  # 从左上角开始顺时针旋转
        kernelFlatten = np.array([64,128,1,32,0,2,16,8,4])  # 从右上角开始顺时针旋转
        for h in range(1, height-1):
            for w in range(1, width-1):
                LBPFlatten = (gray[h-1:h+2, w-1:w+2] >= gray[h, w]).flatten()  # 展平为一维向量, (9,)
                dst[h, w] = np.vdot(LBPFlatten, kernelFlatten)  # 一维向量的内积
        return dst

    def getLBP3(gray):
        height, width = gray.shape
        dst = np.zeros((height, width), np.uint8)
        for h in range(1, height-1):
            for w in range(1, width-1):
                center = gray[h, w]
                code = 0  # 从左上角开始顺时针旋转
                code |= (gray[h-1, w-1] >= center) << (np.uint8)(7)
                code |= (gray[h-1, w  ] >= center) << (np.uint8)(6)
                code |= (gray[h-1, w+1] >= center) << (np.uint8)(5)
                code |= (gray[h  , w+1] >= center) << (np.uint8)(4)
                code |= (gray[h+1, w+1] >= center) << (np.uint8)(3)
                code |= (gray[h+1, w  ] >= center) << (np.uint8)(2)
                code |= (gray[h+1, w-1] >= center) << (np.uint8)(1)
                code |= (gray[h  , w-1] >= center) << (np.uint8)(0)
                dst[h, w] = code
        return dst

    # 原始 LBP 算法:选取中心点周围的 8个像素点,阈值处理后标记为 8位二进制数
    img = cv2.imread("../images/fabric1.png", flags=1)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 灰度图像

    # 1) 二重循环, 二维矩阵相乘
    timeBegin = cv2.getTickCount()
    imgLBP1 = getLBP1(gray)  # # 从左上角开始顺时针旋转
    timeEnd = cv2.getTickCount()
    time = (timeEnd-timeBegin)/cv2.getTickFrequency()
    print("1) 二重循环, 二维矩阵相乘:", round(time, 4))

    # 2) 二重循环, 一维向量的内积
    timeBegin = cv2.getTickCount()
    imgLBP2 = getLBP2(gray)  # 从右上角开始顺时针旋转
    timeEnd = cv2.getTickCount()
    time = (timeEnd-timeBegin)/cv2.getTickFrequency()
    print("2) 二重循环, 一维向量的内积:", round(time, 4))

    # 3) 二重循环, 按位操作
    timeBegin = cv2.getTickCount()
    imgLBP3 = getLBP3(gray)  # 从右上角开始顺时针旋转
    timeEnd = cv2.getTickCount()
    time = (timeEnd-timeBegin)/cv2.getTickFrequency()
    print("3) 二重循环, 按位操作:", round(time, 4))

    # 4) skimage 特征检测
    from skimage.feature import local_binary_pattern
    timeBegin = cv2.getTickCount()
    lbpSKimage = local_binary_pattern(gray, 8, 1)
    timeEnd = cv2.getTickCount()
    time = (timeEnd-timeBegin)/cv2.getTickFrequency()
    print("4) skimage.feature 封装:", round(time, 4))

    plt.figure(figsize=(9, 6))
    plt.subplot(231), plt.axis('off'), plt.title("origin")
    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    plt.subplot(232), plt.axis('off'), plt.title("gray")
    plt.imshow(gray, 'gray')
    plt.subplot(233), plt.axis('off'), plt.title("LBP(skimage)")
    plt.imshow(lbpSKimage, 'gray')
    plt.subplot(234), plt.axis('off'), plt.title("LBP(TopLeft)")
    plt.imshow(imgLBP1, 'gray')
    plt.subplot(235), plt.axis('off'), plt.title("LBP(TopRight)")
    plt.imshow(imgLBP2, 'gray')
    plt.subplot(236), plt.axis('off'), plt.title("LBP(Bitwise)")
    plt.imshow(imgLBP3, 'gray')
    plt.tight_layout()
    plt.show()

运行结果:

  1. 二重循环, 二维矩阵相乘: 2.7288
  2. 二重循环, 一维向量的内积: 1.7105
  3. 二重循环, 按位操作: 8.3292
  4. skimage.feature 封装: 0.0756

运行结果表明,不同的程序实现方法,对于运算速度的影响很大。

而不同的 LBP 排列顺序(从左上角或右上角开始循环)对提取纹理特征的影响并不大。


在这里插入图片描述


【本节完】

版权声明:
youcans@xupt 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/125670942)
Copyright 2022 youcans, XUPT
Crated:2022-7-7

222. 特征提取之弗里曼链码
225. 特征提取之傅里叶描述子

猜你喜欢

转载自blog.csdn.net/youcans/article/details/125670942