- 人工智能
- 计算机视觉
- 预训练
【免费下载链接】PaddleSeg
Easy-to-use image segmentation library with awesome pre-trained model zoo, supporting wide-range of practical tasks in Semantic Segmentation, Interactive Segmentation, Panoptic Segmentation, Image Matting, 3D Segmentation, etc.
本文围绕 PaddleSeg 仓库中 contrib/PanopticSeg 全景分割工具包所使用的标签编码协议展开。全景分割(Panoptic Segmentation)将语义分割(semantic segmentation)与实例分割(instance segmentation)统一到同一张标注图上,每个像素不仅携带"属于哪个类别"的信息,还携带"属于该类别的第几个实例"的信息。读懂这套pan_id编码协议,是理解 PanopticSeg 数据预处理、模型后处理、可视化与 PQ(Panoptic Quality)评测的前提。读完本文,你将掌握pan_id的数学构成、thing/stuff/未知像素三类标签的编码规则、默认常量label_divisor的由来,以及该协议在数据加载、后处理和评测链路中的真实调用方式。
1. 协议总览:一张图上每个像素的pan_id
在 PanopticSeg 工具包中,全景分割图(无论是数据集提供的参考标签图,还是模型推理输出的预测图)的每个像素值都被称为pan_id(panoptic ID)。协议规定:
假设共有
c个类别,最大实例个数为n,则pan_id的取值范围为0到c * label_divisor + n(闭区间)。
也就是说,pan_id不是简单的类别编号,而是把"类别维度"与"实例维度"压缩进同一个整数。这一设计的核心收益是:一张单通道整型图即可同时表达语义与实例信息,无需为二者维护两张独立标签图;同时,任意一个pan_id都可以通过除法和取模运算唯一地还原出(cat_id, ins_id)二元组,便于评测与可视化。
2. 三种标签的编码规则
2.1 thing 类别(可数物体类)
对于 thing 类(如人、车、动物等可以逐个计数的物体类别),pan_id由类别 ID 与实例 ID 联合编码:
pan_id = (cat_id + 1) * label_divisor + ins_id其中:
cat_id是类别 ID,从0开始计数;ins_id是实例 ID,从1开始计数。
注意两个细节:类别部分带+1偏移,实例 ID 从1而不是0起步。这两点都是为了让编码结果严格避开"未知标签"的保留值0,并让 stuff 类与 thing 类在数值空间上自然分层(见下文)。
2.2 stuff 类(不可数区域类)
对于 stuff 类(如天空、地面、墙壁等背景区域,不区分个体),pan_id不再附加实例分量:
pan_id = (cat_id + 1) * label_divisor等价于ins_id = 0的特例。stuff 像素的pan_id恰好落在其类别区间内的"基准位置"上,即pan_id % label_divisor == 0。这一性质被工具包直接利用来判定"该标签是否属于 crowd / 未分实例区域"(见第 5 节的is_crowd)。
2.3 未知标签
对于无法确定结果或未标注的像素,pan_id统一取值为0。由于cat_id + 1 >= 1且 stuff 编码最小值为1 * label_divisor = 1000,0在编码空间中是独立保留的,不会与任何合法标签冲突。
2.4 常量label_divisor
label_divisor是一个预设常量,用于在数值空间中划分"类别段"与"实例段"的宽度。协议规定其默认值为1000。以默认值为例,cat_id = 0的 thing 类第 1 个实例编码为1001,cat_id = 0的 stuff 类编码为1000,而cat_id = 1的 thing 类第 3 个实例编码为2003,等等。可以直观理解为:每个类别占用连续 1000 个 ID 号段,其中(cat_id+1)*1000为该类 stuff 基准,其后的若干正整数为该类各实例。
3. 源码中的编码与解码实现
3.1encode.py:协议的唯一权威实现
协议的数学公式在 paddlepanseg/utils/encode.py 中被完整落地,共 3 个函数:
_CAT_ID_OFFSET = 1 def decode_pan_id(pan_id, label_divisor): return (pan_id // label_divisor) - _CAT_ID_OFFSET, pan_id % label_divisor def encode_pan_id(cat_id, label_divisor, ins_id=0): return (cat_id + _CAT_ID_OFFSET) * label_divisor + ins_id def is_crowd(pan_id, label_divisor): return pan_id % label_divisor == 0encode_pan_id(cat_id, label_divisor, ins_id=0)与文档公式一一对应,ins_id缺省为0,即默认编码 stuff 标签;decode_pan_id(pan_id, label_divisor)是编码的逆运算:pan_id // label_divisor - 1得到cat_id,pan_id % label_divisor得到ins_id(stuff 时为0);is_crowd(pan_id, label_divisor)利用ins_id == 0的性质判定该 ID 是否属于未分实例的 crowd/stuff 区域。
源码文件头部的注释TODO: Make the encoding system object-oriented and configurable.也提示我们:目前这套编码以模块级函数形式存在,工具包仍以label_divisor作为唯一可调参数。
3.2 从 COCO 标注到三张标签图:DecodeLabels
数据预处理阶段是编码协议最核心的消费方。在 paddlepanseg/transforms/transforms.py 的DecodeLabels中,工具包把 COCO 风格的 JSON 标注(segments_info,其中id为原始标注 ID、category_id为原始类别 ID、iscrowd标记是否属于拥挤区域)重编码为统一的pan_id:
class DecodeLabels(object): def __init__(self, label_divisor, ignore_index): self.label_divisor = label_divisor self.ignore_index = ignore_index def __call__(self, data): raw_label = data['label'] segments_info = data['ann'] thing_ids = set(data['thing_ids']) ins_label = np.zeros_like(raw_label, dtype='int64') sem_label = np.full_like(raw_label, self.ignore_index, dtype='int64') pan_label = np.zeros_like(raw_label, dtype='int64') ins_id = 0 class_id_tracker = Counter() for seg in segments_info: id_ = seg['id'] mask = (raw_label == id_) cat_id = seg['category_id'] sem_label[mask] = cat_id if cat_id in thing_ids: if seg['iscrowd'] == 0: # Do not include crowded instances in `ins_label` ins_id += 1 ins_label[mask] = ins_id # Re-encode `pan_id` using `cat_id` and tracked class instance id class_id_tracker[cat_id] += 1 pan_id = encode_pan_id( cat_id, self.label_divisor, ins_id=class_id_tracker[cat_id]) pan_label[mask] = pan_id else: pan_id = encode_pan_id(cat_id, self.label_divisor) pan_label[mask] = pan_id seg['id'] = pan_id ...该实现透露了三个重要的工程细节:
- 实例 ID 按类别分别计数:
class_id_tracker以cat_id为键分别计数,因此每个类别的实例都从 1 开始编号,而非全局连续编号——这与第 2 节公式中"ins_id是该类别内的实例 ID"的语义严格一致; - crowd 区域只参与 pan 编码:
iscrowd == 1的区域不写入ins_label,但仍会通过class_id_tracker占用一个实例计数并生成对应的pan_id; - 一图三用:同一份标注被展开为
ins_label(纯实例图)、sem_label(纯语义图,背景像素填ignore_index=255)和pan_label(pan_id全景图),训练与评测阶段按需取用。
4. 配置层面的串联:label_divisor从 YAML 到运行时
label_divisor不仅在数据侧生效,还贯穿后处理器与评测器,因此它在配置文件中被统一声明并通过 YAML 锚点(anchor)共享。以 Mask2Former COCO 配置 configs/mask2former/mask2former_resnet50_os16_coco_1024x1024_bs4_370k.yml 为例:
num_classes: &num_classes 133 ignore_index: &ignore_index 255 label_divisor: &label_divisor 1000 ... train_dataset: type: COCO ... label_divisor: *label_divisor num_classes: *num_classes ignore_index: *ignore_index val_dataset: ... - type: DecodeLabels label_divisor: *label_divisor ignore_index: *ignore_index ...同一套&label_divisor 1000锚点被train_dataset、val_dataset以及后处理器postprocessor多处复用,保证数据编码、模型输出重编码与评测三方使用完全一致的除数。Panoptic DeepLab 的 Cityscapes 配置 configs/panoptic_deeplab/panoptic_deeplab_resnet50_os32_cityscapes_1025x513_bs8_90k.yml 同样遵循这一模式。
从源码看,label_divisor有多级默认值兜底机制:
- 配置解析层:paddlepanseg/cvlibs/config.py 中
_set_attr_if_not_exists(pp_cfg, 'label_divisor', 1000),未配置时写入默认值1000; - 数据集层:paddlepanseg/datasets/base_dataset.py 中
PanopticDataset定义类常量LABEL_DIVISOR = 1000,构造参数label_divisor=None时自动回退到该类常量; - 后处理器层:
Postprocessor基类与PanopticDeepLabPostprocessor、MaskFormerPostprocessor的构造参数均带label_divisor=1000默认值。
因此即便用户完全不写该字段,协议也能以默认值1000正常工作;而 COCO、Cityscapes 等数据集的类别数均远小于 1000,默认除数在工程上是安全的。
5. 编码协议在后处理与评测中的应用
5.1 PanopticDeepLab 后处理:重编码预测结果
模型输出的原始张量并不是pan_id图,需要后处理器按同一协议重新编码。在 paddlepanseg/postprocessors/panoptic_deeplab_pp.py 的_merge_semantic_and_instance中:
- thing 实例先经中心点检测、像素分组得到实例图,再对每个实例的掩码在其语义类内做多数投票(majority voting)确定
class_id,并通过class_id_tracker按类计实例,随后调用encode_pan_id(class_id, self.label_divisor, ins_id=new_ins_id)写入pan_seg; - stuff 区域则要求语义预测一致且面积不小于
stuff_area(默认 2048 像素),直接调用encode_pan_id(class_id, self.label_divisor)(即ins_id=0)写入。
由此,预测全景图与参考标签共用同一套数值空间,评测时可以直接逐像素比对。
5.2 PQ 评测:从pan_id还原cat_id
Panoptic Quality(PQ)评测需要把预测的每个pan_id映射回类别。在 paddlepanseg/utils/evaluation/pan_seg_evaluator.py 中:
labels, labels_cnt = np.unique(pred, return_counts=True) for label, label_cnt in zip(labels, labels_cnt): if label == VOID: # id==0 stands for area to be ignored continue cat_id, _ = decode_pan_id(label, self.label_divisor) ...- 未知像素(
pan_id == 0)在评测时被直接跳过; - 其余每个
pan_id都通过decode_pan_id还原出cat_id并组装预测标注字典,随后与 GT 标注做匹配与混淆矩阵统计。
同理,实例分割评测器 paddlepanseg/utils/evaluation/ins_seg_evaluator.py 也通过decode_pan_id(i, self.label_divisor)解析类别,并用is_crowd(i, self.label_divisor)跳过未分实例的区域。
5.3 可视化:按类取色 + 按实例抖动
编码协议同样服务于可视化。在 paddlepanseg/utils/visualize.py 的visualize_panoptic中,逐一遍历图中唯一的pan_id,通过decode_pan_id(lab, label_divisor)得到(cat_id, ins_id):基础色取自类别的 colormap;当ins_id > 0时,在基础色附近做随机抖动生成该实例专属颜色,从而让同一类别下的不同实例在视觉上可区分。这正是"单通道pan_id图能支撑完整彩色全景可视化"的原因。
6. 数值示例与自检
为便于理解,下面用label_divisor = 1000给出几个具体编码:
| 场景 | cat_id | ins_id | pan_id 计算 | pan_id |
|---|---|---|---|---|
| thing 类第 1 个实例 | 0 | 1 | (0+1)×1000 + 1 | 1001 |
| thing 类第 2 个实例 | 0 | 2 | (0+1)×1000 + 2 | 1002 |
| thing 类第 1 个实例 | 1 | 1 | (1+1)×1000 + 1 | 2001 |
| stuff 类 | 2 | 0 | (2+1)×1000 + 0 | 3000 |
| 未知 / 未标注 | — | — | — | 0 |
反向验证:pan_id = 2001时,decode_pan_id(2001, 1000)得到(2001 // 1000 - 1, 2001 % 1000) = (1, 1),与编码输入一致;而pan_id = 3000时is_crowd(3000, 1000)返回True,符合 stuff 语义。
7. 协议要点小结
- 单图双语义:
pan_id一张单通道图同时编码语义类别与实例序号,pan_id = (cat_id + 1) * label_divisor + ins_id; - thing 与 stuff 分层:thing 使用
ins_id >= 1,stuff 退化为ins_id = 0,二者在数值上不重叠,且均可由% label_divisor快速区分; 0号保留:pan_id = 0专用于未知/未标注像素,评测时按 VOID 忽略;label_divisor默认 1000:在配置、数据集、后处理器与评测器四层均有默认值兜底,可通过 YAML 锚点全局统一定制;- 全链路一致:数据加载(
DecodeLabels)、模型后处理(encode_pan_id重编码)、PQ 评测(decode_pan_id还原)与可视化(decode_pan_id取色)共用 utils/encode.py 中的同一套编解码函数,确保训练、推理与评估的数值口径完全统一。
如需进一步了解该协议在完整数据流程中的位置,可继续阅读 quick_start_en.md、full_features_en.md 与 dev_guide_en.md,或直接对照 mask2former 配置 与 panoptic_deeplab 配置 验证各环节的label_divisor取值。
- 人工智能
- 计算机视觉
- 预训练
【免费下载链接】PaddleSeg
Easy-to-use image segmentation library with awesome pre-trained model zoo, supporting wide-range of practical tasks in Semantic Segmentation, Interactive Segmentation, Panoptic Segmentation, Image Matting, 3D Segmentation, etc.
相关推荐
零代码搞定图像分割数据集:PaddleSeg数据生成工具全攻略
零代码搞定图像分割数据集:PaddleSeg数据生成工具全攻略 图像分割模型训练中,高质量标注数据短缺是最棘手的问题。标注一张语义分割图像平均需要30分钟,专业
人工智能计算机视觉预训练SRS 实时媒体服务器全景指南:协议、转封装、编解码与部署实践
SRS 实时媒体服务器全景指南:协议、转封装、编解码与部署实践 SRS(Simple Realtime Server)是一个 简单、高性能、AI 驱动的实时媒体
音视频后端直播Transformers 中的 SegFormer:MiT 编码器与全 MLP 解码器的语义分割实践指南
Transformers 中的 SegFormer:MiT 编码器与全 MLP 解码器的语义分割实践指南 本文围绕 Transformers 官方模型文档 Se
人工智能深度学习机器学习预训练微调NLP计算机视觉语音多模态
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考