空域变换包含灰度变换和空间滤波
灰度变换是通过点对点的映射进行图像增强,是一种点运算
空间滤波是基于邻域的一种运算,即图像像素的灰度值不仅和当前点有关,还和这个点周围邻域像素点的灰度值有关。所以空间滤波其实是一种加权求和的运算
空间滤波可以分为两大类:平滑和锐化
平滑是通过模糊图像来将输入图像进行平滑,它计算领域像素灰度值的平均值作为输出,类似于积分运算。因为高频的部分会被平均值吸收掉,所以平滑处理也类似是一种低通滤波
平滑的作用可以分为两类:
平滑滤波器需要保证滤波器的权重和为1,这有两个目的。
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
dst=cv2.blur(img,(3,3))
cv2.imshow('img', np.hstack((img, dst)))
cv2.imwrite('./image.png',np.hstack((img, dst)))
cv2.waitKey()
cv2.destroyAllWindows()
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
kernel=np.ones((3,3),dtype=np.float32)/9
dst=cv2.filter2D(img,-1,kernel)
cv2.imshow('img', np.hstack((img, dst)))
cv2.imwrite('./image.png',np.hstack((img, dst)))
cv2.waitKey()
cv2.destroyAllWindows()
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
kernel=np.array([[1,2,1],[2,4,2],[1,2,1]],dtype=np.float32)/16
dst=cv2.filter2D(img,-1,kernel)
cv2.imshow('img', np.hstack((img, dst)))
cv2.imwrite('./image.png',np.hstack((img, dst)))
cv2.waitKey()
cv2.destroyAllWindows()
邻域均值滤波器在平滑图像的时候虽然会降低了噪声,但是同样模糊了图像 .
我们在模糊图像的时候应该考虑一点的就是距离中心像素的的权重应该大一点,远离中心像素点的影响要弱一些。满足这一特性的话,最好的滤波器是高斯核。
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
dst=cv2.GaussianBlur(img,(5,5),sigmaX=0.5)
cv2.imshow('img', np.hstack((img, dst)))
cv2.imwrite('./image.png',np.hstack((img, dst)))
cv2.waitKey()
cv2.destroyAllWindows()
几何均值滤波器和算术平均滤波器的平滑效果类似,但是损失的图像细节更少
import cv2
import numpy as np
def geometric_mean_filter(img, kernel_size=3):
height, width = img.shape[:2] # 获取图像的size
ksize = kernel_size # 滤波器的大小
order = 1 / pow(ksize, 2) # 阶次
pad = int((ksize - 1) / 2) # 填充的大小
img_pad = np.pad(img, pad, mode='edge') # 边缘padding
for i in range(pad, pad + height):
for j in range(pad, pad + width):
ret = np.prod(img_pad[i - pad:i + pad + 1, j - pad:j + pad + 1], dtype=np.float32) # 计算kernel里面的乘积
img[i - pad][j - pad] = pow(ret, order)
return img.astype(np.uint8)
img = cv2.imread("./img1.png", 0)
img_copy = img.copy() # 图像备份
dst = geometric_mean_filter(img_copy) # 几何均值滤波器
cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey()
cv2.destroyAllWindows()
谐波平均滤波器既能处理盐粒噪声,又能处理类似于高斯噪声,但是不能处理胡椒噪声
盐粒噪声salt : 是指灰度值突变成白色点的噪声(白色--->255)
胡椒噪声pepper:是指灰度值突变成黑色点的噪声(黑色--->0)
这里并不是特指0和255,是指相对突变的颜色
import cv2
import numpy as np
def harmonic_averaging_filter(img,kernel_size = 3): # 定义谐波平均滤波器函数
height ,width = img.shape[:2] # 获取图像的size
ksize = kernel_size # 滤波器的大小
pad = int((ksize - 1) / 2) # 填充的大小
img_pad = np.pad(img,pad,mode='edge') # 边缘padding
bais = 1e-8 # 极小值ε
for i in range(pad,pad+height):
for j in range(pad,pad+width):
ret = np.sum(1.0 / (img_pad[i - pad:i + pad + 1, j - pad:j + pad + 1] + bais),dtype=np.float32) # 计算kernel里面的倒数并且求和
img[i - pad][j - pad] = kernel_size * kernel_size / ret
return img.astype(np.uint8)
img = cv2.imread("./img1.png", 0)
for i in range(2000): # 添加2000个胡椒噪声
x = np.random.randint(0,img.shape[0])
y = np.random.randint(0,img.shape[1])
img[x][y] = 0 # 0 为pepper 噪声 255 为salt 噪声
img_copy = img.copy() # 图像备份
dst =harmonic_averaging_filter(img_copy)
cv2.imshow('img',np.hstack((img,dst)))
cv2.waitKey()
cv2.destroyAllWindows()
import cv2
import numpy as np
def harmonic_averaging_filter(img, kernel_size=3): # 定义谐波平均滤波器函数
height, width = img.shape[:2] # 获取图像的size
ksize = kernel_size # 滤波器的大小
pad = int((ksize - 1) / 2) # 填充的大小
img_pad = np.pad(img, pad, mode='edge') # 边缘padding
bais = 1e-8 # 极小值ε
for i in range(pad, pad + height):
for j in range(pad, pad + width):
ret = np.sum(1.0 / (img_pad[i - pad:i + pad + 1, j - pad:j + pad + 1] + bais),
dtype=np.float32) # 计算kernel里面的倒数并且求和
img[i - pad][j - pad] = kernel_size * kernel_size / ret
return img.astype(np.uint8)
img = cv2.imread("./img1.png", 0)
for i in range(2000): # 添加2000个胡椒噪声
x = np.random.randint(0, img.shape[0])
y = np.random.randint(0, img.shape[1])
img[x][y] = 255 # 0 为pepper 噪声 255 为salt 噪声
img_copy = img.copy() # 图像备份
dst = harmonic_averaging_filter(img_copy)
cv2.imshow('img', np.hstack((img, dst)))
cv2.imwrite('./image.png',np.hstack((img, dst)))
cv2.waitKey()
cv2.destroyAllWindows()
Q = 0 的时候,反谐波滤波器简化为算术平均滤波器,也就是邻域均值滤波器
Q > 0 的时候,处理胡椒噪声
Q < 0 的时候,处理盐粒噪声
Q = -1 的时候,简化为谐波平均滤波器
反谐波平均滤波器可以处理salt-pepper噪声,但是不能同时处理很容易理解,处理噪声需要Q满足一定的条件,所以Q不能同时正负
import cv2
import numpy as np
def inverse_harmonic_averaging_filter(img, kernel_size=3, Q=1.5): # 反谐波平均滤波器
height, width = img.shape[:2] # 获取图像的size
ksize = kernel_size # 滤波器的大小
pad = int((ksize - 1) / 2) # 填充的大小
img_pad = np.pad(img, pad, mode='edge') # 边缘padding
order = Q # 阶数
bais = 1e-8 # 偏差,防止分母为 0
for i in range(pad, pad + height):
for j in range(pad, pad + width):
sub_area = img_pad[i - pad:i + pad + 1, j - pad:j + pad + 1] + bais # kernel里面的子区域
img[i - pad][j - pad] = np.sum(pow(sub_area, order + 1)) / np.sum(pow(sub_area, order))
return img.astype(np.uint8)
img = cv2.imread("./img1.png", 0)
img_old = img.copy() # 拷贝原图
for i in range(5000): # 手工添加噪声点,0为pepper噪声,255为salt噪声
x = np.random.randint(0, img.shape[0])
y = np.random.randint(0, img.shape[1])
img[x][y] = 0
img_noise = img.copy() # 备份噪声图像
dst_pos = inverse_harmonic_averaging_filter(img.copy(), Q=1.5)
dst_neg = inverse_harmonic_averaging_filter(img.copy(), Q=-1.5)
cv2.imshow('img', np.hstack((img_old, img_noise, dst_pos, dst_neg))) # 图像顺序:原图、噪声图、处理后的图
cv2.waitKey()
cv2.destroyAllWindows()
统计排序滤波器不同于之前的滤波器,首先滤波器的内部是没有权重的,因为它是一种统计量的表达。并且,统计排序滤波器是将滤波范围内的区域按照设定排序,所以它也区别于几何均值滤波器等等,因为它不对像素点进行改变。
中值滤波是用滤波器处理区域的中值灰度值去代替像素的值,其特点是能够有效降低随机噪声,且模糊程度要小得多,所以中值滤波器处理椒盐噪声的效果尤为突出。
salt-pepper噪声:不是特指0,255这两个像素点,它是指噪声以黑白点的形式叠加在图像上。只要它满足如下两个条件,我们就说它是属于椒盐噪声点
灰度值突变的点,它应该是噪声点,噪声是由图像灰度值突变形成的
灰度值较周围的像素点灰度而言,过亮(最亮为255,属于salt噪声)或者过暗(最暗为0,属于pepper噪声)
最大值滤波是找到滤波器处理区域的最大值灰度值去代替像素的值,它能够找到被处理区域中的最亮点,或者用于削弱与明亮区域相邻的暗区域。而胡椒噪声是灰度值突变成很低的点,所以最大值滤波可以有效降低胡椒噪声
最小值滤波是找到滤波器处理区域的最小值灰度值去代替像素的值,它能够找到被处理区域中的最暗点,或者用于削弱与暗色区域相邻的明亮区域。而盐粒噪声是灰度值突变成很高的点,所以最小值滤波可以有效降低盐粒噪声
中点滤波是找到滤波器处理区域的最小值灰度值和最大值灰度值的中点去代替像素的值,中点滤波器适用于处理随机分布的噪声,例如高斯噪声或者均匀噪声
import cv2
import numpy as np
def statistical_sorting_filter(img, kernel_size=3, transform='median'):
height, width = img.shape[:2] # 获取图像的长宽
ksize = kernel_size # 滤波器的size
pad = int((ksize - 1) / 2) # padding 的大小
img_pad = np.pad(img, pad, mode="edge") # 将原图进行边缘上的填充
for i in range(pad, pad + height): # 遍历img_pad 图像的点,将滤波后的带能赋值给原图img对应位置
for j in range(pad, pad + width):
value = img_pad[i - pad:i + pad + 1, j - pad:j + pad + 1] # 取出子区域
if transform == 'median': # 中值滤波
img[i - pad][j - pad] = np.median(value)
if transform == 'max': # 最大值滤波
img[i - pad][j - pad] = np.max(value)
if transform == 'min': # 最小值滤波
img[i - pad][j - pad] = np.min(value)
if transform == 'middle': # 中点滤波
img[i - pad][j - pad] = value.max() * 0.5 + value.min() * 0.5
return img.astype(np.uint8)
img = cv2.imread('./img1.png', 0)
img_old = img.copy() # 拷贝原图
for i in range(5000): # 随机加入椒盐噪声
x = np.random.randint(0, img.shape[0])
y = np.random.randint(0, img.shape[1])
img[x][y] = np.random.randint(0, 2) * 255 # 生成 0,1 ,扩大255倍,就会生成0,255椒盐点
img_noise = img.copy() # 拷贝噪声图
dst = statistical_sorting_filter(img, transform='max') # 统计滤波器处理
cv2.imshow('img', np.hstack((img_old, img_noise, dst))) # 图像顺序:原图、噪声图、处理后的图像
cv2.waitKey()
cv2.destroyAllWindows()
空间滤波的另一种用途是图像的锐化,锐化的作用是突出灰度中的过渡区域
图像的模糊在空间域当中可以通过平滑(平均)邻域中的像素实现,类似于积分运算
图像的锐化在空间域当中可以通过锐化(差值)邻域中的像素实现,类似于微分运算
因为差值会计算出两个像素灰度值的差异,而在灰度值变换平坦的区域差值几乎为零。所以,锐化滤波的作用是削弱图像灰度值变换缓慢的区域,同时增强灰度值存在明显差异的区域,这些区域往往是边缘或者噪声。
我们经常把高频叫做细节,低频叫做背景
定义差分的方法需要满足如下要求:
灰度台阶:紧邻两个像素点的灰度值突变
灰度斜坡:若干个像素点,紧邻的像素点均有差异,且差异值不变
因为锐化对灰度值平坦的区域不会有反应,所以滤波器的和应该为0
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
img = cv2.resize(img, None, fx=0.6, fy=0.6) # 缩放图像
sobel_x = cv2.Sobel(img, cv2.CV_16S, dx=1, dy=0) # 求取梯度图像,dx代表水平梯度
sobelx = cv2.convertScaleAbs(sobel_x) # 取绝对值,并转为uint8
sobel_y = cv2.Sobel(img, cv2.CV_16S, dx=0, dy=1) # 求取梯度图像,dy代表垂直梯度
sobely = cv2.convertScaleAbs(sobel_y) # 取绝对值,并转为uint8
sobel_img = cv2.addWeighted(sobelx, 1, sobely, 1, 0) # 加权求和
cv2.imshow('img', np.hstack((img, sobelx, sobely, sobel_img)))
cv2.waitKey()
cv2.destroyAllWindows()
Scharr 和 Sobel 算子类似,只不过增加了中心位置的权重
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
img = cv2.resize(img, None, fx=0.6, fy=0.6) # 缩放图像
"""
这里图像的深度不能写-1,负责会和输入图像的数据类型一致,也就是uint8无符号类型,那么梯度出现负的时候就会被零截断,这是我们不想看到的
图像深度的表示如图,这里我们用 CV_16S,因为灰度值差异不会很大
"""
sobel_x = cv2.Scharr(img, cv2.CV_16S, dx=1, dy=0) # 求取梯度图像,dx代表水平梯度
sobelx = cv2.convertScaleAbs(sobel_x) # 取绝对值,并转为uint8
sobel_y = cv2.Scharr(img, cv2.CV_16S, dx=0, dy=1) # 求取梯度图像,dy代表垂直梯度
sobely = cv2.convertScaleAbs(sobel_y) # 取绝对值,并转为uint8
sobel_img = cv2.addWeighted(sobelx, 1, sobely, 1, 0) # 加权求和
cv2.imshow('img', np.hstack((img, sobelx, sobely, sobel_img)))
cv2.imwrite('./image.png',np.hstack((img, sobelx, sobely, sobel_img)))
cv2.waitKey()
cv2.destroyAllWindows()
一阶差分算法可以有效的提取目标的边缘
但是数字图像中的边缘在灰度上通常类似于斜坡过度,而一阶差分的定义是在灰度斜坡的一阶导数必须是非零的,所以图像的一阶差分会导致较宽的边缘
于是我们可以将一阶差分再次进行差分,也就是二阶差分去获取更加精细的图像
二阶差分的定义需要满足以下条件:
灰度值恒定的区域二阶导数必须为零
灰度台阶或者灰度斜坡的开始处和结束处二阶导数必须非零
灰度台阶的二阶导数必须为零
因此二阶差分会突出图像中的急剧灰度过渡,并不强调灰度值缓慢变换的区域,所以往往会产生具有灰色边缘线和其他不连续性的图像,且往往二阶导数的图像都是偏暗的
为了突出图像,往往将拉普拉斯图像和原图像相加,既可以恢复背景特征,又可以保留二阶导数的锐化效果
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
img = cv2.resize(img, None, fx=0.7, fy=0.7) # 缩小图像
img_blur = cv2.GaussianBlur(img, (7, 7), sigmaX=1) # 高斯模糊图像
img_sharpen = cv2.Laplacian(img_blur, cv2.CV_16S) # 拉普拉斯算子处理
img_sharp = cv2.convertScaleAbs(img_sharpen) # 取绝对值,并转为uint8
dst = img - img_sharpen # 叠加到原图
dst = cv2.normalize(dst, None, 0, 255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U) # 归一化
cv2.imshow('img', np.hstack((img, img_blur, img_sharp, dst)))
cv2.waitKey()
cv2.imwrite('./image.png',np.hstack((img, img_blur, img_sharp, dst)))
cv2.destroyAllWindows()