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

图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像。图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析、生物识别、视频监控和安全等领域。随着深度学习技术的发展,基于深度学习的图像超分方法在多个测试任务上,相比传统图像超分方法,取得了更优的性能和效果。

文章目录

  • 1 OpenCV dnn_superres模块介绍
  • 2 OpenCV dnn_superres模块使用
    • 2.1 图像超分放大单输出
      • 2.1.1 接口介绍
      • 2.1.2 示例代码
      • 2.1.3 结果
    • 2.2 图像超分放大多输出
      • 2.2.1 接口介绍
      • 2.2.2 示例代码
      • 2.2.3 结果
    • 2.3 视频超分放大
  • 3 不同图像超分算法性能比较
    • 3.1 不同图像超分算法效果评估
    • 3.2 不同图像超分算法速度评估
    • 3.3 官方超分放大基准测试
    • 3.4 超分算法选择总结
  • 4 参考
    • 4.1 相关论文
    • 4.2 参考代码
    • 4.3 参考文档

关于基于深度学习的图像超分辨率的综述可以见文章:

【超分辨率】—图像超分辨率(Super-Resolution)技术研究

关于基于深度学习的图像超分辨率放大的介绍和最新进展可以见文章:

【超分辨率】—基于深度学习的图像超分辨率最新进展与趋势

OpenCV contrib库中dnn_superres模块用于实现基于深度学习的图像超分放大,本文主要介绍使用此模块进行超分放大。关于dnn_superres模块的代码介绍可以见:

Super Resolution using Convolutional Neural Networks

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

OpenCV_contrib库在windows下编译使用指南

本文所有代码见:

OpenCV-Practical-Exercise

1 OpenCV dnn_superres模块介绍

dnn_superres包含四种基于深度学习的算法,用于放大图像,这些模型能让图像放大2~4倍。具体模型介绍如下:
EDSR

  • 模型和官方代码地址:EDSR_Tensorflow
  • 论文:Enhanced Deep Residual Networks for Single Image Super-Resolution
  • 模型大小:〜38.5MB。这是一个量化版本,因此可以将其上传到GitHub。(原始模型大小为150MB。)
  • 模型参数:提供x2,x3,x4训练模型
  • 优点:高精度
  • 缺点:模型文件大且运行速度慢
  • 速度:在Intel i7-9700K CPU上的256x256图像,每个放大比例所需时间均小于3秒。

ESPCN

  • 模型和官方代码地址:TF-ESPCN
  • 论文:Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network
  • 模型大小:〜100kb
  • 模型参数:提供x2,x3,x4训练模型
  • 优点:体积小,速度快,并且仍然表现良好
  • 缺点:与更新的、更健壮的模型相比,在视觉上表现更差。
  • 速度:在Intel i7-9700K CPU上的256x256图像上,每个放大比例所需时间均小于0.01秒。

FSRCNN

  • 模型和官方代码地址:FSRCNN_Tensorflow
  • 论文:Accelerating the Super-Resolution Convolutional Neural Network
  • 模型大小:〜40KB(对于FSRCNN-small,约为9kb)
  • 模型参数:提供x2,x3,x4训练模型和small训练模型
  • 优点:快速,小巧
  • 缺点:不够准确
  • 速度:在Intel i7-9700K CPU上的256x256图像上,每个放大比例所需时间均小于0.01秒。
  • 其他:FSRCNN-small具有较少的参数,因此精度较低,但速度更快。

LapSRN

  • 模型和官方代码地址:TF-LAPSRN
  • 论文:Deep laplacian pyramid networks for fast and accurate super-resolution
  • 模型大小:1-5Mb之间
  • 模型参数:提供x2,x4,x8训练模型
  • 优点:该模型可以通过一次向前传递进行多尺度超分辨率。可以支持2x,4x,8x和[2x,4x]和[2x,4x,8x]超分辨率。
  • 缺点:它比ESPCN和FSRCNN慢,并且精度比EDSR差。
  • 速度:在Intel i7-9700K CPU上的256x256图像上,每个放大比例所需时间均小于0.1秒。。

2 OpenCV dnn_superres模块使用

2.1 图像超分放大单输出

2.1.1 接口介绍

在本节中,我们将学习如何使用dnn_superres中的函数,通过已有训练的神经网络对图像进行放大。实际上就是调用模型构造模型,只不过dnn_superres对这些模型的调用函数进行了封装,并且建立了通用接口。调用方法如下:
C++

// Make dnn super resolution instance
    // 创建dnn超分辨率对象
    DnnSuperResImpl sr;
    // 读取模型
    sr.readModel(path);
    // 设定算法和放大比例
    sr.setModel(algorithm, scale);
    // 放大图像
    sr.upsample(img, img_new);

Python

# 创建模型
    sr = dnn_superres.DnnSuperResImpl_create()
    # 读取模型
    sr.readModel(path)
    #  设定算法和放大比例
    sr.setModel(algorithm, scale)
    # 放大图像
    img_new = sr.upsample(img)

2.1.2 示例代码

主要展示通过OpenCV自带resize函数或调用深度学习将一张图像发大指定倍数,C++代码和Python代码如下。
C++/dnn_superres.cpp

