Bootstrap

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

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

本文需要OpenCV contrib库,OpenCV contrib库的编译安装见:

OpenCV_contrib库在windows下编译使用指南

本文所有代码见:

OpenCV-Practical-Exercise

1 OpenCV中图像质量评价算法介绍

1.1 相关背景

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

  1. 全参考图像质量评价,适用情形:一个“干净”参考(非扭曲)图像以衡量扭曲图像的质量。此度量可用于评估图像压缩算法的质量。
  2. 半参考图像质量评价,适用情形:如果没有参考图像,而是具有一些选择性信息的图像(例如,水印图像)来比较和测量失真图像的质量。
  3. 无参考图像质量评价,适用情形:算法得到的唯一输入是要测量其质量的图像。

在OpenCV contrib的quality模块中一共有提供了5种图像质量评价算法,按上面的类别分仅提供全参考图像质量评价和无参考图像质量评价两种类别的算法,没有半参考图像质量评价算法。官方代码地址见quality,其中包含的5种图像质量评价算法具体如下:

  • 均方误差 Mean squared error (MSE)
  • 峰值信噪比 Peak signal-to-noise ratio (PSNR)
  • 结构相似性 Structural similarity (SSIM)
  • 梯度幅度相似性偏差 Gradient Magnitude Similarity Deviation (GMSD)
  • 盲/无参考图像空间质量评估器 Blind/Referenceless Image Spatial Quality Evaluation (BRISQUE)

这5种图像质量评价算法中,除了BRISQUE是无参考图像质量评价算法外,其他都是全参考图像质量评价。本文不具体介绍这些算法的原理,仅介绍这些算法的应用。想知道具体原理见链接:

事实上,各种图像质量评估算法都是寻找不同数学公式给出一个评判结果,差异并不那么大,仅知道使用即可。就全参考图像质量评价算法而言,一般情况下GMSD效果比其他全参考图像质量评价算法效果好。无参考图形质量评价以BRISQUE为代表。半参考图像质量评价更多用于发论文,实际应用不多。近年来也有深度学习应用于图像质量评估,但是效果还不错,但速度太慢。关于图像质量评估算法具体进一步研究可参考链接:图像质量评估指标(Image Quality Assessment,IQA)

1.2 OpenCV中图像质量评价算法接口介绍

OpenCV中图像质量评价算法接口分为静态方法和实例方法,静态方法固定快捷,实例方法灵活性强。其中全参考图像质量评价算法接口类似,只需要更改函数名即可,因为各种参考图像质量算法其实都数学公式应用变换数学公式即可。BRISQUE在[OpenCV实战]37 图像质量评价BRISQUE中已经提到如何使用,不过用起来相对opencv_contrib库中的quality模块麻烦,唯一好处[OpenCV实战]37 图像质量评价BRISQUE提到的方法不需要编译opencv_contrib库,但是实际建议使用opencv_contrib库的quality模块来实现图像质量评估算法。

1.2.1 opencv_contrib中全参考图像质量评价算法具体接口

C++/静态方法

// output quality map
// 质量结果图
// 质量结果图quality_map就是检测图像和基准图像各个像素点差值图像
cv::Mat quality_map;
// compute MSE via static method
// cv::noArray() if not interested in output quality maps
// 静态方法,一步到位
// 如果不想获得质量结果图,将quality_map替换为noArray()
cv::Scalar result_static = quality::QualityMSE::compute(img1, img2, quality_map);

C++/实例方法

// alternatively, compute MSE via instance
cv::Ptr<quality::QualityBase> ptr = quality::QualityMSE::create(img1);
// compute MSE, compare img1 vs img2
cv::Scalar result = ptr->compute(img2);
ptr->getQualityMap(quality_map);

Python/静态方法

# 静态方法,一步到位
# 质量结果图quality_map就是检测图像和基准图像各个像素点差值结果
result_static, quality_map = cv2.quality.QualityMSE_compute(img1, img2)

Python/实例方法

