1. 程式人生 > >生活,哭泣著奔向死亡,又放不下理想,掙扎著歌唱

生活,哭泣著奔向死亡,又放不下理想,掙扎著歌唱

本文主要介紹了原始LBP以及各種改進LBP的原理,最後通過C++實現各種LBP得到LBP圖譜。

LBP(Local Binary Patterns,區域性二值模式)是一種能夠有效地度量和提取影象區域性紋理資訊的運算元,具有旋轉不變性和灰度不變性等顯著的優點。它是人臉識別中一種提取特徵的重要方法,具有對光照不敏感的特性,但是對姿態和表情的魯棒性不強。

一、原始LBP

1996年,Ojala大牛搞出了LBP特徵。原始的LBP演算法的基本思想是在3*3的視窗內,以視窗中心畫素為閾值,將相鄰的8個畫素的灰度值與其進行比較,若周圍畫素值大於中心畫素值,則該畫素點的位置被標記為1,否則為0。

這樣,3*3鄰域內的8個點經過比較可產生8位二進位制數,如圖1中00010011(通常轉換為十進位制數即LBP碼,共256種),即得到該視窗中心畫素點的LBP值,並用這個值來反映該區域的紋理資訊。如下圖所示:



LBP的操作可以被定義為:


  這裡,S是一個函式符號:



二、LBP擴充套件

基本的LBP運算元只侷限在3*3的鄰域內,對於較大影象大尺寸的結構不能很好的提取需要的紋理特徵,因此研究人員不斷對其提出了各種改進和優化。

1、圓形LBP運算元

基本的 LBP運算元的最大缺陷在於它只覆蓋了一個固定半徑範圍內的小區域,這顯然不能滿足不同尺寸和頻率紋理的需要。為了適應不同尺度的紋理特徵,並達到灰度和旋轉不變性的要求,Ojala等對 LBP 運算元進行了改進,將 3×3鄰域擴充套件到任意鄰域,並用圓形鄰域代替了正方形鄰域,改進後的 LBP 運算元允許在半徑為 R 的圓形鄰域內有任意多個畫素點。從而得到了諸如半徑為R的圓形區域內含有P個取樣點的LBP運算元。

比如下圖定義了一個5X5的領域:



上圖中的8個黑色的取樣點,每個取樣點的值可以通過下式計算


通過上式可以計算任意個取樣點的座標,但是計算得到的座標未必完全是整數,所以可以通過雙線性插值來得到該取樣點的畫素值:


2、LBP旋轉不變模式

由於編碼的起始點是一定的,每一種二值編碼模式經旋轉(迴圈位移)後會產生不同的編碼結果。為了形成旋轉不變的編碼模式,對同一編碼模式經旋轉後產生的編碼結果編碼為同一值,即這些旋轉結果中的最小值。

旋轉不變的編碼模式如下圖所示:



一共36個旋轉不變的LBP編碼模式,如下圖所示:


3、LBP等價模式

原始的LBP運算元,隨著鄰域內取樣點數的增加,二進位制模式的種類是急劇增加的。

對於半徑為R的圓形區域內含有P個取樣點的LBP運算元將會產P^2中模式,如5X5領域內20個取樣點,有2^20=104857種二進位制模式。過多的二值模式對於特徵的提取以及資訊的存取都是不利的。例如,將LBP運算元用於人臉識別時,常採用的LBP模式的統計直方圖來表達人臉資訊,而較多的模式種類將使得資料量過大,且直方圖過於稀疏。因此,需要對原始LBP模式進行降維,使得資料量減少的情況下能最好的代表影象的資訊。

旋轉LBP模式同樣存在缺陷,大量的實驗證明LBP模式的36種情況在一幅影象中分佈出現的頻率差異較大,得到的效果不是很好。因此人們提出了uniform LBP。
“等價模式”被定義為:當某個LBP所對應的迴圈二進位制數從0到1或者從1到0最多有兩次跳變時,該LBP所對應的二進位制就稱為一個等價模式。在實際影象中,計算出來的大部分值都在等價模式之中,可達百分之90%以上。uniformLBP模式的個數為P(P-1)+2,P為領域畫素點個數。對於8個取樣點,uniform形式有58種輸出, 其他的所有值為第59類,這樣直方圖從原來的256維降到了59維,並且可以減少高頻噪聲帶來的影響。