// 图像超分放大单输出
#include 
#include 
#include 
using namespace std;
using namespace cv;
using namespace dnn;
using namespace dnn_superres;
int main()
{
	string img_path = string("./image/image.png");
	// 可选择算法,bilinear, bicubic, edsr, espcn, fsrcnn or lapsrn
	string algorithm = string("fsrcnn");
	// 放大比例,可输入值2,3,4
	int scale = 4;
	// 模型路径
	string path = "./model/FSRCNN-small_x4.pb";
	// Load the image
	// 载入图像
	Mat img = cv::imread(img_path);
	// 如果输入的图像为空
	if (img.empty())
	{
		std::cerr << "Couldn't load image: " << img << "\n";
		return -2;
	}
	Mat original_img(img);
	// Make dnn super resolution instance
	// 创建dnn超分辨率对象
	DnnSuperResImpl sr;
	// 超分放大后的图像
	Mat img_new;
	// 双线性插值
	if (algorithm == "bilinear")
	{
		resize(img, img_new, Size(), scale, scale, cv::INTER_LINEAR);
	}
	// 双三次插值
	else if (algorithm == "bicubic")
	{
		resize(img, img_new, Size(), scale, scale, cv::INTER_CUBIC);
	}
	else if (algorithm == "edsr" || algorithm == "espcn" || algorithm == "fsrcnn" || algorithm == "lapsrn")
	{
		// 读取模型
		sr.readModel(path);
		// 设定算法和放大比例
		sr.setModel(algorithm, scale);
		// 放大图像
		sr.upsample(img, img_new);
	}
	else
	{
		std::cerr << "Algorithm not recognized. \n";
	}
	// 如果失败
	if (img_new.empty())
	{
		// 放大失败
		std::cerr << "Upsampling failed. \n";
		return -3;
	}
	cout << "Upsampling succeeded. \n";
	// Display image
	// 展示图片
	cv::namedWindow("Initial Image", WINDOW_AUTOSIZE);
	// 初始化图片
	cv::imshow("Initial Image", img_new);
	//cv::imwrite("./saved.jpg", img_new);
	cv::waitKey(0);
	return 0;
}

Python/dnn_superres.py

# -*- coding: utf-8 -*-
"""
Created on Fri Aug 20 20:08:22 2020
@author: luohenyueji
图像超分放大单输出
"""
import cv2
from cv2 import dnn_superres
def main():
    img_path = "./image/image.png"
    # 可选择算法,bilinear, bicubic, edsr, espcn, fsrcnn or lapsrn
    algorithm = "bilinear"
    # 放大比例,可输入值2,3,4
    scale = 4
    # 模型路径
    path = "./model/LapSRN_x4.pb"
    # 载入图像
    img = cv2.imread(img_path)
    # 如果输入的图像为空
    if img is None:
        print("Couldn't load image: " + str(img_path))
        return
    original_img = img.copy()
    # 创建模型
    sr = dnn_superres.DnnSuperResImpl_create()
    if algorithm == "bilinear":
        img_new = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_LINEAR)
    elif algorithm == "bicubic":
        img_new = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
    elif algorithm == "edsr" or algorithm == "espcn" or algorithm == "fsrcnn" or algorithm == "lapsrn":
        # 读取模型
        sr.readModel(path)
        #  设定算法和放大比例
        sr.setModel(algorithm, scale)
        # 放大图像
        img_new = sr.upsample(img)
    else:
        print("Algorithm not recognized")
    # 如果失败
    if img_new is None:
        print("Upsampling failed")
    print("Upsampling succeeded. \n")
    # Display
    # 展示图片
    cv2.namedWindow("Initial Image", cv2.WINDOW_AUTOSIZE)
    # 初始化图片
    cv2.imshow("Initial Image", img_new)
    cv2.imwrite("./saved.jpg", img_new)
    cv2.waitKey(0)
if __name__ == '__main__':
    main()

2.1.3 结果

放大四倍,不同算法效果如下所示:

方法 结果
原图 [OpenCV实战]44 使用OpenCV进行图像超分放大_第1张图片
bilinear [OpenCV实战]44 使用OpenCV进行图像超分放大_第2张图片
bicubic [OpenCV实战]44 使用OpenCV进行图像超分放大_第3张图片
edsr [OpenCV实战]44 使用OpenCV进行图像超分放大_第4张图片
espcn [OpenCV实战]44 使用OpenCV进行图像超分放大_第5张图片
fsrcnn [OpenCV实战]44 使用OpenCV进行图像超分放大_第6张图片
fsrcnn-small [OpenCV实战]44 使用OpenCV进行图像超分放大_第7张图片
lapsrn [OpenCV实战]44 使用OpenCV进行图像超分放大_第8张图片

2.2 图像超分放大多输出

2.2.1 接口介绍

本节主要介绍如何通过LapSRN多输出来放大图像。如果给出了节点的名称,OpenCV的dnn模块支持一次推断访问多个节点。LapSRN模型可以在一次推理运行中提供更多输出。现在,LapSRN模型可以支持2x,4x,8x和(2x,4x)和(2x,4x,8x)超分辨率。经过训练的LapSRN模型文件具有以下输出节点名称:

  • 2x模型:NCHW_output
  • 4x模型:NCHW_output_2x,NCHW_output_4x
  • 8x模型:NCHW_output_2x,NCHW_output_4x,NCHW_output_8x

其次这个功能用处不那么大,LapSRN效果很一般。不过看看挺好的。
由于Python相关实现代码有所问题,因此该部分只提供C++代码。调用方法如下。相比单输出放大,需要设定输出层名字并通过upsampleMultioutput输出各输出层的放大结果。

// 可选多输入放大比例2,4,8。','分隔放大比例
    string scales_str = string("2,4,8");
    // 可选模型输出放大层比例名,NCHW_output_2x,NCHW_output_4x,NCHW_output_8x
    // 需要根据模型和输入放大比例共同确定确定
    string output_names_str = string("NCHW_output_2x,NCHW_output_4x,NCHW_output_8x");
    // 创建Dnn Superres对象
    DnnSuperResImpl sr;
    // 获得最大放大比例
    int scale = *max_element(scales.begin(), scales.end());
    std::vector outputs;
    // 读取模型
    sr.readModel(path);
    // 设定模型输出
    sr.setModel("lapsrn", scale);
    // 多输出超分放大图像
    sr.upsampleMultioutput(img, outputs, scales, node_names);

2.2.2 示例代码

C++/dnn_superres_multioutput.cpp

