天天看點

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

OpenCV C++ 方框濾波、均值濾波、高斯濾波——詳細講解

  • 一、理論與概念講解
    • <1>關于平滑處理
    • <2>圖像濾波與濾波器
    • <3>對線性濾波器的簡介
    • <4>關于濾波和模糊
    • <5>鄰域算子與線性鄰域濾波
  • 二、方框濾波——boxblur函數
  • 三、均值濾波——blur函數
    • 1)均值濾波的理論簡析
    • 2)均值濾波的缺陷
    • 3)在OpenCV中使用均值濾波——blur函數
  • 四、高斯濾波——GaussianBlur函數
  • 五、深入——OpenCV源碼剖析

本系列文章由@淺墨_毛星雲 出品,轉載請注明出處。

文章連結: http://blog.csdn.net/poem_qianmo/article/details/22745559

寫作目前博文時配套使用的OpenCV版本: 4.20

這篇文章中,我們一起仔細探讨了OpenCV圖像處理技術中比較熱門的圖像濾波操作。圖像濾波系列文章淺墨準備花兩次更新的時間來講,此為上篇,為大家剖析了“方框濾波“,”均值濾波“和”高斯濾波“三種常見線性鄰域濾波操作。而作為非線性濾波的“中值濾波”和“雙邊濾波”,留待我們下次剖析。

先上一張精彩截圖:

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

淺墨其實很希望把這篇文章寫得精簡和簡明扼要,發現越深入寫進去,需要講的周邊内容越多,于是文章越寫越長,

因為文章很長,如果詳細啃的話,或許會消化不良。在這裡給大家一個指引,如果是單單想要掌握這篇文章中講解的OpenCV線性濾波相關的三個函數:boxFilter,blur和GaussianBlur的使用方法的話,直接點選最上面目錄跳轉。

OK,我們開始吧。

一、理論與概念講解

<1>關于平滑處理

“平滑處理“(smoothing)也稱“模糊處理”(bluring),是一項簡單且使用頻率很高的圖像處理方法。平滑處理的用途有很多,最常見的是用來減少圖像上的噪點或者失真。在涉及到降低圖像分辨率時,平滑處理是非常好用的方法。

<2>圖像濾波與濾波器

首先我們看一下圖像濾波的概念。圖像濾波,即在盡量保留圖像細節特征的條件下對目标圖像的噪聲進行抑制,是圖像預進行中不可缺少的操作,其處理效果的好壞将直接影響到後續圖像處理和分析的有效性和可靠性。

消除圖像中的噪聲成分叫作圖像的平滑化或濾波操作。信号或圖像的能量大部分集中在幅度譜的低頻和中頻段是很常見的,而在較高頻段,感興趣的資訊經常被噪聲淹沒。是以一個能降低高頻成分幅度的濾波器就能夠減弱噪聲的影響。

圖像濾波的目的有兩個:一是抽出對象的特征作為圖像識别的特征模式;另一個是為适應圖像處理的要求,消除圖像數字化時所混入的噪聲。

而對濾波處理的要求也有兩條:一是不能損壞圖像的輪廓及邊緣等重要資訊;二是使圖像清晰視覺效果好。

