基于python的遥感影像灰色关联矩阵纹理特征计算

news2025/1/22 8:44:05

遥感影像纹理特征是描述影像中像素间空间关系的统计特征,常用于地物分类、目标识别和变化检测等遥感应用中。常见的纹理特征计算方式包括灰度共生矩阵(GLCM)、灰度差异矩阵(GLDM)、灰度不均匀性矩阵(GLRLM)等。这些方法通过对像素灰度值的统计分析,揭示了影像中像素间的空间分布规律,从而提取出纹理特征。

常用的纹理特征包括

1. 对比度(Contrast): 描述图像中灰度级之间的对比程度。

2. 同质性(Homogeneity): 描述图像中灰度级分布的均匀程度。

3. 熵(Entropy): 描述图像中灰度级分布的不确定性。

4. 方差(Variance): 描述图像中灰度级的离散程度。

5. 相关性(Correlation): 描述图像中像素间的灰度值相关程度。

6. 能量(Energy): 是灰度共生矩阵各元素值的平方和。

7. 均值(Mean): 描述图像中每个灰度级出现的平均频率。

8. 不相似度(Dissimilarity): 描述图像中不同灰度级之间的差异程度。

这些纹理特征可以通过GLCM等方法计算得到,用于描述遥感影像的纹理信息,对于遥感影像的分类和分析具有重要意义。

在ENVI中可以直接使用工具箱的工具直接计算得到输出影像

在这里插入图片描述

此外也可以使用python的skimage.feature库,这个库的greycoprops函数只有contrast、dissimilarity、homogeneity、ASM、energy和correlation这几个特征没有均值、方差和熵。因此下面对这个库的原函数进行修正,添加了方差等计算的greycoprops函数

