1. 程式人生 > >openCV 中的高斯濾波GaussianBlur函式

openCV 中的高斯濾波GaussianBlur函式

在上次的opencv原始碼解析之濾波前言1中,按照opencv_tutorials.pdf中的濾波部分試了下常用的4種濾波器的使用方法。在opencv的C++中,這4個函式分別為:blur,GaussianBlur,meidaBlur,bilateralFilter.下面就這幾個函式在opencv中的功能,以及引數做個介紹:

  1. 均值濾波:其函式宣告為:void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )。

這個函式在上一節中介紹過了,這裡簡單些一下。

功能:對輸入的影象src進行均值濾波後用dst輸出。

引數:src和dst當然分別是輸入影象和輸出影象。size為均值濾波器模板大小。Anchor為錨點(具體什麼沒看原始碼不懂),如果為Point(-1,-1),則錨點是濾波器的中心點。borderType為邊緣點插值型別。

理解:以原圖對應畫素為中心的與模板中心重疊,將模板覆蓋領域內全部畫素求均值就是濾波後像素的值了。

  1. 高斯濾波:其函式宣告為: void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT ) ;

功能:對輸入的影象src進行高斯濾波後用dst輸出。

引數:src和dst當然分別是輸入影象和輸出影象。Ksize為高斯濾波器模板大小,sigmaX和sigmaY分別為高斯濾波在橫線和豎向的濾波係數(有點晦澀,等下解釋)。borderType為邊緣點插值型別。

理解:數字影象的濾波可以簡單的這麼理解,就是對原影象的每一個畫素濾波,那麼對應這個畫素濾波後的值是根據其相鄰畫素(包括自己那個點)與一個濾波模板進行相乘即可。所以具體到高斯濾波,我們只要知道這個高斯濾波的模板即可。

那怎麼確定這個模板呢?首先這個模板的大小為ksize,其每個數字的計算是這樣的:

其中 是歸一化係數,因為其和要為1.

為了簡化,一般在二維影象處理中,ui和uj取0,sigma1和sigma2取相等。所以公式就簡化為 :

因此很容易就計算出模板每個位置的數字了,簡單吧!

但是要注意2點,第一點就是ksize的寬和高必須是奇數;第二點就是如果引數sigmaX=sigmaY=0,則實際用的是公式sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8 .

  1. 中值濾波:其函式宣告為void medianBlur(InputArray src, OutputArray dst, int ksize)。

功能:對輸入的影象src進行中值濾波後用dst輸出。

引數:src和dst當然分別是輸入影象和輸出影象。ksize為均值濾波器模板大小,因為模板為正方形,所以只有一個引數。

理解:以原圖對應畫素為中心的與模板中心重疊,將模板覆蓋領域內全部畫素排序後的中間值就是濾波後像素的值了,所以模板長度必須為奇數。

  1. 雙向濾波:其函式宣告為:void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT )

功能:對輸入的影象src進行雙向濾波後用dst輸出。

引數:src和dst當然分別是輸入影象和輸出影象。d為每個畫素領域的直徑,sigmaColor為顏色空間的標準偏差,sigmaSpace為座標空間的標準偏差。borderType為邊緣點插值型別。

理解:暫時不明白雙向濾波的工作原理,以後有時間弄懂再補上吧,也歡迎大家補上。

函式宣告為:

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

     功能:對輸入的影象src進行高斯濾波後用dst輸出。

     引數:src和dst當然分別是輸入影象和輸出影象。Ksize為高斯濾波器模板大小,sigmaX和sigmaY分別為高斯濾波在橫線和豎向的濾波係數。borderType為邊緣擴充套件點插值型別。

     接下來的工作就是進入GaussianBlur函式內部,跟蹤其函式程式碼,經過分析,在該函式內部呼叫了很多其他的函式,其呼叫的函式層次結構如下圖所示:

     這裡我們分析原始碼不需要深入到最底層,我們只需分析到函式createSeparableLinearFilter和getGaussianKernel這一層。

     那就開始我們的原始碼分析工作吧!

     從函式呼叫層次結構圖可以看出,要分析函式GaussianBlur,必須先分析其呼叫過的內部函式。

     因此首先分析函式getGaussianKernel。

     功能:返回一個ksize*1的陣列,陣列元素滿足高斯公式:

     其中只有係數alpha和引數sigma未知,sigma的求法為:

     如果輸入sigma為非正,則計算公式為:sigma = 0.3*((ksize-1)*0.5 - 1) + 0.8 .

     如果輸入sigma為正,則就用該輸入引數sigma。

     最後alpha為歸一化係數,即計算出的ksize個數之和必須為1,所以後面只需求ksize個數,計算其和並求倒即可。

