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

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

【超分辨率】—图像超分辨率(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

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 <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn_superres.hpp>
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 结果

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

方法 结果
原图
bilinear
bicubic
edsr
espcn
fsrcnn
fsrcnn-small
lapsrn

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<Mat> outputs;
// 读取模型
sr.readModel(path);
// 设定模型输出
sr.setModel("lapsrn", scale);
// 多输出超分放大图像
sr.upsampleMultioutput(img, outputs, scales, node_names);

2.2.2 示例代码

C++/dnn_superres_multioutput.cpp

// 图像超分放大多输出
#include <iostream>
#include <sstream>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn_superres.hpp>
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<int> 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<String> 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<Mat> 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算法效果如下所示:

方法 结果
原图
LapSRN_x2
LapSRN_x4
LapSRN_x8

2.3 视频超分放大

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

// 视频超分放大多输出
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn_superres.hpp>
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<int>(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 <iostream>
#include <opencv2/opencv_modules.hpp>
#include <opencv2/dnn_superres.hpp>
#include <opencv2/quality.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace std;
using namespace cv;
using namespace dnn_superres;
// 展示图片
static void showBenchmark(vector<Mat> images, string title, Size imageSize,
const vector<String> imageTitles,
const vector<double> psnrValues,
const vector<double> ssimValues)
{
// 文字信息
int fontFace = FONT_HERSHEY_COMPLEX_SMALL;
int fontScale = 1;
Scalar fontColor = Scalar(255, 255, 255);
// 图像数量
int len = static_cast<int>(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<Mat> allImages;
// 放大后的图片
Mat img_new;
// Read and set the dnn model
// 读取和设定模型
sr.readModel(model);
sr.setModel(algorithm, scale);
// 放大图像
sr.upsample(img_downscaled, img_new);
vector<double> psnrValues = vector<double>();
vector<double> ssimValues = vector<double>();
// 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<Mat> imgs{ img_new, bicubic, nearest, lanczos };
// 要显示的标题
vector<String> 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函数调用不同选项也会有类似结果,差距没有想象那么大。

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

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

// 不同图像超分算法速度评估
#include <iostream>
#include <opencv2/dnn_superres.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace std;
using namespace cv;
using namespace dnn_superres;
static void showBenchmark(vector<Mat> images, string title, Size imageSize,
const vector<String> imageTitles,
const vector<double> perfValues)
{
int fontFace = FONT_HERSHEY_COMPLEX_SMALL;
int fontScale = 1;
Scalar fontColor = Scalar(255, 255, 255);
int len = static_cast<int>(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<double> 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 <Mat> imgs{ img_new, bicubic, nearest, lanczos };
vector <String> 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普通算法效果更好都是耗时更多。

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
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
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
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
LapSRN无三倍放大
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
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
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
Original Bicubic interpolation Nearest neighbor interpolation
PSRN / SSIM / Speed (CPU) 26.3139 / 0.8033 / 0.001107 23.8291 / 0.7340 / 0.000611
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实战]44 使用OpenCV进行图像超分放大的更多相关文章

  1. ACNet:用于图像超分的非对称卷积网络

    编辑:Happy 首发:AIWalker Paper:https://arxiv.org/abs/2103.13634 Code:https://github.com/hellloxiaotian/A ...

  2. HMS Core机器学习服务图像超分能力,基于深度学习提升新闻阅读体验

    在移动端阅读资讯时,人们对高分辨率.高质量的图像要求越来越高.但受限于网络流量.存储.图片源等诸多因素,用户无法便捷获得高质量图片.移动端显示设备的高分辨率图片获得问题亟待解决.不久前,HMS Cor ...

  3. 体验SRCNN和FSRCNN两种图像超分网络应用

    摘要:图像超分即超分辨率,将图像从模糊的状态变清晰. 本文分享自华为云社区<图像超分实验:SRCNN/FSRCNN>,作者:zstar. 图像超分即超分辨率,将图像从模糊的状态变清晰.本文 ...

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

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

  5. OpenCV计算机视觉学习(8)——图像轮廓处理(轮廓绘制,轮廓检索,轮廓填充,轮廓近似)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 1, ...

  6. OpenCV计算机视觉学习(13)——图像特征点检测(Harris角点检测,sift算法)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 前言 ...

  7. 项目实战:Qt+OpenCV大家来找茬(Qt抓图,穿透应用,识别左右图区别,框选区别,微调位置)

    前言   本项目的出现理由只是笔者的一个念头,于是利用专业Qt和Opencv相关的知识开发一个辅助工具,本文章仅用于Qt和Opencv结合的学习.   Demo演示效果          运行包下载地 ...

  8. OpenCV中IplImage图像格式与BYTE图像数据的转换

    最近在将Karlsruhe Institute of Technology的Andreas Geiger发表在ACCV2010上的Efficent Large-Scale Stereo Matchin ...

  9. OpenCV学习笔记:如何扫描图像、利用查找表和计时

    目的 我们将探索以下问题的答案: 如何遍历图像中的每一个像素? OpenCV的矩阵值是如何存储的? 如何测试我们所实现算法的性能? 查找表是什么?为什么要用它? 测试用例 这里我们测试的,是一种简单的 ...

  10. OpenCV 编程简单介绍(矩阵/图像/视频的基本读写操作)

    PS. 因为csdn博客文章长度有限制,本文有部分内容被截掉了.在OpenCV中文站点的wiki上有可读性更好.而且是完整的版本号,欢迎浏览. OpenCV Wiki :<OpenCV 编程简单 ...

随机推荐

  1. Docker | 使用dockerfile生成镜像,清理docker空间

    用dockerfile生成镜像并挂载数据卷 编写dockerfile文件 创建dockerfile01 文件 # 基础镜像 FROM centos VOLUME ["volume01&quo ...

  2. 运行eeui项目不出现 WiFI真机同步 IP地址

    从git上 clone项目之后,安装依赖  npm install eeui环境配置    npm install eeui-cli -g 问题:npm run dev  后项目一直不出现 WiFI真 ...

  3. 华为路由器OSPF配置常用命令

    OSPF单区域配置 ospf 1 进入ospf视图1代表进程号 area 0 创建区域并进入OSPF区域视图,输入要创建的区域ID,骨干区域即区域0 network +IP +匹配码 display ...

  4. Docker安装MongoDB并使用Navicat连接

    MongoDB简介: MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案.是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库 ...

  5. Qt Quick 用cmake怎么玩子项目

    以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」https://mp.weixin.qq.com/s/o-_aGqreuQda-ZmKktvxwA 以往在公司开发众多的项目中 ...

  6. 驱动开发:内核监视LoadImage映像回调

    在笔者上一篇文章<驱动开发:内核注册并监控对象回调>介绍了如何运用ObRegisterCallbacks注册进程与线程回调,并通过该回调实现了拦截指定进行运行的效果,本章LyShark将带 ...

  7. 题解 P6745 『MdOI R3』Number

    前言 不知道是不是正解但是觉得挺好理解. 科学计数法 将一个数表示为\(a\times 10^x\) 的形式.其中\(a\leq10\),\(x\) 为整数. \(\sf Solution\) 其实这 ...

  8. 从0到1搭建redis6.0.7

    redis集群搭建 一.安装redis 源码安装: 1.下载源码包: wget http://download.redis.io/releases/redis-6.0.7.tar.gz 2.解压到指定 ...

  9. 嵌入式-C语言基础:实现字符串拷贝函数

    自己实现一个字符串的拷贝函数 #include<stdio.h> #include<stdlib.h> #include <string.h> char * mys ...

  10. 什么是CLR?

    CLR翻译为"公共语言运行库",全称是Common Language Runtime,它是一个可以由多种语言使用的"运行时",CLR的核心功能如内存管理,程序集 ...