// 图像超分放大多输出
#include 
#include 
#include 
#include 
using namespace std;
using namespace cv;
using namespace dnn_superres;
int main()
{
	// 图像路径
	string img_path = string("./image/image.png");
	if (img_path.empty())
	{
		printf("image is empty!");
	}
	// 可选多输入放大比例2,4,8。','分隔放大比例
	string scales_str = string("2,4,8");
	// 可选模型输出放大层比例名,NCHW_output_2x,NCHW_output_4x,NCHW_output_8x
	// 需要根据模型和输入放大比例共同确定确定
	string output_names_str = string("NCHW_output_2x,NCHW_output_4x,NCHW_output_8x");
	// 模型路径
	std::string path = string("./model/LapSRN_x8.pb");
	// Parse the scaling factors
	// 解析放大比例因子
	std::vector scales;
	char delim = ',';
	{
		std::stringstream ss(scales_str);
		std::string token;
		while (std::getline(ss, token, delim))
		{
			scales.push_back(atoi(token.c_str()));
		}
	}
	// Parse the output node names
	// 解析模型放大层参数
	std::vector node_names;
	{
		std::stringstream ss(output_names_str);
		std::string token;
		while (std::getline(ss, token, delim))
		{
			node_names.push_back(token);
		}
	}
	// Load the image
	// 导入图片
	Mat img = cv::imread(img_path);
	Mat original_img(img);
	if (img.empty())
	{
		std::cerr << "Couldn't load image: " << img << "\n";
		return -2;
	}
	// Make dnn super resolution instance
	// 创建Dnn Superres对象
	DnnSuperResImpl sr;
	// 获得最大放大比例
	int scale = *max_element(scales.begin(), scales.end());
	std::vector outputs;
	// 读取模型
	sr.readModel(path);
	// 设定模型输出
	sr.setModel("lapsrn", scale);
	// 多输出超分放大图像
	sr.upsampleMultioutput(img, outputs, scales, node_names);
	for (unsigned int i = 0; i < outputs.size(); i++)
	{
		cv::namedWindow("Upsampled image", WINDOW_AUTOSIZE);
		// 在图上显示当前放大比例
		cv::putText(outputs[i], format("Scale %d", scales[i]), Point(10, 30), FONT_HERSHEY_PLAIN, 2.0, Scalar(255, 0, 255), 2, LINE_AA);
		cv::imshow("Upsampled image", outputs[i]);
		cv::imwrite(to_string(i) + ".jpg", outputs[i]);
		cv::waitKey(-1);
	}
	return 0;
}

2.2.3 结果

放大二倍、四倍、八倍的LapSRN算法效果如下所示:

方法 结果
原图 [OpenCV实战]44 使用OpenCV进行图像超分放大_第9张图片
LapSRN_x2 [OpenCV实战]44 使用OpenCV进行图像超分放大_第10张图片
LapSRN_x4 [OpenCV实战]44 使用OpenCV进行图像超分放大_第11张图片
LapSRN_x8 [OpenCV实战]44 使用OpenCV进行图像超分放大_第12张图片

2.3 视频超分放大

实际视频超分放大输出,就是把视频每一帧提取出来,超分放大每一帧图像。代码如下,实际上如果电脑配置很一般不建议视频超分放大,对电脑配置性能要求很高,建议使用opencv cuda进行运算。
C++/dnn_superres_video.cpp

// 视频超分放大多输出
#include 
#include 
#include 
using namespace std;
using namespace cv;
using namespace dnn_superres;
int main()
{
	string input_path = string("./video/chaplin.mp4");
	string output_path = string("./video/https://gitee.com/luohenyueji/article_picture_warehouse/raw/master/CSDN/%5BOpenCV%E5%AE%9E%E6%88%98%5D44%20%E4%BD%BF%E7%94%A8OpenCV%E8%BF%9B%E8%A1%8C%E5%9B%BE%E5%83%8F%E8%B6%85%E5%88%86%E6%94%BE%E5%A4%A7/out_chaplin.mp4");
	// 选择模型 edsr, espcn, fsrcnn or lapsrn
	string algorithm = string("lapsrn");
	// 放大比例,2,3,4,8,根据模型结构选择
	int scale = 2;
	// 模型路径
	string path = string("./model/LapSRN_x2.pb");
	// 打开视频
	VideoCapture input_video(input_path);
	// 输入图像编码尺寸
	int ex = static_cast(input_video.get(CAP_PROP_FOURCC));
	// 获得输出视频图像尺寸
	Size S = Size((int)input_video.get(CAP_PROP_FRAME_WIDTH) * scale,
		(int)input_video.get(CAP_PROP_FRAME_HEIGHT) * scale);
	VideoWriter output_video;
	output_video.open(output_path, ex, input_video.get(CAP_PROP_FPS), S, true);
	// 如果视频没有打开
	if (!input_video.isOpened())
	{
		std::cerr << "Could not open the video." << std::endl;
		return -1;
	}
	// 读取超分放大模型
	DnnSuperResImpl sr;
	sr.readModel(path);
	sr.setModel(algorithm, scale);
	for (;;)
	{
		Mat frame, output_frame;
		input_video >> frame;
		if (frame.empty())
			break;
		// 上采样图像
		sr.upsample(frame, output_frame);
		output_video << output_frame;
		namedWindow("Upsampled video", WINDOW_AUTOSIZE);
		imshow("Upsampled video", output_frame);
		namedWindow("Original video", WINDOW_AUTOSIZE);
		imshow("Original video", frame);
		char c = (char)waitKey(1);
		// esc退出
		if (c == 27)
		{
			break;
		}
	}
	input_video.release();
	output_video.release();
	return 0;
}

Python/dnn_superres_video.py