obj = cv2.quality.QualityMSE_create(img1)
result = obj.compute(img2)
quality_map = obj.getQualityMap()

1.2.2 opencv_contrib中无参考图像质量评价算法具体接口

C++/静态方法

// path to the trained model
cv::String model_path = "./model/brisque_model_live.yml";
// path to range file
cv::String range_path = "./model/brisque_range_live.yml";
// 静态计算方法
cv::Scalar result_static = quality::QualityBRISQUE::compute(img, model_path, range_path);

C++/实例方法

cv::Ptr<quality::QualityBase> ptr = quality::QualityBRISQUE::create(model_path, range_path);
// computes BRISQUE score for img
cv::Scalar result = ptr->compute(img)

Python/静态方法

# path to the trained model
model_path = "./model/brisque_model_live.yml"
# path to range file
range_path = "./model/brisque_range_live.yml"
# 静态计算方法
result_static = cv2.quality.QualityBRISQUE_compute(img, model_path, range_path)

Python/实例方法

obj = cv2.quality.QualityBRISQUE_create(model_path, range_path)
result = obj.compute(img)

1.2.3 opencv_contrib中图像质量评价算法输出参数介绍

对于静态方法和实例方法输出结果一样的,都是输出在不同颜色通道下的结果,比如对于全参考图像质量评价算法而言RGB图就是分别输出R、G、B三个通道的结果,所以最后需要求均值。对BRISQUE而言不管是彩色图还是灰度图都只输出一个0到100之间的数。各个算法的结果特点如下表所示:

算法输出结果特点
MSE结果越小,检测图像和基准图像的差距越小
PSNR结果越小,检测图像和基准图像的差距越大
GMSD结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越大
SSIM结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越小
BRISQUE结果为一个0到100之间的数,越小表示检测图像质量越好

2 代码实现与结果分析

2.1 代码实现

本文所提供的代码可以对者图像进行质量评价。本文提供C++和Python代码实现,但是MSE的Python实例计算代码可能有问题,可以用Python静态方法代替,所有代码如下:

C++

#include <opencv2/opencv.hpp>
#include <opencv2/quality.hpp>

using namespace std;
using namespace cv;

// 计算结果均值
double calMEAN(Scalar result)
{
	int i = 0;
	double sum = 0;
	// 计算总和
	for (auto val : result.val)
	{
		if (0 == val || isinf(val))
		{
			break;
		}
		sum += val;
		i++;
	}
	return sum / i;
}

// 均方误差 MSE
double MSE(Mat img1, Mat img2)
{
	// output quality map
	// 质量结果图
	// 质量结果图quality_map就是检测图像和基准图像各个像素点差值图像
	cv::Mat quality_map;
	// compute MSE via static method
	// cv::noArray() if not interested in output quality maps
	// 静态方法,一步到位
	// 如果不想获得质量结果图,将quality_map替换为noArray()
	cv::Scalar result_static = quality::QualityMSE::compute(img1, img2, quality_map);

	/* 另外一种动态计算的方法
	// alternatively, compute MSE via instance
	cv::Ptr<quality::QualityBase> ptr = quality::QualityMSE::create(img1);
	// compute MSE, compare img1 vs img2
	cv::Scalar result = ptr->compute(img2);
	ptr->getQualityMap(quality_map);
	*/

	return calMEAN(result_static);
}

// 峰值信噪比 PSNR
double PSNR(Mat img1, Mat img2)
{
	// 质量结果图
	// 质量结果图quality_map就是检测图像和基准图像各个像素点差值图像
	cv::Mat quality_map;
	// 静态方法,一步到位
	// 如果不想获得质量结果图,将quality_map替换为noArray()
	// 第四个参数为PSNR计算公式中的MAX,即图片可能的最大像素值,通常为255
	cv::Scalar result_static = quality::QualityPSNR::compute(img1, img2, quality_map, 255.0);

	/* 另外一种动态计算的方法
	cv::Ptr<quality::QualityBase> ptr = quality::QualityPSNR::create(img1, 255.0);
	cv::Scalar result = ptr->compute(img2);
	ptr->getQualityMap(quality_map);*/

	return calMEAN(result_static);
}

