图像处理-005模糊

图像处理-005模糊

图像是获取信息的重要来源,但图像存在着噪声(过多的干扰信息),清除噪声有利于后续图像信息获取及特征提取。图像处理中,去噪的过程即模糊的过程。

图像模糊也称图像平滑处理。

在数字图像中,图像表示为像素 的二维数组,像素是组成图像的最基本单元,由亮度intensity(灰色图像仅有单个像素亮度)或色彩color(3个像素亮度值)表示。在表示图像的像素数组中,邻近像素点像素值的不同导致图像的不平滑,图像平滑便是指图像邻近像素点像素值趋近。趋近后图像的某些区域会丢失详细信息,表现出图像变模糊。图像模糊去除了图像中的高频信息,达到消除图像噪声、边缘的目标。

图像模糊是图像像素取周围像素平均值。

图像模糊是通过低通滤波卷积核与图像做卷积实现的,背后的实质是图像的卷积运算。卷积运算后图像边缘会变得模糊。所谓卷积是对图像像素的操作,使图像中的每一个像素点均为源图像与卷积核的乘积。如图所示:

在这里插入图片描述

卷积指两个函数通过乘法运算得到第三个函数的过程,运算时两个函数必需具有相同的维数。其物理意义是:一个函数(单位响应)在另一个函数(输入图像~~~~)上的加权叠加。

OpenCV提供了四种主要类型的模糊方式: Averaging, Gaussian Blurring,Median Blurring,Bilateral Filtering.

Averaging(均值模糊)

均值模糊也称均值滤波,邻域平均,是图像模糊处理中最简单的一种,处理时将原图中的一个像素值和它周围临近的N个像素值与卷积核做乘法运算,然后求得平均值,获得新图中该点的像素值。

opencv提供 cv.blur()cv.boxFilter()两种函数用于均值模糊计算。

c:
void cv::blur(InputArray     src, #输入图像
              OutputArray     dst, #与输入图像同大小,同类型的输出图像
              Size     ksize,  #卷积核大小
              Point     anchor = Point(-1,-1), #卷积核锚点位置
              int     borderType = BORDER_DEFAULT #边框类型
)        
Python:
cv.blur(src, ksize[, dst[, anchor[, borderType]]]) -> dst

blur函数使用的卷积核公式:

K = 1 ksize.width   *   ksize.height [ 1 1 1 ⋯ 1 1 1 1 1 ⋯ 1 1 ⋮ ⋮ ⋮ ⋱ ⋮ ⋮ 1 1 1 ⋯ 1 1 ] \texttt{K} = \frac{1}{\texttt{ksize.width * ksize.height}} \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 & 1 \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \vdots & \vdots & \vdots & \ddots & \vdots & \vdots \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \end{bmatrix} K=ksize.width * ksize.height1 111111111111111

C:
void cv::boxFilter(InputArray     src, #输入图像
                   OutputArray     dst, #与输入图像同大小,同类型的输出图像
                   int     ddepth, #输出图像深度,默认值为-1 此时同输入图像
                   Size     ksize, #卷积核大小
                   Point     anchor = Point(-1,-1), #锚点 默认(-1,-1)指卷积核中心位置
                   bool     normalize = true,  #卷积核是否归一化
                   int     borderType = BORDER_DEFAULT #卷积时边界处理
)        
Python:
cv.boxFilter(src, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]]) ->    dst

boxFilter函数使用的公式为:

K = α [ 1 1 1 ⋯ 1 1 1 1 1 ⋯ 1 1 ⋮ ⋮ ⋮ ⋱ ⋮ ⋮ 1 1 1 ⋯ 1 1 ] \texttt{K} = \alpha \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 & 1 \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \vdots & \vdots & \vdots & \ddots & \vdots & \vdots \\ 1 & 1 & 1 & \cdots & 1 & 1 \end{bmatrix} K=α 111111111111111

其中:

α = { 1 ksize.width   *   ksize.height when   normalize=true 1 otherwise \alpha = \begin{cases} \frac{1}{\texttt{ksize.width * ksize.height}} & \texttt{when } \texttt{normalize=true} \\1 & \texttt{otherwise}\end{cases} α={ksize.width * ksize.height11when normalize=trueotherwise

从公式可见,boxFilter函数提供了比blur更丰富的形式,在卷积核做归一化时两函数是一致的。

实现代码: C++

void ImageBlur::image_blur(const Mat &img) {
  logger_info("======image_blur===========");
//  均值模糊
  averaging_blur(img);
  averaging_boxFilter(img);
  averaging_boxFilter_unnormalize(img);


}

void ImageBlur::averaging_blur(const Mat &img) {
  logger_info("======averaging_blur===========");
  Mat dst;
//  卷积和大小
  Size kernel_size = Size(5, 5);
//  锚点 默认卷积核中心点
  Point anchor = Point(-1, -1);
//  均值模糊
  blur(img, dst, kernel_size, anchor);

  imshow("averaging_blur", dst);
}

void ImageBlur::averaging_boxFilter(const Mat &img) {
  logger_info("======averaging_boxFilter===depth=%d========", img.depth());
  Mat dst;
  //  卷积和大小
  Size kernel_size = Size(5, 5);
  //  锚点 默认卷积核中心点
  Point anchor = Point(-1, -1);
  //  均值模糊
  boxFilter(img, dst, img.depth(), kernel_size, anchor);

  imshow("averaging_boxFilter", dst);
}

/**
 * 卷积核不做归一化
 * @param img
 */
void ImageBlur::averaging_boxFilter_unnormalize(const Mat &img) {
  logger_info("======averaging_boxFilter_unnormalize===depth=%d========", img.depth());
  Mat dst;
  //  卷积和大小
  Size kernel_size = Size(13, 13);
  //  锚点 默认卷积核中心点
  Point anchor = Point(-1, -1);
  //  均值模糊
  boxFilter(img, dst, img.depth(), kernel_size, anchor, false);

  imshow("averaging_boxFilter_unnormalize", dst);
}

实现代码: python

def image_blur(origin_image):
    # 均值模糊
    average_blur_kernel_5 = averaging_blur_kernel_5(origin_image)
    average_blur_kernel_11 = averaging_blur_kernel_11(origin_image)
    average_boxfilter_kernel_11 = averaging_boxfilter_kernel_11(origin_image)
    average_boxfilter_kernel_11_unnormalize = averaging_boxfilter_kernel_11_unnormalize(origin_image)

    titles = ['Original Image',
              "averaging_blur_kernel_5",
              "averaging_blur_kernel_11",
              "average_boxfilter_kernel_11",
              "average_boxfilter_kernel_11_unnormalize"]
    images = [cv.cvtColor(origin_image, cv.COLOR_BGR2RGB),
              cv.cvtColor(average_blur_kernel_5, cv.COLOR_BGR2RGB),
              cv.cvtColor(average_blur_kernel_11, cv.COLOR_BGR2RGB),
              cv.cvtColor(average_boxfilter_kernel_11, cv.COLOR_BGR2RGB),
              cv.cvtColor(average_boxfilter_kernel_11_unnormalize, cv.COLOR_BGR2RGB)]
    for i in range(5):
        plt.subplot(2, 3, i + 1)
        plt.imshow(images[i], 'gray', vmin=0, vmax=255)
        plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()

    key = cv.waitKey(0)
    while key != ord('q'):
        key = cv.waitKey(0)

    cv.destroyAllWindows()


def averaging_blur_kernel_5(origin_img):
    logger.log.info("averaging_blur_kernel_5")
    # 卷积核大小
    kernel_size = (5, 5)
    # 锚点
    anchor = (-1, -1)
    dst = cv.blur(origin_img, kernel_size, None, anchor)
    cv.imshow("averaging_blur_kernel_5", dst)
    return dst


def averaging_blur_kernel_11(origin_img):
    logger.log.info("averaging_blur_kernel_11")
    # 卷积核大小
    kernel_size = (11, 11)
    # 锚点
    anchor = (-1, -1)
    dst = cv.blur(origin_img, kernel_size, None, anchor)
    cv.imshow("averaging_blur_kernel_5", dst)
    return dst


def averaging_boxfilter_kernel_11(origin_img):
    logger.log.info("averaging_boxfilter_kernel_11")
    # 卷积核大小
    kernel_size = (11, 11)
    # 锚点
    anchor = (-1, -1)
    dst = cv.boxFilter(origin_img, -1, kernel_size, None, anchor)
    cv.imshow("averaging_boxfilter_kernel_11", dst)
    return dst


def averaging_boxfilter_kernel_11_unnormalize(origin_img):
    logger.log.info("averaging_boxfilter_kernel_11_unnormalize")
    # 卷积核大小
    kernel_size = (11, 11)
    # 锚点
    anchor = (-1, -1)
    dst = cv.boxFilter(origin_img, -1, kernel_size, None, anchor, 0)
    cv.imshow("averaging_boxfilter_kernel_11_unnormalize", dst)
    return dst

处理效果如下图所示:

在这里插入图片描述

Gaussian Blurring(高斯模糊)

均值模糊中确定某个像素点的值取周围像素点的像素均值,未考虑周围像素点远近的影响。理论上讲,离得越近影响因子越大,反之亦然。为解决均值模糊未考虑周围距离的远近带来的影响引入高斯模糊。

高斯模糊特征指对图像应用高斯函数后使图像变得平滑以消除图像噪声的现象。与均值模糊不同的是高斯模糊采用了一种非均匀低通滤波器。更高地保留了图像的边缘效果,降低图像噪声,忽略图像中的细节,高斯模糊通常将图像与高斯卷积核做卷积实现。

高斯卷积核使用正态分布来确立卷积核的系数,其高斯卷积核系数公式如下所示:

G 2 D ( x , y , σ ) = 1 2 π σ 2 e − x 2 + y 2 2 σ 2 G_{2D}(x,y,\sigma) = \frac{1}{2\pi\sigma^2} e^{ - \frac{x^2 + y^2}{2\sigma^2}} G2D(x,y,σ)=2πσ21e2σ2x2+y2

其中,x, y是卷积核元素的位置, σ \sigma σ是卷积核元素分布的标准差。

opencv提供了 cv.getGaussianKernel()函数来获取高斯卷积核系数矩阵,矩阵大小为 ksize × 1 \texttt{ksize} \times 1 ksize×1,其公式如下:

G i = α ∗ e − ( i − ( ksize − 1 ) / 2 ) 2 / ( 2 σ 2 ) G_i= \alpha *e^{-(i-( \texttt{ksize} -1)/2)^2/(2 \sigma^2)} Gi=αe(i(ksize1)/2)2/(2σ2)

其中 ksize − 1 \texttt{ksize}-1 ksize1 α \alpha α为系数,使得 ∑ i G i = 1 \sum_i G_i = 1 iGi=1, 即卷积核系数和为1。

Mat cv::getGaussianKernel(int     ksize,     # 卷积核大小,为正奇数
                          double     sigma, #高斯分布标准差,若为负数,则置为:sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8
                          int     ktype = CV_64F #高斯卷积类型  CV_32F  CV_64F .
                          )        
Python:
cv.getGaussianKernel(ksize, sigma[, ktype]) ->    retval

对图像做高斯模糊时opencv提供了 cv.GaussianBlur()。其详细说明如下:

void cv::GaussianBlur(InputArray     src,  #源图像
                      OutputArray     dst, #目标图像 与源图像同大小同类型
                      Size     ksize,      #卷积核大小ksize.width ksize.height均需是正的奇数, 若为0则通过sigma计算得到
                      double     sigmaX,   #X轴高斯标准差
                      double     sigmaY = 0, #Y轴高斯标准差
                      int     borderType = BORDER_DEFAULT # 卷积时边界处理方式
                      )        
Python:
cv.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) ->    dst

实现代码: C++

/**
 * 高斯模糊 kernel
 * @param img
 */
void ImageBlur::gaussian_blur_kernel(const Mat &img) {
  logger_info("======gaussian_blur===========");
  Mat dst;
  for (int i = 1; i < 31; i = i + 2) {
//卷积核越大 模糊越明显
    GaussianBlur(img, dst, Size(i, i), 0, 0);
    Mat kernel = getGaussianKernel(i, 0);
    imshow("gaussian_blur_kernel" + i, dst);
  }
}

实现代码: python

# 高斯模糊 X轴标准差0
def gaussian_blur_sigma_0(origin_image):
    logger.log.info("gaussian_blur")
    titles = ["origin_image"]
    images = [cv.cvtColor(origin_image, cv.COLOR_BGR2RGB)]
    for i in range(1, 31, 2):
        dst = cv.GaussianBlur(origin_image, (i, i), 0)
        titles.append("gaussian_blur_" + str(i))
        images.append(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
    for i in range(16):
        plt.subplot(4, 4, i + 1)
        plt.imshow(images[i], 'gray', vmin=0, vmax=255)
        plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()
    return dst


# 高斯模糊 X轴标准差5
def gaussian_blur_sigma_5(origin_image):
    logger.log.info("gaussian_blur")
    titles = ["origin_image"]
    images = [cv.cvtColor(origin_image, cv.COLOR_BGR2RGB)]
    for i in range(1, 31, 2):
        dst = cv.GaussianBlur(origin_image, (i, i), 5)
        titles.append("gaussian_blur_" + str(i))
        images.append(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
    for i in range(16):
        plt.subplot(4, 4, i + 1)
        plt.imshow(images[i], 'gray', vmin=0, vmax=255)
        plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()
    return dst

不同卷积核下,X轴标准差为0时的效果:
在这里插入图片描述

不同卷积核下,X轴标准差为5时的效果:

在这里插入图片描述

Median Blurring(中值模糊)

中值模糊与均值类似,不同的是中值模糊图像中卷积核锚点对应位置的像素值使用卷积核覆盖区域下所有元素中值替换,中值模糊对处理图像中的椒盐噪声非常有效。椒盐噪声指一种随机出现的黑点(胡椒)或者白点(盐),前者是高灰度噪声,后者是低灰度噪声,一般两者同时出现在图像中。在均值模糊和高斯模糊中卷积核锚点对应图像位置的像素值是重新计算的,中值模糊中该处的值为卷积核覆盖图像区域下的某个点的值。

OpenCV提供了中值模糊函数cv.medianBlur()

void cv::medianBlur(InputArray     src,     #输入图像
                    OutputArray     dst, #输出图像,与输入图像同类型,同大小
                    int     ksize)         #卷积核大小,非负的奇数
Python:
cv.medianBlur(src, ksize[, dst]    )->dst

实现代码: C++

/**
 * 中值模糊
 * @param img
 */
void ImageBlur::median_blur(const Mat &img) {
  logger_info("======median_blur===========");
  Mat dst;
  medianBlur(img, dst, 15);
  imshow("median_blur", dst);
}

实现代码: Python

# 中值模糊
def median_blur(origin_image):
    logger.log.info("median_blur")
    titles = ["origin_image"]
    images = [cv.cvtColor(origin_image, cv.COLOR_BGR2RGB)]
    for i in range(1, 31, 2):
        dst = cv.medianBlur(origin_image, i)
        titles.append("median_blur_" + str(i))
        images.append(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
    for i in range(16):
        plt.subplot(4, 4, i + 1)
        plt.imshow(images[i], 'gray', vmin=0, vmax=255)
        plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()
    return dst

在这里插入图片描述

Bilateral Filtering(双边滤波)

双边滤波能在保留图像边缘的一种模糊方式,避免边缘信息丢失,保持图像轮廓的完整。与其它模糊相比,双边滤波比较耗时。

高斯卷积核计算像素时,仅考虑与卷积核叠加的像素邻域,并依据此计算出高斯加权均值,未考虑周围像素具有相同亮度的情况,也未考虑像素处于图像边缘的场景。

图像边缘的像素亮度变化明显,双边滤波与高斯模糊相比考虑像素邻域间的差异,只对与中心像素亮度相近的进行模糊处理,因而保留边缘。

OpenCV提供cv.bilateralFilter()函数做双边滤波。

void cv::bilateralFilter(InputArray     src,#8bit 单通道/三通道图像
                         OutputArray     dst,#与输入图像同类型,同大小的图像
                         int     d,  #像素邻域直径,
                         double     sigmaColor,#色彩空间的标准差 值越大表示模糊时涉及邻域内越远的色彩
                         double     sigmaSpace,#坐标空间的标准差 值越大表示邻域中涉及越远的母亲节不像像素
                         int     borderType = BORDER_DEFAULT #卷积时边界处理方式
                         )        
Python:
cv.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]]) ->    dst

实现代码: C++

/**
 * 双边模糊
 * @param img
 */
void ImageBlur::bilateral_filter(const Mat &img) {
  logger_info("======bilateral_filter===========");
  Mat dst;
  bilateralFilter(img, dst, 20, 40, 10);
  imshow("bilateral_filter", dst);
}

实现代码: Python

# 双边模糊
def bilateral_blur(origin_image):
    logger.log.info("bilateral_blur")
    titles = ["origin_image"]
    images = [cv.cvtColor(origin_image, cv.COLOR_BGR2RGB)]
    for i in range(1, 31, 2):
        dst = cv.bilateralFilter(origin_image, i, i * 2, i / 2)
        titles.append("bilateral_" + str(i))
        images.append(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
    for i in range(16):
        plt.subplot(4, 4, i + 1)
        plt.imshow(images[i], 'gray', vmin=0, vmax=255)
        plt.title(titles[i])
        plt.xticks([]), plt.yticks([])
    plt.show()
    return dst

在这里插入图片描述

可以明显看出,与高斯模糊相比,图像的边缘得到很好地保留。

参考文献:

  1. http://szeliski.org/Book/

  2. https://en.wikipedia.org/wiki/Convolution

  3. http://www.songho.ca/dsp/convolution/convolution.html#definition

  4. http://www.cs.csi.cuny.edu/~gu/teaching/courses/csc76010/slides/Parallel_Longlong.pdf

  5. https://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Filtering.html

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值