平滑濾波是低頻增強的空間域濾波技術。它的目的有兩類:一類是模糊;另一類是消除噪音。(各種“兩",:))

空間域的平滑濾波一般采用簡單平均法進行,就是求鄰近像元點的平均亮度值。鄰域的大小與平滑的效果直接相關,鄰域越大平滑的效果越好,但鄰域過大,平滑會使邊緣資訊損失的越大,進而使輸出的圖像變得模糊,是以需合理選擇鄰域的大小。

關于濾波器,一種形象的比喻法是:我們可以把濾波器想象成一個包含權重系數的視窗,當使用這個濾波器平滑處理圖像時,就把這個視窗放到圖像之上,透過這個視窗來看我們得到的圖像。

濾波器的種類有很多, 在新版本的OpenCV中,提供了如下五種常用的圖像平滑處理操作方法,且他們分别被封裝在單獨的函數中,使用起來非常友善:

線性濾波:

方框濾波——boxblur函數

均值濾波(鄰域平均濾波)——blur函數

高斯濾波——GaussianBlur函數

非線性濾波:

中值濾波——medianBlur函數

雙邊濾波——bilateralFilter函數

今天我們要講解的是作為線性濾波的方框濾波,均值濾波和高斯濾波。兩種非線性濾波操作——中值濾波和雙邊濾波,我們留待下次講解。

<3>對線性濾波器的簡介

線性濾波器:線性濾波器經常用于剔除輸入信号中不想要的頻率或者從許多頻率中選擇一個想要的頻率。

幾種常見的線性濾波器:

允許低頻率通過的低通濾波器。

允許高頻率通過的高通濾波器。

允許一定範圍頻率通過的帶通濾波器。

阻止一定範圍頻率通過并且允許其它頻率通過的帶阻濾波器。

允許所有頻率通過、僅僅改變相位關系的全通濾波器。

阻止一個狹窄頻率範圍通過的特殊帶阻濾波器,陷波濾波器(Band-stop filter)。

<4>關于濾波和模糊

關于濾波和模糊,大家往往在初次接觸的時候會弄混淆,“一會兒說濾波,一會兒又說模糊,什麼玩意兒啊”。

沒關系,在這裡,我們就來辨識一下,為大家掃清障礙。

我們上文已經提到過,濾波是将信号中特定波段頻率濾除的操作,是抑制和防止幹擾的一項重要措施。

為了友善說明,就拿我們經常用的高斯濾波來作例子吧。我們知道,濾波可分低通濾波和高通濾波兩種。而高斯濾波是指用高斯函數作為濾波函數的濾波操作,至于是不是模糊,要看是高斯低通還是高斯高通,低通就是模糊,高通就是銳化。

其實說白了是很簡單的,對吧:

高斯濾波是指用高斯函數作為濾波函數的濾波操作。

高斯模糊就是高斯低通濾波。

<5>鄰域算子與線性鄰域濾波

鄰域算子(局部算子)是利用給定像素周圍的像素值的決定此像素的最終輸出值的一種算子。而線性鄰域濾波是一種常用的鄰域算子,像素的輸出值取決于輸入像素的權重和,具體過程如下圖。

鄰域算子除了用于局部色調調整以外,還可以用于圖像濾波,實作圖像的平滑和銳化,圖像邊緣增強或者圖像噪聲的去除。本篇文章,我們介紹的主角是線性鄰域濾波算子,即用不同的權重去結合一個小鄰域内的像素,來得到應有的處理效果。

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

圖注:鄰域濾波(卷積):左邊圖像與中間圖像的卷積産生右邊圖像。目标圖像中藍色标記的像素是利用原圖像中紅色标記的像素計算得到的。

線性濾波處理的輸出像素值g(i,j) 是輸入像素值 f(i+k,j+I) 的權重和 :

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

上面的式子可以簡單寫作:

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

其中f表示輸入像素值,h表示權重系數“核“,g表示輸出像素值

二、方框濾波——boxblur函數

方框濾波(box Filter)被封裝在一個名為boxblur的函數中,即boxblur函數的作用是使用方框濾波器(box filter)來模糊一張圖檔,從src輸入,從dst輸出。

函數原型如下:

C++: void boxFilter(InputArray src,OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1), boolnormalize=true, int borderType=BORDER_DEFAULT ) 
           

參數詳解:

第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。該函數對通道是獨立處理的,且可以處理任意通道數的圖檔,但需要注意,待處理的圖檔深度應該為CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。

第二個參數,OutputArray類型的dst,即目标圖像,需要和源圖檔有一樣的尺寸和類型。

第三個參數,int類型的ddepth,輸出圖像的深度,-1代表使用原圖深度,即src.depth()。

