简介:针对城市绿地信息提取中小样本分类困难、分类模型单一等问题,这份PDF完整给出基于卷积神经网络(CNN)的场景分类技术路线:利用WorldView-2高分辨率遥感影像建立影像库,通过LiSVM分类器完成像元二分类,再以CNN进行深度学习训练与多尺度影像分类,融合两次分类结果实现南京市建邺区绿地自动提取,总体分类精度达到87.74%。论文还介绍了AlexNet与哈希编码结合以缩短训练时间、基于CNN与E2LSH的遥感图像检索方法以减少信息损失,并探讨了军事目标识别、人脸表情识别等扩展应用,兼顾原理与工程细节。资源共1个文件,为单篇PDF论文,大小1.63MB,已有130人学习下载,适合遥感、测绘及深度学习研究者快速获取完整实验框架与算法对比细节。文中还包含卷积神经网络理论模型、影像预处理、实验设置与精度评价等内容,便于直接复现与参考。
1. 绿地信息提取为什么先做场景分类,而不是直接分割
拿到绿地信息提取任务,大多数团队的第一反应是上语义分割模型,逐像素标出树、草、裸土、水体。我现在的习惯是先停一步,把问题收敛成「场景分类 + 滑动窗口概率制图」的组合方案。原因很简单:遥感影像的绿地呈现强上下文特征——一片杂草丛生的裸地和一个打理整齐的公园草皮,在像素颜色上几乎无法区分,但把它们放到足够大的场景窗口里,视觉语义就拉开了。场景分类神经网络天然能学到这种大尺度特征,训练数据也更容易获得。
这个标题真正的含义不是「用CNN做像素分割」,而是把绿地信息提取转换成影像块的类别判断问题,再通过滑动窗口、概率融合和矢量化输出最终的绿地分布。对遥感从业者、土地调查人员和地理信息工程师来说,这条路线的工程成本低、模型可迁移性强,尤其适合处理高分辨率无人机影像和亚米级卫星影像。如果你也纠结过「标注像素太贵」「模型精度上不去」「跨区域泛化崩盘」,这篇文章会讲清楚模型怎么选、参数怎么设、坑在哪里。
2. 卷积神经网络场景分类与语义分割的边界,以及为什么选前者
2.1 场景分类不是「粗糙的分割」,而是把决策权交给上下文
语义分割网络(如UNet、DeepLabV3+)输出的是每个像素的类别,解决的是「这里是什么」;场景分类网络(如ResNet、EfficientNet)输出的是整张图属于某个类别的概率,解决的是「这张图里发生了什么」。绿地信息提取看起来必须走到像素级,但实际上,最终的交付物往往是「绿地斑块」或「绿地占比」,而不是精确到株的植被边界。
场景分类到这个任务里有一个明显优势:它天然具备更大的感受野。以ResNet50为例,最后一层卷积的感受野可以超过200像素,当输入窗口是512×512时,模型能看到整个地块的布局关系。相比之下,UNet在解码阶段虽然有跳跃连接,但它的高层语义来自编码器下采样,本质上编码器基干也在做「场景理解」,只是损失函数不同。换句话说,你完全可以先做场景分类,把概率图当成分割结果的地图,再用阈值和连通域分析转成矢量边界。
提示:如果你的目标只是统计绿地面积、生成绿地分布图,场景分类的精度和稳定性通常优于端到端分割,尤其在训练样本不足时差距更明显。
2.2 从LeNet到图卷积,绿地场景分类的模型选型口径
场景分类骨干网络经历了明显的代际变化。LeNet-5作为最早的CNN结构,只适用于28×28的MNIST级输入,遥感影像输入尺寸动辄512×512甚至更大,它无论从深度还是感受野上都撑不起这个任务。VGG16则用堆叠3×3卷积把网络推到16层,但参数量超过1.3亿,训练和推理都比较吃力。真正适合绿地场景分类的基干,至少要从ResNet起步。
ResNet系列通过残差连接解决了深层网络的梯度消失问题,ResNet50在ImageNet上的Top-5错误率在3.8%左右,迁移到遥感场景分类时,城市和乡村的绿地类别通常能拿到不错的基线。EfficientNet则通过复合缩放统一调整深度、宽度和分辨率,在同等计算量下精度更高,但推理时对GPU显存更敏感。近两年图卷积神经网络(GCN)也被引入场景分类——它的思路是把输入影像超像素分割成区域节点,用图结构建模区域间的关系,在超大型遥感影像上能突破GPU显存限制。但图卷积的工程链更长,需要额外的超像素分割步骤,在绿地提取这类边界相对松散的任务上性价比不高。
2.3 输出层设计:单标签还是多标签,影响后续提取路径
场景分类的最后一层有两种常见设计。一种是单标签分类,用Softmax输出「绿地/非绿地/其他」;另一种是多标签分类,用Sigmoid分别输出「是否含绿地」「是否含水体」「是否含建筑」。我一般更倾向于多标签输出,因为遥感影像块往往同时包含多个类别,一个512×512窗口里既有公园绿地又有道路,单标签训练只会强迫模型二选一,丢失了部分监督信息。
绿地信息提取场景中,多标签的一个直接收益是:你可以用「绿地类别的概率值」直接作为后续提取的置信度输入,而不是先做argmax再二值化。这个值保留了丰富的边界过渡信息,在滑动窗口重叠区域做平均时能得到平滑的绿地概率热图,最终矢量化的边界比硬分类结果自然很多。这本质上也是DeepLab系列引入Softmax概率图做CRF后处理的逻辑,只是绿地提取任务没那么依赖CRF,概率图本身已经够用。
3. 用PyTorch实现绿地场景分类的训练管线:数据、模型与参数
3.1 数据切块:用滑动窗口把任意尺寸遥感影像变成训练样本
模型输入尺寸直接影响感受野与细节保持的平衡。绿地场景分类的输入窗口我看两类偏好:512×512在卫星影像上是0.5米分辨率下的256米见方,能包含一块完整街区绿地,但显存压力大;224×224则与ImageNet预训练模型完全对齐,迁移学习时不用修改全连接层输入。我的建议是:训练用224×224起步,精度瓶颈时再把输入升到384或512,配合EfficientNet的AutoAugment策略,性价比远高于直接换网络结构。
下面这个脚本把一张高分辨率遥感影像按步长滑窗切成训练块,并保存窗口坐标:
import tifffile import numpy as np from pathlib import Path def sliding_window_crop(image_path, window_size=224, stride=112): img = tifffile.imread(image_path) # (H, W, C),支持多光谱 h, w = img.shape[:2] crops = [] coords = [] for y in range(0, h - window_size + 1, stride): for x in range(0, w - window_size + 1, stride): cropped = img[y:y+window_size, x:x+window_size] crops.append(cropped) coords.append((x, y)) # 保证覆盖右下角边界区域 if h - window_size > y or w - window_size > x: crops.append(img[h-window_size:h, w-window_size:w]) coords.append((w-window_size, h-window_size)) return np.stack(crops), np.array(coords) # 步长 stride 建议设为 window_size * 0.5,形成重叠区域 # 重叠区域在推理阶段用于概率平均,能有效压制图块边缘伪影这里的关键参数是stride。当stride = window_size时不重叠,相邻图块的预测概率容易出现接缝;当stride = 0.5 * window_size时每个像素至多被四个图块覆盖,推理时对概率图做逐像素平均即可消除大部分边界效应。训练阶段则相反,较大的stride能减少样本冗余,避免模型反复看到同一块地物导致过拟合。
3.2 模型初始化与迁移学习:ImageNet权重为什么能平移过来
绿地场景分类的样本量通常只有几千到几万张,远不足以从零训练一个深层CNN。最可靠的做法是加载在ImageNet上预训练的权值,再把最后一层全连接替换成自己的分类头。
import torch import torch.nn as nn from torchvision import models def build_model(num_classes=3, model_name='resnet50', pretrained=True): if model_name == 'resnet50': model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2) # IMAGENET1K_V2 的权重在 224x224 输入下精度更高 in_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, 256), nn.ReLU(inplace=True), nn.Linear(256, num_classes) ) elif model_name == 'efficientnet_b3': model = models.efficientnet_b3(weights=models.EfficientNet_B3_Weights.IMAGENET1K_V1) in_features = model.classifier[1].in_features model.classifier = nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, 256), nn.ReLU(inplace=True), nn.Linear(256, num_classes) ) return model代码里两层全连接中间夹了Dropout,这个设计不是随手加的。遥感影像块内部纹理高度重复,训练阶段全连接层很容易直接记住一些高频噪声模式,Dropout在这里重要作用是让分类头不过度依赖前一层最活跃的那几个神经元,迁移到其他区域时泛化能力更好。如果你要训多标签输出,记得把最后的nn.Linear(256, num_classes)配合nn.Sigmoid()使用,损失函数换成BCEWithLogitsLoss,而不是CrossEntropyLoss。
3.3 训练参数和图像增强:绿地场景的特点决定增强策略
遥感影像和自然图像的光谱统计差异极大,ImageNet预训练模型在RGB影像上表现正常,但如果是多光谱数据(含近红外波段),需要把近红外通道复制或替换到RGB通道中。绿地信息提取通常近红外波段很重要——归一化植被指数NDVI就是靠红波段和近红外的比值计算出来的。把NDVI作为额外通道和RGB拼在一起输进模型,比单一RGB的效果通常提升3到5个百分点。
图像增强策略上,水平翻转、垂直翻转、随机旋转90度对遥感影像都是语义保持变换,农田、林地旋转后类别不变。但要注意不能随意使用色彩抖动,因为绿地类别对颜色本身高度敏感,过强的饱和度或亮度扰动会把训练样本的绿色分布拉偏。一个推荐的试验配置:
| 参数 | 推荐值 | 理由 |
|---|---|---|
| 输入尺寸 | 224×224(可升至384) | 对齐ImageNet预训练权重 |
| batch_size | 32(224)/16(384) | 兼顾GPU显存与BN稳定性 |
| 优化器 | AdamW, lr=1e-4 | 权重衰减设为1e-4防过拟合 |
| 学习率策略 | Cosine Annealing + warmup 5 epochs | warmup期避开预训练权重剧烈震荡 |
| 损失函数 | 多标签用BCEWithLogitsLoss | 每类独立判定,契合绿地混合场景 |
| 训练轮数 | 30~50 | 遥感数据量小,太长必过拟合 |
训练阶段用torchvision.transforms里的RandomHorizontalFlip和RandomRotation就够,不用上复杂的自动增强策略,因为遥感地物的语义对旋转和翻转天然具备不变性,这是与ImageNet分类任务一个重要的不同点。
3.4 评估不能只看准确率:绿地提取更关心召回率和Kappa
绿地提取的最终用户(园林部门、土地规划)在乎的是「我的绿地有没有被漏掉」,这意味着召回率比精确率更关键。一个能把95%的绿地找出来、但附带10%误判的模型,在实际业务中远好过一个精确率98%、召回率只有70%的模型——后者会把大片沿街绿化带整体漏掉,面积统计严重偏低。
评估脚本里至少输出每类别的Precision、Recall、F1和总体Kappa系数,不能只打印一个总体准确率。
import numpy as np from sklearn.metrics import confusion_matrix, cohen_kappa_score def evaluate_segmentation(prob, y_true, thresholds=0.5): # prob: (N, H, W),y_true: (N, H, W),其中正类=1表示绿地 y_pred = (prob >= thresholds).astype(np.uint8) # 统计时把前景和背景 reshape 成向量 gt = y_true.flatten() pred = y_pred.flatten() tn, fp, fn, tp = confusion_matrix(gt, pred).ravel() recall = tp / max(tp + fn, 1) precision = tp / max(tp + fp, 1) f1 = 2 * precision * recall / max(precision + recall, 1) kappa = cohen_kappa_score(gt, pred) return {'recall': recall, 'precision': precision, 'f1': f1, 'kappa': kappa} # 调阈值时看 Precision-Recall 曲线的拐点,而不是默认 0.5 # 场景分类输出的概率往往是偏或欠的,绿地类别概率普遍偏高, # 阈值设 0.55 或 0.6 通常能减少小面积误检,但要注意城市阴影误判参数说明里最值得关注的是thresholds。模型输出的绿地概率如果普遍在0.6以上且非绿地概率在0.2以下,调阈值到0.6能让精度上升但不明显伤召回;如果两类概率分布有重叠,说明场景窗口尺寸过小,模型还没看到足够的上下文,优先调大窗口而不是调阈值。
4. 遥感影像落地的四个真实坑:分辨率、多尺度、类别失衡与后处理
4.1 不同空间分辨率下的绿地语义漂移
同一块城市公园,在2米分辨率的卫星影像上是一个绿色团块,在0.05米分辨率的无人机影像上则能看到树冠间隙中的地表土。场景分类模型学到的「绿地特征」和影像分辨率强相关,跨分辨率推理时精度会显著下滑。一个典型的失败案例:用0.5米WorldView影像训练的模型,直接拿到1米分辨率多光谱数据上跑,绿地误检率从5%涨到22%。
解决思路不是换模型,而是做分辨率重采样对齐。在推理前把所有影像统一重采样到训练集的中位数分辨率,通常用双线性插值;如果目标分辨率明显低于训练集,建议增加一个下采样模糊预处理的测试分支,用多尺度滑窗把同场景的分类结果做平均,比单一尺度鲁棒得多。
4.2 多尺度问题:同一棵大树在不同窗口尺寸下类别不同
一棵大冠幅的雄树,在224×224窗口里可能占据整个画面,被模型识别为「林地」;同一棵树放进512×512窗口,周围的道路和建筑信息进入视野,模型可能改判为「城市绿带」。如果你用的分类体系里绿地是二分类(有/无),这个问题不致命;但如果你把绿地细分为林地、草地、绿带,多尺度冲突就非常明显。
处理方案是训练阶段采用随机多尺度裁剪,并在推理阶段做尺度投票。
from torchvision import transforms class MultiScaleCrop: def __init__(self, base_size=224, scales=[0.7, 1.0, 1.3]): self.base_size = base_size self.scales = scales def __call__(self, img): scale = np.random.choice(self.scales) new_size = int(self.base_size * scale) resize = transforms.Resize((new_size, new_size)) img = resize(img) # 随机裁剪回 base_size,模拟不同距离下同一地物的观测 crop = transforms.RandomCrop(self.base_size) return crop(img) # 推理时的尺度投票:同一影像块缩放成多个尺度分别预测 # 把三个尺度的绿地概率取均值,边界比任何单一尺度更柔和也更稳参数说明:scales的取值范围决定了模型对地物尺度变化的承受能力。遥感影像应用中0.7倍到1.3倍是安全区间,过大缩放会让道路、建筑等线性地物严重变形,反而引入伪影。训练阶段每个epoch随机选一个尺度,推理阶段固定三个尺度投票,这个小改动在跨地块测试中通常能贡献3%以上的Kappa增益。
4.3 类别不平衡:绿地占影像80%以上时模型学到的是「无脑输出绿地」
绿地占比高的影像(比如城郊、自然保护区)存在严重的背景类别占比失衡。假设一张影像里85%是绿地,负样本只有15%,模型只需要把所有像素都预测成绿地,准确率就有85%。这对场景分类和基于场景概率图的做法是致命的——模型会学出一个高偏置的分类头,把阴影、柏油路全都当作绿地。
最直接的解决办法是样本层面的欠采样加权。训练时给非绿地类别更高的采样权重。
class WeightedRandomSampler(torch.utils.data.sampler.Sampler): def __init__(self, labels, weights, num_samples=None): self.labels = np.array(labels) self.weights = weights.copy() self.num_samples = num_samples or len(labels) def __iter__(self): idx = np.random.choice(len(self.labels), self.num_samples, replace=True, p=self.weights) return iter(idx.tolist()) def __len__(self): return self.num_samples # 假设 labels 里 80% 是 0(绿地),20% 是 1(非绿地) # 把 weights 设为: 绿地 0.5,非绿地 2.0,相当于重采样负样本权重怎么设才不至于矫枉过正?我习惯先统计数据集的类别比例,令w_i = 1 / sqrt(p_i),其中p_i是第i类的频率。这个公式来自LDA的Fisher准则思路,平方根处理能避免极端类别获得过大权重导致在少量样本上过拟合。如果训练损失震荡明显,把权重再向1靠拢一些。
4.4 后处理:用连通域分析和形态学操作滤除椒盐噪声
场景分类滑窗推理得到的绿地概率图在直接阈值化之后通常会有两类噪声:小面积孤立点(单棵树、阴影被误判)和大块边缘的毛刺。这些小图斑对面积统计的干扰远大于对目视效果的干扰,一个极端的例子是,一块0.5厘米见方的地物在0.1米分辨率下可能有20多个像素,足够被单独标记成一个「绿地斑块」。
处理流程如下:
阈值化绿地概率图 -> 用 OpenCV 做开运算(先腐蚀后膨胀)-> connectedComponentsWithStats 过滤面积小于阈值的小斑块 -> 最终二值图import cv2 import numpy as np def refine_green_probability(prob_map, thr=0.5, min_area_pixels=50): binary = (prob_map >= thr).astype(np.uint8) # 开运算核用 3x3,过大核会吞掉细长绿带 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(cleaned, connectivity=8) for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] < min_area_pixels: cleaned[labels == i] = 0 return cleanedmin_area_pixels的设置原则取决于输出图斑的最小制图单元。国家森林资源调查的惯例是0.067公顷(对应1亩),如果你处理的是0.5米分辨率影像,这个面积约等于268个像素;如果是0.1米无人机影像,则是6700个像素。直接固定50个像素显然不合理,应该按栅格分辨率计算:min_area_pixels = min_area_m2 / (resolution_m * resolution_m)。
5. 从绿地概率图到矢量图斑:用GDAL矢量化并统计面积
5.1 用GDAL的Polygonize把二值栅格转成Shapefile
模型输出的地表真实坐标信息保存在栅格文件里,绿地提取的最终交付物通常是带边界的矢量面文件。用GDAL自带工具就可以完成,不需要额外安装复杂的GIS库。
# 假设已经得到绿地二值栅格 green_mask.tif # 背景值设为0,绿地值为1,四邻域连通 gdal_polygonize.py green_mask.tif -f "ESRI Shapefile" green_polygons.shp # 如果栅格较大,加一个 -mask 选项只矢量化值为1的区域 gdal_polygonize.py -mask green_mask.tif green_mask.tif -f "ESRI Shapefile" green_polygons.shp参数说明:-mask的意义是避免每一个背景像素都被矢量化成一个矩形,否则输出Shapefile会异常臃肿。另外GDAL默认使用四邻域连通(上下左右),如果绿地中存在被道路分割的细长连接带,建议先对栅格做一次膨胀再矢量化,否则细窄走廊会被切断成多个面。
5.2 面积统计:用PostGIS或者QGIS表达式,避开投影坐标面积失真的坑
矢量化的绿地斑块要算面积时,最常见的一个坑是直接用Web墨卡托投影坐标系的EPSG:4326来计算。Web墨卡托在高纬度地区严重拉伸面积,例如在北纬60度附近,计算出的面积比真实面积放大约4倍。绿地信息提取必须把矢量转换为合适的投影坐标系后再统计。
-- 在 PostGIS 中先把几何字段转换为平方米制投影(示例取 UTM Zone 50N) SELECT gid, ST_Area(ST_Transform(geom, 32650)) AS area_m2, ST_Transform(geom, 32650) AS geom_utm FROM green_polygons WHERE ST_Area(ST_Transform(geom, 32650)) > 0;UTM投影带的选择以影像中心经度为准,中国从东经72度到135度跨6个带(UTM Zone 43N到53N),用错了带会产生轻微的面积偏差。如果不确定用哪个带,可以用ST_Transform到以影像中心点定义的正交投影(ST_SetSRID几个函数组合),但那样SQL写起来很长,实际工程里直接按中心经度选带最省事。
在QGIS里也可以用表达式方便地完成同样的事:
area($geometry) -- 先确保图层已经重新投影5.3 从绿地概率图快速估算总绿地率的三个技巧
最后收一个具体技巧:如果只是要一个「总绿地率」数字,完全不必每次都转矢量。直接把二值栅格导入Python做像素统计:
import numpy as np import tifffile def green_ratio_from_mask(mask_path, resolution): mask = tifffile.imread(mask_path) # mask 的像素值为0或1 green_pixels = np.sum(mask == 1) total_pixels = mask.shape[0] * mask.shape[1] green_area_m2 = green_pixels * resolution * resolution total_area_m2 = total_pixels * resolution * resolution return green_area_m2 / total_area_m2 # 多光谱影像里如果绿地二值图存在 NoData,记得先通过 ReadAsArray 把 NoData 值 # 替换为0,再进行统计,否则 NoData 会被当成非绿地计入总量技巧二:在输出概率图时,把滑窗重叠区域的平均次数顺便生成一份「覆盖次数栅格」,绿地的面积置信度就可以用「概率值 * 覆盖次数 / 最大覆盖次数」来衡量。覆盖次数低的位置往往是影像边缘或滑窗未覆盖区,统计面积时应当另作说明。
技巧三:场景分类网络输出的绿地概率热力图本身就能作为「绿地适宜度」分析底图,不需要转二值。如果甲方要求量化不确定性,直接拿每个像素的概率方差当置信度输出,比二值图更能说明模型的边界判断置信程度——这也是CNN场景分类比传统NDVI阈值法更灵活的一点,它在特征空间里做概率估计,而不是简单地在波段比值上拉一刀。
本文还有配套的精品资源,点击获取