圖像處理基礎算法-LBP特徵描述算子

LBP特徵描述算子-人臉檢測

2.1 簡介

  LBP指局部二值模式(Local Binary Pattern),是一種用來描述圖像局部特徵的算子,具有灰度不變性和旋轉不變性等顯著優點。LBP常應用於人臉識別和目標檢測中,在OpenCV中有使用LBP特徵進行人臉識別的接口,也有用LBP特徵訓練目標檢測分類器的方法,OpenCV實現了LBP特徵的計算,但沒有提供一個單獨的計算LBP特徵的接口。也就是說OpenCV中使用了LBP算法,但是沒有提供函數接口。

2.2 學習目標

  • 瞭解人臉檢測相關流程
  • 理解LBP算法相關原理
  • 掌握基於OpenCV的LBP算法實現

2.3 算法理論介紹

2.3.1 LBP原理介紹

  LBP特徵用圖像的局部領域的聯合分佈TT 來描述圖像的紋理特徵,如果假設局部鄰域中像素個數爲P(P>1)P(P >1),那麼紋理特徵的聯合分佈TT 可以表述成:

T=t(gc,g0,,gp1)p=0,,P1(2-1)T=t\left(g_{c}, g_{0}, \ldots, g_{p-1}\right) \quad p=0, \ldots, P-1\tag{2-1}

  其中, gcg_c 表示相應局部鄰域的中心像素的灰度值, gpg_p 表示以中心像素圓心,以R爲半徑的圓上的像素的灰度值。

  假設中心像素和局部鄰域像素相互獨立,那麼這裏可以將上面定義式寫成如下形式:

T=t(gc,g0gc,,gp1gc)p=0,,P1t(gc)t(g0gc,,gp1gc)(2-2)\begin{aligned} T &=t\left(g_{c}, g_{0}-g_{c}, \ldots, g_{p-1}-g_{c}\right) \quad p=0, \ldots, P-1 \\ & \approx t\left(g_{c}\right) t\left(g_{0}-g_{c}, \ldots, g_{p-1}-g_{c}\right) \end{aligned}\tag{2-2}

  其中t(gc)t(g_c)決定了局部區域的整體亮度,對於紋理特徵,可以忽略這一項,最終得到:

Tt(g0gc,,gp1gc)p=0,,P1(2-3)T \approx t\left(g_{0}-g_{c}, \ldots, g_{p-1}-g_{c}\right) \quad p=0, \ldots, P-1\tag{2-3}

  上式說明,將紋理特徵定義爲鄰域像素和中心像素的差的聯合分佈函數,因爲gpgcg_p − g_c是基本不受亮度均值影響的,所以從上式可以看出,此時統計量T 是一個跟亮度均值,即灰度級無關的值。

  最後定義特徵函數如下:
Tt(s(g0gc),,s(gp1gc))p=0,,P1s(x)={1,x00,x<0(2-4)\begin{array}{l} T \approx t\left(s\left(g_{0}-g_{c}\right), \ldots, s\left(g_{p-1}-g_{c}\right)\right) p=0, \ldots, P-1 \\ s(x)=\left\{\begin{array}{l} 1, x \geq 0 \\ 0, x<0 \end{array}\right. \end{array}\tag{2-4}

  定義灰度級不變LBP爲:

LBPP,R=p=0P1s(gpgc)2p(2-5)L B P_{P, R}=\sum_{p=0}^{P-1} s\left(g_{p}-g_{c}\right) 2^{p}\tag{2-5}

  即二進制編碼公式。

通俗解釋:

  原始的LBP算子定義在像素333*3的鄰域內,以鄰域中心像素爲閾值,相鄰的8個像素的灰度值與鄰域中心的像素值進行比較,若周圍像素大於中心像素值,則該像素點的位置被標記爲1,否則爲0。這樣,333*3鄰域內的8個點經過比較可產生8爲二進制數,將這8位二進制數依次排列形成一個二進制數字,這個二進制數字就是中心像素的LBP值,LBP值共有28種可能,因此LBP值有256種可能。中心像素的LBP值反映了該像素周圍區域的紋理信息。

注意:計算LBP特徵的圖像必須是灰度圖,如果是彩色圖,需要先轉換成灰度圖


圖 2.3.1 LBP計算示意圖

2.3.2 圓形LBP算子

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


圖 2.3.2 圓形LBP示意圖

  對於給定中心點(xc,yc)(x_c,y_c),其鄰域像素位置爲(xp,yp)(x_p,y_p)pPp∈P,其採樣點(xp,yp)(x_p,y_p)用如下公式計算:

xp=xc+Rcos(2πpP)yp=yc+Rsin(2πpP)(2-6)\begin{array}{l} x_{p}=x_{c}+\operatorname{Rcos}\left(\frac{2 \pi p}{P}\right) \\ y_{p}=y_{c}+\operatorname{Rsin}\left(\frac{2 \pi p}{P}\right) \end{array}\tag{2-6}

  R是採樣半徑,p是第p個採樣點,P是採樣數目。如果近鄰點不在整數位置上,就需要進行插值運算,可以參考這篇博客 OpenCV框架下的插值算法

3.3.3 LBP旋轉不變性及等價模式

  LPB特徵是灰度不變,但不是旋轉不變的,同一幅圖像,進行旋轉以後,其特徵將會有很大的差別,影響匹配的精度。Ojala在LBP算法上,進行改進,實現了具有旋轉不變性的LPB的特徵。

  實現方法:不斷旋轉圓形鄰域得到一系列初始定義的LPB值,取最小值作爲該鄰域的值。

LBPPRri=min(ROR(LBPP,Rri,i)i=0,1,,P1)(2-7)L B P_{P R}^{ri}=\min \left(R O R\left(L B P_{P, R}^{ri}, i\right) | i=0,1, \ldots, P-1\right)\tag{2-7}

  其中LBPPRriL B P_{P R}^{ri}表示具有旋轉不變性的LBP特徵。ROR(x,i)ROR(x, i)爲旋轉函數,表示將xx右循環ii位。


圖 2.3.3 求取旋轉不變的LPB特徵示意圖

等價模式:

  一個LBP算子可以產生不同的二進制模式,對於LBPpRLBP^{R}_p將會產生2p2^p種模式。比如777*7鄰域內有2362^{36}種模式。如此多的二值模式對於信息的提取和識別都是不利的。

  Ojala等認爲,在實際圖像中,絕大多數LPB模式最多隻包含兩次從1到0或從0到1的跳變。

等價模式:當某個局部二進制模式所對應的循環二進制數從0到1或從1到0最多有兩次跳變時,該局部二進制模式所對應的二進制就稱爲一個等價模式。

  比如:00000000,11111111,11110010,10111111都是等價模式。

  檢查某種模式是否是等價模式:
U(Gp)=s(gp1gc)s(g0gc)+p=1P1s(gpgc)s(gP1gc)(2-8)U\left(G_{p}\right)=\left|s\left(g_{p_{-1}}-g_{c}\right)-s\left(g_{0}-g_{c}\right)\right|+\sum_{p=1}^{P_{-1}}\left|s\left(g_{p}-g_{c}\right)-s\left(g_{P-1}-g_{c}\right)\right|\tag{2-8}

  將其和其移動一位後的二進制模式按位相減。並絕對值求和。若U(Gp)\left(G_{p}\right) 小於等於2,則爲等價模式。

  混合模式:除了等價模式之外的稱爲混合模式。

  改進後的LPB模式數由2 p^{p}(p爲鄰域集內的採集點數 ) 降維爲p(p1)+2p*(p-1)+2 。維數減少,可以降低高頻噪聲的影響。Ojala認爲等價模式佔總模式中的絕大數。圖2.4 ( a ), ( b ), ( c )等價模式分別佔88%,93%和76%。


圖 2.3.4

  可以通過低通濾波的方法來增強等價模式所佔的比例。圖2.4( c )經過高斯濾波後,其等價模式所佔比可以增加到90%。

2.3.4 人臉檢測流程

  人臉檢測過程採用多尺度滑窗搜索方式,每個尺度通過一定步長截取大小爲20x20的窗口,然後將窗口放到分類器中進行是不是人臉的判決,如果是人臉則該窗口通過所有分類器;反之,會在某一級分類器被排除。


圖 2.3.5 人臉檢測流程圖

2.4 基於OpenCV的實現

python

  • 使用OpenCV的LBP於預訓練模型
  • 將haarcascade_frontalface_default.xml下載至本地以方便調用,下載鏈接:https://github.com/opencv/opencv/blob/master/data/lbpcascades/lbpcascade_frontalface_improved.xml
#coding:utf-8
import cv2 as cv

# 讀取原始圖像
img= cv.imread('*.png')
#face_detect = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

face_detect = cv.CascadeClassifier("lbpcascade_frontalface_improved.xml")
# 檢測人臉
# 灰度處理
gray = cv.cvtColor(img, code=cv.COLOR_BGR2GRAY)

# 檢查人臉 按照1.1倍放到 周圍最小像素爲5
face_zone = face_detect.detectMultiScale(gray, scaleFactor = 2, minNeighbors = 2) # maxSize = (55,55)
print ('識別人臉的信息:\n',face_zone)

# 繪製矩形和圓形檢測人臉
for x, y, w, h in face_zone:
    # 繪製矩形人臉區域
    cv.rectangle(img, pt1 = (x, y), pt2 = (x+w, y+h), color = [0,0,255], thickness=2)
    # 繪製圓形人臉區域 radius表示半徑
    cv.circle(img, center = (x + w//2, y + h//2), radius = w//2, color = [0,255,0], thickness = 2)

# 設置圖片可以手動調節大小
cv.namedWindow("Easmount-CSDN", 0)

# 顯示圖片
cv.imshow("Easmount-CSDN", img)

# 等待顯示 設置任意鍵退出程序
cv.waitKey(0)
cv.destroyAllWindows()

原圖:

在這裏插入圖片描述

檢測結果:

在這裏插入圖片描述

c++

uchar GetMinBinary(uchar *binary)
{
	// 計算8個二進制
	uchar LBPValue[8] = { 0 };
	for (int i = 0; i <= 7; ++i)
	{
		LBPValue[0] += binary[i] << (7 - i);
		LBPValue[1] += binary[(i + 7) % 8] << (7 - i);
		LBPValue[2] += binary[(i + 6) % 8] << (7 - i);
		LBPValue[3] += binary[(i + 5) % 8] << (7 - i);
		LBPValue[4] += binary[(i + 4) % 8] << (7 - i);
		LBPValue[5] += binary[(i + 3) % 8] << (7 - i);
		LBPValue[6] += binary[(i + 2) % 8] << (7 - i);
		LBPValue[7] += binary[(i + 1) % 8] << (7 - i);
	}
	// 選擇最小的
	uchar minValue = LBPValue[0];
	for (int i = 1; i <= 7; ++i)
	{
		if (LBPValue[i] < minValue)
		{
			minValue = LBPValue[i];
		}
	}

	return minValue;
}

//計算9種等價模式
int ComputeValue9(int value58)
{
	int value9 = 0;
	switch (value58)
	{
	case 1:
		value9 = 1;
		break;
	case 2:
		value9 = 2;
		break;
	case 4:
		value9 = 3;
		break;
	case 7:
		value9 = 4;
		break;
	case 11:
		value9 = 5;
		break;
	case 16:
		value9 = 6;
		break;
	case 22:
		value9 = 7;
		break;
	case 29:
		value9 = 8;
		break;
	case 58:
		value9 = 9;
		break;
	}
	return value9;
}

//灰度不變常規LBP(256)
void NormalLBPImage(const Mat &srcImage, Mat &LBPImage)
{
	CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
	LBPImage.create(srcImage.size(), srcImage.type());


	Mat extendedImage;
	copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_DEFAULT);

	// 計算LBP特徵圖
	int heightOfExtendedImage = extendedImage.rows;
	int widthOfExtendedImage = extendedImage.cols;
	int widthOfLBP = LBPImage.cols;
	uchar *rowOfExtendedImage = extendedImage.data + widthOfExtendedImage + 1;
	uchar *rowOfLBPImage = LBPImage.data;
	for (int y = 1; y <= heightOfExtendedImage - 2; ++y, rowOfExtendedImage += widthOfExtendedImage, rowOfLBPImage += widthOfLBP)
	{
		// 列
		uchar *colOfExtendedImage = rowOfExtendedImage;
		uchar *colOfLBPImage = rowOfLBPImage;
		for (int x = 1; x <= widthOfExtendedImage - 2; ++x, ++colOfExtendedImage, ++colOfLBPImage)
		{
			// 計算LBP值
			int LBPValue = 0;
			if (colOfExtendedImage[0 - widthOfExtendedImage - 1] >= colOfExtendedImage[0])
				LBPValue += 128;
			if (colOfExtendedImage[0 - widthOfExtendedImage] >= colOfExtendedImage[0])
				LBPValue += 64;
			if (colOfExtendedImage[0 - widthOfExtendedImage + 1] >= colOfExtendedImage[0])
				LBPValue += 32;
			if (colOfExtendedImage[0 + 1] >= colOfExtendedImage[0])
				LBPValue += 16;
			if (colOfExtendedImage[0 + widthOfExtendedImage + 1] >= colOfExtendedImage[0])
				LBPValue += 8;
			if (colOfExtendedImage[0 + widthOfExtendedImage] >= colOfExtendedImage[0])
				LBPValue += 4;
			if (colOfExtendedImage[0 + widthOfExtendedImage - 1] >= colOfExtendedImage[0])
				LBPValue += 2;
			if (colOfExtendedImage[0 - 1] >= colOfExtendedImage[0])
				LBPValue += 1;
			colOfLBPImage[0] = LBPValue;
		}
	}
}
// 等價灰度不變LBP(58)
void UniformNormalLBPImage(const Mat &srcImage, Mat &LBPImage)// 計算等價模式LBP特徵圖
{
	// 參數檢查,內存分配
	CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
	LBPImage.create(srcImage.size(), srcImage.type());

	// 計算LBP圖
	// 擴充原圖像邊界,便於邊界處理
	Mat extendedImage;
	copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_DEFAULT);

	// 構建LBP 等價模式查找表
	//int table[256];
	//BuildUniformPatternTable(table);

	// LUT(256種每一種模式對應的等價模式)
	static const int table[256] = { 1, 2, 3, 4, 5, 0, 6, 7, 8, 0, 0, 0, 9, 0, 10, 11, 12, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 14, 0, 15, 16, 17, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 20, 0, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25,
		0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 27, 0, 28, 29, 30, 31, 0, 32, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0
		, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 36, 37, 38, 0, 39, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42
		, 43, 44, 0, 45, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 47, 48, 49, 0, 50, 0, 0, 0, 51, 52, 53, 0, 54, 55, 56, 57, 58 };

	// 計算LBP
	int heightOfExtendedImage = extendedImage.rows;
	int widthOfExtendedImage = extendedImage.cols;
	int widthOfLBP = LBPImage.cols;
	uchar *rowOfExtendedImage = extendedImage.data + widthOfExtendedImage + 1;
	uchar *rowOfLBPImage = LBPImage.data;
	for (int y = 1; y <= heightOfExtendedImage - 2; ++y, rowOfExtendedImage += widthOfExtendedImage, rowOfLBPImage += widthOfLBP)
	{
		// 列
		uchar *colOfExtendedImage = rowOfExtendedImage;
		uchar *colOfLBPImage = rowOfLBPImage;
		for (int x = 1; x <= widthOfExtendedImage - 2; ++x, ++colOfExtendedImage, ++colOfLBPImage)
		{
			// 計算LBP值
			int LBPValue = 0;
			if (colOfExtendedImage[0 - widthOfExtendedImage - 1] >= colOfExtendedImage[0])
				LBPValue += 128;
			if (colOfExtendedImage[0 - widthOfExtendedImage] >= colOfExtendedImage[0])
				LBPValue += 64;
			if (colOfExtendedImage[0 - widthOfExtendedImage + 1] >= colOfExtendedImage[0])
				LBPValue += 32;
			if (colOfExtendedImage[0 + 1] >= colOfExtendedImage[0])
				LBPValue += 16;
			if (colOfExtendedImage[0 + widthOfExtendedImage + 1] >= colOfExtendedImage[0])
				LBPValue += 8;
			if (colOfExtendedImage[0 + widthOfExtendedImage] >= colOfExtendedImage[0])
				LBPValue += 4;
			if (colOfExtendedImage[0 + widthOfExtendedImage - 1] >= colOfExtendedImage[0])
				LBPValue += 2;
			if (colOfExtendedImage[0 - 1] >= colOfExtendedImage[0])
				LBPValue += 1;

			colOfLBPImage[0] = table[LBPValue];
		}
	}
}

// 等價旋轉不變LBP(9)
void UniformRotInvLBPImage(const Mat &srcImage, Mat &LBPImage)
{
	// 參數檢查,內存分配
	CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
	LBPImage.create(srcImage.size(), srcImage.type());

	// 擴充圖像,處理邊界情況
	Mat extendedImage;
	copyMakeBorder(srcImage, extendedImage, 1, 1, 1, 1, BORDER_DEFAULT);

	// 構建LBP 等價模式查找表
	//int table[256];
	//BuildUniformPatternTable(table);

	// 通過查找表
	static const int table[256] = { 1, 2, 3, 4, 5, 0, 6, 7, 8, 0, 0, 0, 9, 0, 10, 11, 12, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 14, 0, 15, 16, 17, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 20, 0, 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25,
		0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 27, 0, 28, 29, 30, 31, 0, 32, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0
		, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 36, 37, 38, 0, 39, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42
		, 43, 44, 0, 45, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 47, 48, 49, 0, 50, 0, 0, 0, 51, 52, 53, 0, 54, 55, 56, 57, 58 };

	uchar binary[8] = { 0 };// 記錄每個像素的LBP值
	int heigthOfExtendedImage = extendedImage.rows;
	int widthOfExtendedImage = extendedImage.cols;
	int widthOfLBPImage = LBPImage.cols;

	uchar *rowOfExtendedImage = extendedImage.data + widthOfExtendedImage + 1;
	uchar *rowOfLBPImage = LBPImage.data;
	for (int y = 1; y <= heigthOfExtendedImage - 2; ++y, rowOfExtendedImage += widthOfExtendedImage, rowOfLBPImage += widthOfLBPImage)
	{
		// 列
		uchar *colOfExtendedImage = rowOfExtendedImage;
		uchar *colOfLBPImage = rowOfLBPImage;
		for (int x = 1; x <= widthOfExtendedImage - 2; ++x, ++colOfExtendedImage, ++colOfLBPImage)
		{
			// 計算旋轉不變LBP(最小的二進制模式)
			binary[0] = colOfExtendedImage[0 - widthOfExtendedImage - 1] >= colOfExtendedImage[0] ? 1 : 0;
			binary[1] = colOfExtendedImage[0 - widthOfExtendedImage] >= colOfExtendedImage[0] ? 1 : 0;
			binary[2] = colOfExtendedImage[0 - widthOfExtendedImage + 1] >= colOfExtendedImage[0] ? 1 : 0;
			binary[3] = colOfExtendedImage[0 + 1] >= colOfExtendedImage[0] ? 1 : 0;
			binary[4] = colOfExtendedImage[0 + widthOfExtendedImage + 1] >= colOfExtendedImage[0] ? 1 : 0;
			binary[5] = colOfExtendedImage[0 + widthOfExtendedImage] >= colOfExtendedImage[0] ? 1 : 0;
			binary[6] = colOfExtendedImage[0 + widthOfExtendedImage - 1] >= colOfExtendedImage[0] ? 1 : 0;
			binary[7] = colOfExtendedImage[0 - 1] >= colOfExtendedImage[0] ? 1 : 0;
			int minValue = GetMinBinary(binary);
			// 計算58種等價模式LBP
			int value58 = table[minValue];
			// 計算9種等價模式
			colOfLBPImage[0] = ComputeValue9(value58);
		}
	}
}
//灰度不變常規LBP(256)特徵
void NormalLBPFeature(const Mat &srcImage, Size cellSize, Mat &featureVector)
{
	// 參數檢查,內存分配
	CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);
	Mat LBPImage;
	NormalLBPImage(srcImage, LBPImage);
	// 計算cell個數
	int widthOfCell = cellSize.width;
	int heightOfCell = cellSize.height;
	int numberOfCell_X = srcImage.cols / widthOfCell;// X方向cell的個數
	int numberOfCell_Y = srcImage.rows / heightOfCell;

	// 特徵向量的個數
	int numberOfDimension = 256 * numberOfCell_X*numberOfCell_Y;
	featureVector.create(1, numberOfDimension, CV_32FC1);
	featureVector.setTo(Scalar(0));

	// 計算LBP特徵向量
	int stepOfCell = srcImage.cols;
	int pixelCount = cellSize.width*cellSize.height;
	float *dataOfFeatureVector = (float *)featureVector.data;

	// cell的特徵向量在最終特徵向量中的起始位置
	int index = -256;
	for (int y = 0; y <= numberOfCell_Y - 1; ++y)
	{
		for (int x = 0; x <= numberOfCell_X - 1; ++x)
		{
			index += 256;
			// 計算每個cell的LBP直方圖
			Mat cell = LBPImage(Rect(x * widthOfCell, y * heightOfCell, widthOfCell, heightOfCell));
			uchar *rowOfCell = cell.data;
			for (int y_Cell = 0; y_Cell <= cell.rows - 1; ++y_Cell, rowOfCell += stepOfCell)
			{
				uchar *colOfCell = rowOfCell;
				for (int x_Cell = 0; x_Cell <= cell.cols - 1; ++x_Cell, ++colOfCell)
				{
					++dataOfFeatureVector[index + colOfCell[0]];
				}
			}

			// 一定要歸一化!否則分類器計算誤差很大
			for (int i = 0; i <= 255; ++i)
				dataOfFeatureVector[index + i] /= pixelCount;
		}
	}
}
// 等價灰度不變LBP(58)特徵
void UniformNormalLBPFeature(const Mat &srcImage, Size cellSize, Mat &featureVector)
{
	// 參數檢查,內存分配
	CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);

	Mat LBPImage;
	UniformNormalLBPImage(srcImage, LBPImage);

	// 計算cell個數
	int widthOfCell = cellSize.width;
	int heightOfCell = cellSize.height;
	int numberOfCell_X = srcImage.cols / widthOfCell;// X方向cell的個數
	int numberOfCell_Y = srcImage.rows / heightOfCell;

	// 特徵向量的個數
	int numberOfDimension = 58 * numberOfCell_X*numberOfCell_Y;
	featureVector.create(1, numberOfDimension, CV_32FC1);
	featureVector.setTo(Scalar(0));

	// 計算LBP特徵向量
	int stepOfCell = srcImage.cols;
	int index = -58;// cell的特徵向量在最終特徵向量中的起始位置
	float *dataOfFeatureVector = (float *)featureVector.data;
	for (int y = 0; y <= numberOfCell_Y - 1; ++y)
	{
		for (int x = 0; x <= numberOfCell_X - 1; ++x)
		{
			index += 58;

			// 計算每個cell的LBP直方圖
			Mat cell = LBPImage(Rect(x * widthOfCell, y * heightOfCell, widthOfCell, heightOfCell));
			uchar *rowOfCell = cell.data;
			int sum = 0; // 每個cell的等價模式總數
			for (int y_Cell = 0; y_Cell <= cell.rows - 1; ++y_Cell, rowOfCell += stepOfCell)
			{
				uchar *colOfCell = rowOfCell;
				for (int x_Cell = 0; x_Cell <= cell.cols - 1; ++x_Cell, ++colOfCell)
				{
					if (colOfCell[0] != 0)
					{
						// 在直方圖中轉化爲0~57,所以是colOfCell[0] - 1
						++dataOfFeatureVector[index + colOfCell[0] - 1];
						++sum;
					}
				}
			}
			// 一定要歸一化!否則分類器計算誤差很大
			for (int i = 0; i <= 57; ++i)
				dataOfFeatureVector[index + i] /= sum;
		}
	}
}
// 等價旋轉不變LBP(9)特徵
void UniformRotInvLBPFeature(const Mat &srcImage, Size cellSize, Mat &featureVector)
{
	// 參數檢查,內存分配
	CV_Assert(srcImage.depth() == CV_8U && srcImage.channels() == 1);

	Mat LBPImage;
	UniformRotInvLBPImage(srcImage, LBPImage);

	// 計算cell個數
	int widthOfCell = cellSize.width;
	int heightOfCell = cellSize.height;
	int numberOfCell_X = srcImage.cols / widthOfCell;// X方向cell的個數
	int numberOfCell_Y = srcImage.rows / heightOfCell;

	// 特徵向量的個數
	int numberOfDimension = 9 * numberOfCell_X*numberOfCell_Y;
	featureVector.create(1, numberOfDimension, CV_32FC1);
	featureVector.setTo(Scalar(0));

	// 計算LBP特徵向量
	int stepOfCell = srcImage.cols;
	int index = -9;// cell的特徵向量在最終特徵向量中的起始位置
	float *dataOfFeatureVector = (float *)featureVector.data;
	for (int y = 0; y <= numberOfCell_Y - 1; ++y)
	{
		for (int x = 0; x <= numberOfCell_X - 1; ++x)
		{
			index += 9;

			// 計算每個cell的LBP直方圖
			Mat cell = LBPImage(Rect(x * widthOfCell, y * heightOfCell, widthOfCell, heightOfCell));
			uchar *rowOfCell = cell.data;
			int sum = 0; // 每個cell的等價模式總數
			for (int y_Cell = 0; y_Cell <= cell.rows - 1; ++y_Cell, rowOfCell += stepOfCell)
			{
				uchar *colOfCell = rowOfCell;
				for (int x_Cell = 0; x_Cell <= cell.cols - 1; ++x_Cell, ++colOfCell)
				{
					if (colOfCell[0] != 0)
					{
						// 在直方圖中轉化爲0~8,所以是colOfCell[0] - 1
						++dataOfFeatureVector[index + colOfCell[0] - 1];
						++sum;
					}
				}
			}
			// 直方圖歸一化
			for (int i = 0; i <= 8; ++i)
				dataOfFeatureVector[index + i] /= sum;
		}
	}
}