第四個參數,Size類型(對Size類型稍後有講解)的ksize,核心的大小。一般這樣寫Size( w,h )來表示核心的大小( 其中,w 為像素寬度, h為像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小

第五個參數,Point類型的anchor,表示錨點(即被平滑的那個點),注意他有預設值Point(-1,-1)。如果這個點坐标是負值的話,就表示取核的中心為錨點,是以預設值Point(-1,-1)表示這個錨點在核的中心。

第六個參數,bool類型的normalize,預設值為true,一個辨別符,表示核心是否被其區域歸一化(normalized)了。

第七個參數,int類型的borderType,用于推斷圖像外部像素的某種邊界模式。有預設值BORDER_DEFAULT,我們一般不去管它。

boxFilter()函數方框濾波所用的核為:

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

其中:

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

其中f表示原圖,h表示核,g表示目标圖,當normalize=true的時候,方框濾波就變成了我們熟悉的均值濾波。也就是說,均值濾波是方框濾波歸一化(normalized)後的特殊情況。其中,歸一化就是把要處理的量都縮放到一個範圍内,比如(0,1),以便統一處理和直覺量化。

而非歸一化(Unnormalized)的方框濾波用于計算每個像素鄰域内的積分特性,比如密集光流算法(dense optical flow algorithms)中用到的圖像倒數的協方差矩陣(covariance matrices of image derivatives)

如果我們要在可變的視窗中計算像素總和,可以使用integral()函數。

調用代碼示範如下:

//載入原圖
    Mat image=imread("2.jpg");
    //進行均值濾波操作
    Mat out;
    boxFilter(image, out, -1,Size(5, 5));
           

用上面三句核心代碼架起來的完整程式代碼:

//-----------------------------------【頭檔案包含部分】---------------------------------------
//     描述:包含程式所依賴的頭檔案
//----------------------------------------------------------------------------------------------
#include "opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
 
//-----------------------------------【命名空間聲明部分】---------------------------------------
//     描述:包含程式所使用的命名空間
//----------------------------------------------------------------------------------------------- 
using namespace cv;
 
//-----------------------------------【main( )函數】--------------------------------------------
//     描述:控制台應用程式的入口函數,我們的程式從這裡開始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //載入原圖
       Mat image=imread("2.jpg");
 
       //建立視窗
       namedWindow("均值濾波【原圖】" );
       namedWindow("均值濾波【效果圖】");
 
       //顯示原圖
       imshow("均值濾波【原圖】", image );
 
       //進行濾波操作
       Mat out;
       boxFilter(image, out, -1,Size(5, 5));
 
       //顯示效果圖
       imshow("均值濾波【效果圖】" ,out );
 
       waitKey(0 );    
}
           

運作效果圖(核心大小Size(5, 5)):

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

三、均值濾波——blur函數

均值濾波,是最簡單的一種濾波操作,輸出圖像的每一個像素是核視窗内輸入圖像對應像素的像素的平均值( 所有像素權重系數相等),其實說白了它就是歸一化後的方框濾波。

我們在下文進行源碼剖析時會發現,blur函數内部中其實就是調用了一下boxFilter。

1)均值濾波的理論簡析

均值濾波是典型的線性濾波算法,主要方法為鄰域平均法,即用一片圖像區域的各個像素的均值來代替原圖像中的各個像素值。一般需要在圖像上對目标像素給出一個模闆(核心),該模闆包括了其周圍的臨近像素(比如以目标像素為中心的周圍8(3x3-1)個像素,構成一個濾波模闆,即去掉目标像素本身)。再用模闆中的全體像素的平均值來代替原來像素值。即對待處理的目前像素點(x,y),選擇一個模闆,該模闆由其近鄰的若幹像素組成,求模闆中所有像素的均值,再把該均值賦予目前像素點(x,y),作為處理後圖像在該點上的灰階個g(x,y),即個g(x,y)=1/m ∑f(x,y) ,其中m為該模闆中包含目前像素在内的像素總個數。

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

2)均值濾波的缺陷

均值濾波本身存在着固有的缺陷,即它不能很好地保護圖像細節,在圖像去噪的同時也破壞了圖像的細節部分,進而使圖像變得模糊,不能很好地去除噪聲點。

3)在OpenCV中使用均值濾波——blur函數

blur函數的作用是,對輸入的圖像src進行均值濾波後用dst輸出。

blur函數文檔中,給出的其核是這樣的:

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

這個核心一看就明了,就是在求均值,即blur函數封裝的就是均值濾波。

blur函數的原型:

C++: void blur(InputArray src, OutputArraydst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
           

第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。該函數對通道是獨立處理的,且可以處理任意通道數的圖檔,但需要注意,待處理的圖檔深度應該為CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。

第二個參數,OutputArray類型的dst,即目标圖像,需要和源圖檔有一樣的尺寸和類型。比如可以用Mat::Clone,以源圖檔為模闆,來初始化得到如假包換的目标圖。

第三個參數,Size類型(對Size類型稍後有講解)的ksize,核心的大小。一般這樣寫Size( w,h )來表示核心的大小( 其中,w 為像素寬度, h為像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小

第四個參數,Point類型的anchor,表示錨點(即被平滑的那個點),注意他有預設值Point(-1,-1)。如果這個點坐标是負值的話,就表示取核的中心為錨點,是以預設值Point(-1,-1)表示這個錨點在核的中心。

第五個參數,int類型的borderType,用于推斷圖像外部像素的某種邊界模式。有預設值BORDER_DEFAULT,我們一般不去管它。

調用代碼示範:

//載入原圖
   Mat image=imread("1.jpg");
   //進行均值濾波操作
   Mat out;
   blur(image, out, Size(7, 7));
           

為了大家的了解和應用友善,還是給出用上面三句核心代碼架起來完整程式的代碼:

//-----------------------------------【頭檔案包含部分】---------------------------------------
//     描述:包含程式所依賴的頭檔案
//----------------------------------------------------------------------------------------------
#include "opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include <stdio.h>
 
//-----------------------------------【命名空間聲明部分】---------------------------------------
//     描述:包含程式所使用的命名空間
//----------------------------------------------------------------------------------------------- 
using namespace cv;
 
//-----------------------------------【main( )函數】--------------------------------------------
//     描述:控制台應用程式的入口函數,我們的程式從這裡開始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //載入原圖
       Mat image=imread("1.jpg");
 
       //建立視窗
       namedWindow("均值濾波【原圖】" );
       namedWindow("均值濾波【效果圖】");
 
       //顯示原圖
       imshow("均值濾波【原圖】", image );
 
       //進行濾波操作
       Mat out;
       blur(image, out, Size(7, 7));
 
       //顯示效果圖
       imshow("均值濾波【效果圖】" ,out );
 
       waitKey(0 );    
}
           

運作效果圖(核心大小Size(7, 7)):

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

四、高斯濾波——GaussianBlur函數

1)高斯濾波的理論簡析

高斯濾波是一種線性平滑濾波,适用于消除高斯噪聲,廣泛應用于圖像處理的減噪過程。通俗的講,高斯濾波就是對整幅圖像進行權重平均的過程,每一個像素點的值,都由其本身和鄰域内的其他像素值經過權重平均後得到。高斯濾波的具體操作是:用一個模闆(或稱卷積、掩模)掃描圖像中的每一個像素,用模闆确定的鄰域内像素的權重平均灰階值去替代模闆中心像素點的值。

大家常常說高斯濾波最有用的濾波操作,雖然它用起來,效率往往不是最高的。

高斯模糊技術生成的圖像,其視覺效果就像是經過一個半透明螢幕在觀察圖像,這與鏡頭焦外成像效果散景以及普通照明陰影中的效果都明顯不同。高斯平滑也用于計算機視覺算法中的預先處理階段,以增強圖像在不同比例大小下的圖像效果(參見尺度空間表示以及尺度空間實作)。從數學的角度來看,圖像的高斯模糊過程就是圖像與正态分布做卷積。由于正态分布又叫作高斯分布,是以這項技術就叫作高斯模糊。

圖像與圓形方框模糊做卷積将會生成更加精确的焦外成像效果。由于高斯函數的傅立葉變換是另外一個高斯函數,是以高斯模糊對于圖像來說就是一個低通濾波操作。

高斯濾波器是一類根據高斯函數的形狀來選擇權值的線性平滑濾波器。高斯平滑濾波器對于抑制服從正态分布的噪聲非常有效。一維零均值高斯函數為:

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

其中,高斯分布參數Sigma決定了高斯函數的寬度。對于圖像處理來說,常用二維零均值離散高斯函數作平滑濾波器。

二維高斯函數為:

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

2)在OpenCV中使用高斯濾波——GaussianBlur函數

GaussianBlur函數的作用是用高斯濾波器來模糊一張圖檔,對輸入的圖像src進行高斯濾波後用dst輸出。它将源圖像和指定的高斯核函數做卷積運算,并且支援就地過濾(In-placefiltering)。

C++: void GaussianBlur(InputArray src,OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, intborderType=BORDER_DEFAULT )
           

第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。它可以是單獨的任意通道數的圖檔,但需要注意,圖檔深度應該為CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。

第二個參數,OutputArray類型的dst,即目标圖像,需要和源圖檔有一樣的尺寸和類型。比如可以用Mat::Clone,以源圖檔為模闆,來初始化得到如假包換的目标圖。

第三個參數,Size類型的ksize高斯核心的大小。其中ksize.width和ksize.height可以不同,但他們都必須為正數和奇數。或者,它們可以是零的,它們都是由sigma計算而來。

第四個參數,double類型的sigmaX,表示高斯核函數在X方向的的标準偏差。

第五個參數,double類型的sigmaY,表示高斯核函數在Y方向的的标準偏差。若sigmaY為零,就将它設為sigmaX,如果sigmaX和sigmaY都是0,那麼就由ksize.width和ksize.height計算出來。