uniform形式的58種LBP模式如下圖所示:


三、LBP人臉識別

將LBP用於人臉識別時,一般不將LBP圖作為特徵用於識別,而是統計LBP特徵圖的直方圖作為特徵向量用於分類識別。

但是如果直接統計兩張完整圖片的LBP直方圖進行分類識別的話,一但人臉位置沒有對準,識別效果會很差。所以,LBP人臉識別的一般做法是將人臉影象進行分塊,對每塊子影象進行LBP直方圖統計,並將所以塊的直方圖首尾相連組成一個向量,這個向量即是人臉的特徵描述。通過比較兩張人臉影象的統計直方圖特徵向量的相似度,即可實現人臉識別。

人臉分塊示意圖:

統計直方圖特徵向量相似度的計算公式:


對LBP特徵向量進行提取的一般步驟:
  1. 首先將一張圖片分成若干個子塊圖片區域(cell)
  2. 對於每個cell中的一個畫素,將相鄰的8個畫素的灰度值與其進行比較,若周圍畫素值大於中心畫素值,則該畫素點的位置被標記為1,否則為0。這樣,3*3鄰域內的8個點經比較可產生8位二進位制數,即得到該視窗中心畫素點的LBP值
  3. 然後計算每個cell的直方圖,即每個數字(假定是十進位制數LBP值)出現的頻率;然後對該直方圖進行歸一化處理
  4. 最後將得到的每個cell的統計直方圖進行連線成為一個特徵向量,也就是整幅圖的LBP紋理特徵向量
  5. 通過一定的方法比較兩張圖片的LBP特徵向量的相似度來實現人臉識別

四、各版本LBP的C++實現

#include<opencv2/highgui/highgui.hpp>

using namespace cv;

//原始LBP
Mat LBP(Mat img)
{
   Mat result;
   result.create(img.rows - 2, img.cols -2 , img.type());
  
   result.setTo(0);

   for(int i = 1; i<img.rows - 1; i++)
   {
      for(int j = 1;j<img.cols -1; j++)
	  {
	     uchar center = img.at<uchar>(i, j);
		 uchar code = 0;
		 code |= (img.at<uchar>(i-1, j-1) >= center)<<7;
		 code |= (img.at<uchar>(i-1, j) >= center)<<6;
		 code |= (img.at<uchar>(i-1, j+1) >= center)<<5;
		 code |= (img.at<uchar>(i, j+1) >= center)<<4;
		 code |= (img.at<uchar>(i+1, j+1) >= center)<<3;
		 code |= (img.at<uchar>(i+1, j) >= center)<<2;
		 code |= (img.at<uchar>(i+1, j-1) >= center)<<1;
		 code |= (img.at<uchar>(i, j-1) >= center)<<0;
		 result.at<uchar>(i -1, j -1) = code;	   
	  }
   }
   return result;
}

//圓形LBP
Mat ELBP(Mat img, int radius, int neighbors)
{
   Mat result;
   result.create(img.rows-radius*2, img.cols-radius*2, img.type());
   result.setTo(0);

   for(int n=0; n<neighbors; n++)
	 {
        // sample points
        float x = static_cast<float>(radius * cos(2.0*CV_PI*n/static_cast<float>(neighbors)));
        float y = static_cast<float>(-radius * sin(2.0*CV_PI*n/static_cast<float>(neighbors)));
        // relative indices
        int fx = static_cast<int>(floor(x));
        int fy = static_cast<int>(floor(y));
        int cx = static_cast<int>(ceil(x));
        int cy = static_cast<int>(ceil(y));
        // fractional part
        float ty = y - fy;
        float tx = x - fx;
        // set interpolation weights
        float w1 = (1 - tx) * (1 - ty);
        float w2 =      tx  * (1 - ty);
        float w3 = (1 - tx) *      ty;
        float w4 =      tx  *      ty;
        // iterate through your data
        for(int i=radius; i < img.rows-radius;i++) 
		{
            for(int j=radius;j < img.cols-radius;j++) 
			{
                // calculate interpolated value
                float t = static_cast<float>(w1*img.at<uchar>(i+fy,j+fx) + w2*img.at<uchar>(i+fy,j+cx) + w3*img.at<uchar>(i+cy,j+fx) + w4*img.at<uchar>(i+cy,j+cx));
                // floating point precision, so check some machine-dependent epsilon
                result.at<uchar>(i-radius,j-radius) += ((t > img.at<uchar>(i,j)) || (std::abs(t-img.at<uchar>(i,j)) < std::numeric_limits<float>::epsilon())) << n;
            }
	    }
   }
   return result;
}

