图像处理学习笔记之空间滤波(4)高斯滤波

本文介绍了高斯滤波在图像处理中的应用,包括高斯函数、高斯模板及其与均值滤波的区别。高斯滤波器通过高斯分布确定模板系数,σ值影响滤波效果。当σ增大时,图像变得更加模糊。OpenCV中的GaussianBlur函数用于实现高斯滤波,详细阐述了其参数和使用方法。
该文章已生成可运行项目,

高斯函数

高斯滤波器是也一种线性滤波器,能够有效的抑制噪声,平滑图像。其作用原理和均值滤波器类似,都是取滤波器窗口内的像素的均值作为输出。其窗口模板的系数和均值滤波器不同,均值滤波器的模板系数都是相同的为1;而高斯滤波器根据高斯分布来确定各模板得系数,所以高斯滤波器相比于均值滤波器对图像个模糊程度较小。
二维高斯函数如下:
G ( x , y ) = 1 2 π σ 2 e − x 2 + y 2 2 σ 2 G(x,y)=\frac{1}{2\pi\sigma^2}e^-\frac{x^2+y^2}{2\sigma^2} G(x,y)=2πσ21e2σ2x2+y2
其中(x,y)为点坐标,在图像中为整数,σ是标准差。标准差代表着数据的离散程度,如下图所示,σ越大,高斯函数越平滑,数据的离散程序越大。对于图像滤波来说,如果σ较小,那么生成的模板的中心系数较大,而周围的系数较小,这样对图像的平滑效果就不是很明显;反之,σ较大,则生成的模板的各个系数相差就不是很大,比较类似均值模板,对图像的平滑效果比较明显。σ值的确定依赖于问题背景,需要具体问题具体分析。
在这里插入图片描述
在这里插入图片描述
从上图可以看出:
(1)在核大小固定的情况下,sigma值越大,权值分布越平缓。因此,邻域各个点的值对输出值的影响越大,最终结果造成图像越模糊。
(2)在核大小固定的情况下,sigma值越小,权值分布越突起。因此,邻域各个点的值对输出值的影响越小,图像变化也越小。假如中心点权值为1,其他点权值为0,那么最终结果是图像没有任何变化。
(3)sigma固定时,核越大图像越模糊。
(4)sigma固定时,核越小图像变化越小。
相比于图像的简单平滑,高斯滤波效率要低一些,在离散型噪声的消除方面,高斯滤波的效果并不理想。然而如果要对图像的总体特征进行提取和增强时,高斯滤波相对就具有很大的优势。下图对比了图像的简单滤波和高斯滤波的处理差异,图(a)表示了一个5×5邻域内的像素灰度分布情况,从图中可以看出此邻域内有两处灰度较高的亮度。图(b)对图(a)进行3×3邻域线性滤波的结果,从图(b)中可以看出,原图像中的两处亮点被连接在了一起,失去了原图像的特征,图(c)为对图(a)进行3×3邻域高斯滤波的结果,可以发现图(c)中依然保留着原图像的特征。
在这里插入图片描述

高斯模板

