opencv源碼解析之(4):GaussianBlur()

本文轉載自:https://www.cnblogs.com/tornadomeet/archive/2012/03/10/2389617.html 作者:tornadomeet 轉載請註明該聲明。

     這一節來真正進入opencv的源碼分析中,本次分析的函數是GaussianBlur(),即高斯濾波函數。在前前面博文《opencv源碼解析之濾波前言2》:http://www.cnblogs.com/tornadomeet/archive/2012/03/05/2379921.html 中已經闡述了這個函數的用法,即:

     其函數聲明爲:

     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源碼已經分析結束了,格式排版太累了!歡迎交流!



 

發佈了0 篇原創文章 · 獲贊 49 · 訪問量 30萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章