编程|遥感影像显示拉伸

计算机显示图像主要以两种方式的数据类型:浮点型和整型。

浮点型:图像范围大小在0:1之间

整型:图像范围大小在0:255之间

其中,0:255的整型也称为8bit,8字节。一个字节代表01,8个字节也就是2的8次方,等于255。正常图像通常是8bit,即可直接在手机端或者PC端直接显示。

若R、G、B每种颜色使用一个字节(8bit)表示,每幅图像可以有1670万种颜色;

若R、G、B每种颜色使用两个字节(16bit)表示,每幅图像可以有10的12次方种颜色;

如果是灰度图像,每个象素用一个字节(8bit)表示,一幅图像可以有256级灰度;

若每个象素用两个字节(16bit)表示,一幅图像可以有65536级灰度。

理论上说,16bit的图像,灰度级数和颜色比8bit的好得多,但是,还得看你的印刷硬

件是否支持那么多灰度级数和颜色的印刷。如果在普通显示器上观看,两者并没有什么差别。

色彩深度(Depth of Color),色彩深度又叫色彩位数。视频画面中红、绿、蓝三个颜色

通道中每种颜色为N位,总的色彩位数则为3N,色彩深度也就是视频设备所能辨析的色彩

范围。目前有18bit、24bit、30bit、36bit、42bit和48bit位等多种。24位色被称为真彩

色,R、G、B各8bit,常说的8bit,色彩总数为1670万,如诺基亚手机参数,多少万色素

就这个概念。

而遥感影像一般是12bit或者16bit。以landsat9影像为例,它可以有65536级灰度,属于

16bit数据,但是显示的时候需要做一个映射过程:16bit->8bit,把原始的数据压缩映射到

0-255或者0-1,这个过程在遥感领域称为拉伸,在过程使用的方法称为拉伸方式。使用直

方图的形式可以形象地描绘出原始影像到显示影像的过程细节。

解压后的LANDSAT9文件夹

xTfQRH.png

原始影像直接可视化:

xThPTf.png

# 提示警告
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

其直方图如下:

xThFk8.png

显示为全白的原因在于其数据大小,大于255。即全转为了255(白色为255)

为了其能正常显示,我们选择第一种拉伸方式(最大值拉伸),字面意思就是每个像素值

除以这景影像的最大值,保证其能缩放在0-1范围。

xTho9g.png

直方图如下:

xTh54S.png

现在是以浮点型显示,接下来可以转为整型,再次观察有无变化。

具体操作是,在上图的基础上,使用1%线性拉伸。

影像就是一个矩阵,用img表示。1%线性拉伸解释如下,

data_min 是待拉伸的矩阵的最小1%的数据
data_max 是待拉伸的矩阵的最大99%的数据
maxout = 255
minout = 0
result是拉伸后矩阵

result= minout + (img-data_min )/(data_max -data_min )*(maxout- minout)

拓展:2%线性拉伸或者x%线性拉伸,如上面的公式一般,修改data_min和data_max即可,如果无需转255,上式变为

result= (img-data_min )/(data_max -data_min )

xT4N8g.png

xTTsOJ.png

或者是,直接对原始影像进行线性拉伸拉伸

xTo9gg.png

直方图如下:

xT4wKs.png

本文代码(写的不规整,仅供参考)

#!/usr/bin/env python
# -*- coding: utf-8 -*- 
# @Time : 2022/10/31 15:25 
# @File : histogram.py
import numpy as np
from skimage import io, exposure
from matplotlib import pyplot as plt
import os
from osgeo import gdal
def color_image_show(img, title):
    """
    Show image
    Input:
    img - 3D array of uint16 type
    title - string
    """
    fig = plt.figure(figsize=(10, 10))
    fig.set_facecolor('white')
    plt.imshow(img/np.max(img))
    plt.title(title)
    # save
    go = True
    i = 1
    while  go:
        if os.path.exists('test' + str(i) +'.png') ==0:
            go = False
            name ='test' + str(i) +'.png'
            plt.savefig(name,dpi=500)
            plt.show()
        else:
            i += 1
    return img/np.max(img)

def determinate_band(path):
    tif_list = get_file_name(path, 'TIF')
    try:
        for tif in tif_list:
            if os.path.splitext(tif)[0].split('_')[-1] == 'B2':
                b = tif
            if os.path.splitext(tif)[0].split('_')[-1] == 'B3':
                g = tif
            if os.path.splitext(tif)[0].split('_')[-1] == 'B4':
                r = tif
            if os.path.splitext(tif)[0].split('_')[-1] == 'B5':
                nir = tif
            if os.path.splitext(tif)[0].split('_')[-1] == 'B6':
                swir1 = tif
            if os.path.splitext(tif)[0].split('_')[-1] == 'B7':
                swir2 = tif

    except:
        print('error')
    if b:
        return [b, g, r, nir, swir1, swir2]
