TensorFlow处理遥感影像?数据格式咋转?

GIS基础理论
Dr.GIS
wowwwai GIS研习社 · 工具流程与项目排障

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

TensorFlow处理遥感影像 GeoTIFF转TFRecord数据格式转换流程
遥感影像进入 TensorFlow 前,通常需要完成读取、切片、归一化、格式转换和地理参考保存几个步骤。

引言: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。此时要用原始影像的 profiletransformcrs 来恢复地理参考。

示例:把分类结果写回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 处理遥感影像的数据格式转换就会清晰很多,后续模型训练和结果制图也更容易排错。