遥感影像有效灰度归一化代码

Jun 15,2020   4297 words   16 min


一个简单的需求是对一幅遥感影像的灰度进行归一化,使归一化后的灰度分布在0到1之前,其实这是属于灰度拉伸的范畴,在之前的这篇博客中就介绍过相关内容。对于普通影像自然是使用OpenCV的API或基于OpenCV简单写个代码实现,如这篇博客的内容。但由于这里需要处理的是带有地理信息的遥感影像,自然需要使用GDAL。由于原理已经介绍过,这里直接放代码。

1.代码

灰度归一化代码如下,需要注意的地方都写在注释里了。对于影像读写部分的代码这里无需太多关心,毕竟不是我们这篇博客的主要研究内容,重点放在main()函数里就行。代码中读写影像部分是基于之前这篇博客的内容。如果真的感兴趣的话,可以参考。

# coding=utf-8
from osgeo import gdal
from gdalconst import *
import numpy as np


def readImage(img_path):
    band_data = []
    band_name = []

    # 以只读方式打开遥感影像
    dataset = gdal.Open(img_path, GA_ReadOnly)
    if dataset is None:
        print("Unable to open image file.")
        return band_data
    else:
        print("Open image file success.\n")

        # 读取地理变换参数
        param_geoTransform = dataset.GetGeoTransform()
        print "GeoTransform info:\n", param_geoTransform, "\n"

        # 读取投影信息
        param_proj = dataset.GetProjection()
        print "Projection info:\n", param_proj, "\n"

        # 读取波段数及影像大小
        bands_num = dataset.RasterCount
        print("Image height:" + dataset.RasterYSize.__str__() + " Image width:" + dataset.RasterXSize.__str__())
        print(bands_num.__str__() + " bands in total.")

        # 依次读取波段数据
        for i in range(bands_num):
            # 获取影像的第i+1个波段
            band_i = dataset.GetRasterBand(i + 1)

            # 获取影像第i+1个波段的描述(名称)
            name = band_i.GetDescription()
            band_name.append(name)

            # 读取第i+1个波段数据
            data = band_i.ReadAsArray(0, 0, band_i.XSize, band_i.YSize)
            band_data.append(data)

            print("band " + (i + 1).__str__() + " read success.")
            if name != "":
                print "Name:", name
        return band_data, param_geoTransform, param_proj, band_name


def writeImage(save_path, bands, geotrans=None, proj=None, names=None):
    projection = [
        # WGS84坐标系(EPSG:4326)
        """GEOGCS["WGS 84", DATUM["WGS_1984", SPHEROID["WGS 84", 6378137, 298.257223563, AUTHORITY["EPSG", "7030"]], AUTHORITY["EPSG", "6326"]], PRIMEM["Greenwich", 0, AUTHORITY["EPSG", "8901"]], UNIT["degree", 0.01745329251994328, AUTHORITY["EPSG", "9122"]], AUTHORITY["EPSG", "4326"]]""",
        # Pseudo-Mercator、球形墨卡托或Web墨卡托(EPSG:3857)
        """PROJCS["WGS 84 / Pseudo-Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Mercator_1SP"],PARAMETER["central_meridian",0],PARAMETER["scale_factor",1],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["X",EAST],AXIS["Y",NORTH],EXTENSION["PROJ4","+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs"],AUTHORITY["EPSG","3857"]]"""
    ]

    if bands is None or bands.__len__() == 0:
        return False
    else:
        # 认为各波段大小相等,所以以第一波段信息作为保存
        band1 = bands[0]
        # 设置影像保存大小、波段数
        img_width = band1.shape[1]
        img_height = band1.shape[0]
        num_bands = bands.__len__()

        # 设置保存影像的数据类型
        if 'int8' in band1.dtype.name:
            datatype = gdal.GDT_Byte
        elif 'int16' in band1.dtype.name:
            datatype = gdal.GDT_UInt16
        else:
            datatype = gdal.GDT_Float32

        # 创建文件
        driver = gdal.GetDriverByName("GTiff")
        dataset = driver.Create(save_path, img_width, img_height, num_bands, datatype)

        if dataset is not None:
            # 写入仿射变换参数
            if geotrans is not None:
                dataset.SetGeoTransform(geotrans)

            # 写入投影参数
            if proj is not None:
                if proj is 'WGS84' or \
                        proj is 'wgs84' or \
                        proj is 'EPSG:4326' or \
                        proj is 'EPSG-4326' or \
                        proj is '4326':
                    dataset.SetProjection(projection[0])  # 写入投影
                elif proj is 'EPSG:3857' or \
                        proj is 'EPSG-3857' or \
                        proj is '3857':
                    dataset.SetProjection(projection[1])  # 写入投影
                else:
                    dataset.SetProjection(proj)  # 写入投影

            # 逐波段写入数据
            for i in range(bands.__len__()):
                raster_band = dataset.GetRasterBand(i + 1)

                # 设置没有数据的像素值为0
                raster_band.SetNoDataValue(0)

                if names is not None:
                    # 设置波段的描述(名称)
                    raster_band.SetDescription(names[i])

                # 写入数据
                raster_band.WriteArray(bands[i])
            print("save image success.")
            return True


if __name__ == '__main__':
    input_img = "F:\\blue\\Blue_201810.tif"
    output_img = "output.tif"

    # 读取影像
    band_data, param_geoTransform, param_proj, band_name = readImage(input_img)
    # 获得第一波段数据
    band_1 = band_data[0]

    # 归一化的范围
    target_max_v = 1.0
    target_min_v = 0.0
    # 原始影像灰度范围(不含无意义值)
    old_max_v = np.max(band_1)
    old_min_v = 0.0

    print 'original max value', np.max(band_1)
    print 'original min value', np.min(band_1)

    # 按照最大最小值法进行归一化,计算缩放比率
    scale_ratio = (target_max_v - target_min_v) / (old_max_v - old_min_v)

    # 对影像进行归一化,无意义值赋为0
    band_1_n = np.where(band_1 >= old_min_v, (band_1 - old_min_v) * scale_ratio, target_min_v)
    print 'normalized max value', np.max(band_1_n)
    print 'normalized min value', np.min(band_1_n)

    # 输出影像
    writeImage(output_img, [band_1_n], param_geoTransform, param_proj, band_name)

最后为了方便,也把代码传到了Github上,点击查看

2.效果展示

如下是某个原始影像的灰度统计。 在原始影像中,无效值被赋为了-10000。我们在归一化的时候并不需要考虑它们。影像的最大值约为0.84。所以其实我们要归一化的范围就是从0到0.84,将其映射到0到1之间。如下图是归一化后的统计结果。 可以看到,灰度值范围已经成功被归一化到0到1之间了。这样便完成了我们的需求。

本文作者原创,未经许可不得转载,谢谢配合

返回顶部