// 梯度幅度相似性偏差 GMSD
double GMSD(Mat img1, Mat img2)
{
	// 质量结果图
	// 质量结果图quality_map就是检测图像和基准图像各个像素点差值图像
	cv::Mat quality_map;
	// 静态方法,一步到位
	// 如果不想获得质量结果图,将quality_map替换为noArray()
	cv::Scalar result_static = quality::QualityGMSD::compute(img1, img2, quality_map);
	/* 另外一种动态计算的方法
	cv::Ptr<quality::QualityBase> ptr = quality::QualityGMSD::create(img1);
	cv::Scalar result = ptr->compute(img2);
	ptr->getQualityMap(quality_map);*/
	return calMEAN(result_static);
}

// 结构相似性 SSIM
double SSIM(Mat img1, Mat img2)
{
	// 质量结果图
	// 质量结果图quality_map就是检测图像和基准图像各个像素点差值图像
	cv::Mat quality_map;
	// 静态方法,一步到位
	// 如果不想获得质量结果图,将quality_map替换为noArray()
	cv::Scalar result_static = quality::QualitySSIM::compute(img1, img2, quality_map);
	/* 另外一种动态计算的方法
	cv::Ptr<quality::QualityBase> ptr = quality::QualitySSIM::create(img1);
	cv::Scalar result = ptr->compute(img2);
	ptr->getQualityMap(quality_map);*/
	return calMEAN(result_static);
}

// 盲/无参考图像空间质量评估器 BRISQUE
double BRISQUE(Mat img)
{
	// path to the trained model
	cv::String model_path = "./model/brisque_model_live.yml";
	// path to range file
	cv::String range_path = "./model/brisque_range_live.yml";
	// 静态计算方法
	cv::Scalar result_static = quality::QualityBRISQUE::compute(img, model_path, range_path);
	/* 另外一种动态计算的方法
	cv::Ptr<quality::QualityBase> ptr = quality::QualityBRISQUE::create(model_path, range_path);
	// computes BRISQUE score for img
	cv::Scalar result = ptr->compute(img);*/
	return calMEAN(result_static);
}

void qualityCompute(String methodType, Mat img1, Mat img2)
{
	// 算法结果和算法耗时
	double result;
	TickMeter costTime;

	costTime.start();
	if ("MSE" == methodType)
		result = MSE(img1, img2);
	else if ("PSNR" == methodType)
		result = PSNR(img1, img2);
	else if ("PSNR" == methodType)
		result = PSNR(img1, img2);
	else if ("GMSD" == methodType)
		result = GMSD(img1, img2);
	else if ("SSIM" == methodType)
		result = SSIM(img1, img2);
	else if ("BRISQUE" == methodType)
		result = BRISQUE(img2);
	costTime.stop();
	cout << methodType << "_result is: " << result << endl;
	cout << methodType << "_cost time is: " << costTime.getTimeSec() / costTime.getCounter() << " s" << endl;
}

int main()
{
	// img1为基准图像,img2为检测图像
	cv::Mat img1, img2;
	img1 = cv::imread("image/original-rotated-image.jpg");
	img2 = cv::imread("image/noise-version.jpg");

	if (img1.empty() || img2.empty())
	{
		cout << "img empty" << endl;
		return 0;
	}

	// 结果越小,检测图像和基准图像的差距越小
	qualityCompute("MSE", img1, img2);
	// 结果越小,检测图像和基准图像的差距越大
	qualityCompute("PSNR", img1, img2);
	// 结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越大
	qualityCompute("GMSD", img1, img2);
	// 结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越小
	qualityCompute("SSIM", img1, img2);
	// BRISQUE不需要基准图像
	// 结果为一个0到100之间的数,越小表示检测图像质量越好
	qualityCompute("BRISQUE", cv::Mat{}, img2);
	system("pause");
	return 0;
}