為了結果的正确性着想,最好是把第三個參數Size,第四個參數sigmaX和第五個參數sigmaY全部指定到。

第六個參數,int類型的borderType,用于推斷圖像外部像素的某種邊界模式。有預設值BORDER_DEFAULT,我們一般不去管它。

調用代碼示範如下:

//載入原圖
       Mat image=imread("1.jpg");
       //進行濾波操作
       Mat out;
       GaussianBlur( image, out, Size( 5, 5 ), 0, 0 ); 
           

用上面三句核心代碼架起來的完整程式代碼:

//-----------------------------------【頭檔案包含部分】---------------------------------------
//     描述:包含程式所依賴的頭檔案
//----------------------------------------------------------------------------------------------
#include "opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include <stdio.h>
 
//-----------------------------------【命名空間聲明部分】---------------------------------------
//     描述:包含程式所使用的命名空間
//----------------------------------------------------------------------------------------------- 
using namespace cv;
 
//-----------------------------------【main( )函數】--------------------------------------------
//     描述:控制台應用程式的入口函數,我們的程式從這裡開始
//-----------------------------------------------------------------------------------------------
int main( )
{
       //載入原圖
       Mat image=imread("1.jpg");
 
       //建立視窗
       namedWindow("均值濾波【原圖】" );
       namedWindow("均值濾波【效果圖】");
 
       //顯示原圖
       imshow("均值濾波【原圖】", image );
 
       //進行均值濾波操作
       Mat out;
       GaussianBlur(image, out, Size( 3, 3 ), 0, 0 );
 
       //顯示效果圖
       imshow("均值濾波【效果圖】" ,out );
 
       waitKey(0 );    
}
           

運作效果圖(核心大小Size(5, 5)):

【OpenCV C++ 】線性鄰域濾波:方框濾波、均值濾波、高斯濾波——詳細講解一、理論與概念講解二、方框濾波——boxblur函數三、均值濾波——blur函數四、高斯濾波——GaussianBlur函數五、深入——OpenCV源碼剖析

五、深入——OpenCV源碼剖析

**原文作者 OpenCV 2.4.8,安裝路徑下可找到函數的詳細源碼,目前作者版本4.20,路徑下并沒有詳細的源碼,隻能找到封裝好的函數**

<1>OpenCV中boxFilter函數源碼解析

我們可以在OpenCV的安裝路徑的\sources\modules\imgproc\src下的smooth.cpp源檔案的第711行找到boxFilter函數的源代碼。

//-----------------------------------【boxFilter()函數中文注釋版源代碼】----------------------------
//     代碼作用:進行box Filter濾波操作的函數
//     說明:以下代碼為來自于計算機開源視覺庫OpenCV的官方源代碼
//     OpenCV源代碼版本:2.4.8
//     源碼路徑:…\opencv\sources\modules\imgproc\src\smooth.cpp
//     源檔案中如下代碼的起始行數:711行
//     中文注釋by淺墨
//--------------------------------------------------------------------------------------------------------
void cv::boxFilter( InputArray _src,OutputArray _dst, int ddepth,
                Size ksize, Point anchor,
                bool normalize, int borderType)
{
   Mat src = _src.getMat();//拷貝源圖的形參Mat資料到臨時變量,用于稍後的操作
int sdepth =src.depth(), cn = src.channels();//定義int型臨時變量,代表源圖深度的sdepth,源圖通道的引用cn
 
//處理ddepth小于零的情況
   if( ddepth < 0 )
       ddepth = sdepth;
   _dst.create( src.size(), CV_MAKETYPE(ddepth, cn) );//初始化目标圖
Mat dst =_dst.getMat();//拷貝目标圖的形參Mat資料到臨時變量,用于稍後的操作
 
//處理 borderType不為 BORDER_CONSTANT 且normalize為真的情況
   if( borderType != BORDER_CONSTANT && normalize )    {
       if( src.rows == 1 )
           ksize.height = 1;
       if( src.cols == 1 )
           ksize.width = 1;
}


 
//若之前有過HAVE_TEGRA_OPTIMIZATION優化選項的定義,則執行宏體中的tegra優化版函數并傳回
#ifdef HAVE_TEGRA_OPTIMIZATION
   if ( tegra::box(src, dst, ksize, anchor, normalize, borderType) )
       return;
#endif
 
       //調用FilterEngine濾波引擎,正式開始濾波操作
   Ptr<FilterEngine> f = createBoxFilter( src.type(), dst.type(),
                        ksize, anchor,normalize, borderType );
   f->apply( src, dst );
}
           

