图像处理学习笔记之空间滤波(3)均值滤波

本文介绍了图像处理中的空间滤波原理,包括相关与卷积的概念,以及边界处理的方法。重点讲解了均值滤波,解释了其理论基础,探讨了滤波器模板的选择,并通过OpenCV的blur函数展示了均值滤波在去噪过程中的应用,同时也指出均值滤波可能导致图像细节损失的问题。

滤波原理

空间滤波器有一个邻域和对该邻域包围的图像像素执行的预定义操作组成。滤波产生一个新像素,新像素的坐标等于邻域中心的坐标,像素的值是滤波操作的结果。滤波器的中心访问输入图像的每个像素,就生成了滤波后的图像。如果在图像像素上执行的是线性操作,则该滤波器称为线性滤波器。否则,滤波器就称为非线性滤波器
  下图说明了使用3*3邻域的线性空间滤波的机理。在图像中的任意一点(x,y),滤波器的输出g(x,y)是滤波器系数与该滤波器包围的图像像素的乘积之和:
g ( x , y ) = w ( − 1 , − 1 ) f ( x − 1 , y − 1 ) + . . . w ( 0 , 0 ) f ( x , y ) + . . . + w ( 1 , 1 ) f ( x + 1 , y + 1 ) g(x,y)=w(-1,-1)f(x-1,y-1)+...w(0,0)f(x,y)+...+w(1,1)f(x+1,y+1) g(x,y)=w(1,1)f(x1,y1)+...w(0,0)f(x,y)+...+w(1,1)f(x+1,y+1)
很明显,滤波器的中心系数w(0,0)对准位置(x,y)的像素。对于一个大小为m×n的模板,我们假设m=2a+1且n=2b+1,其中,a,b为正整数。这意味着在后续的讨论中,我们关注的是奇数尺寸的滤波器,其最小尺寸为3×3。一般来说,使用大小为m×n的滤波器对大小为M×N的图像进行线性空间滤波,可由下式表示:
g ( x , y ) = ∑ s = − a a ∑ t = − b b w ( s , t ) f ( x + s , y + t ) g(x,y)=\sum_{s=-a}^a\sum_{t=-b}^bw(s,t)f(x+s,y+t) g(x,y)=s=aat=bbw(s,t)f(x+s,y+t)
其中,x和y是可变的,以便w中的每个像素可访问f中的每个像素。
在这里插入图片描述

相关与卷积

在执行线性空间滤波时,相关和卷积是两个比较接近的概念。相关是滤波器模板移过图像并计算每个位置乘积之和的处理。卷积和相关类似,但滤波器首先要旋转180°。一个大小为m×n的滤波器w(x,y)与一幅图像f(x,y)做相关操作,可表示为:
w ∘ f = ∑ s = − a a ∑ t = − b b w ( s , t ) f ( x + s , y + t ) a = m − 1 2 , b = n − 1 2 w\circ f=\sum_{s=-a}^a\sum_{t=-b}^bw(s,t)f(x+s,y+t)\quad a=\frac{m-1}{2},b=\frac{n-1}{2} wf=s=aat=bbw(s,t)f(x+s,y+t)a=2m1,b=2n1
这一等式对所有位移变量x和y求值,以便w的所有元素访问f的每一个像素。
  类似的,w(x,y)和f(x,y)的卷积表示为:
w ∗ f = ∑ s = − a a ∑ t = − b b w ( s , t ) f ( x − s , y − t ) a = m − 1 2 , b = n − 1 2 w*f=\sum_{s=-a}^a\sum_{t=-b}^bw(s,t)f(x-s,y-t)\quad a=\frac{m-1}{2},b=\frac{n-1}{2} wf=s=aat=bbw(s,t)f(xs,yt)a=2m1,b=2n1
正因为相关和卷积的概念比较接近,所以在一些文献中并不严格区分相关和卷积。在一些文献中,您可能会遇到卷积滤波器、卷积模板或卷积核的概念,这些术语用来表示一种空间滤波器,并且滤波器并未真正使用卷积。

边界处理

