OpenCV中的improc组件——图像处理中的三种线性滤波(邻域算子)(13)

imgproc组件是Image 和Process的缩写,即图像处理
图像处理模块包含以下内容:

  • 线性和非线性的图像滤波
  • 图像的几何变换
  • 其他图像转换
  • 直方图相关
  • 结果分析和形状描述
  • 运动分析和对象跟踪
  • 特征检测
  • 目标检测
    本将我们来学习基于图像处理的三种线性滤波:方框滤波,均值滤波,高斯滤波

1. 线性滤波:方框滤波,均值滤波,高斯滤波

1.1 平滑处理
平滑处理就是模糊处理,是一种简单且使用频率很高的图像处理方法。平滑处理最常见的用来减少图像上的噪声点或者失真。在涉及到降低图像分辨率时,平滑处理是非常好用的方法。
1.2 图像滤波和滤波器
图像滤波,指的是尽量保留图像细节特征的条件下对目标图像的噪声进行抑制,是图像预处理中不可缺少的操作,处理的好坏直接影响后续图像的处理和分析的有效性和可靠性。
消除图像中的噪声成分叫做图像的平滑化或滤波操作。信号或图像的能量大部分集中在幅度谱的低频或中频段,而在高频段,有用的信息经常会被噪声淹没,因此一个能降低高频成分幅度的滤波器就能减弱噪声的影响
图像滤波有两个目的: 一是抽出对象的特征作为图像识别的特征模式;另一个是为了适应图像处理的要求,消除图像数字化时所混入的噪声
对滤波处理的两个要求:一是不能损坏图像边缘及轮廓等重要信息;二是使得图像清晰视觉效果好
平滑滤波时低频增强的空间域滤波技术。目的:一是模糊,二是消除噪音;
空间域的平滑滤波一般采用简单平均法进行,就是求邻近像元点的平均亮度值。邻域的大小直接与平滑的效果直接相关,邻域越大,平滑效果越好,但邻域过大,平滑会使得边缘信息损失的越大,从而使得图像变得模糊,选合适的邻域即可。
OpenCV提供了5种平滑处理图像的操作,我们可以把滤波器想象成一个包含加权系数的窗口,使用这个进行平滑处理图像时,就把这个窗口加到图像,通过窗口来看图像的过程:

  • 方框滤波——BoxBlur函数
  • 均值滤波(邻域平均滤波)——Blur函数
  • 高斯滤波——GaussianBlur函数
  • 中值滤波——medianBlur函数
  • 双边滤波——bilateralFilter函数
    线性滤波:方框滤波,均值滤波,高斯滤波
    非线性滤波:中值滤波,双边滤波

1.3 线性滤波器
线性滤波器:用于剔除输入信号中不想要的频率或从许多频率中选择一个想要的频率。
常见的线性滤波器有:

  • 低通滤波器:允许低频滤通过;
  • 高通滤波器:允许高频通过;
  • 带通滤波器:允许一定范围频率通过;
  • 带阻滤波器:阻止一定范围频率通过,其他频率可以通过;
  • 全通滤波器:允许所有频率通过,仅改变相位关系;
  • 陷波滤波器:阻止一个狭窄频率范围通过,是一种特殊的带阻滤波器;

1.4 滤波和模糊
滤波就是将信号中特定波段频率滤除的操作,时防止和抑制干扰的一项重要措施。
拿高斯滤波看,滤波分为低通和高通,高斯滤波是指用高斯函数作为绿波函数的滤波器,至于是不是模糊取决于是高斯低通还是高斯高通,低通是模糊,高通是锐化

  • 高斯滤波是指用高斯函数作为绿波函数的滤波操作;
  • 高斯模糊就是高斯低通滤波;