其中的Ptr是用來動态配置設定的對象的智能指針模闆類。可以發現,函數的内部代碼思路是很清晰的,先拷貝源圖的形參Mat資料到臨時變量,定義一些臨時變量,在處理ddepth小于零的情況,接着處理 borderType不為 BORDER_CONSTANT 且normalize為真的情況,最終調用FilterEngine濾波引擎建立一個BoxFilter,正式開始濾波操作。

這裡的FilterEngine是OpenCV圖像濾波功能的核心引擎,我們有必要詳細剖析看其源代碼。

<2>FilterEngine類解析——OpenCV圖像濾波核心引擎

FilterEngine類是OpenCV關于圖像濾波的主力軍類,OpenCV圖像濾波功能的核心引擎。各種濾波函數比如blur, GaussianBlur,到頭來其實是就是在函數末尾處定義了一個Ptr類型的f,然後f->apply( src, dst )了一下而已。

這個類可以把幾乎是所有的濾波操作施加到圖像上。它包含了所有必要的中間緩存器。有很多和濾波相關的create系函數的傳回值直接就是Ptr。比如cv::createSeparableLinearFilter(),

cv::createLinearFilter(),cv::createGaussianFilter(), cv::createDerivFilter(),

cv::createBoxFilter() 和cv::createMorphologyFilter().,這裡給出其中一個函數的原型吧:

Ptr<FilterEngine>createLinearFilter(int srcType, int dstType, InputArray kernel, Point_anchor=Point(-1,-1), double delta=0, int rowBorderType=BORDER_DEFAULT, intcolumnBorderType=-1, const Scalar& borderValue=Scalar() )
           

上面我們提到過了,其中的Ptr是用來動态配置設定的對象的智能指針模闆類,而上面的尖括号裡面的模闆參數就是FilterEngine。

使用FilterEngine類可以分塊處理大量的圖像,建構複雜的管線,其中就包含一些進行濾波階段。如果我們需要使用預先定義好的的濾波操作,cv::filter2D(), cv::erode(),以及cv::dilate(),可以選擇,他們不依賴于FilterEngine,自立自強,在自己函數體内部就實作了FilterEngine提供的功能。不像其他的諸如我們今天講的blur系列函數,依賴于FilterEngine引擎。

我們看下其類聲明經過淺墨詳細注釋的源碼:

//-----------------------------------【FilterEngine類中文注釋版源代碼】----------------------------
//     代碼作用:FilterEngine類,OpenCV圖像濾波功能的核心引擎
//     說明:以下代碼為來自于計算機開源視覺庫OpenCV的官方源代碼
//     OpenCV源代碼版本:2.4.8
//     源碼路徑:…\opencv\sources\modules\imgproc\include\opencv2\imgproc\imgproc.hpp
//     源檔案中如下代碼的起始行數:222行
//     中文注釋by淺墨
//--------------------------------------------------------------------------------------------------------
 
class CV_EXPORTS FilterEngine
{
public:
   //預設構造函數
   FilterEngine();
   //完整的構造函數。 _filter2D 、_rowFilter 和 _columnFilter之一,必須為非空
   FilterEngine(const Ptr<BaseFilter>& _filter2D,
                 constPtr<BaseRowFilter>& _rowFilter,
                constPtr<BaseColumnFilter>& _columnFilter,
                 int srcType, int dstType, intbufType,
                 int_rowBorderType=BORDER_REPLICATE,
                 int _columnBorderType=-1,
                 const Scalar&_borderValue=Scalar());
   //預設析構函數
   virtual ~FilterEngine();
   //重新初始化引擎。釋放之前濾波器申請的記憶體。
   void init(const Ptr<BaseFilter>& _filter2D,
              constPtr<BaseRowFilter>& _rowFilter,
              constPtr<BaseColumnFilter>& _columnFilter,
              int srcType, int dstType, intbufType,
              int_rowBorderType=BORDER_REPLICATE, int _columnBorderType=-1,
              const Scalar&_borderValue=Scalar());
   //開始對指定了ROI區域和尺寸的圖檔進行濾波操作
   virtual int start(Size wholeSize, Rect roi, int maxBufRows=-1);
    //開始對指定了ROI區域的圖檔進行濾波操作
   virtual int start(const Mat& src, const Rect&srcRoi=Rect(0,0,-1,-1),
                      bool isolated=false, intmaxBufRows=-1);
   //處理圖像的下一個srcCount行(函數的第三個參數)
   virtual int proceed(const uchar* src, int srcStep, int srcCount,
                        uchar* dst, intdstStep);
   //對圖像指定的ROI區域進行濾波操作,若srcRoi=(0,0,-1,-1),則對整個圖像進行濾波操作
   virtual void apply( const Mat& src, Mat& dst,
                        const Rect&srcRoi=Rect(0,0,-1,-1),
                        Point dstOfs=Point(0,0),
                        bool isolated=false);
 
