Gabor滤波是一种非常常见的特征提取算法,在人脸识别等领域有着很广泛的应用,在这里我主要介绍一下Gabor滤波器的公式及Opencv下的代码实现,以及我做的一些参数变化的实验。
注意,这里我介绍的Gabor算法与在人脸识别中使用的Gabor算法貌似是不太相同的,具体内容我没有深入了解。
Gabor滤波的公式如下所示:
其中实数部分为:
虚数部分为:
这里面的参数:
(1)x,y分别表示像素坐标位置;
(2)λ表示滤波的波长;
(3)θ表示Gabor核函数图像的倾斜角度;
(4)ψ表示相位偏移量,取值范围是-180~180;
(5)σ表示高斯函数的标准差;
(6)γ表示长宽比,决定这Gabor核函数图像的椭圆率。
这里的参数不了解不要紧,后面我将会针对这些参数进行一系列的实验,直观的展示出这些参数的作用。
在Opencv中的给出了实现核函数的代码,如下所示,这里我将我的理解作为注释加入到代码中:
//获取Gabor的核函数,并以Mat的形式返回
//ksize表示核函数窗口大小,sigma,theta,lambd,gamma和psi分别为前面提到的参数
//ktype表示需要获得的核函数矩阵的数据类型
cv::Mat cv::getGaborKernel( Size ksize, double sigma, double theta,
double lambd, double gamma, double psi, int ktype )
{
//获取两个数学遍历sigma_x和sigma_y,方便后面的计算,没有特殊含义
double sigma_x = sigma;
double sigma_y = sigma/gamma;
int nstds = 3;//为后面的当ksize出现负数时计算核函数窗口大小提供参数
int xmin, xmax, ymin, ymax;
double c = cos(theta), s = sin(theta);
//当ksize的width和height没有负数时,取其一半给xmax和ymax
if( ksize.width > 0 )
xmax = ksize.width/2;
else
xmax = cvRound(std::max(fabs(nstds*sigma_x*c), fabs(nstds*sigma_y*s)));
if( ksize.height > 0 )
ymax = ksize.height/2;
else
ymax = cvRound(std::max(fabs(nstds*sigma_x*s), fabs(nstds*sigma_y*c)));
xmin = -xmax;
ymin = -ymax;
CV_Assert( ktype == CV_32F || ktype == CV_64F );
//核函数矩阵,ymax-ymin+1和xmax-xmin+1是为保证核函数矩阵是长和宽都是奇数
Mat kernel(ymax - ymin + 1, xmax - xmin + 1, ktype);
double scale = 1;
//方便计算,可以自己带入到后面的v的计算公式中会发现Gabor滤波器的实数部分的公式
double ex = -0.5/(sigma_x*sigma_x);
double ey = -0.5/(sigma_y*sigma_y);
double cscale = CV_PI*2/lambd;
for( int y = ymin; y <= ymax; y++ )
for( int x = xmin; x <= xmax; x++ )
{
double xr = x*c + y*s;
double yr = -x*s + y*c;
//计算公式,Opencv这里获取的是Gabor滤波器的实数部分
double v = scale*exp(ex*xr*xr + ey*yr*yr)*cos(cscale*xr + psi);
if( ktype == CV_32F )
kernel.at(ymax - y, xmax - x) = (float)v;
else
kernel.at(ymax - y, xmax - x) = v;
}
return kernel;
}
这里Opencv只是获得了Gabor的实数部分,为何没有虚数部分我暂时还不太清楚,如果有人清楚请留言指出,谢谢!
1、λλ(波长)变化:
//注意:为了演示方便这里采用的核函数窗口大小比较大,一般情况下不会采用这么大的核函数窗口
//另外,如要保存核函数结果,记得先把图像归一化到0~255以后再保存,否则可能只有黑色
int main()
{
Mat kernel1 = getGaborKernel(Size(311, 311), 10, 0, 5, 0.5, 0, CV_32F);
Mat kernel2 = getGaborKernel(Size(311, 311), 10, 0, 10, 0.5, 0, CV_32F);
Mat kernel3 = getGaborKernel(Size(311, 311), 10, 0, 15, 0.5, 0, CV_32F);
Mat kernel4 = getGaborKernel(Size(311, 311), 10, 0, 20, 0.5, 0, CV_32F);
imshow("lambda: 5", kernel1);
imshow("lambda: 10", kernel2);
imshow("lambda: 15", kernel3);
imshow("lambda: 20", kernel4);
waitKey();
return 0;
}
可以看出波长越大,黑白相间的间隔越大
(2)θ变化:
//注意:角度要转换为弧度
int main()
{
Mat kernel1 = getGaborKernel(Size(311, 311), 10, 0, 10, 0.5, 0, CV_32F);
Mat kernel2 = getGaborKernel(Size(311, 311), 10, CV_PI * 0.25, 10, 0.5, 0, CV_32F);
Mat kernel3 = getGaborKernel(Size(311, 311), 10, CV_PI * 0.5, 10, 0.5, 0, CV_32F);
Mat kernel4 = getGaborKernel(Size(311, 311), 10, CV_PI * 0.75, 10, 0.5, 0, CV_32F);
imshow("theta: 0", kernel1);
imshow("theta: 45", kernel2);
imshow("theta: 90", kernel3);
imshow("theta: 135", kernel4);
waitKey();
return 0;
}
(3)ψ的变化:
当ψ为0时以白条为中心,当ψ为180时,以黑条为中心
(4)σ的变化:
int main()
{
Mat kernel1 = getGaborKernel(Size(311, 311), 5, 0, 10, 0.5, 0, CV_32F);
Mat kernel2 = getGaborKernel(Size(311, 311), 10, 0, 10, 0.5, 0, CV_32F);
Mat kernel3 = getGaborKernel(Size(311, 311), 15, 0, 10, 0.5, 0, CV_32F);
Mat kernel4 = getGaborKernel(Size(311, 311), 20, 0, 10, 0.5, 0, CV_32F);
imshow("sigma: 5", kernel1);
imshow("sigma: 10", kernel2);
imshow("sigma: 15", kernel3);
imshow("sigma: 20", kernel4);
waitKey();
return 0;
}
可以看出,随着σ的增大,条纹数量越多
(5)γ的变化:
int main()
{
Mat kernel1 = getGaborKernel(Size(311, 311), 10, 0, 10, 0.5, 0, CV_32F);
Mat kernel2 = getGaborKernel(Size(311, 311), 10, 0, 10, 1.0, 0, CV_32F);
Mat kernel3 = getGaborKernel(Size(311, 311), 10, 0, 10, 1.5, 0, CV_32F);
Mat kernel4 = getGaborKernel(Size(311, 311), 10, 0, 10, 2.0, 0, CV_32F);
imshow("gamma: 0.5", kernel1);
imshow("gamma: 1.0", kernel2);
imshow("gamma: 1.5", kernel3);
imshow("gamma: 2.0", kernel4);
waitKey();
return 0;
}
可以看出,随着γγ的增大,核函数图像形状会发生改变,γγ越小,核函数图像会越高,随着其增大,图像会变的越矮。
再得到Gabor核函数以后,其使用方法就比较简单了,其实就是使用该核函数窗口对图像进行卷积操作,参考小魏的修行路【图像处理】Gabor滤波器中使用Gabor滤波和图像归一化的方法。
注:Opencv中Gabor滤波只使用实数部分,同时wiki中Gabor filter中的介绍和实现示例也同样只使用了实数部分,《基于 Gabor 滤波器的特征抽取技术》一文中提到,Gabor滤波中使用了复数,计算量会比较大,而已经有很多应用只使用实数部分近似得到了不错的结果,所以可能因此这里使用了实数部分。