图像质量评估指标——SSIM介绍及计算方法
SSIM全称为Structural Similarity,即结构相似性,用于评估两幅图像相似度的指标,常用于衡量图像失真前与失真后的相似性,也用于衡量模型生成图像的真实性,如图像去雨、图像去雾、图像和谐化等等。
计算方法
SSIM的计算基于滑动窗口实现,即每次计算均从图片上取一个尺寸为 N × N N\times N N×N的窗口,基于窗口计算SSIM指标,遍历整张图像后再将所有窗口的数值取平均值,作为整张图像的SSIM指标。
假设
x
x
x表示第一张图像窗口中的数据,
y
y
y表示第二张图像窗口中的数据。其中图像的相似性由三部分构成:luminance(亮度)、contrast(对比度)和structure(结构)。luminance计算公式为:
l
(
x
,
y
)
=
2
μ
x
μ
y
+
c
1
μ
x
2
+
μ
y
2
+
c
1
l(x,y)=\frac{2\mu_x\mu_y+c_1}{\mu_x^2+\mu_y^2+c_1}
l(x,y)=μx2+μy2+c12μxμy+c1
contrast计算公式为:
c
(
x
,
y
)
=
2
σ
x
σ
y
+
c
2
σ
x
2
+
σ
y
2
+
c
2
c(x,y)=\frac{2\sigma_x\sigma_y+c_2}{\sigma_x^2+\sigma_y^2+c_2}
c(x,y)=σx2+σy2+c22σxσy+c2
structure计算公式为:
s
(
x
,
y
)
=
σ
x
y
+
c
3
σ
x
σ
y
+
c
3
s(x,y)=\frac{\sigma_{xy}+c_3}{\sigma_x\sigma_y+c_3}
s(x,y)=σxσy+c3σxy+c3
其中
μ
x
\mu_x
μx和
μ
y
\mu_y
μy依次表示
x
x
x和
y
y
y的均值,
σ
x
\sigma_x
σx和
σ
y
\sigma_y
σy依次表示
x
x
x和
y
y
y的方差,
σ
x
y
\sigma_{xy}
σxy表示
x
x
x和
y
y
y之间的协方差,
c
1
=
(
k
1
L
)
2
c_1=(k_1L)^2
c1=(k1L)2、
c
2
=
(
k
2
L
)
2
c_2=(k_2L)^2
c2=(k2L)2以及
c
3
=
c
2
/
2
c_3=c_2/2
c3=c2/2,表示三个常数,避免分母为0,
k
1
k_1
k1与
k
2
k_2
k2依次默认为
0.01
0.01
0.01和
0.03
0.03
0.03,
L
L
L表示图像像素值的范围,即
2
B
−
1
2^B-1
2B−1。
最后SSIM的计算公式为:
S
S
I
M
(
x
,
y
)
=
[
l
(
x
,
y
)
α
⋅
c
(
x
,
y
)
β
⋅
s
(
x
,
y
)
γ
]
SSIM(x,y)=[l(x,y)^{\alpha}·c(x,y)^{\beta}·s(x,y)^{\gamma}]
SSIM(x,y)=[l(x,y)α⋅c(x,y)β⋅s(x,y)γ]
如果令
α
,
β
,
γ
\alpha, \beta, \gamma
α,β,γ均为1,则得到常用的SSIM计算公式:
S
S
I
M
(
x
,
y
)
=
(
2
μ
x
μ
y
+
c
1
)
(
2
σ
x
y
+
c
2
)
(
μ
x
2
+
μ
y
2
+
c
1
)
(
σ
x
2
+
σ
y
2
+
c
2
)
SSIM(x,y)=\frac{(2\mu_x\mu_y+c_1)(2\sigma_{xy}+c_2)}{(\mu_x^2+\mu_y^2+c_1)(\sigma_x^2+\sigma_y^2+c_2)}
SSIM(x,y)=(μx2+μy2+c1)(σx2+σy2+c2)(2μxμy+c1)(2σxy+c2)
代码实现
- 参考论文《Spatial Attentive Single-Image Deraining with a High Quality Real Rain Dataset》的源码:/stevewongv/SPANet
def gaussian(window_size, sigma):
# 计算公式:e^(-x^2)/(2*sigma^2),其中x表示距离中心点的距离,sigma默认1.5
gauss = torch.Tensor([exp(-(x - window_size // 2) ** 2 / float(2 * sigma ** 2)) for x in range(window_size)])
# 数据归一化
return gauss / gauss.sum()
# 计算滑动窗口权重
def create_window(window_size, channel):
# 利用滑动窗口尺寸先计算一个一维,并且服从正态分布的数据
# 注意这里利用unsqueeze函数扩了一下维度,从行向量变为了列向量
_1D_window = gaussian(window_size, 1.5).unsqueeze(1)
# 列向量乘以行向量,变为n*n的矩阵,正好对应窗口权重
_2D_window = _1D_window.mm(_1D_window.t()).float().unsqueeze(0).unsqueeze(0)
# 沿通道维度复制channel遍,每个通道对应一个权重(这里所有通道权重相同,均服从正态分布),并且变为连续存储的数据
window = Variable(_2D_window.expand(channel, 1, window_size, window_size).contiguous())
# 返回窗口权重数据
return window
def _ssim(img1, img2, window, window_size, channel, size_average=True):
# 计算每个滑动窗口的均值
# 卷积运算正好是窗口数据按权重求和再取均值,因此可以利用二维卷积运算来计算窗口中数据的均值
mu1 = F.conv2d(img1, window, padding=window_size // 2, groups=channel)
mu2 = F.conv2d(img2, window, padding=window_size // 2, groups=channel)
# 均值取平方,即E^2(X)
mu1_sq = mu1.pow(2)
mu2_sq = mu2.pow(2)
# 计算E(X)E(Y),用于后续计算协方差
mu1_mu2 = mu1 * mu2
# 依次计算img1与img2的方差
# 这里计算方差利用公式D(X)=E(X^2)-E^2(X),其中E^2(X)表示均值的平方,即上述公式中的mu1_sq、mu2_sq、mu1_mu2
sigma1_sq = F.conv2d(img1 * img1, window, padding=window_size // 2, groups=channel) - mu1_sq
sigma2_sq = F.conv2d(img2 * img2, window, padding=window_size // 2, groups=channel) - mu2_sq
# 计算img1、img2之间的协方差
# 利用公式Conv(X,Y)=E(XY)-E(X)E(Y)
sigma12 = F.conv2d(img1 * img2, window, padding=window_size // 2, groups=channel) - mu1_mu2
C1 = 0.01 ** 2
C2 = 0.03 ** 2
# 利用上述得到的指标,传入公式计算ssim值,此时会得到一张图,最后再求均值即可
ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2))
if size_average:
return ssim_map.mean()
else:
return ssim_map.mean(1).mean(1).mean(1)
def ssim(img1, img2, window_size=11, size_average=True):
# 将输入的图像数据限制为0-1之间(一般数据就是位于0-1之间,防止出现异常值)
img1 = torch.clamp(img1, min=0, max=1)
img2 = torch.clamp(img2, min=0, max=1)
# 得到图片通道数
(_, channel, _, _) = img1.size()
# 得到窗口的权重数据,离窗口中心越远,权重越小。权重服从高斯分布(正态分布)
window = create_window(window_size, channel)
# 如果图片数据储存在cuda上(即利用显卡训练),则将窗口权重数据也传入cuda中
if img1.is_cuda:
window = window.cuda(img1.get_device())
# 统一数据类型
window = window.type_as(img1)
# 调用_ssim,计算ssim值
return _ssim(img1, img2, window, window_size, channel, size_average)
直接调用skimage库中的函数
from skimage.metrics import structural_similarity
# im1, im2分别表示参与计算的图像数据
# data_range表示图像数据的范围,一般设置为255或者1(如果对图像数据做了归一化操作,则为1)
# channel_axis表示颜色通道位于图像的第几维度,如果不指定的话,则默认输入灰度图像
ssim = structural_similarity(im1, im2, win_size=None, gradient=False, data_range=None,
channel_axis=None, multichannel=False, gaussian_weights=False, full=False)
以上仅是笔者个人见解,若有问题,欢迎指正。