当前位置:   article > 正文

遥感图像处理中常用的python操作_python 遥感图像处理 mmvc

python 遥感图像处理 mmvc

本章节主要参考《python地理空间分析指南》第六章

文章中的所有操作都可以在ENVI中完成,这里只提供一个图像处理的思路。


一、图像波段变换

波段变换最常用的地方就是进行图像显示,例如使用假彩色图像以凸显植被信息。图像波段变换即将图像波段的组合顺序重新排列并显示的方法,主要使用GDAL库。下面以一个例子进行实验,首先给出数据的下载地址:

http://git.io/vqs41

打开原图可以看到,植被明显呈现红色(植被在绿光波段反射率高,而图像将绿光波段放到了红色通道),说明该图为假彩色影像:

在本示例中,将使用gdal_array模块加载图片到numpy数组中,然后将其另存为新的geotiff文件。需要注意的是,numpy引用数组的方式是(y,x)(行,列)型的,常用的电子表格及其他软件则是(x,y)(行,列)型。

下面给出波段变换的代码:

  1. from osgeo import gdal_array
  2. # 源图片的名称
  3. src = "FalseColor.tif"
  4. # 将源图片载入到数组中
  5. arr = gdal_array.LoadFile(src)
  6. # 交换波段1和波段2的位置,使用“高级分片”功能直接对波段进行重新排列
  7. output = gdal_array.SaveArray(arr[[1, 0, 2], :], "swap.tif", format="GTiff",prototype=src)
  8. # 取消输出,避免在某些平台上损坏文件
  9. output = None

波段变换后的影像为:

二、创建直方图

这里创建的直方图的是像元值得频数直方图,横轴代表像元值,纵轴代表像元数量。本示例使用的数据是上述波段变换的结果swap.tif影像。首先给出创建直方图的代码:

  1. from osgeo import gdal_array
  2. import turtle as t
  3. def histogram(a, bins=list(range(0, 256))):
  4. fa = a.flat
  5. n = gdal_array.numpy.searchsorted(gdal_array.numpy.sort(fa), bins)
  6. n = gdal_array.numpy.concatenate([n, [len(fa)]])
  7. hist = n[1:] - n[:-1]
  8. return hist
  9. def draw_histogram(hist, scale=True):
  10. t.color("black")
  11. axes = ((-355, -200), (355, -200), (-355, -200), (-355, 250))
  12. t.up()
  13. for p in axes:
  14. t.goto(p)
  15. t.down()
  16. t.up()
  17. t.goto(0, -250)
  18. t.write("VALUE", font=("Arial, ", 12, "bold"))
  19. t.up()
  20. t.goto(-400, 280)
  21. t.write("FREQUENCY", font=("Arial, ", 12, "bold"))
  22. x = -355
  23. y = -200
  24. t.up()
  25. for i in range(1, 11):
  26. x = x+65
  27. t.goto(x, y)
  28. t.down()
  29. t.goto(x, y-10)
  30. t.up()
  31. t.goto(x, y - 25)
  32. t.write("{}".format((i*25)), align="center")
  33. x = -355
  34. y = -200
  35. t.up()
  36. pixels = sum(hist[0])
  37. if scale:
  38. max = 0
  39. for h in hist:
  40. hmax = h.max()
  41. if hmax > max:
  42. max = hmax
  43. pixels = max
  44. label = int(pixels/10)
  45. for i in range(1, 11):
  46. y = y+45
  47. t.goto(x, y)
  48. t.down()
  49. t.goto(x-10, y)
  50. t.up()
  51. t.goto(x-15, y-6)
  52. t.write("{}".format((i*label)), align="right")
  53. x_ratio = 709.0 / 256
  54. y_ratio = 450.0 / pixels
  55. colors = ["red", "green", "blue"]
  56. for j in range(len(hist)):
  57. h = hist[j]
  58. x = -354
  59. y = -199
  60. t.up()
  61. t.goto(x, y)
  62. t.down()
  63. t.color(colors[j])
  64. for i in range(256):
  65. x = i * x_ratio
  66. y = h[i] * y_ratio
  67. x = x - (709/2)
  68. y = y + -199
  69. t.goto((x, y))
  70. img = "swap.tif"
  71. histograms = []
  72. arr = gdal_array.LoadFile(img)
  73. for b in arr:
  74. histograms.append(histogram(b))
  75. draw_histogram(histograms)
  76. t.pen(shown=False)
  77. t.done

执行上述代码,创建出的直方图为:

为了将上述代码进行绝对比例显示,我们将上述代码的倒数第三行修改为:

draw_histogram(histograms, scale=False)

执行上述代码,显示的结果为:

可以看到,图像的灰度分布明显偏暗,因此我们需要对直方图进行均衡化处理。直方图均衡化处理后的影像的明暗程度更为均匀。下面给出直方图均衡化的代码:

  1. from osgeo import gdal_array
  2. import operator
  3. from functools import reduce
  4. def histogram(a, bins=list(range(0, 256))):
  5. fa = a.flat
  6. n = gdal_array.numpy.searchsorted(gdal_array.numpy.sort(fa), bins)
  7. n = gdal_array.numpy.concatenate([n, [len(fa)]])
  8. hist = n[1:] - n[:-1]
  9. return hist
  10. def stretch(a):
  11. """
  12. 在gdal_array上传的图像数组中执行直方图均衡化操作
  13. """
  14. hist = histogram(a)
  15. lut = []
  16. for b in range(0, len(hist), 256):
  17. # 步长尺寸
  18. step = reduce(operator.add, hist[b:b+256]) / 255
  19. # 创建均衡的查找表
  20. n = 0
  21. for i in range(256):
  22. lut.append(n/step)
  23. n = n + hist[i+b]
  24. gdal_array.numpy.take(lut, a, out=a)
  25. return a
  26. src = "swap.tif"
  27. arr = gdal_array.LoadFile(src)
  28. stretched = stretch(arr)
  29. output = gdal_array.SaveArray(arr, "stretched.tif", format="GTIFF", prototype=src)
  30. 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文件对遥感影像进行裁剪:

  1. import operator
  2. from osgeo import gdal, gdal_array, osr
  3. import shapefile
  4. from PIL import ImageDraw, Image
  5. # 用于裁剪的栅格影像
  6. raster = "stretched.tif"
  7. # 用于裁剪的多边形shapefile文件,注意这里路径的问题
  8. shp = "hancock/hancock"
  9. # 裁剪后的栅格文件名
  10. output = "clip"
  11. def imageToArray(i):
  12. """
  13. 将一个python影像库的数组转换为一个gdal_array图片
  14. """
  15. a = gdal_array.numpy.fromstring(i.tostring(), 'b')
  16. a.shape = i.im.size[1], i.im.size[0]
  17. return a
  18. def world2pixel(geoMatrix, x, y):
  19. """
  20. 使用gdal库的geomatrix对象(gdal.GetGeoTransform())计算地理坐标的像素位置
  21. """
  22. ulX = geoMatrix[0]
  23. ulY = geoMatrix[3]
  24. xDist = geoMatrix[1]
  25. yDist = geoMatrix[5]
  26. rtnX = geoMatrix[2]
  27. rtnY = geoMatrix[4]
  28. pixel = int((x - ulX) / xDist)
  29. line = int((ulY - y) / abs(yDist))
  30. return (pixel, line)
  31. # 将源数据作为gdal_array载入
  32. srcArray = gdal_array.LoadFile(raster)
  33. # 同时载入gdal库的图片从而获取geotransform
  34. srcImage = gdal.Open(raster)
  35. geoTrans = srcImage.GetGeoTransform()
  36. # 使用pyshp库打开shapefile文件
  37. r = shapefile.Reader("{}.shp".format(shp))
  38. # 将图层扩展转换为图片像素坐标
  39. minX, minY, maxX,maxY = r.bbox
  40. ulX, ulY = world2pixel(geoTrans, minX, maxY)
  41. lrX, lrY = world2pixel(geoTrans, maxX, minY)
  42. # 计算新图像的像素尺寸
  43. pxWidth = int(lrX - ulX)
  44. pxHeight = int(lrY - ulY)
  45. clip = srcArray[:, ulY:lrY, ulX:lrX]
  46. # 为图片创建一个新的geomatrix对象以便附加地理参照数据
  47. geoTrans = list(geoTrans)
  48. geoTrans[0] = minX
  49. geoTrans[3] = maxY
  50. # 在一个空白的8字节黑白遮罩图片上把点映射为像素,绘制县市边界线
  51. pixels = []
  52. for p in r.shape(0).points:
  53. pixels.append(world2pixel(geoTrans, p[0], p[1]))
  54. rasterPoly = Image.new("L", (pxWidth, pxHeight), 1)
  55. # 使用PIL创建一个空白图片用于绘制多边形
  56. rasterize = ImageDraw.Draw(rasterPoly)
  57. rasterize.polygon(pixels, 0)
  58. # 将PIL图片转换为numpy数组
  59. mask = imageToArray(rasterPoly)
  60. # 根据掩模图层对图片进行裁剪
  61. clip = gdal_array.numpy.choose(mask, (clip, 0)).astype(gdal_array.numpy.uint8)
  62. # 将裁剪图像保存为tiff
  63. 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]:

对上述影像进行分类,其代码为:

  1. from osgeo import gdal_array
  2. # 输入文件
  3. src = "./thermal/thermal.tif"
  4. # 输出文件名
  5. tgt = "classified.jpg"
  6. # 使用gdal库加载图片到numpy
  7. srcArr = gdal_array.LoadFile(src)
  8. # 根据类别数目将直方图分割成20个颜色区间
  9. classes = gdal_array.numpy.histogram(srcArr, bins=20)[1]
  10. # 颜色查找表的记录数必须是len(classes)+1,声明RGN元组
  11. lut = [[255, 0, 0], [191, 48, 48], [166, 0, 0], [255, 64, 64], [255, 155, 155],
  12. [255, 116, 0], [191, 113, 48], [255, 178, 115], [0, 153, 153], [29, 115, 155],
  13. [0, 99, 99], [166, 75, 0], [0, 204, 0], [51, 204, 204], [255, 150, 64],
  14. [92, 204, 204], [38, 153, 38], [0, 133, 0], [57, 230, 57], [103, 230, 103],
  15. [184, 138, 0]]
  16. # 分类初始值
  17. start = 1
  18. # 创建一个RGB颜色的JPEG图片输出
  19. rgb = gdal_array.numpy.zeros((3, srcArr.shape[0], srcArr.shape[1], ), gdal_array.numpy.float32)
  20. # 处理所有类并声明颜色
  21. for i in range(len(classes)):
  22. mask = gdal_array.numpy.logical_and(start <= srcArr, srcArr <= classes[i])
  23. for j in range(len(lut[i])):
  24. rgb[j] = gdal_array.numpy.choose(mask, (rgb[j], lut[i][j]))
  25. start = classes[i] + 1
  26. # 保存图片
  27. output = gdal_array.SaveArray(rgb.astype(gdal_array.numpy.uint8), tgt, format="JPEG")
  28. output = None

分类好的最终影像结果为:

五、图像特征提取

本示例将从Landsat 8 OLI拍摄的热红外图像中获取一组屏障岛屿区域影像子集的图片。

数据的下载地址为:

http://git.io/vqarj

原始数据只有一个波段,通过统计,发现像元DN值介于[26838 31320]。原始影像为:

本例子的目标是将影像中的3座岛屿提取出来并将其保存为shapefile文件。在执行提取操作之前会先对物馆区域进行遮罩,由于岛屿和水体形成了鲜明对比,所以这里可以采用阈值分割的方法进行处理。下面直接给出提取岛屿的代码:

  1. from osgeo import gdal_array
  2. # 输入文件名称
  3. src = "./islands/islands.tif"
  4. # 输出文件名称
  5. tgt = "islands_classified.tif"
  6. # 使用gdal库将图像载入numpy
  7. srcArr =gdal_array.LoadFile(src)
  8. # 将直方图分为2个子区间以便区分
  9. classes = gdal_array.numpy.histogram(srcArr, bins=2)[1]
  10. lut = [[255, 0, 0], [0, 0, 0], [255, 255, 255]]
  11. # 分类的起始值
  12. start = 1
  13. # 建立输出图片
  14. rgb = gdal_array.numpy.zeros((3, srcArr.shape[0], srcArr.shape[1]), gdal_array.numpy.float32)
  15. # 处理所有类别并分配颜色
  16. for i in range(len(classes)):
  17. mask = gdal_array.numpy.logical_and(start <= srcArr, srcArr <= classes[i])
  18. for j in range(len(lut[i])):
  19. rgb[j] = gdal_array.numpy.choose(mask, (rgb[j], lut[i][j]))
  20. start = classes[i] + 1
  21. # 保存图片
  22. gdal_array.SaveArray(rgb.astype(gdal_array.numpy.uint8), tgt, format="GTIFF", prototype=src)

提取后的效果图为:

接下来需要将提取出的岛屿另存为shapefile文件。GDAL库中有一个函数Polygonize()能够很好的处理。Polygonize()方法允许你声明一个遮罩图层并且会使用黑色作为影像过滤器,这样一来就不会将水系区域作为一个多边形提取,最终我们只会得到岛屿部分的图像。需要注意的一点是,在脚本中从源图片中拷贝了地理参照信息到shapefile文件中,这样做的目的是正确进行地理定位。将特征区域转换为shapefile文件的代码为:

  1. from osgeo import gdal, ogr, osr
  2. # 阈值化后的输出栅格文件名称
  3. src = "islands_classified.tif"
  4. # 输出的shapefile文件名称
  5. tgt = "extract.shp"
  6. # 图层名称
  7. tgtLayer = "extract"
  8. # 打开输入的栅格文件
  9. srcDS = gdal.Open(src)
  10. # 获取第一个波段
  11. band = srcDS.GetRasterBand(1)
  12. # 让gdal库使用该波段作为遮罩层
  13. mask = band
  14. # 创建输出的shapefile文件
  15. driver = ogr.GetDriverByName("ESRI Shapefile")
  16. shp = driver.CreateDataSource(tgt)
  17. # 拷贝空间索引
  18. srs = osr.SpatialReference()
  19. srs.ImportFromWkt(srcDS.GetProjectionRef())
  20. layer = shp.CreateLayer(tgtLayer, srs=srs)
  21. # 创建dbf文件
  22. fd = ogr.FieldDefn("DN", ogr.OFTInteger)
  23. layer.CreateField(fd)
  24. dst_field = 0
  25. # 从图片中自动提取特征
  26. extract = gdal.Polygonize(band, mask, layer, dst_field, [], None)

上述代码的运行结果为(下图中的红色轮廓):

接下来需要对shp文件简单的添加一部分属性信息,具体的代码为:

  1. import shapefile
  2. import pngcanvas
  3. r = shapefile.Reader("extract.shp")
  4. xdist = r.bbox[2] - r.bbox[0]
  5. ydist = r.bbox[3] - r.bbox[1]
  6. iwidth = 800
  7. iheight = 600
  8. xratio = iwidth / xdist
  9. yratio = iheight / ydist
  10. polygons = []
  11. for shape in r.shapes():
  12. for i in range(len(shape.parts)):
  13. pixels = []
  14. pt = None
  15. if i < len(shape.parts) - 1:
  16. pt = shape.points[shape.parts[i]:shape.parts[i+1]]
  17. else:
  18. pt = shape.points[shape.parts[i]:]
  19. for x, y in pt:
  20. px = int(iwidth -((r.bbox[2] - x) * xratio))
  21. py = int((r.bbox[3] - y) * yratio)
  22. pixels.append([px, py])
  23. polygons.append(pixels)
  24. c = pngcanvas.PNGCanvas(iwidth, iheight)
  25. for p in polygons:
  26. c.polyline(p)
  27. f = open("extract.png", "wb")
  28. f.write(c.dump())
  29. f.close()

六、变化监测

变化监测一般是需要获取变化前和变化后的影像,然后使用一定的方法对两幅图像进行处理,从而获得变化信息。最常用的方法无非就是图像差值处理,还有比值法,PCA等方法。下面的示例用比较简单的差值法来进行变换监测。实验需要的数据下载地址为:

飓风前的影像 http://git.io/vqa6h

飓风后的影像 http://git.io/vqaic

用ENVI打开原始数据,左边为变化前的全色影像,右边为变化后的假彩色影像,在ENVI Classic中将两幅图像进行link,从下图可以很直观的看到,大桥非常明显的被飓风毁坏:

执行变化监测的步骤大致为:用gdal_array将图片导入numpy数据中——变化前后影像作差——图像分类着色——保存图片。具体的代码为:

  1. from osgeo import gdal, gdal_array
  2. import numpy as np
  3. # 飓风前影像
  4. img1 = "./before/before.tif"
  5. # 飓风后影像
  6. img2 = "./after/after.tif"
  7. # 将上述图像载入数组
  8. arr1 = gdal_array.LoadFile(img1).astype(np.int8)
  9. arr2 = gdal_array.LoadFile(img2)[1].astype(np.int8)
  10. # 在图片数组上执行差值操作
  11. diff = arr2 - arr1
  12. # 建立类别架构并将变化特征隔离
  13. classes = np.histogram(diff, bins=5)[1]
  14. # 用黑色遮罩不是特变明显的变化特征
  15. lut = [[0, 0, 0], [0, 0, 0], [0, 0, 0],
  16. [0, 0, 0], [0, 255, 0], [255, 0, 0]]
  17. # 类别初始值
  18. start = 1
  19. # 创建输出图片
  20. rgb = np.zeros((3, diff.shape[0], diff.shape[1], ), np.int8)
  21. # 处理所有类别并配色
  22. for i in range(len(classes)):
  23. mask = np.logical_and(start <= diff, diff <= classes[i])
  24. for j in range(len(lut[i])):
  25. rgb[j] = np.choose(mask, (rgb[j], lut[i][j]))
  26. start = classes[i] + 1
  27. # 保存图片结果
  28. output = gdal_array.SaveArray(rgb, "change.tif", format="GTiff", prototype=img2)
  29. output = None

实验结果中, 绿色表示添加的元素,红色表示减少的元素。示例中采用的差值算法的处理结果虽然不够完美,但也能提供一种变化检测的处理思路。最终的实验结果为:

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/329863
推荐阅读
相关标签
  

闽ICP备14008679号