☰
遥感大图随机切图实战:从原理到代码的完整指南
2026/10/15 22:15:32 网站建设 项目流程

1. 遥感大图切分这件事,为什么值得单独拎出来讲

做遥感影像深度学习的人,几乎都绕不开一个坎:手里拿到的是一整张大图,可能是几千乘几千像素,也可能是上万乘上万像素的正射影像,而模型能吃的往往只是 256×256 或者 512×512 的小图。这中间的落差,不是简单一句“切一下”就能糊弄过去的。

我自己最开始做遥感语义分割的时候,就吃过这个亏。当时拿到一张大概 8000×8000 的影像,想着直接切就完事了,结果切出来的训练集要么全是背景,要么全是同一类地物,模型训着训着就摆烂了。后来才慢慢意识到,切图这件事本身就是一门学问,它直接决定了训练集的质量,而训练集质量又直接决定了模型的上限。

这篇内容我想聊的就是:怎么把一张遥感大图,随机地、合理地分割成一批小图,拿来当训练集。关键词是“随机分割”,但随机不等于乱来,里面有不少门道。适合正在做遥感分割、目标检测、变化检测的朋友,也适合刚入门、手里只有大图不知道怎么下手的新手。我会把原理、参数、代码、踩过的坑都摊开讲,尽量让你看完就能直接抄作业。

2. 先想清楚:为什么是“随机切”而不是“网格切”

2.1 网格切图最省事,但它的毛病很致命

最直觉的做法是网格切图:从左上角开始,按固定步长滑动窗口,比如每 256 像素切一刀,规规矩矩地把大图切成一个矩阵排列的小图集合。代码写起来也简单,两层循环就搞定。

但问题在于,遥感影像的地物分布往往是不均匀的。比如一张城市影像,可能左上角是密集建筑区,右下角是大片农田,中间还有一条河。网格切图会把这种空间分布原封不动地带进训练集——如果建筑区恰好占了 60% 的面积,那训练集里就有 60% 的样本是建筑,模型会严重偏向这一类。

更麻烦的是,网格切图容易切出大量“无效样本”。遥感影像里经常有大片同质区域,比如一整块水体、一整片裸地,网格切下去,可能连续几十张小图长得几乎一模一样。这些冗余样本不仅浪费训练时间,还会让模型过拟合到某些特定纹理上。

2.2 随机切图解决的核心问题

随机切图的做法是:不按固定网格走,而是在大图范围内随机采样若干个切图中心点,再以这些点为中心抠出固定尺寸的小图。这样做有几个直接好处。

第一,样本分布更接近真实分布的无偏估计。随机采样意味着每个位置被选中的概率相同,长期来看,训练集的类别比例会趋近于整张大图的比例,而不是被空间聚集性放大。

第二,天然带来数据增强的效果。每次随机切出来的小图组合都不一样,相当于在数据层面做了一次重采样。对于小数据集来说,这种随机性本身就是一种正则化。

第三,方便控制样本数量和类别平衡。你可以先随机切一大批,再根据每张小图的标签分布做筛选,把纯背景的、纯单一类别的按比例剔除或保留,主动权在自己手里。

不过随机切也有代价:可能切出很多“半截”目标。比如一栋建筑正好被切在边缘,只剩一半。这个问题后面会专门讲怎么缓解。

2.3 两种策略的对比

对比维度网格切图随机切图
实现难度低,两层循环中,需要采样逻辑
样本分布受空间聚集影响大接近全局无偏估计
冗余样本多,同质区域重复可通过去重缓解
数据增强效果无天然具备
边缘截断问题固定位置截断随机位置截断
适用场景地物均匀分布地物分布不均、类别不平衡

我自己的经验是:如果影像地物分布比较均匀,网格切图够用;但只要涉及类别不平衡或者空间聚集明显,随机切图基本是更优解。实际项目里我通常两种都切一份,对比一下训练效果再定。

3. 随机切图的核心参数,每一个都别拍脑袋定

3.1 切图尺寸:不是越大越好,也不是越小越好