# -*- coding: utf-8 -*-
"""
Created on Fri Aug 20 21:08:22 2020
@author: luohenyueji
视频超分放大
"""
import cv2
from cv2 import dnn_superres
def main():
    input_path = "./video/chaplin.mp4"
    output_path = "./video/https://gitee.com/luohenyueji/article_picture_warehouse/raw/master/CSDN/%5BOpenCV%E5%AE%9E%E6%88%98%5D44%20%E4%BD%BF%E7%94%A8OpenCV%E8%BF%9B%E8%A1%8C%E5%9B%BE%E5%83%8F%E8%B6%85%E5%88%86%E6%94%BE%E5%A4%A7/out_chaplin.mp4"
    # 选择模型 edsr, espcn, fsrcnn or lapsrn
    algorithm = "lapsrn"
    # 放大比例,2,3,4,8,根据模型结构选择
    scale = 2
    # 模型路径
    path = "./model/LapSRN_x2.pb"
    # 打开视频
    input_video = cv2.VideoCapture(input_path)
    # 输入图像编码尺寸
    ex = int(input_video.get(cv2.CAP_PROP_FOURCC))
    # 获得输出视频图像尺寸
    # 如果视频没有打开
    if input_video is None:
        print("Could not open the video.")
        return
    S = (
    int(input_video.get(cv2.CAP_PROP_FRAME_WIDTH)) * scale, int(input_video.get(cv2.CAP_PROP_FRAME_HEIGHT)) * scale)
    output_video = cv2.VideoWriter(output_path, ex, input_video.get(cv2.CAP_PROP_FPS), S, True)
    # 读取超分放大模型
    sr = dnn_superres.DnnSuperResImpl_create()
    sr.readModel(path)
    sr.setModel(algorithm, scale)
    while True:
        ret, frame = input_video.read()  # 捕获一帧图像
        if not ret:
            print("read video error")
            return
        # 上采样图像
        output_frame = sr.upsample(frame)
        output_video.write(output_frame)
        cv2.namedWindow("Upsampled video", cv2.WINDOW_AUTOSIZE);
        cv2.imshow("Upsampled video", output_frame)
        cv2.namedWindow("Original video", cv2.WINDOW_AUTOSIZE);
        cv2.imshow("Original video", frame)
        c = cv2.waitKey(1);
        # esc退出
        if 27 == c:
            break
    input_video.release()
    output_video.release()
if __name__ == '__main__':
    main()

3 不同图像超分算法性能比较

3.1 不同图像超分算法效果评估

通过PSNR和SSIM来评估图像放大后的效果,PSNR越大,图像失真越小。SSIM也是越大,图像失真越小。PSNR和SSIM介绍见博客:PSNR和SSIM
本节对比四类算法放大图像后的PSNR值和SSIM值,因为电脑性能原因只放大2倍。具体放大倍数可自行调试。代码如下:
C++/dnn_superres_benchmark_quality.cpp

// 不同图像超分算法效果评估
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
using namespace cv;
using namespace dnn_superres;
// 展示图片
static void showBenchmark(vector images, string title, Size imageSize,
	const vector imageTitles,
	const vector psnrValues,
	const vector ssimValues)
{
	// 文字信息
	int fontFace = FONT_HERSHEY_COMPLEX_SMALL;
	int fontScale = 1;
	Scalar fontColor = Scalar(255, 255, 255);
	// 图像数量
	int len = static_cast(images.size());
	int cols = 2, rows = 2;
	// 建立背景图像
	Mat fullImage = Mat::zeros(Size((cols * 10) + imageSize.width * cols, (rows * 10) + imageSize.height * rows),
		images[0].type());
	stringstream ss;
	int h_ = -1;
	// 拼接显示图片
	for (int i = 0; i < len; i++)
	{
		int fontStart = 15;
		int w_ = i % cols;
		if (i % cols == 0)
			h_++;
		Rect ROI((w_ * (10 + imageSize.width)), (h_ * (10 + imageSize.height)), imageSize.width, imageSize.height);
		Mat tmp;
		resize(images[i], tmp, Size(ROI.width, ROI.height));
		ss << imageTitles[i];
		putText(tmp,
			ss.str(),
			Point(5, fontStart),
			fontFace,
			fontScale,
			fontColor,
			1,
			16);
		ss.str("");
		fontStart += 20;
		ss << "PSNR: " << psnrValues[i];
		putText(tmp,
			ss.str(),
			Point(5, fontStart),
			fontFace,
			fontScale,
			fontColor,
			1,
			16);
		ss.str("");
		fontStart += 20;
		ss << "SSIM: " << ssimValues[i];
		putText(tmp,
			ss.str(),
			Point(5, fontStart),
			fontFace,
			fontScale,
			fontColor,
			1,
			16);
		ss.str("");
		fontStart += 20;
		tmp.copyTo(fullImage(ROI));
	}
	namedWindow(title, 1);
	imshow(title, fullImage);
	imwrite("save.jpg", fullImage);
	waitKey();
}
static Vec2d getQualityValues(Mat orig, Mat upsampled)
{
	double psnr = PSNR(upsampled, orig);
	// 前两个参数为对比图片,第三个参数为输出数组
	Scalar q = quality::QualitySSIM::compute(upsampled, orig, noArray());
	double ssim = mean(Vec3d((q[0]), q[1], q[2]))[0];
	return Vec2d(psnr, ssim);
}
int main()
{
	// 图片路径
	string img_path = string("./image/image.png");
	// 算法名称 edsr, espcn, fsrcnn or lapsrn
	string algorithm = string("lapsrn");
	// 模型路径,根据算法确定
	string model = string("./model/LapSRN_x2.pb");
	// 放大系数
	int scale = 2;
	Mat img = imread(img_path);
	if (img.empty())
	{
		cerr << "Couldn't load image: " << img_path << "\n";
		return -2;
	}
	// Crop the image so the images will be aligned
	// 裁剪图像,使图像对齐
	int width = img.cols - (img.cols % scale);
	int height = img.rows - (img.rows % scale);
	Mat cropped = img(Rect(0, 0, width, height));
	// Downscale the image for benchmarking
	// 缩小图像,以实现基准质量测试
	Mat img_downscaled;
	resize(cropped, img_downscaled, Size(), 1.0 / scale, 1.0 / scale);
	// Make dnn super resolution instance
	// 超分模型初始化
	DnnSuperResImpl sr;
	vector allImages;
	// 放大后的图片
	Mat img_new;
	// Read and set the dnn model
	// 读取和设定模型
	sr.readModel(model);
	sr.setModel(algorithm, scale);
	// 放大图像
	sr.upsample(img_downscaled, img_new);
	vector psnrValues = vector();
	vector ssimValues = vector();
	// DL MODEL
	// 获得模型质量评估值
	Vec2f quality = getQualityValues(cropped, img_new);
	// 模型质量评价PSNR
	psnrValues.push_back(quality[0]);
	// 模型质量评价SSIM
	ssimValues.push_back(quality[1]);
	// 数值越大图像质量越好
	cout << sr.getAlgorithm() << ":" << endl;
	cout << "PSNR: " << quality[0] << " SSIM: " << quality[1] << endl;
	cout << "----------------------" << endl;
	// BICUBIC
	// INTER_CUBIC - 三次样条插值放大图像
	Mat bicubic;
	resize(img_downscaled, bicubic, Size(), scale, scale, INTER_CUBIC);
	quality = getQualityValues(cropped, bicubic);
	psnrValues.push_back(quality[0]);
	ssimValues.push_back(quality[1]);
	cout << "Bicubic " << endl;
	cout << "PSNR: " << quality[0] << " SSIM: " << quality[1] << endl;
	cout << "----------------------" << endl;
	// NEAREST NEIGHBOR
	// INTER_NEAREST - 最近邻插值
	Mat nearest;
	resize(img_downscaled, nearest, Size(), scale, scale, INTER_NEAREST);
	quality = getQualityValues(cropped, nearest);
	psnrValues.push_back(quality[0]);
	ssimValues.push_back(quality[1]);
	cout << "Nearest neighbor" << endl;
	cout << "PSNR: " << quality[0] << " SSIM: " << quality[1] << endl;
	cout << "----------------------" << endl;
	// LANCZOS
	// Lanczos插值放大图像
	Mat lanczos;
	resize(img_downscaled, lanczos, Size(), scale, scale, INTER_LANCZOS4);
	quality = getQualityValues(cropped, lanczos);
	psnrValues.push_back(quality[0]);
	ssimValues.push_back(quality[1]);
	cout << "Lanczos" << endl;
	cout << "PSNR: " << quality[0] << " SSIM: " << quality[1] << endl;
	cout << "-----------------------------------------------" << endl;
	// 要显示的图片
	vector imgs{ img_new, bicubic, nearest, lanczos };
	// 要显示的标题
	vector titles{ sr.getAlgorithm(), "Bicubic", "Nearest neighbor", "Lanczos" };
	showBenchmark(imgs, "Quality benchmark", Size(bicubic.cols, bicubic.rows), titles, psnrValues, ssimValues);
	waitKey(0);
	return 0;
}