def greycoprops(P, prop='contrast'):
    """Calculate texture properties of a GLCM.

    Compute a feature of a grey level co-occurrence matrix to serve as
    a compact summary of the matrix. The properties are computed as
    follows:

    - 'contrast': :math:`\\sum_{i,j=0}^{levels-1} P_{i,j}(i-j)^2`
    - 'dissimilarity': :math:`\\sum_{i,j=0}^{levels-1}P_{i,j}|i-j|`
    - 'homogeneity': :math:`\\sum_{i,j=0}^{levels-1}\\frac{P_{i,j}}{1+(i-j)^2}`
    - 'ASM': :math:`\\sum_{i,j=0}^{levels-1} P_{i,j}^2`
    - 'energy': :math:`\\sqrt{ASM}`
    - 'correlation':
        .. math:: \\sum_{i,j=0}^{levels-1} P_{i,j}\\left[\\frac{(i-\\mu_i) \\
                  (j-\\mu_j)}{\\sqrt{(\\sigma_i^2)(\\sigma_j^2)}}\\right]

    Each GLCM is normalized to have a sum of 1 before the computation of texture
    properties.

    Parameters
    ----------
    P : ndarray
        Input array. `P` is the grey-level co-occurrence histogram
        for which to compute the specified property. The value
        `P[i,j,d,theta]` is the number of times that grey-level j
        occurs at a distance d and at an angle theta from
        grey-level i.
    prop : {'contrast', 'dissimilarity', 'homogeneity', 'energy', \
            'correlation', 'ASM'}, optional
        The property of the GLCM to compute. The default is 'contrast'.

    Returns
    -------
    results : 2-D ndarray
        2-dimensional array. `results[d, a]` is the property 'prop' for
        the d'th distance and the a'th angle.

    References
    ----------
    .. [1] The GLCM Tutorial Home Page,
           http://www.fp.ucalgary.ca/mhallbey/tutorial.htm

    Examples
    --------
    Compute the contrast for GLCMs with distances [1, 2] and angles
    [0 degrees, 90 degrees]

    >>> image = np.array([[0, 0, 1, 1],
    ...                   [0, 0, 1, 1],
    ...                   [0, 2, 2, 2],
    ...                   [2, 2, 3, 3]], dtype=np.uint8)
    >>> g = greycomatrix(image, [1, 2], [0, np.pi/2], levels=4,
    ...                  normed=True, symmetric=True)
    >>> contrast = greycoprops(g, 'contrast')
    >>> contrast
    array([[0.58333333, 1.        ],
           [1.25      , 2.75      ]])

    """
    check_nD(P, 4, 'P')

    (num_level, num_level2, num_dist, num_angle) = P.shape
    if num_level != num_level2:
        raise ValueError('num_level and num_level2 must be equal.')
    if num_dist <= 0:
        raise ValueError('num_dist must be positive.')
    if num_angle <= 0:
        raise ValueError('num_angle must be positive.')

    # normalize each GLCM
    P = P.astype(np.float32)
    glcm_sums = np.apply_over_axes(np.sum, P, axes=(0, 1))
    glcm_sums[glcm_sums == 0] = 1
    P /= glcm_sums

    # create weights for specified property
    I, J = np.ogrid[0:num_level, 0:num_level]
    if prop == 'contrast':
        weights = (I - J) ** 2
    elif prop == 'dissimilarity':
        weights = np.abs(I - J)
    elif prop == 'homogeneity':
        weights = 1. / (1. + (I - J) ** 2)
    elif prop in ['ASM', 'energy', 'correlation', 'entropy', 'mean', 'variance']:
        pass
    else:
        raise ValueError('%s is an invalid property ?' % (prop))

    # compute property for each GLCM
    if prop == 'energy':
        asm = np.apply_over_axes(np.sum, (P ** 2), axes=(0, 1))[0, 0]
        results = np.sqrt(asm)
    elif prop == 'ASM':
        results = np.apply_over_axes(np.sum, (P ** 2), axes=(0, 1))[0, 0]
    elif prop == 'entropy':
        results = np.apply_over_axes(np.sum, -P * np.log10(P+0.00001), axes=(0, 1))[0, 0]
    elif prop == 'correlation':
        results = np.zeros((num_dist, num_angle), dtype=np.float64)
        I = np.array(range(num_level)).reshape((num_level, 1, 1, 1))
        J = np.array(range(num_level)).reshape((1, num_level, 1, 1))
        diff_i = I - np.apply_over_axes(np.sum, (I * P), axes=(0, 1))[0, 0]
        diff_j = J - np.apply_over_axes(np.sum, (J * P), axes=(0, 1))[0, 0]

        std_i = np.sqrt(np.apply_over_axes(np.sum, (P * (diff_i) ** 2),
                                           axes=(0, 1))[0, 0])
        std_j = np.sqrt(np.apply_over_axes(np.sum, (P * (diff_j) ** 2),
                                           axes=(0, 1))[0, 0])
        cov = np.apply_over_axes(np.sum, (P * (diff_i * diff_j)),
                                 axes=(0, 1))[0, 0]

        # handle the special case of standard deviations near zero
        mask_0 = std_i < 1e-15
        mask_0[std_j < 1e-15] = True
        results[mask_0] = 1

        # handle the standard case
        mask_1 = mask_0 == False
        results[mask_1] = cov[mask_1] / (std_i[mask_1] * std_j[mask_1])
    elif prop in ['contrast', 'dissimilarity', 'homogeneity']:
        weights = weights.reshape((num_level, num_level, 1, 1))
        results = np.apply_over_axes(np.sum, (P * weights), axes=(0, 1))[0, 0]
    elif prop == 'mean':
        I = np.array(range(num_level)).reshape((num_level, 1, 1, 1))
        results = np.apply_over_axes(np.sum, (P * I), axes=(0, 1))[0, 0]
    elif prop == 'variance':
        I = np.array(range(num_level)).reshape((num_level, 1, 1, 1))
        mean = np.apply_over_axes(np.sum, (P * I), axes=(0, 1))[0, 0]
        results = np.apply_over_axes(np.sum, (P * (I - mean) ** 2), axes=(0, 1))[0, 0]
        
    return results

具体影像分析时还需要考虑灰色关联矩阵计算的角度、步长、灰度级数和窗口大小。以一张多光谱影像为例,相关性使用了greycoprops,其他的特征使用公式计算,实际上导入上面的新greycoprops函数后其他特征都可以用函数计算,具体代码如下,输出结果为多光谱各个波段的纹理特征的多通道影像:

from osgeo import gdal, osr
import os
import numpy as np
import cv2
from skimage import data
from skimage.feature import greycoprops