1.5 邻域算子和线性邻域滤波
邻域算子(局部算子):是利用给定像素周围的像素值来决定此像素的最终输出值的一种算子。线性邻域滤波就是一种常用的邻域算子,像素的输出值取决于输入像素的加权和
邻域算子除了用于局部色调调整之外,还可以用于图像滤波,实现图像的平滑和锐化,图像边缘增强或图像噪声的去除。
线性邻域算子,用不同的权重去结合一个小邻域内的像素,来得到应有的处理效果。
在这里插入图片描述
如图所式:邻域滤波(卷积)——左边图像与中间图像的卷积产生 右边图像。
线性滤波处理的输出像素值g(i,j)是输入像素值f(i+k,j+I)的加权和, g ( i , j ) = ∑ k , I ( i + k , j + I ) h ( k , I ) g(i, j ) = \sum\limits_{k,I}(i+k,j+I)h(k,I) g(i,j)=k,I(i+k,j+I)h(k,I),其中h(k,j)是"核“,是滤波器的加权系数,即滤波器的滤波系数。
滤波函数还可以写为: g = f ⊗ h g = f \otimes h g=fh, f表示输入像素值,h表示加权系数“核”, g表示输出像素值 。
1.6 方框滤波(box Filter)
boxblur函数的作用是使用方框滤波器来模糊一张图片。
函数原型:
void boxFilter(InputArray src, OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1, -1), boolnormalize=true, int borderType=BORDEX_DEFAULT)

  • 参数一:输入的原图像,该函数对通道是独立处理的,可以处理任意通道数的图像。但待处理的图像深度应该为CV_8U, CV_16U, CV_16S, CV_32F,CV_64F之一。
  • 参数二:目标图像,需要和原图像一样尺寸和类型;
  • 参数三:输出图像的深度,-1代表使用原深度,即src.depth();
  • 参数四:Size类型的ksize,内核的大小。一般用Size(w, h)表示内核的大小,其中w为像素宽度,h为像素的高度。Size(3, 3)的核大小。
  • 参数五:表示锚点(平滑的那个点)。默认是Point(-1, -1).如果这个点的坐标是负值的话,就取核的中心为锚点。
  • 参数六:表示内核是否被其区域归一化了
  • 参数七:用于推断图像外部像素的某种边界模式。一般不官。

BoxFilter()函数方框滤波所用的核表示:
当normal为true时, a = 1 k s i z e . w i d t h ∗ k s i z e . h e i g h t a =\frac{1}{ksize.width * ksize.height} a=ksize.widthksize.height1, 当normal为false时,a=1;
在这里插入图片描述
当normal为true时,方框滤波就是均值滤波,也就是说均值滤波是方框滤波归一化后的特殊情况。归一化就是把要处理的量缩放到一个范围内,比如(0,1 ),以便统一处理和直观量化。
而非归一化的方框滤波用于计算每个像素邻域内的积分特性,比如密集光流算法中用到的图像倒数的协方差矩阵。
若要在可变的窗口中计算像素总和,可以用intergral()函数。

1.7 均值滤波
<1> 均值滤波:输出图像的每一个像素是核框口内输入图像对应像素的平均值(所有像素加权系数相等),也就是归一化后的方框滤波。
主要方法是邻域平均法,即用一片图像区域的个各像素的均值来代替原图像的各个像素。一般需要在原图像上对目标图像给出一个模板(核),该模板包含了其周围的邻近像素(目标像素为中心的周围8个像素,构成一个滤波模板,去掉目标目标像素点(x,y(+))。然后用模板中全体像素的平均值来替代原来像素质。

即对待处理的当前像素点(x,y)选择一个模板,该模板由近邻的若干个像素组成,求模板中所有像素的均值,再把该均值赋予当前像素点(x,y),作为处理后图像在该点上的灰度点g(x,y),即 g ( x , y ) = 1 m ∑ f ( x , y ) g(x,y) = \frac{1}{m} \sum{f(x,y)} g(x,y)=m1f(x,y), 其中m是该模板中当前像素在内的像素的总个数。

<2> 均值滤波缺陷
均值滤波不能很好的保留图像的细节,在图像去噪的同时也破坏了图像的细节部分,使得图像模糊,不能很好的去除噪声。

<3> blur函数的作用是:对输入的图像进行均值滤波后输出;
核如下:
在这里插入图片描述
blur函数原型:
void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1, -1 ), int borderType=BORDER_DEFAULT);

  • 参数一:和方框滤波一样;
  • 参数二:同方框滤波;
  • 参数三: 内核大小,同方框滤波;
  • 参数四:同方框滤波;
  • 参数五:不管;