Python/dnn_superres_benchmark_quality.py

# -*- coding: utf-8 -*-
"""
Created on Fri Aug 20 22:08:22 2020
@author: luohenyueji
不同图像超分算法效果评估
"""
import cv2
from cv2 import dnn_superres
import numpy as np
# TODO 绘图
def showBenchmark(imgs, titles, psnrValues, ssimValues):
    # 绘图
    for i in range(0, len(imgs)):
        # 标题绘图
        cv2.putText(imgs[i], titles[i], (10, 30), cv2.FONT_HERSHEY_PLAIN, 1.5,
                    (255, 0, 255), 2, cv2.LINE_AA)
        # psnr值
        cv2.putText(imgs[i], "PSNR: " + str(psnrValues[i]), (10, 60), cv2.FONT_HERSHEY_PLAIN, 1.5,
                    (255, 0, 255), 2, cv2.LINE_AA)
        # ssim值
        cv2.putText(imgs[i], "SSIM: " + str(ssimValues[i]), (10, 90), cv2.FONT_HERSHEY_PLAIN, 1.5,
                    (255, 0, 255), 2, cv2.LINE_AA)
    # 图片拼接展示
    img = np.vstack([np.hstack([imgs[0], imgs[1]]), np.hstack([imgs[2], imgs[3]])])
    cv2.imshow("Quality benchmark", img)
    cv2.waitKey(0)
# TODO 图像质量评估
def getQualityValues(upsampled, orig):
    psnr = cv2.PSNR(upsampled, orig)
    q, _ = cv2.quality.QualitySSIM_compute(upsampled, orig)
    ssim = (q[0] + q[1] + q[2]) / 3
    return round(psnr, 3), round(ssim, 3)
