赞
踩
本章节主要参考《python地理空间分析指南》第六章。
文章中的所有操作都可以在ENVI中完成,这里只提供一个图像处理的思路。
波段变换最常用的地方就是进行图像显示,例如使用假彩色图像以凸显植被信息。图像波段变换即将图像波段的组合顺序重新排列并显示的方法,主要使用GDAL库。下面以一个例子进行实验,首先给出数据的下载地址:
http://git.io/vqs41
打开原图可以看到,植被明显呈现红色(植被在绿光波段反射率高,而图像将绿光波段放到了红色通道),说明该图为假彩色影像:
在本示例中,将使用gdal_array模块加载图片到numpy数组中,然后将其另存为新的geotiff文件。需要注意的是,numpy引用数组的方式是(y,x)(行,列)型的,常用的电子表格及其他软件则是(x,y)(行,列)型。
下面给出波段变换的代码:
- from osgeo import gdal_array
-
- # 源图片的名称
- src = "FalseColor.tif"
- # 将源图片载入到数组中
- arr = gdal_array.LoadFile(src)
- # 交换波段1和波段2的位置,使用“高级分片”功能直接对波段进行重新排列
- output = gdal_array.SaveArray(arr[[1, 0, 2], :], "swap.tif", format="GTiff",prototype=src)
- # 取消输出,避免在某些平台上损坏文件
- output = None
波段变换后的影像为:
这里创建的直方图的是像元值得频数直方图,横轴代表像元值,纵轴代表像元数量。本示例使用的数据是上述波段变换的结果swap.tif影像。首先给出创建直方图的代码:
- from osgeo import gdal_array
- import turtle as t
-
-
- def histogram(a, bins=list(range(0, 256))):
- fa = a.flat
- n = gdal_array.numpy.searchsorted(gdal_array.numpy.sort(fa), bins)
- n = gdal_array.numpy.concatenate([n, [len(fa)]])
- hist = n[1:] - n[:-1]
- return hist
-
-
- def draw_histogram(hist, scale=True):
- t.color("black")
- axes = ((-355, -200), (355, -200), (-355, -200), (-355, 250))
- t.up()
- for p in axes:
- t.goto(p)
- t.down()
- t.up()
- t.goto(0, -250)
- t.write("VALUE", font=("Arial, ", 12, "bold"))
- t.up()
- t.goto(-400, 280)
- t.write("FREQUENCY", font=("Arial, ", 12, "bold"))
- x = -355
- y = -200
- t.up()
- for i in range(1, 11):
- x = x+65
- t.goto(x, y)
- t.down()
- t.goto(x, y-10)
- t.up()
- t.goto(x, y - 25)
- t.write("{}".format((i*25)), align="center")
- x = -355
- y = -200
- t.up()
- pixels = sum(hist[0])
- if scale:
- max = 0
- for h in hist:
- hmax = h.max()
- if hmax > max:
- max = hmax
- pixels = max
- label = int(pixels/10)
- for i in range(1, 11):
- y = y+45
- t.goto(x, y)
- t.down()
- t.goto(x-10, y)
- t.up()
- t.goto(x-15, y-6)
- t.write("{}".format((i*label)), align="right")
- x_ratio = 709.0 / 256
- y_ratio = 450.0 / pixels
- colors = ["red", "green", "blue"]
- for j in range(len(hist)):
- h = hist[j]
- x = -354
- y = -199
- t.up()
- t.goto(x, y)
- t.down()
- t.color(colors[j])
- for i in range(256):
- x = i * x_ratio
- y = h[i] * y_ratio
- x = x - (709/2)
- y = y + -199
- t.goto((x, y))
-
-
- img = "swap.tif"
- histograms = []
- arr = gdal_array.LoadFile(img)
- for b in arr:
- histograms.append(histogram(b))
- draw_histogram(histograms)
- t.pen(shown=False)
- t.done
执行上述代码,创建出的直方图为:
为了将上述代码进行绝对比例显示,我们将上述代码的倒数第三行修改为:
draw_histogram(histograms, scale=False)
执行上述代码,显示的结果为:
可以看到,图像的灰度分布明显偏暗,因此我们需要对直方图进行均衡化处理。直方图均衡化处理后的影像的明暗程度更为均匀。下面给出直方图均衡化的代码:
- from osgeo import gdal_array
- import operator
- from functools import reduce
-
-
- def histogram(a, bins=list(range(0, 256))):
- fa = a.flat
- n = gdal_array.numpy.searchsorted(gdal_array.numpy.sort(fa), bins)
- n = gdal_array.numpy.concatenate([n, [len(fa)]])
- hist = n[1:] - n[:-1]
- return hist
-
-
- def stretch(a):
- """
- 在gdal_array上传的图像数组中执行直方图均衡化操作
- """
- hist = histogram(a)
- lut = []
- for b in range(0, len(hist), 256):
- # 步长尺寸
- step = reduce(operator.add, hist[b:b+256]) / 255
- # 创建均衡的查找表
- n = 0
- for i in range(256):
- lut.append(n/step)
- n = n + hist[i+b]
- gdal_array.numpy.take(lut, a, out=a)
- return a
-
-
- src = "swap.tif"
- arr = gdal_array.LoadFile(src)
- stretched = stretch(arr)
- output = gdal_array.SaveArray(arr, "stretched.tif", format="GTIFF", prototype=src)
- output = None
经过直方图均衡化后的影像结果为:
为了直观的展示直方图均衡化前后的影像变化,这里使用ENVI中的view swipe功能,左边显示的是未均衡化的影像,右边显示的是均衡化后的影像,可以明显看出两幅影像的差异。
重新运行绘制直方图的代码,从结果中可以看到,三个波段的DN值不再明显偏小,而是相对均匀一些。显示的结果为:
我们在处理遥感图像的过程中,往往需要根据已有的shapefile文件来裁剪影像。一般图像裁剪的思路是:加载影像——读取shp文件——栅格化shp文件——将shp做成mask——对影像进行filter——保存裁剪的结果。这里我们主要使用GDAL库进行操作。本示例将会使用县界shapefile文件来裁剪遥感影像,这里的遥感影像是上述创建直方图过程中,进行直方图均衡化后的遥感影像。不过遥感影像的预处理对图像裁剪步骤影像不大。
原始数据的下载地址为:
遥感数据(需要手动进行波段变换和直方图均衡化):http://git.io/vqs41
shapefile数据:http://git.io/vqsRH
在ENVI中打开原始影像和shapefile文件,显示为:
下面利用shapefile文件对遥感影像进行裁剪:
- import operator
- from osgeo import gdal, gdal_array, osr
- import shapefile
- from PIL import ImageDraw, Image
-
- # 用于裁剪的栅格影像
- raster = "stretched.tif"
- # 用于裁剪的多边形shapefile文件,注意这里路径的问题
- shp = "hancock/hancock"
- # 裁剪后的栅格文件名
- output = "clip"
-
-
- def imageToArray(i):
- """
- 将一个python影像库的数组转换为一个gdal_array图片
- """
- a = gdal_array.numpy.fromstring(i.tostring(), 'b')
- a.shape = i.im.size[1], i.im.size[0]
- return a
-
-
- def world2pixel(geoMatrix, x, y):
- """
- 使用gdal库的geomatrix对象(gdal.GetGeoTransform())计算地理坐标的像素位置
- """
- ulX = geoMatrix[0]
- ulY = geoMatrix[3]
- xDist = geoMatrix[1]
- yDist = geoMatrix[5]
- rtnX = geoMatrix[2]
- rtnY = geoMatrix[4]
- pixel = int((x - ulX) / xDist)
- line = int((ulY - y) / abs(yDist))
- return (pixel, line)
-
-
- # 将源数据作为gdal_array载入
- srcArray = gdal_array.LoadFile(raster)
- # 同时载入gdal库的图片从而获取geotransform
- srcImage = gdal.Open(raster)
- geoTrans = srcImage.GetGeoTransform()
- # 使用pyshp库打开shapefile文件
- r = shapefile.Reader("{}.shp".format(shp))
- # 将图层扩展转换为图片像素坐标
- minX, minY, maxX,maxY = r.bbox
- ulX, ulY = world2pixel(geoTrans, minX, maxY)
- lrX, lrY = world2pixel(geoTrans, maxX, minY)
- # 计算新图像的像素尺寸
- pxWidth = int(lrX - ulX)
- pxHeight = int(lrY - ulY)
- clip = srcArray[:, ulY:lrY, ulX:lrX]
- # 为图片创建一个新的geomatrix对象以便附加地理参照数据
- geoTrans = list(geoTrans)
- geoTrans[0] = minX
- geoTrans[3] = maxY
- # 在一个空白的8字节黑白遮罩图片上把点映射为像素,绘制县市边界线
- pixels = []
- for p in r.shape(0).points:
- pixels.append(world2pixel(geoTrans, p[0], p[1]))
- rasterPoly = Image.new("L", (pxWidth, pxHeight), 1)
- # 使用PIL创建一个空白图片用于绘制多边形
- rasterize = ImageDraw.Draw(rasterPoly)
- rasterize.polygon(pixels, 0)
- # 将PIL图片转换为numpy数组
- mask = imageToArray(rasterPoly)
- # 根据掩模图层对图片进行裁剪
- clip = gdal_array.numpy.choose(mask, (clip, 0)).astype(gdal_array.numpy.uint8)
- # 将裁剪图像保存为tiff
- gdal_array.SaveArray(clip, "{}.tif".format(output), format="GTIFF", prototype=raster)
常用的图像分类方法包括监督分类,非监督分类等。本示例采用阈值切片法进行分类。使用的数据为Landsat 8 遥感影像,下载地址为:
http://github.com/GeospatialPython/Learn/blob/master/thermal.zip?raw=true
用ENVI打开原始数据,发现该数据仅有一个波段,像元DN值介于[13559 33021]:
对上述影像进行分类,其代码为:
- from osgeo import gdal_array
-
- # 输入文件
- src = "./thermal/thermal.tif"
-
- # 输出文件名
- tgt = "classified.jpg"
-
- # 使用gdal库加载图片到numpy
- srcArr = gdal_array.LoadFile(src)
-
- # 根据类别数目将直方图分割成20个颜色区间
- classes = gdal_array.numpy.histogram(srcArr, bins=20)[1]
-
- # 颜色查找表的记录数必须是len(classes)+1,声明RGN元组
- lut = [[255, 0, 0], [191, 48, 48], [166, 0, 0], [255, 64, 64], [255, 155, 155],
- [255, 116, 0], [191, 113, 48], [255, 178, 115], [0, 153, 153], [29, 115, 155],
- [0, 99, 99], [166, 75, 0], [0, 204, 0], [51, 204, 204], [255, 150, 64],
- [92, 204, 204], [38, 153, 38], [0, 133, 0], [57, 230, 57], [103, 230, 103],
- [184, 138, 0]]
-
- # 分类初始值
- start = 1
-
- # 创建一个RGB颜色的JPEG图片输出
- rgb = gdal_array.numpy.zeros((3, srcArr.shape[0], srcArr.shape[1], ), gdal_array.numpy.float32)
-
- # 处理所有类并声明颜色
- for i in range(len(classes)):
- mask = gdal_array.numpy.logical_and(start <= srcArr, srcArr <= classes[i])
- for j in range(len(lut[i])):
- rgb[j] = gdal_array.numpy.choose(mask, (rgb[j], lut[i][j]))
- start = classes[i] + 1
-
- # 保存图片
- output = gdal_array.SaveArray(rgb.astype(gdal_array.numpy.uint8), tgt, format="JPEG")
- output = None
分类好的最终影像结果为:
本示例将从Landsat 8 OLI拍摄的热红外图像中获取一组屏障岛屿区域影像子集的图片。
数据的下载地址为:
http://git.io/vqarj
原始数据只有一个波段,通过统计,发现像元DN值介于[26838 31320]。原始影像为:
本例子的目标是将影像中的3座岛屿提取出来并将其保存为shapefile文件。在执行提取操作之前会先对物馆区域进行遮罩,由于岛屿和水体形成了鲜明对比,所以这里可以采用阈值分割的方法进行处理。下面直接给出提取岛屿的代码:
- from osgeo import gdal_array
-
- # 输入文件名称
- src = "./islands/islands.tif"
-
- # 输出文件名称
- tgt = "islands_classified.tif"
-
- # 使用gdal库将图像载入numpy
- srcArr =gdal_array.LoadFile(src)
-
- # 将直方图分为2个子区间以便区分
- classes = gdal_array.numpy.histogram(srcArr, bins=2)[1]
-
- lut = [[255, 0, 0], [0, 0, 0], [255, 255, 255]]
-
- # 分类的起始值
- start = 1
-
- # 建立输出图片
- rgb = gdal_array.numpy.zeros((3, srcArr.shape[0], srcArr.shape[1]), gdal_array.numpy.float32)
-
- # 处理所有类别并分配颜色
- for i in range(len(classes)):
- mask = gdal_array.numpy.logical_and(start <= srcArr, srcArr <= classes[i])
- for j in range(len(lut[i])):
- rgb[j] = gdal_array.numpy.choose(mask, (rgb[j], lut[i][j]))
- start = classes[i] + 1
-
- # 保存图片
- gdal_array.SaveArray(rgb.astype(gdal_array.numpy.uint8), tgt, format="GTIFF", prototype=src)
提取后的效果图为:
接下来需要将提取出的岛屿另存为shapefile文件。GDAL库中有一个函数Polygonize()能够很好的处理。Polygonize()方法允许你声明一个遮罩图层并且会使用黑色作为影像过滤器,这样一来就不会将水系区域作为一个多边形提取,最终我们只会得到岛屿部分的图像。需要注意的一点是,在脚本中从源图片中拷贝了地理参照信息到shapefile文件中,这样做的目的是正确进行地理定位。将特征区域转换为shapefile文件的代码为:
- from osgeo import gdal, ogr, osr
-
- # 阈值化后的输出栅格文件名称
- src = "islands_classified.tif"
- # 输出的shapefile文件名称
- tgt = "extract.shp"
- # 图层名称
- tgtLayer = "extract"
- # 打开输入的栅格文件
- srcDS = gdal.Open(src)
- # 获取第一个波段
- band = srcDS.GetRasterBand(1)
- # 让gdal库使用该波段作为遮罩层
- mask = band
- # 创建输出的shapefile文件
- driver = ogr.GetDriverByName("ESRI Shapefile")
- shp = driver.CreateDataSource(tgt)
- # 拷贝空间索引
- srs = osr.SpatialReference()
- srs.ImportFromWkt(srcDS.GetProjectionRef())
- layer = shp.CreateLayer(tgtLayer, srs=srs)
- # 创建dbf文件
- fd = ogr.FieldDefn("DN", ogr.OFTInteger)
- layer.CreateField(fd)
- dst_field = 0
- # 从图片中自动提取特征
- extract = gdal.Polygonize(band, mask, layer, dst_field, [], None)
上述代码的运行结果为(下图中的红色轮廓):
接下来需要对shp文件简单的添加一部分属性信息,具体的代码为:
- import shapefile
- import pngcanvas
-
- r = shapefile.Reader("extract.shp")
- xdist = r.bbox[2] - r.bbox[0]
- ydist = r.bbox[3] - r.bbox[1]
- iwidth = 800
- iheight = 600
- xratio = iwidth / xdist
- yratio = iheight / ydist
- polygons = []
-
- for shape in r.shapes():
- for i in range(len(shape.parts)):
- pixels = []
- pt = None
- if i < len(shape.parts) - 1:
- pt = shape.points[shape.parts[i]:shape.parts[i+1]]
- else:
- pt = shape.points[shape.parts[i]:]
- for x, y in pt:
- px = int(iwidth -((r.bbox[2] - x) * xratio))
- py = int((r.bbox[3] - y) * yratio)
- pixels.append([px, py])
- polygons.append(pixels)
- c = pngcanvas.PNGCanvas(iwidth, iheight)
- for p in polygons:
- c.polyline(p)
- f = open("extract.png", "wb")
- f.write(c.dump())
- f.close()
变化监测一般是需要获取变化前和变化后的影像,然后使用一定的方法对两幅图像进行处理,从而获得变化信息。最常用的方法无非就是图像差值处理,还有比值法,PCA等方法。下面的示例用比较简单的差值法来进行变换监测。实验需要的数据下载地址为:
飓风前的影像 http://git.io/vqa6h
飓风后的影像 http://git.io/vqaic
用ENVI打开原始数据,左边为变化前的全色影像,右边为变化后的假彩色影像,在ENVI Classic中将两幅图像进行link,从下图可以很直观的看到,大桥非常明显的被飓风毁坏:
执行变化监测的步骤大致为:用gdal_array将图片导入numpy数据中——变化前后影像作差——图像分类着色——保存图片。具体的代码为:
- from osgeo import gdal, gdal_array
- import numpy as np
-
- # 飓风前影像
- img1 = "./before/before.tif"
- # 飓风后影像
- img2 = "./after/after.tif"
-
- # 将上述图像载入数组
- arr1 = gdal_array.LoadFile(img1).astype(np.int8)
- arr2 = gdal_array.LoadFile(img2)[1].astype(np.int8)
-
- # 在图片数组上执行差值操作
- diff = arr2 - arr1
-
- # 建立类别架构并将变化特征隔离
- classes = np.histogram(diff, bins=5)[1]
-
- # 用黑色遮罩不是特变明显的变化特征
- lut = [[0, 0, 0], [0, 0, 0], [0, 0, 0],
- [0, 0, 0], [0, 255, 0], [255, 0, 0]]
- # 类别初始值
- start = 1
- # 创建输出图片
- rgb = np.zeros((3, diff.shape[0], diff.shape[1], ), np.int8)
-
- # 处理所有类别并配色
- for i in range(len(classes)):
- mask = np.logical_and(start <= diff, diff <= classes[i])
- for j in range(len(lut[i])):
- rgb[j] = np.choose(mask, (rgb[j], lut[i][j]))
- start = classes[i] + 1
-
- # 保存图片结果
- output = gdal_array.SaveArray(rgb, "change.tif", format="GTiff", prototype=img2)
- output = None
实验结果中, 绿色表示添加的元素,红色表示减少的元素。示例中采用的差值算法的处理结果虽然不够完美,但也能提供一种变化检测的处理思路。最终的实验结果为:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。