Learning OpenCV with iOS:图像模糊--线性滤波

前言

上一篇我们讲解了 OpenCV 的图像亮度和对比度调整。本篇主要向大家介绍下图像模糊。按惯例,先来一张效果图。

模糊

所谓模糊,可以先简单理解为每一个像素都取周边像素的平均值。

上图中,2 是中间点像素值,周边像素都是 1。

中间点取周围点的平均值,就会变成 1。在数值上叫平滑。在图形上,就产生了模糊效果,也就是中间点失去了细节。

图像模糊

图像模糊是 opencv 常见的操作,使用模糊操作的原因是为了给图像预处理时降低噪声影响。 Smooth 和 Blur 是 opencv 图像模糊的 API,其背后的原理其实是数学的卷积操作

其中权重核 h(k,l) 为“滤波系数”。上面的式子可以简记为:

通常这些卷积算子计算都是线性操作,所以又叫线性滤波

线性滤波

均值滤波

均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素,再用模板中的全体像素的平均值来代替原来像素值。

还记得第二篇里所讲掩膜操作吧,均值滤波的过程跟掩膜操作极其相似。

滤波操作:在 9x9 上面有 3x3 的窗口,从左到右,从上到下移动,白色的每个像素点值之和取平均值赋给中心红色像素作为它处理之后的像素值。其中,模板就是 3x3 的窗口,红色格子为目标像素,白色格子为周围的临近像素

此类操作被称为卷积计算,而模板和 kernel 就是卷积计算中的卷积算子

opencv 提供了均值滤波(模糊)的 API

1
2
3
4
5
6
7
8
9
10
/**
@param src 输入图像
@param dst 输出图像
@param ksize 模糊kernel大小
@param anchor 锚点; 默认值为Point(-1,-1) ,表示在锚点在kernel的中心
@param borderType 查看#BorderTypes
 */
blur( InputArray src, OutputArray dst,
                        Size ksize, Point anchor = Point(-1,-1),
                        int borderType = BORDER_DEFAULT );

铠玩模糊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
+ (UIImage *)blur:(UIImage *)image sizeX:(int)sizeX sizeY:(int)sizeY {
    Mat src;
    UIImageToMat(image, src);

    Mat dst;
    blur(src, dst, cv::Size(sizeX, sizeY));

    UIImage* result = MatToUIImage(dst);

    return result;
}

class BlurViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var resultImageView: UIImageView!

    private var sizeX: Int32 = 3
    private var sizeY: Int32 = 3

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func onSliderValueChanged(_ sender: UISlider) {
        sizeX = Int32(sender.value)
        transform()
    }

    @IBAction func onSlider2ValueChanged(_ sender: UISlider) {
        sizeY = Int32(sender.value)
        transform()
    }

    private func transform() {
        let image = OpenCV.blur(imageView.image, sizeX: sizeX, sizeY: sizeY)
        resultImageView.image = image
    }
}

一些思考

滤波操作为何能将图像模糊?

滤波操作其实是用周围的像素平均值作为目标像素的值,经过这样的处理后其实就是整个图像像素值差距缩小。差距缩小了自然就比较没有辨识度了,也就是模糊了。

为何在铠玩模糊中,只改变 Size 的 x 大小会让铠在 x 轴方向模糊?

这题就当讨论题吧,有兴趣的朋友可以在文章的评论处讨论。 提示:改变 x 的值其实就是改变 kernel 的形态,单独将 x 增加,就相当于 kernel 的形态变成宽度大于高度的长方形。

均值模糊有没有什么问题?

我们知道图像都是连续的越靠近的点关系越密切,越远离的点关系越疏远,均值模糊只是简单的取平均,没有分配权重,肯定存在不合理之处。相比之下,加权平均更合理,距离越近的点权重越大,距离越远的点权重越小。 下面我们就来看下带权重的高斯滤波。

高斯滤波

正态分布

如上图,正态分布是一种钟摆形曲线,越接近中心,取值越大,越远离中心,取值越小。 计算平均值的时候,我们只需要将中心点作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。

高斯函数

一维函数

σ:标准差,在这里又叫做高斯半径。 σ2:方差。 f(x):概率 μ:均值,即期望。

在计算平均值的时候,中心点就是原点,所以 μ 等于 0。可得简化后的函数:

根据一维函数,可以推导得到二维函数:

图像是二维的,所以通常处理图像时,我们使用二维高斯函数。

计算例子

假定中心点的坐标是(0,0),那么距离它最近的 8 个点的坐标如下:

假设 σ=1.5,则权重矩阵如下:

这 9 个点的权重总和等于 0.4787147,归一化后得到最终的权重矩阵:

假设现有图像矩阵如下:

与权重相乘后得到的矩阵如下:

将这 9 个值相加就是中心点的最终值:13.9401236。而通过均值滤波得到的结果是 13.5。

均值与高斯哪家强

1
2
3
4
5
6
7
8
9
10
11
12
13
@param sigmaX Gaussian kernel standard deviation in X direction.
@param sigmaY Gaussian kernel standard deviation in Y direction; if sigmaY is zero, it is set to be
equal to sigmaX, if both sigmas are zeros, they are computed from ksize.width and ksize.height,
respectively (see #getGaussianKernel for details); to fully control the result regardless of
possible future modifications of all this semantics, it is recommended to specify all of ksize,
sigmaX, and sigmaY.
@param borderType pixel extrapolation method, see #BorderTypes

@sa  sepFilter2D, filter2D, blur, boxFilter, bilateralFilter, medianBlur
 */
CV_EXPORTS_W void GaussianBlur( InputArray src, OutputArray dst, Size ksize,
                                double sigmaX, double sigmaY = 0,
                                int borderType = BORDER_DEFAULT );
1
2
3
4
5
6
7
8
9
10
11
+ (UIImage *)gaussianblur:(UIImage *)image sizeX:(int)sizeX sizeY:(int)sizeY {
    Mat src;
    UIImageToMat(image, src);

    Mat dst;
    GaussianBlur(src, dst, cv::Size(sizeX, sizeY), 11);

    UIImage* result = MatToUIImage(dst);

    return result;
}

仔细观看可以看到,高斯模糊图像的轮廓较均值的清晰些,没有那么“模糊”。

小结

本篇主要介绍了图像模糊的概念,并通过例子讲解了均值模糊和高斯模糊。模糊经常在图像预处理降时使用到,需要好好掌握其原理,以便于应对不同情况。今天就到这了,有疑问的朋友可以给我留言,咱们下篇见!


CatchZeng
Written by CatchZeng Follow
AI (Machine Learning) and DevOps enthusiast.