Video Object Segmentation 评估指标
本篇介绍常用的视频目标分割的评估方法,即文章 CVPR2016: A Benchmark Dataset and Evaluation Methodology for Video Object Segmentation 中提出的 DAVIS 数据集和评估指标:
- Region Similarity $\mathcal{J}$
- Contour Accuracy $\mathcal{F}$
- Temporal stability $\mathcal{T}$
在 2017 年及以后,DAVIS 竞赛保留的主要评估方法是 Region Similarity $\mathcal{J}$ 和 Contour Accuracy $\mathcal{F}$。
代码
github 官方代码:
除了 Python 版本的代码,官方还提供了 MATLAB 版本代码,这里使用 Python 2017 优化版进行介绍,注意官方代码采用 python 2.x。
在监督评估框架中,给定特定帧上的真实(groundtruth)掩码 $G$ 和输出分割 $M$,任何评估措施最终都必须回答 $M$ 与 $G$ 拟合程度如何的问题。对于图像,可以使用两个互补的图像观点:基于区域和基于轮廓的措施。
环境配置
1 | # !git clone https://github.com/fperazzi/davis-2017.git |
评估指标
1 | import os |
1 | work_dir = "/disk0/documents/python/nb/davis-2017/python/lib/davis/measures" |
1 | sys.path |
['/disk0/documents/python/nb/davis-2017/python/lib/davis/measures',
'/usr/local/miniconda/envs/davis/lib/python310.zip',
'/usr/local/miniconda/envs/davis/lib/python3.10',
'/usr/local/miniconda/envs/davis/lib/python3.10/lib-dynload',
'',
'/home/jinzhongxu/.local/lib/python3.10/site-packages',
'/home/jinzhongxu/.local/lib/python3.10/site-packages/feelvos-0.5-py3.10.egg',
'/usr/local/miniconda/envs/davis/lib/python3.10/site-packages']
Region Similarity $\mathcal{J}$
为了测量基于区域的分割相似度,即错误标记像素的数量,使用 Jaccard 索引 $\mathcal{J}$ 定义为估计的分割和 groundtruth 掩模的交集过并。Jaccard 索引自 PASCAL VOC2008 中首次出现以来就被广泛采用,因为它提供了关于错误标记像素数量的直观、尺度不变的信息。给定一个输出分割 $M$ 和相应的 groundtruth 掩码 $G$,它被定义为:
$$ \mathcal{J} = \frac{|M \cap{G}|}{|M \cup{G}|} $$直白的说,区域相似度就是针对某一帧,计算标注的真实掩膜与视频目标分割算法得到的掩膜,这两幅(0,1)二值图像之间的 jaccard 相似系数。
需要说明的是,官方代码使用的 numpy 版本过低,如果使用高版本的 numpy,请将代码中 np.bool
改成 np.bool_
。
1 | import cv2 |
1 | ann_path = "/disk0/proj-pf/track-cutie/data/davis/annotation/000.png" |
1 | ann = cv2.imread(ann_path) |
1 | plt.imshow(ann) |
1 | plt.imshow(seg) |
1 | # 只取最后一个图层 |
1 | plt.imshow(np.hstack([seg_r, ann_r]), cmap="gray") |
1 | jaccard.db_eval_iou(annotation=ann_bin, segmentation=seg_bin) |
0.9267666392769104
除了对于单帧的区域相似度评估,还给出了对于一个视频的区域相似度评估,分别从三个角度进行算法评估(如果是半监督 VOS,那么给出的第一帧 groundtruth 帧被忽略):
- mean: 一个视频序列所有帧的 $\mathcal{J}$ 值加起来求算数平均,最终结果是该视频的 $\mathcal{J}$-mean 值。该值越高说明算法越好;
- recall: 一个视频序列所有帧的 $\mathcal{J}$ 值超过一定阈值(如 0.5)的帧的个数(不是 $\mathcal{J}$ 值)加起来求算数平均(分母为总帧数),最终结果是该视频的 $\mathcal{J}$-recall 值。该值越高说明算法越好;
- delay: 一个视频序列所有帧的前四分之一帧的 $\mathcal{J}$ 平均值减去后四分之一帧的 $\mathcal{J}$ 平均值,最终结果是该视频的 $\mathcal{J}$-delay 值。该值越低说明算法越好。一般情况下,视频前面帧的跟踪和分割效果比后面帧好,所以该值应该大于 0,越接近 0越好。
核心代码:
1 | import numpy as np |
Contour Accuracy $\mathcal{F}$
从基于等高线的角度来看,可以将 $M$ 解释为一组封闭的等高线 $c(M)$,这些等高线划定了掩模的空间范围。因此,可以计算基于轮廓的精度,并通过二部图匹配来召回 $c(M)$ 和 $c(G)$ 的轮廓点之间的 $P_c$ 和 $R_c$,以便对小的不准确性具有鲁棒性。作者认为所谓的 F-measure $\mathcal{F}$ 是两者之间的一个很好的权衡,定义为:
$$ \mathcal{F} = \frac{2 P_c R_c}{P_c + R_c} $$为了提高效率,在实验中,通过形态学算子近似二分匹配。
直白的说,轮廓精度就是针对某一帧,先分别计算标注的真实掩膜和视频目标分割算法得到的掩膜的轮廓,针对两幅轮廓图,分别计算精确率 $P$ 和召回率 $R$,最终计算两者的调和平均值得到 $F$-score。
需要说明的是,官方代码使用的 numpy 版本过低,如果使用高版本的 numpy,请将代码中 np.bool
改成 np.bool_
。
1 | import f_boundary |
1 | ann_map = f_boundary.seg2bmap(seg=ann_bin) |
1 | # 查看边界细节 |
[[False False False False False False False False False False False False]
[False False False False False False False False False False False False]
[False False False False False False False False False False False False]
[False False False False False False False False False False False False]
[False False False False False False False False False False True True]
[False False False False False False False False True True True False]
[False False False False False False True True True False False False]
[False False True True True True True False False False False False]
[False False True True True True True True True True True True]
[False False False False False False False False False False False False]]
1 | seg_map = f_boundary.seg2bmap(seg=seg_bin) |
1 | type(seg_map), seg_map.shape |
(numpy.ndarray, (480, 854))
1 | plt.imshow(seg_map, cmap="gray") |
1 | f_boundary.db_eval_boundary(foreground_mask=seg_bin, gt_mask=ann_bin) |
0.8924810970352192
类似于区域相似度,除了对于单帧的轮廓精度评估,还给出了对于一个视频的轮廓精度评估,分别从三个角度进行算法评估(如果是半监督 VOS,那么给出的第一帧 groundtruth 帧被忽略):
- mean: 一个视频序列所有帧的 $\mathcal{F}$ 值加起来求算数平均,最终结果是该视频的 $\mathcal{F}$-mean 值。该值越高说明算法越好;
- recall: 一个视频序列所有帧的 $\mathcal{F}$ 值超过一定阈值(如 0.5)的帧个数(不是 $\mathcal{F}$值)加起来求算数平均(分母是总帧数),最终结果是该视频的 $\mathcal{F}$-recall 值。该值越高说明算法越好;
- delay: 一个视频序列所有帧的前四分之一帧的 $\mathcal{F}$ 平均值减去后四分之一帧的 $\mathcal{F}$ 平均值,最终结果是该视频的 $\mathcal{F}$-delay 值。该值越低说明算法越好。一般情况下,视频前面帧的跟踪和分割效果比后面帧好,所以该值应该大于 0,越接近 0越好。
核心代码:
1 | import numpy as np |
mean, delay, recall 核心代码:
1 | import numpy as np |
Temporal stability $\mathcal{T}$
直观上,$\mathcal{J}$ 衡量两个掩码的像素匹配程度,而 $\mathcal{F}$ 衡量轮廓的准确性。然而,结果的时间稳定性是视频对象分割的一个相关方面,因为对象形状的演变是识别和抖动的重要线索,不稳定的边界在视频编辑应用中是不可接受的。因此,作者 2016 最初提出评估指标时另外引入了一个时间稳定性措施来惩罚这种不希望的影响。
关键的挑战是区分物体的可接受运动和不希望的不稳定和抖动。为了做到这一点,估计从一帧到下一帧转换掩码所需的变形。从直观上看,如果变换是平滑和精确的,则可以认为结果是稳定的。
形式上,将帧 $t$ 的掩模 $M_t$ 转换成代表其轮廓 $P(M_t)$ 的多边形。然后使用形状上下文描述符(SCD)来描述每个点 $p_t^i \in p(M_t)$。接下来,将匹配作为一个动态时间扭曲(DTW)问题,寻找 $p_t^i$ 和 $p_{t+1}^j$ 之间的匹配,以最小化匹配点之间的 SCD 距离,同时保持点在形状中存在的顺序。
得到的每个匹配点的平均代价被用作时间稳定性 $\mathcal{T}$ 的度量。直观地说,匹配将补偿运动和小的变形,但它不会补偿轮廓的振荡和不准确性,这是想要测量的。咬合和非常强烈的变形会被误解为轮廓不稳定,因此在没有这种影响的序列子集上计算测量。
但是,DAVIS 2017 开始,不再把该指标作为竞赛的评判依据。
MATLAB 版的时间稳定性调用非常方便,但是 Python 版的因为是使用 C++ 开发,需要编译后才能使用,一种编译方式是:
1 | git clone https://github.com/fperazzi/davis-2017.git |
此时,在 davis-2017/build/release
下面就可以看到编译好的 tstab.so