Python

# -*- coding: utf-8 -*-
"""
Created on Fri Oct  9 05:27:28 2020

@author: luohenyueji
"""

import cv2
import numpy as np
import time

# ----- 时间装饰器,打印运行结果和运行时间
def usetime(func):
    def inner(*args, **kwargs):
        time_start = time.time()
        # 装饰的函数在此运行
        result = func(*args, **kwargs)
        time_run = time.time() - time_start
        # 打印结果
        print(func.__name__ + '_result is: {:.3f}'.format(result))
        # 打印运行时间
        print(func.__name__ + '_cost time is: {:.3f} s'.format(time_run))

    return inner


# ----- 均方误差 MSE
@usetime
def MSE(img1, img2):
    # 静态方法,一步到位
    # 质量结果图quality_map就是检测图像和基准图像各个像素点差值结果
    result_static, quality_map = cv2.quality.QualityMSE_compute(img1, img2)
    # 另外一种动态计算的方法,但是MSE的计算可能有问题
    # obj = cv2.quality.QualityMSE_create(img1)
    # result = obj.compute(img2)
    # quality_map = obj.getQualityMap()
    # 计算均值
    score = np.mean([i for i in result_static if (i != 0 and not np.isinf(i))])
    score = 0 if np.isnan(score) else score
    return score


# ----- 峰值信噪比 PSNR
@usetime
def PSNR(img1, img2):
    # 静态方法,一步到位
    # 质量结果图quality_map就是检测图像和基准图像各个像素点差值结果
    # maxPixelValue参数为PSNR计算公式中的MAX,即图片可能的最大像素值,通常为255
    result_static, quality_map = cv2.quality.QualityPSNR_compute(img1, img2, maxPixelValue=255)
    # 另外一种动态计算的方法
    # obj = cv2.quality.QualityPSNR_create(img1, maxPixelValue=255)
    # result = obj.compute(img2)
    # quality_map = obj.getQualityMap()
    # 计算均值
    score = np.mean([i for i in result_static if (i != 0 and not np.isinf(i))])
    return score


# ----- 梯度幅度相似性偏差 GMSD
@usetime
def GMSD(img1, img2):
    # 静态方法,一步到位
    # 质量结果图quality_map就是检测图像和基准图像各个像素点差值结果
    result_static, quality_map = cv2.quality.QualityGMSD_compute(img1, img2)
    # 另外一种动态计算的方法
    # obj = cv2.quality.QualityGMSD_create(img1)
    # result = obj.compute(img2)
    # quality_map = obj.getQualityMap()
    # 计算均值
    score = np.mean([i for i in result_static if (i != 0 and not np.isinf(i))])
    score = 0 if np.isnan(score) else score
    return score


# ----- 结构相似性 SSIM
@usetime
def SSIM(img1, img2):
    # 静态方法,一步到位
    # 质量结果图quality_map就是检测图像和基准图像各个像素点差值结果
    result_static, quality_map = cv2.quality.QualitySSIM_compute(img1, img2)
    # 另外一种动态计算的方法
    # obj = cv2.quality.QualitySSIM_create(img1)
    # result = obj.compute(img2)
    # quality_map = obj.getQualityMap()
    # 计算均值
    score = np.mean([i for i in result_static if (i != 0 and not np.isinf(i))])
    score = 0 if np.isnan(score) else score
    return score


# ----- 盲/无参考图像空间质量评估器 BRISQUE
@usetime
def BRISQUE(img):
    # path to the trained model
    model_path = "./model/brisque_model_live.yml"
    # path to range file
    range_path = "./model/brisque_range_live.yml"
    # 静态计算方法
    result_static = cv2.quality.QualityBRISQUE_compute(img, model_path, range_path)
    # # 另外一种动态计算的方法
    # obj = cv2.quality.QualityBRISQUE_create(model_path, range_path)
    # result = obj.compute(img)
    # 计算均值
    score = np.mean([i for i in result_static if (i != 0 and not np.isinf(i))])
    score = 0 if np.isnan(score) else score
    return score


