🌞欢迎莅临我的个人主页👈🏻这里是我专注于深度学习领域、用心分享知识精粹与智慧火花的独特角落!🍉
🌈如果大家喜欢文章,欢迎:关注🍷+点赞👍🏻+评论✍🏻+收藏🌟,如有错误敬请指正!🪐
🍓“请不要相信胜利就像山坡上的蒲公英一样唾手可得,但是请相信生活中总有美好值得我们全力以赴,哪怕粉身碎骨!”🌹
前言
超分辨率是计算机视觉领域的重要技术,旨在提高低分辨率图像的质量。它通过复杂算法从低分辨率图像中重构出高分辨率图像,在医疗影像、影视娱乐等领域有广泛应用,为图像质量提升带来新突破。
超分辨率技术分为多种:插值算法、重建技术和深度学习。本文将重点介绍插值算法的原理和应用,并简要说明三种深度学习技术和相关专业软件在超分辨率领域的应用情况!
目录
最近邻插值
双线性插值
双三次插值
Lanczos插值
样条插值
高斯插值
深度学习的SR技术
画质增强专业软件
图像增强效果对比
原理:插值算法通过在已知像素点之间插入新的像素值来提高图像的分辨率。它相对简单直接,能够在一定程度上增加图像的尺寸和细节表现。
最近邻插值
- 原理:每个新像素的值直接采用脱离其最近的已知像素的值。
- 优点:计算简单,速度快。
- 缺点:图像边缘可能会出现锯齿效应和块状效应,图像质量较低。
🍃手写实现
def nearest_neighbor(image, scale_factor):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:return:
'''
img_np = np.array(image)
img_np = img_np.astype(np.float32)
original_height, original_width = img_np.shape[:2]
new_height = int(original_height * scale_factor)
new_width = int(original_width * scale_factor)
# 创建新的图像矩阵
new_img_np = np.zeros((new_height, new_width, img_np.shape[2]), dtype=img_np.dtype)
# 遍历新图像的每个像素,进行最近邻插值
for i in range(new_height):
for j in range(new_width):
# 计算对应于原始图像的最近邻像素
orig_x = int(i / scale_factor)
orig_y = int(j / scale_factor)
new_img_np[i, j] = img_np[orig_x, orig_y]
new_img = Image.fromarray(np.clip(new_img_np, 0, 255).astype(np.uint8))
return new_img
🍂内置函数
image1 = image.resize((height * scale_factor, width * scale_factor), Image.NEAREST)
💥完整代码
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
# -----------------------------------------------------#
# 最近邻插值
# ------------------------------------------------------#
def nearest_neighbor(image, scale_factor):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:return:
'''
img_np = np.array(image)
img_np = img_np.astype(np.float32)
original_height, original_width = img_np.shape[:2]
new_height = int(original_height * scale_factor)
new_width = int(original_width * scale_factor)
# 创建新的图像矩阵
new_img_np = np.zeros((new_height, new_width, img_np.shape[2]), dtype=img_np.dtype)
# 遍历新图像的每个像素,进行最近邻插值
for i in range(new_height):
for j in range(new_width):
# 计算对应于原始图像的最近邻像素
orig_x = int(i / scale_factor)
orig_y = int(j / scale_factor)
new_img_np[i, j] = img_np[orig_x, orig_y]
new_img = Image.fromarray(np.clip(new_img_np, 0, 255).astype(np.uint8))
return new_img
if __name__ == '__main__':
path = r'test/test.jpg'
scale_factor = 3
image = Image.open(path).convert('RGB')
height, width = image.size
image0 = nearest_neighbor(image, scale_factor)
image1 = image.resize((height * scale_factor, width * scale_factor), Image.NEAREST)
plt.subplot(1, 3, 1)
plt.imshow(image)
plt.title("Original")
plt.subplot(1, 3, 2)
plt.imshow(image0)
plt.title("Nearest")
plt.subplot(1, 3, 3)
plt.imshow(image1)
plt.title("PIL")
plt.show()
🍓效果展示
双线性插值
- 原理:对目标像素点的上下和左右像素点的数值进行线性插值,取其加权托盘。
- 优点:比最近邻接值更平滑,过渡减弱自然。
- 缺点:图像的细节和边缘可能会模糊。
🍃手写实现
def bilinear_interpolation(image, scale_factor):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:return:
'''
image = np.array(image)
image = image.astype(np.float32)
height, width = image.shape[:2]
new_height, new_width = int(height * scale_factor), int(width * scale_factor)
# 初始化新图像
if len(image.shape) == 3: # 彩色图像
new_image = np.zeros((new_height, new_width, image.shape[2]), dtype=image.dtype)
else: # 灰度图像
new_image = np.zeros((new_height, new_width), dtype=image.dtype)
# 缩放比例
row_scale = height / new_height
col_scale = width / new_width
for i in range(new_height):
for j in range(new_width):
# 找到原图像中的对应坐标
src_row = i * row_scale
src_col = j * col_scale
# 计算上下和左右的整数像素位置
row_top = min(int(np.floor(src_row)), height - 2)
row_bottom = row_top + 1
col_left = min(int(np.floor(src_col)), width - 2)
col_right = col_left + 1
# 计算插值的权重
row_weight = src_row - row_top
col_weight = src_col - col_left
# 进行插值计算
if len(image.shape) == 3: # 彩色图像
for c in range(image.shape[2]):
top_left = image[row_top, col_left, c]
top_right = image[row_top, col_right, c]
bottom_left = image[row_bottom, col_left, c]
bottom_right = image[row_bottom, col_right, c]
top = top_left + (top_right - top_left) * col_weight
bottom = bottom_left + (bottom_right - bottom_left) * col_weight
new_image[i, j, c] = top + (bottom - top) * row_weight
else: # 灰度图像
top_left = image[row_top, col_left]
top_right = image[row_top, col_right]
bottom_left = image[row_bottom, col_left]
bottom_right = image[row_bottom, col_right]
top = top_left + (top_right - top_left) * col_weight
bottom = bottom_left + (bottom_right - bottom_left) * col_weight
new_image[i, j] = top + (bottom - top) * row_weight
new_image = Image.fromarray(np.clip(new_image, 0, 255).astype(np.uint8))
return new_image
🍂内置函数
# Scipy
image1 = zoom(image, (scale_factor, scale_factor, 1), order=1)
# PIL
image2 = image.resize((height*scale_factor, width*scale_factor), Image.BILINEAR)
💥完整代码
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from scipy.ndimage import zoom
# -----------------------------------------------------#
# 双线性插值
# ------------------------------------------------------#
def bilinear_interpolation(image, scale_factor):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:return:
'''
image = np.array(image)
image = image.astype(np.float32)
height, width = image.shape[:2]
new_height, new_width = int(height * scale_factor), int(width * scale_factor)
# 初始化新图像
if len(image.shape) == 3: # 彩色图像
new_image = np.zeros((new_height, new_width, image.shape[2]), dtype=image.dtype)
else: # 灰度图像
new_image = np.zeros((new_height, new_width), dtype=image.dtype)
# 缩放比例
row_scale = height / new_height
col_scale = width / new_width
for i in range(new_height):
for j in range(new_width):
# 找到原图像中的对应坐标
src_row = i * row_scale
src_col = j * col_scale
# 计算上下和左右的整数像素位置
row_top = min(int(np.floor(src_row)), height - 2)
row_bottom = row_top + 1
col_left = min(int(np.floor(src_col)), width - 2)
col_right = col_left + 1
# 计算插值的权重
row_weight = src_row - row_top
col_weight = src_col - col_left
# 进行插值计算
if len(image.shape) == 3: # 彩色图像
for c in range(image.shape[2]):
top_left = image[row_top, col_left, c]
top_right = image[row_top, col_right, c]
bottom_left = image[row_bottom, col_left, c]
bottom_right = image[row_bottom, col_right, c]
top = top_left + (top_right - top_left) * col_weight
bottom = bottom_left + (bottom_right - bottom_left) * col_weight
new_image[i, j, c] = top + (bottom - top) * row_weight
else: # 灰度图像
top_left = image[row_top, col_left]
top_right = image[row_top, col_right]
bottom_left = image[row_bottom, col_left]
bottom_right = image[row_bottom, col_right]
top = top_left + (top_right - top_left) * col_weight
bottom = bottom_left + (bottom_right - bottom_left) * col_weight
new_image[i, j] = top + (bottom - top) * row_weight
new_image = Image.fromarray(np.clip(new_image, 0, 255).astype(np.uint8))
return new_image
if __name__ == '__main__':
path = r'test/test.jpg'
scale_factor = 3
image = Image.open(path).convert('RGB')
height, width = image.size
image_bilinear = bilinear_interpolation(image, scale_factor)
image1 = zoom(image, (scale_factor, scale_factor, 1), order=1)
image2 = image.resize((height*scale_factor, width*scale_factor), Image.BILINEAR)
plt.subplot(2, 2, 1)
plt.imshow(image)
plt.title("Original")
plt.subplot(2, 2, 2)
plt.imshow(image_bilinear)
plt.title("Bilinear")
plt.subplot(2, 2, 3)
plt.imshow(image1)
plt.title("Scipy")
plt.subplot(2, 2, 4)
plt.imshow(image2)
plt.title("PIL")
plt.show()
🍓效果展示
双三次插值
- 原理:基于16个端点像素(距离最近的4x4像素块)进行三次迭代方式插值计算新像素的值。
- 优点:相比双线性插值,生成的图像更加平滑,边缘细节更好,过渡更自然。
- 缺点:计算量比双线性插值大,图像可能会有一些模糊。
🍃手写实现
def cubic_weight(t):
"""计算三次插值权重的函数"""
a = -0.5
t = abs(t)
if t <= 1:
return (a + 2) * (t ** 3) - (a + 3) * (t ** 2) + 1
elif t <= 2:
return a * (t ** 3) - 5 * a * (t ** 2) + 8 * a * t - 4 * a
return 0
def bicubic_interpolation(image, scale_factor):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:return:
'''
img_np = np.array(image)
img_np = img_np.astype(np.float32)
original_height, original_width = img_np.shape[:2]
new_height = int(original_height * scale_factor)
new_width = int(original_width * scale_factor)
new_img_np = np.zeros((new_height, new_width, img_np.shape[2]), dtype=img_np.dtype)
# 遍历新图像的每个像素,进行双三次插值
for i in range(new_height):
for j in range(new_width):
# 计算原始图像中浮点坐标的位置
orig_x = i / scale_factor
orig_y = j / scale_factor
# 取整得到插值中心的整数部分和小数部分
x_int = int(orig_x)
y_int = int(orig_y)
dx = orig_x - x_int
dy = orig_y - y_int
# 双三次插值的核心部分,遍历16个邻近像素
for m in range(-1, 3):
for n in range(-1, 3):
# 获取边界处理后的坐标
xm = min(max(x_int + m, 0), original_height - 1)
yn = min(max(y_int + n, 0), original_width - 1)
# 计算权重
weight_x = cubic_weight(m - dx)
weight_y = cubic_weight(n - dy)
weight = weight_x * weight_y
# 插值计算,按通道计算
new_img_np[i, j] += img_np[xm, yn] * weight
new_img = Image.fromarray(np.clip(new_img_np, 0, 255).astype(np.uint8))
return new_img
🍂内置函数
image1 = image.resize((height * scale_factor, width * scale_factor), Image.BICUBIC)
💥完整代码
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
# -----------------------------------------------------#
# 双三次插值
# ------------------------------------------------------#
def cubic_weight(t):
"""计算三次插值权重的函数"""
a = -0.5
t = abs(t)
if t <= 1:
return (a + 2) * (t ** 3) - (a + 3) * (t ** 2) + 1
elif t <= 2:
return a * (t ** 3) - 5 * a * (t ** 2) + 8 * a * t - 4 * a
return 0
def bicubic_interpolation(image, scale_factor):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:return:
'''
img_np = np.array(image)
img_np = img_np.astype(np.float32)
original_height, original_width = img_np.shape[:2]
new_height = int(original_height * scale_factor)
new_width = int(original_width * scale_factor)
new_img_np = np.zeros((new_height, new_width, img_np.shape[2]), dtype=img_np.dtype)
# 遍历新图像的每个像素,进行双三次插值
for i in range(new_height):
for j in range(new_width):
# 计算原始图像中浮点坐标的位置
orig_x = i / scale_factor
orig_y = j / scale_factor
# 取整得到插值中心的整数部分和小数部分
x_int = int(orig_x)
y_int = int(orig_y)
dx = orig_x - x_int
dy = orig_y - y_int
# 双三次插值的核心部分,遍历16个邻近像素
for m in range(-1, 3):
for n in range(-1, 3):
# 获取边界处理后的坐标
xm = min(max(x_int + m, 0), original_height - 1)
yn = min(max(y_int + n, 0), original_width - 1)
# 计算权重
weight_x = cubic_weight(m - dx)
weight_y = cubic_weight(n - dy)
weight = weight_x * weight_y
# 插值计算,按通道计算
new_img_np[i, j] += img_np[xm, yn] * weight
new_img = Image.fromarray(np.clip(new_img_np, 0, 255).astype(np.uint8))
return new_img
if __name__ == '__main__':
path = r'test/test.jpg'
scale_factor = 3
image = Image.open(path).convert('RGB')
height, width = image.size
image0 = bicubic_interpolation(image, scale_factor)
image1 = image.resize((height * scale_factor, width * scale_factor), Image.BICUBIC)
plt.subplot(1, 3, 1)
plt.imshow(image)
plt.title("Original")
plt.subplot(1, 3, 2)
plt.imshow(image0)
plt.title("Bicubic")
plt.subplot(1, 3, 3)
plt.imshow(image1)
plt.title("PIL")
plt.show()
🍓效果展示
Lanczos插值
- 原理:利用Lanczos核函数进行插值,通常在3x3或5x5像素邻域内进行计算。该方法是对双三次插值的进一步改进。
- 优点:高质量的插值,能够较好地保留图像细节,减少剪辑效果。
- 缺点:计算复杂度较高,容易产生振铃效应(ringing artifacts)。
🍃手写实现
def lanczos_weight(x, a):
"""Lanczos 核函数,计算权重"""
if x == 0:
return 1
elif -a < x < a:
return np.sinc(x) * np.sinc(x / a)
else:
return 0
def lanczos_interpolation_custom(image, scale_factor, a=3):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:param a: Lanczos 窗宽,通常为2或3
:return:
'''
img_np = np.array(image)
img_np = img_np.astype(np.float32)
# 获取原始图像的尺寸
original_height, original_width = img_np.shape[:2]
# 计算新图像的尺寸
new_height = int(original_height * scale_factor)
new_width = int(original_width * scale_factor)
# 创建新的图像矩阵
new_img_np = np.zeros((new_height, new_width, img_np.shape[2]), dtype=img_np.dtype)
# 遍历新图像的每个像素,进行 Lanczos 插值
for i in range(new_height):
for j in range(new_width):
# 计算原始图像中浮点坐标的位置
orig_x = i / scale_factor
orig_y = j / scale_factor
# 取整得到整数部分和小数部分
x_int = int(orig_x)
y_int = int(orig_y)
dx = orig_x - x_int
dy = orig_y - y_int
# Lanczos 插值核心部分,考虑 a×a 个邻近像素
total_weight = 0
for m in range(-a + 1, a):
for n in range(-a + 1, a):
xm = min(max(x_int + m, 0), original_height - 1)
yn = min(max(y_int + n, 0), original_width - 1)
weight_x = lanczos_weight(m - dx, a)
weight_y = lanczos_weight(n - dy, a)
weight = weight_x * weight_y
new_img_np[i, j] += img_np[xm, yn] * weight
total_weight += weight
# 防止权重过小,做归一化
if total_weight > 0:
new_img_np[i, j] = new_img_np[i, j] / total_weight
new_img = Image.fromarray(np.clip(new_img_np, 0, 255).astype(np.uint8))
return new_img
🍂内置函数
image1 = image.resize((height * scale_factor, width * scale_factor), Image.LANCZOS)
💥完整代码
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
# -----------------------------------------------------#
# Lanczos插值
# ------------------------------------------------------#
def lanczos_weight(x, a):
"""Lanczos 核函数,计算权重"""
if x == 0:
return 1
elif -a < x < a:
return np.sinc(x) * np.sinc(x / a)
else:
return 0
def lanczos_interpolation_custom(image, scale_factor, a=3):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:param a: Lanczos 窗宽,通常为2或3
:return:
'''
img_np = np.array(image)
img_np = img_np.astype(np.float32)
original_height, original_width = img_np.shape[:2]
new_height = int(original_height * scale_factor)
new_width = int(original_width * scale_factor)
# 创建新的图像矩阵
new_img_np = np.zeros((new_height, new_width, img_np.shape[2]), dtype=img_np.dtype)
# 遍历新图像的每个像素,进行 Lanczos 插值
for i in range(new_height):
for j in range(new_width):
# 计算原始图像中浮点坐标的位置
orig_x = i / scale_factor
orig_y = j / scale_factor
# 取整得到整数部分和小数部分
x_int = int(orig_x)
y_int = int(orig_y)
dx = orig_x - x_int
dy = orig_y - y_int
# Lanczos 插值核心部分,考虑 a×a 个邻近像素
total_weight = 0
for m in range(-a + 1, a):
for n in range(-a + 1, a):
xm = min(max(x_int + m, 0), original_height - 1)
yn = min(max(y_int + n, 0), original_width - 1)
weight_x = lanczos_weight(m - dx, a)
weight_y = lanczos_weight(n - dy, a)
weight = weight_x * weight_y
new_img_np[i, j] += img_np[xm, yn] * weight
total_weight += weight
# 防止权重过小,做归一化
if total_weight > 0:
new_img_np[i, j] = new_img_np[i, j] / total_weight
new_img = Image.fromarray(np.clip(new_img_np, 0, 255).astype(np.uint8))
return new_img
if __name__ == '__main__':
path = r'test/test.jpg'
scale_factor = 3
image = Image.open(path).convert('RGB')
height, width = image.size
image0 = lanczos_interpolation_custom(image, scale_factor)
image1 = image.resize((height * scale_factor, width * scale_factor), Image.BILINEAR)
plt.subplot(1, 3, 1)
plt.imshow(image)
plt.title("Original")
plt.subplot(1, 3, 2)
plt.imshow(image0)
plt.title("Lanczos")
plt.subplot(1, 3, 3)
plt.imshow(image1)
plt.title("PIL")
plt.show()
🍓效果展示
样条插值
- 原理:通过样条函数(如立方样条函数)进行取值,以平滑曲线形式连接已知像素点,从而计算新的像素值。
- 优点:生成的图像非常平滑,适合处理曲线轮廓的图像。
- 缺点:计算复杂,可能导致图像过度平滑,损失细节。
🍃手写实现
def cubic_spline_coefficients(x, y):
'''
:param x: x 坐标点数组(升序排列)
:param y: y 坐标点数组
:return: (a, b, c, d): 分段三次多项式系数
'''
n = len(x) - 1 # 段数
h = np.diff(x) # 每段的长度
# 构造三对角矩阵 A
A = np.zeros((n + 1, n + 1))
A[0, 0] = 1
A[n, n] = 1
for i in range(1, n):
A[i, i - 1] = h[i - 1]
A[i, i] = 2 * (h[i - 1] + h[i])
A[i, i + 1] = h[i]
# 构造右侧的向量 B
B = np.zeros(n + 1)
for i in range(1, n):
B[i] = 3 * ((y[i + 1] - y[i]) / h[i] - (y[i] - y[i - 1]) / h[i - 1])
# 求解二阶导数向量 M
M = np.linalg.solve(A, B)
# 计算三次样条的系数 a, b, c, d
a = y[:-1]
b = (y[1:] - y[:-1]) / h - h * (2 * M[:-1] + M[1:]) / 3
c = M[:-1]
d = (M[1:] - M[:-1]) / (3 * h)
return a, b, c, d
def cubic_spline_interpolation_1d(x, y, x_new):
'''
:param x: 原始数据的 x 坐标
:param y: 原始数据的 y 坐标
:param x_new: 要插值的 x 坐标点
:return: 插值后对应的 y 值
'''
a, b, c, d = cubic_spline_coefficients(x, y)
n = len(x) - 1
y_new = np.zeros_like(x_new)
for i, x_i in enumerate(x_new):
for j in range(n):
if x[j] <= x_i <= x[j + 1]:
dx = x_i - x[j]
y_new[i] = a[j] + b[j] * dx + c[j] * dx ** 2 + d[j] * dx ** 3
break
return y_new
def cubic_spline_interpolation_image(image, scale_factor):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:return:
'''
image = np.array(image)
image = image.astype(np.float32)
height, width = image.shape[:2]
# 创建原始图像的网格
x = np.arange(width)
y = np.arange(height)
# 创建新图像的网格
new_x = np.linspace(0, width - 1, int(width * scale_factor))
new_y = np.linspace(0, height - 1, int(height * scale_factor))
# 初始化新图像
if len(image.shape) == 3: # 彩色图像
channels = image.shape[2]
new_image = np.zeros((len(new_y), len(new_x), channels), dtype=image.dtype)
for c in range(channels):
# 对每个通道进行行和列的插值
temp = np.zeros((height, len(new_x)), dtype=image.dtype)
for i in range(height):
temp[i, :] = cubic_spline_interpolation_1d(x, image[i, :, c], new_x)
for j in range(len(new_x)):
new_image[:, j, c] = cubic_spline_interpolation_1d(y, temp[:, j], new_y)
else: # 灰度图像
temp = np.zeros((height, len(new_x)), dtype=image.dtype)
new_image = np.zeros((len(new_y), len(new_x)), dtype=image.dtype)
for i in range(height):
temp[i, :] = cubic_spline_interpolation_1d(x, image[i, :], new_x)
for j in range(len(new_x)):
new_image[:, j] = cubic_spline_interpolation_1d(y, temp[:, j], new_y)
new_image = Image.fromarray(np.clip(new_image, 0, 255).astype(np.uint8))
return new_image
🍂内置函数
from scipy.interpolate import RectBivariateSpline
def spline_interpolation(image, scale_factor):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:return:
'''
image = np.array(image)
image = image.astype(np.float32)
height, width = image.shape[:2]
# 创建原始图像的网格
x = np.arange(width)
y = np.arange(height)
# 创建新图像的网格
new_x = np.linspace(0, width - 1, int(width * scale_factor))
new_y = np.linspace(0, height - 1, int(height * scale_factor))
# 初始化新图像
if len(image.shape) == 3: # 彩色图像
channels = image.shape[2]
new_image = np.zeros((len(new_y), len(new_x), channels), dtype=image.dtype)
for c in range(channels):
# 对每个通道使用 RectBivariateSpline 进行插值
spline = RectBivariateSpline(y, x, image[:, :, c])
new_image[:, :, c] = spline(new_y, new_x)
else: # 灰度图像
spline = RectBivariateSpline(y, x, image)
new_image = spline(new_y, new_x)
new_image = Image.fromarray(np.clip(new_image, 0, 255).astype(np.uint8))
return new_image
💥完整代码
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import RectBivariateSpline
from PIL import Image
# -----------------------------------------------------#
# 样条插值
# ------------------------------------------------------#
# --------------------手写实现-------------------------#
def cubic_spline_coefficients(x, y):
'''
:param x: x 坐标点数组(升序排列)
:param y: y 坐标点数组
:return: (a, b, c, d): 分段三次多项式系数
'''
n = len(x) - 1 # 段数
h = np.diff(x) # 每段的长度
# 构造三对角矩阵 A
A = np.zeros((n + 1, n + 1))
A[0, 0] = 1
A[n, n] = 1
for i in range(1, n):
A[i, i - 1] = h[i - 1]
A[i, i] = 2 * (h[i - 1] + h[i])
A[i, i + 1] = h[i]
# 构造右侧的向量 B
B = np.zeros(n + 1)
for i in range(1, n):
B[i] = 3 * ((y[i + 1] - y[i]) / h[i] - (y[i] - y[i - 1]) / h[i - 1])
# 求解二阶导数向量 M
M = np.linalg.solve(A, B)
# 计算三次样条的系数 a, b, c, d
a = y[:-1]
b = (y[1:] - y[:-1]) / h - h * (2 * M[:-1] + M[1:]) / 3
c = M[:-1]
d = (M[1:] - M[:-1]) / (3 * h)
return a, b, c, d
def cubic_spline_interpolation_1d(x, y, x_new):
'''
:param x: 原始数据的 x 坐标
:param y: 原始数据的 y 坐标
:param x_new: 要插值的 x 坐标点
:return: 插值后对应的 y 值
'''
a, b, c, d = cubic_spline_coefficients(x, y)
n = len(x) - 1
y_new = np.zeros_like(x_new)
for i, x_i in enumerate(x_new):
for j in range(n):
if x[j] <= x_i <= x[j + 1]:
dx = x_i - x[j]
y_new[i] = a[j] + b[j] * dx + c[j] * dx ** 2 + d[j] * dx ** 3
break
return y_new
def cubic_spline_interpolation_image(image, scale_factor):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:return:
'''
image = np.array(image)
image = image.astype(np.float32)
height, width = image.shape[:2]
# 创建原始图像的网格
x = np.arange(width)
y = np.arange(height)
# 创建新图像的网格
new_x = np.linspace(0, width - 1, int(width * scale_factor))
new_y = np.linspace(0, height - 1, int(height * scale_factor))
# 初始化新图像
if len(image.shape) == 3: # 彩色图像
channels = image.shape[2]
new_image = np.zeros((len(new_y), len(new_x), channels), dtype=image.dtype)
for c in range(channels):
# 对每个通道进行行和列的插值
temp = np.zeros((height, len(new_x)), dtype=image.dtype)
for i in range(height):
temp[i, :] = cubic_spline_interpolation_1d(x, image[i, :, c], new_x)
for j in range(len(new_x)):
new_image[:, j, c] = cubic_spline_interpolation_1d(y, temp[:, j], new_y)
else: # 灰度图像
temp = np.zeros((height, len(new_x)), dtype=image.dtype)
new_image = np.zeros((len(new_y), len(new_x)), dtype=image.dtype)
for i in range(height):
temp[i, :] = cubic_spline_interpolation_1d(x, image[i, :], new_x)
for j in range(len(new_x)):
new_image[:, j] = cubic_spline_interpolation_1d(y, temp[:, j], new_y)
new_image = Image.fromarray(np.clip(new_image, 0, 255).astype(np.uint8))
return new_image
# --------------------------内置函数--------------------------------------#
def spline_interpolation(image, scale_factor):
'''
:param image: 输入图像
:param scale_factor: 缩放因子
:return:
'''
image = np.array(image)
image = image.astype(np.float32)
height, width = image.shape[:2]
# 创建原始图像的网格
x = np.arange(width)
y = np.arange(height)
# 创建新图像的网格
new_x = np.linspace(0, width - 1, int(width * scale_factor))
new_y = np.linspace(0, height - 1, int(height * scale_factor))
# 初始化新图像
if len(image.shape) == 3: # 彩色图像
channels = image.shape[2]
new_image = np.zeros((len(new_y), len(new_x), channels), dtype=image.dtype)
for c in range(channels):
# 对每个通道使用 RectBivariateSpline 进行插值
spline = RectBivariateSpline(y, x, image[:, :, c])
new_image[:, :, c] = spline(new_y, new_x)
else: # 灰度图像
spline = RectBivariateSpline(y, x, image)
new_image = spline(new_y, new_x)
new_image = Image.fromarray(np.clip(new_image, 0, 255).astype(np.uint8))
return new_image
if __name__ == '__main__':
path = r'test/test.jpg'
scale_factor = 3
image = Image.open(path).convert('RGB')
height, width = image.size
image_spline = cubic_spline_interpolation_image(image, scale_factor)
image1 = spline_interpolation(image, scale_factor)
plt.subplot(1, 3, 1)
plt.imshow(image)
plt.title("Original")
plt.subplot(1, 3, 2)
plt.imshow(image_spline)
plt.title("Spline")
plt.subplot(1, 3, 3)
plt.imshow(image1)
plt.title("Scipy")
plt.show()
🍓效果展示
高斯插值
- 原理:基于高斯分布的加权平均方法,通过赋予相邻像素不同的权重来计算新的像素值。
- 优点:平滑并保留一定的细节,减少噪声影响。
- 缺点:计算复杂度首要。
🍃手写实现
def gaussian_weight(distance, sigma):
"""计算距离对应的高斯权重"""
return math.exp(-(distance ** 2) / (2 * sigma ** 2))
def gaussian_kernel(size, sigma):
"""生成高斯核"""
kernel = np.zeros((size, size))
center = size // 2
for i in range(size):
for j in range(size):
distance = np.sqrt((i - center) ** 2 + (j - center) ** 2)
kernel[i, j] = gaussian_weight(distance, sigma)
return kernel / np.sum(kernel) # 归一化核
def gaussian_interpolation(image, scale_factor, kernel_size=3, sigma=0.5):
"""使用高斯插值对图像进行缩放"""
image = np.array(image)
image = image.astype(np.float32)
height, width = image.shape[:2]
kernel = gaussian_kernel(kernel_size, sigma)
new_height, new_width = int(height * scale_factor), int(width * scale_factor)
if len(image.shape) == 3: # 彩色图像
new_image = np.zeros((new_height, new_width, image.shape[2]), dtype=image.dtype)
else: # 灰度图像
new_image = np.zeros((new_height, new_width), dtype=image.dtype)
# 缩放比例
row_scale = height / new_height
col_scale = width / new_width
# 对新图像的每个像素进行插值
for i in range(new_height):
for j in range(new_width):
# 在原图像中找到对应的坐标
src_row = i * row_scale
src_col = j * col_scale
row_floor = int(np.floor(src_row))
col_floor = int(np.floor(src_col))
# 初始化加权和
weighted_sum = np.zeros(image.shape[2]) if len(image.shape) == 3 else 0.0
weight_total = 0.0
# 遍历高斯核
for m in range(kernel_size):
for n in range(kernel_size):
row_offset = row_floor + m - kernel_size // 2
col_offset = col_floor + n - kernel_size // 2
# 如果超出图像边界,跳过该点
if row_offset < 0 or row_offset >= height or col_offset < 0 or col_offset >= width:
continue
# 加权平均
weight = kernel[m, n]
if len(image.shape) == 3:
weighted_sum += weight * image[row_offset, col_offset, :]
else:
weighted_sum += weight * image[row_offset, col_offset]
weight_total += weight
# 归一化结果
if len(image.shape) == 3:
new_image[i, j, :] = weighted_sum / weight_total
else:
new_image[i, j] = weighted_sum / weight_total
new_image = Image.fromarray(np.clip(new_image, 0, 255).astype(np.uint8))
return new_image
🍂内置函数
from scipy.ndimage import gaussian_filter
image1 = gaussian_filter(image, sigma=0.5)
💥完整代码
import numpy as np
import math
import matplotlib.pyplot as plt
from scipy.ndimage import gaussian_filter
from PIL import Image
# -----------------------------------------------------#
# 高斯插值
# ------------------------------------------------------#
def gaussian_weight(distance, sigma):
"""计算距离对应的高斯权重"""
return math.exp(-(distance ** 2) / (2 * sigma ** 2))
def gaussian_kernel(size, sigma):
"""生成高斯核"""
kernel = np.zeros((size, size))
center = size // 2
for i in range(size):
for j in range(size):
distance = np.sqrt((i - center) ** 2 + (j - center) ** 2)
kernel[i, j] = gaussian_weight(distance, sigma)
return kernel / np.sum(kernel) # 归一化核
def gaussian_interpolation(image, scale_factor, kernel_size=3, sigma=0.5):
"""使用高斯插值对图像进行缩放"""
image = np.array(image)
image = image.astype(np.float32)
height, width = image.shape[:2]
kernel = gaussian_kernel(kernel_size, sigma)
new_height, new_width = int(height * scale_factor), int(width * scale_factor)
if len(image.shape) == 3: # 彩色图像
new_image = np.zeros((new_height, new_width, image.shape[2]), dtype=image.dtype)
else: # 灰度图像
new_image = np.zeros((new_height, new_width), dtype=image.dtype)
# 缩放比例
row_scale = height / new_height
col_scale = width / new_width
# 对新图像的每个像素进行插值
for i in range(new_height):
for j in range(new_width):
# 在原图像中找到对应的坐标
src_row = i * row_scale
src_col = j * col_scale
row_floor = int(np.floor(src_row))
col_floor = int(np.floor(src_col))
# 初始化加权和
weighted_sum = np.zeros(image.shape[2]) if len(image.shape) == 3 else 0.0
weight_total = 0.0
# 遍历高斯核
for m in range(kernel_size):
for n in range(kernel_size):
row_offset = row_floor + m - kernel_size // 2
col_offset = col_floor + n - kernel_size // 2
# 如果超出图像边界,跳过该点
if row_offset < 0 or row_offset >= height or col_offset < 0 or col_offset >= width:
continue
# 加权平均
weight = kernel[m, n]
if len(image.shape) == 3:
weighted_sum += weight * image[row_offset, col_offset, :]
else:
weighted_sum += weight * image[row_offset, col_offset]
weight_total += weight
# 归一化结果
if len(image.shape) == 3:
new_image[i, j, :] = weighted_sum / weight_total
else:
new_image[i, j] = weighted_sum / weight_total
new_image = Image.fromarray(np.clip(new_image, 0, 255).astype(np.uint8))
return new_image
if __name__ == '__main__':
path = r'test/test.jpg'
scale_factor = 3
image = Image.open(path).convert('RGB')
height, width = image.size
image_gaussian = gaussian_interpolation(image, scale_factor, kernel_size=3, sigma=0.5)
image1 = gaussian_filter(image, sigma=0.5)
plt.subplot(1, 3, 1)
plt.imshow(image)
plt.title("Original")
plt.subplot(1, 3, 2)
plt.imshow(image_gaussian)
plt.title("Gaussian")
plt.subplot(1, 3, 3)
plt.imshow(image1)
plt.title("Scipy")
plt.show()
🍓效果展示
深度学习的SR技术
总体而言,基于插值的方法相对简单,在超分辨率重建中的性能往往不如基于学习的方法。因此下面我们就简单介绍3种在OpenCV基于深度学习的超分辨率模型实践方法吧。
1️⃣安装opencv和contrib模块
contrib模块是SR接口代码所在的位置,我们需要使用的接口或模块称为dnn_superres(dnn代表深度神经网络;superres代表超分辨率)
pip install opencv-python
pip install opencv-contrib-python
2️⃣模型下载
当前在OpenCV主要支持4中不同的SR模型,它们都可以按2、3和4的比例放大图像,但是在准确性、大小和速度上各部相同。
EDSR:目前表现最好的模型。然而,它也是参数量最大的模型,因此具有最大的文件大小和最慢的推理。
下载链接:https://github.com/Saafke/EDSR_Tensorflow/tree/master/models
ESPCN:相对较小的模型,具有快速和良好的推理能力,可以进行实时视频放大(取决于图像大小)。
下载链接:https://github.com/fannymonori/TF-ESPCN/tree/master/export
FSRCNN:快速准确推理的小模型,还可以进行实时视频放大。
下载链接:https://github.com/Saafke/FSRCNN_Tensorflow/tree/master/models
LapSRN:中等大小的模型,可以提升8倍分辨率。
下载链接:https://github.com/fannymonori/TF-LapSRN/tree/master/export
3️⃣实践
import cv2
from cv2 import dnn_superres
sr = dnn_superres.DnnSuperResImpl_create()
# -------------------输入图像路径--------------------------#
path = r'test/test.jpg'
# ----------------------模型路径---------------------------#
model = r'models/EDSR/EDSR_x3.pb'
image = cv2.imread(path)
sr.readModel(model)
sr.setModel('edsr', 3)
result = sr.upsample(image)
cv2.imshow('image', result)
cv2.waitKey()
cv2.destroyAllWindows()
4️⃣注意事项
-
在使用.jpg图像时出现错误,请尝试切换到.png格式。
-
确保setModel()中的参数与readModel()中使用的模型匹配。
-
想使用GPU进行推理(默认是CPU),可以在读入模型后将后端设置为CUDA
-
path = "EDSR_x3.pb" sr.readModel(path) # Set CUDA backend and target to enable GPU inference sr.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) sr.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
5️⃣效果展示
我只尝试了3种SR模型,没有使用LapSRN模型,但就目前来看,EDSR在细节方面确实是表现得最好的一个,但缺点就是推理时间太长了,长达3秒钟。
画质增强专业软件
🍁除了使用代码进行画质增强,我们还可以使用相关的专业软件进行处理噢🍁
Upscayl的GitHub地址:https://github.com/upscayl/upscayl
Upscayl项目是一个免费开源的 AI 图像升频器,支持 Linux、macOS 和 Windows 系统。它使用先进的人工智能算法来放大和增强低分辨率图像,且不损失质量。在该项目页面中,可以找到相关的代码、文档、发布版本等信息。如果大家对图像增强、人工智能图像处理等方面感兴趣,可以关注这个项目。
🍓效果展示
效果可以说是非常好了,完全碾压目前我所了解的所有图像增强算法,当然了,人家毕竟是专业的嘛。值得一说的是,这个软件不仅支持单张图像,还支持文件夹处理噢。
图像增强效果对比
说了这么多的画质增强算法,我觉得需要有一张不同方法的效果对比图才足够让大家清楚各种算法的优缺点嘛,OK,大家继续往下看吧!
🍇推理时间对比
可以看到,只考虑插值算法实现图像画质增强的方法中,双线性插值、双三次插值和Lanczos插值效果都还是不错的。当然深度学习的效果也是明显是优于传统插值算法的,而这当中EDSR模型性能最佳,但推理时间巨慢,不建议处理大批量数据。
当然,如果只考虑处理后的图像质量,那么使用Upscayl软件进行画质增强无疑是上上之选,不仅细节恢复的非常好,而且图像的噪声也处理得非常到位,对于科研工作者来说无疑是最佳的选择!🍉
🍹总结
总的来说,超分辨率的主要作用在于恢复图像的细节部分,这对于因各种因素而导致画质模糊或像素低的图像具有重大意义。虽然超分辨率技术众多,但并非都适用于同一种数据,大家按需选择即可。当然,我的首选肯定是使用专业的软件进行图像恢复啦,毕竟人家是专业的呀!🐶
参考链接:超分辨率基准测试