摄影是全世界数百万人最喜爱的爱好。毕竟,这有多难啊!用美国著名摄影师阿巴斯•黛安娜的话来说:

拍照就像深夜踮着脚尖走进厨房,偷奥利奥饼干。

拍照很容易,但是拍一张高质量的照片却很难。它需要良好的组成和照明。正确的镜头和优越的设备可以带来很大的不同。但最重要的是,一张高质量的照片需要良好的品味和判断力。但是,是否有一种数学质量度量来捕捉人类的判断呢?答案只能为是或者否。

对于算法来说,有一些质量度量是很容易捕捉到的。例如,我们可以查看像素捕获的信息,并将图像标记为噪声或模糊。但是另一方面,一些质量度量几乎不可能被算法捕捉到。例如,算法很难评估需要有文化背景的图片质量。在这篇文章中,我们将学习一种预测图像质量分数的算法。

1 介绍

1.1 什么是图像质量评估Image Quality Assessment (IQA)?

图像质量评价(IQA)算法以任意图像作为输入,输出质量分数作为输出。有三种类型的IQA:

  1. 全参考图像质量评价

    适用情形:您有一个“干净”参考(非扭曲)图像,以衡量您的扭曲图像的质量。此度量可用于评估图像压缩算法的质量,在该算法中,我们可以访问原始图像及其压缩版本。
  2. 半参考图像质量评价

    适用情形:如果没有参考图像,而是具有一些选择性信息的图像(例如,水印图像)来比较和测量失真图像的质量。
  3. 无参考图像质量评价

    适用情形:算法得到的唯一输入是要测量其质量的图像

1.2 无参考图像质量评价

在这篇文章中,我们将讨论一个无参考图像质量评价的IQA度量算法,称为盲/无参考图像空间质量评估器Blind/Referenceless Image Spatial Quality Evaluator (BRISQUE)。在深入研究这个理论之前,让我们先了解两个基本术语:

  1. 失真图像(扭曲图像)

    顾名思义,失真图像是指被模糊、噪声、水印、颜色变换、几何变换等因素扭曲的原始图像。下图是TID 2008数据库中使用的图像失真情况。

  2. 自然图像

    自然图像是指由相机直接拍摄没有后期处理的图像。以下自然图像和失真图像的示例。左图是自然图像,右图是失真图像。

正如你所能想象的,图像清晰度与图像是失真的还是自然的没有关系。例如,当视频被巧妙地用运动模糊渲染时,由于故意模糊,算法可能会对其质量产生混淆。因此,我们必须在正确的背景下使用这种正确的图像质量评价方法来度量图像。

1.3 图像质量评估(IQA)数据集

图像质量好于坏是一个主观问题。为了获得一个优秀的图像质量评估算法,我们需要给出许多图像的算法示例和它们的质量分数。谁为这些训练图像给定质量得分?当然是人类。但我们不能仅仅依靠一个人的意见。因此,我们需要综合评估个人的意见,并为图像分配0(最佳)和100(最差)之间的平均分数。该分数在学术文献中称为平均质量分数。幸运的是我们不需要自己收集这些数据,有一个数据集称为TID 2008已经被用于研究目的。有关TID2008的信息见:

https://pdfs.semanticscholar.org/6dd3/7b57f3391b438fa588f98a1c2067365ae5ca.pdf

如下图所示,TID2008图像质量得分范围为0到100,得分越小,主观质量越好。

2 盲/无参考图像空间质量评估器(BRISQUE)

下图给出了计算BRISQUE所涉及的步骤

主要为三步:

  1. 提取自然场景统计(NSS)
  2. 计算特征向量
  3. 预测图像质量得分

2.1 提取自然场景统计(NSS)

自然图像的像素强度的分布不同于失真图像的像素强度的分布。当归一化像素强度并计算这些归一化强度上的分布时,这种分布差异更加明显。特别是经过归一化后的自然图像像素强度服从高斯分布(Bell曲线),而非自然图像或畸变图像的像素强度不服从高斯分布。因此,通过像素高斯分布曲线是衡量图像失真的一种方法。我们已在下图中说明了这一点。



上图左图显示了一幅有添加人工效应的自然图像,符合高斯分布。右边为一个人造图像,但不适合同样的分布。

2.1.1 MSCN(Mean Subtracted Contrast Normalized)系数

