纹理作为一种重要的视觉线索,是图像中普遍存在而又难以描述的特征,图像的纹理特征一般是指图像上地物重复排列造成的灰度值有规则的分布。纹理特征的关键在于纹理特征的提取方法。目前,用于纹理特征提取的方法有很多,最具有代表性的是有基于二阶概率密度的灰度共生矩阵、符合人眼视觉特性的小波变换、纹理谱法以及基于图像结构基元的纹理元方法等。
为了更有效地描述图像局部纹理特征,又先后提取了局部二值模式(Local Binary Pattern,LBP)和韦伯局部描述符(Weber Local Descriptor,WLD),这两种方法都是基于邻域像素点间的灰度变化特征来描述图像纹理的。 因两者易于理解、便于计算且具有较好的局部特征描述能力而被广泛地应用于纹理分类 、目标检测、人脸识别 、合成孔径雷达(Synthetic Aperture Radar,SAR) 图像索引 、指纹活性检测 、图像伪造检测等众多领域中。

一 WLD原理

韦伯定律是反映心理量和物理量之间关系的定律,它表明能够引起感觉差异的差别阈值与原始刺激的强度之比是一个常量,即:

$$\frac{ΔI}{I}=k$$

其中$k$是一个常量;$ΔI$表示差别阈值;$I$表示原始刺激的强度。由此可以推知,刺激的变化所引起的感觉差异不仅与刺激变化的大小有关,还与原始刺激的强度有关。局部图像描述符WLB就是根据该定律提出的,它包含两个算子:差分激励算子和方向算子。WLD计算除边缘像素点外的每个像素点的差分激励和方向,并以其二维分布直方图来联合表征图像的纹理特征。

1、差分激励算子

差分激励反映局部窗内灰度变化的强度信息。通过计算局部窗内邻域像素点与中心像素点间的灰度差值和与中心像素点灰度值的比值$G_{ratio}(x_c)$,再利用反正切变换将分布在$[-P,+∞]$范围内的$G_{ratio}(x_c)$映射到区间$(-\frac{\pi}{2},\frac{\pi}{2})$内得到,差分激励$ξ(x_c)$的计算式为:

$$ξ(x_c)=arctan(G_{ratio}(x_c))=arctan(\sum\limits_{i=0}^{P-1}\frac{(x_i-x_c)}{x_c})$$

其中,$x_c$和$x_i,i=0,1,...,P-1$分别表示中心像素点和邻域像素点的灰度值;$P$表示邻域像素点个数。

2、方向算子

方向反映局部窗内灰度变化的空间分布信息。通过局部窗内水平方向与垂直方向上邻域像素点的灰度差值比值的反正切变换来描述。 其计算式为:

$$Φ(x_c)=arctan(\frac{D_V}{D_H})$$

其中,$D_H$和$D_V$分别表示水平方向上和垂直方向上中心像素点两侧的邻域像素点间的灰度差异,若对于$3\times3$像素的局部窗口如下图所示,则$D_H=x_7-x_3$,$D_V=x_5-x_1$。

为了能够更加有效的区分局部窗口的灰度分布变换,进一步将方向由$Φ(x_c)\in(-\frac{\pi}{2},\frac{\pi}{2})$变换到了$Φ'(x_c)\in[0,2\pi]$,其变换公式为:

$$Φ'(x_c)=\begin{cases} Φ(x_c) \qquad D_H>0,D_V>0 \\ \pi+Φ(x_c) \qquad D_H<0,D_V>0 \\\pi+Φ(x_c) \qquad D_H<0,D_V<0 \\ 2\pi+Φ(x_c) \qquad D_H>0,D_V<0 \end{cases}$$

3、WLD直方图

WLD采用均匀量化技术,将方向$Φ'(x_c)$均匀地量化为$T$个方向,将差分激励均匀地划分为$M$个频段,分别对应于图像中的高频、中频和低频变化,再将划分的每个频段上将差分激励均匀地量化为$S$格,形成一个$T\times{C}=T\times(M\times{S})$的二维直方图,并通过编码将其转化为一维向量用于表示图像的纹理特征。