切图尺寸直接受限于模型的输入要求。常见的分割网络像 U-Net、DeepLab 系列,输入 256×256 或 512×512 都很常见。但尺寸选择不只是“迁就模型”,还要考虑地物尺度。

举个具体的例子:如果你做的是建筑物提取,一栋普通居民楼在 0.5 米分辨率的影像上大概占 20×20 像素。切 256×256 的话,一张图里能包含十几栋楼,上下文信息足够。但如果你做的是车辆检测,一辆车可能只有 10×10 像素,切 256×256 就有点浪费,因为大部分区域是背景,可以考虑切小一点,比如 128×128,提高有效目标的密度。

我的经验法则是:切图尺寸至少要是目标典型尺寸的 8 到 10 倍。这样既能保证目标完整,又能提供足够的上下文。太小了目标容易被截断,太大了单张图的信息密度低,训练效率下降。

另外要注意显存。512×512 的 RGB 图,batch size 设 8 的话,显存占用比 256×256 的 batch size 8 高出四倍左右。如果显存吃紧,宁可切小一点、多切一些样本,也不要硬上大尺寸然后被迫把 batch size 降到 1,那样训练会很不稳定。

3.2 采样数量:切多少张才够

这个问题没有标准答案,但有一个实用的估算思路。假设你的大图是 10000×10000,切图尺寸 256×256,那么理论上最多能切出大约 (10000/256)² ≈ 1525 张不重叠的小图。如果允许重叠,数量还能翻几倍。

但“能切多少”和“该切多少”是两回事。对于训练集,我通常按下面的逻辑来定:

  • 先看类别数量。如果是二分类(比如建筑/非建筑),每类至少需要几百到上千个正样本才比较稳。
  • 再看模型参数量。小模型(几百万参数)对数据量要求低一些,大模型(几千万参数)需要更多数据。
  • 最后看算力预算。切太多训练一轮太慢,切太少又不够。

实际操作中,我会先随机切 2000 到 5000 张作为候选池,然后统计每张图的标签分布,再按需筛选。这个量级对于大多数遥感分割任务来说,配合迁移学习基本够用了。

3.3 重叠率:切图之间的“安全距离”

随机切图如果不控制重叠,可能切出大量高度相似的小图,尤其是当采样点挨得很近的时候。解决办法是设置一个最小中心点距离,或者用重叠率来约束。

重叠率指的是相邻两张切图之间重叠区域占切图尺寸的比例。比如切图 256×256,重叠率 0.5,意味着相邻切图中心点距离至少 128 像素。这样既能保证覆盖度,又能减少冗余。

但随机切图本身没有“相邻”的概念,所以更实用的做法是:在采样时记录已选中心点,新采样点与所有已选点的距离必须大于某个阈值。这个阈值可以设为切图尺寸乘以 (1 - 重叠率)。比如切图 256,希望重叠不超过 50%,那阈值就是 128。

提示:这个距离检查在采样数量多的时候会变慢,可以用网格哈希或者 KD 树来加速。样本量几千的时候,暴力循环也能接受,不用过度优化。

3.4 边界处理:别让目标被切得七零八落

随机切图最让人头疼的就是边缘截断。一张图切下去,正好把一栋楼切成两半,标签也跟着断掉,模型学到的就是残缺目标,推理时遇到完整目标反而可能出问题。

缓解办法有几个。第一个是padding:在切图之前,先给大图四周补一圈,补的宽度等于切图尺寸的一半。这样即使采样点靠近原图边缘,切出来的小图也不会越界,边缘目标至少有一半能保留。补的方式可以用镜像、常数填充或者直接复制边缘像素,遥感影像里我一般用镜像填充,视觉上更自然。

第二个是过滤:切完之后检查每张小图,如果边缘区域有目标被截断,且截断比例超过某个阈值(比如目标面积的 30% 被切掉了),就把这张图丢掉或者标记为低质量样本。

第三个是采样点约束:把采样范围限制在大图内部,距离边缘至少半个切图尺寸。这样切出来的图永远不越界,但代价是边缘区域的目标永远进不了训练集。如果边缘区域有重要地物,这个方法就不合适。