有几种不同的方法来规范化图像。一种这样的归一化称为MSCN。下图显示了如何计算MSCN系数。

上图MSCN计算过程能够可视化为:

下面要讲述详细的数学推导,但不要让下面的数学吓倒你。数学之后的代码要简单得多,容易理解!

有关高斯模糊相关概念解释见:

https://www.jianshu.com/p/302a895c12dd

我们在C++和Python中使用GaussianBlur函数计算MSCN系数,如下所示:

C++

Mat im = imread("image_scenery.jpg"); // read image
cvtColor(im, im, COLOR_BGR2GRAY); // convert to grayscale im.convertTo(im, 1.0/255); // normalize and copy the read image to orig_bw
Mat mu(im.size(), CV_64FC1, 1);
GaussianBlur(im, mu, Size(7, 7), 1.166); // apply gaussian blur
Mat mu_sq = mu.mul(mu); // compute sigma
Mat sigma = im.size(), CV_64FC1, 1);
sigma = im.mul(im);
GaussianBlur(sigma, sigma, Size(7, 7), 1.166); // apply gaussian blur
subtract(sigma, mu_sq, sigma); // sigma = sigma - mu_sq
cv::pow(sigma, double(0.5), sigma); // sigma = sqrt(sigma)
add(sigma, Scalar(1.0/255), sigma); // to avoid DivideByZero Exception
Mat structdis(im.size(), CV_64FC1, 1);
subtract(im, mu, structdis); // structdis = im - mu
divide(structdis, sigma, structdis);

Python

im = cv2.imread("image_scenery.jpg", 0) # read as gray scale
blurred = cv2.GaussianBlur(im, (7, 7), 1.166) # apply gaussian blur to the image
blurred_sq = blurred * blurred
sigma = cv2.GaussianBlur(im * im, (7, 7), 1.166)
sigma = (sigma - blurred_sq) ** 0.5
sigma = sigma + 1.0/255 # to make sure the denominator doesn't give DivideByZero Exception
structdis = (im - blurred)/sigma # final MSCN(i, j) image

2.1.2 相邻像素间的乘积关系

MSCN为像素强度提供了不错的归一化。然而自然退休与失真图像的差异不仅限于像素强度分布,还包括相邻像素之间的关系。为了捕获相邻像素的关系,在四个方向来求出相邻元素的两两乘积,即:水平(H),垂直(V),左对角线(D1),右对角线(D2)。

可以用Python和C++计算两两乘积,如下所示:

C++

// declare shifting indices array
int shifts[4][2] = { {0, 1}, {1, 0}, {1, 1}, {-1, 1} }; // calculate pair-wise products for every combination of shifting indices
for(int itr_shift = 1; itr_shift <= 4; itr_shift++)
{
int* reqshift = shifts[itr_shift - 1]; // the required shift index
// declare shifted image
Mat shifted_structdis(imdist_scaled.size(), CV_64F, 1);
// BwImage is a helper class to create a copy of the image and create helper functions to access it's pixel values
BwImage OrigArr(structdis);
BwImage ShiftArr(shifted_structdis);
// calculate pair-wise component for the given orientation
for(int i = 0; i < structdis.rows; i++)
{
for(int j = 0; j < structdis.cols; j++) { if(i + reqshift[0] >= 0 && i + reqshift[0] < structdis.rows && j + reqshift[1] >= 0 && j + reqshift[1] < structdis.cols)
{
ShiftArr[i][j] = OrigArr[i + reqshift[0]][j + reqshift[1]];
}f
else
{
ShiftArr[i][j] = 0;
}
}
}
Mat shifted_new_structdis;
shifted_new_structdis = ShiftArr.equate(shifted_new_structdis);
// find the pairwise product
multiply(structdis, shifted_new_structdis, shifted_new_structdis);
}

Python

# indices to calculate pair-wise products (H, V, D1, D2)
shifts = [[0,1], [1,0], [1,1], [-1,1]]
# calculate pairwise components in each orientation
for itr_shift in range(1, len(shifts) + 1):
OrigArr = structdis
reqshift = shifts[itr_shift-1] # shifting index for i in range(structdis.shape[0]):
for j in range(structdis.shape[1]):
if(i + reqshift[0] >= 0 and i + reqshift[0] < structdis.shape[0] \ and j + reqshift[1] >= 0 and j + reqshift[1] < structdis.shape[1]):
ShiftArr[i, j] = OrigArr[i + reqshift[0], j + reqshift[1]]
else:
ShiftArr[i, j] = 0

