本项目根据ZJU《计算机视觉》课程内容整理了目前常见的图像复原算法(包括传统算法和深度学习的方法)并利用Python-Opencv实现模型复现;利用主流的图像修复评价函数(MSE、PSNR以及SSIM)实现复原算法效果的客观定量评价。
图像复原的过程可以主要分为:
参考博文
图像的频率是表征图像中灰度变化剧烈程度的指标,是灰度在平面空间上的梯度。因此图像的傅立叶变换表示了(灰度)分布的剧烈程度。
如下图所示,对参考图进行快速傅立叶变换(FFT),其中白色区域表示梯度变换明显部分,黑色区域表示梯度变换缓慢的部分。 左图为没有谱中心处理的结果,四角表示低频部分,中心表示高频部分;经过谱中心化之后,区域进行了变换,四角表示高频部分,中心表示低频部分。
import matplotlib.pyplot as plt
import numpy as np
from numpy import fft
import math
import cv2
import math
plt.figure(figsize=(15, 9))
img = plt.imread(path)
plt.subplot(231),plt.imshow(img),plt.title('picture')
#根据公式转成灰度图
img = 0.2126 * img[:,:,0] + 0.7152 * img[:,:,1] + 0.0722 * img[:,:,2]
#显示灰度图
plt.subplot(232),plt.imshow(img,'gray'),plt.title('original')
#进行傅立叶变换,并显示结果
fft2 = np.fft.fft2(img)
plt.subplot(233),plt.imshow(np.abs(fft2),'gray'),plt.title('fft2')
#将图像变换的原点移动到频域矩形的中心,并显示效果
shift2center = np.fft.fftshift(fft2)
plt.subplot(234),plt.imshow(np.abs(shift2center),'gray'),plt.title('shift2center')
#对傅立叶变换的结果进行对数变换,并显示效果
log_fft2 = np.log(1 + np.abs(fft2))
plt.subplot(235),plt.imshow(log_fft2,'gray'),plt.title('log_fft2')
#对中心化后的结果进行对数变换,并显示结果
log_shift2center = np.log(1 + np.abs(shift2center))
plt.subplot(236),plt.imshow(log_shift2center,'gray'),plt.title('log_shift2center')
plt.show()
图像f(x,y)在图像面上移动并且图像f(x,y)除移动外不随时间变化。令x0(t)和y0(t)分别代表位移的x分量和y分量,那么在快门开启的时间T内,胶片上某点的总曝光量是图像在移动过程中一系列相应像素的亮度对该点作用之总和。
也就是说,运动模糊图像是由同一图像在产生距离延迟后与原图像想叠加而成。如果快门开启与关闭的时间忽略不计,则有:
g ( x , y ) = ∫ 0 T f [ x − x 0 ( t ) ) , y − y 0 ( t ) ) ] d t g(x,y)=\int_{0}^{T}f[x-x_{0}(t)), y-y_{0}(t))]dt g(x,y)=∫0Tf[x−x0(t)),y−y0(t))]dt
本文设计实现运动模糊的模型函数 m o t i o n _ p r o c e s s ( i m a g e _ s i z e , m o t i o n _ a n g l e ) motion\_process(image\_size,motion\_angle) motion_process(image_size,motion_angle),它包含两个参数:图像的尺寸大小image_size以及运动的角度motion_angle。当运动位移为9、运动角度为45度时,则该模型函数的构建过程如下:
首先是创建与图像同等大小的全0矩阵,然后找到全0矩阵的中心行数center_position,再计算出运动角度的tan值与cot值,算出运动的偏移量offset。 再令 α ≤ 45 ° \alpha\leq45° α≤45°时,
P S F [ i n t ( c e n t e r p o s i t i o n + o f f s e t ) , PSF[int(center_position+offset), PSF[int(centerposition+offset),
i n t ( c e n t e r p o s i t i o n − o f f s e t ) ] = 1 int(center_position-offset)]=1 int(centerposition−offset)]=1
当 α ≥ 45 ° \alpha\ge45° α≥45°时,
P S F [ i n t ( c e n t e r p o s i t i o n − o f f s e t ) , PSF[int(center_position-offset), PSF[int(centerposition−offset),
i n t ( c e n t e r p o s i t i o n + o f f s e t ) ] = 1 int(center_position+offset)]=1 int(centerposition+offset)]=1则该模型对应的图像如下图所示:
# 生成卷积核和锚点
def genaratePsf(length, angle):
half = length/2
EPS = np.finfo(float).eps
alpha = (angle - math.floor(angle / 180) * 180) / 180 * math.pi
cosalpha = math.cos(alpha)
sinalpha = math.sin(alpha)
if cosalpha < 0:
xsign = -1
elif angle == 90:
xsign = 0
else:
xsign = 1
psfwdt = 1;
# 模糊核大小
sx = int(math.fabs(length * cosalpha + psfwdt * xsign - length * EPS))
sy = int(math.fabs(length * sinalpha + psfwdt - length * EPS))
psf1 = np.zeros((sy, sx))
# psf1是左上角的权值较大,越往右下角权值越小的核。
# 这时运动像是从右下角到左上角移动
for i in range(0, sy):
for j in range(0, sx):
psf1[i][j] = i * math.fabs(cosalpha) - j * sinalpha
rad = math.sqrt(i * i + j * j)
if rad >= half and math.fabs(psf1[i][j]) <= psfwdt:
temp = half - math.fabs((j + psf1[i][j] * sinalpha) / cosalpha)
psf1[i][j] = math.sqrt(psf1[i][j] * psf1[i][j] + temp * temp)
psf1[i][j] = psfwdt + EPS - math.fabs(psf1[i][j]);
if psf1[i][j] < 0:
psf1[i][j] = 0
# 运动方向是往左上运动,锚点在(0,0)
anchor = (0, 0)
# 运动方向是往右上角移动,锚点一个在右上角 #同时,左右翻转核函数,使得越靠近锚点,权值越大
if angle < 90 and angle > 0:
psf1 = np.fliplr(psf1)
anchor = (psf1.shape[1] - 1, 0)
elif angle > -90 and angle < 0: # 同理:往右下角移动
psf1 = np.flipud(psf1)
psf1 = np.fliplr(psf1)
anchor = (psf1.shape[1] - 1, psf1.shape[0] - 1)
elif anchor < -90: # 同理:往左下角移动
psf1 = np.flipud(psf1)
anchor = (0, psf1.shape[0] - 1)
psf1 = psf1 / psf1.sum()
return psf1, anchor
逆滤波是一种无约束的图像复原算法,其目标是找到最优估计图像,即最小化 J ( f ^ ) = ∥ n ∥ 2 = ∥ g − H f ^ ∥ 2 J(\hat{f})=\left \| n\right\|^2=\left \| g-H\hat{f}\right\|^2 J(f^)=∥n∥2=∥∥∥g−Hf^∥∥∥2。如果已知退化图像的傅立叶变换和系统冲激响应函数(“滤被” 传递函数),则可以求得原图像的傅立叶变换,经傅立叶反变换就可以求得原始图像 f ( x , y ) f(x, y) f(x,y),其中 退化图像的傅立叶变换 G ( u , v ) G(u, v) G(u,v) 除以点扩散函数的傅立叶变换结果 H ( u , v ) H(u, v) H(u,v) 起到了反向滤波的作用。
f ( x , y ) = F − 1 [ F ( u , v ) ] = F − 1 [ G ( u , v ) H ( u , v ) ] f(x, y)=F^{-1}[F(u, v)]=F^{-1}\left[\frac{G(u, v)}{H(u, v)}\right] f(x,y)=F−1[F(u,v)]=F−1[H(u,v)G(u,v)]
由上式可得,当 H ( u , v ) H(u, v) H(u,v)很小时, F ^ ( u , v ) \hat{F}(u, v) F^(u,v)极大,出现病态性质。因此在分母引入修正项k,使得其不会趋近于0。但是仍会对噪声具有放大作用,因此无约束复原方法不适合复原含有噪声的图像。
def inverse(input, PSF, eps): # 逆滤波
input_fft = fft.fft2(input)
PSF_fft = fft.fft2(PSF) + eps # 噪声功率,这是已知的,考虑epsilon
result = fft.ifft2(input_fft / PSF_fft) # 计算F(u,v)的傅里叶反变换
result = np.abs(fft.fftshift(result))
return result
有约束图像复原技术是指除了要求了解关于退化系统的传递函数之外,还需要了解的噪声的先验知识,并采用不同的约束条件。
其目标函数为 J ( f ^ ) = ∥ Q f ^ ∥ 2 + α ( ∥ g − H f ^ ∥ 2 − ∥ n ∥ 2 ) J(\hat{f})=\left \| Q\hat{f}\right\|^2 + \alpha(\left \| g-H\hat{f}\right\|^2-\left \| n\right\|^2) J(f^)=∥∥∥Qf^∥∥∥2+α(∥∥∥g−Hf^∥∥∥2−∥n∥2),其中 Q Q Q为 f f f的线性算子。选用不同的变换矩阵 Q Q Q得到不同物理意义的约束复原算法。
def wiener(input,PSF,eps,K=0.01): # 维纳滤波
input_fft=fft.fft2(input)
PSF_fft=fft.fft2(PSF) +eps
PSF_fft_1=np.conj(PSF_fft) /(np.abs(PSF_fft)**2 + K)
result=fft.ifft2(input_fft * PSF_fft_1)
result=np.abs(fft.fftshift(result))
return result
图像修复模型的好坏需要对结果品质进行评价,衡量图像修复的指标主要有以下三种:
MSE:均方差,两点之间数值差异。
def mse(img1, img2):
mse = np.mean( (img1/255. - img2/255.) ** 2)
return mse
PSNR:峰值信噪比,即峰值信号的能量与噪声的平均能量之比。定义式如下: P S N R = 10 × log 10 M a x V a l u e 2 M S E PSNR = 10\times\log_{10} \frac{MaxValue^2}{MSE} PSNR=10×log10MSEMaxValue2,其中 M a x V a l u e 2 MaxValue^2 MaxValue2表示为存储最大位数 2 b i t s − 1 2^{bits}-1 2bits−1。PSNR指标越高,说明图像质量越好。
def psnr(img1, img2):
mse = np.mean((img1/1.0 - img2/1.0) ** 2 )
if mse < 1.0e-10:
return 100
return 10 * math.log10(255.0**2/mse)
SSIM:上述基于像素的差剖面的计算不符合人类视觉系统(Human Visual System,HVS)的评价结果,因此需要对评价方式进行重新考量。 从自然图像高度结构化的特征出发,通过亮度(luminance)、对比度(contrast)和结构(structure)三个方面估计感知结构信息的变化。
# 方法1
import numpy as np
from PIL import Image
from scipy.signal import convolve2d
def matlab_style_gauss2D(shape=(3,3),sigma=0.5):
# 2D gaussian mask - should give the same result as MATLAB's
# fspecial('gaussian',[shape],[sigma])
m,n = [(ss-1.)/2. for ss in shape]
y,x = np.ogrid[-m:m+1,-n:n+1]
h = np.exp( -(x*x + y*y) / (2.*sigma*sigma) )
h[ h < np.finfo(h.dtype).eps*h.max() ] = 0
sumh = h.sum()
if sumh != 0:
h /= sumh
return h
def filter2(x, kernel, mode='same'):
return convolve2d(x, np.rot90(kernel, 2), mode=mode)
def compute_ssim(im1, im2, k1=0.01, k2=0.03, win_size=11, L=255):
if not im1.shape == im2.shape:
raise ValueError("Input Imagees must have the same dimensions")
if len(im1.shape) > 2:
raise ValueError("Please input the images with 1 channel")
M, N = im1.shape
C1 = (k1*L)**2
C2 = (k2*L)**2
window = matlab_style_gauss2D(shape=(win_size,win_size), sigma=1.5)
window = window/np.sum(np.sum(window))
if im1.dtype == np.uint8:
im1 = np.double(im1)
if im2.dtype == np.uint8:
im2 = np.double(im2)
mu1 = filter2(im1, window, 'valid')
mu2 = filter2(im2, window, 'valid')
mu1_sq = mu1 * mu1
mu2_sq = mu2 * mu2
mu1_mu2 = mu1 * mu2
sigma1_sq = filter2(im1*im1, window, 'valid') - mu1_sq
sigma2_sq = filter2(im2*im2, window, 'valid') - mu2_sq
sigmal2 = filter2(im1*im2, window, 'valid') - mu1_mu2
ssim_map = ((2*mu1_mu2+C1) * (2*sigmal2+C2)) / ((mu1_sq+mu2_sq+C1) * (sigma1_sq+sigma2_sq+C2))
return np.mean(np.mean(ssim_map))
# 方法2:skimage
import cv2
from skimage import metrics
ssim_i = metrics.structural_similarity(image, result_i)
ssim_w = metrics.structural_similarity(image, result_w)
# ssim_in = measure.compare_ssim(image, result_in)
# ssim_wn = measure.compare_ssim(image, result_wn)
print(ssim_i, ssim_w)
使用上述三种指标评价复原结果:
分析:从之前实验看到,MSE的表现有差别,PSNR表现类似的result_in和result_wn,实际从人的主管感觉来说差别是很大的。这里SSIM中,认为contrast的stretch,以及均值的偏移,即整体上明暗变化,基本上并不会影响人类对图像的内容的理解。因此应该让这样的图片得到的质量值更高。原因在于我们可以把original information近乎完全的恢复出来,只需要做pixel-wise的映射即可。而像比如blur,JPEG compression等,许多结构信息已经永久丢失了,因此应该相似度低。
《Single Image Haze Removal Using Dark Channel Prior》
基于暗通道先验的去雾算法实际上是一种统计意义上的算法,作者总结了大量的室外无雾的图像,发现了在无雾图像中局部区域存在一些像素,这些像素中至少有一个颜色通道的亮度值非常非常低(低亮度值区域不包括天空区域)。由大量雾霾data获得视差图,用于对输入的去雾霾处理。
《Removing camera shake from a single photograph》
与去雾霾方法类似,提供统计方法估计模糊核,以此复原清晰图 L L L: B = K × L + N B = K \times L+N B=K×L+N。算法主要分为两步:1、根据输入图像估计模糊核,以由粗到精的方式避免局部极小值。2、利用估计的模糊核,采用标准的去卷积算法估计清晰图像。
首先需要提供四个输入:1、模糊图像B;2、模糊图像的一个矩形块;3、模糊核的大小上限值(像素);4、关于模糊核方向的初步猜测,除此之外,我们需要在处理之前将图像B转换到一个线性的色彩空间,利用反伽玛校正(inverse gamma-correction), γ \gamma γ的值为2.2。根据用户指定的块,将原始图像的所有颜色通道联合一起产生灰度模糊块P。