MMSegmentation 中的 PointRend:基于点渲染的高效语义分割实现与配置实战指南
【免费下载链接】mmsegmentationOpenMMLab Semantic Segmentation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmsegmentation
PointRend(Point-based Rendering)把图像分割类比为计算机图形学中的渲染过程,只在"最难判断"的边界点位置进行精细预测,从而在几乎不增加计算量的前提下显著提升分割边界的锐利度。本文以 MMSegmentation 仓库中 configs/point_rend 目录下的官方实现为主线,深入讲解 PointRend 的核心算法原理、PointHead源码实现、完整配置文件解读、训练与测试方法,并给出 Cityscapes 与 ADE20K 上的官方基准结果。读完本文,你将能够读懂并复现 PointRend 语义分割模型,并能在此基础上针对自己的数据集调整参数。
PointRend 算法核心:把分割当成"渲染"
PointRend 论文(《PointRend: Image Segmentation as Rendering》,CVPR 2020)的核心洞察是:语义分割中的逐像素分类,与计算机图形学中的渲染存在相似的"过采样/欠采样"问题——大部分平坦区域的类别是显而易见的,真正的难点集中在物体边缘这类高频区域。传统方法为了不丢失边缘细节,只能对整个高分辨率特征图做全量计算,代价高昂;PointRend 则借鉴渲染中的自适应细分(adaptive subdivision)思想:
- 只在少数关键点(point)上做精细预测,这些点由"不确定性"驱动地自适应选取;
- 粗预测(coarse prediction)负责整体结构,点级精细预测负责修正边界;
- 通过**迭代细分(iterative subdivision)**逐步逼近高分辨率输出,在保持效率的同时输出"锐利"的物体边界。
因此 PointRend 可以作为一种通用模块叠加在现有 SOTA 分割模型之上,同时适用于实例分割与语义分割任务。在 MMSegmentation 中,PointRend 的实现采用"粗分割头 + 点精化头"的级联(cascade)结构,代码实现集中在 mmseg/models/decode_heads/point_head.py 与 mmseg/models/segmentors/cascade_encoder_decoder.py。
整体架构:CascadeEncoderDecoder 与双解码头设计
PointRend 在 MMSegmentation 中的整体模型结构由基础模型配置 configs/base/models/pointrend_r50.py 定义,属于CascadeEncoderDecoder类型,配置了num_stages=2,即两级解码头级联:
model = dict( type='CascadeEncoderDecoder', num_stages=2, pretrained='open-mmlab://resnet50_v1c', backbone=dict(type='ResNetV1c', depth=50, ...), neck=dict(type='FPN', in_channels=[256, 512, 1024, 2048], out_channels=256, num_outs=4), decode_head=[ dict(type='FPNHead', ...), # 第一级:粗分割头 dict(type='PointHead', ...) # 第二级:点精化头 ], train_cfg=dict(num_points=2048, oversample_ratio=3, importance_sample_ratio=0.75), test_cfg=dict(mode='whole', subdivision_steps=2, subdivision_num_points=8196, scale_factor=2))各组件职责如下:
| 组件 | 类型 | 作用 |
|---|---|---|
| backbone | ResNetV1c(R-50/R-101) | 提取多尺度特征,输出 4 个 stage 的特征图 |
| neck | FPN | 将 4 个尺度特征统一到 256 通道,供两个解码头共享 |
| decode_head[0] | FPNHead | 粗分割头,先输出一个全分辨率(或低分辨率)的粗 logits |
| decode_head[1] | PointHead | 点精化头,基于粗预测在不确定点处做精细化分类 |
从 cascade_encoder_decoder.py 的源码可以看到,CascadeEncoderDecoder要求decode_head必须是一个长度等于num_stages的 list,并在初始化时逐个构建,最终以最后一级头的align_corners、num_classes作为整个模型的统一属性。
前向与训练流程
- 推理前向(
encode_decode,cascade_encoder_decoder.py):head[0].forward(x)产出粗 logits,随后第 1 级到倒数第 2 级逐个forward(x, out)传递,最后由最后一级predict(x, out, img_metas, test_cfg)输出精化结果。 - 训练流程(
_decode_head_forward_train,cascade_encoder_decoder.py):每一级解码头都计算自己的损失(decode_0、decode_1前缀),粗头提供全图监督,点头的损失只在被采样到的点位置计算。
PointHead 源码解析:不确定性、点采样与迭代细分
PointHead继承自BaseCascadeDecodeHead(cascade_decode_head.py),其关键设计在 mmseg/models/decode_heads/point_head.py 中实现,包含四个核心环节。
1. 不确定性度量:计算每个位置有多"难"
def calculate_uncertainty(seg_logits): top2_scores = torch.topk(seg_logits, k=2, dim=1)[0] return (top2_scores[:, 1] - top2_scores[:, 0]).unsqueeze(1)不确定性被定义为"最高类得分与第二高类得分之差"(point_head.py)。差值越小,说明该位置在多个类别之间摇摆不定,越需要精细预测——这正好对应物体边缘区域。
2. 训练时点采样:过采样 + 重要性采样
训练时点选择实现在get_points_train(point_head.py),受train_cfg三个参数控制:
num_points=2048:每张图最终用于计算损失的点数;oversample_ratio=3:先随机采样num_points * oversample_ratio个候选点,从中选出不确定性最高的部分;importance_sample_ratio=0.75:num_points中 75% 来自"不确定性最高"的点,剩余 25% 为均匀随机点,保证边界点与背景点都有覆盖。
源码中特别强调(见 point_head.py 的注释):必须先对候选点处的 logits 采样,再计算这些采样点的不确定性;如果在粗预测图上先算不确定性再采样,会得到错误的结果。
训练时loss流程(point_head.py)为:在torch.no_grad()下采样点 → 用point_sample在细粒度特征与粗 logits 上分别采样 → 拼接后经 MLP 预测点 logits → 与 GT 在对应点处的标签计算交叉熵损失,并额外记录acc_point点级精度。
3. 点级 MLP 分类器:细粒度特征 + 粗特征拼接
PointHead用共享的 1D 卷积(等价于 MLP)对采样点做分类。其forward(point_head.py)将细粒度点特征(point_sample从 FPN 特征中采样)与粗预测点特征(从上一级 FPNHead 输出中采样)沿通道拼接,依次通过num_fcs=3个全连接层,且默认coarse_pred_each_layer=True,即每一层之后都再次拼回粗特征,防止细粒度分支的信息在多层传播中被稀释,最后经fc_seg(1x1 Conv1d)输出各类别 logits。
4. 测试时迭代细分:逐步细化边界
测试阶段predict(point_head.py)执行"细分-重预测"循环,受test_cfg控制:
subdivision_steps=2:细分迭代次数;scale_factor=2:每次迭代将 logits 上采样 2 倍;subdivision_num_points=8196:每次迭代选取的最不确定点数。
流程是:将粗 logits 按 2 倍上采样 → 用get_points_test选出该分辨率下不确定性最高的min(height*width, 8196)个点 → 在这批点处用细粒度特征与原始粗特征做精细预测 → 通过scatter_把新预测值写回 logits 对应位置。经过 2 次迭代,边界区域的预测被逐步精化,而平坦区域保持粗预测结果,从而以远低于全分辨率推理的成本获得锐利边界。
配置文件逐项解读:从 Cityscapes 到 ADE20K
基础模型配置(configs/base/models/pointrend_r50.py)
- 数据预处理器:
SegDataPreProcessor使用 ImageNet 统计的均值/标准差(mean=[123.675, 116.28, 103.53],std=[58.395, 57.12, 57.375]),bgr_to_rgb=True处理 OpenCV 读图通道顺序,pad_val=0、seg_pad_val=255分别指定图像与分割标签的 padding 值。 - backbone:
ResNetV1c,输出 4 个 stage(out_indices=(0, 1, 2, 3)),contract_dilation=True,配合 FPN 使用。 - FPN neck:输入
[256, 512, 1024, 2048]四尺度特征,统一输出 256 通道的 4 层金字塔。 - FPNHead(粗头):
feature_strides=[4, 8, 16, 32]对应 FPN 各层相对于原图的下采样倍数,channels=128,dropout_ratio=-1表示不使用 dropout,num_classes随数据集变化(Cityscapes 为 19,ADE20K 为 150),损失为CrossEntropyLoss(use_sigmoid=False,即多类 softmax 交叉熵)。 - PointHead(精化头):
in_channels=[256]、in_index=[0]表示只取 FPN 第 0 层(stride=4,分辨率最高、细节最丰富)作为细粒度特征来源;num_fcs=3、coarse_pred_each_layer=True如前所述。
Cityscapes 训练配置(80k 迭代)
pointrend_r50_4xb2-80k_cityscapes-512x1024.py 继承四份基础配置:
_base_ = [ '../_base_/models/pointrend_r50.py', '../_base_/datasets/cityscapes.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' ] crop_size = (512, 1024) data_preprocessor = dict(size=crop_size)crop_size=(512, 1024):Cityscapes 图像裁剪尺寸,同步传给data_preprocessor用于训练时归一化尺寸对齐。- 学习率调度(
param_scheduler)采用两段式:前 200 次迭代用LinearLR以start_factor=0.1做 warmup,之后用PolyLR(power=0.9、eta_min=1e-4)衰减至 80000 次迭代结束,by_epoch=False表示按迭代而非 epoch 调度。 - 基础调度 schedule_80k.py 定义了 SGD 优化器(
lr=0.01, momentum=0.9, weight_decay=0.0005)、IterBasedTrainLoop(max_iters=80000, val_interval=8000)以及每 8000 次迭代保存 checkpoint 的CheckpointHook。
R-101 版本 pointrend_r101_4xb2-80k_cityscapes-512x1024.py 只通过继承改写两个字段:pretrained='open-mmlab://resnet101_v1c'与backbone=dict(depth=101),体现了 MMSegmentation 配置继承体系的简洁性。
ADE20K 训练配置(160k 迭代)
pointrend_r50_4xb4-160k_ade20k-512x512.py 针对 ADE20K 做了三处关键调整:
- 数据集与调度替换为 ade20k.py 与 schedule_160k.py,
crop_size=(512, 512); num_classes从 19 改为 150(ADE20K 类别数),两个解码头都要同步修改;PolyLR的end从 80000 改为 160000。
训练、测试与推理命令
在安装好 MMSegmentation 及其依赖(requirements/runtime.txt)之后,可直接使用仓库自带的训练/测试脚本:
# 单卡训练 Cityscapes 80k 迭代 python tools/train.py configs/point_rend/pointrend_r50_4xb2-80k_cityscapes-512x1024.py # 多卡分布式训练(4 GPU) bash tools/dist_train.sh configs/point_rend/pointrend_r50_4xb2-80k_cityscapes-512x1024.py 4 # 测试并输出 mIoU python tools/test.py configs/point_rend/pointrend_r50_4xb2-80k_cityscapes-512x1024.py /path/to/checkpoint.pth # 单张图片推理 python demo/image_demo.py demo/demo.png \ configs/point_rend/pointrend_r50_4xb2-80k_cityscapes-512x1024.py \ /path/to/checkpoint.pth注意:PointHead依赖 mmcv 的point_sample算子,若缺失会在初始化时抛出 "Please install mmcv-full for point_sample ops" 的运行时错误(见 point_head.py),因此请确保安装了包含该算子的 mmcv 版本。
模型的完整元信息(权重下载地址、训练日志、批量大小、训练资源等)可在 configs/point_rend/metafile.yaml 中查到,包括每个模型对应的 Weights 与 Training log 链接;tests/test_config.py中的test_config_build_segmentor会自动遍历各配置目录并验证build_segmentor能否成功构建模型,可用于校验改动后的配置。
官方基准结果
以下结果来自仓库 configs/point_rend/README.md 与 configs/point_rend/metafile.yaml,均以 4 张 V100 GPU 训练得到。其中 mIoU(ms+flip) 表示多尺度(multi-scale)推理并叠加水平翻转(flip)后的指标。
Cityscapes(19 类)
| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config |
|---|---|---|---|---|---|---|---|---|---|
| PointRend | R-50 | 512x1024 | 80000 | 3.1 | 8.48 | V100 | 76.47 | 78.13 | config |
| PointRend | R-101 | 512x1024 | 80000 | 4.2 | 7.00 | V100 | 78.30 | 79.97 | config |
ADE20K(150 类)
| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config |
|---|---|---|---|---|---|---|---|---|---|
| PointRend | R-50 | 512x512 | 160000 | 5.1 | 17.31 | V100 | 37.64 | 39.17 | config |
| PointRend | R-101 | 512x512 | 160000 | 6.1 | 15.50 | V100 | 40.02 | 41.60 | config |
从表中可以看到,R-101 相比 R-50 在两个数据集上均带来稳定的 mIoU 提升(Cityscapes +1.83、ADE20K +2.38),而显存与推理时间的代价也同步增加。值得注意的是,Cityscapes 配置下 PointRend 的显存占用仅 3.1 GB(R-50),这正体现了"只在关键点上精细计算"带来的效率优势。
如何基于 PointRend 定制自己的模型
基于上述配置继承机制,可以非常方便地适配新数据集:
- 复制 pointrend_r50_4xb2-80k_cityscapes-512x1024.py 为自定义配置文件;
- 将
_base_中的数据集配置替换为你自己的数据集(参考 configs/base/datasets 下的模板); - 同步修改 FPNHead 与 PointHead 的
num_classes; - 按显存情况调整
crop_size、train_cfg.num_points(采样点数)与test_cfg.subdivision_steps(细分次数)——点数与细分次数是精度与速度之间的直接权衡旋钮; - 若换用其他骨干,仅需修改
pretrained与backbone字段(R-101 的做法即为此例)。
引用
如果你在研究中使用了 PointRend 或本仓库实现,可参考如下 BibTeX:
@inproceedings{kirillov2020pointrend, title={Pointrend: Image segmentation as rendering}, author={Kirillov, Alexander and Wu, Yuxin and He, Kaiming and Girshick, Ross}, booktitle={Proceedings of the IEEE/CVF conference on computer vision and pattern recognition}, pages={9799--9808}, year={2020} }【免费下载链接】mmsegmentationOpenMMLab Semantic Segmentation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmsegmentation
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考