可以使用cv2.warpAffine方法将两个for循环简化为几行代码。这将大大加快计算速度。

# create affine matrix (to shift the image)
M = np.float32([[1, 0, reqshift[1]], [0, 1, reqshift[0]]])
ShiftArr = cv2.warpAffine(OrigArr, M, (structdis.shape[1], structdis.shape[0])

2.2 计算特征向量

到目前为止,我们已经从原始图像派生了5个图像参数,即1个MSCN图像和4个两两乘积图像,以捕获相邻关系(水平、垂直、左对角、右对角)。接下来,我们将使用以上5个参数来计算尺寸为36×1的特征向量。请注意,原始输入图像可以是任何尺寸(宽度/高度),但特征向量的大小始终为36×1。

通过将MSCN图像拟合到广义高斯分布(GGD)来计算36×1特征向量的前两个元素。GGD有两个参数-一个用于形状,另一个用于方差。

接下来,用非对称广义高斯分布(AGGD)拟合相邻元素乘积参数中的任何一个。AGGD是广义高斯拟合(GGD)的不对称形式。它有四个参数,即形状,均值,左方差和右方差。由于有4个两两相乘图像参数,我们最终得到16个值。

因此,我们最终得到了特征向量的18个元素。图像缩小到原来大小的一半,并重复相同的过程以获得18个新的数字,使总数达到36个。下表概述了这一点。

特征范围 功能描述 程序
1-2 形状和方差 GGD拟合MSCN参数
3-6 形状,均值,左方差,右方差 AGGD拟合水平参数
7-10 形状,均值,左方差,右方差 AGGD拟合垂直参数
11-14 形状,均值,左方差,右方差 AGGD拟合对角线(左)参数
15-18 形状,均值,左方差,右方差 AGGD拟合对角线(右)参数

2.3 图像质量评分的预测

在典型的机器学习应用程序中,首先将图像转换为特征向量。然后将训练数据集中所有图像的特征向量和输出(在这种情况下是质量分数)输入到支持向量机(SVM)等学习算法中。可以下载tid2008数据并且训练一个支持向量机来解决这个问题,但是在这篇文章中,我们将简单地使用作者提供的经过训练的模型。

下载地址:

http://www.ponomarenko.info/tid2008.htm

首先加载训练后的模型,然后利用模型产生的支持向量预测概率,利用LIBSVM预测最终的质量分数。需要注意的是,特征向量首先被缩放为-1到1,然后用于预测。我们共享Python和C++实现这一点的方法:

3 结果和代码

3.1 结果

我们对四种类型的失真进行了度量。以下是理论上每个失真的最终质量分数。但是这个仅仅是理论上的,实际上效果在不同电脑和不同图像分辨率上并不是这样的。对于图像压缩结果可能大相径庭。不过总的来说BRISQUE效果不错,但是精度不够,主要是svm还需要自己训练。

原始图像 JPEG2K压缩 重压缩 高斯噪声 中位数模糊
26.8286 30.7417 33.0692 79.8751 72.7044

3.2 代码

OpenCV4.1版本的扩展模块已经有BRISQUE的实现代码,具体可以见官方文档。对于自己编写代码,需要引入libsvm,但是实现起来也不难。具体应用如下:

C++将libsvm与代码文件放到同一目录下,并导入libsvm中的svm.h文件和svm.cpp文件

其中computescore用于计算brisque得分,brisque用于提取特征。

此外在文件目录下还有allmodel文件和allrange文件,allmodel文件为svm模型文件,allrange为训练数据的图像特征相关信息文件。

对于python,运行brisquequality.py文件就行了,但是要注意libsvm需要和brisquequality.py在同一目录下。

全部代码就不在这里展示,所有代码见:

https://github.com/luohenyueji/OpenCV-Practical-Exercise

4 参考

https://www.learnopencv.com/image-quality-assessment-brisque/

https://blog.csdn.net/weixin_34265814/article/details/94455570

[OpenCV实战]37 图像质量评价BRISQUE的更多相关文章

  1. [OpenCV实战]48 基于OpenCV实现图像质量评价

    本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价.图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏. 本文 ...

  2. 图像质量评价方法PSNR+SSIM&&评估指标SROCC,PLCC

    update:2018-04-07 今天发现ssim的计算里面有高斯模糊,为了快速计算,先对每个小块进行计算,然后计算所有块的平均值.可以参考源代码实现,而且代码实现有近似的在里面!matlab中中图 ...

  3. OpenCV实战:人脸关键点检测(FaceMark)

    Summary:利用OpenCV中的LBF算法进行人脸关键点检测(Facial Landmark Detection) Author:    Amusi Date:       2018-03-20 ...

  4. 图像质量评价-NQM和WPSNR

    王保全. 基于混合专家模型的快速图像超分辨率方法研究与实现[D]. 2015. PSNR 和SSIM 在有时候并不能很确切的表示图像质量 标准,该论文中根据一定量的人为的感知评分作为参考,用斯皮尔曼等 ...

  5. [OpenCV实战]50 用OpenCV制作低成本立体相机

    本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...

  6. [OpenCV实战]49 对极几何与立体视觉初探

    本文主要介绍对极几何(Epipolar Geometry)与立体视觉(Stereo Vision)的相关知识.对极几何简单点来说,其目的就是描述是两幅视图之间的内部对应关系,用来对立体视觉进行建模,实 ...

  7. [OpenCV实战]44 使用OpenCV进行图像超分放大

    图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像.图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析.生物识别.视频监 ...

  8. opencv实战——图像矫正算法深入探讨

    摘要 在机器视觉中,对于图像的处理有时候因为放置的原因导致ROI区域倾斜,这个时候我们会想办法把它纠正为正确的角度视角来,方便下一步的布局分析与文字识别,这个时候通过透视变换就可以取得比较好的裁剪效果 ...

  9. [OpenCV实战]45 基于OpenCV实现图像哈希算法

    目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash).图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅 ...

随机推荐

  1. 自主创建mybtis管理应用,用以横向管理数据源

    这个是我写的第一个随手小记,一晃眼做后端开发也有7年多了,现在也准备将一些杂七杂八的资料整理下.也算是回顾这7年中做的比较有意思的东西了. 这个需求是我17年做的,当时的应用场景是仓储库比较多,随时会 ...

  2. session保存作用域

    session保存作用域,作用范围是一次会话. Session的工作机制 当服务器端调用了request.getSession()方法 检查当前请求中是否携带了JSESSIONID这个Cookie 有 ...

  3. 1.ElasticSearch系列之集群部署

    第一步:安装JDK JDK要求jdk1.8+,不安装也可以,ES自带JDK 第二步:系统配置 2.1 禁用交换区 sudo swapoff -a 2.2 开最大文件数的限制 编辑文件 /etc/sec ...

  4. 前后端代码分离开发(Vue)

  5. 齐博x1标签实例:做模板组图单图无图混排的处理

    代码如下, {qb:tag name="xxx" type="cms" rows="10"} {if ( count($rs['picurl ...

  6. 复杂场景数据处理的 OLTP 与 OLAP 融合实践

    本文首发于 NebulaGraph 公众号 Dag Controller 介绍 Dag Controller 是 NebulaGraph 企业版的系统,经过反复测试无误后进行了发布,它主要解决的是 O ...

  7. 你应该知道的数仓安全:都是同名Schema惹的祸

    摘要:我是管理员账号,怎么还没有权限?当小伙伴询问的时候,我第一时间就会想到都是用户同名Schema惹的祸 本文分享自华为云社区<你应该知道的数仓安全--都是同名Schema惹的祸>,作者 ...

  8. Pycharm自定义实时模板

    pycharm添加模板 添加装饰器模板 # 1.file-->Setting-->Editor-->Code Style -->Live Templates# 2." ...

  9. 最长不下降子序列(线段树优化dp)

    最长不下降子序列 题目大意: 给定一个长度为 N 的整数序列:A\(_{1}\),A\(_{2}\),⋅⋅⋅,A\(_{N}\). 现在你有一次机会,将其中连续的 K 个数修改成任意一个相同值. 请你 ...

  10. 关于Docker的一些事--Docker-Compose 升级版本

    起源 近来一直在研究怎么搭建自己的私有网盘,本着虚心耐心,认真求是态度,开始做起了实验,最终种草了Nextcloud这款开源网盘,然而用私人的服务器感觉很卡,故转战到了一个基友的服务器,感觉非常吊! ...