358 lines
11 KiB
Python
358 lines
11 KiB
Python
# coding: utf-8
|
||
# The code is written by Linghui
|
||
|
||
import numpy as np
|
||
from skimage import data
|
||
from matplotlib import pyplot as plt
|
||
import time
|
||
from PIL import Image
|
||
import spectral
|
||
import os
|
||
import warnings
|
||
warnings.filterwarnings('ignore')
|
||
|
||
# 导入GLCM计算模块
|
||
try:
|
||
# 尝试相对导入
|
||
from . import get_glcm
|
||
except ImportError:
|
||
try:
|
||
# 尝试绝对导入
|
||
import get_glcm
|
||
except ImportError:
|
||
# 如果都失败,尝试从当前目录导入
|
||
import sys
|
||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||
if current_dir not in sys.path:
|
||
sys.path.insert(0, current_dir)
|
||
import get_glcm
|
||
|
||
# 使用get_glcm模块进行GLCM计算
|
||
|
||
class SpatialFeatureConfig:
|
||
"""
|
||
空间特征提取配置类
|
||
"""
|
||
def __init__(self):
|
||
# GLCM参数
|
||
self.nbit = 64 # gray levels
|
||
self.mi = 0 # min gray
|
||
self.ma = 255 # max gray
|
||
self.slide_window = 7 # sliding window size
|
||
|
||
# GLCM计算参数
|
||
self.step = [2] # step distances
|
||
self.angle = [0] # angles in radians
|
||
|
||
# 数据处理参数
|
||
self.band_index = 25 # 要处理的波段索引
|
||
|
||
# 文件路径
|
||
self.image_path = r"C:\Program Files\Spectronon3\_internal\examples\leaf_small.bip.hdr"
|
||
|
||
# 输出设置
|
||
self.output_dir = "output"
|
||
self.save_dat = True # 是否保存为dat文件
|
||
self.save_png = False # 是否保存为PNG文件
|
||
|
||
def validate(self):
|
||
"""验证配置参数的有效性"""
|
||
if not os.path.exists(self.image_path):
|
||
raise FileNotFoundError(f"图像文件不存在: {self.image_path}")
|
||
|
||
if self.band_index < 0:
|
||
raise ValueError("波段索引不能为负数")
|
||
|
||
if self.nbit <= 0:
|
||
raise ValueError("nbit必须大于0")
|
||
|
||
if self.slide_window <= 0 or self.slide_window % 2 == 0:
|
||
raise ValueError("slide_window必须为正奇数")
|
||
|
||
return True
|
||
|
||
def run_glcm_analysis(config):
|
||
"""
|
||
执行GLCM纹理特征分析
|
||
|
||
参数:
|
||
config: SpatialFeatureConfig对象
|
||
"""
|
||
start = time.time()
|
||
|
||
print('---------------0. Parameter Setting-----------------')
|
||
|
||
# 验证配置
|
||
try:
|
||
config.validate()
|
||
print("配置验证通过")
|
||
except Exception as e:
|
||
print(f"配置验证失败: {e}")
|
||
return
|
||
|
||
print('-------------------1. Load Data---------------------')
|
||
|
||
# 加载高光谱数据
|
||
img, h, w = load_hyperspectral_data(config.image_path, config.band_index)
|
||
|
||
# 提取纹理特征
|
||
features_dict = extract_texture_features(img, config)
|
||
|
||
print('---------------4. Display and Result----------------')
|
||
print(f'使用的波段: {config.band_index}')
|
||
print(f'GLCM参数: distances={config.step}, angles={[f"{a*180/np.pi:.0f}°" for a in config.angle]}')
|
||
print(f'图像尺寸: {h}x{w}')
|
||
print(f'处理时间: {time.time() - start:.2f} 秒')
|
||
|
||
# 保存特征数据
|
||
if config.save_dat:
|
||
output_filename = f"spatial_features_band{config.band_index}"
|
||
output_path = os.path.join(config.output_dir, output_filename)
|
||
save_features_to_dat(features_dict, output_path, config)
|
||
|
||
print('GLCM纹理特征分析完成!')
|
||
return features_dict
|
||
|
||
|
||
|
||
|
||
|
||
def load_hyperspectral_data(image_path, band_index=None):
|
||
"""
|
||
加载高光谱数据并可选地提取指定波段
|
||
|
||
参数:
|
||
- image_path: 高光谱图像文件路径
|
||
- band_index: 要提取的波段索引,如果为None则返回整个图像
|
||
|
||
返回:
|
||
- img: 处理后的图像数据
|
||
- h, w: 图像尺寸
|
||
"""
|
||
print(f'正在读取高光谱图像: {image_path}')
|
||
|
||
try:
|
||
# 使用spectral库读取ENVI格式
|
||
img_obj = spectral.open_image(image_path)
|
||
hyperspectral_data = img_obj.load()
|
||
print(f'使用spectral库读取成功,数据形状: {hyperspectral_data.shape}')
|
||
except Exception as e:
|
||
print(f'spectral库读取失败: {e}')
|
||
raise ValueError(f"无法读取图像文件: {image_path}")
|
||
|
||
# 检查数据维度
|
||
if len(hyperspectral_data.shape) != 3:
|
||
raise ValueError(f"高光谱数据应该是3维的,当前形状: {hyperspectral_data.shape}")
|
||
|
||
h, w, bands = hyperspectral_data.shape
|
||
print(f'图像尺寸: {h}x{w}, 波段数: {bands}')
|
||
|
||
# 如果指定了波段索引,提取该波段
|
||
if band_index is not None:
|
||
if band_index < 0 or band_index >= bands:
|
||
raise ValueError(f"波段索引 {band_index} 超出范围 [0, {bands-1}]")
|
||
|
||
print(f'提取波段 {band_index}...')
|
||
img = hyperspectral_data[:, :, band_index].astype(np.float32)
|
||
|
||
# 归一化到0-255范围
|
||
img_min, img_max = img.min(), img.max()
|
||
if img_max > img_min:
|
||
img = np.uint8(255.0 * (img - img_min) / (img_max - img_min))
|
||
else:
|
||
img = np.zeros_like(img, dtype=np.uint8)
|
||
|
||
print(f'提取的波段数据范围: [{img_min:.2f}, {img_max:.2f}] -> [0, 255]')
|
||
else:
|
||
# 如果没有指定波段,返回第一个波段作为示例
|
||
print('未指定波段,使用第一个波段...')
|
||
img = hyperspectral_data[:, :, 0].astype(np.float32)
|
||
img_min, img_max = img.min(), img.max()
|
||
if img_max > img_min:
|
||
img = np.uint8(255.0 * (img - img_min) / (img_max - img_min))
|
||
else:
|
||
img = np.zeros_like(img, dtype=np.uint8)
|
||
|
||
return img, h, w
|
||
|
||
|
||
def save_features_to_dat(features_dict, output_path, config):
|
||
"""
|
||
将纹理特征保存为dat文件和对应的hdr文件
|
||
|
||
参数:
|
||
- features_dict: 包含各种纹理特征的字典
|
||
- output_path: 输出文件路径(不含扩展名)
|
||
- config: 配置对象
|
||
"""
|
||
try:
|
||
# 确保输出目录存在
|
||
output_dir = os.path.dirname(output_path)
|
||
if output_dir and not os.path.exists(output_dir):
|
||
os.makedirs(output_dir)
|
||
|
||
# 准备要保存的特征
|
||
feature_names = ['mean', 'variance', 'homogeneity', 'contrast',
|
||
'dissimilarity', 'entropy', 'energy', 'correlation', 'Auto_correlation']
|
||
|
||
# 将所有特征堆叠成多波段数据
|
||
feature_list = []
|
||
band_names = []
|
||
|
||
for feature_name in feature_names:
|
||
if feature_name in features_dict:
|
||
feature_data = features_dict[feature_name]
|
||
feature_list.append(feature_data)
|
||
band_names.append(feature_name)
|
||
|
||
if not feature_list:
|
||
print("没有找到要保存的特征数据")
|
||
return
|
||
|
||
# 堆叠所有特征为多波段数组
|
||
stacked_features = np.stack(feature_list, axis=-1)
|
||
print(f"特征数据形状: {stacked_features.shape}")
|
||
|
||
# 保存为dat文件(二进制格式)
|
||
dat_path = f"{output_path}.dat"
|
||
with open(dat_path, 'wb') as f:
|
||
# 将数据转换为float32格式保存
|
||
stacked_features.astype(np.float32).tofile(f)
|
||
|
||
print(f"特征数据已保存到: {dat_path}")
|
||
|
||
# 保存对应的hdr文件
|
||
hdr_path = f"{output_path}.hdr"
|
||
_save_hdr_file(hdr_path, stacked_features.shape, config, band_names)
|
||
|
||
print(f"头文件已保存到: {hdr_path}")
|
||
|
||
except Exception as e:
|
||
print(f"保存dat文件时出错: {e}")
|
||
|
||
|
||
def _save_hdr_file(hdr_path, data_shape, config, band_names):
|
||
"""
|
||
保存ENVI头文件
|
||
|
||
参数:
|
||
- hdr_path: 头文件路径
|
||
- data_shape: 数据形状 (height, width, bands)
|
||
- config: 配置对象
|
||
- band_names: 波段名称列表
|
||
"""
|
||
height, width, bands = data_shape
|
||
|
||
# 计算数据范围(为每个波段计算)
|
||
data_range_info = f"Data ranges vary per band (normalized 0-1)"
|
||
|
||
header_content = f"""ENVI
|
||
description = {{Spatial Texture Features [Band: {config.band_index}, Window: {config.slide_window}, Step: {config.step}, Angles: {[f"{a*180/np.pi:.0f}°" for a in config.angle]}]}}
|
||
samples = {width}
|
||
lines = {height}
|
||
bands = {bands}
|
||
header offset = 0
|
||
file type = ENVI Standard
|
||
data type = 4
|
||
interleave = bip
|
||
byte order = 0
|
||
band names = {{{', '.join(band_names)}}}
|
||
data range = {{{data_range_info}}}
|
||
GLCM_params = {{nbit: {config.nbit}, window: {config.slide_window}, step: {config.step}, angles: {config.angle}}}
|
||
"""
|
||
|
||
with open(hdr_path, 'w', encoding='utf-8') as f:
|
||
f.write(header_content)
|
||
|
||
|
||
def extract_texture_features(img, config):
|
||
"""
|
||
提取纹理特征
|
||
|
||
参数:
|
||
- img: 输入图像
|
||
- config: 配置对象
|
||
|
||
返回:
|
||
- features_dict: 包含各种纹理特征的字典
|
||
"""
|
||
print('------------------2. 计算GLCM特征---------------------')
|
||
img = img.squeeze()
|
||
|
||
# 使用get_glcm计算GLCM特征
|
||
glcm = get_glcm.calcu_glcm(img, config.mi, config.ma, config.nbit,
|
||
config.slide_window, config.step, config.angle)
|
||
|
||
print('GLCM计算完成!')
|
||
print('-----------------3. 提取纹理特征-------------------')
|
||
|
||
# 提取纹理特征
|
||
# 使用最后一个step和angle的特征
|
||
i, j = len(config.step)-1, len(config.angle)-1
|
||
glcm_cut = glcm[:, :, i, j, :, :]
|
||
|
||
features_dict = {
|
||
'image_shape': img.shape,
|
||
'mean': get_glcm.calcu_glcm_mean(glcm_cut, config.nbit),
|
||
'variance': get_glcm.calcu_glcm_variance(glcm_cut, config.nbit),
|
||
'homogeneity': get_glcm.calcu_glcm_homogeneity(glcm_cut, config.nbit),
|
||
'contrast': get_glcm.calcu_glcm_contrast(glcm_cut, config.nbit),
|
||
'dissimilarity': get_glcm.calcu_glcm_dissimilarity(glcm_cut, config.nbit),
|
||
'entropy': get_glcm.calcu_glcm_entropy(glcm_cut, config.nbit),
|
||
'energy': get_glcm.calcu_glcm_energy(glcm_cut, config.nbit),
|
||
'correlation': get_glcm.calcu_glcm_correlation(glcm_cut, config.nbit),
|
||
'Auto_correlation': get_glcm.calcu_glcm_Auto_correlation(glcm_cut, config.nbit)
|
||
}
|
||
|
||
return features_dict
|
||
|
||
|
||
def main():
|
||
"""主函数:执行空间特征提取流程"""
|
||
start = time.time()
|
||
|
||
print('---------------0. Parameter Setting-----------------')
|
||
|
||
# 创建配置对象
|
||
config = SpatialFeatureConfig()
|
||
|
||
# 可以在这里修改配置参数
|
||
# config.band_index = 30 # 修改波段索引
|
||
# config.step = [2, 4, 8] # 修改步长
|
||
# config.angle = [0, np.pi/4, np.pi/2, np.pi*3/4] # 修改角度
|
||
|
||
# 验证配置
|
||
try:
|
||
config.validate()
|
||
print("配置验证通过")
|
||
except Exception as e:
|
||
print(f"配置验证失败: {e}")
|
||
return
|
||
|
||
print('-------------------1. Load Data---------------------')
|
||
|
||
# 加载高光谱数据
|
||
img, h, w = load_hyperspectral_data(config.image_path, config.band_index)
|
||
|
||
# 提取纹理特征
|
||
features_dict = extract_texture_features(img, config)
|
||
|
||
print('---------------4. Display and Result----------------')
|
||
print(f'使用的波段: {config.band_index}')
|
||
print(f'GLCM参数: distances={config.step}, angles={[f"{a*180/np.pi:.0f}°" for a in config.angle]}')
|
||
print(f'图像尺寸: {h}x{w}')
|
||
print(f'处理时间: {time.time() - start:.2f} 秒')
|
||
|
||
# 保存特征数据
|
||
if config.save_dat:
|
||
output_filename = f"spatial_features_band{config.band_index}"
|
||
output_path = os.path.join(config.output_dir, output_filename)
|
||
save_features_to_dat(features_dict, output_path, config)
|
||
|
||
print('处理完成!')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|
||
|
||
|