1.8 高斯滤波
<1> 高斯滤波是一种线性平滑滤波,可以消除高斯噪声,用于图像处理的減噪过程。就是对整幅图像进行加权平均的过程,每个像素点的值,都由本身和邻域内的其他像素值经过加权平均后得到。
具体操作:用一个模板(卷积,淹膜)扫描图像中的每个像素,用模板确定的邻域内像素的加权平均灰度值去代替模板中的像素点的值。,从数学角度上讲就是图像的高斯模糊就是图像与正态分布做卷积。正态分布又叫高斯分布。
高斯滤波是一类根据高斯函数的形状来选择权值的线性平滑滤波器。高斯平滑滤波器对于抑制服从正态分布的噪声非常有效。
一维零均值高斯函数如下:
在这里插入图片描述,其中 σ \sigma σ决定了高斯函数的宽度。
对图像来说常用2维的零均值高斯函数作为平滑滤波器,二维高斯函数如下:
在这里插入图片描述
<2> GaussianBlur函数的作用:用高斯滤波器模糊一张图像。</font color=red>它将原图像与指定的高斯核函数做卷积运算,并且支持就地卷积
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY =0, intborderType=BORDER_DEFAULT);

  • 参数一:同上;
  • 参数二:同上;
  • 参数三:高斯内核大小,其中ksize.width和ksize.height可以不同,但都必须是正数和奇数,或者是0, 都有sigma计算得到。
  • 参数四:sigmaX, 表示高斯核函数在X方向的标准偏差;
  • 参数五:sigmaY,Y方向上的偏差,若为0,就将它设为sigmaX; 如果sigmaX, sigmaY都是0,那么就由ksize.width和ksize.height得到。
  • 参数六:不处理;

1.9 线性滤波源码分析
<1> OpenCV安装路径下的modules/imgproc/src/box_filter.dispatch.cpp文件中找到boxFilter的源码。

void boxFilter(InputArray _src, OutputArray _dst, int ddepth,
               Size ksize, Point anchor,
               bool normalize, int borderType)
{
    CV_INSTRUMENT_REGION();

    CV_Assert(!_src.empty());

    CV_OCL_RUN(_dst.isUMat() &&
               (borderType == BORDER_REPLICATE || borderType == BORDER_CONSTANT ||
                borderType == BORDER_REFLECT || borderType == BORDER_REFLECT_101),
               ocl_boxFilter3x3_8UC1(_src, _dst, ddepth, ksize, anchor, borderType, normalize))

    CV_OCL_RUN(_dst.isUMat(), ocl_boxFilter(_src, _dst, ddepth, ksize, anchor, borderType, normalize))

    Mat src = _src.getMat();  //复制原图像的形参Mat数据到临时变量,用于稍后的操作
    int stype = src.type(), sdepth = CV_MAT_DEPTH(stype), cn = CV_MAT_CN(stype);
    if( ddepth < 0 )
        ddepth = sdepth;
    _dst.create( src.size(), CV_MAKETYPE(ddepth, cn) );  //初始化目标图
    Mat dst = _dst.getMat(); //复制目标图的形参Mat数据到临时变量
    if( borderType != BORDER_CONSTANT && normalize && (borderType & BORDER_ISOLATED) != 0 )
    {
        if( src.rows == 1 )
            ksize.height = 1;
        if( src.cols == 1 )
            ksize.width = 1;
    }

    Point ofs;
    Size wsz(src.cols, src.rows);
    if(!(borderType&BORDER_ISOLATED))
        src.locateROI( wsz, ofs );

    CALL_HAL(boxFilter, cv_hal_boxFilter, src.ptr(), src.step, dst.ptr(), dst.step, src.cols, src.rows, sdepth, ddepth, cn,
             ofs.x, ofs.y, wsz.width - src.cols - ofs.x, wsz.height - src.rows - ofs.y, ksize.width, ksize.height,
             anchor.x, anchor.y, normalize, borderType&~BORDER_ISOLATED);

    CV_OVX_RUN(true,
               openvx_boxfilter(src, dst, ddepth, ksize, anchor, normalize, borderType))

    //CV_IPP_RUN_FAST(ipp_boxfilter(src, dst, ksize, anchor, normalize, borderType));

    borderType = (borderType&~BORDER_ISOLATED);

    Ptr<FilterEngine> f = createBoxFilter( src.type(), dst.type(),
                        ksize, anchor, normalize, borderType ); //调用FIlterEngine滤波引擎,正式开始滤波操作

    f->apply( src, dst, wsz, ofs );
}

