由于最近在阅读图像超分辨率方面的RCAN论文,里面涉及到了两幅图像之间的相似性,所以就引入了这个指标,并最终使用pyhton进行实现。结构相似性,是一种衡量两幅图像相似度的指标。该指标首先由德州大学奥斯丁分校的图像和视频工程实验室(Laboratory for Image and Video Engineering)提出。而如果两幅图像是压缩前和压缩后的图像,那么SSIM算法就可以用来评估压缩后的图像质量。本文着重在于代码实现SSIM函数,而该算法的原理以及为什么需要高斯核函数作为模板的权值并不关心,如果需要的话可以在文章的后面大的连接里面自行阅读。
在计算两幅图像的结构相似性,需要高斯模板用来作为一个像素区域的概率值,进而计算得到某个区域的像素平均值,标准差,方差和协方差等概念。除此之外给出一组公式用来计算SSIM:
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+C1} L(X,Y)=μX2+μY2+C12μXμY+C1
C ( X , Y ) = 2 σ X σ Y + C 2 σ 2 2 + σ Y 2 + C 2 C(X,Y) = \frac{2\sigma_X\sigma_Y+C_2}{\sigma_2^2+\sigma_Y^2+C_2} C(X,Y)=σ22+σY2+C22σXσY+C2
S ( X , Y ) = σ X Y + C 3 σ X σ Y + C 3 S(X,Y) = \frac{\sigma_{XY+C_3}}{\sigma_X\sigma_Y+C3} S(X,Y)=σXσY+C3σXY+C3
当里面的参数 C 3 = C 2 / 2 C_3 = C_2/2 C3=C2/2时 S S I M ( X , Y ) 也 可 以 写 成 下 面 的 式 子 SSIM(X,Y)也可以写成下面的式子 SSIM(X,Y)也可以写成下面的式子
S S I M ( X , Y ) = L ( X , Y ) C ( X , Y ) S ( X , Y ) = 2 μ X μ Y + C 1 μ X 2 + μ Y 2 + C 1 ∗ 2 σ X Y + C 2 σ 2 2 + σ Y 2 + C 2 SSIM(X,Y) = L(X,Y)C(X,Y)S(X,Y)=\frac{2\mu_X \mu_Y+C_1}{\mu_X^2 + \mu_Y^2+C1}* \frac{2\sigma_{XY}+C_2}{\sigma_2^2+\sigma_Y^2+C_2} SSIM(X,Y)=L(X,Y)C(X,Y)S(X,Y)=μX2+μY2+C12μXμY+C1∗σ22+σY2+C22σXY+C2
里面的 μ X 、 σ X \mu_X、\sigma_X μX、σX分别代表X这幅图片里面的均值。
下面用图示的方式演示涌口滑动的过程:
上图的左边是高斯核尺寸是 ( 2 k + 1 ) ∗ ( 2 k + 1 ) (2k+1)*(2k+1) (2k+1)∗(2k+1),加入我们将要从图片的第 ( i , j ) (i,j) (i,j)个位置与高斯核进行运算(如果熟悉卷积神经网络的话大家肯定就很快就可以理解)。然后我们使用高斯模块在整个图像上移动得到一个SSIM_MAP,其尺寸就是 ( w − 2 k ) ∗ ( d − 2 k ) (w-2k)*(d-2k) (w−2k)∗(d−2k)。
按照前人的做法是使用一个 ( 2 k + 1 ) ∗ ( 2 k + 1 ) (2k+1)*(2k+1) (2k+1)∗(2k+1)的高斯模板滑动整个图像得到一个SSIM_MAP之后再计算其平均值作为两幅图像的SSIM。这里需要强调的是区域里面的每个像素出现的概率是符合高斯分布的,而不是均匀分布。故我们首先要设计一个高斯核函数确定权重,计算公式如下:
K ( i , j ) = 1 2 π σ 2 e x p − ( i − k ) 2 + ( j − k ) 2 2 π σ 2 , 0 ≤ i , j ≤ 2 k K(i,j) = \frac{1}{2\pi\sigma^2}exp^{-\frac{(i-k)^2+(j-k)^2}{2\pi\sigma^2}}, 0 \le i,j \le2k K(i,j)=2πσ21exp−2πσ2(i−k)2+(j−k)2,0≤i,j≤2k
由于区域里面的每个点具有不同的权重,在这里我们认为从图片的 ( i , j ) (i,j) (i,j)点开始与高斯核匹配的的平均值是:
μ X ( i , j ) = Σ m = 0 m = 2 k Σ n = 0 n = 2 k k ( m , n ) X ( i + m , j + n ) \mu_X(i,j) = \Sigma_{m=0}^{m=2k}\Sigma_{n=0}^{n=2k}k(m,n)X(i+m,j+n) μX(i,j)=Σm=0m=2kΣn=0n=2kk(m,n)X(i+m,j+n)
根据方差的公式我们写作:
σ X 2 ( i , j ) = Σ m = 0 m = 2 k Σ n = 0 n = 2 k k ( m , n ) ( X ( i + m , j + n ) − μ X ( i , j ) ) 2 = Σ m = 0 m = 2 k Σ n = 0 n = 2 k k ( m , n ) ( X ( i + m , j + n ) 2 − 2 X ( i + m , j + n ) μ X ( i , j ) 2 + μ X 2 ( i , j ) = Σ m = 0 m = 2 k Σ n = 0 n = 2 k k ( m , n ) X ( i + m , j + n ) 2 − μ X 2 ( i , j ) \sigma_X^2(i,j) = \Sigma_{m=0}^{m=2k}\Sigma_{n=0}^{n=2k}k(m,n)(X(i+m,j+n)-\mu_X(i,j))^2 \\=\Sigma_{m=0}^{m=2k}\Sigma_{n=0}^{n=2k}k(m,n)(X(i+m,j+n)^2-2X(i+m,j+n)\mu_X(i,j)^2+\mu_X^2(i,j) \\=\Sigma_{m=0}^{m=2k}\Sigma_{n=0}^{n=2k}k(m,n)X(i+m,j+n)^2-\mu_X^2(i,j) σX2(i,j)=Σm=0m=2kΣn=0n=2kk(m,n)(X(i+m,j+n)−μX(i,j))2=Σm=0m=2kΣn=0n=2kk(m,n)(X(i+m,j+n)2−2X(i+m,j+n)μX(i,j)2+μX2(i,j)=Σm=0m=2kΣn=0n=2kk(m,n)X(i+m,j+n)2−μX2(i,j)
协方差公式:
σ X , Y = Σ m = 0 m = 2 k Σ n = 0 n = 2 k k ( m , n ) ( X ( i + m , j + n ) − μ X ( i , j ) ) ( Y ( i + m , j + n ) − μ Y ( i , j ) ) = Σ m = 0 m = 2 k Σ n = 0 n = 2 k k ( m , n ) X ( i + m , j + n ) Y ( i + m , j + n ) − μ X ( i , j ) μ Y ( i , j ) \sigma_{X,Y} = \Sigma_{m=0}^{m=2k}\Sigma_{n=0}^{n=2k}k(m,n)(X(i+m,j+n)-\mu_X(i,j))(Y(i+m,j+n)-\mu_Y(i,j))\\ = \Sigma_{m=0}^{m=2k}\Sigma_{n=0}^{n=2k}k(m,n)X(i+m,j+n)Y(i+m,j+n) - \mu_X(i,j)\mu_Y(i,j) σX,Y=Σm=0m=2kΣn=0n=2kk(m,n)(X(i+m,j+n)−μX(i,j))(Y(i+m,j+n)−μY(i,j))=Σm=0m=2kΣn=0n=2kk(m,n)X(i+m,j+n)Y(i+m,j+n)−μX(i,j)μY(i,j)
由于上面的几部分已经将SSIM公式里面的参数计算出来了,下面就是我基于自己的理解编写出来的代码:
import numpy as np
import cv2
def getres(img, kernel, i,j):
k_w, k_d = kernel.shape
res = np.sum(img[i:i+k_w, j:j+k_d]*kernel)
return res
def conv2d(img, kernel):
img_w, img_d = img.shape
k_w, k_d = kernel.shape
res = [[getres(img, kernel, i,j) for j in range(img_d-k_d+1)] for i in range(img_w-k_w+1)]
return np.array(res, dtype=np.float32)
def ssim(img1=None, img2=None, k=11, sigma=1.5): #输入的图片是三通道的rgb图片格式为uint8
if img1.shape != img2.shape:
print("the shape of the two image is different, please check!!!")
return None
if k <= 5 and k%2 == 0:
print("the kernel size is small or odd!!!")
return None
if sigma <= 0:
print("the sigma can not be 0 or less 0!!!")
return None
img1 = cv2.cvtColor(img1, cv2.COLOR_RGB2YCR_CB)[:,:,0].astype(np.float32)
img2 = cv2.cvtColor(img2, cv2.COLOR_RGB2YCR_CB)[:,:,0].astype(np.float32)
c1 = (0.01*255)**2
c2 = (0.03*255)**2
filter = [[ ((i-k//2)*(i-k//2) + (j-k//2)*(j-k//2))/(-2*sigma*sigma) for j in range(k)] for i in range(k)]
filter = np.array(np.exp(filter), dtype=np.float32)
filter = filter / np.sum(filter)
mu1 = conv2d(img1, filter)
mu2 = conv2d(img2, filter)
mu1_sq = mu1 * mu1
mu2_sq = mu2 * mu2
mu1_mu2 = mu1 * mu2
sigma1_sq = conv2d(img1*img1, filter) - mu1_sq
sigma2_sq = conv2d(img2*img2, filter) - mu2_sq
sigma12 = conv2d(img1*img2, filter) - mu1_mu2
ssim_map = (2*mu1_mu2 +c1)*(2*sigma12+c2) / (mu1_sq + mu2_sq + c2) / (sigma1_sq + sigma2_sq + c2)
return np.mean(ssim_map)
如果大家只是为了使用函数得到两幅图像之间的SSIM,那么简单来说就是先读取图像,并且将图片的通道转化为RGB模式,这个原因是因为我在代码里面将图片转化为YCBCR并且仅计算Y通道上的SSIM。这里的计算方式是参照RCAN论文所述。
#首先读取两张图片,因为opencv是按照BGR读入的所以我就先进行了color的cvt。
#要是引用的该函数的话只用调用ssim(x,y)即可,里面的其他变量我已经在函数里面设置好了。
#可以更改的就是高斯核的大小即参数k, 已经高斯核的标准差sigma
img1 = cv2.imread("D:/microsoft/RCAN-master/RCAN_TestCode/HR/B100/x4/3096_HR_x4.png")
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
img2 = img1
ssim(img1, img2)
图像质量评估算法 SSIM(结构相似性)
今天终于弄清楚了fspecial(‘gaussian’,3,1.8)怎么算的了
Residual Channel Attention Networks即RCAN差分辨率网络
SSIM百度百科