Opencvソース解析の(4):GaussianBlue()

25373 ワード

この節ではopencvのソースコード解析に本格的に入り,今回解析した関数はGaussianBlur(),すなわちGaussフィルタ関数である.前述の博文「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はGaussフィルタテンプレートサイズであり,sigmaXとsigmaYはそれぞれGaussフィルタの横線と縦方向のフィルタ係数である.borderTypeはエッジ拡張点補間タイプです.
 
次の作業はGaussianBlur関数の内部に入って、その関数コードを追跡して、分析を経て、この関数の内部で多くの他の関数を呼び出して、その呼び出しの関数の階層は下図のようです:
opencv源码解析之(4):GaussianBlur()_第1张图片
ここでは、ソースコードを最下層に深く分析する必要はありません.関数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で、1つの2次元フィルタコアを得ることができます.2つの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、フィルタパラメータはsigma 1とsigma 2から算出する、エッジ拡張モードは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のソースコードの分析が終わり、フォーマットのレイアウトが疲れました!交流を歓迎します!