我通常组合使用:先 padding,再过滤,采样点约束作为可选项。padding 是必做的,过滤看任务对目标完整性的敏感程度。

4. 从大图到小图:一套可复现的随机切图流程

4.1 整体流程拆解

在动手写代码之前,先把流程理清楚。随机切图不是一步操作,而是一条流水线:

  1. 读取大图和对应的标签图(如果有的话)。
  2. 对大图做 padding,避免切图越界。
  3. 确定采样区域范围(考虑 padding 后的坐标偏移)。
  4. 随机采样中心点,同时做最小距离约束。
  5. 以每个中心点为中心,抠出固定尺寸的图像块和标签块。
  6. 对每个图像块做质量检查(标签分布、截断情况)。
  7. 保存合格的图像块,记录元数据(中心点坐标、标签统计等)。

这条流水线里,第 4 步和第 6 步是决定质量的关键。第 4 步控制样本的空间分布,第 6 步控制样本的类别分布。

4.2 采样中心点的生成逻辑

采样中心点看起来简单,但有几个细节容易忽略。

第一个细节是采样范围。padding 之后,大图的有效尺寸变成了 (H + 2p, W + 2p),其中 p 是 padding 宽度。切图尺寸是 s,那么中心点的合法范围是 [s/2, H + 2p - s/2]。如果直接用原始尺寸算,切出来的图会越界。

第二个细节是最小距离约束的实现。最朴素的做法是维护一个已选点列表,每次采样后遍历列表检查距离。样本量上千的时候,这个 O(n²) 的复杂度还能接受;上万就明显卡了。可以用一个空间网格来加速:把大图划分成边长为阈值的网格,每个网格记录已选点,新点只需要检查所在网格及相邻网格。

第三个细节是采样数量与去重的平衡。如果你想要 3000 张图,但最小距离约束很严格,可能采样到 2500 张就再也找不到合法点了。这时候要么放宽距离约束,要么接受更少的样本。我的做法是设置一个最大尝试次数,比如尝试 10 万次采样,能采多少算多少,然后看实际数量是否满足需求。

4.3 标签图的同步切分

遥感任务里,图像和标签必须严格对齐。切图的时候,图像块和标签块要用完全相同的中心点和尺寸,否则标签就错位了。

这里有个容易踩的坑:图像和标签的插值方式不同。图像切图时如果需要缩放(一般不需要,因为切图是裁剪不是缩放),要用双线性插值;但标签图绝对不能用双线性插值,否则会出现 0.5 这种类别值,必须用最近邻插值。好在随机切图是纯裁剪操作,不涉及缩放,所以这个问题在切图阶段不会出现。但如果你后续对切出来的小图做 resize 增强,就一定要注意标签用最近邻。

另一个坑是多波段图像的处理。遥感影像可能有多个波段,比如 RGB 加近红外,共 4 个波段。切图的时候要保证所有波段同步切,不能只切 RGB 然后忘了近红外。用 numpy 的话,如果图像是 (H, W, C) 格式,切片操作img[y:y+s, x:x+s, :]会自动带上所有波段,比较省心。如果是 (C, H, W) 格式,就要写成img[:, y:y+s, x:x+s]。

4.4 质量检查与样本筛选

切完图不是就完事了,必须做质量检查。我通常检查三个指标:

标签覆盖率:每张小图里,有效标签(非背景、非忽略区域)占的比例。如果一张图 95% 都是背景,那它对训练的贡献很小,可以考虑丢掉。但也不能一刀切,因为背景样本对模型学会“什么是背景”也有价值。我的做法是保留一部分纯背景样本,比例控制在总样本的 10% 到 20%。

类别分布:统计每张小图里各类别的像素占比。如果某个类别在整张大图里占 5%,但切出来的样本里有一半以上完全不含这个类别,说明采样可能偏了。这时候可以针对性地增加含稀有类别的采样点。

边缘截断:检查小图边缘是否有目标被截断。这个需要标签的连通域信息,实现起来稍复杂。简单版本是看边缘一圈像素里有没有有效标签,如果有且面积占比不小,就标记为可疑样本。

下面是一个简化的质量检查代码示例:

import numpy as np def check_patch_quality(img_patch, label_patch, ignore_value=255): """检查切图质量,返回统计信息""" valid_mask = label_patch != ignore_value valid_ratio = valid_mask.sum() / label_patch.size # 统计各类别占比 unique, counts = np.unique(label_patch[valid_mask], return_counts=True) class_dist = dict(zip(unique.tolist(), (counts / valid_mask.sum()).tolist())) # 检查边缘截断:看边缘一圈是否有有效标签 edge_label = np.concatenate([ label_patch[0, :], label_patch[-1, :], label_patch[:, 0], label_patch[:, -1] ]) edge_valid_ratio = (edge_label != ignore_value).sum() / edge_label.size return { 'valid_ratio': valid_ratio, 'class_dist': class_dist, 'edge_valid_ratio': edge_valid_ratio }

这个函数返回的信息可以用来做筛选决策。比如 valid_ratio 低于 0.05 的丢掉,edge_valid_ratio 高于 0.3 的标记为可疑。

5. 代码落地:一份能直接跑的随机切图脚本

5.1 依赖与输入约定

这份脚本只依赖 numpy 和 opencv(或者 PIL),不依赖深度学习框架,所以很轻量。输入约定如下:

  • 大图路径:支持 tif、png、jpg 等常见格式。
  • 标签路径:可选,如果有的话,格式和大图一致,单波段。
  • 输出目录:切出来的小图会按序号命名保存。
  • 参数:切图尺寸、采样数量、最小距离、padding 宽度等。

我用 rasterio 读 tif 是因为遥感影像经常是多波段的,rasterio 对波段的处理比较友好。如果你只有 RGB 图,用 opencv 的cv2.imread也完全够用。

5.2 核心切图函数

import os import numpy as np import rasterio from rasterio.pad import pad # 注意:rasterio没有pad,这里用numpy实现 def random_crop_large_image( img_path, label_path=None, output_dir='./patches', patch_size=256, num_patches=3000, min_distance=None, pad_width=None, max_attempts=100000, seed=42 ): """ 将大图随机切分为小图 """ np.random.seed(seed) os.makedirs(output_dir, exist_ok=True) # 读取大图 with rasterio.open(img_path) as src: img = src.read() # (C, H, W) img = np.transpose(img, (1, 2, 0)) # 转为 (H, W, C) H, W, C = img.shape # 读取标签 label = None if label_path is not None: with rasterio.open(label_path) as src: label = src.read(1) # (H, W) # 设置padding if pad_width is None: pad_width = patch_size // 2 # 镜像padding img_padded = np.pad( img, ((pad_width, pad_width), (pad_width, pad_width), (0, 0)), mode='reflect' ) if label is not None: label_padded = np.pad( label, ((pad_width, pad_width), (pad_width, pad_width)), mode='reflect' ) Hp, Wp = img_padded.shape[:2] # 设置最小距离 if min_distance is None: min_distance = patch_size // 2 # 采样中心点 half = patch_size // 2 centers = [] attempts = 0 while len(centers) < num_patches and attempts < max_attempts: attempts += 1 cy = np.random.randint(half, Hp - half) cx = np.random.randint(half, Wp - half) # 检查最小距离 too_close = False for (py, px) in centers: if (cy - py) ** 2 + (cx - px) ** 2 < min_distance ** 2: too_close = True break if too_close: continue centers.append((cy, cx)) print(f'实际采样到 {len(centers)} 个中心点,尝试了 {attempts} 次') # 切图并保存 saved = 0 for idx, (cy, cx) in enumerate(centers): y1, y2 = cy - half, cy + half x1, x2 = cx - half, cx + half img_patch = img_padded[y1:y2, x1:x2, :] # 保存图像 img_out = os.path.join(output_dir, f'patch_{idx:05d}.png') # 这里用opencv或PIL保存,注意波段顺序 import cv2 if C >= 3: cv2.imwrite(img_out, cv2.cvtColor(img_patch[:, :, :3], cv2.COLOR_RGB2BGR)) else: cv2.imwrite(img_out, img_patch[:, :, 0]) # 保存标签 if label is not None: label_patch = label_padded[y1:y2, x1:x2] label_out = os.path.join(output_dir, f'label_{idx:05d}.png') cv2.imwrite(label_out, label_patch) saved += 1 print(f'共保存 {saved} 张切图到 {output_dir}') return centers