当模板中心对应输入图像的边界像素时,其邻域范围可能扩展到输入图像的边界之外,而那里并没有定义。解决这个问题的思路有两种:一种是忽略这些边界的像素,仅考虑图像内部与边界距离小于等于模板半径的像素。当图像尺寸比较大且感兴趣目标在图像内部时这种方法的效果可以接受。另一种是将输入图像扩展,即如果用半径为r的模板进行模板运算,则在图像的四条边界外各扩展一个r行或r列,从而可以实现对边界上像素的运算。这些新增行或列中像素的值可以采用不同的方式确定。在opencv中,边界的处理方式有以下几种:

模式描述
BORDER_CONSTANTiiiiii|abcdefgh|iiiiiii
BORDER_REPLICATEaaaaaa|abcdefgh|hhhhhhh
BORDER_WRAPcdefgh|abcdefgh|abcdefg
BORDER_REFLECT_101gfedcb|abcdefgh|gfedcba
BORDER_TRANSPARENTuvwxyz|absdefgh|ijklmno
BORDER_REFLECT101和BORDER_REFLECT_101相同
BORDER_DEFAULT和BORDER_REFLECT_101相同
BORDER_ISOLATEDdo not look outside of ROI

例如,当模板尺寸为3*3,并使用BORDER_DEFAULT方式扩展下图所示的图像边界时,图像像素为:
在这里插入图片描述

均值滤波

理论基础

均值滤波器的概念比较直观,它使用滤波器模板确定的邻域内像素的平均灰度值代替图像中每个像素的值,这种处理降低了图像灰度的“尖锐”变化。均值滤波本身存在着固有的缺陷,即它不能很好地保护图像细节,在去噪的同时也破坏了图像的细节部分,从而使图像变得模糊,不能很好的去除噪声点。
下图显示了两个3×3平滑滤波器。第一个滤波器产生模板下方的标准像素平均值
R = 1 9 ∑ i = 1 9 z i R=\frac{1}{9}\sum_{i=1}^9z_i R=91i=19zi
R是由模板定义的3×3邻域内像素灰度的平均值。一个m×n模板应有等于1/mn的归一化常数。所有系数都相等的空间均值滤波器有时称为盒状滤波器。
在这里插入图片描述
第二个模板称之为加权平均。从上图可以看出,越接近模板中心的的系数取值越大,这样做的目的是试图降低模糊。实际应用中,为保证各模板系数均为整数以减少计算量,常取模板周边最小的系数为1,内部的系数成比例增加,中心系数最大。这里增加的比例可根据各系数位置与模板中心的距离来确定,如依次根据距离的倒数来确定各内部系数的值。
一幅M×N的图像经过一个大小为m×n的加权平均滤波的过程可由下式给出:
g ( x , y ) = ∑ s = − a a ∑ t = − b b w ( s , t ) f ( x + s , y + t ) ∑ s = − a a ∑ t = − b b w ( s , t ) g(x,y)=\frac{\sum_{s=-a}^a\sum_{t=-b}^bw(s,t)f(x+s,y+t)}{\sum_{s=-a}^a\sum_{t=-b}^bw(s,t)} g(x,y)=s=aat=bbw(s,t)s=aat=bbw(s,t)f(x+s,y+t)
Opencv提供了blur函数来实现图像均值滤波,其声明为:

void blur (InputArray src, OutputArray dst, Size ksize, Point anchor, int borderType);

参数说明:

src:输入图像,它可以有任意数量的通道,对每个通道是独立处理的。但待处理的图像的深度应该是CV_8U,CV_16U,CV_16S,CV_32F或CV_64F。
dst:目标图像,与src大小和类型都相同的图像。
ksize: 内核大小,一般用Size(w,h)来表示内核的大小。如size(3,3)表示3*3de 核大小。
anchor:表示锚点,默认值为Point(-1,-1)表示锚点处于核的中心。
borderType:边界模式,用于推断图像外的像素。
使用blur函数的实例代码为:

int main()
{
    Mat srcImage = imread("lena512.bmp", IMREAD_GRAYSCALE);
    Mat dstImage;
    blur(srcImage, dstImage, Size(3, 3));
    namedWindow("源图像");
    imshow("源图像", srcImage);
    namedWindow("结果");
    imshow("结果", dstImage);
    waitKey(0);
    return 0;
}

对添加椒盐噪声的图片进行处理,从结果可以看出,均值滤波使图像变得模糊,破坏了图像的细节。
在这里插入图片描述
在这里插入图片描述

自定义滤波函数实现

自定义的blur函数代码如下,边界处理采用BORDER_DEFAULT 方式。

void myblur(Mat& src, Mat& dst, Size ksize)
{
    if(src.data == nullptr && ksize.area () == 0)
        return ;
    if(ksize.width % 2 == 0 && ksize.height % 2 == 0 && ksize.width < 3 && ksize.height < 3)
        return;
    int a = (ksize.width - 1) / 2;
    int b = (ksize.height - 1) / 2;

    //模板
    Mat kernel(ksize.width, ksize.height, CV_8U, Scalar::all(1));

    Mat src_ext(src.rows + 2 * b, src.cols + 2 * a, src.type(), Scalar::all(0));
    Mat dst_ext = src_ext.clone();
    //分通道处理
    vector<Mat> srcchns;
    vector<Mat> extchns;
    vector<Mat> dstchns;
    split (src, srcchns);
    split (src_ext, extchns);
    split(dst_ext, dstchns);
    for(int ch = 0; ch < srcchns.size(); ch++)
    {
        //先填充中间,再填充边缘
        for(int i = 0; i < src.rows; i++)
        {
            for(int j = 0; j < src.cols; j++)
            {
                extchns[ch].at<uchar>(i + b, j + a) = srcchns[ch].at<uchar>(i, j);
            }
        }
        //填充上下边缘
        for(int i = 0; i < b; i++)
        {
            for(int j = 0; j < src.cols; j++)
            {
                extchns[ch].at<uchar>(i, j + a) = srcchns[ch].at<uchar>(b - i, j); //上边缘
                extchns[ch].at<uchar>(extchns[ch].rows - 1 - i, j + a) = srcchns[ch].at<uchar>(srcchns[ch].rows - 1 - b + i, j);
            }
        }
        //填充左右边缘
        for(int i = 0; i < a; i++)
        {
            for(int j = 0; j < src_ext.rows; j++)
            {
                //extchns[ch].at<uchar>(j + b, i) = srcchns[ch].at<uchar>(j, a - i);
                //extchns[ch].at<uchar>(j + b, extchns[ch].cols - a + i) = srcchns[ch].at<uchar>(j, srcchns[ch].cols  - a - i);
                extchns[ch].at<uchar>(j, i) = extchns[ch].at<uchar>(j, 2 * a - i);
                extchns[ch].at<uchar>(j, extchns[ch].cols - a + i) = extchns[ch].at<uchar>(j, extchns[ch].cols  - a - i - 2);
            }
        }
        for(int i = a; i < extchns[ch].rows - a; i++)
        {
            for(int j = b; j < extchns[ch].cols - b; j++)
            {
                double pix = 0.0;
                for(int x = -a; x <= a; x++)
                {
                    for(int y = -b; y <= b; y++)
                    {
                        pix += extchns[ch].at<uchar>(i + x, j + y) * kernel.at<uchar>(a + x, b + y);
                    }
                }
                dstchns[ch].at<uchar>(i, j) = saturate_cast<uchar>(pix / (ksize.height * ksize.width));
            }
        }
    }
    Mat dstImg;
    merge(dstchns, dstImg);
    dst = Mat(dstImg, Range(a, dstImg.rows - a), Range(b, dstImg.cols - b));
}

int main()
{
    Mat srcImage = imread("lena512.bmp", IMREAD_GRAYSCALE);
    Mat dstImage;
    myblur(srcImage, dstImage, Size(3, 3));
    namedWindow("源图像");
    imshow("源图像", srcImage);
    namedWindow("结果");
    imshow("结果", dstImage);
    waitKey(0);
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值