def main():
    # img1为基准图像,img2为检测图像
    img1 = cv2.imread("image/cut-original-rotated-image.jpg")
    img2 = cv2.imread("image/cut-noise-version.jpg")
    if img1 is None or img2 is None:
        print("img empty")
        return
    # 结果越小,检测图像和基准图像的差距越小
    MSE(img1, img2)
    # 结果越小,检测图像和基准图像的差距越大
    PSNR(img1, img2)
    # 结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越大
    GMSD(img1, img2)
    # 结果为一个0到1之间的数,越大表示检测图像和基准图像的差距越小
    SSIM(img1, img2)
    # 结果为一个0到100之间的数,越小表示检测图像质量越好
    BRISQUE(img2)


if __name__ == '__main__':
    main()

2.2 结果分析

上面的代码实现了对不同图片的图像质量诊断,并输出各种方法在不同图像下的评分和方法检测速度。速度计算主要基于C++代码。
具体检测结果如下表所示,其中-nan(ind)表示结果出错,通常是两张图像一样。原图下的结果是原图和原图比,模糊图片和噪声图片是与原图为基准图片比较的结果。按清晰度而言,原图>模糊图片>噪声图片。
下面分别显示分辨率为612x816和中心裁剪分辨率305x305的结果。

结果原图/分辨率612x816模糊图片/分辨率612x816噪声图片/分辨率612x816
方法
MSE-nan(ind)1490.281734.03
PSNR-nan(ind)16.398915.7454
GMSD-nan(ind)0.2095120.199491
SSIM10.302560.482258
BRISQUE53.390163.485971.2059
结果原图/分辨率305x305模糊图片/分辨率305x305噪声图片/分辨率305x305
方法
MSE-nan(ind)1303.96984.486
PSNR-nan(ind)16.978418.2243
GMSD-nan(ind)0.1111760.113035
SSIM10.302560.687856
BRISQUE56.173642.061673.3258

各个方法具体检测速度如下表所示:

速度/s原图/分辨率612x816模糊图片/分辨率612x816噪声图片/分辨率612x816
方法
MSE0.0290.0210.020
PSNR0.0170.0190.019
GMSD0.0320.0310.032
SSIM0.0840.0860.084
BRISQUE0.0680.0730.071
速度/s原图/分辨率305x305模糊图片/分辨率305x305噪声图片/分辨率305x305
方法
MSE0.0060.0050.005
PSNR0.0040.0050.004
GMSD0.0120.0110.012
SSIM0.0250.0310.033
BRISQUE0.0270.0280.028

从上面的结果可以得到如下分析:

  1. 对于612x816分辨率图片,结果正确的有MSE,GMSD,BRISQUE;对于305x305分辨率图片,如果从局部上来看,噪声图片和模糊图片清晰图差不太多,结果正确的有PSNR,GMSD。然而对于BRISQUE模糊图片的清晰度评分比原图高。所以通常情况下,有参考图片,GMSD准确率最高,其他方法并不靠谱,BRISQUE需要更加完整的大图才有好的效果。
  2. 就速度而言,图像分辨率越高,各个方法耗时也越多,毕竟都是靠图像像素点差值公式计算的,不过都能在1s以内获得结果。
  3. 如果有有参图像,最好用GMSD。BRISQUE更适合高分辨率图片,如果要低分辨率使用,建议自己重新训练模型,毕竟BRISQUE的模型太老了。关于BRISQUE模型训练见:quality/samples

总而言之,现在图像质量评价算法都只能针对某种特定环境使用,在实际最好针对每一种图像噪声情况设定一种判定算法,现在各个视频检测平台也都是这样做的。如果普通使用看看GMSD和BRISQUE即可。

3 参考

3.1 参考代码

3.2 参考文章

;