这段代码有几个地方值得说明。第一,padding 用的是reflect模式,比常数填充更自然,不会在边缘引入突兀的黑色区域。第二,最小距离检查用的是欧氏距离的平方,避免开方运算,快一点点。第三,保存图像时如果波段数大于等于 3,只取前三个波段存成 RGB,因为 cv2.imwrite 对多波段支持不好。如果你需要保存所有波段,建议用 rasterio 或者 numpy 的 save 函数。

5.3 采样效率优化:从暴力循环到网格加速

上面的最小距离检查是暴力循环,当 centers 列表有几千个点的时候,每次采样都要遍历一遍,总复杂度是 O(n²)。采样 3000 个点,平均每次检查 1500 个点,总共约 450 万次距离计算,其实还能接受,大概几秒钟。但如果要采样上万个点,就会明显变慢。

优化思路是用空间网格。把 padding 后的图像划分成边长为 min_distance 的网格,每个网格记录落在其中的中心点。新采样点时,只需要检查所在网格及周围 8 个网格里的点,复杂度降到接近 O(1)。

class GridHash: def __init__(self, cell_size): self.cell_size = cell_size self.grid = {} def _key(self, y, x): return (y // self.cell_size, x // self.cell_size) def add(self, y, x): k = self._key(y, x) self.grid.setdefault(k, []).append((y, x)) def has_neighbor_within(self, y, x, radius): cy, cx = self._key(y, x) r = int(np.ceil(radius / self.cell_size)) for dy in range(-r, r + 1): for dx in range(-r, r + 1): for (py, px) in self.grid.get((cy + dy, cx + dx), []): if (y - py) ** 2 + (x - px) ** 2 < radius ** 2: return True return False

用这个结构替换暴力循环,采样上万个点也就一两秒的事。不过对于大多数项目,几千个样本暴力循环完全够用,不用过早优化。

5.4 保存格式与命名规范

切图保存看起来是小事,但命名混乱会给后续训练带来很多麻烦。我的命名规范是:

  • 图像:patch_00001.png,五位数字,从 0 开始。
  • 标签:label_00001.png,和图像序号一一对应。
  • 元数据:metadata.csv,记录每张图的中心点坐标、原始大图文件名、标签统计等。

元数据特别重要。当你发现某张图有问题时,可以通过元数据快速定位到它在原图上的位置,方便排查。另外,如果后续要做分层采样或者类别平衡,元数据里的标签统计就是现成的依据。

保存格式上,png 是无损的,适合标签图;图像如果对存储空间敏感,可以用 jpg,但要注意 jpg 是有损压缩,可能引入伪影,对训练有轻微影响。遥感影像我一般用 png 或者 tif,保证无损。

6. 踩过的坑:随机切图里那些文档不会写的问题

6.1 切出来的样本全是背景,模型学了个寂寞

这是我最早踩的坑。一张大图里可能 70% 是背景(比如大片裸地、水体),随机切图如果不加筛选,切出来的样本里大部分也是背景。模型训练时,背景样本占了主导,loss 被背景拉低,但实际推理时对前景目标的识别很差。

解决办法是分层采样。先统计整张大图的类别分布,然后按类别比例来分配采样配额。比如前景占 30%,背景占 70%,那采样时就让前景样本占 30% 左右,而不是任由随机采样决定。具体做法可以是:先随机切一大批,然后按标签覆盖率分成“前景丰富”和“背景为主”两组,再按比例抽取。

另一个办法是加权损失。切图阶段不控制比例,但在训练时给前景类别更高的权重。这个方法更简单,但效果不如分层采样直接。我通常两个一起用,双保险。

6.2 采样点扎堆,切出一堆几乎一样的图

随机采样如果没有最小距离约束,可能出现采样点扎堆的情况。尤其是当采样数量接近理论上限时,随机采样会倾向于填满所有空隙,导致很多切图高度重叠。

我遇到过最夸张的情况:两张切图的中心点只差了 10 个像素,切出来的图 95% 的区域是重合的。这种样本对训练几乎没有额外贡献,反而增加了计算量。

最小距离约束是必须的。但阈值设多少合适?我的经验是设为切图尺寸的 0.5 到 0.7 倍。太小了冗余多,太大了采样数量上不去。具体数值可以这样估算:如果大图面积是 A,切图面积是 s²,最小距离是 d,那么理论上最多能放大约 A / (d²) 个点。用这个公式反推 d,就能在采样数量和冗余度之间找到平衡。

6.3 标签忽略区域的处理,一不小心就学错了

遥感标签里经常有“忽略区域”,比如云遮挡、阴影、不确定区域,通常用 255 或者 -1 表示。切图的时候,这些忽略区域会跟着切进来。如果训练时不对它们做处理,模型会把忽略区域当成一个正常类别来学,结果就是推理时把云也预测成某个地物。

处理办法有两个。一是在损失函数里屏蔽忽略区域,比如 PyTorch 的 CrossEntropyLoss 有 ignore_index 参数,设成 255 就行。二是在切图阶段就把忽略区域占比过高的样本丢掉。我通常两个都做:切图时丢掉忽略区域超过 50% 的样本,训练时再设 ignore_index 兜底。

还有一个隐蔽的坑:padding 引入的伪标签。如果你用镜像 padding 处理标签图,边缘的忽略区域会被镜像复制,可能产生一些奇怪的标签组合。虽然影响不大,但如果你的任务对边缘敏感,建议标签图用常数填充(比如填 255),而不是镜像。

6.4 多波段影像的波段顺序,搞反了训练全白费

遥感影像的波段顺序和普通 RGB 不一样。常见的多光谱影像可能是蓝、绿、红、近红外,而 opencv 读进来默认是 BGR。如果你用 opencv 读图然后直接送进模型,而模型期望的是 RGB,颜色就全反了。

更麻烦的是,有些影像的波段顺序在元数据里,不读元数据根本不知道。我建议在读图阶段就统一转成 RGB 顺序,并且把波段信息记录在元数据里。切图的时候保持这个顺序不变,训练和推理都用同一套顺序。

如果你用的是 rasterio,src.read()返回的是 (bands, H, W),波段顺序就是文件里的原始顺序。你需要根据元数据或者数据说明来确定哪个波段是红、哪个是绿。这一步千万别偷懒,我见过有人把近红外当成红波段用,训练出来的模型在可视化时颜色诡异,排查了半天才发现是波段顺序错了。

7. 切完之后:训练集的组织与验证集划分

7.1 训练集和验证集怎么分才合理

切出来的小图不能随便分训练集和验证集。如果随机分,可能出现训练集和验证集里的样本来自大图的同一区域,导致验证集失去意义——模型在训练时已经“见过”那片区域了,验证指标虚高。

正确的做法是按空间区域划分。把大图划分成几个不重叠的区块,比如按田字形分成四块,其中三块切出来的图做训练,一块做验证。这样验证集的样本在空间上和训练集完全隔离,指标更可信。

如果大图只有一张,没法按区域分,那就按采样中心点的位置来分。比如以图像中心为界,左半部分的切图做训练,右半部分做验证。虽然不如区块划分严格,但比纯随机分好得多。

7.2 样本去重:别让相似的图同时进训练集

随机切图可能产生高度相似的样本,尤其是当最小距离约束不够严格时。这些相似样本如果同时进训练集,相当于变相增加了某些区域的权重,可能导致过拟合。

去重的思路是计算样本之间的相似度,把相似度超过阈值的样本只保留一张。相似度可以用直方图对比、感知哈希或者简单的像素差。对于遥感影像,我常用的是缩略图加均方误差:把每张切图缩到 32×32,然后两两算 MSE,超过阈值的视为重复。

但去重也有代价:可能把一些有价值的样本也去掉。所以阈值要调,我一般从 0.95 的相似度开始试,看去掉多少样本,再决定是否放宽。

7.3 数据增强与切图的配合

切图本身是一种数据增强,但不是万能的。切完之后还可以叠加其他增强,比如翻转、旋转、颜色抖动。但要注意,遥感影像的增强和自然图像不太一样。

翻转和旋转一般没问题,但旋转要小心:遥感影像有方向性,比如建筑物的朝向、道路的走向,过度旋转可能引入不真实的样本。我通常只用 90 度的整数倍旋转,加上水平翻转和垂直翻转,这样不会引入插值伪影,也不会破坏地物的方向分布。

颜色抖动要谨慎。遥感影像的色调和地物类型有强关联,比如植被是绿的,水体是蓝的。如果颜色抖动太猛,可能把植被调成黄色,模型就学乱了。我一般只做轻微的亮度和对比度调整,幅度控制在 10% 以内。

8. 一些让切图更聪明的进阶思路

8.1 基于内容的自适应采样

纯随机采样虽然无偏,但效率不是最高的。如果能让采样更“聪明”一点,比如优先采样包含稀有类别或者目标密集的区域,就能用更少的样本达到更好的训练效果。

实现思路是:先对整张大图做一个粗略的类别概率图(可以用一个预训练模型跑一遍,或者用简单的阈值分割),然后根据概率图来加权采样。目标密集的区域采样概率高,纯背景区域采样概率低。

这个方法的效果取决于概率图的准确性。如果概率图本身不准,加权采样可能引入偏差。我一般把它作为可选步骤,在样本量充足时用纯随机,样本量紧张时用加权采样。

8.2 多尺度切图

单一尺度的切图可能无法覆盖所有目标。比如一张图里既有大面积的农田,又有小型的建筑物。切图尺寸固定的话,要么农田被切得太碎,要么建筑物太小看不清。

多尺度切图的做法是:用不同的切图尺寸各切一批,比如 128、256、512 各切一些,然后混合训练。这样模型能同时看到不同尺度的目标,泛化能力更强。

代价是训练复杂度上升,因为不同尺寸的图不能放在同一个 batch 里(除非做 resize)。我的做法是分阶段训练:先用大尺寸切图训一个基础模型,再用小尺寸切图微调,或者反过来。

8.3 切图与在线采样的结合

如果训练时数据量很大,切图全部存到磁盘再读会占用大量空间和 IO。另一种思路是在线切图:训练时实时从大图里随机切,切完直接送进模型,不落盘。

在线切图的好处是节省存储,而且每次 epoch 切出来的样本都不一样,数据增强效果更强。坏处是训练速度受限于切图速度,如果大图读取慢,会拖累训练。

实现上可以用 PyTorch 的 Dataset 类,在__getitem__里做随机切图。大图可以预先加载到内存(如果内存够的话),或者用内存映射文件。对于特别大的图,可以用 rasterio 的窗口读取,只读需要的区域,避免把整张图加载进来。

9. 我个人在实际操作中的几点体会

切图这件事,看起来是数据预处理里最不起眼的一环,但它对最终效果的影响远超很多人的预期。我做过对比实验,同样的模型、同样的训练参数,只是切图策略不同,mIoU 能差 5 到 10 个百分点。这个差距,比换一个 backbone 带来的提升还大。

我的核心体会是:切图不是机械操作,而是对数据分布的一次重新设计。你在切图阶段做的每一个决定——切多大、切多少、怎么采样、怎么筛选——都是在告诉模型“什么重要、什么不重要”。这个信号如果给对了,模型学起来事半功倍;给错了,后面再怎么调参都是事倍功半。

另外一点是,切图策略要和任务匹配。做建筑物提取和做农作物分类,切图策略应该不一样。前者需要保留建筑物的完整形状,后者更关注纹理和光谱特征。没有一套通用的最优参数,只能根据任务特点去试。我通常会在小规模数据上快速试几组参数,看验证集表现,再放大到全量数据。

最后分享一个小技巧:切完图之后,随机抽几十张可视化出来看看。不用多,几十张就够。你会直观地发现很多统计指标看不出来的问题,比如某些类别的样本明显偏少、某些区域的切图质量明显差、标签有系统性偏移等等。这个习惯帮我省了很多返工的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询