标准差σ确定后,就可以确定模板的大小。高斯函数是钟形曲线,距离中心越远数值越小,足够远处可以忽略不计。在区间(μ−σ,μ+σ)范围内的面积占曲线下总面积的68%,(μ−2σ,μ+2σ)范围占95%,(μ−3σ,μ+3σ)范围占99.7%,一般3σ外的数值已接近于0,可以忽略。半径为3σ即窗口大小为6σ×6σ即可,通常取最近的奇数。
在这里插入图片描述
要想得到一个高斯滤波器的模板,可以对高斯函数进行离散化,得到的高斯函数值作为模板的系数。对于模板的大小为 (2k+1)×(2k+1),模板中各个元素值的计算公式如下:
H ( i , i ) = 1 2 π σ 2 e − i 2 + i 2 2 σ 2 H(i,i)=\frac{1}{2\pi\sigma^2}e^-\frac{i^2+i^2}{2\sigma^2} H(i,i)=2πσ21e2σ2i2+i2
这样计算出来的模板有两种形式:小数和整数。
小数形式的模板,就是直接计算得到的值,然后进行归一化处理;
整数形式的,则需要进行归一化处理,将模板左上角的值归一化为1,下面会具体介绍。使用整数的模板时,需要在模板的前面加一个系数。
例如,大小为3×3,σ=0.8的模板系数计算过程如下:
在这里插入图片描述
可以严格证明,一个2-D的高斯卷积可分解为顺序执行的两个1-D高斯卷积,即一个2-D高斯平均模板可拆分成两个1-D高斯平均模板。例如:
1 16 [ 1 2 1 2 4 2 1 2 1 ] = 1 4 [ 1 2 1 ] 1 4 [ 1 2 1 ] \frac{1}{16}\left[ \begin{matrix}1 & 2 & 1 \\2 & 4 & 2 \\ 1&2&1\end{matrix} \right]=\frac{1}{4}\left[ \begin{matrix}1 \\ 2\\ 1 \end{matrix}\right]\frac{1}{4}\left[\begin{matrix}1&2&1\end{matrix}\right] 161121242121=4112141[121]
对n×n的模板,这样的分解可将一个计算量为O(n^2)的操作用两个计算量为O(n)的操作来代替。为了得到1-D的离散模板,可对高斯函数在整数位置-n,…0,…+n采样。具体可取n=2σ+1(σ高斯方差),而模板尺寸是S=2n+1。例如,对σ=1,模板尺寸最多为7就够了;σ=2,则模板尺寸至少为11。

GaussianBlur

Opencv中使用GaussinBlur函数进行高斯滤波,函数声明如下:

void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY, int borderType)

src:输入图像,可以包含任意的通道数,每个通道都是单独处理。图像深度必须是CV_8U, CV_16U, CV_16S, CV_32F或者CV_64F。
dst:输出图像,大小和类型与输入图像相同
 ksize: 高斯核大小。ksize.width和ksize.height可以不同,但它们都必须是正数和奇数。如果是零,则从给定的sigma计算得到。计算的公式为:

ksize.width = cvRound(sigma1*(depth == CV_8U ? 3 : 4)*2 + 1)|1;
ksize.height = cvRound(sigma2*(depth == CV_8U ? 3 : 4)*2 + 1)|1;

sigmaX: X方向的高斯核标准偏差。
sigmaY: Y方向的高斯核标准偏差。如果sigmaY为零,则将其设置为与sigmaX相同。如果两个sigma均为零,则分别从ksize.width和ksize.height计算。计算的公式为:
s i g m a = 0.3 ∗ ( ( k s i z e − 1 ) ∗ 0.5 − 1 ) + 0.8 sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8 sigma=0.3((ksize1)0.51)+0.8
borderType:边界像素处理方式。
使用GaussianBlur函数的示例代码如下:

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

myGaussianBlur函数