二维直方图如上图所示,横轴表示方向,纵轴表示差分激励。每个小矩形表示在该方向下所在差分激励区间像素的数量,数量不同,颜色不同。

下图是WLD直方图得到的过程:

1、在每个主方向上得到差分激励子直方图,得到$H(0)$至$H(T-1)$;

2、将$H(k)$分成$M$个子区间,即$l_m,m=0,...,M-1$,将$H(k)$中的${l_m}$对应放置在$t=k$和$m=i$处;

3、将$m=i$的一行子直方图拼接成一个直方图,即$H_i$;

4、将$H_i$组合成一个直方图,即WLD直方图。

然后可以把WLD直方图用于后续分类。

4、缺点

我们来看下图所示的3个局部灰度分布示例。从3个局部窗内的灰度分布看,(a)~(c)分别表示了高频、中频和低频3种变换。按照WLD的计算方法,他们的$ΔI$都等于0,即差分激励等于0,而且,在垂直方向上的灰度值差值也为0,表明方向也等于0.这意味着,WLD将无法区分这3个纹理模式。

存在上述问题的主要原因有:

  • WLD的差分激励算子是利用各向同性的边缘检测滤波器———拉普拉斯算子统计局部窗内$P$个邻域像素点与中心像素点间的灰度差值之和$ΔI$,导致了灰度变化的正负差值相互抵消,换言之,局部窗内的灰度变化信息没有充分体现;
  • WLD 的方向算子仅表达了水平方向和垂直方向上邻域像素点间灰度变化梯度的空间分布方位,不能充分反映局部窗内灰度变化的空间分布结构信息,难以体现纹理的内在变化特征;

5、改进

纹理特征是指与空间分布相关的图像灰度等级的变化。 这意味着灰度图像的纹理既与各像素点间灰度变化的梯度幅值有关,也与其梯度的空间分布密切相关,两者是有机的一体。 针对WLD 存在的问题,提出一种基于正负梯度改进的 WLD(WLD-PNG)。

其核心思想是:

基于局部窗内邻域像素点与中心像素点间灰度变化的正负梯度信息,分离计算正、负差分激励以保留灰度等级的变化特征;

利用 LBP 模式提取正负梯度分布的结构信息,以反映灰度等级变化的空间分布特征。 最后,采用均匀量化和编码技术,将两者有机联合建立图像的纹理特征。

三 代码实现

由于opencv没有提供WLD算法的实现,但是从网上我们可以找到基于Weber local descriptors的人脸识别的代码和基于OPENCV的WLD特征提取程序,我们作为参考,修改自己的代码如下:

# -*- coding: utf-8 -*-
"""
Created on Sat Sep 1 19:08:10 2018 @author: zy
""" ''' WLD
韦伯局部描述符 '''
import cv2
import numpy as np class WLD(object):
def __init__(self):
#差分激励由中心像素与周围像素强度的差异以及中心像素强度组成,分别由f1和f2滤波器得出。
self.__f1 = np.array([[1,1,1],
[1,-8,1],
[1,1,1]])
self.__f2 = np.array([[0,0,0],
[0,1,0],
[0,0,0]])
#方向反映局部窗内灰度变化的空间分布信息。通过局部窗内水平方向与垂直方向上邻域像素点
#的灰度差值比值的反正切变换来描述。 方向分为竖直方向和水平方向,由f3和f4滤波器得出。
self.__f3 = np.array([[0,-1,0],
[0,0,0],
[0,1,0]])
self.__f4 = np.array([[0,0,0],
[1,0,-1],
[0,0,0]])
#方向量化个数 如果修改,也需要修改__classify_fai函数
self.__T = 12
#差分激励量化个数 如果修改,也需要修改__classify_epc函数
self.__M= 8
#每个频段上将差分激励均匀地量化为S格 可以修改
self.__S = 4 def calc_hist(self,image,concatenate=True):
'''
输出统计直方图 形状[M,T,S]或[MxTxS,] args:
image:输入灰度图
concatenate:表明输出直方图是否合并为一维
'''
his = np.zeros((self.__M,self.__T,self.__S),np.float32)
rows,cols = image.shape[:2]
#计算像素点个数
sum_pix = rows*cols
epc,theta = self.__compute(image)
for i in range(rows):
for j in range(cols):
#把差分激励分为8个区间,分别对应一个类别
m = self.__classify_epc(epc[i][j])
t = theta[i][j]
s = self.__classify_s(epc[i][j],m)
his[m-1][t-1][s-1] += 1
#归一化
his /= sum_pix
if concatenate:
his = np.reshape(his,-1)
return his def __compute(self,image):
'''
计算每个像素点的差分激励和方向
'''
rows,cols = image.shape[:2]
#用于保存每个像素点对应的差分激励算子
epc = np.zeros((rows,cols),dtype=np.float32)
#用于保存每个像素点对应的方向算子
theta = np.zeros((rows,cols),dtype=np.float32) '''
计算差分激励ξ
'''
v1 = cv2.filter2D(image,cv2.CV_16SC1,self.__f1)
v2 = cv2.filter2D(image,cv2.CV_16SC1,self.__f2)
for i in range(rows):
for j in range(cols):
#-π/2~π/2
epc[i][j] = np.arctan(v1[i][j]/(v2[i][j]+0.0001)) '''
计算每个像素点的方向Φ,把方向[0,2pi]均匀地量化为12个区间
'''
v3 = cv2.filter2D(image,cv2.CV_16SC1,self.__f3)
v4 = cv2.filter2D(image,cv2.CV_16SC1,self.__f4)
for i in range(rows):
for j in range(cols):
theta[i][j] = np.arctan(v3[i][j]/((v4[i][j])+0.0001))
if v3[i][j]>0 and v4[i][j]>0:
pass
elif v3[i][j]>0 and v4[i][j]<0:
theta[i][j] = theta[i][j]+2*np.pi
else:
theta[i][j]=theta[i][j]+np.pi
theta[i][j] = self.__classify_fai(theta[i][j])
theta = theta.astype(np.uint8)
return epc,theta def __classify_fai(self,value):
'''
把方向值value分类 类别为1、2、3、4、5、6、7、8、9、10、11、12 args:
value:数值 0~2π之间
'''
if value >= 0 and value < 0.15*np.pi:
return 1
elif value >= np.pi*0.15 and value < 0.35*np.pi:
return 2
elif value >= np.pi*0.35 and value < 0.5*np.pi:
return 3
elif value >= np.pi*0.5 and value < 0.65*np.pi:
return 4
elif value >= 0.65*np.pi and value < 0.85*np.pi:
return 5
elif value >= np.pi*0.85 and value < np.pi:
return 6
elif value >= np.pi and value < 1.15*np.pi:
return 7
elif value >= 1.15*np.pi and value < 1.35*np.pi:
return 8
elif value >= np.pi*1.35 and value < 1.5*np.pi:
return 9
elif value >= 1.5*np.pi and value < 1.65*np.pi:
return 10
elif value >= 1.65*np.pi and value < 1.85*np.pi:
return 11
else:
return 12 def __classify_epc(self,value):
'''
把差分激励值value分类,划分为8个区间 类别为1、2、3、4、5、6、7、8 args:
value:数值 -π/2~π/2之间
'''
if value >= np.pi*(-0.5) and value < (-0.3)*np.pi:
return 1
elif value >= np.pi*(-0.3) and value < (-0.15)*np.pi:
return 2
elif value >= np.pi*(-0.15) and value < (-0.05)*np.pi:
return 3
elif value >= np.pi*(-0.05) and value < 0:
return 4
elif value >= 0 and value < 0.05*np.pi:
return 5
elif value >= np.pi*0.05 and value < 0.15*np.pi:
return 6
elif value >= np.pi*0.15 and value < 0.3*np.pi:
return 7
else:
return 8 def __classify_s(self,value,label):
'''
将每个区间的差分激励再次划分为S格 args:
value:差分激励值 -π/2~π/2之间
label:当前所属区间 1,2,3,...,8
'''
if label == 1:
space = ((-0.3)*np.pi - (-0.5)*np.pi)/self.__S
return int((value - (-0.5)*np.pi)/space)+1
elif label == 2:
space = ((-0.15)*np.pi - (-0.3)*np.pi)/self.__S
return int((value - (-0.3)*np.pi)/space)+1
elif label == 3:
space = ((-0.05)*np.pi - (-0.15)*np.pi)/self.__S
return int((value - (-0.15)*np.pi)/space)+1
elif label == 4:
space = 0-(-0.05)*np.pi/self.__S
return int((value - (-0.05)*np.pi)/space)+1
elif label == 5:
space = 0.05*np.pi /self.__S
return int(value /space)+1
elif label == 6:
space = (0.15*np.pi - 0.05*np.pi)/self.__S
return int((value - 0.05*np.pi)/space)+1
elif label == 7:
space = (0.3*np.pi - 0.15*np.pi)/self.__S
return int((value - 0.15*np.pi)/space)+1
else:
space = (0.5*np.pi - 0.3*np.pi)/self.__S
n = int((value - 0.3*np.pi)/space)+1
if n == self.__S+1:
n = self.__S
return n def draw_hist(hist):
'''
绘制直方图
'''
#首先先创建一个黑底的图像,为了可以显示彩色,所以该绘制图像是一个8位的3通道图像
#图像高为100,宽为直方图的长度
width = len(hist)
height = 100
draw_image = np.zeros((height,width,3),dtype= np.uint8)
#获取最大值
max_value = np.max(hist)
#数值量化
value = np.asarray((hist*0.9/max_value*height),dtype=np.int8)
for i in range(width):
cv2.line(draw_image,(i,height-1),(i,height-1-value[i]),(255,0,0))
#显示
cv2.imshow('hist',draw_image)
cv2.waitKey(0)
cv2.destroyAllWindows() if __name__=='__main__':
image = cv2.imread('./image/match1.jpg')
image = cv2.resize(image,dsize=(600,400))
imgray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
wld = WLD()
hist = wld.calc_hist(imgray)
print(hist.shape)
print(hist)
print(np.sum(hist))
draw_hist(hist)

运行结果如下:

参考文章

基于改进WLD的纹理特征提取方法

局部图像描述(weber local descriptor)

第十五节、韦伯局部描述符(WLD,附源码)的更多相关文章

  1. 第十六节、特征描述符BRIEF(附源码)

    我们已经知道SIFT算法采用128维的特征描述子,由于描述子用的是浮点数,所以它将会占用512字节的空间.类似的SUFR算法,一般采用64维的描述子,它将占用256字节的空间.如果一幅图像中有1000 ...

  2. 第十四节、FAST角点检测(附源码)

    在前面我们已经陆续介绍了许多特征检测算子,我们可以根据图像局部的自相关函数求得Harris角点,后面又提到了两种十分优秀的特征点以及他们的描述方法SIFT特征和SURF特征.SURF特征是为了提高运算 ...

  3. 仿酷狗音乐播放器开发日志十九——CTreeNodeUI的bug修复二(附源码)

    转载请说明原出处,谢谢 今天本来打算把仿酷狗播放列表的子控件拖动插入功能做一下,但是仔细使用播放列表控件时发现了几个逻辑错误,由于我的播放 列表控件是基于CTreeViewUI和CTreeNodeUI ...

  4. Android 音视频深入 十九 使用ijkplayer做个视频播放器(附源码下载)

    项目地址https://github.com/979451341/Myijkplayer 前段时候我觉得FFmpeg做个视频播放器好难,虽然播放上没问题,但暂停还有通过拖动进度条来设置播放进度,这些都 ...

  5. centos shell脚本编程1 正则 shell脚本结构 read命令 date命令的用法 shell中的逻辑判断 if 判断文件、目录属性 shell数组简单用法 $( ) 和${ } 和$(( )) 与 sh -n sh -x sh -v 第三十五节课

    centos   shell脚本编程1 正则  shell脚本结构  read命令  date命令的用法  shell中的逻辑判断  if 判断文件.目录属性  shell数组简单用法 $( ) 和$ ...

  6. 第四百一十五节,python常用排序算法学习

    第四百一十五节,python常用排序算法学习 常用排序 名称 复杂度 说明 备注 冒泡排序Bubble Sort O(N*N) 将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮 ...

  7. 第三百七十五节,Django+Xadmin打造上线标准的在线教育平台—创建课程机构app,在models.py文件生成3张表,城市表、课程机构表、讲师表

    第三百七十五节,Django+Xadmin打造上线标准的在线教育平台—创建课程机构app,在models.py文件生成3张表,城市表.课程机构表.讲师表 创建名称为app_organization的课 ...

  8. 第三百五十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—scrapy信号详解

    第三百五十五节,Python分布式爬虫打造搜索引擎Scrapy精讲—scrapy信号详解 信号一般使用信号分发器dispatcher.connect(),来设置信号,和信号触发函数,当捕获到信号时执行 ...

  9. 第三百一十五节,Django框架,CSRF跨站请求伪造

    第三百一十五节,Django框架,CSRF跨站请求伪造  全局CSRF 如果要启用防止CSRF跨站请求伪造,就需要在中间件开启CSRF #中间件 MIDDLEWARE = [ 'django.midd ...

