Python 图像处理 OpenCV (7):图像平滑(滤波)处理

前文传送门:python

「Python 图像处理 OpenCV (1):入门」算法

「Python 图像处理 OpenCV (2):像素处理与 Numpy 操做以及 Matplotlib 显示图像」dom

「Python 图像处理 OpenCV (3):图像属性、图像感兴趣 ROI 区域及通道处理」函数

「Python 图像处理 OpenCV (4):图像算数运算以及修改颜色空间」.net

「Python 图像处理 OpenCV (5):图像的几何变换」3d

「Python 图像处理 OpenCV (6):图像的阈值处理」code

1. 引言

第一件事情仍是先作名词解释,图像平滑究竟是个啥?orm

从字面意思理解貌似图像平滑好像是在说图像滑动。blog

emmmmmmmmmmmmmmm。。。。图片

其实半毛钱关系也没有,图像平滑技术一般也被成为图像滤波技术(这个名字看到可能你们会有点感受)。

每一幅图像都包含某种程度的噪声,噪声能够理解为由一种或者多种缘由形成的灰度值的随机变化,如由光子通量的随机性形成的噪声等等。

而图像平滑技术或者是图像滤波技术就是用来处理图像上的噪声,其中,可以具有边缘保持做用的图像平滑处理,成为了你们关注的重点。

这不废话,处理个图片降噪,结果把整个图像搞的跟玻璃上糊上了一层水雾同样,这种降噪有啥意义。

本文会介绍 OpenCV 中提供的图像平滑的 4 个算法:

  • 均值滤波
  • 方框滤波
  • 高斯滤波
  • 中值滤波

下面开始一个一个看吧:)

先给出一个给马里奥加噪声的程序,程序来源于杨老师的博客:http://www.javashuo.com/article/p-pvyzzktp-d.html ,完整代码以下:

import cv2 as cv
import numpy as np

# 读取图片
img = cv.imread("maliao.jpg", cv.IMREAD_UNCHANGED)
rows, cols, chn = img.shape

# 加噪声
for i in range(5000):
    x = np.random.randint(0, rows)
    y = np.random.randint(0, cols)
    img[x, y, :] = 255

cv.imshow("noise", img)

# 图像保存
cv.imwrite("maliao_noise.jpg", img)

# 等待显示
cv.waitKey()
cv.destroyAllWindows()

上面这段程序其实是在图片上随机加了 5000 个白点,这个噪声真的是够大的了。

2. 2D 图像卷积

在介绍滤波以前先简单介绍下 2D 图像卷积,图像卷积其实就是图像过滤。

图像过滤的时候可使用各类低通滤波器( LPF ),高通滤波器( HPF )等对图像进行过滤。

低通滤波器( LPF )有助于消除噪声,可是会使图像模糊。

高通滤波器( HPF )有助于在图像中找到边缘。

OpenCV 为咱们提供了一个函数 filter2D() 来将内核与图像进行卷积。

咱们尝试对图像进行平均滤波, 5 x 5 平均滤波器内核以下:

$$
K = \frac{1}{25}
\begin{bmatrix}
1 & 1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 & 1
\end{bmatrix}
$$

具体操做以下:

咱们保持这个内核在一个像素上,将全部低于这个内核的 25 个像素相加,取其平均值,而后用新的平均值替换中心像素。它将对图像中的全部像素继续此操做,完整的示例代码以下:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

# 读取图片
img = cv.imread("maliao_noise.jpg", cv.IMREAD_UNCHANGED)
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)

kernel = np.ones((5,5),np.float32)/25

dst = cv.filter2D(rgb_img, -1, kernel)

titles = ['Source Image', 'filter2D Image']
images = [rgb_img, dst]

for i in range(2):
    plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

plt.show()

能够看到,噪点确实去除掉了,就是图片变得模糊起来。

3. 均值滤波

均值滤波是指任意一点的像素值,都是周围 N * M 个像素值的均值。

其实均值滤波和上面的那个图像卷积的示例,作了一样的事情,我只是用 filter2D() 这个方法手动完成了均值滤波,实际上 OpenCV 为咱们提供了专门的均值滤波的方法,前面图像卷积没有看明白的同窗,能够再一遍均值滤波,我尽可能把这个事情整的明白的。

仍是来画个图吧:

中间那个红色的方框里面的值,是周围 25 个格子区域中的像素的和去除以 25 ,这个公式是下面这样的:

$$
K = \frac{1}{25}
\begin{bmatrix}
1 & 1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 & 1
\end{bmatrix}
$$

我为了偷懒,全部的格子里面的像素值都写成 1 ,毕竟 n / n 永远都等于 1 ,快夸我机智。

上面这个 5 * 5 的矩阵称为核,针对原始图像内的像素点,采用核进行处理,获得结果图像。

这个核咱们能够自定义大小,好比 5 * 5 ,3 * 3 , 10 * 10 等等,具体定义多大彻底看疗效。

OpenCV 为我提供了 blur() 方法用做实现均值滤波,原函数以下:

def blur(src, ksize, dst=None, anchor=None, borderType=None)
  • kSize: 内核参数,其实就是图片进行卷积的时候相乘的那个矩阵,具体的卷积是如何算的,网上有不少,我这里就不介绍了,所获得的图像是模糊的,并且图像实际上是按照原来的比例缺乏了(原图像-内核参数+1)^2 个单元格。
  • anchor: Point 类型,即锚点,有默认值 Point(-1, -1) ,当坐标为负值,就表示取核的中心。
  • borderType: Int 类型,用于推断图像外部像素的某种边界模式,有默认值 BORDER_DEFAULT 。

接下来是均值滤波的示例代码:

import cv2 as cv
import matplotlib.pyplot as plt

# 读取图片
img = cv.imread("maliao_noise.jpg", cv.IMREAD_UNCHANGED)
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)

# 均值滤波
blur_img = cv.blur(rgb_img, (3, 3))
# blur_img = cv.blur(img, (5, 5))
# blur_img = cv.blur(img, (10, 10))
# blur_img = cv.blur(img, (20, 20))

titles = ['Source Image', 'Blur Image']
images = [rgb_img, blur_img]

for i in range(2):
    plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

plt.show()

这个降噪的效果好像没有前面 2D 卷积的那个降噪效果好,可是图像更为清晰,由于我在这个示例中使用了更小的核 3 * 3 的核,顺便我也试了下大核,好比代码中注释掉的 10 * 10 的核或者 20 * 20 的核,实时证实,核越大降噪效果越好,可是相反的是图像会越模糊。

4. 方框滤波

方框滤波和均值滤波核基本一致,其中的区别是需不须要进行归一化处理。

什么是归一化处理等下再说,咱们先看方框滤波的原函数:

def boxFilter(src, ddepth, ksize, dst=None, anchor=None, normalize=None, borderType=None)
  • src: 原始图像。
  • ddepth: Int 类型,目标图像深度,一般用 -1 表示与原始图像一致。
  • kSize: 内核参数。
  • dst: 输出与 src 大小和类型相同的图像。
  • anchor: Point 类型,即锚点,有默认值 Point(-1, -1) 。
  • normalize: Int 类型,表示是否对目标图像进行归一化处理。

当 normalize 为 true 时,须要执行均值化处理。

当 normalize 为 false 时,不进行均值化处理,其实是求周围各像素的和,很容易发生溢出,溢出时均为白色,对应像素值为 255 。

完整示例代码以下:

import cv2 as cv
import matplotlib.pyplot as plt

# 读取图片
img = cv.imread('maliao_noise.jpg')
source = cv.cvtColor(img, cv.COLOR_BGR2RGB)

# 方框滤波
result = cv.boxFilter(source, -1, (5, 5), normalize = 1)

# 显示图形
titles = ['Source Image', 'BoxFilter Image']
images = [source, result]

for i in range(2):
    plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

plt.show()

当咱们把 normalize 的属性设为 0 时,不进行归一化处理,结果就变成了下面这个样子:

5. 高斯滤波

为了克服简单局部平均法的弊端(图像模糊),目前已提出许多保持边缘、细节的局部平滑算法。它们的出发点都集中在如何选择邻域的大小、形状和方向、参数加平均及邻域各店的权重系数等。

在高斯滤波的方法中,其实是把卷积核换成了高斯核,那么什么是高斯核呢?

简单来说就是方框仍是那个方框,原来每一个方框里面的权是相等的,你们最后取平均,如今变成了高斯分布的,方框中心的那个权值最大,其他方框根据距离中心元素的距离递减,构成一个高斯小山包,这样取到的值就变成了加权平均。

下图是所示的是 3 * 3 和 5 * 5 领域的高斯核。

高斯滤波是在 OpenCV 中是由 GaussianBlur() 方法进行实现的,它的原函数以下:

def GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None)
  • sigmaX: 表示 X 方向方差。

这里须要注意的是 ksize 核大小,在高斯核当中,核 (N, N) 必须是奇数, X 方向方差主要控制权重。

完整的示例代码以下:

import cv2 as cv
import matplotlib.pyplot as plt

# 读取图片
img = cv.imread('maliao_noise.jpg')
source = cv.cvtColor(img, cv.COLOR_BGR2RGB)

# 方框滤波
result = cv.GaussianBlur(source, (3, 3), 0)

# 显示图形
titles = ['Source Image', 'GaussianBlur Image']
images = [source, result]

for i in range(2):
    plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

plt.show()

6. 中值滤波

在使用邻域平均法去噪的同时也使得边界变得模糊。

而中值滤波是非线性的图像处理方法,在去噪的同时能够兼顾到边界信息的保留。

中值滤波具体的作法是选一个含有奇数点的窗口 W ,将这个窗口在图像上扫描,把窗口中所含的像素点按灰度级的升或降序排列,取位于中间的灰度值来代替该点的灰度值。

下图是一个一维的窗口的滤波过程:

在 OpenCV 中,主要是经过调用 medianBlur() 来实现中值滤波,它的原函数以下:

def medianBlur(src, ksize, dst=None)

中值滤波的核心数和高斯滤波的核心数同样,必需要是大于 1 的奇数。

示例代码以下:

import cv2 as cv
import matplotlib.pyplot as plt

# 读取图片
img = cv.imread('maliao_noise.jpg')
source = cv.cvtColor(img, cv.COLOR_BGR2RGB)

# 方框滤波
result = cv.medianBlur(source, 3)

# 显示图形
titles = ['Source Image', 'medianBlur Image']
images = [source, result]

for i in range(2):
    plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

plt.show()

能够明显看到,目前中值滤波是对原图像降噪后还原度最高的,经常使用的中值滤波的图形除了可使用方框,还有十字形、圆形和环形,不一样形状的窗口产生不一样的滤波效果。

方形和圆形窗口适合外轮廓线较长的物体图像,而十字形窗口对有尖顶角状的图像效果好。

对于一些细节较多的复杂图像,能够屡次使用不一样的中值滤波。

7. 示例代码

若是有须要获取源码的同窗能够在公众号回复「OpenCV」进行获取。

8. 参考

http://www.javashuo.com/article/p-pvyzzktp-d.html

http://www.woshicver.com/

相关文章
相关标签/搜索