void myapply(Mat& src, Mat& dst, Mat& kernelX, Mat& kernelY)
{
    int chns = src.channels();
    int a = (kernelX.rows - 1) / 2;
    int b = (kernelY.rows - 1) / 2;
    dst.create(src.rows, src.cols, src.type());
    //分通道处理
    vector<Mat> srcchns;
    vector<Mat> dstchns;
    split(src, srcchns);
    split(dst, dstchns);
    double* px = kernelX.ptr<double>();
    for(int ch = 0; ch < chns; ch++)
    {
        //x方向一维高斯模糊
        Mat mat(src.rows, src.cols, CV_64F);
        for(int i = 0; i < src.rows; i++)
        {
            for(int j = a; j < src.cols - a; j++)
            {
                double t = 0.0;
                for(int k = 0; k < kernelX.rows; k++)
                {
                    t += srcchns[ch].at<uchar>(i, j - a + k) * px[k];
                }
                mat.at<double>(i, j) = t;
            }
            //左边界
            for(int j = 0; j < a; j++)
            {
                double t = 0.0;
                for(int k = 0; k < kernelX.rows; k++)
                {
                    t += srcchns[ch].at<uchar>(i, abs(a - k - j)) * px[k];
                }
                mat.at<double>(i, j) = t;
            }
            //右边界
            for(int j = src.cols - a; j < src.cols; j++)
            {
                double t = 0.0;
                for(int k = 0; k < kernelX.rows; k++)
                {
                    t += srcchns[ch].at<uchar>(i, j - abs(a - k)) * px[k];
                }
                mat.at<double>(i, j) = t;
            }
        }
        //y方向一维高斯模糊
        double* py = kernelY.ptr<double>();
        for(int i = 0; i < srcchns[ch].cols; i++)
        {
            for(int j = b; j < srcchns[ch].rows - b; j++)
            {
                double t = 0.0;
                for(int k = 0; k < kernelY.rows; k++)
                {
                    t += mat.at<double>(j - b + k, i) * py[k];
                }
                dstchns[ch].at<uchar>(j, i) = saturate_cast<uchar>(t);
            }
            //上边界
            for(int i = 0; i < srcchns[ch].cols; i++)
            {
                for(int j = 0; j < b; j++)
                {
                    double t = 0.0;
                    for(int k = 0; k < kernelY.rows; k++)
                    {
                        t += mat.at<double>(abs(b - k - j), i) * py[k];
                    }
                    dstchns[ch].at<uchar>(j, i) = saturate_cast<uchar>(t);
                }
            }
            //下边界
            for(int i = 0; i < srcchns[ch].cols; i++)
            {
                for(int j = srcchns[ch].rows - b; j < srcchns[ch].rows; j++)
                {
                    double t = 0.0;
                    for(int k = 0; k < kernelY.rows; k++)
                    {
                        t += mat.at<double>(j - abs(b - k), i) * py[k];
                    }
                    dstchns[ch].at<uchar>(j, i) = saturate_cast<uchar>(t);
                }
            }
        }
    }
    merge (dstchns, dst);
}

cv::Mat mygetGaussianKernel( int n, double sigma )
{
    Mat kernel(n, 1, CV_64F, Scalar::all(0));
    double* cd = kernel.ptr<double>();
    double sigmaX = sigma > 0 ? sigma : ((n - 1) * 0.5 - 1) * 0.3 + 0.8;
    double scale2X = -0.5 / (sigmaX * sigmaX);
    double sum = 0.0;
    for(int i = 0; i < n; i++)
    {
        double x = i - (n - 1) * 0.5;
        double t = exp(scale2X * x * x);
        cd[i] = t;
        sum += cd[i];
    }

    sum = 1.0 / sum;
    for(int i = 0; i < n; i++ )
    {
        cd[i] *= sum;
    }
    return kernel;
}

void myGaussianBlur(Mat& src, Mat& dst, Size ksize, double sigmaX, double sigmaY)
{
    Size size = src.size();
    if(size.height == 1)
        ksize.height = 1;
    if(size.width == 1)
        ksize.width = 1;
    if(ksize.width == 1 && ksize.height == 1)
    {
        src.copyTo(dst);
        return;
    }
    if(sigmaY <= 0)
        sigmaY = sigmaX;
    if( ksize.width <= 0 && sigmaX > 0 )
        ksize.width = cvRound(sigmaX * 6 + 1) | 1;
    if( ksize.height <= 0 && sigmaY > 0 )
        ksize.height = cvRound(sigmaY * 6 + 1) | 1;

    CV_Assert( ksize.width > 0 && ksize.width % 2 == 1 &&
               ksize.height > 0 && ksize.height % 2 == 1 );

    sigmaX = std::max( sigmaX, 0.0 );
    sigmaY = std::max( sigmaY, 0.0 );

    Mat kx, ky;
    kx = mygetGaussianKernel( ksize.width, sigmaX);
    if( ksize.height == ksize.width && std::abs(sigmaX - sigmaY) < DBL_EPSILON )
        ky = kx;
    else
        ky = mygetGaussianKernel( ksize.height, sigmaY);
    myapply (src, dst, kx, ky);
}
本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值