TV去噪的原理描述为:
受噪声污染的图像总变分比无噪图像总变分大
啥是总变分?根据以下公式:
其中 u x u_{x} ux, u y u_{y} uy是像素在 x x x, y y y方向的梯度
可知总变分是梯度幅值的积分,为了消除噪声,需要使总变分( J J J)最小化。
根据公式的字面理解,是要让全图梯度值之和最小?那纯色图的梯度值之和是最小的,那确实也没有噪声了。
实际上为了使去噪结果更接近原图,避免失真,推导时会加入保真项,最后的推导结果是:
这就是 J J J取极值的解,以下的思考与其它地方查到的不同,不知是否更准确:
再举个例子,假如离散序列为【100,100,500,500,500】
梯度是【0,400,0,0,?】( x n + 1 − x n x_{n+1}-x_{n} xn+1−xn)
梯度的梯度是【400,-400,0,?,?】
假如有噪点,那么离散序列是:【100,100,500,100,100】
那么梯度是【0,400,-400,0,?】
梯度的梯度是【400,-800,400,?,?】
明显噪点的 ▽ ( ▽ u ∣ ▽ u ∣ ) \triangledown (\frac{\triangledown u}{|\triangledown u|}) ▽(∣▽u∣▽u)更可观
(以上猜想概不负责)---------------------------------------------------------------------
最后根据论文【变分图像去噪】,们得到了一个迭代公式:
其中 n n n代表迭代次数, n = 0 n=0 n=0时, u 0 u^{0} u0代表输入图, △ t \triangle t △t是时间步长参数,类似权重, λ \lambda λ是正则化参数, d i v p divp divp是扩散项
n = 0 n=0 n=0时, d i v p = 0 divp=0 divp=0
每次迭代,都要用上一次的输出图像 u n u^{n} un减去 △ t ( λ ( u x , y n − u x , y 0 ) − d i v p ) \triangle t(\lambda (u_{x,y}^{n}-u_{x,y}^{0})-divp) △t(λ(ux,yn−ux,y0)−divp),理论上当它跟之前的解一样等于0时,每次迭代的结果相同,意味着噪声被去除了。
如果某个点的divp较大,那么迭代输出与前一次的差距就更大,起到了去噪的作用。
如果divp=0,那么公式变为: u x , y n + 1 = ( 1 − △ t λ ) u x , y n − △ t λ u x , y 0 u_{x,y}^{n+1}=(1-\triangle t\lambda) u_{x,y}^{n}-\triangle t\lambda u_{x,y}^{0} ux,yn+1=(1−△tλ)ux,yn−△tλux,y0,也就是 u x , y n u_{x,y}^{n} ux,yn与 u x , y 0 u_{x,y}^{0} ux,y0的加权和。
然后 u u u的一系列导数计算公式为(我看到有一篇文章搞错了加减号):
具体求解过程,论文中更详细,数学好的朋友可以去看看。变分图像去噪
python代码我会贴在文末,注意迭代次数不能太少, λ \lambda λ=0。
# -*- coding: utf-8 -*-
"""
Created on Sun Mar 7 14:24:12 2021
@author: SEELE
"""
import cv2
import numpy as np
import math
def gasuss_noise(image, mean=0, var=0.001):
image = np.array(image/255, dtype=float)
noise = np.random.normal(mean, var ** 0.5, image.shape)
out = image + noise
if out.min() < 0:
low_clip = -1.
else:
low_clip = 0.
out = np.clip(out, low_clip, 1.0)
out = out*255
#cv.imshow("gasuss", out)
return out
img = cv2.imread('F:/hh/2.jpg')
img = cv2.resize(img,(500,500))
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray = gray.astype('float32')
size = img.shape # h w c
#加高斯噪声
gray = gasuss_noise(gray)
cv2.imshow('addnoise',gray/255)
maxloop = 100
n = 0
#h = 1
t = 0.1
lamda = 0
ep = 1
divp = 0
imtmp = gray.copy()#曾经用等号赋值,吃了大亏
imnew = np.zeros([size[0],size[1]])
for loop in range(0,maxloop):
print("processed %d times" % (loop))
#这段也可以直接用矩阵计算来写
for y in range(1,size[0] - 1):
for x in range(1,size[1] - 1):
ux = (imtmp[y,x+1] - imtmp[y,x-1]) / 2
uy = (imtmp[y+1,x] - imtmp[y-1,x]) / 2
uxx = imtmp[y,x+1] + imtmp[y,x-1] - imtmp[y,x] - imtmp[y,x]
uyy = imtmp[y+1,x] + imtmp[y-1,x] - imtmp[y,x] - imtmp[y,x]
uxy = (imtmp[y+1,x+1] + imtmp[y-1,x-1] - imtmp[y+1,x-1] - imtmp[y-1,x+1]) / 4
divp = ((uy * uy + ep) * uxx - 2* ux * uy * uxy + (ux * ux + ep) * uyy) / ((ux * ux + uy * uy + ep))#防止除0
imnew[y,x] = imtmp[y,x] + t *( lamda * (gray[y,x] - imtmp[y,x]) + divp)
dv = 1* t * lamda * (gray[y,x] - imtmp[y,x])
if x == 25 and y == 15:
print('dp=%d,div=%d,im=%d' % (divp,dv,imnew[y,x]))
if loop != maxloop-1:
imtmp = imnew.copy()
if loop % 20 == 0:
string = 'loop='+str(loop)
cv2.imshow(string,imnew.astype('uint8'))
#可以限制一下范围
#for y in range(1,size[0] - 1):
# for x in range(1,size[1] - 1):
# imnew[y,x] = min(max(imnew[y,x],0),255)
imnew = imnew.astype('uint8')
cv2.imshow('result',imnew)
图中分别是循环0、20、40、60、80、100次的结果,可以看出这个算法其实还是不能直接拿来用的