//八位二進位制跳變次數
int getHopCount(uchar i)
{
	uchar a[8] ={0};
	int cnt =0;
	int k = 7;

	while(k)
	{
	   a[k] = i&1;
	   i = i>>1;
	   --k;
	}

	for(int k =0; k<7;k++)
	{
	   if(a[k] !=a[k+1])
		   ++cnt;
	}

	if(a[0] != a[7])
		++cnt;

	return cnt;
}

//旋轉不變LBP
Mat RILBP(Mat img)
{
   uchar RITable[256];
   int temp;
   int val;
   Mat result;
   result.create(img.rows - 2, img.cols -2 , img.type());
   result.setTo(0);

   for(int i = 0; i<256; i++)
   {
	   val =i;
      for(int j =0; j<7; j++)
	  {
	     temp = i>>1;
		 if(val>temp)
		 {
		   val = temp;
		 }
	  }
    RITable[i] = val;
   }

   for(int i = 1; i<img.rows - 1; i++)
   {
      for(int j = 1;j<img.cols -1; j++)
	  {
	     uchar center = img.at<uchar>(i, j);
		 uchar code = 0;
		 code |= (img.at<uchar>(i-1, j-1) >= center)<<7;
		 code |= (img.at<uchar>(i-1, j) >= center)<<6;
		 code |= (img.at<uchar>(i-1, j+1) >= center)<<5;
		 code |= (img.at<uchar>(i, j+1) >= center)<<4;
		 code |= (img.at<uchar>(i+1, j+1) >= center)<<3;
		 code |= (img.at<uchar>(i+1, j) >= center)<<2;
		 code |= (img.at<uchar>(i+1, j-1) >= center)<<1;
		 code |= (img.at<uchar>(i, j-1) >= center)<<0;
		 result.at<uchar>(i -1, j -1) = RITable[code];	   
	  }
   }
   return result;
}

//UniformLBP
Mat UniformLBP(Mat img)
{
	uchar UTable[256];
	memset(UTable, 0, 256*sizeof(uchar));
	uchar temp =1;
   for(int i =0; i<256; i++)
   {
	   if(getHopCount(i)<=2)
	   {
	      UTable[i] = temp;
		  ++temp;
	   }
   }
	 Mat result;
   result.create(img.rows - 2, img.cols -2 , img.type());
  
   result.setTo(0);

   for(int i = 1; i<img.rows - 1; i++)
   {
      for(int j = 1;j<img.cols -1; j++)
	  {
	     uchar center = img.at<uchar>(i, j);
		 uchar code = 0;
		 code |= (img.at<uchar>(i-1, j-1) >= center)<<7;
		 code |= (img.at<uchar>(i-1, j) >= center)<<6;
		 code |= (img.at<uchar>(i-1, j+1) >= center)<<5;
		 code |= (img.at<uchar>(i, j+1) >= center)<<4;
		 code |= (img.at<uchar>(i+1, j+1) >= center)<<3;
		 code |= (img.at<uchar>(i+1, j) >= center)<<2;
		 code |= (img.at<uchar>(i+1, j-1) >= center)<<1;
		 code |= (img.at<uchar>(i, j-1) >= center)<<0;
		 result.at<uchar>(i -1, j -1) = UTable[code];	   
	  }
   }
   return result;
}

int main()
{
   Mat src = imread("../data/lena.bmp", 0);

   Mat dst = LBP(src);
   Mat edst = ELBP(src, 1, 8);
   Mat pic = RILBP(src);
   Mat img = UniformLBP(src);

   imshow("原始圖片", src);
   imshow("原始LBP", dst);
   imshow("圓形LBP", edst);
   imshow("旋轉不變LBP", pic);
   imshow("UniformLBP", img);

   waitKey(0);

   return 0;
}


程式碼執行結果,如下圖所示: