图像无参考评价模型BRISQUE原理简介以及python实现

引言

自然场景统计(NSS)经常被应用于图像的质量评价。人们发现,用高质量设备采集的自然图像(自然场景)有着一定的统计特征(如服从一些类高斯分布),而图像的失真会使这些统计特征发生改变,因此利用图像的一些统计特征作为图像特征,可以完成图像的无参考评价。
BRISQUE来自于论文《No-Reference Image Quality Assessment in the Spatial Domain》,是一个经典的利用NSS进行NR-IQA的模型。观察到自然图像局部归一化亮度系数(MSCN),强烈趋向于单位正态高斯特性,因此作者假设失真会改变MSCN的分布状况,并基于此提取特征。

原理简介

1.局部亮度归一化

图像无参考评价模型BRISQUE原理简介以及python实现_第1张图片
如上图左列所示,像素与其周围的像素具有很高的相关性,因为图像函数通常是分段平滑的,我们在左列可以观察到一种对角结构。图像的归一化能够大大减少像素的相关性,如右列所示。
局部亮度归一化过程如下式:
在这里插入图片描述
其中:
图像无参考评价模型BRISQUE原理简介以及python实现_第2张图片
C=1,是为了防止分母为0。根据上面式子可见这其实是z-score归一化过程。

2.利用广义高斯分布拟合MSCN获得特征
获得的MSCN具有一定的统计特征(如类高斯分布),根据下图可以观察到不同失真类型图像和自然图像具有不同分布特征,文献w使用广义高斯分布(GGD)对图像MSCN的分布进行拟合。
图像无参考评价模型BRISQUE原理简介以及python实现_第3张图片
零均值广义高斯分布(GGD)的定义如下:
图像无参考评价模型BRISQUE原理简介以及python实现_第4张图片
利用GGD拟合分布获得的形状参数(α, σ^2 )作为特征1和特征2。关于广义高斯分布的参数拟合方法实现问题,我在另一篇博客单独描述,这里主要介绍IQA模型原理,不在叙述。
3.利用非对称广义高斯分布拟合MSCN相邻系数内积
作者也研究了相邻MSCN系数之间的统计特征,虽然自然图像的MSCN系数比较均匀,但是会随着失真的影响而改变。因此作者在四个不同方向计算相邻系数内积,并利用非对称广义高斯分布(AGGD)拟合分布特征。四个方向的内积定义如下:
图像无参考评价模型BRISQUE原理简介以及python实现_第5张图片
零均值非对称广义高斯分布(AGGD)的定义如下:

图像无参考评价模型BRISQUE原理简介以及python实现_第6张图片
在这里插入图片描述
利用AGGD拟合获得的形状参数(η, ν, σ_l^2 , σ_r^2 )将作为特征3-18(4个方向 x 4个特征)。关于非对称广义高斯分布的参数拟合方法实现问题,我亦在另一篇博客单独描述,这里主要介绍IQA模型原理,不在叙述。

至此,对图像的18个特征提取就完成了,具体的特征内容如下表。需要指出的是,文献对图像进行了尺度为2的下采样,然后由提取了18个特征,因此共36个特征。
图像无参考评价模型BRISQUE原理简介以及python实现_第7张图片

代码实现

文献的作者是给出MATLAB代码的,地址如下:
http://live.ece.utexas.edu/research/quality/BRISQUE_release.zip
一般用迅雷就能下下来。我也存到网盘上了,下不了的可以用网盘:
链接:https://pan.baidu.com/s/1RxlaOAlGGMLWZV7X7sTqOQ
提取码:bab3
最近发现作者也更新了一个C++版本的代码:
http://live.ece.utexas.edu/research/Quality/brisque_revised.zip
网盘链接:https://pan.baidu.com/s/1Ei204UN3xBWMTWS0Zakkig
提取码:zt71
本文的python实现:
链接:https://pan.baidu.com/s/1ZocxCf6Gudzbmo1pRDtMBg
提取码:s0vw
现给出python实现代码:

import numpy as np
import cv2
from scipy.special import gamma
#产生二维高斯核函数
#这个函数参考自:https://blog.csdn.net/qq_16013649/article/details/78784791
def gaussian_2d_kernel(kernel_size, sigma):
    kernel = np.zeros((kernel_size, kernel_size))
    center = kernel_size // 2
    if sigma == 0:
        sigma = ((kernel_size - 1) * 0.5 - 1) * 0.3 + 0.8
    s = 2 * (sigma ** 2)
    sum_val = 0
    for i in range(0, kernel_size):
        for j in range(0, kernel_size):
            x = i - center
            y = j - center
            kernel[i, j] = np.exp(-(x ** 2 + y ** 2) / s)
            sum_val += kernel[i, j]
    sum_val = 1 / sum_val
    return kernel * sum_val

#相关操作
def correlation(img,kernal):
    kernal_heigh = kernal.shape[0]
    kernal_width = kernal.shape[1]
    h = kernal_heigh // 2
    w = kernal_width // 2
    # 边界补全
    img = np.pad(img, ((h, h), (w, w)), 'constant')
    cor_heigh = img.shape[0] - kernal_heigh + 1
    cor_width = img.shape[1] - kernal_width + 1
    result = np.zeros((cor_heigh, cor_width), dtype=np.float64)
    for i in range(cor_heigh):
        for j in range(cor_width):
            result[i][j] = (img[i:i + kernal_heigh, j:j + kernal_width] * kernal).sum()
    return result

def estimate_GGD_parameters(vec):
    gam =np.arange(0.2,10.0,0.001)#产生候选的γ
    r_gam = (gamma(1/gam)*gamma(3/gam))/((gamma(2/gam))**2)#根据候选的γ计算r(γ)
    sigma_sq=np.mean((vec)**2)
    E=np.mean(np.abs(vec))
    r=sigma_sq/(E**2)#根据sigma^2和E计算r(γ)
    diff=np.abs(r-r_gam)
    gamma_param=gam[np.argmin(diff, axis=0)]
    return [gamma_param,sigma_sq]

def estimate_AGGD_parameters(vec):
    alpha =np.arange(0.2,10.0,0.001)#产生候选的α
    r_alpha=((gamma(2/alpha))**2)/(gamma(1/alpha)*gamma(3/alpha))#根据候选的γ计算r(α)
    sigma_l=np.sqrt(np.mean(vec[vec<0]**2))
    sigma_r=np.sqrt(np.mean(vec[vec>0]**2))
    gamma_=sigma_l/sigma_r
    u2=np.mean(vec**2)
    m1=np.mean(np.abs(vec))
    r_=m1**2/u2
    R_=r_*(gamma_**3+1)*(gamma_+1)/((gamma_**2+1)**2)
    diff=(R_-r_alpha)**2
    alpha_param=alpha[np.argmin(diff, axis=0)]
    const1 = np.sqrt(gamma(1 / alpha_param) / gamma(3 / alpha_param))
    const2 = gamma(2 / alpha_param) / gamma(1 / alpha_param)
    eta =(sigma_r-sigma_l)*const1*const2
    return [alpha_param,eta,sigma_l**2,sigma_r**2]


def brisque_feature(dis_image):
    dis_image=dis_image.astype(np.float32)#类型转换十分重要
    kernal=gaussian_2d_kernel(7,7/6)
    ux=correlation(dis_image,kernal)
    ux_sq=ux*ux
    sigma=np.sqrt(np.abs(correlation(dis_image**2,kernal)-ux_sq))
    mscn=(dis_image-ux)/(1+sigma)
    f1_2=estimate_GGD_parameters(mscn)
    H=mscn*np.roll(mscn,1,axis=1)
    V=mscn*np.roll(mscn,1,axis=0)
    D1=mscn*np.roll(np.roll(mscn,1,axis=1),1,axis=0)
    D2=mscn*np.roll(np.roll(mscn,-1,axis=1),-1,axis=0)
    f3_6=estimate_AGGD_parameters(H)
    f7_10=estimate_AGGD_parameters(V)
    f11_14=estimate_AGGD_parameters(D1)
    f15_18=estimate_AGGD_parameters(D2)
    return f1_2+f3_6+f7_10+f11_14+f15_18

相关操作和产生高斯核是直接用代码写的,也可以调用python-opencv的相关函数实现。相关操作后的输出图像与输入图像大小相等,因此需要边界补0。
上述为特征f1-f18的提取,提取f1-f36需要进行下采样:

img=cv2.imread(path+str(img_id)+'.bmp',cv2.IMREAD_GRAYSCALE)
feat=brisque_feature(img)
size = img.shape
#下采样
img = cv2.resize(img, (int(size[1] / 2), int(size[0]/ 2)), cv2.INTER_NEAREST)
feat=feat+brisque_feature(img)

与文献w不同的是,这里使用网格搜索寻找SVR的最优参数,SVR使用的sklearn中的模块。
sklearn中的GridSearchCV可以实现网格搜索。

from sklearn.svm import SVR
from sklearn.model_selection import GridSearchCV
#C、γ的搜索范围都是从1e-4到1e4,取9个候选值
parameters={"kernel": ("linear", 'rbf'), "C": np.logspace(-4, 4, 9), "gamma": np.logspace(-4, 4,9)}
svr = GridSearchCV(SVR(), param_grid=parameters,cv=4)#4折交叉验证
svr.fit(feat, score)
print('The parameters of the best model are: ')
print(svr.best_params_)

关于代码第六行的score,是你的训练集的DMOS值,以LIVE数据库为例,一般按照80%—20%划分训练集测试集,训练时通过BRISQUE算法提取训练集的feat,训练集图片对应DMOS作为score。需要注意的是LIVE数据库提供的DMOS值存储在MATLAB格式的.mat文件中,需要使用scipy打开。
使用网格搜索确定的参数训练的SVR效果略优于文献的结果。评估过程与文献相同,采用80%训练数据,20%测试数据,1000次随机训练-测试,利用1000次的中位数作为结果,本文的结果与文献的结果对比如下表:
在这里插入图片描述

你可能感兴趣的:(Image/Video,Quality,Assessment)