TensorFlow处理遥感影像?数据格式咋转?
很多同学第一次做“TensorFlow处理遥感影像?数据格式咋转?”这个问题时,会卡在同一个地方:手里的数据是 GeoTIFF、IMG 或者多波段栅格,而 TensorFlow 训练代码通常需要 NumPy 数组、TFRecord、PNG/JPEG 切片,甚至还要保留坐标系、仿射变换和 NoData 信息。本文从 GIS 角度讲清楚遥感影像进 TensorFlow 前的数据格式转换流程,重点解决 GeoTIFF 转 NumPy、GeoTIFF 转 TFRecord、遥感影像切片、标签同步和结果回写等实际问题。

引言:TensorFlow处理遥感影像时,格式转换不是简单改后缀
在普通深度学习教程里,图像往往是 JPEG 或 PNG;但在 GIS 和遥感项目里,原始数据通常是 GeoTIFF、ENVI IMG、Sentinel-2 SAFE、Landsat 影像或多波段栅格。它们不仅包含像素值,还包含坐标系、分辨率、仿射变换、NoData、波段顺序等空间信息。
所以,TensorFlow处理遥感影像的关键不是“把 tif 改成 jpg”,而是要把遥感栅格安全地转成模型可读的数据,同时保证训练标签、预测结果和原始地理空间位置能够对应起来。
本文适合以下场景:
- 你有 GeoTIFF 遥感影像,想用 TensorFlow 做分类、分割或目标检测。
- 你需要把遥感影像转成 NumPy、TFRecord 或影像切片。
- 你担心转换后坐标系丢失、标签错位、波段顺序错误。
- 你需要把 TensorFlow 预测结果重新写回 GeoTIFF。
背景:遥感影像和普通深度学习图片有什么不同
普通图片一般只有 RGB 三个通道,像素范围多为 0 到 255,不带坐标信息。遥感影像则复杂得多,常见差异包括:
- 波段更多:如 Sentinel-2 有多个可见光、近红外、短波红外波段。
- 像素类型不同:可能是 UInt16、Float32,而不是普通图片常见的 UInt8。
- 带地理参考:GeoTIFF 中保存了坐标系、分辨率、仿射变换等信息。
- 存在 NoData:云、边界外区域、无效区域可能用特定值表示。
- 尺寸很大:一景影像可能几 GB,不能一次性全部送入 GPU。
- 标签必须严格对齐:语义分割任务中,影像和标签栅格必须像素级一致。
因此,GeoTIFF 转 TensorFlow 数据格式时,通常要经历“读取波段、裁剪切片、归一化、标签配准、保存索引、训练读取”几个环节。
原理:TensorFlow能读什么,GIS数据需要保留什么
TensorFlow 本身并不直接理解 GIS 坐标系。它关心的是张量,也就是类似 [height, width, channels] 的多维数组。遥感影像格式转换的核心,就是把带地理信息的栅格转换成 TensorFlow 可读的张量,同时把地理参考另行保存或在预测后恢复。
TensorFlow常用输入格式
| 格式 | 适用场景 | 优点 | 注意事项 |
|---|---|---|---|
| NumPy 数组 | 实验、小样本、调试 | 简单直观,适合快速验证 | 大数据量时内存压力较大 |
| TFRecord | 正式训练、大规模数据 | 读取效率高,适合 TensorFlow 数据管道 | 需要编写序列化和解析代码 |
| PNG/JPEG 切片 | 三波段影像、分类样本 | 直观,便于检查 | 容易丢失位深、波段和地理信息 |
| GeoTIFF 切片 | 需要保留空间参考的训练切片 | 保留坐标系和仿射变换 | TensorFlow 不能直接作为标准图片读取 |
GIS处理中必须保留的信息
如果只是训练模型,可以把 GeoTIFF 转成数组;但如果后续要把预测结果回写成地图,就必须记录以下信息:
- CRS:坐标参考系统,例如 EPSG:4326 或 EPSG:3857。
- Transform:仿射变换,用来描述像素行列号和真实坐标的关系。
- Width 与 Height:原始影像尺寸。
- Bounds:影像空间范围。
- NoData:无效值标记。
- 切片位置:每个训练 patch 在原始影像中的 row、col 起点。
一句话理解:TensorFlow 负责学习像素模式,GIS 部分负责保证这些像素仍然能回到正确的地理位置。
步骤:GeoTIFF转NumPy并送入TensorFlow
如果你刚开始做实验,建议先用 Rasterio 把 GeoTIFF 转为 NumPy 数组。这是 TensorFlow处理遥感影像最容易调试的一种方式。
步骤1:安装常用环境
pip install rasterio numpy tensorflow
如果你使用 Conda,建议优先从 conda-forge 安装 Rasterio,减少 GDAL 依赖冲突:
conda install -c conda-forge rasterio numpy
pip install tensorflow
步骤2:读取多波段GeoTIFF
import rasterio
import numpy as np
tif_path = "input_image.tif"
with rasterio.open(tif_path) as src:
image = src.read() # shape: [bands, height, width]
profile = src.profile
transform = src.transform
crs = src.crs
nodata = src.nodata
print(image.shape)
print(crs)
print(transform)
Rasterio 读取出的数组维度是 [bands, height, width],而 TensorFlow 常用维度是 [height, width, channels],所以需要转置。
image_tf = np.transpose(image, (1, 2, 0)) # [height, width, bands]
print(image_tf.shape)
步骤3:处理NoData和归一化
遥感影像通常不是 0 到 255 的普通图片。很多卫星影像是 UInt16,像素值可能在 0 到 10000 之间。直接输入模型会导致训练不稳定,因此需要归一化。
image_tf = image_tf.astype(np.float32)
if nodata is not None:
image_tf[image_tf == nodata] = np.nan
# 简单归一化示例:按最大反射率范围缩放
image_tf = image_tf / 10000.0
# 把 NaN 替换为 0,避免 TensorFlow 训练报错
image_tf = np.nan_to_num(image_tf, nan=0.0)
如果不同波段的数值范围差异很大,可以按波段分别计算均值和标准差,但要注意训练集和预测集必须使用同一套归一化参数。
步骤4:增加批次维度
TensorFlow 模型通常需要输入 [batch, height, width, channels]。
input_tensor = np.expand_dims(image_tf, axis=0)
print(input_tensor.shape)
此时可以把 input_tensor 送入 TensorFlow 模型。但实际项目中,整景影像通常太大,不能一次性送入 GPU,所以更常见的做法是切成小块。
步骤:遥感影像切片并保持标签同步
对于语义分割、建筑物提取、耕地识别、水体提取等任务,常用做法是把大影像切成固定大小的 patch,例如 256×256 或 512×512。影像切片和标签切片必须使用同样的窗口,否则训练标签会错位。
步骤1:按窗口读取影像和标签
import rasterio
from rasterio.windows import Window
import numpy as np
image_path = "image.tif"
label_path = "label.tif"
patch_size = 256
stride = 256
image_patches = []
label_patches = []
with rasterio.open(image_path) as img_src, rasterio.open(label_path) as lab_src:
assert img_src.width == lab_src.width
assert img_src.height == lab_src.height
assert img_src.transform == lab_src.transform
assert img_src.crs == lab_src.crs
for row in range(0, img_src.height - patch_size + 1, stride):
for col in range(0, img_src.width - patch_size + 1, stride):
window = Window(col, row, patch_size, patch_size)
img = img_src.read(window=window) # [bands, h, w]
lab = lab_src.read(1, window=window) # [h, w]
img = np.transpose(img, (1, 2, 0)).astype(np.float32)
img = img / 10000.0
lab = lab.astype(np.int32)
image_patches.append(img)
label_patches.append(lab)
image_patches = np.array(image_patches)
label_patches = np.array(label_patches)
print(image_patches.shape)
print(label_patches.shape)
步骤2:检查标签类别值
训练前要确认标签不是彩色图像,而是类别编码。例如水体为 1、非水体为 0,建筑物为 1、背景为 0。可以用下面代码检查类别值:
unique_values = np.unique(label_patches)
print(unique_values)
如果标签是 RGB 彩色图,需要先把颜色映射成类别 ID。否则 TensorFlow 会把颜色值当成连续数值,导致训练目标错误。
步骤3:构建TensorFlow数据集
import tensorflow as tf
dataset = tf.data.Dataset.from_tensor_slices((image_patches, label_patches))
dataset = dataset.shuffle(1000).batch(8).prefetch(tf.data.AUTOTUNE)
for x, y in dataset.take(1):
print(x.shape)
print(y.shape)
这种方式适合数据量不大的实验。如果样本很多,建议把切片写成 TFRecord,以提高读取效率并减少内存占用。
步骤:GeoTIFF转TFRecord用于大规模训练
GeoTIFF 转 TFRecord 是 TensorFlow处理遥感影像时比较常见的正式训练方案。TFRecord 是 TensorFlow 推荐的数据存储格式,适合把大量样本顺序写入文件,再通过 tf.data 高效读取。
步骤1:定义TFRecord写入函数
import tensorflow as tf
import numpy as np
def bytes_feature(value):
return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))
def int64_feature(value):
return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
def write_example(image_patch, label_patch):
image_patch = image_patch.astype(np.float32)
label_patch = label_patch.astype(np.int32)
feature = {
"height": int64_feature(image_patch.shape[0]),
"width": int64_feature(image_patch.shape[1]),
"channels": int64_feature(image_patch.shape[2]),
"image": bytes_feature(image_patch.tobytes()),
"label": bytes_feature(label_patch.tobytes())
}
example = tf.train.Example(features=tf.train.Features(feature=feature))
return example.SerializeToString()
步骤2:写出TFRecord文件
tfrecord_path = "train.tfrecord"
with tf.io.TFRecordWriter(tfrecord_path) as writer:
for img, lab in zip(image_patches, label_patches):
example = write_example(img, lab)
writer.write(example)
print("TFRecord saved:", tfrecord_path)
步骤3:读取TFRecord
def parse_example(example_proto):
feature_description = {
"height": tf.io.FixedLenFeature([], tf.int64),
"width": tf.io.FixedLenFeature([], tf.int64),
"channels": tf.io.FixedLenFeature([], tf.int64),
"image": tf.io.FixedLenFeature([], tf.string),
"label": tf.io.FixedLenFeature([], tf.string)
}
parsed = tf.io.parse_single_example(example_proto, feature_description)
h = tf.cast(parsed["height"], tf.int32)
w = tf.cast(parsed["width"], tf.int32)
c = tf.cast(parsed["channels"], tf.int32)
image = tf.io.decode_raw(parsed["image"], tf.float32)
image = tf.reshape(image, [h, w, c])
label = tf.io.decode_raw(parsed["label"], tf.int32)
label = tf.reshape(label, [h, w])
return image, label
dataset = tf.data.TFRecordDataset(["train.tfrecord"])
dataset = dataset.map(parse_example, num_parallel_calls=tf.data.AUTOTUNE)
dataset = dataset.batch(8).prefetch(tf.data.AUTOTUNE)
如果你的训练样本来自多个影像区域,建议在 TFRecord 中额外保存样本来源、行列号、影像 ID 等信息,方便后续定位错误样本。
步骤:TensorFlow预测结果回写为GeoTIFF
很多遥感深度学习项目最后都要输出可在 QGIS、ArcGIS Pro 或 WebGIS 中查看的 GeoTIFF。此时要用原始影像的 profile、transform 和 crs 来恢复地理参考。
示例:把分类结果写回GeoTIFF
import rasterio
import numpy as np
# 假设 prediction 是模型输出后的二维分类结果 [height, width]
prediction = np.zeros((profile["height"], profile["width"]), dtype=np.uint8)
out_profile = profile.copy()
out_profile.update({
"count": 1,
"dtype": "uint8",
"nodata": 0
})
with rasterio.open("prediction_result.tif", "w", **out_profile) as dst:
dst.write(prediction, 1)
如果你是按 patch 预测,需要把每个 patch 按原来的 row、col 位置拼回去。对于有重叠的切片,可以用投票、平均概率或中心区域裁剪来减少边缘接缝。
常见坑:TensorFlow处理遥感影像格式转换最容易错在哪里
坑1:把GeoTIFF直接转JPG导致信息丢失
GeoTIFF 转 JPG 会丢失坐标系、仿射变换、NoData 和高位深信息。对于三波段可视化样本可以这样做,但不建议作为严肃遥感模型训练的默认方案。
坑2:波段顺序搞反
Rasterio 读取是 [bands, height, width],TensorFlow 常用是 [height, width, channels]。如果没有转置,模型输入维度会错误,或者把高度当成通道。
坑3:训练和预测归一化方式不一致
训练时除以 10000,预测时却除以 255,会让模型输入分布完全变化。归一化规则必须写入配置,并在训练、验证、预测阶段保持一致。
坑4:影像和标签没有严格对齐
语义分割任务中,影像和标签的 CRS、Transform、分辨率、宽高必须一致。只看地图上“差不多重合”是不够的,必须用代码检查。
坑5:NoData参与训练
如果 NoData 区域没有掩膜,模型会学习到无效边界、黑边或填充值特征。建议在切片时过滤无效像素比例过高的 patch。
坑6:整景影像一次性读入显存
大幅遥感影像不能直接整体送入 TensorFlow。正确做法是窗口读取、切片训练、分块预测,再把结果拼接回 GeoTIFF。
方法比较:NumPy、TFRecord、PNG切片和GeoTIFF切片怎么选
| 方法 | 推荐程度 | 适合任务 | GIS注意点 |
|---|---|---|---|
| GeoTIFF 转 NumPy | 适合入门调试 | 小样本实验、快速验证 | 需要手动保存 profile 和 transform |
| GeoTIFF 转 TFRecord | 适合正式训练 | 大规模分类、分割训练 | 建议保存样本位置和来源影像 ID |
| GeoTIFF 转 PNG/JPEG | 谨慎使用 | RGB 可视化、简单分类样本 | 会丢失地理参考和高位深信息 |
| 保留 GeoTIFF 切片 | 适合GIS工作流 | 需要回溯空间位置的项目 | 文件数量多,训练读取需额外封装 |
如果你只是学习 TensorFlow处理遥感影像,可以先用 NumPy;如果要做工程化训练,建议使用 TFRecord;如果项目强调空间位置追踪,可以同时保存 GeoTIFF 切片和 TFRecord 索引。
检查清单:转换前后一定要核对这些内容
- 原始影像和标签的 CRS 是否一致。
- 原始影像和标签的 Transform 是否一致。
- 影像和标签的宽、高、分辨率是否一致。
- 波段顺序是否符合模型设计,例如 RGB、NIR、SWIR 的顺序。
- 数组维度是否从
[bands, height, width]转成[height, width, channels]。 - NoData 是否被掩膜或替换,是否避免参与训练。
- 归一化参数是否在训练和预测中保持一致。
- 切片 row、col 是否被保存,方便预测结果拼回原图。
- TFRecord 解析时的数据类型是否与写入时一致。
- 输出 GeoTIFF 是否正确写入 CRS、Transform、dtype 和 nodata。
FAQ:TensorFlow处理遥感影像数据格式转换常见问题
Q1:TensorFlow可以直接读取GeoTIFF吗?
TensorFlow 标准图像读取接口主要面向 JPEG、PNG 等普通图片,不适合直接读取带地理参考的 GeoTIFF。实际项目中通常用 Rasterio、GDAL 或 rioxarray 读取 GeoTIFF,再转成 NumPy 数组或 TFRecord。
Q2:GeoTIFF转TFRecord后坐标系会不会丢失?
TFRecord 本身不会自动保留坐标系。你需要主动把 CRS、Transform、切片 row、col、原始影像 ID 等信息保存到额外字段或索引文件中。否则模型预测结果很难准确回写到原始地图位置。
Q3:遥感影像一定要转成PNG才能训练吗?
不一定。PNG 适合普通三通道图片或可视化检查,但遥感影像常有多波段和高位深数据,转 PNG 可能丢失信息。对于多光谱影像,推荐使用 NumPy 或 TFRecord。
Q4:多波段遥感影像怎么输入TensorFlow模型?
读取后把数组整理成 [height, width, channels],其中 channels 就是波段数。比如 4 波段影像输入形状可以是 [256, 256, 4]。同时模型第一层输入通道数也要设置为 4。
Q5:影像切片大小选256还是512?
没有固定答案。256×256 训练更轻,适合显存较小和样本较多的情况;512×512 能保留更多上下文,但显存占用更高。建议先从 256 开始,再根据目标尺度和 GPU 显存调整。
Q6:预测结果为什么在QGIS里位置不对?
常见原因是写出 GeoTIFF 时没有使用原始影像的 CRS 和 Transform,或者 patch 拼接时 row、col 位置错了。应先检查输出文件的坐标系、分辨率、范围是否与原始影像一致。
Q7:标签是矢量面,怎么做成TensorFlow训练标签?
需要先把矢量面栅格化成与影像完全一致的标签栅格。栅格化时要使用原始影像的分辨率、范围、CRS 和 Transform,确保每个像素位置严格对应。
结论:先保证GIS空间一致性,再谈TensorFlow训练效果
“TensorFlow处理遥感影像?数据格式咋转?”这个问题的核心,不是选择某一个万能格式,而是建立一条可靠的数据转换链路:GeoTIFF 读取、波段整理、NoData 处理、归一化、切片、TFRecord 或 NumPy 训练、预测结果回写 GeoTIFF。
对于入门实验,推荐先用 Rasterio 将 GeoTIFF 转 NumPy,跑通模型输入输出;对于正式项目,推荐使用 TFRecord 管理大规模训练样本;对于需要在 QGIS 或 ArcGIS Pro 中查看成果的任务,必须保留原始影像的 CRS、Transform 和切片位置。
只要把空间参考、数组维度、标签对齐和归一化这四件事处理好,TensorFlow 处理遥感影像的数据格式转换就会清晰很多,后续模型训练和结果制图也更容易排错。