灰度共生矩阵(GLCM)纹理特征实战:Python代码提取7个Haralick特征
在计算机视觉和图像处理领域,纹理分析是一个重要的研究方向。纹理特征能够描述图像中像素之间的空间关系,为图像分类、目标识别和场景理解等任务提供关键信息。本文将深入探讨灰度共生矩阵(GLCM)这一经典纹理特征提取方法,并提供可直接复用的Python实现代码。
1. GLCM纹理特征基础
灰度共生矩阵(Gray-Level Co-occurrence Matrix,GLCM)是1973年由Haralick等人提出的一种纹理分析方法。它通过统计图像中特定空间关系的像素对出现的频率来描述纹理特征。GLCM的核心思想是捕捉图像中灰度值的空间分布规律。
1.1 GLCM的基本原理
GLCM计算的是在特定空间关系(距离d和方向θ)下,灰度值为i的像素与灰度值为j的像素同时出现的概率。常见的计算方向包括:
- 0°(水平方向)
- 45°(对角线方向)
- 90°(垂直方向)
- 135°(反对角线方向)
GLCM矩阵的大小取决于图像的灰度级数。对于一个8位灰度图像(256级),如果直接计算,GLCM会非常大(256×256)。因此,通常会对灰度级进行压缩,例如降到16级或8级。
1.2 Haralick特征简介
Haralick从GLCM中提取了14个统计量来描述纹理特征。本文重点实现以下7个最常用的特征:
- 对比度(Contrast):度量图像局部变化的程度,反映纹理的清晰度
- 能量(Energy):又称角二阶矩,反映图像灰度分布的均匀性
- 熵(Entropy):衡量图像纹理的随机性和复杂度
- 逆方差(Inverse Difference Moment):反映图像局部均匀性
- 相关性(Correlation):度量灰度线性依赖关系
- 均值(Mean):GLCM中像素对的平均灰度值
- 标准差(Standard Deviation):GLCM中像素对灰度值的离散程度
2. Python实现GLCM特征提取
下面我们将实现一个完整的GLCM特征提取类,包含图像预处理、GLCM计算和特征提取三个主要部分。
2.1 图像预处理
首先,我们需要对输入图像进行预处理,包括灰度转换和灰度级压缩:
import numpy as np import cv2 import math from matplotlib import pyplot as plt class GLCMFeatureExtractor: def __init__(self, gray_level=16): self.gray_level = gray_level def preprocess_image(self, image_path): """读取并预处理图像""" # 读取图像 img = cv2.imread(image_path) if img is None: raise ValueError(f"无法读取图像: {image_path}") # 转换为灰度图 if len(img.shape) == 3: img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) else: img_gray = img # 调整图像大小(可选) img_gray = cv2.resize(img_gray, (img_gray.shape[1]//2, img_gray.shape[0]//2), interpolation=cv2.INTER_CUBIC) # 灰度级压缩 max_gray = np.max(img_gray) if max_gray > self.gray_level: img_gray = (img_gray * (self.gray_level-1) / max_gray).astype(np.uint8) return img_gray2.2 GLCM计算
接下来实现GLCM的核心计算函数:
def compute_glcm(self, img_gray, d=1, theta=0): """计算灰度共生矩阵 Args: img_gray: 灰度图像 d: 像素对距离 theta: 方向角度(0,45,90,135) Returns: glcm: 归一化的灰度共生矩阵 """ height, width = img_gray.shape glcm = np.zeros((self.gray_level, self.gray_level), dtype=np.float32) # 根据方向计算偏移量 if theta == 0: # 0度方向 dx, dy = d, 0 elif theta == 45: # 45度方向 dx, dy = d, -d elif theta == 90: # 90度方向 dx, dy = 0, d elif theta == 135: # 135度方向 dx, dy = -d, d else: raise ValueError("theta必须是0,45,90或135度") # 计算共生矩阵 for y in range(max(-dy, 0), height - max(dy, 0)): for x in range(max(-dx, 0), width - max(dx, 0)): i = img_gray[y, x] j = img_gray[y + dy, x + dx] glcm[i, j] += 1 # 归一化 glcm /= glcm.sum() return glcm2.3 特征提取
基于计算得到的GLCM,我们可以提取7个Haralick特征:
def extract_features(self, glcm): """从GLCM中提取7个Haralick特征""" # 初始化特征值 features = { 'contrast': 0.0, 'energy': 0.0, 'entropy': 0.0, 'homogeneity': 0.0, 'correlation': 0.0, 'mean': 0.0, 'std_dev': 0.0 } # 计算均值 mean_i = 0.0 mean_j = 0.0 for i in range(self.gray_level): for j in range(self.gray_level): mean_i += i * glcm[i, j] mean_j += j * glcm[i, j] # 计算标准差 std_i = 0.0 std_j = 0.0 for i in range(self.gray_level): for j in range(self.gray_level): std_i += (i - mean_i)**2 * glcm[i, j] std_j += (j - mean_j)**2 * glcm[i, j] std_i = math.sqrt(std_i) std_j = math.sqrt(std_j) # 计算各特征 for i in range(self.gray_level): for j in range(self.gray_level): p = glcm[i, j] if p > 0: # 对比度 features['contrast'] += (i - j)**2 * p # 能量 features['energy'] += p**2 # 熵 features['entropy'] -= p * math.log(p) # 同质性(逆方差) features['homogeneity'] += p / (1 + (i - j)**2) # 相关性 if std_i > 0 and std_j > 0: features['correlation'] += ((i - mean_i) * (j - mean_j) * p) / (std_i * std_j) # 均值 features['mean'] = mean_i # 标准差 features['std_dev'] = std_i return features3. 多方向GLCM特征融合
为了获得更全面的纹理描述,我们通常会计算多个方向的GLCM,然后将特征进行融合:
def extract_multi_direction_features(self, image_path, distances=[1], angles=[0, 45, 90, 135]): """提取多方向GLCM特征并融合""" img_gray = self.preprocess_image(image_path) all_features = [] for d in distances: for angle in angles: glcm = self.compute_glcm(img_gray, d, angle) features = self.extract_features(glcm) all_features.append(features) # 计算各方向特征的平均值 avg_features = {} for key in all_features[0].keys(): avg_features[key] = np.mean([f[key] for f in all_features]) return avg_features4. 应用案例与可视化
4.1 不同类型图像的纹理特征对比
我们选取三种不同类型的图像(织物、卫星图像和医学图像)进行纹理特征分析:
def compare_texture_features(image_paths): extractor = GLCMFeatureExtractor(gray_level=16) results = [] for path in image_paths: features = extractor.extract_multi_direction_features(path) results.append((path.split('/')[-1], features)) # 可视化对比 fig, axes = plt.subplots(1, len(image_paths), figsize=(15, 5)) for idx, (name, features) in enumerate(results): # 显示图像 img = cv2.imread(image_paths[idx]) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) axes[idx].imshow(img) axes[idx].axis('off') axes[idx].set_title(name) # 打印特征值 print(f"\n图像: {name}") for key, value in features.items(): print(f"{key:15}: {value:.4f}") plt.tight_layout() plt.show() return results4.2 特征可视化
我们可以将提取的特征进行可视化,更直观地比较不同纹理的特性:
def visualize_features(results): """可视化不同图像的纹理特征""" names = [name for name, _ in results] features = [feat for _, feat in results] feature_names = list(features[0].keys()) plt.figure(figsize=(12, 8)) for i, feat_name in enumerate(feature_names): plt.subplot(3, 3, i+1) values = [f[feat_name] for f in features] plt.bar(names, values) plt.title(feat_name) plt.xticks(rotation=45) plt.tight_layout() plt.show()5. 工程实践建议
在实际应用中,使用GLCM提取纹理特征时需要注意以下几点:
5.1 参数选择优化
灰度级数:
- 通常选择8、16或32级
- 级数太少会丢失纹理细节,太多会增加计算量
距离d的选择:
- 常用d=1,但对于大尺度纹理可能需要更大的d
- 可以尝试多个距离值并比较结果
方向选择:
- 至少选择0°、45°、90°和135°四个方向
- 对于有明显方向性的纹理,可以增加方向数量
5.2 性能优化技巧
对于大图像或实时应用,可以考虑以下优化方法:
def optimized_glcm_computation(img_gray, d=1, theta=0, gray_level=16): """优化版的GLCM计算""" # 使用向量化操作替代循环 height, width = img_gray.shape # 根据方向计算偏移 if theta == 0: dx, dy = d, 0 elif theta == 45: dx, dy = d, -d elif theta == 90: dx, dy = 0, d elif theta == 135: dx, dy = -d, d # 创建坐标网格 y, x = np.indices((height - abs(dy), width - abs(dx))) x_shift = x + dx y_shift = y + dy # 获取像素对 pixels_i = img_gray[y, x] pixels_j = img_gray[y_shift, x_shift] # 计算共生矩阵 glcm = np.zeros((gray_level, gray_level), dtype=np.float32) np.add.at(glcm, (pixels_i, pixels_j), 1) # 归一化 glcm /= glcm.sum() return glcm5.3 实际应用中的注意事项
图像尺寸一致性:
- 确保所有比较的图像具有相同或相似的尺寸
- 必要时进行归一化处理
光照条件:
- GLCM对光照变化敏感
- 应用前可进行直方图均衡化
特征选择:
- 不是所有Haralick特征都对特定任务有用
- 可以通过特征重要性分析选择最相关的特征
与其他特征结合:
- GLCM特征可以与其他纹理特征(如LBP)结合使用
- 也可以与颜色特征、形状特征等组合
6. 扩展应用:纹理分类
GLCM特征常用于图像分类任务。下面是一个简单的纹理分类示例:
from sklearn.svm import SVC from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report def texture_classification(image_paths, labels): """使用GLCM特征进行纹理分类""" extractor = GLCMFeatureExtractor() # 提取特征 X = [] for path in image_paths: features = extractor.extract_multi_direction_features(path) X.append(list(features.values())) X = np.array(X) y = np.array(labels) # 划分训练测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42) # 训练SVM分类器 clf = SVC(kernel='rbf', gamma='scale') clf.fit(X_train, y_train) # 评估 y_pred = clf.predict(X_test) print(classification_report(y_test, y_pred)) return clf7. 总结与展望
灰度共生矩阵作为一种经典的纹理分析方法,在多个领域都有广泛应用。本文实现的Python代码可以直接应用于各种图像分析任务,包括但不限于:
- 遥感图像分类
- 医学图像分析
- 工业表面检测
- 材质识别
- 生物特征识别
在实际项目中,GLCM特征通常需要与其他特征和方法结合使用,并根据具体任务进行调整和优化。随着深度学习的发展,传统纹理特征方法可以与深度特征相结合,发挥各自的优势。