Python之Matplotlib數據可視化(八):HOG實現簡單人臉識別

在這裏插入圖片描述
真實世界的數據集通常都充滿噪音和雜質,有的可能是缺少特徵,有的可能是數據形式很難轉換成整齊的 [n_samples, n_features] 特徵矩陣。怎麼提取特徵這件事情並沒有萬靈藥,只能靠數據科學家不斷地磨鍊直覺、積累經驗。

機器學習中最有趣、也是最具挑戰性的任務就是圖像識別,前面也已經介紹過一些通過像素級特徵進行分類學習的案例。在真實世界中,數據通常不會像數據集這麼整齊,再用簡單的像素特徵就不合適了。也正因如此,有關圖像數據特徵提取方法的研究取得了大量成果。

此次將介紹一種圖像特徵提取技術——方向梯度直方圖Histogram of Oriented GradientsHOGhttp://bit.ly/2fCEAcb)。它可以將圖像像素轉換成向量形式,與圖像具體內容有關,與圖像合成因素無關,如照度(illumination。將根據這些特徵,使用機器學習算法和內容開發一個簡單的人臉識別管道。

首先還是導入標準的程序庫:

import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
import numpy as np

1 HOG特徵

方向梯度直方圖是一個簡單的特徵提取程序,專門用來識別有行人(pedestrians)的圖像內容。

HOG 方法包含以下五個步驟:

  • (1) 圖像標準化(可選),消除照度對圖像的影響。
  • (2) 用與水平和垂直方向的亮度梯度相關的兩個過濾器處理圖像,捕捉圖像的邊、角和紋理信息。
  • (3) 將圖像切割成預定義大小的圖塊,然後計算每個圖塊內梯度方向的頻次直方圖。
  • (4) 對比每個圖塊與相鄰圖塊的頻次直方圖,並做標準化處理,進一步消除照度對圖像的影響。
  • (5) 獲得描述每個圖塊信息的一維特徵向量。

用 HOG 方法提取的圖像特徵可視化

Scikit-Image 項目內置了一個快速的 HOG 提取器,可以用它快速獲取並可視化每個圖塊的方向梯度

from skimage import data, color, feature
import skimage.data
image = color.rgb2gray(data.chelsea())
hog_vec, hog_vis = feature.hog(image, visualise=True)
fig, ax = plt.subplots(1, 2, figsize=(12, 6),subplot_kw=dict(xticks=[], yticks=[]))
ax[0].imshow(image, cmap='gray')
ax[0].set_title('input image')
ax[1].imshow(hog_vis)
ax[1].set_title('visualization of HOG features');

出現問題

在這裏插入圖片描述
具體可參見:
python – hog()得到了一個意外的關鍵字參數’visualise’

問題解決

這是一個非常小的錯誤,但關鍵字參數可視化的拼寫錯誤.它應該是

hog_vec, hog_vis = feature.hog(image, visualize=True)

源碼:

import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
import numpy as np
from skimage import data, color, feature
import skimage.data
image = color.rgb2gray(data.chelsea())
hog_vec, hog_vis = feature.hog(image, visualize=True)
fig, ax = plt.subplots(1, 2, figsize=(12, 6),subplot_kw=dict(xticks=[], yticks=[]))
ax[0].imshow(image, cmap='gray')
ax[0].set_title('input image')
ax[1].imshow(hog_vis)
ax[1].set_title('visualization of HOG features');
plt.show()

結果:
在這裏插入圖片描述

2 HOG實戰:簡單人臉識別器

有了圖像的 HOG 特徵後,就可以用 Scikit-Learn 的任意評估器建立一個簡單人臉識別算法,這裏使用線性支持向量機(SVM),具體步驟如下:

  • (1) 獲取一組人臉圖像縮略圖,構建“正”(positive)訓練樣本。
  • (2) 獲取另一組人臉圖像縮略圖,構建“負”(negative)訓練樣本。
  • (3) 提取訓練樣本的 HOG 特徵。
  • (4) 對樣本訓練一個線性 SVM 模型。
  • (5) 爲“未知”圖像傳遞一個移動的窗口,用模型評估窗口中的內容是否是人臉。
  • (6) 如果發現和已知圖像重疊,就將它們組合成一個窗口。

下面一步一步來實現。

(1) 獲取一組正訓練樣本

首先找一些能體現人臉變化的圖像作爲正訓練樣本。獲取這些圖像的方法很簡單——Wild 數據集裏面帶標籤的人臉圖像就是,用 Scikit-Learn 可以直接下載:

In[3]: 	from sklearn.datasets import fetch_lfw_people
		faces = fetch_lfw_people()
		positive_patches = faces.images
		positive_patches.shape
Out[3]: (13233, 62, 47)

這樣就可以獲得用於訓練的 13 000 張照片了。

(2) 獲取一組負訓練樣本

之後需要獲取一組近似大小的縮略圖,但它們不在訓練樣本中。

解決這個問題的一種方法是引入別的圖像語料庫,然後再按需求抽取縮略圖。這裏使用 Scikit-Image 的圖像數據,再用 Scikit-Image 的 PatchExtractor 提取縮略圖

In[4]: 	from skimage import data, transform
		imgs_to_use = ['camera', 'text', 'coins', 'moon',
					'page', 'clock', 'immunohistochemistry',
					'chelsea', 'coffee', 'hubble_deep_field']
		images = [color.rgb2gray(getattr(data, name)())
			for name in imgs_to_use]
In[5]:
		from sklearn.feature_extraction.image import PatchExtractor
		def extract_patches(img, N, scale=1.0,
							patch_size=positive_patches[0].shape):
			extracted_patch_size = \
			tuple((scale * np.array(patch_size)).astype(int))
			extractor = PatchExtractor(patch_size=extracted_patch_size,max_patches=N, random_state=0)
			patches = extractor.transform(img[np.newaxis])
			if scale != 1:
				patches = np.array([transform.resize(patch, patch_size) for patch in patches])
			return patches
		negative_patches = np.vstack([extract_patches(im, 1000, scale)
								for im in images for scale in [0.5, 1.0, 2.0]])
		negative_patches.shape
Out[5]: (30000, 62, 47)

現在就有了 30 000 張尺寸合適、未經識別的圖像。先來看一些圖像,直觀感受一下

fig, ax = plt.subplots(6, 10)
for i, axi in enumerate(ax.flat):
	axi.imshow(negative_patches[500 * i], cmap='gray')
	axi.axis('off')
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
import numpy as np
from skimage import data, color, feature
import skimage.data
from sklearn.datasets import fetch_lfw_people
faces = fetch_lfw_people()
positive_patches = faces.images
positive_patches.shape
from skimage import data, transform
imgs_to_use = ['camera', 'text', 'coins', 'moon',
                'page', 'clock', 'immunohistochemistry',
                'chelsea', 'coffee', 'hubble_deep_field']
images = [color.rgb2gray(getattr(data, name)())
            for name in imgs_to_use]

from sklearn.feature_extraction.image import PatchExtractor
def extract_patches(img, N, scale=1.0,
    patch_size=positive_patches[0].shape):
    extracted_patch_size = \
    tuple((scale * np.array(patch_size)).astype(int))
    extractor = PatchExtractor(patch_size=extracted_patch_size,max_patches=N, random_state=0)
    patches = extractor.transform(img[np.newaxis])
    if scale != 1:
        patches = np.array([transform.resize(patch, patch_size)
                            for patch in patches])
    return patches

negative_patches = np.vstack([extract_patches(im, 1000, scale) for im in images for scale in [0.5, 1.0, 2.0]])


fig, ax = plt.subplots(6, 10)
for i, axi in enumerate(ax.flat):
    axi.imshow(negative_patches[500 * i], cmap='gray')
    axi.axis('off')
plt.show()

沒有人臉的負圖像訓練集
我們希望這些圖像可以讓我們的算法學會“沒有人臉”是什麼樣子。

(3) 組合數據集並提取 HOG 特徵

現在就有了正樣本和負樣本。將它們組合起來,然後計算 HOG 特徵。這些步驟需要耗
點兒時間,因爲對每張圖象進行 HOG 特徵提取的計算量可不小:

In[7]:	from itertools import chain
		X_train = np.array([feature.hog(im) for im in chain(positive_patches, negative_patches)])
		y_train = np.zeros(X_train.shape[0])
		y_train[:positive_patches.shape[0]] = 1
In[8]: 	X_train.shape
Out[8]: (43233, 1215)

這樣,我們就獲得了 43 000 個訓練樣本,每個樣本有 1215 個特徵。現在有了特徵矩陣,就可以給 Scikit-Learn 訓練了。

(4) 訓練一個支持向量機

下面用此次介紹過的工具來創建一個縮略圖分類器。對於高維度的二元分類(是不是人臉)任務,用線性支持向量機是個不錯的選擇。這裏用 Scikit-LearnLinearSVC ,因爲它比 SVC 更適合處理大樣本數據。

首先,用簡單的高斯樸素貝葉斯分類器算一個初始解:

In[9]: 	from sklearn.naive_bayes import GaussianNB
		from sklearn.model_selection import cross_val_score
		cross_val_score(GaussianNB(), X_train, y_train)
Out[9]: array([ 0.9408785 , 0.8752342 , 0.93976823])

我們發現,對於訓練數據,即使用簡單的樸素貝葉斯算法也可以獲得 90% 以上的準確率。現在再用支持向量機分類,用網格搜索獲取最優的邊界軟化參數 C

In[10]: from sklearn.svm import LinearSVC
		from sklearn.grid_search import GridSearchCV
		grid = GridSearchCV(LinearSVC(), {'C': [1.0, 2.0, 4.0, 8.0]})
		grid.fit(X_train, y_train)
		grid.best_score_
Out[10]: 0.98667684407744083
In[11]: grid.best_params_
Out[11]: {'C': 4.0}

用最優的評估器重新訓練數據集:

In[12]: model = grid.best_estimator_
		model.fit(X_train, y_train)
Out[12]: LinearSVC(C=4.0, class_weight=None, dual=True,
					fit_intercept=True, intercept_scaling=1,
					loss='squared_hinge', max_iter=1000,
					multi_class='ovr', penalty='l2',
					random_state=None, tol=0.0001, verbose=0)

(5) 在新圖像中尋找人臉

模型已經訓練完成,讓我們拿一張新圖像檢驗模型的訓練效果。使用一張宇航員照片的局部圖像,在上面運行一個移動窗口來評估每次移動的結果

test_image = skimage.data.astronaut()
test_image = skimage.color.rgb2gray(test_image)
test_image = skimage.transform.rescale(test_image, 0.5)
test_image = test_image[:160, 40:180]
plt.imshow(test_image, cmap='gray')
plt.axis('off');

在這裏插入圖片描述
然後,創建一個不斷在圖像中移動的窗口,然後計算每次移動位置的 HOG 特徵:

In[14]: 
def sliding_window(img, patch_size=positive_patches[0].shape,istep=2, jstep=2, scale=1.0):
	Ni, Nj = (int(scale * s) for s in patch_size)
	for i in range(0, img.shape[0] - Ni, istep):
		for j in range(0, img.shape[1] - Ni, jstep):
			patch = img[i:i + Ni, j:j + Nj]
			if scale != 1:
				patch = transform.resize(patch, patch_size)
			yield (i, j), patch
indices, patches = zip(*sliding_window(test_image))
patches_hog = np.array([feature.hog(patch) for patch in patches])
patches_hog.shape
Out[14]: (1911, 1215)

最後,收集這些 HOG 特徵,並用訓練好的模型來評估每個窗口中是否有人臉:

In[15]: labels = model.predict(patches_hog)
		labels.sum()
Out[15]: 33.0

在近 2000 幅圖像中,總共發現了 33 幅帶人臉的圖像。用矩形把收集到的信息畫在圖像上:

fig, ax = plt.subplots()
ax.imshow(test_image, cmap='gray')
ax.axis('off')
Ni, Nj = positive_patches[0].shape
indices = np.array(indices)
for i, j in indices[labels == 1]:
	ax.add_patch(plt.Rectangle((j, i), Nj, Ni, edgecolor='red', alpha=0.3, lw=2, facecolor='none'))

在這裏插入圖片描述
所有窗口都重疊在一起,並找到了圖像中的人臉!簡單的幾行 Python 代碼就有着巨的威力。

3 完整源代碼

import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
import numpy as np
from skimage import data, color, feature
import skimage.data
from sklearn.datasets import fetch_lfw_people
faces = fetch_lfw_people()
positive_patches = faces.images
positive_patches.shape

faces = fetch_lfw_people()
positive_patches = faces.images
positive_patches.shape
from skimage import data, transform
imgs_to_use = ['camera', 'text', 'coins', 'moon',
                'page', 'clock', 'immunohistochemistry',
                'chelsea', 'coffee', 'hubble_deep_field']
images = [color.rgb2gray(getattr(data, name)())
            for name in imgs_to_use]

from sklearn.feature_extraction.image import PatchExtractor
def extract_patches(img, N, scale=1.0,
    patch_size=positive_patches[0].shape):
    extracted_patch_size = \
    tuple((scale * np.array(patch_size)).astype(int))
    extractor = PatchExtractor(patch_size=extracted_patch_size,max_patches=N, random_state=0)
    patches = extractor.transform(img[np.newaxis])
    if scale != 1:
        patches = np.array([transform.resize(patch, patch_size)
                            for patch in patches])
    return patches