def export_tif(out_tif_name, var_lat, var_lon, NDVI, bandname=None):
    # 判断数组维度
    if len(NDVI.shape) > 2:
        im_bands, im_height, im_width = NDVI.shape
    else:
        im_bands, (im_height, im_width) = 1, NDVI.shape
    LonMin, LatMax, LonMax, LatMin = [min(var_lon), max(var_lat), max(var_lon), min(var_lat)]
    # 分辨率计算
    Lon_Res = (LonMax - LonMin) / (float(im_width) - 1)
    Lat_Res = (LatMax - LatMin) / (float(im_height) - 1)
    driver = gdal.GetDriverByName('GTiff')
    out_tif = driver.Create(out_tif_name, im_width, im_height, im_bands, gdal.GDT_Float32)  # 创建框架

    # 设置影像的显示范围
    # Lat_Res一定要是-的
    geotransform = (LonMin, Lon_Res, 0, LatMax, 0, -Lat_Res)
    out_tif.SetGeoTransform(geotransform)

    # 获取地理坐标系统信息,用于选取需要的地理坐标系统
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(4326)  # 定义输出的坐标系为"WGS 84",AUTHORITY["EPSG","4326"]
    out_tif.SetProjection(srs.ExportToWkt())  # 给新建图层赋予投影信息

    # 数据写出
    if im_bands == 1:
        out_tif.GetRasterBand(1).WriteArray(NDVI)
    else:
        for bands in range(im_bands):
            # 为每个波段设置名称
            if bandname is not None:
                out_tif.GetRasterBand(bands + 1).SetDescription(bandname[bands])
            out_tif.GetRasterBand(bands + 1).WriteArray(NDVI[bands])
    
    # 生成 ENVI HDR 文件
    hdr_file = out_tif_name.replace('.tif', '.hdr')
    with open(hdr_file, 'w') as f:
        f.write('ENVI\n')
        f.write('description = {Generated by export_tif}\n')
        f.write('samples = {}\n'.format(im_width))
        f.write('lines   = {}\n'.format(im_height))
        f.write('bands   = {}\n'.format(im_bands))
        f.write('header offset = 0\n')
        f.write('file type = ENVI Standard\n')
        f.write('data type = {}\n'.format(gdal.GetDataTypeName(out_tif.GetRasterBand(1).DataType)))
        f.write('interleave = bsq\n')
        f.write('sensor type = Unknown\n')
        f.write('byte order = 0\n')
        band_names_str = ', '.join(str(band) for band in bandname)
        f.write('band names = {%s}\n'%(band_names_str))
       
    del out_tif
                                                        
                                                        
def fast_glcm(img, vmin=0, vmax=255, levels=8, kernel_size=5, distance=1.0, angle=0.0):
    '''
    Parameters
    ----------
    img: array_like, shape=(h,w), dtype=np.uint8
        input image
    vmin: int
        minimum value of input image
    vmax: int
        maximum value of input image
    levels: int
        number of grey-levels of GLCM
    kernel_size: int
        Patch size to calculate GLCM around the target pixel
    distance: float
        pixel pair distance offsets [pixel] (1.0, 2.0, and etc.)
    angle: float
        pixel pair angles [degree] (0.0, 30.0, 45.0, 90.0, and etc.)

    Returns
    -------
    Grey-level co-occurrence matrix for each pixels
    shape = (levels, levels, h, w)
    '''

    mi, ma = vmin, vmax
    ks = kernel_size
    h,w = img.shape

    # digitize
    bins = np.linspace(mi, ma+1, levels+1)
    gl1 = np.digitize(img, bins) - 1

    # make shifted image
    dx = distance*np.cos(np.deg2rad(angle))
    dy = distance*np.sin(np.deg2rad(-angle))
    mat = np.array([[1.0,0.0,-dx], [0.0,1.0,-dy]], dtype=np.float32)
    gl2 = cv2.warpAffine(gl1, mat, (w,h), flags=cv2.INTER_NEAREST,
                         borderMode=cv2.BORDER_REPLICATE)

    # make glcm
    glcm = np.zeros((levels, levels, h, w), dtype=np.uint8)
    for i in range(levels):
        for j in range(levels):
            mask = ((gl1==i) & (gl2==j))
            glcm[i,j, mask] = 1

    kernel = np.ones((ks, ks), dtype=np.uint8)
    for i in range(levels):
        for j in range(levels):
            glcm[i,j] = cv2.filter2D(glcm[i,j], -1, kernel)
    # 灰色关联矩阵归一化,之后结果与ENVI相同
    glcm = glcm.astype(np.float32)
    glcm_sums = np.apply_over_axes(np.sum, glcm, axes=(0, 1))
    glcm_sums[glcm_sums == 0] = 1
    glcm /= glcm_sums
    return glcm