def main():
    # 图片路径
    img_path = "./image/butterfly.png"
    # 算法名称 edsr, espcn, fsrcnn or lapsrn
    algorithm = "lapsrn"
    # 模型路径,根据算法确定
    model = "./model/LapSRN_x2.pb"
    # 放大系数
    scale = 2
    psnrValues = []
    ssimValues = []
    img = cv2.imread(img_path)
    if img is None:
        print("Couldn't load image: " + str(img_path))
    # Crop the image so the images will be aligned
    # 裁剪图像,使图像对齐
    width = img.shape[0] - (img.shape[0] % scale)
    height = img.shape[1] - (img.shape[1] % scale)
    cropped = img[0:width, 0:height]
    # Downscale the image for benchmarking
    # 缩小图像,以实现基准质量测试
    img_downscaled = cv2.resize(cropped, None, fx=1.0 / scale, fy=1.0 / scale)
    # Make dnn super resolution instance
    # 超分模型初始化
    sr = dnn_superres.DnnSuperResImpl_create()
    # Read and set the dnn model
    # 读取和设定模型
    sr.readModel(model)
    sr.setModel(algorithm, scale)
    # 放大图像
    img_new = sr.upsample(img_downscaled)
    # DL MODEL
    # 获得模型质量评估值
    psnr, ssim = getQualityValues(cropped, img_new)
    psnrValues.append(psnr)
    ssimValues.append(ssim)
    print(sr.getAlgorithm() + "\n")
    print("PSNR: " + str(psnr) + " SSIM: " + str(ssim) + "\n")
    print("-" * 50)
    # INTER_CUBIC - 三次样条插值放大图像
    bicubic = cv2.resize(img_downscaled, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
    psnr, ssim = getQualityValues(cropped, bicubic)
    psnrValues.append(psnr)
    ssimValues.append(ssim)
    print("Bicubic \n")
    print("PSNR: " + str(psnr) + " SSIM: " + str(ssim) + "\n")
    print("-" * 50)
    # INTER_NEAREST - 最近邻插值
    nearest = cv2.resize(img_downscaled, None, fx=scale, fy=scale, interpolation=cv2.INTER_NEAREST)
    psnr, ssim = getQualityValues(cropped, nearest)
    psnrValues.append(psnr)
    ssimValues.append(ssim)
    print("Nearest neighbor \n")
    print("PSNR: " + str(psnr) + " SSIM: " + str(ssim) + "\n")
    print("-" * 50)
    # Lanczos插值放大图像
    lanczos = cv2.resize(img_downscaled, None, fx=scale, fy=scale, interpolation=cv2.INTER_LANCZOS4);
    psnr, ssim = getQualityValues(cropped, lanczos)
    psnrValues.append(psnr)
    ssimValues.append(ssim)
    print("Lanczos \n")
    print("PSNR: " + str(psnr) + " SSIM: " + str(ssim) + "\n")
    print("-" * 50)
    imgs = [img_new, bicubic, nearest, lanczos]
    titles = [sr.getAlgorithm(), "Bicubic", "Nearest neighbor", "Lanczos"]
    showBenchmark(imgs, titles, psnrValues, ssimValues)
if __name__ == '__main__':
    main()

通过lapsrn模型进行超分放大,结果如图所示。可以知道的是lapsrn模型效果实际最好,但是实际中resize函数调用不同选项也会有类似结果,差距没有想象那么大。
[OpenCV实战]44 使用OpenCV进行图像超分放大_第13张图片

3.2 不同图像超分算法速度评估

本节对比四类算法差分放大所需时间,因为电脑性能原因只放大2倍。具体放大倍数可自行调试。代码如下:
C++/dnn_superres_benchmark_time.cpp

// 不同图像超分算法速度评估
#include 
#include 
#include 
#include 
using namespace std;
using namespace cv;
using namespace dnn_superres;
static void showBenchmark(vector images, string title, Size imageSize,
	const vector imageTitles,
	const vector perfValues)
{
	int fontFace = FONT_HERSHEY_COMPLEX_SMALL;
	int fontScale = 1;
	Scalar fontColor = Scalar(255, 255, 255);
	int len = static_cast(images.size());
	int cols = 2, rows = 2;
	Mat fullImage = Mat::zeros(Size((cols * 10) + imageSize.width * cols, (rows * 10) + imageSize.height * rows),
		images[0].type());
	stringstream ss;
	int h_ = -1;
	for (int i = 0; i < len; i++)
	{
		int fontStart = 15;
		int w_ = i % cols;
		if (i % cols == 0)
			h_++;
		Rect ROI((w_ * (10 + imageSize.width)), (h_ * (10 + imageSize.height)), imageSize.width, imageSize.height);
		Mat tmp;
		resize(images[i], tmp, Size(ROI.width, ROI.height));
		ss << imageTitles[i];
		putText(tmp,
			ss.str(),
			Point(5, fontStart),
			fontFace,
			fontScale,
			fontColor,
			1,
			16);
		ss.str("");
		fontStart += 20;
		ss << perfValues[i];
		putText(tmp,
			ss.str(),
			Point(5, fontStart),
			fontFace,
			fontScale,
			fontColor,
			1,
			16);
		ss.str("");
		tmp.copyTo(fullImage(ROI));
	}
	namedWindow(title, 1);
	imshow(title, fullImage);
	imwrite("save.jpg", fullImage);
	waitKey();
}
int main()
{
	// 图片路径
	string img_path = string("./image/butterfly.png");
	// 算法名称 edsr, espcn, fsrcnn or lapsrn
	string algorithm = string("lapsrn");
	// 模型路径,根据算法确定
	string model = string("./model/LapSRN_x2.pb");
	// 放大系数
	int scale = 2;
	Mat img = imread(img_path);
	if (img.empty())
	{
		cerr << "Couldn't load image: " << img << "\n";
		return -2;
	}
	// Crop the image so the images will be aligned
	// 对齐图像
	int width = img.cols - (img.cols % scale);
	int height = img.rows - (img.rows % scale);
	Mat cropped = img(Rect(0, 0, width, height));
	// Downscale the image for benchmarking
	// 缩小图像,以实现基准测试
	Mat img_downscaled;
	resize(cropped, img_downscaled, Size(), 1.0 / scale, 1.0 / scale);
	// Make dnn super resolution instance
	DnnSuperResImpl sr;
	Mat img_new;
	// Read and set the dnn model
	// 读取模型
	sr.readModel(model);
	sr.setModel(algorithm, scale);
	double elapsed = 0.0;
	vector perf;
	TickMeter tm;
	// DL MODEL
	// 计算时间
	tm.start();
	sr.upsample(img_downscaled, img_new);
	tm.stop();
	// 运行时间s
	elapsed = tm.getTimeSec() / tm.getCounter();
	perf.push_back(elapsed);
	cout << sr.getAlgorithm() << " : " << elapsed << endl;
	// BICUBIC
	Mat bicubic;
	tm.start();
	resize(img_downscaled, bicubic, Size(), scale, scale, INTER_CUBIC);
	tm.stop();
	elapsed = tm.getTimeSec() / tm.getCounter();
	perf.push_back(elapsed);
	cout << "Bicubic" << " : " << elapsed << endl;
	// NEAREST NEIGHBOR
	Mat nearest;
	tm.start();
	resize(img_downscaled, nearest, Size(), scale, scale, INTER_NEAREST);
	tm.stop();
	elapsed = tm.getTimeSec() / tm.getCounter();
	perf.push_back(elapsed);
	cout << "Nearest" << " : " << elapsed << endl;
	// LANCZOS
	Mat lanczos;
	tm.start();
	resize(img_downscaled, lanczos, Size(), scale, scale, INTER_LANCZOS4);
	tm.stop();
	elapsed = tm.getTimeSec() / tm.getCounter();
	perf.push_back(elapsed);
	cout << "Lanczos" << " : " << elapsed << endl;
	vector  imgs{ img_new, bicubic, nearest, lanczos };
	vector  titles{ sr.getAlgorithm(), "Bicubic", "Nearest neighbor", "Lanczos" };
	showBenchmark(imgs, "Time benchmark", Size(bicubic.cols, bicubic.rows), titles, perf);
	waitKey(0);
	return 0;
}

Python/dnn_superres_benchmark_time.py

# -*- coding: utf-8 -*-
"""
Created on Fri Aug 20 22:38:22 2020
@author: luohenyueji
不同图像超分算法速度评估
"""
import cv2
from cv2 import dnn_superres
import numpy as np
# TODO 绘图
def showBenchmark(imgs, titles, perf):
    # 绘图
    for i in range(0, len(imgs)):
        # 标题绘图
        cv2.putText(imgs[i], titles[i], (10, 30), cv2.FONT_HERSHEY_PLAIN, 1.5,
                    (255, 0, 255), 2, cv2.LINE_AA)
        # psnr值
        cv2.putText(imgs[i], str(round(perf[i], 3)), (10, 60), cv2.FONT_HERSHEY_PLAIN, 1.5,
                    (255, 0, 255), 2, cv2.LINE_AA)
    # 图片拼接展示
    img = np.vstack([np.hstack([imgs[0], imgs[1]]), np.hstack([imgs[2], imgs[3]])])
    cv2.imshow("Quality benchmark", img)
    cv2.waitKey(0)
def main():
    # 图片路径
    img_path = "./image/image.png"
    # 算法名称 edsr, espcn, fsrcnn or lapsrn
    algorithm = "lapsrn"
    # 模型路径,根据算法确定
    model = "./model/LapSRN_x2.pb"
    # 放大系数
    scale = 2
    # 时间系数
    perf = []
    
    img = cv2.imread(img_path)
    
    if img is None:
        print("Couldn't load image: " + str(img_path))
    
    # Crop the image so the images will be aligned
    # 裁剪图像,使图像对齐
    width = img.shape[0] - (img.shape[0] % scale)
    height = img.shape[1] - (img.shape[1] % scale)
    cropped = img[0:width, 0:height]
    
    # Downscale the image for benchmarking
    # 缩小图像,以实现基准质量测试
    img_downscaled = cv2.resize(cropped, None, fx=1.0 / scale, fy=1.0 / scale)
    
    # Make dnn super resolution instance
    # 超分模型初始化
    sr = dnn_superres.DnnSuperResImpl_create()
    
    # Read and set the dnn model
    # 读取和设定模型
    sr.readModel(model)
    sr.setModel(algorithm, scale)
    
    timer = cv2.TickMeter()
    timer.start()
    # 放大图像
    img_new = sr.upsample(img_downscaled)
    timer.stop()
    # 运行时间s
    elapsed = timer.getTimeSec() / timer.getCounter()
    perf.append(elapsed)
    print(sr.getAlgorithm() + " : " + str(elapsed))
    
    # INTER_CUBIC - 三次样条插值放大图像
    timer.start()
    bicubic = cv2.resize(img_downscaled, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
    timer.stop()
    # 运行时间s
    elapsed = timer.getTimeSec() / timer.getCounter()
    perf.append(elapsed)
    print("Bicubic" + " : " + str(elapsed))
    
    # INTER_NEAREST - 最近邻插值
    timer.start()
    nearest = cv2.resize(img_downscaled, None, fx=scale, fy=scale, interpolation=cv2.INTER_NEAREST)
    timer.stop()
    # 运行时间s
    elapsed = timer.getTimeSec() / timer.getCounter()
    perf.append(elapsed)
    print("Nearest" + " : " + str(elapsed))
    
    # Lanczos插值放大图像
    timer.start()
    lanczos = cv2.resize(img_downscaled, None, fx=scale, fy=scale, interpolation=cv2.INTER_LANCZOS4);
    timer.stop()
    # 运行时间s
    elapsed = timer.getTimeSec() / timer.getCounter()
    perf.append(elapsed)
    print("Lanczos" + " : " + str(elapsed))
    
    imgs = [img_new, bicubic, nearest, lanczos]
    titles = [sr.getAlgorithm(), "Bicubic", "Nearest neighbor", "Lanczos"]
    showBenchmark(imgs, titles, perf)
if __name__ == '__main__':
    main()

通过lapsrn模型进行超分放大,结果如图所示。图中单位为秒/s。lapsrn是OpenCV提供速度最快和精度最低的DNN超分模块,比resize普通算法效果更好都是耗时更多。
[OpenCV实战]44 使用OpenCV进行图像超分放大_第14张图片

3.3 官方超分放大基准测试

OpenCV官方文档给了数据集下的基础测试结果,具体见:Super-resolution benchmarking
在Ubuntu 18.04.02 OS的Intel i7-9700K CPU上数据集超分放大算法结果如下所示。
2倍超分放大

方法 平均时间(s)/cpu 平均PSNR 平均SSIM
ESPCN 0.008795 32.7059 0.9276
EDSR 5.923450 34.1300 0.9447
FSRCNN 0.021741 32.8886 0.9301
LapSRN 0.114812 32.2681 0.9248
Bicubic 0.000208 32.1638 0.9305
Nearest neighbor 0.000114 29.1665 0.9049
Lanczos 0.001094 32.4687 0.9327

3倍超分放大

方法 平均时间(s)/cpu 平均PSNR 平均SSIM
ESPCN 0.005495 28.4229 0.8474
EDSR 2.455510 29.9828 0.8801
FSRCNN 0.008807 28.3068 0.8429
LapSRN 0.282575 26.7330 0.8862
Bicubic 0.000311 26.0635 0.8754
Nearest neighbor 0.000148 23.5628 0.8174
Lanczos 0.001012 25.9115 0.8706

4倍超分放大

方法 平均时间(s)/cpu 平均PSNR 平均SSIM
ESPCN 0.004311 26.6870 0.7891
EDSR 1.607570 28.1552 0.8317
FSRCNN 0.005302 26.6088 0.7863
LapSRN 0.121229 26.7383 0.7896
Bicubic 0.000311 26.0635 0.8754
Nearest neighbor 0.000148 23.5628 0.8174
Lanczos 0.001012 25.9115 0.8706

此外,官方也给出了不同图片在不同算法和不同比例下超分放大的结果,如下所示:
4倍放大一张768x512大小的图像

方法 时间(s)/cpu SNR SSIM
ESPCN 0.01159 26.5471 0.88116
EDSR 3.26758 29.2404 0.92112
FSRCNN 0.01298 26.5646 0.88064
LapSRN 0.28257 26.7330 0.88622
Bicubic 0.00031 26.0635 0.87537
Nearest neighbor 0.00014 23.5628 0.81741
Lanczos 0.00101 25.9115 0.87057

2倍放大一张256x256大小的图像

Set5: butterfly.png size: 256x256
[OpenCV实战]44 使用OpenCV进行图像超分放大_第15张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第16张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第17张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第18张图片
Original Bicubic interpolation Nearest neighbor interpolation Lanczos interpolation
PSRN / SSIM / Speed (CPU) 26.6645 / 0.9048 / 0.000201 23.6854 / 0.8698 / 0.000075 26.9476 / 0.9075 / 0.001039
[OpenCV实战]44 使用OpenCV进行图像超分放大_第19张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第20张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第21张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第22张图片
ESPCN FSRCNN LapSRN EDSR
29.0341 / 0.9354 / 0.004157 29.0077 / 0.9345 / 0.006325 27.8212 / 0.9230 / 0.037937 30.0347 / 0.9453 / 2.077280

3倍放大一张1024x644大小的图像

Urban100: img_001.png size: 1024x644
[OpenCV实战]44 使用OpenCV进行图像超分放大_第23张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第24张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第25张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第26张图片
Original Bicubic interpolation Nearest neighbor interpolation Lanczos interpolation
PSRN / SSIM / Speed (CPU) 27.0474 / 0.8484 / 0.000391 26.0842 / 0.8353 / 0.000236 27.0704 / 0.8483 / 0.002234
[OpenCV实战]44 使用OpenCV进行图像超分放大_第27张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第28张图片 LapSRN无三倍放大 [OpenCV实战]44 使用OpenCV进行图像超分放大_第29张图片
ESPCN FSRCNN LapSRN EDSR
28.0118 / 0.8588 / 0.030748 28.0184 / 0.8597 / 0.094173 30.5671 / 0.9019 / 9.517580

4倍放大一张250x361大小的图像

Set14: comic.png size: 250x361
[OpenCV实战]44 使用OpenCV进行图像超分放大_第30张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第31张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第32张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第33张图片
Original Bicubic interpolation Nearest neighbor interpolation Lanczos interpolation
PSRN / SSIM / Speed (CPU) 19.6766 / 0.6413 / 0.000262 18.5106 / 0.5879 / 0.000085 19.4948 / 0.6317 / 0.001098
[OpenCV实战]44 使用OpenCV进行图像超分放大_第34张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第35张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第36张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第37张图片
ESPCN FSRCNN LapSRN EDSR
20.0417 / 0.6302 / 0.001894 20.0885 / 0.6384 / 0.002103 20.0676 / 0.6339 / 0.061640 20.5233 / 0.6901 / 0.665876

4倍放大一张1356x2040大小的图像

Div2K: 0006.png size: 1356x2040
[OpenCV实战]44 使用OpenCV进行图像超分放大_第38张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第39张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第40张图片
Original Bicubic interpolation Nearest neighbor interpolation
PSRN / SSIM / Speed (CPU) 26.3139 / 0.8033 / 0.001107 23.8291 / 0.7340 / 0.000611
[OpenCV实战]44 使用OpenCV进行图像超分放大_第41张图片 [OpenCV实战]44 使用OpenCV进行图像超分放大_第42张图片
Lanczos interpolation LapSRN
26.1565 / 0.7962 / 0.004782 26.7046 / 0.7987 / 2.274290

3.4 超分算法选择总结

OpenCV中的dnn_superres模块提供的四种图像超分放大深度学习模型,在实践中用的最多的就是EDSR模型。其他三类模型和OpenCV自带的resize函数视觉上差别并不大。但是EDSR模型推理速度太慢,2倍放大和4倍放大可以使用ESPCN代替,4倍和8倍放大可以使用LapSRN。但是总体来说还是使用EDSR为好,毕竟超分放大需要高性能运算,还是用高性能显卡运算较为合适。
此外OpenCV的dnn_superres模块不适用于移动端设备或嵌入式设备,因为OpenCV对设备性能有一定要求。所以移动端可以看看ncnn的超分放大实现。具体见:

srmd ncnn vulkan 通用图片超分放大工具

ncnn用的是srmd超分放大模型,srmd官方代码和ncnn官方实现代码见

SRMD
srmd-ncnn-vulkan

事实上srmd超分放大性能效果高于OpenCV提供的EDSR模型,但SRMD需要显卡进行运算,ncnn在移动端使用vulkan实现加速运算,在PC端如果有显卡也通过ncnn调用SRMD模型。

4 参考

4.1 相关论文

Enhanced Deep Residual Networks for Single Image Super-Resolution
Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network
Accelerating the Super-Resolution Convolutional Neural Network
Deep laplacian pyramid networks for fast and accurate super-resolution

4.2 参考代码

Super Resolution using Convolutional Neural Networks
EDSR_Tensorflow
TF-ESPCN
FSRCNN_Tensorflow
TF-LAPSRN
OpenCV-Practical-Exercise
SRMD
srmd-ncnn-vulkan

4.3 参考文档

超分辨率基准测试
Super-resolution benchmarking
【超分辨率】—图像超分辨率(Super-Resolution)技术研究
【超分辨率】—基于深度学习的图像超分辨率最新进展与趋势
PSNR和SSIM
OpenCV_contrib库在windows下编译使用指南
srmd ncnn vulkan 通用图片超分放大工具

你可能感兴趣的:(OpenCV开发实战,OpenCV,图像处理,opencv,计算机视觉,人工智能)