def linear_stretch(data, num=1):
    x, y = np.shape(data)
    data_new = np.zeros(shape=(x, y))

    data_8bit = data
    data_8bit[data_8bit == -9999] = 0

    # 把数据中的nan转为某个具体数值,例如
    # data_8bit[np.isnan(data_8bit)] = 0
    d2 = np.percentile(data_8bit, num)
    u98 = np.percentile(data_8bit, 100 - num)

    maxout = 255
    minout = 0
    data_8bit_new = minout + ((data_8bit - d2) / (u98 - d2)) * (maxout - minout)
    data_8bit_new[data_8bit_new < minout] = minout
    data_8bit_new[data_8bit_new > maxout] = maxout
    data_8bit_new = data_8bit_new.astype(np.int32)

    return data_8bit_new
def get_file_name(file_dir, type):
    """
    搜索 后缀名为type的文件  不包括子目录的文件
    #
    """
    corretion_file = []
    filelist = os.listdir(file_dir)
    for file in filelist:
        if os.path.splitext(file)[1] == type:
            corretion_file.append(os.path.join(file_dir, file))
    if corretion_file == []:
        for file in filelist:
            if os.path.splitext(file)[1] == '.' + type:
                corretion_file.append(os.path.join(file_dir, file))
    return corretion_file
    
def main():
    path = r'X:\'  # 输入,Landsat 9 影像解压后的路径,你们要修改这里
    tif_list = determinate_band(path)

    # 影像的属性    # attribute
    g_set = gdal.Open(tif_list[1])
    transform = g_set.GetGeoTransform()
    driver = g_set.GetDriver()
    geoTransform = g_set.GetGeoTransform()
    ListgeoTransform = list(geoTransform)
    ListgeoTransform[5] = -ListgeoTransform[5]
    newgeoTransform = tuple(ListgeoTransform)
    proj = g_set.GetProjection()
    cols = g_set.RasterXSize
    rows = g_set.RasterYSize
    temp_arr = np.zeros((rows, cols, 6))
    ##  open tif
    for i in range(6):
        b_set = gdal.Open(tif_list[i])
        # b_set.SetNoDataValue(-9999)
        b = b_set.GetRasterBand(1).ReadAsArray()
        # b = linear_stretch_1(b)
        temp_arr[:, :, i] = b

    img432 = np.dstack((temp_arr[:,:,2], temp_arr[:,:,1], temp_arr[:,:,0]))

    plt.imshow(img432)
    plt.savefig('originRGB.png', dpi=500)
    fig = plt.figure(figsize=(10, 7))
    fig.set_facecolor('white')

    for color, channel in zip('rgb', np.rollaxis(img432, axis=-1)):
        counts, centers = exposure.histogram(channel)
        plt.plot(centers[1::], counts[1::], color=color)
    plt.title('origin RGB histogram')
    plt.savefig('originHistogram.png',dpi=500)
    plt.show()
    # 覆盖原先的img432
    stretchimg432 = color_image_show(img432, 'Landsat 9')
    for color, channel in zip('rgb', np.rollaxis(stretchimg432, axis=-1)):
        counts, centers = exposure.histogram(channel)
        plt.plot(centers[1::], counts[1::], color=color)
    plt.title('max stretch histogram')
    plt.savefig('stretchHistogram.png',dpi=500)
    plt.show()
    plt.imshow(stretchimg432)
    plt.savefig('maxRGB.png', dpi=500)
    fig = plt.figure(figsize=(10, 7))
    fig.set_facecolor('white')

    to255RGB = stretchimg432
    for i in range(3):
        to255RGB[:,:,i] = linear_stretch(stretchimg432[:,:,i],1)
    to255RGB = to255RGB.astype(np.int32)
    to255RGB = np.where(to255RGB>254,255,to255RGB)
    plt.imshow(to255RGB)
    plt.title('255 int RGB image')
    plt.savefig('RGB255.png',dpi=100)
    plt.show()

    for color, channel in zip('rgb', np.rollaxis(to255RGB, axis=-1)):
        counts, centers = exposure.histogram(channel)
        plt.plot(centers[1::], counts[1::], color=color)
    plt.title('first max stretch then linear stretch (255 int RGB image)')
    plt.savefig('linear1%.png',dpi=500)
    plt.show()


    to255RGB = stretchimg432
    for i in range(3):
        to255RGB[:,:,i] = linear_stretch(img432[:,:,i],0)
    to255RGB = to255RGB.astype(np.int32)
    plt.imshow(to255RGB)
    plt.title('first linear stretch (255 int RGB image)')
    plt.savefig('immediatelyRGB255.png',dpi=500)
    plt.show()

    for color, channel in zip('rgb', np.rollaxis(to255RGB, axis=-1)):
        counts, centers = exposure.histogram(channel)
        plt.plot(centers[1::], counts[1::], color=color)
    plt.title('immediately 1 % stretch histogram')
    plt.savefig('firstlinear1%.png',dpi=500)
    plt.show()

    to255RGB = np.where(to255RGB<120,120,to255RGB)
if __name__ == "__main__":
    main()

小结

拉伸方式有很多种,这里不做一一介绍,共同点就是按照某个数学公式把不同数据范围转化为0-1或者0-255。