DeepLab 全景分割评测指标实战:Panoptic Quality 与 Parsing Covering 的实现原理与两种评估流程
【免费下载链接】modelsModels and examples built with TensorFlow项目地址: https://gitcode.com/GitHub_Trending/mode/models
本篇聚焦 TensorFlow Models 仓库 DeepLab 项目中的全景分割(Panoptic Segmentation,又称 Whole Image Parsing)评测体系:详解实例级 Panoptic Quality(PQ)与区域级 Parsing Covering(PC)两项指标的定义、偏置来源与源码实现,并给出两种可直接落地的评估方案——基于 COCO 格式结果的 Python 离线评测(eval_coco_format.py)与基于tf.py_func的 TensorFlow 在线流式评测(streaming_metrics.py)。读完本文,读者将能够在 COCO Panoptic 数据集或自有数据集上正确配置并运行 PQ/PC 评测,并理解每个参数背后的实现逻辑。
一、什么是 Whole Image Parsing(全景分割)评测
Whole Image Parsing(整图解析),也称 Panoptic Segmentation(全景分割),是对语义分割与实例分割的泛化:它为图像中的每个像素同时赋予语义类别标签与实例标签——对背景类("stuff",如天空、路面)给出逐像素的语义分割,对前景物体类("thing",如行人、车辆)给出实例级分割。
这一任务由来已久(Tu et al., 2005 的 Image Parsing),但早期工作往往用各自独立的指标分别评估语义分割结果与目标检测结果。Kirillov et al. 在 2018 年提出统一的实例级指标 Panoptic Quality(PQ)[arXiv:1801.00868],并被 COCO、Mapillary Vistas 等基准采用。
DeepLab 团队(Yang et al., "DeeperLab: Single-Shot Image Parser", arXiv:1902.05093)指出:实例级 PQ 指标往往不成比例地强调小实例的解析质量,且偏向 "thing" 类而轻视 "stuff" 类。为此,他们把此前用于类别无关分割质量评估的 Covering 指标(Arbelaez et al., PAMI 2011)适配到整图解析任务上,提出了区域级指标Parsing Covering(PC)。
仓库在 research/deeplab/evaluation/ 目录下同时提供了 PQ 与 PC 的完整实现,模块构成如下:
| 文件 | 职责 |
|---|---|
| base_metric.py | 抽象基类SegmentationMetric,定义评测接口与类别/实例标签编码 |
| panoptic_quality.py | PQ 指标的 Python 实现(PanopticQuality类) |
| parsing_covering.py | PC 指标的 Python 实现(ParsingCovering类) |
| eval_coco_format.py | COCO 格式结果的离线评测入口(含多进程并行) |
| streaming_metrics.py | TensorFlow 图模式下的流式(streaming)PQ/PC 指标 |
| eval_coco_format_test.py | 与官方 COCO PanopticAPI 对拍的单元测试 |
| testdata/ | 评测用的小型 GT/Pred 样例数据(coco_gt.json、coco_pred.json及 ID 图像) |
二、Panoptic Quality(PQ):定义、偏置与源码实现
2.1 指标定义
给定真值分割 S 与预测分割 S',PQ 定义为(公式图见 equation_pq.png):
- R 与 R' 分别是真值区域(region)与预测区域;
- |TP|、|FP|、|FN| 分别为匹配成功、误检与漏检的实例数量;
- 匹配判据为 IoU > 0.5 的阈值。
展开来看,PQ = SQ × RQ:SQ(Segmentation Quality)是所有匹配实例对 IoU 的平均,RQ(Recognition Quality)等价于检测层面的 F1 分数(IoU 阈值 0.5 下的 TP/FP/FN 统计)。
PQ 的关键性质:同一 "stuff" 类的全部区域被当作一个实例处理,且完全不考虑实例大小。10×10 像素的实例与 1000×1000 像素的实例对指标贡献相同。因此:
- PQ 对小区域的误检(false positives)格外敏感——这也是为什么 COCO 官方评测代码中会提示"删掉小区域可以刷高分数"这类启发式操作;
- 官方源码中甚至会打印一条警告,提醒使用者把本实现的结果与 COCO 官方 API 对拍,因为微小数值差异(<0.1%)在四舍五入后可能被放大(见 eval_coco_format.py#L104-L110 中
_build_metric对pq分支的 warning)。
因此文档的结论是:当应用对任意尺寸实例的解析质量都同等重视时,PQ 是合适的指标。
2.2 源码实现要点(panoptic_quality.py)
从源码结构看,PQ 的统计完全发生在逐图比较阶段,累积量按类(per-class)保存。核心逻辑在PanopticQuality.compare_and_accumulate(panoptic_quality.py#L52-L159):
- 标签编码:先用基类的
_naively_combine_labels(base_metric.py#L78-L81)把 (category, instance) 二元组压成一个整数segment_id = category * max_instances_per_category + instance;再编码出交叉标签intersection_id = gt_segment_id * offset + pred_segment_id,用一次np.unique(..., return_counts=True)即可得到所有 (真值区域, 预测区域) 交集中的像素数。offset必须大于可能出现的最大segment_id数(即"最大唯一标签数"),COCO 常用256*256。 - TP 判定:遍历有非空交集的同类别区域对,计算
IoU = intersection / union,其中 union 会剔除预测区域中落在真值 void 像素上的部分;IoU > 0.5即记为一个 TP,并累加iou_per_class与tp_per_class。 - FN / FP 判定:未匹配上的真值区域计 FN(void 类除外);未匹配上的预测区域计 FP,但如果该预测区域超过一半面积落在真值的 ignored(void/crowd)区域上,则不惩罚——这与 COCO 官方行为一致。
- 指标合成:
result_per_category按PQ_c = SQ_c × RQ_c逐类计算,RQ_c = TP / (TP + 0.5·FN + 0.5·FP),最终结果对所有有效类(非 ignored 且TP+FN+FP ≠ 0的类)取平均(panoptic_quality.py#L161-L174 的_valid_categories)。
detailed_results还支持按is_thing布尔数组把结果拆分为All / Things / Stuff三个组分别报告 PQ、SQ、RQ 与类数 N,print_detailed_results则用 prettytable 输出这张表。merge方法只需把iou/tp/fn/fp四个 per-class 向量相加,这正是多进程并行评测能成立的前提。
三、Parsing Covering(PC):面向"大目标更重要"的应用场景
3.1 动机与定义
在自动驾驶等应用中,近处(即画面中大)的目标远比远处的小目标重要。PC 通过让实例面积参与加权来体现这一点,其公式(图见 equation_pc.png)为:
- S_i 与 S'_i 分别表示第 i 个语义类的真值分割与预测分割;N_i 是 S_i 中真值区域的总像素数;
- 单类 Covering:Cov_i 的计算方式与原 Covering 指标相同,只是只考虑 S_i 的真值区域与 S'_i 的预测区域;
- PC = 所有 C 个语义类的 Cov_i 的平均。
类级别的覆盖度本质上就是按真值面积加权的最大 IoU:
SC(c) = Σ_{R∈S} (|R| · max_{R'∈S'} O(R,R')) / Σ_{R∈S} |R|(见 parsing_covering.py#L43-L56 的类文档字符串。)
PC 与 PQ 的一个显著区别是:PC 不涉及匹配,也就没有 IoU 阈值。为了平等对待 "thing" 与 "stuff",PC 允许部分正确的分割获得部分得分——文档中给出的例子是:若三棵同样大小的树有一棵被完美分割,那么无论把 "tree" 当 stuff 还是 thing,模型得到的 PC 部分得分都相同。
3.2 源码实现要点(parsing_covering.py)
ParsingCovering.compare_and_accumulate(parsing_covering.py#L85-L164)的实现路径与 PQ 不同:
- 分配三个
[num_categories, max_instances_per_category]的数组,分别记录每类真值实例面积gt_areas、预测实例面积pred_areas、以及每类真值实例对应的最大 IoUmax_ious; - 同样用
intersection_id = gt_segment_id * offset + pred_segment_id编码交叉标签并统计交集面积,同时把每个真值实例与它相交的预测实例记录到intersections[(category, gt_instance)]; - 对每个真值实例,在其所有同类别相交预测实例中取最大 IoU(无阈值过滤);
- 若
normalize_by_image_size=True,先把gt_areas除以图像总像素数再累加——这使大分辨率图像中的小目标不会因为像素面积大而占便宜; - 累积
weighted_iou_per_class += Σ(max_ious × gt_areas)与gt_area_per_class += Σ(gt_areas),单类 PC 即weighted_iou / gt_area(result_per_category,parsing_covering.py#L166-L169)。
PC 的detailed_results同样支持 All / Things / Stuff 分组,但每组只输出pc与类数n两个字段(parsing_covering.py#L185-L209)。
四、方案一:COCO 格式的 Python 离线评测(eval_coco_format.py)
4.1 前置准备:安装 COCO Panoptic API
COCO 结果格式已被多个基准采用,因此仓库提供eval_coco_format函数来评估以 COCO 格式 保存的结果(JSON 描述标注结构,ID 图像给出每个区域的像素位置)。使用前需要先把官方 COCO panoptic segmentation 任务 API(panopticapi)下载到本地目录,并把research/与该目录加入PYTHONPATH。仓库的 research/deeplab/g3doc/installation.md "Add Libraries to PYTHONPATH" 一节给出了具体做法:
# From tensorflow/models/research/ export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim # [Optional] for panoptic evaluation, you might need panopticapi: touch ${PANOPTICAPI_DIR}/panopticapi/__init__.py export PYTHONPATH=$PYTHONPATH:${PANOPTICAPI_DIR}/panopticapi4.2 eval_coco_format 的完整参数说明
核心函数签名(eval_coco_format.py#L222-L233):
eval_coco_format(gt_json_file, pred_json_file, gt_folder=None, pred_folder=None, metric='pq', num_categories=201, ignored_label=0, max_instances_per_category=256, intersection_offset=None, normalize_by_image_size=True, num_workers=0, print_digits=3)各参数含义与默认值(默认值均按 COCO panoptic segmentation 数据集设定,评估其他数据集时需要自行修改):
gt_json_file:COCO 格式真值标注 JSON 文件路径(必填);pred_json_file:待评估预测结果的 JSON 文件路径(必填);gt_folder:存放真值 panoptic 格式 ID 图像的文件夹(用于把标注映射到图像区域)。不传时默认为gt_json_file去掉.json后缀的路径(eval_coco_format.py#L272-L275);pred_folder:存放预测 ID 图像的文件夹,默认规则同上;metric:指标名,'pc'或'pq'(由_build_metric分发到ParsingCovering/PanopticQuality,见 eval_coco_format.py#L97-L116);num_categories:数据集的分割类别总数,COCO 为 201(含 void);ignored_label:评测中忽略的类别 id,例如 COCO panoptic 的 "void" 标签 0;max_instances_per_category:每个类别允许的最大实例数,用于保证实例标签唯一(参与segment_id编码);intersection_offset:最大唯一标签数,用于生成交集区域的唯一 id。不传时自动取(num_categories + 1) * max_instances_per_category(eval_coco_format.py#L276-L277);normalize_by_image_size:PC 评测时是否用图像大小归一化真值实例面积(对pc生效,默认 True);num_workers:并行 worker 数。正整数时按图像切分派发到多进程;-1时使用multiprocessing.cpu_count();0 表示单进程;print_digits:结果汇总输出的有效数字位数。
在 COCO 上评测 PQ 时,用户只需提供两个 JSON 文件路径即可(gt_folder/pred_folder/其余参数都有可用默认值)。
4.3 命令行运行方式
脚本通过 absl flags 暴露了同名命令行参数,gt_json_file、gt_folder、pred_json_file、pred_folder四个为必填项(eval_coco_format.py#L335-L338)。仓库提供的小型样例数据位于 research/deeplab/evaluation/testdata/(coco_gt.json、coco_pred.json及coco_gt/、coco_pred/两个 ID 图像目录),可以直接照下面的形态运行:
cd research/deeplab/evaluation python eval_coco_format.py \ --gt_json_file=../testdata/coco_gt.json \ --gt_folder=../testdata/coco_gt \ --pred_json_file=../testdata/coco_pred.json \ --pred_folder=../testdata/coco_pred \ --metric=pq --num_categories=7 \ --intersection_offset=65536其中--intersection_offset=65536(即 256×256)与单元测试中的取值一致;--num_categories=7对应样例数据 6 个类别 + void。切换到--metric=pc即评估 Parsing Covering。
4.4 内部流程与多进程并行
从源码结构看,eval_coco_format的执行链路为(eval_coco_format.py#L268-L321):
- 读取 GT/Pred 两个 JSON,按
image_id用_matched_annotations配对(eval_coco_format.py#L119-L128); _build_metric构造指标聚合器;num_workers > 0时启动多进程:每对标注作为任务放入work_queue,各 worker 用独立的指标实例对分到的图像调用_compute_metric(内部通过_split_panoptic把 COCO ID 图像+segments_info拆成逐像素的 category 图与 instance 图,其中 GT 侧iscrowd区域会被并入 ignored 类,而预测侧忽略iscrowd标记,与官方代码行为一致),最后通过merge合并回主聚合器;_is_thing_array从 JSON 的categories中解析isthing字段,得到 per-category 的 thing/stuff 布尔数组(并校验类别 id 是否连续,缺失时会打 warning);- 调用
print_detailed_results打印 All/Things/Stuff 分组结果表,并返回detailed_results(is_thing)字典。
函数最终返回的是分组结果字典,形如{'All': {'pq':..., 'sq':..., 'rq':..., 'n':...}, 'Things': {...}, 'Stuff': {...}}(PC 模式为{'All': {'pc':..., 'n':...}, ...})。
五、方案二:TensorFlow 在线(streaming)评测
如果希望在训练/评估流程内以类似tf.contrib.metrics.streaming_mean_iou的方式累积指标,仓库提供streaming_metrics.streaming_panoptic_quality与streaming_metrics.streaming_parsing_covering(streaming_metrics.py)。
5.1 构造 metric_map
from deeplab.evaluation import streaming_metrics metric_map = {} metric_map['panoptic_quality'] = streaming_metrics.streaming_panoptic_quality( category_label, instance_label, category_prediction, instance_prediction, num_classes=201, max_instances_per_category=256, ignored_label=0, offset=256*256) metric_map['parsing_covering'] = streaming_metrics.streaming_parsing_covering( category_label, instance_label, category_prediction, instance_prediction, num_classes=201, max_instances_per_category=256, ignored_label=0, offset=256*256, normalize_by_image_size=True) metrics_to_values, metrics_to_updates = slim.metrics.aggregate_metric_map( metric_map)其中:
category_label与instance_label分别是真值的语义分割与实例分割。它们与 COCO 全景分割格式的关系为:panoptic_label = category_label * max_instances_per_category + instance_label;category_prediction与instance_prediction分别是预测的语义分割与实例分割;metric_map是保存 PQ 与 PC 流式结果的字典。
两个 streaming 函数都要求图模式(tf.executing_eagerly()为真时抛出RuntimeError,见 streaming_metrics.py#L106-L107)。其内部实现是:用tf.py_func把整图交给上文的 Python 实现(_panoptic_quality_helper/_parsing_covering_helper)逐图计算 per-class 中间量,再由_running_total维护的LOCAL_VARIABLES累加器跨批次累积(streaming_metrics.py#L40-L54):
streaming_panoptic_quality返回形状[6, num_classes]的张量,6 行依次为pq、sq、rq、total_tp、total_fn、total_fp,update ops 为 4 个累加操作;streaming_parsing_covering返回[3, num_classes],3 行依次为per-class PC、逐类加权 IoU 总和、逐类真值区域面积总和。
5.2 汇总为 tf.summary
评测值张量需要按类展开后过滤无效类再求均值。仓库文档给出的完整汇总模板(对panoptic_quality与parsing_covering分别处理)如下:
summary_ops = [] for metric_name, metric_value in metrics_to_values.iteritems(): if metric_name == 'panoptic_quality': [pq, sq, rq, total_tp, total_fn, total_fp] = tf.unstack( metric_value, 6, axis=0) panoptic_metrics = { 'pq': pq, # Panoptic quality. 'sq': sq, # Segmentation quality. 'rq': rq, # Recognition quality. 'total_tp': total_tp, 'total_fn': total_fn, 'total_fp': total_fp, } # 有效类 = 非 void 类 且 (tp + fn + fp) != 0 的类 valid_classes = tf.logical_and( tf.not_equal(tf.range(0, num_classes), void_label), tf.not_equal(total_tp + total_fn + total_fp, 0)) for target_metric, target_value in panoptic_metrics.iteritems(): output_metric_name = '{}_{}'.format(metric_name, target_metric) op = tf.summary.scalar( output_metric_name, tf.reduce_mean(tf.boolean_mask(target_value, valid_classes))) op = tf.Print(op, [target_value], output_metric_name + '_classwise: ', summarize=num_classes) op = tf.Print( op, [tf.reduce_mean(tf.boolean_mask(target_value, valid_classes))], output_metric_name + '_mean: ', summarize=1) summary_ops.append(op) elif metric_name == 'parsing_covering': [per_class_covering, total_per_class_weighted_ious, total_per_class_gt_areas] = tf.unstack(metric_value, 3, axis=0) valid_classes = tf.logical_and( tf.not_equal(tf.range(0, num_classes), void_label), tf.not_equal( total_per_class_weighted_ious + total_per_class_gt_areas, 0)) op = tf.summary.scalar( metric_name, tf.reduce_mean(tf.boolean_mask(per_class_covering, valid_classes))) op = tf.Print(op, [per_class_covering], metric_name + '_classwise: ', summarize=num_classes) op = tf.Print( op, [tf.reduce_mean( tf.boolean_mask(per_class_covering, valid_classes))], metric_name + '_mean: ', summarize=1) summary_ops.append(op) else: raise ValueError('The metric_name "%s" is not supported.' % metric_name)注意过滤条件与 Python 实现的_valid_categories严格对应:PQ 侧忽略 void 类以及tp+fn+fp == 0的类;PC 侧忽略 void 类以及weighted_iou + gt_area == 0的类。
5.3 接入评估循环
最后按 SLIM 的评估循环驱动评测,可参考 research/deeplab/eval.py——该脚本提供了一个对语义分割做 mIOU 流式评测的简单示例:
metric_values = slim.evaluation.evaluation_loop( master=FLAGS.master, checkpoint_dir=FLAGS.checkpoint_dir, logdir=FLAGS.eval_logdir, num_evals=num_batches, eval_op=metrics_to_updates.values(), final_op=metrics_to_values.values(), summary_op=tf.summary.merge(summary_ops), max_number_of_evaluations=FLAGS.max_number_of_evaluations, eval_interval_secs=FLAGS.eval_interval_secs)六、如何验证评测结果的正确性
仓库自带了可对拍的单元测试,是检验环境是否搭好的最佳起点:
- PQ 对拍官方实现:eval_coco_format_test.py 的
test_compare_pq_with_reference_eval在 testdata/ 样例数据上分别运行官方 panopticapi 的pq_compute与本仓库的eval_coco_format(metric='pq', num_categories=7, ignored_label=0, max_instances_per_category=256, intersection_offset=256*256),断言 All/Things/Stuff 三组的 pq、sq、rq、n 完全一致; - PC 黄金值:
test_compare_pc_with_golden_value断言normalize_by_image_size=False时All组pc ≈ 0.68210561(n=6)、Things 组≈ 0.5890529(n=4)、Stuff 组≈ 0.86821097(n=2);test_compare_pc_with_golden_value_normalize_by_size则断言开启按图像大小归一化时All组pc ≈ 0.68214908840; - 多进程一致性:
test_pc_with_multiple_workers用num_workers=3并行计算,结果与单进程黄金值一致,验证了merge合并逻辑的正确性。
streaming 侧的 streaming_metrics_test.py 则用 testdata 中的 GT/Pred 图像对(如team_gt_instance.png、team_pred_class.png、team_pred_instance.png)在会话中喂入单图/多图,断言 PQ 的 pq/sq/rq 与 TP/FN/FP 张量逐元素取近似相等(如单图 case 的result_sq ≈ [2.06104, 0.7526, 0.54069]),覆盖单图、多图累积与normalize_by_image_size开/关等组合。
七、指标选型与关键参数速查
何时用 PQ、何时用 PC:
- 关注"每个实例不论大小都应被正确解析"(如通用物体理解基准)→ PQ;
- 关注"大目标解析质量"(如自动驾驶中近处目标更重要)、且不希望匹配阈值引入不连续性 → PC;
- 两者都支持 All/Things/Stuff 分组报告,可横向比较模型在 stuff 与 thing 上的短板。
关键参数速查(两种方案共用同一套含义):
| 参数 | COCO 默认值 | 作用 |
|---|---|---|
num_categories | 201 | 类别总数,决定 per-class 数组维度 |
ignored_label | 0 | void 类 id,不参与 TP/FN/FP 统计 |
max_instances_per_category | 256 | 类别×实例数编码上限,须 ≥ 单图同类最大实例数 |
offset(离线为intersection_offset) | (num_categories+1)×256 或 256×256 | 交叉标签编码基数,须大于最大 segment_id 数 |
normalize_by_image_size | True | 仅 PC:累加前按图像像素数归一化真值面积 |
注意事项:
- streaming 指标只能在图模式(TF1 风格会话)中使用,eager 模式会抛
RuntimeError; - 离线 PQ 结果建议与 COCO 官方 API 对拍后再用于论文级汇报(源码 warning 亦提醒四舍五入可能放大 <0.1% 的差异);
- 默认参数按 COCO panoptic 数据集设定,评估自有数据集时必须同步修改
num_categories、ignored_label等取值。
参考论文(与 research/deeplab/evaluation/README.md 的 References 一致):
- Image Parsing: Unifying Segmentation, Detection, and Recognition. Zhuowen Tu et al., IJCV 2005.
- Panoptic Segmentation. Alexander Kirillov et al., arXiv:1801.00868, 2018.
- Microsoft COCO: Common Objects in Context. Tsung-Yi Lin et al., ECCV 2014.
- The Mapillary Vistas Dataset for Semantic Understanding of Street Scenes. Gerhard Neuhold et al., ICCV 2017.
- DeeperLab: Single-Shot Image Parser. Tien-Ju Yang et al., arXiv:1902.05093, 2019.
- Contour Detection and Hierarchical Image Segmentation. Pablo Arbelaez et al., PAMI 2011.
【免费下载链接】modelsModels and examples built with TensorFlow项目地址: https://gitcode.com/GitHub_Trending/mode/models
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考