其中,Ptr是用来动态分配的对象的智能指针模板类。
函数的整体框架是:先复制原图像到临时变量中,再处理ddepth 小于0的情况,然后处理了borderType不为BORDER_CONSTANT且normalilze为真的情况,最终调用一个FilterEigne滤波引擎创建了一个BoxFilter,正式开始滤波操作。由于FilterEnigne是OpenCV的图像滤波的核心引擎,所以我们来看看它的源码。

<2> FilterEngine类解析:OpenCV图像滤波核心引擎
这个类是图像滤波的核心主力,几乎可以把所有的滤波操作施加到图像上,它包含了所有必要的中间缓冲器。各种滤波函数如blur, GaussianBlur就是再函数末尾定义了一个Ptr类型的f,然后f->apply(src, dst)就可以进行滤波操作了。
许多和滤波相关的create系函数的返回值直接就是Ptr,比如:
在filterengine.hpp中可见:
在这里插入图片描述
使用FilterEigen类俄可以分块处理大量的图像,构建复杂的管线,其中就包含进行一些滤波阶段。如果我们使用预先定义好的滤波操作如cv::filter2D(), cv::erode(),cv::dilate(),他们不依赖于FilterEigen,在自己的函数体内就可以实现FilterEigen提供的滤波功能,而我们现在接触到的blur系列的函数都依赖于FIlterEigen类。
在这里插入图片描述
<3> blur函数
函数源码:

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

    boxFilter( src, dst, -1, ksize, anchor, true, borderType );
}

可以看到blur均值滤波函数是调用了方框滤波函数BoxFilter函数,且当第6个参数true时(均一化)后的方框滤波。

<4> Gaussianblur()函数解析