def fast_glcm_texture(img, vmin=0, vmax=255, levels=8, ks=5, distance=1.0, angle=0.0):
    '''
    calc glcm texture
    '''
    h,w = img.shape
    glcm = fast_glcm(img, vmin, vmax, levels, ks, distance, angle)
    mean = np.zeros((h,w), dtype=np.float32)
    var = np.zeros((h,w), dtype=np.float32)
    cont = np.zeros((h,w), dtype=np.float32)
    diss = np.zeros((h,w), dtype=np.float32)
    homo = np.zeros((h,w), dtype=np.float32)
    asm = np.zeros((h,w), dtype=np.float32)
    ent = np.zeros((h,w), dtype=np.float32)
    cor = np.zeros((h, w), dtype=np.float32)
    
    for i in range(levels):
        for j in range(levels):
            mean += glcm[i,j] * i
            homo += glcm[i,j] / (1.+(i-j)**2)
            asm  += glcm[i,j]**2
            cont += glcm[i,j] * (i-j)**2
            diss += glcm[i,j] * np.abs(i-j)
            ent -= glcm[i, j] * np.log10(glcm[i, j] + 0.00001)
    for i in range(levels):
        for j in range(levels):
            var += glcm[i, j] * (i - mean)**2
    greycoprops(glcm, 'correlation')  # 上面计算的几个特征均可这样写
    cor[cor == 1.0] = 0.
    homo[homo == 1.] = 0
    asm[asm == 1.] = 0
    return [mean, var, cont, diss, homo, asm, ent, cor]

# 遥感影像
image = r'..\path\to\your\file\image.tif'
# 文件输出路径
out_path = r'..\output\path'
dataset = gdal.Open(image)  # 读取数据
adfGeoTransform = dataset.GetGeoTransform()
nXSize = dataset.RasterXSize  # 列数
nYSize = dataset.RasterYSize  # 行数
im_data = dataset.ReadAsArray(0, 0, nXSize, nYSize)  # 读取数据
im_data[im_data==65536] = 0
var_lat = []  # 纬度
var_lon = []  # 经度
for j in range(nYSize):
    lat = adfGeoTransform[3] + j * adfGeoTransform[5]
    var_lat.append(lat)
for i in range(nXSize):
    lon = adfGeoTransform[0] + i * adfGeoTransform[1]
    var_lon.append(lon)
result = []
band_name=[]
for i, img in enumerate(im_data):
    img = np.uint8(255.0 * (img - np.min(img))/(np.max(img) - np.min(img))) # normalization
    fast_result = fast_glcm_texture(img, vmin=0, vmax=255, levels=32, ks=3, distance=1.0, angle=0.0)
    result += fast_result
    variable_names = ['mean', 'variance', 'contrast', 'dissimilarity', 'homogeneity', 'ASM', 'entropy', 'correlation']
    for names in variable_names:
        band_name.append('band_'+str(i+1)+'_'+names)
name = os.path.splitext(os.path.basename(image))[0]
export_tif(os.path.join(out_path, '%s_TF.tif' % name), var_lat, var_lon, np.array(result), bandname=band_name)  
print('over')

总结

目前熵值特征计算结果与ENVI有差异,不过只是数值差异,使用影像打开的结果显示一致,说明只是值的范围差异,不影响分析,其他特征均与ENVI的计算结果一致

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1454839.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯Java组备赛(二)