圖 2.4.1 原圖


圖 2.4.2 灰度不變常規LBP


圖 2.4.3 等價灰度不變LBP


圖 2.4.4 等價旋轉不變LBP

2.5 總結

  LBP曾廣泛應用於人臉檢測及人臉識別應用中,但在深度學習與卷積神將網絡迅猛發展的今天,以LBP爲特徵的檢測及識別算法並不具有競爭力,但是作爲學習案例還是很有借鑑意義的,後續也會陸續寫一些基於深度學習的人臉檢測、人臉識別算法的博客,可以繼續關注。


Task02 LBP特徵描述算子-人臉檢測 END
——By:Aaron

博客:Aaron的博客
GitHub:Aaron_Sandy

關於Datawhale:

Datawhale是一個專注於數據科學與AI領域的開源組織,彙集了衆多領域院校和知名企業的優秀學習者,聚合了一羣有開源精神和探索精神的團隊成員。Datawhale以“for the learner,和學習者一起成長”爲願景,鼓勵真實地展現自我、開放包容、互信互助、敢於試錯和勇於擔當。同時Datawhale 用開源的理念去探索開源內容、開源學習和開源方案,賦能人才培養,助力人才成長,建立起人與人,人與知識,人與企業和人與未來的聯結。

y: inline-block;
color: #999;
padding: 2px;">圖 2.4.4 等價旋轉不變LBP

2.5 總結

  LBP曾廣泛應用於人臉檢測及人臉識別應用中,但在深度學習與卷積神將網絡迅猛發展的今天,以LBP爲特徵的檢測及識別算法並不具有競爭力,但是作爲學習案例還是很有借鑑意義的,後續也會陸續寫一些基於深度學習的人臉檢測、人臉識別算法的博客,可以繼續關注。


Task02 LBP特徵描述算子-人臉檢測 END

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章