其原始碼及註釋如下:

複製程式碼
cv::Mat cv::getGaussianKernel( int n, double sigma, int ktype )
{
    const int SMALL_GAUSSIAN_SIZE = 7;
    static const float small_gaussian_tab[][SMALL_GAUSSIAN_SIZE] =
    {
        {1.f},
        {0.25f, 0.5f, 0.25f},
        {0.0625f, 0.25f, 0.375f, 0.25f, 0.0625f},
        {0.03125f, 0.109375f, 0.21875f, 0.28125f, 0.21875f, 0.109375f, 0.03125f}
    };
    
      /*如果sigma小於0,且n為不大於7的奇整數,則核的濾波係數固定了,其固定在陣列

        small_gaussian_tab中,根據其n的長度來選擇具體的值 ,如果不滿足上面的,則固定核為0
        固定核為0表示自己計算其核*/ 
        
    const float* fixed_kernel = n % 2 == 1 && n <= SMALL_GAUSSIAN_SIZE && sigma <= 0 ?
        small_gaussian_tab[n>>1] : 0;

    CV_Assert( ktype == CV_32F || ktype == CV_64F );//確保核元素為32位浮點數或者64位浮點數    Mat kernel(n, 1, ktype);//建立一個n*1的陣列kernel,一個Mat矩陣包括一個矩陣頭和一個指向矩陣元素的指標    float* cf = (float*)kernel.data;//定義指標cf指向kernel單精度浮點型資料    double* cd = (double*)kernel.data;//定義指標cd指向kernerl雙精度浮點型資料
    double sigmaX = sigma > 0 ? sigma : ((n-1)*0.5 - 1)*0.3 + 0.8;//當sigma小於0時,採用公式得到sigma(只與n有關)    double scale2X = -0.5/(sigmaX*sigmaX);//高斯表示式後面要用到    double sum = 0;

    int i;
    for( i = 0; i < n; i++ )
    {
        double x = i - (n-1)*0.5;
        //如果自己算其核的話,就常用公式exp(scale2X*x*x)計算,否則就用固定係數的核        double t = fixed_kernel ? (double)fixed_kernel[i] : std::exp(scale2X*x*x);
        if( ktype == CV_32F )
        {
            cf[i] = (float)t;//單精度要求時存入cf陣列中            sum += cf[i];//進行歸一化時要用到        }
        else
        {
            cd[i] = t;//雙精度時存入cd陣列中            sum += cd[i];
        }
    }

    sum = 1./sum;//歸一化時核中各元素之和為1    for( i = 0; i < n; i++ )
    {
        if( ktype == CV_32F )
            cf[i] = (float)(cf[i]*sum);//歸一化後的單精度核元素        else
            cd[i] *= sum;//歸一化後的雙精度核元素    }

    return kernel;//返回n*1的陣列,其元素或是單精度或是雙精度,且符合高斯分佈}
複製程式碼

    下面該分析函式createSeparableLinearFilter了。

    功能為:建立一個影象濾波其引擎類,其主要處理的是原影象和目標影象資料格式的統以及濾波器核的合成。

其原始碼及註釋如下:

複製程式碼
cv::Ptr<cv::FilterEngine> cv::createSeparableLinearFilter(
    int _srcType, int _dstType,
    InputArray __rowKernel, InputArray __columnKernel,
    Point _anchor, double _delta,
    int _rowBorderType, int _columnBorderType,
    const Scalar& _borderValue )//InputArray是Mat型別,表示的是輸入陣列{
    //_rowKernel儲存其矩陣頭,_columnKernel類似    Mat _rowKernel = __rowKernel.getMat(), _columnKernel = __columnKernel.getMat();
    _srcType = CV_MAT_TYPE(_srcType);//求矩陣的陣列型別,資料型別包過通道數,深度,和資料型別3種    _dstType = CV_MAT_TYPE(_dstType);//類似    int sdepth = CV_MAT_DEPTH(_srcType), ddepth = CV_MAT_DEPTH(_dstType);//求矩陣元素深度    int cn = CV_MAT_CN(_srcType);//求矩陣元素通道    CV_Assert( cn == CV_MAT_CN(_dstType) );//源陣列和目標陣列的通道數必須相等    int rsize = _rowKernel.rows + _rowKernel.cols - 1;//求行長    int csize = _columnKernel.rows + _columnKernel.cols - 1;//求列長    if( _anchor.x < 0 )//求被濾波點的位置        _anchor.x = rsize/2;
    if( _anchor.y < 0 )
        _anchor.y = csize/2;
    
    /*getKernelType()這個函式內部就不分析了,巨集觀上分析一下,其函式宣告為:
    int getKernelType(InputArray kernel, Point anchor)
    功能:根據輸入核係數矩陣kernel和被平滑點anchor來分析該核的型別,其型別主要有以下5種。
    1.普通核,沒什麼特點的
    2.對稱核,anchor點在中心,且中心點2邊的係數對稱相等
    3.反對稱核,anchor點也在中心,但中心點2邊的係數對稱相反
    4.平滑核,即每個數都是非負,且所有數相加為1
    5.整數核,即核內每個係數都是整數
    */    
    int rtype = getKernelType(_rowKernel,
        _rowKernel.rows == 1 ? Point(_anchor.x, 0) : Point(0, _anchor.x));//返回行矩陣核型別    int ctype = getKernelType(_columnKernel,
        _columnKernel.rows == 1 ? Point(_anchor.y, 0) : Point(0, _anchor.y));//返回列矩陣核型別    Mat rowKernel, columnKernel;

    /*在原始碼types_c.h中有
    #define CV_8U   0
    #define CV_8S   1
    #define CV_16U  2
    #define CV_16S  3
    #define CV_32S  4
    #define CV_32F  5
    #define CV_64F  6
    */
    
    int bdepth = std::max(CV_32F,std::max(sdepth, ddepth));//在sdepth,ddepth,CV_32F(即5)中選出一個最大的數    int bits = 0;

    if( sdepth == CV_8U &&
        ((rtype == KERNEL_SMOOTH+KERNEL_SYMMETRICAL &&//行列都是平滑對稱核,且型別為8位無符號整型          ctype == KERNEL_SMOOTH+KERNEL_SYMMETRICAL &&
          ddepth == CV_8U) ||
         ((rtype & (KERNEL_SYMMETRICAL+KERNEL_ASYMMETRICAL)) &&
          (ctype & (KERNEL_SYMMETRICAL+KERNEL_ASYMMETRICAL)) &&
          (rtype & ctype & KERNEL_INTEGER) &&   //或者行列都是整型對稱或反對稱核,且目標陣列型別為16位有符號型          ddepth == CV_16S)) )
    {
        bdepth = CV_32S; //重新給bdepth賦值        bits = ddepth == CV_8U ? 8 : 0;//當目標矩陣型別為CV_8U時,位深就為8,否則為0        
        /*convertTo()函式是源陣列線性變換成目標陣列,第二個引數為目標陣列的型別*/
        _rowKernel.convertTo( rowKernel, CV_32S, 1 << bits );//將源行陣列變換成32s的目標陣列        _columnKernel.convertTo( columnKernel, CV_32S, 1 << bits );//將源列陣列變換成32s的目標陣列        bits *= 2;//為0或者為16        _delta *= (1 << bits);//起放大作用?    }
    else
    {
        if( _rowKernel.type() != bdepth )
            _rowKernel.convertTo( rowKernel, bdepth );//將源行陣列深度轉換為目的陣列深度        else
            rowKernel = _rowKernel;  
        if( _columnKernel.type() != bdepth )
            _columnKernel.convertTo( columnKernel, bdepth );//將源列陣列深度轉換為目的陣列深度        else
            columnKernel = _columnKernel;
    }//到目前這一行為止,也只是做了一個非常簡單的工作,即把輸入的行列矩陣資料型別統一
    int _bufType = CV_MAKETYPE(bdepth, cn);//建立一個緩衝陣列型別,有深度和通道數2方面的資訊?
    /*Ptr<BaseRowFilter> _rowFilter表示建立一個引數為BaseRowFilter的具體類Ptr*/
    Ptr<BaseRowFilter> _rowFilter = getLinearRowFilter(
        _srcType, _bufType, rowKernel, _anchor.x, rtype);
    Ptr<BaseColumnFilter> _columnFilter = getLinearColumnFilter(
        _bufType, _dstType, columnKernel, _anchor.y, ctype, _delta, bits );//基本上也是完成資料型別的整理
    /*FilterEngine為一個通用的影象濾波類
    */
    
    return Ptr<FilterEngine>( new FilterEngine(Ptr<BaseFilter>(0), _rowFilter, _columnFilter,
        _srcType, _dstType, _bufType, _rowBorderType, _columnBorderType, _borderValue ));
    //新建立一個Ptr的模板類並用類FilterEngine的建構函式來初始化它}
複製程式碼

     接著分析函式createGaussianFilter。

     功能:給定濾波核大小和型別,以及2個sigma,就可以得出一個二維濾波核。兩個sigma允許輸入負數等其他不常用的輸入。

     其原始碼及註釋如下:

複製程式碼
cv::Ptr<cv::FilterEngine> cv::createGaussianFilter( int type, Size ksize,
                                        double sigma1, double sigma2,
                                        int borderType )
{
    int depth = CV_MAT_DEPTH(type);//取陣列元素的深度    if( sigma2 <= 0 )
        sigma2 = sigma1;//當第3個引數為非正時,取其與第二個引數相同的值

    // automatic detection of kernel size from sigma    /*一般情況下滿足sigma1>0*/
    if( ksize.width <= 0 && sigma1 > 0 )//當濾波器核的寬非正時,其寬要重新經過計算    /*根據CV_8U來計算,核寬為接近7*sigma1或者9*sigma1*/
        ksize.width = cvRound(sigma1*(depth == CV_8U ? 3 : 4)*2 + 1)|1;
    if( ksize.height <= 0 && sigma2 > 0 )
        /*同理,核高根據CV_8U來計算,為接近7*sigma2或者9*sigma2*/
        ksize.height = cvRound(sigma2*(depth == CV_8U ? 3 : 4)*2 + 1)|1;

    CV_Assert( ksize.width > 0 && ksize.width % 2 == 1 &&
        ksize.height > 0 && ksize.height % 2 == 1 );//確保核寬和核高為正奇數
    sigma1 = std::max( sigma1, 0. );//sigma最小為0    sigma2 = std::max( sigma2, 0. );

    Mat kx = getGaussianKernel( ksize.width, sigma1, std::max(depth, CV_32F) );//得到x方向一維高斯核    Mat ky;
    if( ksize.height == ksize.width && std::abs(sigma1 - sigma2) < DBL_EPSILON )
        ky = kx;//如果核寬和核高相等,且兩個sigma相差很小的情況下,y方向的高斯核去與x方向一樣,減少計算量    else
        ky = getGaussianKernel( ksize.height, sigma2, std::max(depth, CV_32F) );//否則計算y方向的高斯核係數
    return createSeparableLinearFilter( type, type, kx, ky, Point(-1,-1), 0, borderType );//返回2維影象濾波引擎}
複製程式碼

     最後來看真正的高斯濾波函式GaussianBlur:

    功能:對輸入影象_src進行濾波得到輸出影象_dst,濾波核大小為ksize,濾波引數由sigma1和sigma2計算出,邊緣擴充套件模式為borderType.

    其原始碼和註釋如下:

複製程式碼
void cv::GaussianBlur( InputArray _src, OutputArray _dst, Size ksize,
                   double sigma1, double sigma2,
                   int borderType )
{
    Mat src = _src.getMat();//建立一個矩陣src,利用_src的矩陣頭資訊    _dst.create( src.size(), src.type() );//構造與輸入矩陣同大小的目標矩陣    Mat dst = _dst.getMat();//建立一個目標矩陣    
    if( ksize.width == 1 && ksize.height == 1 )
    {
        src.copyTo(dst);//如果濾波器核的大小為1的話,則說明根本就不用濾波,輸出矩陣與輸入矩陣完全相同        return;
    }

    if( borderType != BORDER_CONSTANT )//當邊緣擴充套件不是常數擴充套件時    {
        if( src.rows == 1 )
            ksize.height = 1;//如果輸入矩陣是一個行向量,則濾波核的高強制為1        if( src.cols == 1 )
            ksize.width = 1;//如果輸入矩陣是一個列向量,則濾波核的寬強制為1    }

    /*生成一個高斯濾波器引擎f*/
    Ptr<FilterEngine> f = createGaussianFilter( src.type(), ksize, sigma1, sigma2, borderType );
    f->apply( src, dst );//呼叫引擎函式,完成將輸入矩陣src高斯濾波為輸出矩陣dst}
複製程式碼

     至此,函式GaussianBlur原始碼已經分析結束了,格式排版太累了!歡迎交流!

相關推薦

openCV 中的高濾波GaussianBlur函式

在上次的opencv原始碼解析之濾波前言1中,按照opencv_tutorials.pdf中的濾波部分試了下常用的4種濾波器的使用方法。在opencv的C++中,這4個函式分別為:blur,GaussianBlur,meidaBlur,bilateralFilter.下面

影象識別與處理之Opencv——高濾波GaussianBlur() 11月2日暫存

高斯濾波是一種線性平滑濾波,對於除去高斯噪聲有很好的效果。 在其官方文件中形容高斯濾波為”Probably the most useful filter”,同時也指出高斯濾波並不是效率最高的濾波演算法。 高斯演算法在官方文件給出的解釋是高斯濾波是通過對輸入陣

opencv學習(二十)之高濾波GaussianBlur()

高斯濾波是一種線性平滑濾波,對於除去高斯噪聲有很好的效果。在其官方文件中形容高斯濾波為”Probably the most useful filter”,同時也指出高斯濾波並不是效率最高的濾波演算法。高斯演算法在官方文件給出的解釋是高斯濾波是通過對輸入陣列的每個

opencv學習(十五)方框濾波boxfilter()//均值濾波blur()//高濾波GaussianBlur()(可以此回顧滑動條操作)

opencv原始碼剖析詳見參考書160-165,待深入理解 常用濾波概念 影象濾波過程 1,方框濾波boxfilter() 2,均值濾波blur() 3,高斯濾波Gassian

0027-用OpenCVGaussianBlur函式做高濾波

高斯濾波器是一類根據高斯函式的形狀來選擇權值的線性平滑濾波器,聽說高斯濾波器對於服從正太分佈(高斯分佈)的噪聲非常有效,然而實際來看,貌似效果也不怎麼樣啊,具體的大家可以看本篇帖子程式碼的執行結果,是筆者哪裡沒操作對麼?GaussianBlur函式原型如下: C++: void GaussianBl

基於opencv下對視頻的灰度變換,高濾波,canny邊緣檢測處理,同窗體顯示並保存

rmi 其他 AS info ali 利用 測試結果 14. 中間 如題:使用opencv打開攝像頭或視頻文件,實時顯示原始視頻,將視頻每一幀依次做灰度轉換、高斯濾波、canny邊緣檢測處理(原始視頻和這3個中間步驟處理結果分別在一個窗口顯示),最後將邊緣檢測結果保存為一個

opencvGaussianBlur()函式

opencv之GaussianBlur()函式 2018年04月17日 16:42:50 duwangthefirst 閱讀數:1507 標籤: opencvGaussianFilter高斯濾波影象去噪影象平滑 更多 個人分類: O

OpenCV入門教程之八】線性鄰域濾波專場:方框濾波、均值濾波與高濾波

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

方框濾波、均值濾波、高濾波濾波函式

方框濾波、均值濾波、高斯濾波及濾波函式相關函式如下: void cv::boxFilter( InputArray _src,OutputArray _dst, int ddepth, Size ksize, Point anchor,

opencv影象處理-------高濾波

opencv高斯濾波原理           高斯濾波是一種線性平滑濾波器,運用此濾波器,影象中各個點的畫素值由它鄰域畫素的加權累加值來替換。把鄰域中每個畫素位置對應的權重係數存放在一個矩陣中,矩陣中心的元素對應正在當前正在應用此濾波器的畫素,此

OpenCV學習筆記【七】方框濾波、均值濾波、高濾波

1.平滑處理 平滑處理(smoothing)也稱模糊處理(bluring),是一種簡單且使用頻率很高的影象處理方法。平滑處理的用途有很多,最常見的是用來減少影象上的噪點或者失真。在涉及到影象解析度時,平滑處理是非常好用的方法。 2.影象濾波與濾波器 影象濾波,指儘量保留影象細節特徵的條件

OpenCV入門教程之八】線性鄰域濾波專場 方框濾波 均值濾波與高濾波

本系列文章由@淺墨_毛星雲 出品,轉載請註明出處。 寫作當前博文時配套使用的OpenCV版本: 2.4.8本篇文章中,我們一起仔細探討了OpenCV影象處理技術中比較熱門的影象濾波操作。影象濾波系列文章淺墨準備花兩次更新的時間來講,此為上篇,為大家剖析了“方框濾波“,”均值濾波“和”高斯濾波“三種常見線性鄰域

openCV學習筆記(二十) —— 影象濾波 —— 線性濾波(方框濾波、均值濾波、高濾波

影象濾波簡介 方框濾波——boxFilter()  原理 方框濾波程式  #include<opencv2/opencv.hpp> #include <vector> #include <time.h> using

opencv實現影象鄰域均值濾波、中值濾波、高濾波

void CCVMFCView::OnBlurSmooth()//鄰域均值濾波 { IplImage* in; in = workImg; IplImage* out = cvCreateImage(cvGetSize(in),IPL_DEPTH_8U,workImg-&g

【拜小白opencv】30-平滑處理3線性濾波之——高濾波

常言道“溫故而知新”,寫此文章就是對自己目前學習內容的小小的總結與記錄。 本文力求用最簡潔的語言,詳細的程式碼將此部分內容講解清楚,但由於博主同樣是剛剛接觸OpenCV,或許表達上有些瑕疵,還望讀

CUDA實現影象的高濾波opencv實現)

高斯濾波簡介:       高斯濾波是通過對輸入陣列的每個點與輸入的高斯濾波模板執行卷積計算然後將這些結果一塊組成了濾波後的輸出陣列,通俗的講就是高斯濾波是對整幅影象進行加權平均的過程,每一個畫素點的值都由其本身和鄰域內的其他畫素值經過加權平均後得到。        高斯

[機器學習]SVM中高函式為什麼能對映到無窮維度

核函式: 高斯核函式: 根據泰勒公式,e的指數函式可以寫成無窮維的多項式函式, 高斯函式中有e的指數函式,通過推導可以得出兩個e的指數函式相乘的形式。進而高斯核函式就可以表示為無窮維空間的多項式內積了. 核函式的價值在於它雖然也是將特徵進行從低維到高維的轉換

openCV之高濾波(及程式碼實現)

        高斯濾波是一種線性平滑濾波,適用於消除高斯噪聲,廣泛應用於影象處理的減噪過程。通俗的講,高斯濾波就是對整幅影象進行加權平均的過程,每一個畫素點的值,都由其本身和鄰域內的其他畫素值經過加

python+opencv均值濾波,高濾波,中值濾波,雙邊濾波

濾波演算法主要包括均值濾波,高斯濾波,中值濾波和雙邊濾波。 每種演算法都有自己的特點,建議從原理上了解每種演算法的優缺點。上圖給出簡潔版的總結。 以下是程式碼: import numpy as

Python OpenCV _4初級濾波(均值濾波,高濾波,中值濾波

Python OpenCV這個初級影象處理系列是參考他人的文章寫的,有些地方做了一些改動,沒有太多理論,側重程式碼實現,主要目的是將這些基本操作程式碼系統地梳理一遍,也是為了以後能快速查詢。 理論就不說了,直接上程式碼和效果圖 原圖 一,均值濾波 程式碼: