紋理分割(一)Gabor濾波器學習

第一個項目根據公司那邊提供的學習資料,需要用到Gabor濾波器對圖像進行處理,公司那邊關於項目的說法比較商業化,叫X-Ray Image Auto Judging System,之前找了很久論文都沒有思路,用這個英文查找論文,也是不對路,這讓我在前期浪費不少時間,後來查閱大量論文之後,確定關於目前的項目的學術說法應該是“輪胎X射線圖缺陷檢測”,英文是“X-Ray Defect Detection”,目前放暑假,不在學校檢索論文比較麻煩,只能根據各路大神網友的博客在做這個項目。


這個項目涉及的主要學術方向是“複雜多紋理圖像提取”,能查到的最普遍的做法是採用Gabor濾波器對圖像進行處理,可能閱讀論文量不到,也許有更好的圖像處理方法和思路,這裏記錄下我目前的做法吧,有效果,但是不是特別好。


看到的大神寫到的博客,非常詳細,大部分信息整理自以下博客:

1. Gabor濾波器詳細介紹:http://mplab.ucsd.edu/tutorials/gabor.pdf

2. Gabor濾波器學習:http://blog.csdn.net/jinshengtao/article/details/17797641

3. 國外大學對Gabor濾波器的參數的講解:http://matlabserver.cs.rug.nl/edgedetectionweb/web/edgedetection_params.html

4. 備份論文,在校外沒法下載:http://www.researchgate.net/publication/252703921_Designing_multiple_Gabor_filters_for_multitexture_image_segmentation

5. 德國某公司的宣傳網頁,看樣子做的非常棒:http://www.cyxplus.fr/produit/our-industy-activities/cyxpert-automatic-defect-detection


一、Gabor Filter 

一維Gabor濾波器

Gabor濾波器是處理一維信號(比如音頻)最佳的帶通濾波器,一個複雜的Gabor濾波器是一個高斯核函數乘以一個複雜的sin函數(A complex Gabor filter is defined as the product of a Gaussian kernel times a complex sinusoid),比如:


k  theta  fo是濾波器的參數。我們可以把這個複雜的Gabor濾波器想象成兩個相位濾波器分成了一個複雜函數的實部和虛部,

二維Gabor濾波器

但是我要用到的是二維Gabor濾波器,也叫空間Gabor濾波器(The Spatial (2-D) Gabor Filter),但是第一條參考中給出的二維Gabor濾波器講解太過於。。怎麼說。

按照普通的解釋來吧,二維Gabor函數的數學表達式:

如何得到的這個公式呢?大神博客http://blog.csdn.net/yanmy2012/article/details/8090400)中給出的詳細求解過程,截圖如下:




根據第三條參考文獻的解釋,關於實部和虛部的說明,只有一句話,實部可以對圖像進行平滑濾波,虛部可以用來邊緣檢測,具體的用法可以可以參考相關論文,不在學校下論文不方便,暫且寫這點吧。

下面具體看一下Gabor濾波器的參數說明:




二、Gabor濾波器的應用和適應性

根據第二條參考文獻給出的說明,可以總結以下幾點:

1. Gabor濾波器可以很好的近似單細胞的感受野細胞(光強刺激下的傳遞函數),在提取目標的局部空間和頻率域信息方面具有良好的特性。

2. 雖然Gabor小波本身不能構成正交基,但在特定參數下可構成緊框架。Gabor小波對於圖像的邊緣敏感,能夠提供良好的方向選擇和尺度選擇特性,而且對於光照變化不敏感,能夠提供對光照變化良好的適應性。-------Gabor小波被廣泛應用於視覺信息理解

3. 二維Gabor小波變換是在時頻域進行信號分析處理的重要工具,其變換系數有着良好的視覺特性和生物學背景。------因此被廣泛應用於圖像處理、模式識別等領域。

4. 與傳統的傅立葉變換相比,Gabor小波變換具有良好的時頻局部化特性。即非常容易地調整Gabor濾波器的方向、基頻帶寬及中心頻率從而能夠最好的兼顧信號在時空域和頻域中的分辨能力.

