PaddleSeg遥感影像分割实战:从训练到ONNX跨平台部署
1. 项目整体思路与方案选型1.1 遥感分割场景下PaddleSeg能解决什么从遥感影像里自动提取道路、建筑物、水体、农田核心是像素级语义分割。PaddleSeg是一套语义分割工具里面集成了主流分割网络、损失函数、数据增强、评估工具和模型导出能力正好覆盖了“训练→验证→部署”整条链路。要做项目时我一般先问三个问题影像多大、标注长什么样、最后跑在什么设备上。这三个问题决定了后面所有选型。PaddleSeg并不是唯一选择但它在“工程落地”这个层面做得相当顺手。配置文件驱动训、评、导一条命令新的数据集只要按照约定准备好图片和标签写一个YAML就能开跑。我之前用其他框架光是数据加载、loss加权、评估脚本这些就要写一大片在PaddleSeg里基本不用重复造轮子。另一个重要原因是它导ONNX相对顺滑而ONNX又是目前部署链路里最常见的中间格式很多推理引擎、边缘设备都能吃。适合谁来参考如果你手上有标注好的遥感影像想快速出一个分割模型并且后面要跨平台部署那这篇文章就是按这条路线写的。如果你只是想入门语义分割也可以从第一部分开始看把数据集准备和训练参数理解透对以后切换到其他框架也有帮助。1.2 模型选型时要考虑的不只是精度遥感影像分割最典型的特征是大尺寸、小目标、类别不均衡。一张影像动不动就是几万乘几万像素道路或房屋占整张图的比例可能很小而且边界要求很精细。针对这些特点模型选型就不单纯是“谁精度高选谁”。我的常规做法是先按资源情况分几档。显存充足、追求高精度优先考虑DeepLabV3或者带ASPP结构的模型这类模型对多尺度目标很友好想训练快、部署轻量可以上BiSeNetV2或者STDC这类实时模型如果数据量不大、希望稳定复现U-Net这类结构简单、容易调参的模型反而更省心。遥感任务里道路、建筑这种细长或块状目标比较多HRNet这种保持高分辨率的结构也能出效果不过显存开销确实大尤其是大patch训练时容易OOM。我建议第一次跑项目不要一上来就挑战超大模型。先用一个中等规模的配置把数据、loss、评估流程全部跑通确定问题出在哪再针对性地换更强的backbone或加解码模块。否则训练到一半发现是标签图读取错了那前面的显存和时间都浪费了。1.3 为什么最终要转到ONNX部署PaddlePaddle训练出的模型原生推理通常还是要在Paddle环境里跑但实际项目里不一定都用同一套环境有的服务端用ONNX Runtime有的硬件平台用TensorRT、OpenVINO甚至还要跑到边缘盒子。ONNX相当于一个模型交换格式把训练好的权重和网络结构统一存成一套再由目标推理引擎加载运行。这里有一个经验不要到最后才想起来转ONNX。在做模型选型时就要考虑算子和预处理兼容性比如一些自定义算子在转换时会变成子图性能可能打折有些模型结构在Paddle里能跑转成ONNX后某个算子不支持又得改结构重新训练。提前把转换链路在当前模型上试一遍能省很多后期返工。2. 遥感数据集准备从标注到训练集2.1 影像和标注不是天生的“RGB对应PNG”遥感数据源五花八门。常见的是TIF格式的高分辨率影像可能包含RGB、近红外等多个波段标注可能是矢量文件也可能是已经栅格化好的PNG。最直接的坑是你看到的标签图是彩色PNG里面“道路是白色、建筑是红色”但神经网络要的不是颜色而是类别索引。如果不做颜色映射直接把彩色标签当训练目标loss会一直乱跳模型怎么也学不好。先把所有训练数据统一到一个目录再逐步处理。第一步确认坐标系和像素对齐简单做法是把影像和标注都做重投影再按相同地理范围裁剪如果只是实验至少保证图像和标签的像素一一对应。第二步把矢量标注栅格化成和影像完全对齐的单通道标签图每个像素值对应类别ID背景0、道路1、建筑2之类。第三步统一影像格式和尺寸通常裁剪成512×512或者1024×1024的小patch。裁剪并不是简单的等距切块我习惯带一点重叠。因为如果patch边界正好切到一栋房子两边都只有半个目标训练时模型很容易学到“残缺目标”推理拼接时也会在裂缝处出现断裂。重叠比例一般取10%到20%既不会明显增加数据量又能把边缘目标完整地保留下来。2.2 多光谱、大尺寸和类别不均的处理遥感影像如果只有RGB处理起来最简单直接按三通道读。但如果源文件包含近红外、甚至十几个波段就要想清楚输入通道怎么设计。PaddleSeg内置的预训练backbone通常按三通道输入设计贸然改成5通道会导致预训练权重完全失效训练成本飙升。实际项目里一种做法是额外波段通过人工特征或指数计算后叠加到RGB上去比如归一化差分水体指数可以帮助水体分割另一种做法是只用RGB通道训练先把主流程打通后面再单独优化多光谱输入。类别不均是遥感分割最普遍的问题。道路和房屋在整幅影像里占比可能就5%如果直接用普通交叉熵模型倾向于把所有像素都预测成背景mIoU看起来不错但目标一类几乎没用。PaddleSeg提供了Focal Loss、Dice Loss、Lovasz Hinge Loss等对这类问题很有帮助。我的习惯是把交叉熵和Dice损失按0.7:0.3混合或者直接上Focal Loss具体幅度靠验证集表现调。处理大尺寸影像时还要考虑内存和IO。直接把全图放进GPU是不现实的所以我通常先做一个“影像库”把大影像预先裁成patch并缓存成numpy或者PNG训练时从patch采样而不是每次都去大图上切。这样训练循环快很多也能避免反复读取TIF导致的IO瓶颈。2.3 数据增强要做但要克制遥感分割本身比较“吃数据”如果没有足够多样本适当增强可以有效防止过拟合。常规的水平翻转、垂直翻转、90度旋转、随机缩放、随机裁剪我都在用颜色抖动、亮度对比度调整对遥感影像也有帮助因为不同时期、不同光照下影像颜色差异比较大。但增强不是越多越好。过强的仿射变换会让道路这种细长目标发生严重变形模型可能学到错误的形状先验。对地理信息明显的任务如果训练集和验证集来自不同区域我反而更建议减少随机缩放比例保持接近推理时的尺度范围避免模型对“目标大小”产生错误依赖。数据增强最忌讳的是改变标签语义比如某些任务里水平翻转会使“左车道”变成“右车道”遥感分割这种纯像素标注没有方向语义翻转通常安全但还是要看具体标签含义。3. PaddleSeg训练流程与参数调优3.1 环境安装和目录组织PaddleSeg训练前先把环境装好。我一般用conda建独立环境避免和已有项目冲突。PaddlePaddle的GPU版本需要和CUDA、cuDNN匹配装之前先确认显卡驱动和CUDA runtime版本装错了会报一堆libcudart相关的错误。装好PaddlePaddle之后再安装PaddleSeg同时把Pillow、opencv-python、numpy这些基础库确认到位。训练数据组织是PaddleSeg约定好的data/ images/ 00001.png 00002.png annotations/ 00001.png 00002.png train.txt val.txttrain.txt和val.txt里每行是一对路径常见写法是“images/00001.png annotations/00001.png”。路径分隔符建议统一用正斜杠别混用反斜杠Windows上特别容易出问题。还有一个容易忽略的细节标签图的像素值必须是从0开始的连续整数如果某个类别ID是3但样本里根本没有评估和预测时可能会出边界问题。3.2 配置文件里的关键参数怎么定PaddleSeg使用YAML配置核心需要修改的部分是数据集路径、模型结构、损失函数、训练参数。我用一个中等数据集举例影像patch是512×512类别有6类batch_size设8训练迭代数设40000。学习率我一般用0.01配合poly衰减poly衰减方式在分割任务里表现稳定能让训练后期慢慢收敛。如果batch_size明显偏大学习率也需要相应调整这是最基本的线性缩放经验。下面是一段极简的关键配置示例实际使用时还要按自己的模块路径调整model: type: DeepLabV3P backbone: type: ResNet50_vd pretrained: https://example.com/resnet50_vd.pdparams num_classes: 6 train_dataset: type: Dataset dataset_root: data train_path: data/train.txt num_classes: 6 transforms: - type: ResizeStepScaling scale_step_size: 0.1 - type: RandomHorizontalFlip - type: RandomVerticalFlip - type: RandomPaddingCrop crop_size: [512, 512] mode: train optimizer: type: SGD momentum: 0.9 loss: types: - type: CrossEntropyLoss coef: [1.0]这段配置只是最小可跑版本。实际调参时我会再增加验证集评估、模型保存间隔和可视化输出。如果发现loss曲线不下降第一件事不是调学习率而是回到数据检查标签是否有缺失、是否读取成彩色图、类别ID是否连续。这类问题我至少踩过两三次每次都是“看起来数据没问题”一查全是ID映射错误。3.3 监控训练过程和验证指标PaddleSeg训练时会打印loss和步数信息我习惯同时开VisualDL或TensorBoard看曲线。真正重要的验证指标不只是mIoU遥感项目里还要看类别IoU、PA和卡帕系数。尤其是道路这类小目标总体像素精度可能很高但道路IoU很低模型实际上等于没学到东西。按类别分开看IoU才能定位到底哪一类是短板。训练中典型的“健康曲线”是前一两千步loss快速下降然后进入长尾缓慢下降验证mIoU稳步上升。如果loss下降很快但mIoU几乎不动通常是数据出现严重类别不平衡模型只学了背景如果loss在训练集上很低、验证集上高就要考虑增强强度、模型规模或加正则。大patch训练时GPU显存比较紧张可以试试混合精度或者把输入patch从1024降到768看看精度损失能不能接受。遥感任务里patch太大会让batch_size很小反而影响BN统计和收敛稳定性所以我更倾向于用512或768打底而不是一味追求“看得全”。4. 模型导出ONNX与跨平台部署实践4.1 从PaddleSeg导出静态推理模型PaddleSeg本身是基于动态图训练的部署时首先要导出静态图模型。导出命令大致是这样python tools/export.py \ --config configs/deeplabv3p_resnet50_remote.yaml \ --model_path output/best_model/model.pdparams \ --save_dir export/deploy_model导出的目录里会看到model.pdmodel和model.pdiparams这两个文件组合起来就是PaddleInference可以用的静态图模型。注意导出时配置里要有test_dataset或评估transformsPaddleSeg会根据配置里的数据预处理信息把模型输入格式固定下来。如果这里配置的resize尺寸和最后部署时不一致模型输出的张量尺寸也会不一致后面用起来很别扭。导出后先用PaddleInference跑一遍确认结果和动态图预测一致再进入ONNX转换。不要跳步先验证Paddle侧的输出再验证ONNX侧的输出出问题时能直接确定是导出环节还是转换环节的问题。4.2 用Paddle2ONNX转换并校验输出PaddleSeg里有配套的Paddle2ONNX工具基本命令是paddle2onnx \ --model_dir export/deploy_model \ --model_filename model.pdmodel \ --params_filename model.pdiparams \ --save_file deploy_model.onnx \ --opset_version 11 \ --enable_onnx_checker Trueopset_version的选择需要考虑推理端。ONNX Runtime对高版本opset支持通常很好但TensorRT和部分老版本OpenVINO可能只支持低版本。稳妥起见我一般先用opset 11如果转换报不支持再往上加了看。另一个关键是输入动态shape。Paddle模型导出时如果没固定shape转出的ONNX可能带着动态batch和动态H/W这对某些后端不友好。如果部署场景明确固定输入大小建议在导出或转换时把输入shape确定成[b,3,512,512]TensorRT和VINO都能省去动态shape的麻烦。转换完成后必须做一次数值校验。我用一个简单脚本同时加载Paddle模型和ONNX模型输入同一组随机或真实数据比较输出张量的最大绝对误差。如果误差在1e-4左右基本没问题如果出现几个像素类别跳变就要怀疑预处理差异、resize插值方式或者align_corners设置不一致了。4.3 ONNX优化和跨平台推理ONNX导出后不是终点通常还要做一层优化。onnx-simplifier能自动进行常量折叠、算子融合去掉很多冗余节点onnxoptimizer也有一些针对性的图优化。我的流程是先简化再用ONNX Runtime做精度校验确认无误后再交给目标推理引擎。不同硬件平台的部署路径差别挺大。在NVIDIA GPU上我会用ONNX转TensorRTFloat16精度是首选INT8量化确实能进一步提速度但遥感小目标比较敏感需要仔细调校准集和量化阈值。在Intel CPU上OpenVINO是最省心的选择先把ONNX转成IR格式再用推理API加载如果只是普通的服务端CPU推理直接用ONNX Runtime CPU也很稳。推理阶段还有一个容易被忽略的点预处理一致性。PaddleSeg训练时的transforms通常包含归一化mean和std是固定的部署代码里也要用完全相同的值包括通道顺序RGB还是BGR。我遇到过好几次“模型在训练环境里好好的部署到服务上效果变差”最后排查下来就是部署端用了不同的mean/std或是图片被框架自动转了颜色格式。4.4 大影像推理滑动窗口和结果拼接遥感影像部署不能像普通图片那样直接整张推理因为尺寸太大。我的做法是滑动窗口切割每个窗口重叠一部分推理后只保留重叠区域的中间部分再把所有窗口结果拼回整幅图。这样做可以避免窗口边界处目标被切断、拼接处出现伪影。重叠比例一般取20%左右推理时间会多一点但边缘连续性明显更好。还有一个细节全图输入ONNX时如果模型输入固定为512×512但实际切块是640×640不要直接resize到512然后再拼要按训练时一致的resize方式或者干脆把切块大小直接设成和模型输入一样省掉二次resize带来的对齐问题。5. 常见问题与排查技巧实录5.1 训练阶段高频问题速查遥感数据训练PaddleSeg最容易出问题的点其实不在模型而在数据读取。我把常见现象和排查方向整理成一张表现象可能原因排查和处理loss初始很高且不下降标签是彩色图而非类别索引检查标注像素值做RGB到ID的映射mIoU一直很低但loss不高类别严重不均衡换Dice/Focal Loss或对小类加权训练OOM输入patch过大/batch过大减小crop size、batch size或开混合精度验证集精度和训练集差异大增强过强或样本分布不一致降低随机缩放比例检查不同区域影像亮度图像和标签错位文件名排序不一致严格按txt中路径配对不要依赖文件顺序这些坑都来自真实经历。比如“loss不下降”那次我以为学习率问题调了半天最后发现标注图被存成了三通道彩色PNG网络输出类别数是6标签里却是RGB三分量loss当然不会正常收敛。后来我写了一个数据集检查脚本训练前先打印每张标签的unique values确认像素值都在[0, num_classes-1]范围内再开训练。5.2 ONNX转换失败和精度下降怎么办ONNX转换常见的报错是“Unsupported operator”。PaddleSeg内置的大多数常规结构Paddle2ONNX都能处理但如果用了很新的注意力模块或自定义op就可能转不过去。这时先别急着改模型试试三个低成本的方案升级Paddle2ONNX版本、换一个更高的opset、用onnx-simplifier转一下再检查。如果还不行再看具体是哪一层一般可以通过替换成等价的标准算子解决。转ONNX后精度下降优先检查三件事。第一预处理里的归一化参数是否完整带入部署代码第二模型输入尺寸和训练时是否一致resize插值方式是否匹配第三通道顺序是否一致PaddleSeg里很多模型默认BGR输入而一般图像库读的是RGB不转换就部署会出现严重的颜色错乱。还有一个容易栽的坑是align_corners。PaddleSeg里的上采样层往往带align_corners设置ONNX Runtime、TensorRT对不同插值参数的支持不完全一致。如果输出图整体尺寸对但边缘位置有轻微偏移不妨检查一下模型导出配置里上采样参数一般把align_corners设为False或True固定下来和训练时保持一致即可。5.3 遥感部署性能调优心得如果推理速度不达标第一优先级不是买更贵的卡而是先看有没有做重复的前处理。很多部署代码为了方便把resize、通道转换、归一化都写在Python循环里单帧处理时间甚至超过模型推理时间。正确思路是把预处理写成批量操作或者在支持的地方用TensorRT或OpenVINO内置的预处理插件减少CPU和GPU之间的数据拷贝。批处理对吞吐量提升非常明显。虽然部署时经常是单张大图滑窗但如果服务端能积累多个窗口一起推理ONNX Runtime或TensorRT的batch推理效率比逐个单帧高很多。不过要小心显存占用batch size设置成4或8后做一次压力测试别因为一次显存峰值OOM把整个服务拖垮。我自己的体会是遥感分割项目里“训练出模型”往往只占30%工作量剩下70%都在数据对齐、转换校验和部署适配。刚开始接触时我很不理解为什么“模型都出来了”还要折腾这么久踩过几轮坑之后才发现这些看似琐碎的事情才是工程落地真正的关键。上面这套流程不一定是最优解但至少能帮你避开我走过的弯路先跑通一条最小可用的链路再逐步优化细节。