   //如果濾波器可分離,則傳回true
boolisSeparable() const { return (const BaseFilter*)filter2D == 0; }
 
   //傳回輸入和輸出行數
   int remainingInputRows() const;
intremainingOutputRows() const;
 
   //一些成員參數定義
   int srcType, dstType, bufType;
   Size ksize;
   Point anchor;
   int maxWidth;
   Size wholeSize;
   Rect roi;
   int dx1, dx2;
   int rowBorderType, columnBorderType;
   vector<int> borderTab;
   int borderElemSize;
   vector<uchar> ringBuf;
   vector<uchar> srcRow;
   vector<uchar> constBorderValue;
   vector<uchar> constBorderRow;
   int bufStep, startY, startY0, endY, rowCount, dstY;
   vector<uchar*> rows;
 
   Ptr<BaseFilter> filter2D;
   Ptr<BaseRowFilter> rowFilter;
   Ptr<BaseColumnFilter> columnFilter;
};
           

<3>OpenCV中size類型剖析

size類型我們也講一下,通過轉到定義,我們可以在……\opencv\sources\modules\core\include\opencv2\core\core.hpp路徑下

找到其原型聲明:

typedef Size_<int> Size2i;
typedef Size2i Size;
           

Size_ 是個模闆類,在這裡Size_表示其類體内部的模闆所代表的類型為int。

那這兩句的意思,就是首先給已知的資料類型Size_起個新名字,叫Size2i。

然後又給已知的資料類型Size2i起個新名字,叫Size。

是以,連起來就是,Size_、Size2i、Size這三個類型名等價。

然後我們追根溯源,找到Size_模闆類的定義:

//-----------------------------------【Size_類中文注釋版源代碼】----------------------------
//     代碼作用:作為尺寸相關資料結構的Size_ 模闆類
//     說明:以下代碼為來自于計算機開源視覺庫OpenCV的官方源代碼
//     OpenCV源代碼版本:2.4.8
//     源碼路徑:…\opencv\sources\modules\core\include\opencv2\core\core.hpp
//     源檔案中如下代碼的起始行數:816行
//     中文注釋by淺墨
//--------------------------------------------------------------------------------------------------------
template<typename _Tp> class Size_
{
public:
   typedef _Tp value_type;
 
   //不同的構造函數定義
   Size_();
   Size_(_Tp _width, _Tp _height);
   Size_(const Size_& sz);
   Size_(const CvSize& sz);
   Size_(const CvSize2D32f& sz);
   Size_(const Point_<_Tp>& pt);
 
   Size_& operator = (const Size_& sz);
   //區域(width*height)
   _Tp area() const;
 
   //轉化另一種資料類型。
    template<typename_Tp2> operator Size_<_Tp2>() const;
 
   //轉換為舊式的OpenCV類型.
   operator CvSize() const;
   operator CvSize2D32f() const;
 
   _Tp width, height; //寬度和高度,常用屬性
};
           

可以看到Size_模闆類的内部又是重載了一些構造函數以滿足我們的需要,其中,我們用得最多的是如下這個構造函數:

Size_(_Tp _width, _Tp _height);
           

另外,代碼末尾定義了模闆類型的寬度和高度:

_Tp width, height; //寬度和高度
           

于是我們可以用XXX.width和XXX.height來分别表示其寬度和高度。

給大家一個示例,友善了解:

Size(5, 5);//構造出的Size寬度和高度都為5,即XXX.width和XXX.height都為5
           

<4>OpenCV中blur函數源碼剖析

我們可以在OpenCV的安裝路徑的\sources\modules\imgproc\src下的smooth.cpp源檔案中找到blur的源代碼。對應于淺墨将OpenCV 2.4.8安裝在D:\Program Files\opencv下,那麼,smooth.cpp檔案就在D:\ProgramFiles\opencv\sources\modules\imgproc\src路徑下。

