查看全课大纲(第 19 / 93 节)
- 1.计算机视觉概述
- 2.计算机视觉应用场景
- 3.困难与挑战
- 4.开源库介绍
- 5.图像显示与存储
- 6.CLAHE
- 7.腐蚀膨胀与开闭运算
- 8.滤波与边缘填充
- 9.各种滤波
- 10.图像金字塔
- 11.傅里叶变换
- 12.短时傅里叶变换与小波变换
- 13.颜色特征与颜色直方图
- 14.几何特征_边缘
- 15.几何特征-角点与斑点
- 16.局部特征-SIFT特征描述子
- 17.局部特征-SURF特征描述子
- 18.局部特征-ORB特征描述子
- 19.其他特征描述子
- 20.传统图像分割技术
- 21.GraphCut
- 22.GrabCut
- 23.人脸检测的传统方法
- 24.行人检测的传统方法HOG+SVM
- 25.行人检测DPM
- 26.人工神经网络的架构
- 27.目标函数与梯度下降
- 28.误差反向传播
- 29.可手算的神经网络
- 30.改进目标函数与克服梯度消散
- 31.神经网络之传统神经网络与深度网络的差异
- 32.各种梯度下降算法
- 33.如何选择合适的梯度下降算法
- 34.避免过适应
- 35.CNN的架构
- 36.CNN的误差反向传播_卷积层
- 37.CNN的误差反向传播_池化层
- 38.分类所用的数据集
- 39.Alexnet详解
- 40.VGG详解
- 41.GoogLeNet_v1
- 42.BatchNormalization
- 43.GoogLeNet_v3v4
- 44.ResNet
- 45.ResNeXt
- 46.CNN设计准则
- 47.图像检索与推土机距离
- 48.基于颜色相似性
- 49.基于形状相似性
- 50.基于纹理相似性
- 51.基于局部特征相似性
- 52.DL的二值哈希编码
- 53.高维空间中检索KDTree
- 54.高维空间中检索LSH
- 55.目标识别任务综述
- 56.RCNN
- 57.SPP-Net
- 58.Fast R-CNN
- 59.Faster R-CNN
- 60.R-CNN系列模型回顾
- 61.R-FCN的设计思路
- 62.R-FCN的实现
- 63.YOLOv1
- 64.YOLOv2_9000
- 65.YOLOv3
- 66.图像分割应用场景与数据集
- 67.上采样的实现方式
- 68.FCN网络架构的由来
- 69.FCN构建细节
- 70.DeepLabv1
- 71.CRF
- 72.DeepLabv2
- 73.DeepLabv3
- 74.特定的医学影像分割任务
- 75.U-Net
- 76.3D U-Net
- 77.V-Net
- 78.DenseNet
- 79.病理切片分析任务及数据集
- 80.病理切片分析
- 81.图说任务及其传统处理方法
- 82.深度语言模型与RNN
- 83.LSTM及其变种
- 84.NIC模型
- 85.SAT模型
- 86.图说的性能指标
- 87.判别模型与生成模型
- 88.KL散度与JS散度
- 89.VAE
- 90.GAN的基本原理
- 91.DCGAN与CGAN
- 92.Wasserstein GAN
- 93.SRGAN
其他特征描述子
局部二值模式与Gabor滤波
小象实战讲义 · 基于深度学习的计算机视觉
在之前的章节中,我们学习了SIFT、SURF、ORB等基于关键点的局部特征描述子。这些方法都需要先检测图像中的稳定特征点(如角点、斑点),然后描述其邻域特征。本节我们将介绍两种不同的特征描述方法:局部二值模式(LBP) 和Gabor滤波。LBP是一种基于像素灰度相对关系的纹理描述方法,无需检测特征点;而Gabor滤波则模拟人类视觉系统,擅长提取多尺度、多方向的纹理特征。掌握这两种方法,你将理解传统计算机视觉中纹理分析和特征提取的另一种思路。
💡 核心导读
- LBP的核心思想:将每个像素与其圆形邻域内的像素进行灰度大小比较,生成二进制编码,通过统计直方图形成特征描述,具备灰度不变性和旋转不变性。
- 旋转不变性的实现:通过循环移位二进制编码,取最小值作为该模式的代表值,从而消除旋转带来的影响。
- Gabor滤波的原理:由高斯函数与正弦(余弦)平面波相乘构成,在空域上是一个带方向的局部振荡滤波器,在频域上属于加窗傅里叶变换。
- Gabor滤波器组:通过组合多个不同方向和尺度的Gabor滤波器,可以全面捕捉图像的纹理信息,其设计灵感来源于人类视觉系统。
- 方法对比与定位:LBP与基于关键点的描述子(如SIFT)根本区别在于,LBP是密集的、基于像素的纹理描述,而后者是基于稀疏特征点的局部描述。
LBP:局部二值模式
局部二值模式是一种简单而有效的纹理描述算子。与SIFT、ORB等需要先检测特征点再描述的方法不同,LBP直接对图像中的每一个像素进行处理,生成一个局部纹理描述。
基本定义与计算过程
LBP的基本思路非常直观:对于一个中心像素,将其与周围邻域像素的灰度值进行比较。
- 定义邻域:以当前像素点为圆心,设定一个半径R,在半径为R的圆周上均匀地采样P个点。这构成了一个圆形的邻域。
- 二值化比较:将圆周上P个采样点的灰度值,分别与圆心处中心像素的灰度值进行比较。
- 如果邻域点的灰度值 大于或等于 中心点灰度值,则将该位置标记为 1。
- 如果邻域点的灰度值 小于 中心点灰度值,则将该位置标记为 0。
- 生成编码:按照固定的顺序(例如顺时针方向)将这P个二进制位(0或1)串联起来,形成一个P位的二进制数。
- 转换为LBP值:将这个二进制数转换为对应的十进制数,作为该中心像素点的LBP编码值。
- 生成特征直方图:遍历整幅图像(或图像中的某个区域),计算每个像素的LBP值,然后统计所有LBP值的直方图。这个直方图就代表了该图像(或区域)的纹理特征。
下面我们通过一个具体的例子和代码来演示这个过程。假设我们有一个3x3的图像块(即R=1,P=8的正方形邻域近似)。
import numpy as np
# 示例:一个3x3的图像块灰度值
# 中心像素值为83
image_patch = np.array([
[ 80, 100, 110],
[ 80, 83, 90],
[ 70, 95, 85]
], dtype=np.uint8)
center_value = image_patch[1, 1] # 中心值 83
# 定义8个邻域点的位置(相对于中心点的偏移量),按顺时针顺序
# 顺序:左上, 上, 右上, 右, 右下, 下, 左下, 左
neighbor_offsets = [(-1, -1), (-1, 0), (-1, 1),
(0, 1), (1, 1), (1, 0),
(1, -1), (0, -1)]
binary_string = ''
for dy, dx in neighbor_offsets:
neighbor_value = image_patch[1 + dy, 1 + dx]
if neighbor_value >= center_value:
binary_string += '1'
else:
binary_string += '0'
print(f"中心像素值: {center_value}")
print(f"邻域像素值比较结果(顺时针): {binary_string}")
# 将二进制字符串转换为十进制LBP值
lbp_value = int(binary_string, 2)
print(f"对应的LBP值(十进制): {lbp_value}")中心像素值: 83
邻域像素值比较结果(顺时针): 01111100
对应的LBP值(十进制): 124在这个例子中,我们得到了二进制串01111100,对应的十进制LBP值为124。这个值描述了中心像素与其8邻域之间的局部灰度对比模式。
实现旋转不变性
上述基本的LBP算子对图像旋转是敏感的。如果图像旋转,邻域像素围绕中心点的顺序就会发生变化,导致生成的二进制串不同,最终LBP值也不同。为了解决这个问题,我们引入旋转不变LBP。
其思路是:对于一个P位的二进制串,通过循环移位(将最高位移到最低位,或反之)至多可以产生P种不同的二进制串(遇到周期性模式时会出现重复)。我们取这些二进制串对应的十进制值中最小的那个,作为该局部模式的旋转不变表示。
def get_min_rotation(binary_str):
"""获取二进制串循环移位后的最小值"""
rotations = []
current = binary_str
for _ in range(len(binary_str)):
# 循环左移一位
current = current[1:] + current[0]
rotations.append(current)
# 将所有旋转后的二进制串(包括原始串)转换为十进制,取最小值
decimal_values = [int(b, 2) for b in rotations + [binary_str]]
return min(decimal_values)
# 接上例的二进制串
binary_str = '01111100'
rot_invariant_value = get_min_rotation(binary_str)
print(f"原始二进制串: {binary_str}")
print(f"旋转不变LBP值(十进制): {rot_invariant_value}")
# 验证:列出所有循环移位结果
print("\n所有循环移位结果:")
current = binary_str
for i in range(len(binary_str)+1):
dec_val = int(current, 2)
print(f" 移位{i}: {current} -> {dec_val}")
current = current[1:] + current[0]原始二进制串: 01111100
旋转不变LBP值(十进制): 31
所有循环移位结果:
移位0: 01111100 -> 124
移位1: 11111000 -> 248
移位2: 11110001 -> 241
移位3: 11100011 -> 227
移位4: 11000111 -> 199
移位5: 10001111 -> 143
移位6: 00011111 -> 31
移位7: 00111110 -> 62
移位8: 01111100 -> 124从结果可以看出,所有循环移位产生的十进制值中,最小的是00011111对应的31。因此,该局部模式的旋转不变LBP值为31。无论图像如何旋转,只要局部灰度对比关系不变,我们最终都会得到这个唯一的最小值。
圆形邻域与多尺度扩展
基本的LBP使用3x3正方形邻域(即R=1,P=8)。我们可以将其扩展为更一般的形式,即圆形LBP(Circular LBP) 或扩展LBP(Extended LBP)。
- 圆形邻域:采样点位于以R为半径的圆周上,更符合各向同性的思想。
- 多尺度:通过改变半径R,可以捕捉不同尺度的纹理信息。R越大,关注的纹理结构越粗。
- 多采样点:通过增加采样点数量P,可以对局部纹理进行更精细的描述。
在实际计算圆形邻域上非整数坐标点的灰度值时,通常使用双线性插值来获取。
import cv2
import numpy as np
def circular_lbp_value(center_y, center_x, image, radius=1, num_points=8):
"""计算单个像素点的圆形LBP值(非旋转不变版本)"""
height, width = image.shape
center_val = image[center_y, center_x]
binary_str = ''
for i in range(num_points):
# 计算圆周上第i个点的角度
angle = 2 * np.pi * i / num_points
# 计算该点的坐标(可能为非整数)
x = center_x + radius * np.cos(angle)
y = center_y - radius * np.sin(angle) # 图像y轴向下,故用减号
# 双线性插值获取该点灰度值
# 首先找到包围该点的四个整数坐标点,并保证不越界
x0, y0 = int(np.floor(x)), int(np.floor(y))
x0 = max(min(x0, width - 2), 0)
y0 = max(min(y0, height - 2), 0)
x1, y1 = x0 + 1, y0 + 1
# 计算插值权重
wx = x - x0
wy = y - y0
# 双线性插值公式
interpolated_value = (1 - wx) * (1 - wy) * image[y0, x0] + \
wx * (1 - wy) * image[y0, x1] + \
(1 - wx) * wy * image[y1, x0] + \
wx * wy * image[y1, x1]
if interpolated_value >= center_val:
binary_str += '1'
else:
binary_str += '0'
return int(binary_str, 2)
# 生成一个简单的测试图像(渐变纹理)
test_image = np.zeros((10, 10), dtype=np.uint8)
for i in range(10):
test_image[i, :] = i * 25 # 每行灰度递增
print("测试图像(左上角5x5区域):")
print(test_image[:5, :5])
# 计算中心点(5,5)处,R=2, P=12的圆形LBP值
center_y, center_x = 5, 5
lbp_val = circular_lbp_value(center_y, center_x, test_image, radius=2.0, num_points=12)
print(f"\n中心点({center_y},{center_x})的圆形LBP值(R=2,P=12): {lbp_val}")测试图像(左上角5x5区域):
[[ 0 0 0 0 0]
[ 25 25 25 25 25]
[ 50 50 50 50 50]
[ 75 75 75 75 75]
[100 100 100 100 100]]
中心点(5,5)的圆形LBP值(R=2,P=12): 2111LBP的优点与应用
LBP特征具有以下显著优点:
- 灰度不变性:由于只比较灰度的相对大小(≥或<),只要光照变化是均匀的(等价于对灰度做单调递增变换),比较结果就不会改变,因此对整体光照变化不敏感。
- 旋转不变性:通过上述循环移位取最小值的策略,可以实现旋转不变性。
- 计算简单高效:只需要比较和移位操作,非常适合实时系统。
正因为这些优点,LBP在人脸识别、纹理分类、缺陷检测等领域得到了广泛应用。例如,在人脸图像中,LBP特征能够很好地捕捉眼睛、嘴巴、鼻子等部位的局部纹理模式,即使在不同光照下也能保持稳定。
Gabor滤波:模拟视觉的纹理分析工具
Gabor滤波是一种线性滤波器,由Dennis Gabor提出。其核心思想是使用一个高斯函数与一个正弦(或余弦)平面波相乘,得到的滤波器在空域和频域都具有良好的局部性。神经科学研究表明,哺乳动物视觉皮层中简单细胞的感受野剖面图可以用Gabor函数很好地描述,因此Gabor滤波被认为是一种接近人类视觉系统的特征提取方法。
Gabor滤波器的数学形式
一个二维Gabor滤波器在空域的复数形式定义为:
其中:
- 是空域坐标。
- ,这是将坐标轴旋转了角度,使得滤波器具有方向性。
- 是正弦波的波长,决定了滤波器的尺度,其倒数对应于频率。
- 是滤波器的方向角度(到)。
- 是相位偏移(通常取或,分别对应偶对称和奇对称)。
- 是高斯包络的标准差,决定了滤波器的有效区域大小。
- 是空间纵横比,决定了高斯包络的椭圆度(通常设为1,即圆形)。
其实部(通常用于特征提取)为:
虚部为:
从构成上理解:
- 高斯部分: 是一个椭圆高斯窗口,它给滤波器提供了局部性,即只关注图像中某个局部区域,对中心区域赋予高权重,对边缘区域权重衰减。这模拟了人眼对中心区域关注度更高的特点。
- 正弦波部分: 是一个平面波,其波前与轴垂直。这使滤波器对特定方向和频率的纹理产生响应,模拟了人眼对特定朝向和空间频率的敏感度。
从变换角度理解:
- 在频域,Gabor滤波器相当于一个加窗的傅里叶变换。高斯窗保证了在频域也是局部化的,即它只分析某个中心频率附近、某个方向附近的频率成分。
- 在空域,它就是一个振荡的、有方向的局部模板。
Gabor滤波器组的构建
单一的Gabor滤波器只能检测特定方向和尺度的纹理。为了全面描述图像的纹理特征,我们需要使用一组具有不同方向和不同尺度的Gabor滤波器,称为Gabor滤波器组。
- 多尺度:通过改变波长(或等价地,改变中心频率)来实现。较小的(高频)对应小尺度,能捕捉细节纹理;较大的(低频)对应大尺度,能捕捉粗粒度纹理结构。
- 多方向:通过改变方向角来实现。通常会在到(或到)范围内均匀选取若干个方向。
一个典型的Gabor滤波器组可能包含3个尺度()和8个方向(),总共24个滤波器。
下面我们用代码来生成和可视化一个简化版的Gabor滤波器组(实部)。
import numpy as np
import cv2
def create_gabor_kernel_real(ksize, sigma, theta, lambd, gamma=1.0, psi=0.0):
"""
创建Gabor滤波器的实部核。
ksize: 核大小 (ksize, ksize)
sigma: 高斯包络的标准差
theta: 方向(弧度)
lambd: 正弦波的波长
gamma: 空间纵横比
psi: 相位偏移
"""
# 确保ksize为奇数,有明确的中心点
if ksize % 2 == 0:
ksize += 1
half_ksize = ksize // 2
# 生成坐标网格
y, x = np.mgrid[-half_ksize:half_ksize+1, -half_ksize:half_ksize+1]
# 旋转坐标
x_theta = x * np.cos(theta) + y * np.sin(theta)
y_theta = -x * np.sin(theta) + y * np.cos(theta)
# 计算高斯包络
gaussian = np.exp(-(x_theta**2 + (gamma**2) * y_theta**2) / (2 * sigma**2))
# 计算正弦波部分
sinusoid = np.cos(2 * np.pi * x_theta / lambd + psi)
# Gabor核 = 高斯包络 * 正弦波
kernel = gaussian * sinusoid
# 可选:将核的均值调整为0,增强其对均匀区域的响应为零的特性
# kernel = kernel - kernel.mean()
return kernel
# 设置参数
ksize = 31 # 核大小
sigma = 5.0 # 高斯标准差
lambd = 10.0 # 波长
gamma = 1.0
psi = 0.0
# 创建不同方向的Gabor核(4个方向示例)
thetas = [0, np.pi/4, np.pi/2, 3*np.pi/4] # 0°, 45°, 90°, 135°
kernels = []
for theta in thetas:
kernel = create_gabor_kernel_real(ksize, sigma, theta, lambd, gamma, psi)
kernels.append(kernel)
# 打印第一个核(0°方向)的中心部分(7x7)以供观察
print("0°方向Gabor核的中心区域 (7x7):")
# 为了清晰显示,我们限制小数位数
np.set_printoptions(precision=3, suppress=True)
center_slice = kernels[0][ksize//2-3:ksize//2+4, ksize//2-3:ksize//2+4]
print(center_slice)0°方向Gabor核的中心区域 (7x7):
[[-0.216 0.238 0.662 0.835 0.662 0.238 -0.216]
[-0.238 0.263 0.732 0.923 0.732 0.263 -0.238]
[-0.253 0.28 0.777 0.98 0.777 0.28 -0.253]
[-0.258 0.285 0.793 1. 0.793 0.285 -0.258]
[-0.253 0.28 0.777 0.98 0.777 0.28 -0.253]
[-0.238 0.263 0.732 0.923 0.732 0.263 -0.238]
[-0.216 0.238 0.662 0.835 0.662 0.238 -0.216]]从输出可以看到,0°方向的Gabor核沿x轴振荡(条纹垂直于x轴),而在垂直方向(y轴)上由于高斯窗的作用,强度从中心向两侧衰减。这正是高斯函数与水平方向正弦波相乘的结果。
Gabor滤波的应用:纹理分析与特征提取
将Gabor滤波器组中的每个滤波器与图像进行卷积,可以得到一组滤波响应图。每个响应图反映了图像中具有对应尺度和方向的纹理成分的强度。
纹理特征提取的典型流程:
- 构建一个Gabor滤波器组(例如,3个尺度×8个方向=24个滤波器)。
- 将输入图像(通常先转换为灰度图)与每个滤波器进行卷积,得到24幅响应图。
- 对于每个响应图,可以计算其局部统计量(如均值、方差)或全局统计量,也可以将响应图分割成小块(cell),计算每个小块的统计量,最后串联起来形成高维特征向量。
- 这个特征向量即可用于纹理分类、纹理分割、物体识别等任务。
Gabor特征对光照变化相对不敏感,并且能提供良好的方向选择和尺度选择特性,因此在纹理分析、指纹识别、医学图像分析(如检测组织纹理异常)等领域有广泛应用。
# 伪代码:Gabor特征提取流程示意
1. 读取灰度图像 I
2. 初始化Gabor滤波器组 filters = []
3. 对于 scales = [scale1, scale2, scale3]:
对于 orientations = [0°, 45°, 90°, ...]:
kernel = create_gabor_kernel(sigma, theta, lambd, ...)
filters.append(kernel)
4. 特征向量 feature_vector = []
5. 对于 filters 中的每个 kernel:
response = cv2.filter2D(I, cv2.CV_32F, kernel) # 滤波(OpenCV 的 filter2D 实际执行的是相关运算)
# 计算响应图的某种统计特征,例如将图像分成4x4网格,计算每个网格的均值
h, w = response.shape
cell_h, cell_w = h//4, w//4
for i in range(4):
for j in range(4):
cell = response[i*cell_h:(i+1)*cell_h, j*cell_w:(j+1)*cell_w]
feature_vector.append(cell.mean())
6. 最终得到一个 24(滤波器) * 16(网格) = 384 维的特征向量。📝 动手练一练
- 概念辨析:回顾本节学习的LBP和之前学习的SIFT,请总结它们在根本思路上最主要的区别是什么?这种区别导致了它们在应用场景上有何不同?
👉 点击查看参考答案
主要区别:
- 特征检测的出发点:SIFT(以及SURF、ORB)是基于关键点的描述子。它们首先需要在图像中检测出稳定的、具有显著性的局部特征点(如角点、斑点),然后仅对这些稀疏的点进行特征描述。而LBP是基于像素的纹理描述子,它对图像中每一个像素(或密集采样的像素)都计算一个描述值,不依赖于预先检测的特征点。
- 描述的本质:SIFT等描述的是关键点邻域内的梯度方向分布,形成高维向量(如128维)。LBP描述的是单个像素与其直接邻域像素的灰度大小关系,形成的编码通常通过统计直方图来表达区域纹理。
- 不变性侧重:SIFT在设计上追求全面的尺度、旋转、仿射、光照不变性。LBP天然具有灰度不变性,并通过技巧实现旋转不变性,但其对尺度和仿射变换的鲁棒性较弱。
应用场景差异:
- SIFT类:更适合需要精确匹配和定位的任务,如图像拼接、三维重建、基于关键点的物体识别与姿态估计。因为它们描述的是稀疏的、具有代表性的局部结构。
- LBP:更适合纹理分析、分类和分割任务,如人脸识别(将人脸视为纹理模式)、材质分类、缺陷检测。因为它提供的是密集的、全局或区域的纹理统计信息。
- 手算练习:给定一个中心像素灰度值为100的3x3邻域,其周围8个像素的灰度值按顺时针顺序为:[120, 110, 90, 80, 85, 95, 105, 115]。请计算: a) 该中心像素的基本LBP值(二进制和十进制)。 b) 该模式的旋转不变LBP值(十进制)。
👉 点击查看参考答案
计算过程: a) 基本LBP值: 中心值=100。与邻域比较(≥中心为1,<中心为0): 120(1), 110(1), 90(0), 80(0), 85(0), 95(0), 105(1), 115(1) 得到二进制串:11000011。 转换为十进制:1*2^7 + 1*2^6 + 0*2^5 + 0*2^4 + 0*2^3 + 0*2^2 + 1*2^1 + 1*2^0 = 128 + 64 + 2 + 1 = 195。
b) 旋转不变LBP值: 原始串:11000011 (195) 循环左移1位:10000111 (135) 循环左移2位:00001111 (15) 循环左移3位:00011110 (30) 循环左移4位:00111100 (60) 循环左移5位:01111000 (120) 循环左移6位:11110000 (240) 循环左移7位:11100001 (225) 循环左移8位:11000011 (195) (回到原始)
所有可能值:[195, 135, 15, 30, 60, 120, 240, 225] 最小值为 15。
答案: a) 二进制:11000011,十进制:195。 b) 旋转不变LBP值:15。
本章小结
本节我们深入探讨了传统计算机视觉中两种重要的特征描述方法:局部二值模式(LBP)和Gabor滤波。
- LBP 以其简洁高效著称,通过比较像素与其圆形邻域的灰度相对大小,生成二进制编码来描述局部纹理。通过循环移位取最小值的策略,它实现了旋转不变性,并结合其天然的灰度不变性,在纹理分类和人脸识别等领域表现出色。其与SIFT等方法的根本区别在于,LBP是一种密集的、基于像素的纹理描述,而非基于稀疏关键点的描述。
- Gabor滤波 则从模拟人类视觉系统出发,通过高斯函数与正弦平面波的乘积,构造出在空域和频域都具有良好局部性的滤波器。通过组合不同方向和尺度的滤波器形成滤波器组,Gabor特征能够全面捕捉图像的纹理信息,对光照变化不敏感,非常适合于纹理分析。
这两种方法代表了深度学习兴起之前,研究者们从不同角度(局部二值关系和频率-方向分析)对图像特征进行手工设计的智慧。理解它们,有助于我们更好地把握图像特征的本质,并为后续学习深度学习如何自动学习这些特征打下坚实的基础。
📋 行动清单
- 理解核心机制:确保能清晰阐述LBP如何通过二值比较和循环移位实现灰度与旋转不变性;能说明Gabor滤波器如何由高斯和正弦波构成,并解释其多方向、多尺度的意义。
- 完成手算练习:独立完成LBP值的计算,特别是旋转不变版本的推导过程,加深对算法步骤的理解。
- 思考对比:在脑海中构建一个表格,对比LBP、Gabor与SIFT/SURF在基本思想、不变性、计算复杂度和典型应用场景上的异同。
—— 小象教研组