5. Gabor小波變換具有多分辨率特性即變焦能力。即採用多通道濾波技術,將一組具有不同時頻域特性的Gabor小波應用於圖像變換,每個通道都能夠得到輸入圖像的某種局部特性,這樣可以根據需要在不同粗細粒度上分析圖像。

6. 在特徵提取方面,Gabor小波變換與其它方法相比:一方面其處理的數據量較少,能滿足系統的實時性要求;另一方面,小波變換對光照變化不敏感,且能容忍一定程度的圖像旋轉和變形,當採用基於歐氏距離進行識別時,特徵模式與待測特徵不需要嚴格的對應,故能提高系統的魯棒性。

爲什麼能夠有這些應用呢?我們需要深入的理解下Gabor的特性。根據大神博客(http://blog.csdn.net/yanmy2012/article/details/8090400)中的詳細解釋。在基本視覺皮層裏的簡單細胞的感受野侷限在很小的空域範圍內,並且高度結構化。

1. Gabor變換所採用的核(Kernels)與哺乳動物視覺皮層簡單細胞2D感受野剖面(Profile)非常相似,具有優良的空間局部性和方向選擇性,能夠抓住圖像局部區域內多個方向的空間頻率(尺度)和局部性結構特徵。這樣,Gabor分解可以看作一個對方向和尺度敏感的有方向性的顯微鏡

2. 二維Gabor函數也類似於增強邊緣以及峯、谷、脊輪廓等底層圖像特徵,這相當於增強了被認爲是面部關鍵部件的眼睛、鼻子、嘴巴等信息,同時也增強了諸於黑痣、酒窩、傷疤等局部特徵,從而使得在保留總體人臉信息的同時增強局部特性成爲可能它的小波特性說明了Gabor濾波結果是述圖像局部灰度分佈的有力工具,因此,可以使用Gabor濾波來抽取圖像的紋理信息。

3.  由於Gabor特徵具有良好的空間局部性和方向選擇性,而且對光照、姿態具有一定的魯棒性,因此在人臉識別中獲得了成功的應用。然而,大部分基於Gabor特徵的人臉識別算法中,只應用了Gabor幅值信息,而沒有應用相位信息,主要原因是Gabor相位信息隨着空間位置呈週期性變化,而幅值的變化相對平滑而穩定,幅值反映了圖像的能量譜,Gabor幅值特徵通常稱爲Gabor 能量特徵(Gabor Energy Features)Gabor小波可像放大鏡一樣放大灰度的變化,人臉的一些關鍵功能區域(眼睛、鼻子、嘴、眉毛等)的局部特徵被強化,從而有利於區分不同的人臉圖像。

4. Gabor小波核函數具有與哺育動物大腦皮層簡單細胞的二維反射區相同的特性,即具有較強的空間位置和方向選擇性,並且能夠捕捉對應於空間和頻率的局部結構信息;Gabor濾波器對於圖像的亮度和對比度變化以及人臉姿態變化具有較強的健壯性,並且它表達的是對人臉識別最爲有用的局部特徵。Gabor 小波是對高級脊椎動物視覺皮層中的神經元的良好逼近,是時域和頻域精確度的一種折中。

三、Gabor濾波器的編程實現

這裏我用opencv做的,因爲對matlab不是很熟,二是工業應用一般都是C或C++做的,代碼給出吧,我用mfc做的界面,之前有好幾個版本,驗證了幾個算法,最後這個就是爲了看效果的,比較簡單。因爲用的opencv3.0,所以顯示圖像時遇到了麻煩,查找相關資料,解決辦法是自己建立CvvImage.h CvvImage.cpp文件,添加到工程中即可。還有一個要說明的是,Gabor濾波器的實現,是用到的網上搜到的Gabor.h Gabor.cpp實現的,只是進行了簡單的註釋和文檔整理。原作者。。。額,sorry,找的資料太多,不知道出處了,見諒啊!


Gabor.h頭文件

#ifndef _GABOR_H
#define _GABOR_H
#include <stdio.h>
#include <iostream>
#include <cv.h>

#define PI 3.14159
#define GAMMA  0.5   //The default value of γ,which is the spatial aspect ratio (sigma_x/sigma_y)
#define RATIO_S2L 0.56  //The default value of σ/λ
#define THETA 45
class Gabor
{
public:
	//@construct 構造函數
	Gabor(float dLambda, float dTheta, float dRatio_S2L = RATIO_S2L, float dGamma = GAMMA, float dPhi = 0);
	//@abolish 析構函數
	~Gabor();

	//@init 初始化函數
	void init(float dLambda, float dTheta, float dPhi, float dGamma = GAMMA);
	//@init 初始化函數
	void init(float dSigma, float dTheta, float dPhi);
	//@init 初始化函數
	void init();


	//判斷Gabor內核是否創建成功--@To find whether the Gabor kernel is created
	bool is_kernel(){ return bKernel; }
	//判斷是否初始化成功--@To find whether the parameters is inited
	bool is_init() { return bInit; }
	//判斷初始化的參數是否足夠--@To find whether the parameters inited is enough
	bool is_param() { return bParam; }
	//得到內核函數所在矩陣--@Get the kernel in matrix form
	CvMat* get_Mat() { return pGaborfilter; }
	//得到歸一化圖像--@Get the kernel in image form
	IplImage* get_NormImage();
	//使用Gabor核函數對輸入圖像進行處理--@Do the filtering operation to input image with Gabor kernel
	IplImage* do_Filter(const IplImage *src);

protected:
	bool bParam;        //初始化的參數--bool , if the parameters inited are enough
	bool bKernel;        //bool
	bool bInit;            //bool
	float Lambda;      //餘弦函數波長--Wavelength of the cosine factor, which represent the central frequency of Gabor filter 
	float Theta;          //核函數的方向--Orientation of the Gabor function, the axis x'
	float Sigma;         // 標準差--The standard deviation of x, and for y , it is Sigma/Gamma;
	float Gamma;       // 空間方向率,指定Gabor函數支持的橢圓率--The spatial aspect ratio
	float Phi;              //Gabor的相位偏移--The phase offset of Gabor
	CvSize GaborWindow;    //Gabor窗口的寬度--The width of  window
	CvMat *pGaborfilter;     //The kernel of Gabor filter

private:
	void create_kernel();
};
#endif

Gabor.cpp源文件

#include "stdafx.h"
#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <cstdlib>
#include "Gabor.h"


Gabor::Gabor(float dLambda, float dTheta, float dRatio_S2L, float dGamma, float dPhi)
{
	Lambda = dLambda;
	Theta = dTheta;
	Sigma = dLambda*dRatio_S2L;
	Gamma = dGamma;
	Phi = dPhi;
	pGaborfilter = NULL;
	bParam = 1;
}

Gabor::~Gabor()
{
	cvReleaseMat(&pGaborfilter);
}
void Gabor::init()
{
	float dtmp;
	int itmp;
	if (is_param() == 0)
	{
		AfxMessageBox("The parameters are not enough!");
		return;
	}

	//沒明白這裏是什麼意思?
	dtmp = sqrt(48 * pow(Sigma, 2) + 1);//根號下( 48*Sigma^2 +1 )
	itmp = cvRound(dtmp);//對一個double型的數進行四捨五入,並返回一個整型數!
	if (itmp % 2 == 0)
		itmp++;
	GaborWindow.height = GaborWindow.width = itmp;//創建itmp*itmp的Gabor窗函數
	bInit = 1;

	create_kernel();
}

void Gabor::init(float dSigma, float dTheta, float dPhi)
{
	float dtmp;
	int itmp;

	Sigma = dSigma;
	Theta = dTheta;
	Phi = dPhi;
	Gamma = GAMMA;
	Lambda = Sigma / RATIO_S2L;
	bParam = 1;

	dtmp = sqrt(24 * pow(Sigma, 2));
	itmp = cvRound(dtmp);
	if (itmp % 2 == 0)
		itmp++;
	GaborWindow.height = GaborWindow.width = itmp;
	bInit = 1;

	create_kernel();
}

void Gabor::init(float dLambda, float dTheta, float dPhi, float dGamma)
{
	
	float dtmp;
	int itmp;

	Lambda = dLambda;
	Theta = dTheta;
	Phi = dPhi;
	Gamma = dGamma;
	Sigma = Lambda * RATIO_S2L;
	bParam = 1;

	dtmp = sqrt(24 * pow(Sigma, 2));
	itmp = cvRound(dtmp);
	if (itmp % 2 == 0)
		itmp++;
	GaborWindow.height = GaborWindow.width = itmp;
	bInit = 1;

	create_kernel();
}


void Gabor::create_kernel()
{
	float tmp1, tmp2, xtmp, ytmp, re;
	int i, j, x, y;

	if (is_init() == 0)
	{
		AfxMessageBox("The paremeters haven't been initialed!");
	}

	pGaborfilter = cvCreateMat(GaborWindow.height, GaborWindow.width, CV_32FC1);
	for (i = 0; i < GaborWindow.height; i++)
	{
		for (j = 0; j < GaborWindow.width; j++)
		{
			x = j - GaborWindow.width / 2;
			y = i - GaborWindow.height / 2;

			//源代碼此處的計算公式有誤
			//xtmp = (float)x*cos(Theta) - (float)y*sin(Theta);
			//ytmp = (float)x*sin(Theta) + (float)y*cos(Theta);
			xtmp = (float)x*cos(Theta) + (float)y*sin(Theta);
			ytmp = -(float)x*sin(Theta) + (float)y*cos(Theta);

			tmp1 = exp(-(pow(xtmp, 2) + pow(ytmp*Gamma, 2)) / (2 * pow(Sigma, 2)));
			tmp2 = cos(2 * PI*xtmp / Lambda + Phi);
			// int p=sizeof(float);
			re = tmp1*tmp2;
			cvSetReal2D((CvMat*)pGaborfilter, i, j, re);
		}
	}

	bKernel = 1;
}

IplImage* Gabor::get_NormImage()
{
	if (is_kernel() == 0)
	{
		AfxMessageBox("The filter hasn't bee created!");
		return NULL;
	}

	IplImage *pImg	= cvCreateImage(GaborWindow, IPL_DEPTH_32F, 1);
	IplImage *pImgU8 = cvCreateImage(GaborWindow, IPL_DEPTH_8U, 1);
	CvMat * pMat = cvCreateMat(GaborWindow.height, GaborWindow.width, CV_32FC1);

	cvCopy(pGaborfilter, pImg);

	//歸一化,數組的數值被平移或縮放到一個指定的範圍
	cvNormalize((IplImage*)pImg, (IplImage*)pImg, 0, 255, CV_MINMAX, NULL);
	//使用線性變換轉換輸入數組元素成8位無符號整型 
	cvConvertScaleAbs(pImg, pImgU8, 1, 0);

	return pImgU8;
	//return pImg;
}

IplImage * Gabor::do_Filter(const IplImage *src)
{
	//判斷核函數時候創建成功
	if (is_kernel() == false)
	{
		printf("The Gabor Kernel has not been created!");
		return NULL;
	}

	IplImage *pDestImage = cvCreateImage(cvSize(src->width, src->height), IPL_DEPTH_8U, 1);//創建一個同原圖像相同的目標圖像
	IplImage *tmpImg = cvCloneImage(src);//同cvCreateImage一樣,只是不需要開闢內存空間,直接將原圖像複製到目標圖像
	IplImage *tmpGrayImg = cvCreateImage(cvSize(src->width, src->height), IPL_DEPTH_8U, 1);
	
	//判斷是不是有色圖像
	if (tmpImg->nChannels != 1)
	{
		cvCvtColor(tmpImg, tmpGrayImg, CV_BGR2GRAY);//從有色圖轉到灰度圖
	}
	else
	{
		cvReleaseImage(&tmpGrayImg);
		tmpGrayImg = tmpImg;
	}

	CvMat *pGaborKernel = get_Mat();//其實就是CvMat *pGaborKernel=pGaborfilter;
	//Gabor核函數與原圖像進行卷積計算
	cvFilter2D(tmpGrayImg, pDestImage, pGaborKernel, cvPoint((GaborWindow.width - 1) / 2, (GaborWindow.height - 1) / 2));

	cvReleaseImage(&tmpImg);
	cvReleaseImage(&tmpGrayImg);

	return  pDestImage;
}

對話框Dlg.cpp,這裏只給出了實現按鈕的實現函數代碼,完整的工程代碼,看文章最後的附錄:

void CTyreXDlg::OnBnClickedBtndstimg()
{
	// TODO:  在此添加控件通知處理程序代碼
	UpdateData(TRUE);

	CSliderCtrl   *pSlidCtrlTheta = (CSliderCtrl*)GetDlgItem(IDC_SLIDER_THETA);
	int tmptheta = pSlidCtrlTheta->GetPos();//取得當前位置值  

	CString sValueTheta = "";
	sValueTheta.Format("%d", tmptheta);
	SetDlgItemText(IDC_StaticTheta, sValueTheta);

	//顯示lambda值
	CSliderCtrl   *pSlidCtrlLambda = (CSliderCtrl*)GetDlgItem(IDC_SLIDER_LAMBDA);
	int tmplambda = pSlidCtrlLambda->GetPos();//取得當前位置值  

	CString sValueLambda = "";
	sValueLambda.Format("%d", tmplambda);
	SetDlgItemText(IDC_StaticLambda, sValueLambda);

	//圖像是否加載成功
	if (SrcImg == NULL)
	{
		AfxMessageBox("沒有可處理圖像!!!");
		return;
	}

	/*
		Gabor濾波
	*/
	//構造函數
	Gabor GaborFilter(/*Lambda*/tmplambda *0.1, /*THETA*/PI*tmptheta / 180, RATIO_S2L, GAMMA, 0);
	//初始化
	GaborFilter.init();
	//獲取歸一化圖像
	IplImage* pGaborNormImg = GaborFilter.get_NormImage();
	//用Gabor核函數對輸入圖像處理,返回目標圖像
	IplImage* poutGaborimg;
	poutGaborimg = GaborFilter.do_Filter(SrcImg);

	/****************************************/

	IplImage *pGrayImage;
	// 轉爲灰度圖
	pGrayImage = cvCreateImage(cvGetSize(poutGaborimg), IPL_DEPTH_8U, 1);
	//判斷是不是彩色圖像
	if (poutGaborimg->nChannels != 1)
		cvCvtColor(poutGaborimg, pGrayImage, CV_BGR2GRAY);//從彩色圖轉到灰度圖
	else
	{
		cvReleaseImage(&pGrayImage);
		pGrayImage = poutGaborimg;
	}

	//二值化
	IplImage *pBinaryImage;
	pBinaryImage = cvCreateImage(cvGetSize(pGrayImage), IPL_DEPTH_8U, 1);
	cvThreshold(pGrayImage, pBinaryImage, 0/*二值化閾值*/, 255, CV_THRESH_BINARY);

	//顯示到picture控件
	ShowImgFunc(pBinaryImage, IDC_ShowDstImg);			//二值化圖像
	//ShowImgFunc(pGaborNormImg, IDC_ShowSrcImg);	//歸一化圖像
	//ShowImgFunc(pGrayImage, IDC_ShowSrcImg);			//二值化之前的灰度圖

	cvReleaseImage(&DstImg);
	cvReleaseImage(&DstGaborImg);
	cvReleaseImage(&DstBinaryImg);
}




附錄:

1. 上面代碼的完整的工程文件,可以到這裏下載,不需要積分。我運行成功,但是出現錯誤,或者有不合理的地方,希望各位看到能告訴我一聲,共同進步。

工程文件完整下載地址:http://download.csdn.net/detail/jorg_zhao/8949247


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