项目场景
ZY3或GF7立体像对影像过大,在测试的时候很慢,需要制作较小一点的测试数据。由于在转换坐标以及可视化时需要用到其xml中的四个角点坐标,所以需要同步修改其xml中的相关属性值。之前都是分离来做,先把影像裁成固定尺寸,再把xml中的角点坐标复制出来,计算,再修改回去,效率很低。由于后续可能还需要这么操作,干脆写个Python脚本自动处理。
注意事项
- 由于程序需要卫星的rpc支持,所以只裁剪影像的左上角
- 用向量方式计算裁剪后的角点经纬度坐标,可能有些许误差
- 主要修改角点坐标,并对宽高、范围之类的进行修改
- 裁剪后的小范围影像以及修改后的xml未重命名,存放在新建的目录下
- 新建的目录名可以自行拟定,也可以使用代码自动生成的
- 不同影像的xml结构可能不同,本代码以ZY3的xml为例实现
- 如果所给的路径下只存在影像或者xml,也会进行裁剪操作
- 裁剪影像的部分代码非原创,忘了是很久之前参考的哪位好心人的了,欢迎认领
- 需要gdal以及lxml这两个库支持
- 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,修改成功
上传代码,打完收工。