void GaussianBlur(InputArray _src, OutputArray _dst, Size ksize,
                  double sigma1, double sigma2,
                  int borderType)
{
    CV_INSTRUMENT_REGION();

    CV_Assert(!_src.empty());

    int type = _src.type();
    Size size = _src.size();
    _dst.create( size, type );

    if( (borderType & ~BORDER_ISOLATED) != BORDER_CONSTANT &&
        ((borderType & BORDER_ISOLATED) != 0 || !_src.getMat().isSubmatrix()) )
    {
        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;
    }

    bool useOpenCL = ocl::isOpenCLActivated() && _dst.isUMat() && _src.dims() <= 2 &&
               _src.rows() >= ksize.height && _src.cols() >= ksize.width &&
               ksize.width > 1 && ksize.height > 1;
    CV_UNUSED(useOpenCL);

    int sdepth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);

    Mat kx, ky;
    createGaussianKernels(kx, ky, type, ksize, sigma1, sigma2);

    CV_OCL_RUN(useOpenCL && sdepth == CV_8U &&
            ((ksize.width == 3 && ksize.height == 3) ||
            (ksize.width == 5 && ksize.height == 5)),
            ocl_GaussianBlur_8UC1(_src, _dst, ksize, CV_MAT_DEPTH(type), kx, ky, borderType)
    );

    if(sdepth == CV_8U && ((borderType & BORDER_ISOLATED) || !_src.isSubmatrix()))
    {
        std::vector<ufixedpoint16> fkx, fky;
        createGaussianKernels(fkx, fky, type, ksize, sigma1, sigma2);

        static bool param_check_gaussian_blur_bitexact_kernels = utils::getConfigurationParameterBool("OPENCV_GAUSSIANBLUR_CHECK_BITEXACT_KERNELS", false);
        if (param_check_gaussian_blur_bitexact_kernels && !validateGaussianBlurKernel(fkx))
        {
            CV_LOG_INFO(NULL, "GaussianBlur: bit-exact fx kernel can't be applied: ksize=" << ksize << " sigma=" << Size2d(sigma1, sigma2));
        }
        else if (param_check_gaussian_blur_bitexact_kernels && !validateGaussianBlurKernel(fky))
        {
            CV_LOG_INFO(NULL, "GaussianBlur: bit-exact fy kernel can't be applied: ksize=" << ksize << " sigma=" << Size2d(sigma1, sigma2));
        }
        else
        {
            CV_OCL_RUN(useOpenCL,
                    ocl_sepFilter2D_BitExact(_src, _dst, sdepth,
                            ksize,
                            (const uint16_t*)&fkx[0], (const uint16_t*)&fky[0],
                            Point(-1, -1), 0, borderType,
                            8/*shift_bits*/)
            );

            Mat src = _src.getMat();
            Mat dst = _dst.getMat();

            if (src.data == dst.data)
                src = src.clone();
            CV_CPU_DISPATCH(GaussianBlurFixedPoint, (src, dst, (const uint16_t*)&fkx[0], (int)fkx.size(), (const uint16_t*)&fky[0], (int)fky.size(), borderType),
                CV_CPU_DISPATCH_MODES_ALL);
            return;
        }
    }
    if(sdepth == CV_16U && ((borderType & BORDER_ISOLATED) || !_src.isSubmatrix()))
    {
        CV_LOG_INFO(NULL, "GaussianBlur: running bit-exact version...");

        std::vector<ufixedpoint32> fkx, fky;
        createGaussianKernels(fkx, fky, type, ksize, sigma1, sigma2);

        static bool param_check_gaussian_blur_bitexact_kernels = utils::getConfigurationParameterBool("OPENCV_GAUSSIANBLUR_CHECK_BITEXACT_KERNELS", false);
        if (param_check_gaussian_blur_bitexact_kernels && !validateGaussianBlurKernel(fkx))
        {
            CV_LOG_INFO(NULL, "GaussianBlur: bit-exact fx kernel can't be applied: ksize=" << ksize << " sigma=" << Size2d(sigma1, sigma2));
        }
        else if (param_check_gaussian_blur_bitexact_kernels && !validateGaussianBlurKernel(fky))
        {
            CV_LOG_INFO(NULL, "GaussianBlur: bit-exact fy kernel can't be applied: ksize=" << ksize << " sigma=" << Size2d(sigma1, sigma2));
        }
        else
        {
            // TODO: implement ocl_sepFilter2D_BitExact -- how to deal with bdepth?
            // CV_OCL_RUN(useOpenCL,
            //         ocl_sepFilter2D_BitExact(_src, _dst, sdepth,
            //                 ksize,
            //                 (const uint32_t*)&fkx[0], (const uint32_t*)&fky[0],
            //                 Point(-1, -1), 0, borderType,
            //                 16/*shift_bits*/)
            // );

            Mat src = _src.getMat();
            Mat dst = _dst.getMat();

            if (src.data == dst.data)
                src = src.clone();
            CV_CPU_DISPATCH(GaussianBlurFixedPoint, (src, dst, (const uint32_t*)&fkx[0], (int)fkx.size(), (const uint32_t*)&fky[0], (int)fky.size(), borderType),
                CV_CPU_DISPATCH_MODES_ALL);
            return;
        }
    }

