Python 裁剪卫星影像并修改其xml文件

Python 裁剪卫星影像并修改其xml文件

项目场景

ZY3或GF7立体像对影像过大,在测试的时候很慢,需要制作较小一点的测试数据。由于在转换坐标以及可视化时需要用到其xml中的四个角点坐标,所以需要同步修改其xml中的相关属性值。之前都是分离来做,先把影像裁成固定尺寸,再把xml中的角点坐标复制出来,计算,再修改回去,效率很低。由于后续可能还需要这么操作,干脆写个Python脚本自动处理。


注意事项

  1. 由于程序需要卫星的rpc支持,所以只裁剪影像的左上角
  2. 用向量方式计算裁剪后的角点经纬度坐标,可能有些许误差
  3. 主要修改角点坐标,并对宽高、范围之类的进行修改
  4. 裁剪后的小范围影像以及修改后的xml未重命名,存放在新建的目录下
  5. 新建的目录名可以自行拟定,也可以使用代码自动生成的
  6. 不同影像的xml结构可能不同,本代码以ZY3的xml为例实现
  7. 如果所给的路径下只存在影像或者xml,也会进行裁剪操作
  8. 裁剪影像的部分代码非原创,忘了是很久之前参考的哪位好心人的了,欢迎认领
  9. 需要gdal以及lxml这两个库支持
  10. matplotlib以及icecream两个库的相关代码调试用,可删除

解决方案

代码

# File      :crop_tif_and_xml.py
# Auther    :WooChi
# Time      :2023/03/02
# Version   :1.0
# Function  :


import numpy as np
from icecream import ic
from matplotlib import pyplot as plt
from lxml import etree
import gdal
import os


def cut_img(srcpath, width_size, height_size, ColN=int(1), RowN=int(1), outpath="cut.tif"):
    """
    用来裁剪影像
    :param srcpath:
    :param width_size:
    :param height_size:
    :param ColN:    列向需要多少块
    :param RowN:    行向需要多少块  在这个场景下只需要一张
    :return:
    """

    in_ds = gdal.Open(srcpath)  # 读取要切的原图
    print("open tif file succeed")
    width = in_ds.RasterXSize  # 获取数据宽度
    height = in_ds.RasterYSize  # 获取数据高度
    outbandsize = in_ds.RasterCount  # 获取数据波段数
    datatype = in_ds.GetRasterBand(1).DataType
    nBands = in_ds.RasterCount  # 获取波段数

    # 读取原图中的每个波段
    in_band = []
    for n in range(nBands):
        in_band.append(in_ds.GetRasterBand(n + 1))

        # 定义切图的起始点坐标
        # 定义切图的大小(矩形框)
        col_num = int(width / width_size)  # 宽度可以分成几块
        row_num = int(height / height_size)  # 高度可以分成几块
        if (width % width_size != 0):
            col_num += 1
        if (height % height_size != 0):
            row_num += 1
        # 这边就知道我们一共是分成了多少个 如果说有多余的 那我们就让那些也自己一小块好吧
    if (ColN * RowN != 0 and ColN <= col_num and RowN <= row_num):
        col_num = ColN
        row_num = RowN

    num = 1  # 这个就用来记录一共有多少块的

    print("row_num:%d   col_num:%d" % (row_num, col_num))

    for i in range(col_num):
        for j in range(row_num):
            offset_x = i * width_size
            offset_y = j * height_size
            ## 从每个波段中切需要的矩形框内的数据(注意读取的矩形框不能超过原图大小)
            b_ysize = min(width - offset_y, height_size)
            b_xsize = min(height - offset_x, width_size)

            print("width:%d     height:%d    offset_x:%d    offset_y:%d     b_xsize:%d     b_ysize:%d" % (
                width, height, offset_x, offset_y, b_xsize, b_ysize))
            outband = []
            for n in range(nBands):
                outband.append(in_band[n].ReadAsArray(offset_y, offset_x, b_ysize, b_xsize))
            # 获取Tif的驱动,为创建切出来的图文件做准备
            gtif_driver = gdal.GetDriverByName("GTiff")

            num += 1
            # 创建切出来的要存的文件
            out_ds = gtif_driver.Create(outpath, b_ysize, b_xsize, outbandsize, datatype)
            print("create new tif file succeed")

            # 获取原图的原点坐标信息
            ori_transform = in_ds.GetGeoTransform()

            if ori_transform:
                print(ori_transform)
                print("Origin = ({}, {})".format(ori_transform[0], ori_transform[3]))
                print("Pixel Size = ({}, {})".format(ori_transform[1], ori_transform[5]))

            # 读取原图仿射变换参数值
            top_left_x = ori_transform[0]  # 左上角x坐标
            w_e_pixel_resolution = ori_transform[1]  # 东西方向像素分辨率
            top_left_y = ori_transform[3]  # 左上角y坐标
            n_s_pixel_resolution = ori_transform[5]  # 南北方向像素分辨率

            # 根据反射变换参数计算新图的原点坐标
            top_left_x = top_left_x + offset_y * w_e_pixel_resolution
            top_left_y = top_left_y + offset_x * n_s_pixel_resolution

            # 将计算后的值组装为一个元组,以方便设置
            dst_transform = (
                top_left_x, ori_transform[1], ori_transform[2], top_left_y, ori_transform[4], ori_transform[5])
            print("dst_transform = ", dst_transform)

            # 设置裁剪出来图的原点坐标
            out_ds.SetGeoTransform(dst_transform)

            # 设置SRS属性(投影信息)
            out_ds.SetProjection(in_ds.GetProjection())

            # 写入目标文件
            for n in range(nBands):
                print(outband[n])
                out_ds.GetRasterBand(n + 1).WriteArray(outband[n])
            # 将缓存写入磁盘
            out_ds.FlushCache()
            print("FlushCache succeed")
            del out_ds, outband


def zoom_bounds(bounds, width, height, width_new, height_new):
    """
    用来计算裁剪后的新的经纬度坐标
    """
    bounds_new = np.zeros_like(bounds)
    bounds_new[0, :] = bounds[0, :]

    newRightTop = (bounds[1, :] - bounds[0, :]) * width_new / width + bounds[0, :]
    newLeftBottom = (bounds[3, :] - bounds[0, :]) * height_new / height + bounds[0, :]
    newRightBottom = (bounds[3, :] - bounds[0, :]) * height_new / height + newRightTop

    bounds_new[1, :] = newRightTop
    bounds_new[2, :] = newRightBottom
    bounds_new[3, :] = newLeftBottom

    plt.figure()
    plt.plot(bounds[:, 0], bounds[:, 1], c="r")
    plt.plot([bounds[3, 0], bounds[0, 0]], [bounds[3, 1], bounds[0, 1]], c="r")

    plt.plot(bounds_new[:, 0], bounds_new[:, 1], c="b")
    plt.plot([bounds_new[3, 0], bounds_new[0, 0]], [bounds_new[3, 1], bounds_new[0, 1]], c="b")

    plt.pause(2)

    return bounds_new


def ZY_xml_process(xmlfile, width_new, height_new, outxmlpath):
    """
    处理xml,由于不同影像xml结构不同。在此以ZY3为例实现资源系列影像xml处理功能
    """
    et = etree.parse(xmlfile)

    WidthInPixels = et.find('//WidthInPixels')
    HeightInPixels = et.find('//HeightInPixels')
    width = float(WidthInPixels.text)
    height = float(HeightInPixels.text)

    if (width <= width_new or height <= height_new):
        print("Out of Bounds (width:%d height:%d)" % (width, height))
        return

    LeftTopLon = et.find('//LeftTopPoint//Longtitude')
    LeftTopLat = et.find('//LeftTopPoint//Latitude')
    RightTopLon = et.find('//RightTopPoint//Longtitude')
    RightTopLat = et.find('//RightTopPoint//Latitude')
    RightBottomLon = et.find('//RightBottomPoint//Longtitude')
    RightBottomLat = et.find('//RightBottomPoint//Latitude')
    LeftBottomLon = et.find('//LeftBottomPoint//Longtitude')
    LeftBottomLat = et.find('//LeftBottomPoint//Latitude')

    boundary = np.array([[float(LeftTopLon.text), float(LeftTopLat.text)],
                         [float(RightTopLon.text), float(RightTopLat.text)],
                         [float(RightBottomLon.text), float(RightBottomLat.text)],
                         [float(LeftBottomLon.text), float(LeftBottomLat.text)]])
    ic(boundary)

    boundary_new = zoom_bounds(boundary, width, height, width_new, height_new)

    ic(boundary_new)

    # 接下来修改xml相关属性值

    # 修改四个角点的坐标值(这部分是必须的,因为后续读取的就是这部分值)
    LeftTopLon.text = str(boundary_new[0, 0])
    LeftTopLat.text = str(boundary_new[0, 1])
    RightTopLon.text = str(boundary_new[1, 0])
    RightTopLat.text = str(boundary_new[1, 1])
    RightBottomLon.text = str(boundary_new[2, 0])
    RightBottomLat.text = str(boundary_new[2, 1])
    LeftBottomLon.text = str(boundary_new[3, 0])
    LeftBottomLat.text = str(boundary_new[3, 1])

    et.find('//RightTopPoint//Sample').text = str(width_new)
    et.find('//RightBottomPoint//Sample').text = str(width_new)
    et.find('//RightBottomPoint//Line').text = str(height_new)
    et.find('//LeftBottomPoint//Line').text = str(height_new)

    # 修改中心点以及四个角点对应的像素坐标
    center_col = int(width_new / 2)
    center_row = int(height_new / 2)
    center_lon = (boundary_new[1, 0] + boundary_new[2, 0] - boundary_new[0, 0] - boundary_new[
        3, 0]) / 2 * center_col / width_new + np.min(boundary_new[:, 0])
    center_lat = (boundary_new[0, 1] + boundary_new[1, 1] - boundary_new[2, 1] - boundary_new[
        3, 1]) / 2 * center_row / height_new + np.min(boundary_new[:, 1])

    CenterPointSample = et.find('//CenterPoint//Sample')
    CenterPointLine = et.find('//CenterPoint//Line')
    CenterPointLongtitude = et.find('//CenterPoint//Longtitude')
    CenterPointLatitude = et.find('//CenterPoint//Latitude')

    CenterPointSample.text = str(center_col)
    CenterPointLine.text = str(center_row)
    CenterPointLongtitude.text = str(center_lon)
    CenterPointLatitude.text = str(center_lat)

    # 修改宽高
    WidthInPixels.text = str(width_new)
    HeightInPixels.text = str(height_new)
    WidthInMeters = et.find('//WidthInMeters')
    HeightInMeters = et.find('//HeightInMeters')
    WidthInMeters.text = str(float(WidthInMeters.text) * width_new / width)

    HeightInMeters.text = str(float(HeightInMeters.text) * height_new / height)

    et.write(outxmlpath, encoding="utf-8")


def crop_tif_and_xml(imgPath, width_new, height_new, new_folder=""):
    """
    主函数,在原始影像所在目录下建立一个新目录,判断是否存在影像以及xml,把裁剪后的影像和xml存到新建的目录下,不改变其命名方式
    """
    (folder, imgFile) = os.path.split(imgPath)
    (name, ext) = os.path.splitext(imgFile)

    xmlFile = name + ".xml"

    xmlPath = os.path.join(folder, xmlFile)

    if (len(new_folder) == 0):
        new_folder = name[:3] + "_w" + str(width_new) + "_h" + str(height_new)

    outFolder = os.path.join(folder, new_folder)

    imgOutPath = os.path.join(outFolder, imgFile)
    xmlOutPath = os.path.join(outFolder, xmlFile)

    ic(outFolder)
    ic(imgOutPath)
    ic(xmlOutPath)

    if not os.path.exists(outFolder):
        os.makedirs(outFolder)  # makedirs 创建文件时如果路径不存在会创建这个路径

    if (os.path.exists(imgPath)):
        print(ext[1:], " processing...")
        cut_img(imgPath, int(width_new), int(height_new), outpath=imgOutPath)
    else:
        print("no img file")
    if (os.path.exists(xmlPath) and (name[:2] == "zy" or name[:2] == "ZY")):
        print("xml processing...")
        ZY_xml_process(xmlPath, width_new, height_new, xmlOutPath)
    else:
        print("no xml file")

    print("done")


if __name__ == '__main__':
    imgPath = r"T:\ProjectData\ZY302_2YW\ZY302_2YW\zy302a_bwd_016497_003126_20190520112310_01_sec_0001_1905228482.tif"
    width_new = 3000
    height_new = 3000

    crop_tif_and_xml(imgPath, width_new, height_new)

输出

D:\ProgramData\Anaconda3\envs\Env36\python.exe D:/Project/PythonProject/Crop_tif_and_xml/Crop_tif_and_xml.py
outFolder: 'T:\\ProjectData\\ZY302_2YW\\ZY302_2YW\\zy3_w3000_h3000'
imgOutPath: 'T:\\ProjectData\\ZY302_2YW\\ZY302_2YW\\zy3_w3000_h3000\\zy302a_bwd_016497_003126_20190520112310_01_sec_0001_1905228482.tif'
xmlOutPath: 'T:\\ProjectData\\ZY302_2YW\\ZY302_2YW\\zy3_w3000_h3000\\zy302a_bwd_016497_003126_20190520112310_01_sec_0001_1905228482.xml'
tif  processing...
open tif file succeed
row_num:1   col_num:1
width:24513     height:19995    offset_x:0    offset_y:0     b_xsize:3000     b_ysize:3000
create new tif file succeed
(0.0, 1.0, 0.0, 0.0, 0.0, 1.0)
Origin = (0.0, 0.0)
Pixel Size = (1.0, 1.0)
dst_transform =  (0.0, 1.0, 0.0, 0.0, 0.0, 1.0)
[[277 284 298 ... 258 257 268]
 [284 294 296 ... 263 265 271]
 [292 306 295 ... 271 267 267]
 ...
 [263 257 256 ... 167 151 137]
 [269 268 265 ... 165 170 164]
 [266 274 263 ... 188 193 191]]
FlushCache succeed
xml processing...
boundary: array([[115.414386,  39.663782],
                 [116.04798 ,  39.553868],
                 [115.921683,  39.116167],
                 [115.29204 ,  39.225697]])
done
boundary_new: array([[115.414386  ,  39.663782  ],
                     [115.49192779,  39.65033028],
                     [115.47357131,  39.5846011 ],
                     [115.39602951,  39.59805282]])

进程已结束,退出代码0

检查

显示(裁剪范围示意)

在这里插入图片描述

根据输出的宽高新建的文件夹zy3_w3000_h3000,处理后的文件放在这下面
在这里插入图片描述
检查影像,没毛病
在这里插入图片描述

检查xml,修改成功

在这里插入图片描述


上传代码,打完收工。

猜你喜欢

转载自blog.csdn.net/m0_50910915/article/details/129314232