图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像。图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析、生物识别、视频监控和安全等领域。随着深度学习技术的发展,基于深度学习的图像超分方法在多个测试任务上,相比传统图像超分方法,取得了更优的性能和效果。
关于基于深度学习的图像超分辨率的综述可以见文章:
关于基于深度学习的图像超分辨率放大的介绍和最新进展可以见文章:
OpenCV contrib库中dnn_superres模块用于实现基于深度学习的图像超分放大,本文主要介绍使用此模块进行超分放大。关于dnn_superres模块的代码介绍可以见:
本文需要OpenCV contrib库,OpenCV contrib库的编译安装见:
本文所有代码见:
dnn_superres包含四种基于深度学习的算法,用于放大图像,这些模型能让图像放大2~4倍。具体模型介绍如下:
EDSR
ESPCN
FSRCNN
LapSRN
在本节中,我们将学习如何使用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)
主要展示通过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()
放大四倍,不同算法效果如下所示:
方法
结果
原图
bilinear
bicubic
edsr
espcn
fsrcnn
fsrcnn-small
lapsrn
本节主要介绍如何通过LapSRN多输出来放大图像。如果给出了节点的名称,OpenCV的dnn模块支持一次推断访问多个节点。LapSRN模型可以在一次推理运行中提供更多输出。现在,LapSRN模型可以支持2x,4x,8x和(2x,4x)和(2x,4x,8x)超分辨率。经过训练的LapSRN模型文件具有以下输出节点名称:
其次这个功能用处不那么大,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);
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;
}
放大二倍、四倍、八倍的LapSRN算法效果如下所示:
方法
结果
原图
LapSRN_x2
LapSRN_x4
LapSRN_x8
实际视频超分放大输出,就是把视频每一帧提取出来,超分放大每一帧图像。代码如下,实际上如果电脑配置很一般不建议视频超分放大,对电脑配置性能要求很高,建议使用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()
通过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函数调用不同选项也会有类似结果,差距没有想象那么大。
本节对比四类算法差分放大所需时间,因为电脑性能原因只放大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普通算法效果更好都是耗时更多。
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
OpenCV中的dnn_superres模块提供的四种图像超分放大深度学习模型,在实践中用的最多的就是EDSR模型。其他三类模型和OpenCV自带的resize函数视觉上差别并不大。但是EDSR模型推理速度太慢,2倍放大和4倍放大可以使用ESPCN代替,4倍和8倍放大可以使用LapSRN。但是总体来说还是使用EDSR为好,毕竟超分放大需要高性能运算,还是用高性能显卡运算较为合适。
此外OpenCV的dnn_superres模块不适用于移动端设备或嵌入式设备,因为OpenCV对设备性能有一定要求。所以移动端可以看看ncnn的超分放大实现。具体见:
ncnn用的是srmd超分放大模型,srmd官方代码和ncnn官方实现代码见
事实上srmd超分放大性能效果高于OpenCV提供的EDSR模型,但SRMD需要显卡进行运算,ncnn在移动端使用vulkan实现加速运算,在PC端如果有显卡也通过ncnn调用SRMD模型。
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
Super Resolution using Convolutional Neural Networks
EDSR_Tensorflow
TF-ESPCN
FSRCNN_Tensorflow
TF-LAPSRN
OpenCV-Practical-Exercise
SRMD
srmd-ncnn-vulkan
超分辨率基准测试
Super-resolution benchmarking
【超分辨率】—图像超分辨率(Super-Resolution)技术研究
【超分辨率】—基于深度学习的图像超分辨率最新进展与趋势
PSNR和SSIM
OpenCV_contrib库在windows下编译使用指南
srmd ncnn vulkan 通用图片超分放大工具
手机扫一扫
移动阅读更方便
你可能感兴趣的文章