1. 程式人生 > >人臉識別特徵提取(LBP)及其opencv實現

人臉識別特徵提取(LBP)及其opencv實現

        LBP是一種簡單,有效的紋理分類的特徵提取演算法。LBP運算元是由Ojala等人於1996年提出的,主要的論文是"Multiresolution gray-scale and rotation invariant texture classification with local binary patterns", pami, vol 24, no.7, July 2002。LBP就是"local binary pattern"的縮寫。區域性二值模式是一個簡單但非常有效的紋理運算子。它將各個畫素與其附近的畫素進行比較,並把結果儲存為二進位制數。由於其辨別力強大和計算簡單,區域性二值模式紋理運算元已經在不同的場景下得到應用。LBP最重要的屬性是對諸如光照變化等造成的灰度變化的魯棒性。它的另外一個重要特性是它的計算簡單,這使得它可以對影象進行實時分析。

        區域性二值模式特徵向量可以通過如下方式計算:
        1.將檢測視窗切分為區塊(cells,例如,每個區塊16x16畫素)。
        2.對區塊中的每個畫素,與它的八個鄰域畫素進行比較(左上、左中、左下、右上等)。可以按照順時針或者逆時針的順序進行比較。
        3.對於中心畫素大於某個鄰域的,設定為1;否則,設定為0。這就獲得了一個8位的二進位制數(通常情況下會轉換為十進位制數字),作為該位置的特徵。
        4.對每一個區塊計算直方圖。
        5.此時,可以選擇將直方圖歸一化;
        6.串聯所有區塊的直方圖,這就得到了當前檢測視窗的特徵向量。

        對影象中的每個畫素,通過計算以其為中心的3*3鄰域內各畫素和中心畫素的大小關係,把畫素的灰度值轉化為一個八位二進位制序列。具體計算過程如下圖所示,對於影象的任意一點Ic,其LBP特徵計算為,以Ic為中心,取與Ic相鄰的8各點,按照順時針的方向記為 I0,I1,...,I7;以Ic點的畫素值為閾值,如果 Ii 點的畫素值小於Ic,則 Ii 被二值化為0,否則為1;將二值化得到的0、1序列看成一個8位二進位制數,將該二進位制數轉化為十進位制就可得到Ic點處的LBP運算元的值。
  基本的LBP運算元只侷限在3*3的鄰域內,對於較大影象大尺度的結構不能很好的提取需要的紋理特徵,因此研究者們對LBP運算元進行了擴充套件。新的LBP運算元LBP(P,R) 可以計算不同半徑鄰域大小和不同畫素點數的特徵值,其中P表示周圍畫素點個數,R表示鄰域半徑,同時把原來的方形鄰域擴充套件到了圓形,下圖給出了四種擴充套件後的LBP例子,其中,R可以是小數,對於沒有落到整數位置的點,根據軌道內離其最近的兩個整數位置畫素灰度值,利用雙線性差值的方法可以計算它的灰度值。


        LBP(P,R)有2^p個值,也就是說影象共有2^p種二進位制模型,然而實際研究中發現,所有模式表達資訊的重要程度是不同的,統計研究表明,一幅影象中少數模式特別集中,達到總模式的百分之九十左右的比例,Ojala等人定義這種模式為Uniform模式,如果一個二進位制序列看成一個圈時,0-1以及1-0的變化出現的次數總和不超過兩次,那麼這個序列就是Uniform模式 ,比如,00000000、00011110、00100001、11111111,在使用LBP表達影象紋理時,通常只關心Uniform模式,而將所有其他的模式歸到同一類中。

        LBP運算元利用了周圍點與該點的關係對該點進行量化。量化後可以更有效地消除光照對影象的影響。只要光照的變化不足以改變兩個點畫素值之間的大小關係,那麼LBP運算元的值不會發生變化,所以一定程度上,基於LBP的識別演算法解決了光照變化的問題,但是當影象光照變化不均勻時,各畫素間的大小關係被破壞,對應的LBP模式也就發生了變化。

void elbp(Mat& src, Mat &dst, int radius, int neighbors)
{

	for (int n = 0; n < neighbors; n++)
	{
		// 取樣點的計算
		float x = static_cast<float>(-radius * sin(2.0*CV_PI*n / static_cast<float>(neighbors)));
		float y = static_cast<float>(radius * cos(2.0*CV_PI*n / static_cast<float>(neighbors)));
		// 上取整和下取整的值
		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));
		// 小數部分
		float ty = y - fy;
		float tx = x - fx;
		// 設定插值權重
		float w1 = (1 - tx) * (1 - ty);
		float w2 = tx  * (1 - ty);
		float w3 = (1 - tx) *      ty;
		float w4 = tx  *      ty;
		// 迴圈處理影象資料
		for (int i = radius; i < src.rows - radius; i++)
		{
			for (int j = radius; j < src.cols - radius; j++)
			{
				// 計算插值
				float t = static_cast<float>(w1*src.at<uchar>(i + fy, j + fx) + w2*src.at<uchar>(i + fy, j + cx) + w3*src.at<uchar>(i + cy, j + fx) + w4*src.at<uchar>(i + cy, j + cx));
				// 進行編碼
				dst.at<uchar>(i - radius, j - radius) += ((t > src.at<uchar>(i, j)) || (std::abs(t - src.at<uchar>(i, j)) < std::numeric_limits<float>::epsilon())) << n;
			}
		}
	}
}

void elbp1(Mat& src, Mat &dst)
{

	// 迴圈處理影象資料
	for (int i = 1; i < src.rows - 1; i++)
	{
		for (int j = 1; j < src.cols - 1; j++)
		{
			uchar tt = 0;
			int tt1 = 0;
			uchar u = src.at<uchar>(i, j);
			if (src.at<uchar>(i - 1, j - 1)>u) { tt += 1 << tt1; }
			tt1++;
			if (src.at<uchar>(i - 1, j)>u) { tt += 1 << tt1; }
			tt1++;
			if (src.at<uchar>(i - 1, j + 1) > u) { tt += 1 << tt1; }
			tt1++;
			if (src.at<uchar>(i, j + 1) > u) { tt += 1 << tt1; }
			tt1++;
			if (src.at<uchar>(i + 1, j + 1) > u) { tt += 1 << tt1; }
			tt1++;
			if (src.at<uchar>(i + 1, j) > u) { tt += 1 << tt1; }
			tt1++;
			if (src.at<uchar>(i + 1, j - 1) > u) { tt += 1 << tt1; }
			tt1++;
			if (src.at<uchar>(i - 1, j) > u) { tt += 1 << tt1; }
			tt1++;

			dst.at<uchar>(i - 1, j - 1) = tt;
		}
	}
}