SSIM算法基于HVS更擅长从图像中提取结构信息的事实,并且利用结构相似度来计算图像的感知质量。
在Z. Wang等人的论文Multi-scale structural similarity for image quality assessment中也提到,
S
S
I
M
SSIM
SSIM算法要好于当时的其它的感知图像质量指标。
SSIM的计算公式:
SSIM计算中的图像分割
==在整幅图片的跨度上,图像亮度的均值和方差变化较为剧烈;并且图像上不同区块的失真程度也有可能不同;再者人眼睛每次只能聚焦于一处,更关注局部数据而非全局数据。==因此如上的
S
S
I
M
SSIM
SSIM算法不能直接作用于一整副图像。
在论文Image quality assessment: From error visibility to structural similarity中,作者采用
11
×
11
11 \times 11
11×11的滑动窗口将整副图像分割为
N
N
N个patch,然后计算每一个patch的
S
S
I
M
SSIM
SSIM,最后计算所有patch的
S
S
I
M
SSIM
SSIM值的平均数(
M
e
a
n
S
S
I
M
:
M
S
S
I
M
Mean \ \ SSIM:MSSIM
Mean SSIM:MSSIM)作为整副图像的
S
S
I
M
SSIM
SSIM。
为避免滑动窗口带来的块效应,在计算每个patch的均值
μ
\mu
μ和方差
σ
2
\sigma^2
σ2时,作者采用
σ
=
1.5
\sigma=1.5
σ=1.5的高斯卷积核作加权平均。整副图像有
N
N
N个patch,则
M
S
S
I
M
MSSIM
MSSIM的计算公式为:
FFmpeg计算SSIM的实际实现
FFmpeg提供了计算
S
S
I
M
SSIM
SSIM的实现:
https://github.com/FFmpeg/FFmpeg/blob/master/tests/tiny_ssim.c。
从代码注释中看到:为提升算法性能,没采用论文中的高斯加权方式计算每个patch的
S
S
I
M
SSIM
SSIM,而采用了
8
×
8
8 \times 8
8×8的块来计算每个patch的
S
S
I
M
SSIM
SSIM。
standard approximation of overlapped 8x8 block sums
解释一下注释中的standard approximation of overlapped 8x8 block sums 的含义。分解成两个部分来解释:overlapped 8x8 block和sums。
overlapped 8x8 block的含义:
FFmpeg在计算图像
S
S
I
M
SSIM
SSIM时,首先以
4
×
4
4 \times 4
4×4的块大小把图1所示的分辨率为
W
×
H
W \times H
W×H的图像:
图1:原始图像
图2:分割后的图像
对图2中的每一块用
b
l
o
c
k
(
i
,
j
)
block(i,j)
block(i,j)来表示(图2中的红色块),FFmpeg使用
b
l
o
c
k
(
i
,
j
)
block(i,j)
block(i,j)及其上、右、右上块(图2中的绿色块)来计算
S
S
I
M
:
S
S
I
M
(
x
i
j
,
y
i
j
)
SSIM:SSIM(x_{ij},y_{ij})
SSIM:SSIM(xij,yij)。
b
l
o
c
k
(
i
,
j
)
block(i,j)
block(i,j)及其上、右、右上块构成一个
8
×
8
8\times8
8×8的像素块,并且该
8
×
8
8\times8
8×8块和计算
b
l
o
c
k
(
i
,
j
+
1
)
block(i,j+1)
block(i,j+1)的
S
S
I
M
SSIM
SSIM用到的
8
×
8
8\times8
8×8的块存在重合像素,这就是注释中的overlapped 8x8 block的真正含义。
根据如上规则:
i
∈
[
1
,
H
4
]
,
j
∈
[
0
,
W
4
−
1
]
i \in [1,\frac{H}{4}],j \in [0,\frac{W}{4}-1]
i∈[1,4H],j∈[0,4W−1],即第0行和最后一列的块不会计算
S
S
I
M
SSIM
SSIM。最后FFmpeg中的
S
S
I
M
SSIM
SSIM公式为:
sums的含义
如前所述,分析了FFmpeg计算图像的
S
S
I
M
SSIM
SSIM的整体思路,接下来分析FFmpeg是如何计算
b
l
o
c
k
(
i
,
j
)
block(i,j)
block(i,j)的
S
S
I
M
(
x
i
j
,
y
i
j
)
SSIM(x_{ij},y_{ij})
SSIM(xij,yij)的,即可解释sums的含义。
首先利用源码中的函数ssim_4x4x2_core()来计算
b
l
o
c
k
(
i
,
j
)
block(i,j)
block(i,j)块的结构相似性指标,包含4个指标:
s1:参考图像在
b
l
o
c
k
(
i
,
j
)
block(i,j)
block(i,j)块的像素之和
s2:受损图像在
b
l
o
c
k
(
i
,
j
)
block(i,j)
block(i,j)块的像素之和
ss:参考图像和受损图像在
b
l
o
c
k
(
i
,
j
)
block(i,j)
block(i,j)块的像素平方之和
s12:参考图像和受损图像在
b
l
o
c
k
(
i
,
j
)
block(i,j)
block(i,j)块的对应像素乘积之和。
如上4个指标是后续会用到的sums(4类sum,称为sums),该sums也就是overlapped 8x8 block sums中的sums的概念。
利用sums计算各4x4块的SSIM
接下来利用该sums值计算
S
S
I
M
SSIM
SSIM。为提升效率,FFmpeg会按照行来计算每一行的各个块的sums数据,并将每个行块的sums数据存储在长度为
W
4
\frac{W}{4}
4W的数组指针sum((int(*)[4]))中。
sum指针有两种:
sum0:存储当前行的各块的sums结果
sum1:存储当前行的上一行的sums结果
先计算第
i
−
1
i-1
i−1行块和第
i
i
i行块的sums结果,并分别存入sum1和sum0中。然后遍历第
i
i
i行块的每一个块,并利用sum1和sum0中计算的结果来计算每一块的
S
S
I
M
SSIM
SSIM。
函数ssim_end4()展示了如何利用
b
l
o
c
k
(
i
−
1
,
j
)
block(i-1,j)
block(i−1,j),
b
l
o
c
k
(
i
−
1
,
j
+
1
)
block(i-1,j+1)
block(i−1,j+1),
b
l
o
c
k
(
i
,
j
)
block(i,j)
block(i,j),
b
l
o
c
k
(
i
,
j
+
1
)
block(i,j+1)
block(i,j+1)的sums信息来计算
S
S
I
M
(
x
i
j
,
y
i
j
)
SSIM(x_{ij},y_{ij})
SSIM(xij,yij):
- 先对4个块的sums结果进行加和处理,得到 8 × 8 8\times8 8×8块的sums结果
- 利用该
8
×
8
8\times8
8×8块的sums来计算
b
l
o
c
k
(
i
,
j
)
block(i,j)
block(i,j)的
S
S
I
M
SSIM
SSIM
源码中ssim_end1()展示了如何利用 8 × 8 8\times8 8×8块的sums信息来计算 S S I M SSIM SSIM。具体的计算方法如下。
将红色区块 b l o c k ( i , j ) block(i,j) block(i,j)的图像放大一点,如图3所示。我们接下来计算其 S S I M SSIM SSIM。
图3: b l o c k ( i , j ) block(i,j) block(i,j)的示意图
在计算时,首先将4个区块的sums值求和,得到
8
×
8
8\times8
8×8区块的sums值,分别为:
利用如上的公式对
S
S
I
M
SSIM
SSIM的公式进行计算可以得到:
FFmpeg源码中,对
C
1
C_1
C1和
C
2
C_2
C2的定义中的因子64或63也是根据上面的公式,但是从公式看,FFmpeg对ssim_c1的计算少乘了64:
为简化计算,FFmpeg还做了如下的定义:
最终在FFmpeg中,计算
S
S
I
M
SSIM
SSIM的公式为:
如上公式就是源码的函数ssim_end1()中最终的计算方式。
利用各块的SSIM计算图像的SSIM
计算完所有块的
S
S
I
M
SSIM
SSIM之后,所有块的平均
S
S
I
M
SSIM
SSIM作为该图像的
S
S
I
M
SSIM
SSIM
编码过程中的技巧
FFmpeg计算
S
S
I
M
SSIM
SSIM的实现中,为提升效率和抽象代码逻辑,利用很多的编程技巧,如:
- 计算YUV各分量图像宽度时用w >> !!i
- 为了避免对第0行的特殊处理,采用两层循环来处理
- 计算每一行的各块的sums信息时,为了降低循环次数,每次循环计算2个块的sums结果,ssim_4x4x2_core的函数名可能就是这么来的。
- 计算每一行的各块的 S S I M SSIM SSIM时,为了降低循环次数,每次循环计算4个块的 S S I M SSIM SSIM,源码中的ssim_end4的函数名可能就是这么来的。