negative_patches = np.vstack([extract_patches(im, 1000, scale) for im in images for scale in [0.5, 1.0, 2.0]])


from itertools import chain
X_train = np.array([feature.hog(im) for im in chain(positive_patches, negative_patches)])
y_train = np.zeros(X_train.shape[0])
y_train[:positive_patches.shape[0]] = 1
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import cross_val_score
cross_val_score(GaussianNB(), X_train, y_train)
from sklearn.svm import LinearSVC
from sklearn.model_selection import GridSearchCV
grid = GridSearchCV(LinearSVC(), {'C': [1.0, 2.0, 4.0, 8.0]})
grid.fit(X_train, y_train)
grid.best_score_
model = grid.best_estimator_
model.fit(X_train, y_train)
test_image = skimage.data.astronaut()
test_image = skimage.color.rgb2gray(test_image)
test_image = skimage.transform.rescale(test_image, 0.5)
test_image = test_image[:160, 40:180]
plt.imshow(test_image, cmap='gray')
plt.axis('off');

def sliding_window(img, patch_size=positive_patches[0].shape, istep=2, jstep=2, scale=1.0):
    Ni, Nj = (int(scale * s) for s in patch_size)
    for i in range(0, img.shape[0] - Ni, istep):
        for j in range(0, img.shape[1] - Ni, jstep):
            patch = img[i:i + Ni, j:j + Nj]
            if scale != 1:
                patch = transform.resize(patch, patch_size)
            yield (i, j), patch
indices, patches = zip(*sliding_window(test_image))
patches_hog = np.array([feature.hog(patch) for patch in patches])
patches_hog.shape

labels = model.predict(patches_hog)
labels.sum()

fig, ax = plt.subplots()
ax.imshow(test_image, cmap='gray')
ax.axis('off')
Ni, Nj = positive_patches[0].shape
indices = np.array(indices)
for i, j in indices[labels == 1]:
    ax.add_patch(plt.Rectangle((j, i), Nj, Ni, edgecolor='red', alpha=0.3, lw=2, facecolor='none'))

plt.show()

4 注意事項與改進方案

訓練集,尤其是負樣本特徵(negative feature)並不完整

這個問題的關鍵在於,有許多類似人臉的紋理並不在訓練集裏面,因此我們的模型非常容易產生假正錯誤(false positives)。如果你用前面的算法評估完整的宇航員照片就可能會出錯:模型可能會將圖像的其他地方誤判爲人臉。

如果引入更多的負訓練集圖像,應該可以改善這個問題。另一種改善方案是用更直接的方法,例如困難負樣本挖掘(hard negative mining)。在困難負樣本挖掘方法中,給分類器看一些沒見過的新圖像,找出所有分類器識別錯誤的假正圖像,然後將這些圖像增加到訓練集中,再重新訓練分類器。

目前的管道只搜索一個尺寸

我們的算法會丟失一些尺寸不是 62 像素 × 47 像素的人臉。我們可以採用不同尺寸的窗口來解決這個問題,每次將圖形提供給模型之前,都用 skimage.transform.resize 重置圖像尺寸——其實前面使用的 sliding_window() 函數已經具備這種功能。

應該將包含人臉的重疊窗口合併

一個產品級的管道不應該讓同一張臉重複出現 30 次,而是將這些重疊的窗口合併成一個。這個問題可以通過一個無監督的聚類方法(MeanShift 聚類就是解決這個問題的好辦法)來解決,或者通過機器視覺常用的算法,例如**非極大值抑制(nonmaximum suppression)**來解決。

管道可以更具流線型

一旦解決了以上問題,就可以創建一個更具流線型的管道,將獲取訓練圖像和預測滑動窗口輸出的功能都封裝在管道中,那樣效果會更好。這正體現了 Python 作爲一個數據科學工具的優勢:只要花一點兒功夫,就可以完成原型代碼,並將其打包成設計優良的面向對象 API,讓用戶可以輕鬆地使用它們。

應該考慮使用更新的技術,例如深度學習

不得不說,HOG 和其他圖像特徵提取方法已經不是最新的技術了。許多流行的物體識別管道都在使用各種深度神經網絡(deep neural network)。你可以將神經網絡看成一種評估器,具有自我學習的能力,可以從數據中確定最優特徵提取策略,而不需要依賴用戶的直覺。雖然有關深度神經網絡方法的內容(以及計算量)超出了本節涵蓋的範圍,但是開源工具(如谷歌的 TensorFlow)已經讓深度學習方法不再那麼高高在上。

在這裏插入圖片描述

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