题目1 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt();int max Integer.MIN_VALUE;int min Integer.MAX_VALUE;double sum 0;for(int i0;i<n;i) {int x sc.nextInt()…

IDEA配置Lombok不起作用

IDEA配置Lombok不起作用 我们通常会只用lombok来简化代码。但是使用IDEA的lombok插件时&#xff0c;Lombok并不起作用。 可以按照如下操作。 FIle -> settings ->build,excecution,deployment–>compiler–>annotation processors勾选上 enable annotation proc…

可视化低代码表单设计器

JNPF 表单设计器是一款在线可视化表单建模工具&#xff0c;基于VueSpringboot技术开发&#xff0c;具有组件丰富、操作简单、所见即所得等特性&#xff0c;既能够设计普通的数据录入表单&#xff0c;也能够配合流程设计出各类审批流转表单。 应用地址&#xff1a;https://www.j…

单调栈题目总结

单调栈 496. 下一个更大元素 I 503. 下一个更大元素 II 739. 每日温度 6227. 下一个更大元素 IV 模版归纳 「单调栈」顾名思义就是具有单调性的栈结构&#xff0c;一般常用于找到下一个更大的元素&#xff0c;即当前元素右侧第一个更大的元素 看下面一个例子&#xff1a…

【C++学习手札】多态:掌握面向对象编程的动态绑定与继承机制(初识)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;世界上的另一个我 1:02━━━━━━️&#x1f49f;──────── 3:58 &#x1f504; ◀️ ⏸ ▶️ ☰ &am…

应用回归分析:岭回归

岭回归&#xff0c;也称为Tikhonov正则化&#xff0c;是一种专门用于处理多重共线性问题的回归分析技术。多重共线性是指模型中的自变量高度相关&#xff0c;这种高度的相关性会导致普通最小二乘法&#xff08;OLS&#xff09;估计的回归系数变得非常不稳定&#xff0c;甚至无法…

CDN缓存有什么作用?

CDN缓存是内容分发网络的核心技术之一&#xff0c;它的作用在于通过将内容缓存在边缘服务器上&#xff0c;提高内容的访问速度和可用性。以下是CDN缓存的几个主要作用&#xff1a; 加速内容的访问速度 CDN缓存通过将内容缓存在距离用户更近的边缘服务器上&#xff0c;减少了内…

【C++】C++入门—初识构造函数 , 析构函数,拷贝构造函数,赋值运算符重载

C入门 六个默认成员函数1 构造函数语法特性 2 析构函数语法特性 3 拷贝构造函数特性 4 赋值运算符重载运算符重载赋值运算符重载特例&#xff1a;前置 与 后置前置&#xff1a;返回1之后的结果后置&#xff1a; Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&…

基于SSM的电影购票系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的电影购票系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spri…

这才是大学生该做的副业,别再痴迷于游戏了!

感谢大家一直以来的支持和关注&#xff0c;尤其是在我的上一个公众号被关闭后&#xff0c;仍然选择跟随我的老粉丝们&#xff0c;你们的支持是我继续前行的动力。为了回馈大家长期以来的陪伴&#xff0c;我决定分享一些实用的干货&#xff0c;这些都是我亲身实践并且取得成功的…

【蓝桥杯】算法模板题(Floyd算法)

一.弗洛伊德算法 用途&#xff1a;用来求解多源点最短路径问题。 思想&#xff1a;Floyd算法又称为插点法&#xff0c;是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法。 主要步骤&#xff1a; 1&#xff09;初始化&#xff1a;使用邻接矩阵初始化dis…

离线数仓(三)【业务日志采集平台搭建】

前言 上一篇我们搭建完了用户行为日志数据的采集平台&#xff0c;其实也就是用两个 flume 采集数据到Kafka 中&#xff08;这种结构只有 source 和 channel 没有 sink&#xff09; 。离线数仓中的数据除了用户日志&#xff0c;还有就是业务数据了。 1、电商业务简介 1.1 电商…

Android电量相关知识

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、 查看耗电情况3.1 注册广播 ACTION…

数据结构——lesson3单链表介绍及实现

目录 1.什么是链表&#xff1f; 2.链表的分类 &#xff08;1&#xff09;无头单向非循环链表&#xff1a; &#xff08;2&#xff09;带头双向循环链表&#xff1a; 3.单链表的实现 &#xff08;1&#xff09;单链表的定义 &#xff08;2&#xff09;动态创建节点 &#…

【C++初阶】新手值得一做vector的oj题

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

SQL补充:窗口函数

SQL窗口函数 结合order by关键词和limit关键词是可以解决很多的topN问题&#xff0c;比如从二手房数据集中查询出某个地区的最贵的10套房&#xff0c;从电商交易数据集中查询出实付金额最高的5笔交易&#xff0c;从学员信息表中查询出年龄最小的3个学员等。 但是&#xff0c;…

端口号被占用怎么解决

1、快捷键"winR"打开运行&#xff0c;在其中输入"cmd"命令&#xff0c;回车键打开命令提示符。 2、进入窗口后&#xff0c;输入"netstat -ano"命令&#xff0c;可以用来查看所有窗口被占用的情况。 比如端口号为7680的端口被占用了&#xff0c…

Java+Swing+Txt实现通讯录管理系统

目录 一、系统介绍 1.开发环境 2.技术选型 3.功能模块 4.系统功能 1.系统登录 2.查看联系人 3.新增联系人 4.修改联系人 5.删除联系人 5.工程结构 二、系统展示 1.登录页面 2.主页面 3.查看联系人 4.新增联系人 5.修改联系人 三、部分代码 Login FileUtils …

c高级 函数+Makefile

一、作业 1.写一个函数&#xff0c;输出当前用户的uid和gid&#xff0c;并使用变量接收结果 #!/bin/bash function fun(){retid -uret1id -gecho $ret $ret1 } retfun echo $ret二、练习回顾 1.分文件编译&#xff08;实现冒泡排序&#xff09; 正确的&#xff1a;将数组的…

day32打卡

day32打卡 122. 买卖股票的最佳时机 II 解法&#xff0c;贪心&#xff1a;局部&#xff0c;收集每天的正利润-》整体&#xff0c;获取最大利润 从第0天到第3天&#xff0c;利润为&#xff1a;price[3] - price[0]&#xff0c;也可以是(price[3] - price[2]) (price[2] - pr…