随机推荐

  1. 莫烦keras学习自修第四天【分类问题】

    1.代码实战 #!/usr/bin/env python #! _*_ coding:UTF-8 _*_ # 导入numpy import numpy as np np.random.seed(133 ...

  2. mysql 数据库的主从同步

    1.复制准备 操作系统 centOS 主库(mysql master):  ip为123.56.94.1   port为3306  mysql 版本 5.7.16 从库(mysql slave):   ...

  3. vue环境搭建+vscode

    https://blog.csdn.net/junshangshui/article/details/80376489

  4. vue-cli(vue脚手架)

    vue-cli用于自动生成vue+webpack项目. 安装webpack:npm install webpack -g 检查webpack是否安装成功和版本:webpack -v 如果是webpac ...

  5. Mybatis之执行自定义SQL举例

    本文说明如何使用Mybatis执行我自定义输入的SQL语句. 需要的mybaits文件包括:配置文件(mybatis-config-dao.xml 和 jdbc.properties).接口文件(IS ...

  6. ES 6 系列 - 变量声明

    let 和 const let 声明 (一)基本用法 let 声明的变量只在块级作用域内有效,出了该块则报错,最常见且适合的地方在 for 循环中: var a = []; for (var i = ...

  7. git使用常见问题

    1.git branch使用,创建新的分之后做修改后,其他分支也被同步修改 问题: 原项目在 master 分支,执行下面的操作:  git branch test  git checkout tes ...

  8. 配置 Django

    Django项目的设置文件位于项目同名目录下,名叫settings.py.这个模块,集合了整个项目方方面面的设置属性,是项目启动和提供服务的根本保证. 一.简述 settings.py文件本质上是一个 ...

  9. Balanced Number HDU - 3709 数位dp

    题意: 给出范围 算出 满足  选取一个数中任一一个 树作为支点  两边的数分别乘以到中心的距离和 左和等于右和   的数有多少个 数位DP题 状态转移方程为dp[pos][x][state]=dp[ ...

  10. 树形DP和状压DP和背包DP

    树形DP和状压DP和背包DP 树形\(DP\)和状压\(DP\)虽然在\(NOIp\)中考的不多,但是仍然是一个比较常用的算法,因此学好这两个\(DP\)也是很重要的.而背包\(DP\)虽然以前考的次 ...