#ifdef HAVE_OPENCL
    if (useOpenCL)
    {
        sepFilter2D(_src, _dst, sdepth, kx, ky, Point(-1, -1), 0, borderType);
        return;
    }
#endif

    Mat src = _src.getMat();
    Mat dst = _dst.getMat();

    Point ofs;
    Size wsz(src.cols, src.rows);
    if(!(borderType & BORDER_ISOLATED))
        src.locateROI( wsz, ofs );

    CALL_HAL(gaussianBlur, cv_hal_gaussianBlur, src.ptr(), src.step, dst.ptr(), dst.step, src.cols, src.rows, sdepth, cn,
             ofs.x, ofs.y, wsz.width - src.cols - ofs.x, wsz.height - src.rows - ofs.y, ksize.width, ksize.height,
             sigma1, sigma2, borderType&~BORDER_ISOLATED);

    CV_OVX_RUN(true,
               openvx_gaussianBlur(src, dst, ksize, sigma1, sigma2, borderType))

#if defined ENABLE_IPP_GAUSSIAN_BLUR
    // IPP is not bit-exact to OpenCV implementation
    CV_IPP_RUN_FAST(ipp_GaussianBlur(src, dst, ksize, sigma1, sigma2, borderType));
#endif

    sepFilter2D(src, dst, sdepth, kx, ky, Point(-1, -1), 0, borderType);
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
float DigFil(invar, setic) float invar; int setic; /******************************************************************************/ /* Filter Solutions Version 2009 Nuhertz Technologies, L.L.C. */ /* www.nuhertz.com */ /* +1 602-279-2448 */ /* 3rd Order Band Pass Butterworth */ /* Bilinear Transformation with Prewarping */ /* Sample Frequency = 5.000 KHz */ /* Standard Form */ /* Arithmetic Precision = 4 Digits */ /* */ /* Center Frequency = 300.0 Rad/Sec */ /* Pass Band Width = 20.00 Rad/Sec */ /* */ /******************************************************************************/ /* */ /* Input Variable Definitions: */ /* Inputs: */ /* invar float The input to the filter */ /* setic int 1 to initialize the filter to zero */ /* */ /* Option Selections: */ /* Standard C; Initializable; Internal States; Not Optimized; */ /* */ /* There is no requirement to ever initialize the filter. */ /* The default initialization is zero when the filter is first called */ /* */ /******************************************************************************/ /* */ /* This software is automatically generated by Filter Solutions */ /* no restrictions from Nuhertz Technologies, L.L.C. regarding the use and */ /* distributions of this software. */ /* */ /******************************************************************************/ { float sumnum=0.0, sumden=0.0; int i=0; static float states[6] = {0.0,0.0,0.0,0.0,0.0,0.0}; static float znum[7] = { -7.968e-09, 0.0, 2.39e-08, 0.0, -2.39e-08, 0.0, 7.968e-09 }; static float zden[6] = { .992, -5.949, 14.88, -19.86, 14.92, -5.981 }; if (setic==1){ for (i=0;i<6;i++) states[i] = [i] = [i]*invar; return 0.0; } else{ sumnum = sumden = 0.0; for (i=0;i<6;i++){ sumden += states[i]*zden[i]; sumnum += states[i]*znum[i]; if (i<5) states[i] = states[i+1]; } states[5] = invar-sumden; sumnum += states[5]*znum[6]; return sumnum; } }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值