一起來看一下OpenCV中blur函數定義的真面目:

//-----------------------------------【blur()函數中文注釋版源代碼】----------------------------
//     代碼作用:進行blur均值濾波操作的函數
//     說明:以下代碼為來自于計算機開源視覺庫OpenCV的官方源代碼
//     OpenCV源代碼版本:2.4.8
//     源碼路徑:…\opencv\sources\modules\imgproc\src\smooth.cpp
//     源檔案中如下代碼的起始行數:738行
//     中文注釋by淺墨
//--------------------------------------------------------------------------------------------------------
 
void cv::blur(InputArray src, OutputArray dst,
          Size ksize, Point anchor, int borderType )
{
//調用boxFilter函數進行處理
   boxFilter( src, dst, -1, ksize, anchor, true, borderType );
}
           

可以看到在blur函數内部就是調用了一個boxFilter函數,且第六個參數為true,即我們上文所說的normalize=true,即均值濾波是均一化後的方框濾波。

<5>OpenCV中GaussianBlur函數源碼剖析

最後,我們看一下OpenCV中GaussianBlur函數源代碼:

//-----------------------------------【GaussianBlur()函數中文注釋版源代碼】-----------------------
//     代碼作用:封裝高斯濾波的GaussianBlur()函數
//     說明:以下代碼為來自于計算機開源視覺庫OpenCV的官方源代碼
//     OpenCV源代碼版本:2.4.8
//     源碼路徑:…\opencv\sources\modules\imgproc\src\smooth.cpp
//     源檔案中如下代碼的起始行數:832行
//     中文注釋by淺墨
//--------------------------------------------------------------------------------------------------------
 
void cv::GaussianBlur( InputArray _src,OutputArray _dst, Size ksize,
                   double sigma1, doublesigma2,
                   int borderType )
{
//拷貝形參Mat資料到臨時變量,用于稍後的操作
   Mat src = _src.getMat();
   _dst.create( src.size(), src.type() );
Mat dst =_dst.getMat();
 
//處理邊界選項不為BORDER_CONSTANT時的情況
   if( borderType != BORDER_CONSTANT )
    {
       if( src.rows == 1 )
           ksize.height = 1;
       if( src.cols == 1 )
           ksize.width = 1;
}
 
       //若ksize長寬都為1,将源圖拷貝給目标圖
   if( ksize.width == 1 && ksize.height == 1 )
    {
       src.copyTo(dst);
       return;
}
 
//若之前有過HAVE_TEGRA_OPTIMIZATION優化選項的定義,則執行宏體中的tegra優化版函數并傳回
#ifdef HAVE_TEGRA_OPTIMIZATION
   if(sigma1 == 0 && sigma2 == 0 && tegra::gaussian(src,dst, ksize, borderType))
       return;
#endif
 
//如果HAVE_IPP&& (IPP_VERSION_MAJOR >= 7為真,則執行宏體中語句
#if defined HAVE_IPP &&(IPP_VERSION_MAJOR >= 7)
   if(src.type() == CV_32FC1 && sigma1 == sigma2 &&ksize.width == ksize.height && sigma1 != 0.0 )
    {
       IppiSize roi = {src.cols, src.rows};
       int bufSize = 0;
       ippiFilterGaussGetBufferSize_32f_C1R(roi, ksize.width, &bufSize);
       AutoBuffer<uchar> buf(bufSize+128);
       if( ippiFilterGaussBorder_32f_C1R((const Ipp32f *)src.data,(int)src.step,
                                         (Ipp32f *)dst.data, (int)dst.step,
                                          roi,ksize.width, (Ipp32f)sigma1,
                                         (IppiBorderType)borderType, 0.0,
                                         alignPtr(&buf[0],32)) >= 0 )
           return;
    }
#endif
 
   //調動濾波引擎,正式進行高斯濾波操作
   Ptr<FilterEngine> f = createGaussianFilter( src.type(), ksize,sigma1, sigma2, borderType );
   f->apply( src, dst );
}
           

嗯,今天的源碼解析就到這裡吧,原理部分學完,深入OpenCV源碼部分也學完,相信大家應該對OpenCV中的線性濾波有了比較詳細的認識,已經躍躍欲試想看這個幾個函數用起來可以得出什麼效果了。

下一篇:

OpenCV 線性濾波(方框濾波、均值濾波、高斯濾波)綜合示例

https://blog.csdn.net/m0_51233386/article/details/115220623

繼續閱讀