OpenCV与图像处理学习五——图像滤波与增强:线性、非线性滤波、直方图均衡化与Gamma变换
- 三、图像滤波与增强
- 3.1 线性滤波
- 3.1.1 方框滤波
- 3.1.2 均值滤波
- 3.1.3 高斯滤波
- 3.1.4 一般卷积滤波
- 3.2 非线性滤波
- 3.2.1 中值滤波
- 3.2.2 双边滤波
- 3.3 图像直方图均衡化
- 3.3.1 单通道图像全局均衡化
- 3.3.2 三通道图像全局均衡化
- 3.3.3 局部均衡化/对比度限制自适应直方图均衡化(CLAHE)
- 3.4 Gamma变换
上两次的笔记地址:
OpenCV与图像处理学习三——图像基本操作(1)
OpenCV与图像处理学习四——图像基本操作(2)
这次笔记的主要内容为图像滤波与增强。
对应的OpenCV官方python文档为:
三、图像滤波与增强
所谓滤波实际上是信号处理领域的一个概念,就是将信号中特定频率分量滤除的一项操作。而图像又可以看成是一个二维信号,其中像素值代表信号的强弱。
而图像的高低频的定义如下所示:
- 高频:图像上变化剧烈的部分;
- 低频:图像灰度值变化缓慢,平坦的地方。
根据图像的高低频,设置高通和低通滤波器可以对图像进行相应的变化,其中高通滤波器可以检测变化尖锐,明显的地方,低通滤波器可以让图像变得平滑,消除噪声。
所以总的来说,图像滤波的作用:高通滤波器用于边缘检测(边缘是像素值变化剧烈的地方),低通滤波器用于图像的平滑去噪。
下面会介绍几种滤波的方法,在此之前,还要先了解一个概念:领域算子,如下图所示,利用给定像素周围的像素值决定此像素的最终输出值的一种算子。(其实就是后来的卷积操作)
线性滤波就是一种常用的领域算子,像素的输出取决于输入像素的加权和:
那么非线性滤波自然就不遵循这个规则。
此外,还将介绍可以改善图像视觉效果的技术——直方图均衡化与Gamma变换。
3.1 线性滤波
3.1.1 方框滤波
方框滤波被封装在一个名为boxFilter的函数中,即boxFilter函数的作用是使用方框滤波器(box filter)来模糊一张图片,从src输入,从dst输出,它是用如下的滤波核(领域算子)来计算的:
函数:
dst = cv2.boxFilter( src, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]] )
参数如下所示:
- src:输入图像。
- dst:输出图像,与src同尺寸和类型。
- ddepth:目标图像深度,-1表示使用src的图像深度。
- ksize:滤波核的尺寸,一般为奇数。
- anchor:锚点位置,默认为滤波核的中心位置。
- normalize:表示滤波核是否需要标准化,为True的话就等于均值滤波的滤波核,为False的话很可能卷积计算的结果会超出 0-255 这个范围。
下面来看个例子:
import cv2
import numpy as np
img = cv2.imread('girl2.png', cv2.IMREAD_UNCHANGED)
r = cv2.boxFilter(img, -1, (7,7), normalize = 1)
d = cv2.boxFilter(img, -1, (3,3), normalize = 0)
cv2.namedWindow('img', cv2.WINDOW_AUTOSIZE)
cv2.namedWindow('r', cv2.WINDOW_AUTOSIZE)
cv2.namedWindow('d', cv2.WINDOW_AUTOSIZE)
cv2.imshow('img', img)
cv2.imshow('r', r)
cv2.imshow('d', d)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果如下图所示:
可见如果将normalize参数设置为False,图像计算出来的输出很可能导致值过大。
3.1.2 均值滤波
均值滤波是一种最简单的滤波处理,它取得是卷积核区域内元素的均值,用cv2.blur
函数实现,它的卷积核如下所示:
举个3×3的例子:
函数:
dst = cv2.blur( src, ksize[, dst[, anchor[, borderType]]] )
参数和方框滤波很相似,可以直接参考方框滤波的参数解释。官方文档特意强调了一句:
The call blur(src, dst, ksize, anchor, borderType)
is equivalent to boxFilter(src, dst, src.type(), anchor, true, borderType)
.
再次说明均值滤波是方框滤波的一个常用的特例。
下面看个例子:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('image/opencv.png')
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
blur = cv2.blur(img, (3, 3))
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.subplot(122), plt.imshow(blur), plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()
输出结果为:
均值滤波之后的结果是会变的比较模糊,将核尺寸改为(7,7),输出结果为:
所以,均值滤波的核的尺寸越大,滤波之后的图像越模糊。
3.1.3 高斯滤波
高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。高斯滤波的卷积核权重就不像前面两种滤波方法是一样的了,它的中间像素点的权重最高,越远离中心的像素权重越小,其原理是一个二维高斯函数,如下图所示:
高斯滤波相比均值滤波效率要慢,但是可以有效消除高斯噪声,能保留更多的图像细节,所以被称为最有用的滤波器。
函数:
dst = cv2.GaussianBlur( src, ksize, sigmaX[, dst[, sigmaY[, borderType]]] )
参数如下所示:
- src:输入图像。
- ksize:高斯核的尺寸,核的高度和宽度可以不等,但是必须是正的奇数,也可以是0,那就通过sigma参数计算得到。
- sigmaX:X方向上高斯分布的标准差,调整sigma其实是在调整周围像素对当前像素的影响程度,调大sigma即提高了远处像素对中心像素的影响程度,滤波结果也就越平滑。
- sigmaY:Y方向上高斯分布的标准差。
看个例子:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('image/median.png')
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
blur = cv.GaussianBlur(img, (7, 7), 7)
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(blur), plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()
结果如下所示:
可见高斯滤波是一种很好的去除噪声的滤波方法。
3.1.4 一般卷积滤波
上面三种是比较特殊的滤波方法,其卷积核内的权重有规定的要求,而一般的卷积滤波的卷积核理论上可以是任意值,经过适当的选择和设置,可以完成一些特殊的操作。它的像素值计算公式如下所示:
它所用到的函数是cv2.filter2D()
,函数如下:
dst = cv2.filter2D( src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]] )
参数如下所示:
- src:输入图像。
- ddepth:图像深度。
- kernel:卷积核。
- anchor:锚点位置,默认为中心位置。
- delta:可选项,在计算得到的值的基础上加上的值。
看两个例子,一个是用它来做均值滤波,一个是做特殊的操作——锐化,它们的差别就是所使用的卷积核不一样:
均值滤波:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('./image/opencv.png')
kernel = np.ones((5, 5), np.float32)/25
dst = cv.filter2D(img, -1, kernel)
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(dst), plt.title('Averaging')
plt.xticks([]), plt.yticks([])
plt.show()
锐化:
import cv2 as cv
import numpy as npdef custom_blur_demo(image):kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32) # 锐化dst = cv.filter2D(image, -1, kernel=kernel)cv.imshow("custom_blur_demo", dst)src = cv.imread("./image/sharpen.png")
cv.namedWindow("input image", cv.WINDOW_AUTOSIZE)
cv.imshow("input image", src)
custom_blur_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
ps:卷积核像上面那样设置就可以进行一定的锐化处理,即图像看起来更清晰。
3.2 非线性滤波
非线性滤波不像线性滤波那样,是一个和卷积核对应位相乘后相加的线性运算,这里介绍中值滤波核双边滤波。
3.2.1 中值滤波
中值滤波是用像素点邻域灰度值的中值代替该点的灰度值,中值滤波可以去除椒盐噪声和斑点噪声。
函数:
dst = cv2.medianBlur( src, ksize[, dst] )
参数:
- src:输入图像。
- ksize:核的大小,这里不再是卷积核,而只是取中值的一个范围。
看个例子:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('image/median.png')
median = cv.medianBlur(img, 7)plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(median), plt.title('median')
plt.xticks([]), plt.yticks([])
plt.show()
输出结果为:
3.2.2 双边滤波
双边滤波是结合图像的空间邻近度和像素值相似度的一种折中处理,同时考虑空间与信息和灰度相似性,达到保边去噪的目的,具有简单、非迭代和局部处理的特点。
双边滤波在去噪的同时能较好的保留边缘信息。但与其他滤波器相比,速度较慢。高斯滤波器取像素周围的一个邻域并找到它的高斯加权平均值。这种高斯滤波器是空间的函数,也就是说,滤波时考虑了附近的像素,但它不考虑像素是否具有几乎相同的强度。它不考虑像素是否是边缘像素。所以它也模糊了边缘,这是不太好的。
双边滤波器在空间上也采用高斯滤波器,但多了一个高斯滤波器,它是像素差的函数。空间高斯函数只考虑相邻像素的模糊,而灰度差的高斯函数则只考虑与中心像素亮度相近的像素进行模糊处理。所以它保留了边缘,因为边缘的像素会有很大的强度变化。
函数:
dst = cv2.bilateralFilter( src, d, sigmaColor, sigmaSpace[, dst[, borderType]] )
参数如下所示:
- src:输入图像。
- d:像素的邻域直径,若为非正数则通过sigma计算得到。
- sigmaColor:灰度相似性高斯函数标准差。
- sigmaSpace:空间高斯函数标准差。
看个例子:
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('image/bilateral.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
blur = cv2.bilateralFilter(img, -1, 15, 10)
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(blur), plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()
输出结果如下图所示:
边缘信息保留的还可以。
3.3 图像直方图均衡化
3.3.1 单通道图像全局均衡化
目的:直方图均衡化是将原图像通过某种变换,得到一幅灰度直方图为均匀分布的新图像的方法。
基本思想:对在图像中像素个数多的灰度级进行展宽,而对像素个数少的灰度级进行缩减。从而达到清晰图像(增强对比度)的目的。
考虑一个图像,其像素值仅限于某些特定的值范围内。例如,较亮的图像将所有像素限制为高值。但是一个好的图像会有来自图像所有区域的像素。所以你需要将这个直方图拉伸到两端(如下图所示,来自维基百科),这就是直方图均衡化的作用(简单地说)。这通常会提高图像的对比度。更详细的解释可以参考官方文档的内容讲解:https://docs.opencv.org/4.1.2/d5/daf/tutorial_py_histogram_equalization.html
(横坐标为像素值,纵坐标为该像素值对应的像素的个数)
函数:
dst = cv2.equalizeHist( src[, dst] )
参数:
- src:输入的8bit单通道图像。
作用是均衡化一张灰度图的像素值。具体的实现步骤是:
- 统计直方图中每个灰度值出现的次数;
- 计算累计归一化直方图;
- 重新计算像素点的像素值。
来看个例子:
# 导入opencv
import cv2
# 直接读为灰度图像
img = cv2.imread('./image/dark.png', 0)
cv2.imshow("dark", img)
cv2.waitKey(0)
# 调用cv2.equalizeHist函数进行直方图均衡化
img_equal = cv2.equalizeHist(img)cv2.imshow("img_equal", img_equal)
cv2.waitKey(0)
cv2.destroyAllWindows()
效果如下图所示:
原图是一张整体偏暗的图片,直方图均衡化之后对比度得到了提高,变得更清晰明亮了一些;如果原图是一张过亮的图片,那么均衡化会让它更暗一些。
3.3.2 三通道图像全局均衡化
基本思想是先将三通道分离,对三个通道分别进行全局均衡化,然后将均衡化之后的结果再合并通道,恢复成彩色图像,下面看个例子:
import cv2
import numpy as np
img = cv2.imread("./image/dark1.jpg")
# print(img.shape)
cv2.imshow("src", img)
# 彩色图像均衡化,需要分解通道 对每一个通道均衡化
(b, g, r) = cv2.split(img)
bH = cv2.equalizeHist(b)
gH = cv2.equalizeHist(g)
rH = cv2.equalizeHist(r)
# 合并每一个通道
result = cv2.merge((bH, gH, rH))
cv2.imshow("dst", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果如下所示(右边少截了一点):
3.3.3 局部均衡化/对比度限制自适应直方图均衡化(CLAHE)
我们刚刚看到的全局直方图均衡化考虑了图像的全局对比度。在许多情况下,这不是一个好主意。例如,下图显示了输入图像及其全局直方图均衡化后的结果:
直方图均衡化后,背景对比度确实有所改善。但是比较两幅图像中雕像的脸。由于亮度过高,我们丢失了那里的大部分信息。这是因为它的直方图并不像我们在前面的例子中所看到的那样局限于一个特定的区域(试着绘制输入图像的直方图,你会得到更多的直觉)。
为了解决这一问题,可以采用自适应直方图均衡化方法。在这种情况下,图像被分成称为“tiles”的小块(OpenCV中,tileSize默认为8x8)。然后这些块中的每一块都像往常一样被直方图均衡化。所以在一个很小的区域内,直方图会局限在一个很小的区域内(除非有噪声)。如果有噪音,就会被放大。为了避免这种情况,应用对比度限制。如果任何直方图单元高于指定的对比度限制(OpenCV中默认为40),则在应用直方图均衡化之前,这些像素将被剪裁并均匀分布到其他单元。在均衡后,为了去除瓷砖边缘的伪影,采用双线性插值。
OpenCV中这种均衡化对应的函数为:
retval = cv2.createCLAHE( [, clipLimit[, tileGridSize]] )
参数只有两个:
- clipLimit:对比度限制的阈值;
- tileGridSize:局部直方图均衡化的网格尺寸,输入图像将被分成这么大的矩形小块分别均衡化。
看一下实现的代码:
import numpy as np
import cv2 as cv
img = cv.imread('tsukuba_l.png', 0)
# create a CLAHE object (Arguments are optional).
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
cv.imwrite('clahe_2.jpg',cl1)
参见下面的结果,并将其与上面的结果进行比较,尤其是雕像区域:
增强图像中的有用信息,它可以是一个失真的过程,其目的是要改善图像的视觉效果,针对给定图像的应用场合。
3.4 Gamma变换
Gamma变换是对输入图像灰度值进行的非线性操作,使输出图像灰度值与输入图像灰度值呈指数关系:
目的:Gamma变换就是用来图像增强,其提升了暗部细节,通过非线性变换,让图像从曝光强度的线性响应变得更接近人眼感受的响应,即将漂白(相机曝光)或过暗(曝光不足)的图片,进行矫正。
看一个例子:
import cv2
import numpy as np
img=cv2.imread('./image/dark1.jpg')
def adjust_gamma(image, gamma=1.0):invGamma = 1.0/gammatable = []for i in range(256):table.append(((i / 255.0) ** invGamma) * 255)table = np.array(table).astype("uint8")print(table)return cv2.LUT(image, table)img_gamma = adjust_gamma(img, 2.0)
print(img_gamma)
cv2.imshow("img",img)
cv2.imshow("img_gamma",img_gamma)cv2.waitKey(0)
cv2.destroyAllWindows()
先构建一个gamma变换的表table,即0-255变换后变为多少,然后对图像上的每个像素进行一个查表操作,原像素值查表得到变换后的像素值,即cv2.LUT函数的作用,返回的就是gamma变换之后的图像,结果如下所示:
[ 0 15 22 27 31 35 39 42 45 47 50 52 55 57 59 61 63 6567 69 71 73 74 76 78 79 81 82 84 85 87 88 90 91 93 9495 97 98 99 100 102 103 104 105 107 108 109 110 111 112 114 115 116117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134135 136 137 138 139 140 141 141 142 143 144 145 146 147 148 148 149 150151 152 153 153 154 155 156 157 158 158 159 160 161 162 162 163 164 165165 166 167 168 168 169 170 171 171 172 173 174 174 175 176 177 177 178179 179 180 181 182 182 183 184 184 185 186 186 187 188 188 189 190 190191 192 192 193 194 194 195 196 196 197 198 198 199 200 200 201 201 202203 203 204 205 205 206 206 207 208 208 209 210 210 211 211 212 213 213214 214 215 216 216 217 217 218 218 219 220 220 221 221 222 222 223 224224 225 225 226 226 227 228 228 229 229 230 230 231 231 232 233 233 234234 235 235 236 236 237 237 238 238 239 240 240 241 241 242 242 243 243244 244 245 245 246 246 247 247 248 248 249 249 250 250 251 251 252 252253 253 254 255]
若将gamma参数改为小于1的数,如0.8,则结果为:
[ 0 0 0 0 1 1 2 2 3 3 4 5 5 6 6 7 8 89 9 10 11 11 12 13 13 14 15 16 16 17 18 19 19 20 2122 22 23 24 25 25 26 27 28 29 29 30 31 32 33 34 34 3536 37 38 39 40 40 41 42 43 44 45 46 47 47 48 49 50 5152 53 54 55 56 57 58 58 59 60 61 62 63 64 65 66 67 6869 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 8687 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104105 106 107 108 109 110 111 113 114 115 116 117 118 119 120 121 122 123124 125 127 128 129 130 131 132 133 134 135 136 137 139 140 141 142 143144 145 146 147 149 150 151 152 153 154 155 157 158 159 160 161 162 163164 166 167 168 169 170 171 173 174 175 176 177 178 180 181 182 183 184185 187 188 189 190 191 192 194 195 196 197 198 200 201 202 203 204 206207 208 209 210 212 213 214 215 216 218 219 220 221 222 224 225 226 227229 230 231 232 233 235 236 237 238 240 241 242 243 245 246 247 248 250251 252 253 255]
可以通过比较两个gamma变换的表看出区别,前者是将值普遍变大,所以图会变亮,后者是普遍变小,所以变暗。