Topic:表情識別
Env: win10 + Pycharm2018 + Python3.6.8
Date: 2019/6/23~25 by hw_Chen2018 CSDN: https://blog.csdn.net/qq_34198088/article/details/97895876
【感謝參考文獻作者的辛苦付出;編寫不易,轉載請注明出處,感謝!】
一.簡要介紹
本文方法參考文獻【1】的表情識別方法,實驗數據集為JAFFE,實驗方法較為傳統:手工設計特征+淺層分類器,選擇常規的划分訓練集與測試方法:70%訓練集、30%測試集。最后識別正確率73%,比不上目前深度學習方法,想要學習深度學習的朋友,抱歉本文不能提供任何幫助,不過做為Gabor濾波器的學習,可以作為參考。
二.數據集
實驗中采用Jaffe數據庫,數據庫中包含了213幅(每幅圖像的分辨率:256像素×256像素)日本女性的臉相,每幅圖像都有原始的表情定義。表情庫中共有10個人,每個人有7種表情(中性臉、高興、悲傷、驚奇、憤怒、厭惡、恐懼)。JAFFE數據庫均為正面臉相,且把原始圖像進行重新調整和修剪,使得眼睛在數據庫圖像中的位置大致相同,臉部尺寸基本一致,光照均為正面光源,但光照強度有差異。【2】
三.Gabor濾波器
Gabor小波與人類視覺系統中簡單細胞的視覺刺激響應非常相似。Gabor小波對於圖像的邊緣敏感,能夠提供良好的方向選擇和尺度選擇特性,而且對於光照變化不敏感,能夠提供對光照變化良好的適應性。因此Gabor小波被廣泛應用於視覺信息理解。
在空域,一個2維的Gabor濾波器是一個正弦平面波和高斯核函數的乘積。【2】
Gabor濾波器的表達式如下:
每個參數的意義以及對比圖,可參考【3】
三.實驗過程
1.數據庫中灰度圖分辨率是256256,實驗中所用到的是進過裁剪的人臉圖像,大小為48*48。
2.構建多尺度、多方向Gabor濾波器,實驗中構建的濾波器尺度為5,方向數為8,下圖就是所構建的Gabor濾波。值得注意的是通過Gabor濾波得到的結果是一個復數,在實驗中只選用了實部進行實驗,后面展示的一些結果圖都是結果的實部。
3.人臉多尺度多方向特征
4.我們前面得到了5個尺度,8個方向的Gabor特征圖,融合規則是對每個尺度進行操作,融合的第一步是,在同一尺度下,每個方向的特征圖中,逐像素與0比較,大於0的置1,小於等於0置0,之后對每個像素進行二進制編碼,在右邊公式中,u代表方向序號,v代表尺度,對於融合后的圖像的每個像素,它等於每個方向的的二進制編碼之和。更多融合方法可查閱文獻【1】。
5.分塊直方圖
前面濾波器也提到,Gabor濾波器具有很好的局部特性和方向特性,而圖像直方圖從圖像整體分布角度考慮,反映了圖像的統計特性,但是卻丟失了圖像結構特性,因此【1】中避免使用全局圖像直方圖,采用分塊直方圖的方法,即保持了Gabor的局部細節,又保持了一定的結構特性。本文划分成36個小塊。
6.降維與分類器訓練
由於經過分塊直方圖后特征維數46080,相對於共213個樣本的JAFFE數據集來說,維數偏高,同時也存在特征冗余,即並不是所有特征都對分類起積極作用,這里使用比較簡單的PCA進行降維和特征選擇,感興趣的朋友可以搜下其他的特征選擇,比如常用的包裹式、過濾式等等的特征選擇方法以及以流形學習為代表的非線性降維方法等等。選擇前13個主成分,貢獻率達到90%。使用隨機森林進行分類。
四.實驗結果與分析
ROC曲線 PR曲線
混淆矩陣
(1)對於實驗中使用的特征提取及構建的分類器,通過ROC曲線可知,各類別中分類准確率最高的是第一類,對於測試集全部分類正確,其AUC等於1;分類正確率最低是第三類,低於平均正確率,其AUC等於0.83。
(2) 對於實驗中使用的特征提取及構建的分類器,通過PR曲線可知,各類別中分類准確率最高的仍然是第一類,對於測試集全部分類正確,PR曲線面積等於1;分類正確率最低仍然是第三類,同樣低於平均正確率,PR曲線面積等於0.584。
(3)通過混線矩陣可以很直觀看出,分類器容易把第二類分成第一類,分類結果最好的仍然是第一類。
五.實驗代碼
1.提取特征並保存在CSV文件中。(部分代碼參考了OpenCV中Gabor濾波器)
# coding:utf-8 # ------------------------------------ # topic: discrete express recognition # method: Gabor Multi-orientation Features Fusion and Block Histogram + random forest # source: 劉帥師等: 基於 Gabor 多方向特征融合與分塊直方圖的人臉表情識別方法 等 # env: win10 + pycharm + Python3.6.8 # date: 2019/6/21~25 # author: Chen_hw # CSDN:https://blog.csdn.net/qq_34198088 # 編寫不易,轉載請注明出處! # ------------------------------------ import numpy as np import pandas as pd import matplotlib.pyplot as plt import cv2 from scipy import signal import matplotlib import time import csv matplotlib.rcParams['font.sans-serif'] = ['SimHei'] matplotlib.rcParams['font.family'] = 'sans-serif' matplotlib.rcParams['axes.unicode_minus'] = False class DiscreteAffectModel(object): def __init__(self, dataset_path): self.emotion ={0:'Angry', 1:'Disgust', 2:'Fear', 3:'Happy', 4:'Sad', 5:'Surprise', 6:'Neutral'} self.path = dataset_path self.gabor_filter_size = [50, 50] self.gabor_filter_lambda = [2*np.power(2, 0.5), 3*np.power(2, 0.5), 4*np.power(2, 0.5), 5*np.power(2, 0.5), 6*np.power(2, 0.5)] # 正弦因子波長。通常大於等於2.但不能大於輸入圖像尺寸的五分之一 self.gabor_filter_sigma = [1.58, 2.38, 3.17, 3.96, 4.75] # 高斯包絡的標准差.帶寬設置為1時,σ 約= 0.56 λ self.gabor_filter_theta = [theta for theta in np.arange(0, np.pi, np.pi / 8) ] # Gabor函數平行條紋的法線方向 self.gabor_filter_gamma = 0.5 # 空間縱橫比 self.gabor_filter_psi = 0 # 相移 self.filters_real = [] # 濾波器實數部分 self.filters_imaginary = [] # 濾波器虛數部分 self.filtering_result_real_component = [] self.load_dataset() self.build_gabor_filter() # self.show_gabor_filters() # self.show_different_gabor_filteringResult() # self.show_fusion_rule_1_result() self.process_images() # a = np.array([[1,2,3,4,5,6,7,8], # [1,2,3,4,5,6,7,8], # [1,2,3,4,5,6,7,8], # [1,2,3,4,5,6,7,8], # [1,2,3,4,5,6,7,8], # [1,2,3,4,5,6,7,8], # [1,2,3,4,5,6,7,8], # [1,2,3,4,5,6,7,8]]) # b = self.block_histogram(a) # print(b) def load_dataset(self): dataset = pd.read_csv(self.path, dtype='a') self.label = np.array(dataset['emotion']) self.img_data = np.array(dataset['pixels']) def build_gabor_filter(self): '''構建濾波器,分為實部和虛部''' for r in range(len(self.gabor_filter_lambda)): # 尺度 for c in range(len(self.gabor_filter_theta)): # 方向 self.filters_real.append(self.build_a_gabor_filters_real_component(self.gabor_filter_size, self.gabor_filter_sigma[r], self.gabor_filter_theta[c], self.gabor_filter_lambda[r], self.gabor_filter_gamma, self.gabor_filter_psi)) for r in range(len(self.gabor_filter_lambda)): for c in range(len(self.gabor_filter_theta)): self.filters_imaginary.append(self.build_a_gabor_filters_imaginary_component(self.gabor_filter_size, self.gabor_filter_sigma[r], self.gabor_filter_theta[c], self.gabor_filter_lambda[r], self.gabor_filter_gamma, self.gabor_filter_psi)) def show_fusion_rule_1_result(self): '''顯示規則1的融合結果''' img = np.fromstring(self.img_data[0], dtype=float, sep=' ') img = img.reshape((48, 48)) # 實部 filter_result_real_component = [] # 濾波結果實部 for i in range(len(self.filters_real)): cov_result = signal.convolve2d(img, self.filters_real[i], mode='same', boundary='fill', fillvalue=0) filter_result_real_component.append(cov_result) dst = self.fusion_rule_1(filter_result_real_component) plt.figure() plt.suptitle("融合結果__實部") for j in range(5): plt.subplot(1, 5, j+1) plt.imshow(dst[j], cmap="gray") plt.show() filter_result_imaginary_component = [] # 濾波結果實部 for i in range(len(self.filters_imaginary)): cov_result = signal.convolve2d(img, self.filters_imaginary[i], mode='same', boundary='fill', fillvalue=0) filter_result_imaginary_component.append(cov_result) dst = self.fusion_rule_1(filter_result_imaginary_component) plt.figure() plt.suptitle("融合結果__虛部") for j in range(5): # plt.title('融合后') plt.subplot(1, 5, j+1) plt.imshow(dst[j], cmap="gray") plt.show() plt.show() def show_gabor_filters(self): ''' 顯示Gabor濾波器,分為實部和虛部 ''' # 實部 plt.figure(1,figsize=(9, 9)) plt.tight_layout() plt.axis("off") plt.suptitle("實部") for i in range(len(self.filters_real)): plt.subplot(5, 8, i + 1) plt.imshow(self.filters_real[i], cmap="gray") plt.show() # 虛部 plt.figure(2, figsize=(9, 9)) plt.suptitle("虛部") for i in range(len(self.filters_imaginary)): plt.subplot(5, 8, i + 1) plt.imshow(self.filters_imaginary[i], cmap="gray") plt.show() def show_different_gabor_filteringResult(self): '''展示不同濾波器對於同一副圖像的濾波結果,分為實部與虛部''' img = np.fromstring(self.img_data[0], dtype=float, sep=' ') img = img.reshape((48,48)) # 實部 plt.figure(3, figsize=(9,9)) plt.suptitle('real component') for i in range(len(self.filters_real)): cov_result =signal.convolve2d(img, self.filters_real[i], mode='same', boundary='fill', fillvalue=0) # cov_result = np.imag(cov_result) # cov_result = cv2.filter2D(img, cv2.CV_8UC1, self.filters[i]) # cov_result = np.imag(cov_result) plt.subplot(5, 8, i+1) plt.imshow(cov_result, cmap="gray") plt.show() # 虛部 plt.figure(4, figsize=(9,9)) plt.suptitle('imaginary component') for i in range(len(self.filters_imaginary)): cov_result =signal.convolve2d(img, self.filters_imaginary[i], mode='same', boundary='fill', fillvalue=0) # cov_result = np.imag(cov_result) # cov_result = cv2.filter2D(img, cv2.CV_8UC1, self.filters[i]) # cov_result = np.imag(cov_result) plt.subplot(5, 8, i+1) plt.imshow(cov_result, cmap="gray") plt.show() def build_a_gabor_filters_real_component(self, ksize, # 濾波器尺寸; type:list sigma, # 高斯包絡的標准差 theta, # Gaobr函數平行余紋的法線方向 lambd, # 正弦因子的波長 gamma, # 空間縱橫比 psi): # 相移 ''' 構建一個gabor濾波器實部''' g_f = [] x_max = int(0.5*ksize[1]) y_max = int(0.5*ksize[0]) sigma_x = sigma sigma_y = sigma / gamma c = np.cos(theta) s = np.sin(theta) scale = 1 cscale = np.pi*2/lambd ex = -0.5 / (sigma_x * sigma_x) ey = -0.5 / (sigma_y * sigma_y) for y in range(-y_max, y_max, 1): temp_line = [] for x in range(-x_max, x_max, 1): xr = x * c + y * s yr = -x * s + y * c temp = scale * np.exp(ex * xr * xr + ey * yr * yr) * np.cos(cscale * xr + psi) temp_line.append(temp) g_f.append(np.array(temp_line)) g_f = np.array(g_f) return g_f def build_a_gabor_filters_imaginary_component(self, ksize, # 濾波器尺寸; type:list sigma, # 高斯包絡的標准差 theta, # Gaobr函數平行余紋的法線方向 lambd, # 正弦因子的波長 gamma, # 空間縱橫比 psi): ''' 構建一個gabor濾波器虛部''' g_f = [] x_max = int(0.5*ksize[1]) y_max = int(0.5*ksize[0]) sigma_x = sigma sigma_y = sigma / gamma c = np.cos(theta) s = np.sin(theta) scale = 1 cscale = np.pi*2/lambd ex = -0.5 / (sigma_x * sigma_x) ey = -0.5 / (sigma_y * sigma_y) for y in range(-y_max, y_max, 1): temp_line = [] for x in range(-x_max, x_max, 1): xr = x * c + y * s yr = -x * s + y * c temp = scale * np.exp(ex * xr * xr + ey * yr * yr) * np.sin(cscale * xr + psi) temp_line.append(temp) g_f.append(np.array(temp_line)) g_f = np.array(g_f) return g_f def fusion_rule_1(self, filteringResult): ''' 融合規則1:詳見論文【劉帥師等: 基於 Gabor 多方向特征融合與分塊直方圖的人臉表情識別方法】 融合同一尺度下不同方向的圖像 filteringResult: 每個尺度下各方向的濾波結果,實部與虛部計算都可調用。輸入類型為列表,列表中每個元素類型為array return:融合后的array ''' comparedFilteringResult = [] # 存儲與0比較后的列表 fusion_result = [] # 5個尺度下,每個尺度下個方向融合后結果,類型為list大小為5,每個元素為array for content in filteringResult: temp = list(content.flatten()) temp = [1 if i > 0 else 0 for i in temp] comparedFilteringResult.append(temp) # print(len(comparedFilteringResult[0])) for count in range(5): # 5個尺度 count *= 8 temp = [] for ele in range(len(comparedFilteringResult[0])): # 8個方向 tmp = comparedFilteringResult[count + 0][ele] * np.power(2, 0) +\ comparedFilteringResult[count + 1][ele] * np.power(2, 1) +\ comparedFilteringResult[count + 2][ele] * np.power(2, 2) + \ comparedFilteringResult[count + 3][ele] * np.power(2, 3) +\ comparedFilteringResult[count + 4][ele] * np.power(2, 4) + \ comparedFilteringResult[count + 5][ele] * np.power(2, 5) + \ comparedFilteringResult[count + 6][ele] * np.power(2, 6) + \ comparedFilteringResult[count + 7][ele] * np.power(2, 7) temp.append(tmp) # print(len(temp)) fusion_result.append(temp) fusion_result = [np.array(i) for i in fusion_result] fusion_result = [arr.reshape((48,48)) for arr in fusion_result] return fusion_result def get_fusionRule1_result_realComponent(self): '''提取每幅圖像多尺度多方向Gabor特征,融合每個尺度下各方向濾波結果,將融合圖像保存在csv中''' # multiscale_multiangle_gabor_feature_InOneImage = [] # 單幅圖像多尺度多方向Gabor特征 gabor_feature_images_real_component = [] # 全部圖像的Gabor特征實部 dst_real_component = [] # 保存融合圖像 # 提取特征 for sample in range(self.img_data.shape[0]): # self.img_data.shape[0] img = np.fromstring(self.img_data[sample], dtype=float, sep=' ') img = img.reshape((48, 48)) tempRealComponent = [] for filter in self.filters_real: tempCovResult = signal.convolve2d(img, filter, mode='same', boundary='fill', fillvalue=0) tempRealComponent.append(tempCovResult) gabor_feature_images_real_component.append(tempRealComponent) # 其中每個元素為5個尺度、8個方向,共40幅圖像的列表 for multi_info in gabor_feature_images_real_component: temp = self.fusion_rule_1(multi_info) dst_real_component.append(temp) return dst_real_component # 顯示某一圖像(如0)下某一尺度(如尺度序號0)的融合圖像方法:dst_real_component[0][0] def get_block_histogram(self, fused_image_list): ''' 分塊直方圖 :param fused_image_list: 含融合圖像的列表,樣本數為213,大小為213,其中每個元素類型列表,5個尺度的融合圖像,大小為5, 輸入的元素的元素類型為array,包含48*48的array5個 :return: 每幅圖像的直方圖特征,共213條樣本,每個樣本特征維度5*6*6*256=46080 ''' block_histogram_feature = [] # 不同圖像直方圖特征,每一幅圖像(5個尺度的融合圖像)的直方圖特征為其中一個元素 for multi_sclae_image in fused_image_list: # 遍歷每幅圖像 temp = [] for image in multi_sclae_image: # 遍歷每幅圖像下各尺度融合圖像 tmp_block_histogram_feature = self.block_histogram(image) temp.append(tmp_block_histogram_feature) temp = np.array(temp) temp = temp.flatten() block_histogram_feature.append(temp) # print(f"特征長度:{temp.shape}") block_histogram_feature = np.array(block_histogram_feature) # block_histogram_feature = block_histogram_feature.flatten() # 展開成一維 return block_histogram_feature def process_images(self): ''' 提取每幅圖像特征,並保存在csv中 :return: ''' fusionRule1_result_real = self.get_fusionRule1_result_realComponent() block_histgram_feature = self.get_block_histogram(fusionRule1_result_real) # print(f"類型:{type(block_histgram_feature)}") # print(block_histgram_feature.shape) with open("gabor_feature_real_component.csv", 'w') as gf: writer = csv.writer(gf) writer.writerow(['label', 'feature']) for i in range(block_histgram_feature.shape[0]): data_list = list(block_histgram_feature[i, :]) b = ' '.join(str(x) for x in data_list) l = np.hstack([self.label[i], b ]) writer.writerow(l) def block_histogram(self, inputImage): ''' 計算分塊直方圖特征,塊大小8*8 :param inputImage: 輸入灰度圖,類型ndarray,大小48*48 :return: 分塊直方圖特征,類型為ndarray,一維 ''' block_histogram_feature = [] for row in range(int(inputImage.shape[0]/8)): for col in range(int(inputImage.shape[1]/8)): # hist = cv2.calcHist([inputImage[row*8:row*8+8, col*8:col*8+8]], [0], None, [256], [0, 255]) hist, _ = np.histogram(inputImage[row*8:row*8+8, col*8:col*8+8], bins=[i for i in range(257)]) hist = np.array(hist) block_histogram_feature.append(hist) block_histogram_feature = np.array(block_histogram_feature) block_histogram_feature = block_histogram_feature.flatten() return block_histogram_feature if __name__ =='__main__': time_start = time.time() classify_test = DiscreteAffectModel('face.csv') time_end = time.time() print(f"耗時:{time_end - time_start}"
2. 分類器訓練與測試
import numpy as np from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import make_classification import pandas as pd from sklearn.model_selection import train_test_split from sklearn.decomposition import PCA from sklearn.ensemble import GradientBoostingClassifier from sklearn import metrics from sklearn.model_selection import GridSearchCV from sklearn.model_selection import learning_curve from sklearn.model_selection import cross_val_predict import matplotlib.pyplot as plt import scikitplot as skplt class classify(object): def __init__(self): self.load_dataset() self.classify_RF() # self.classify_GBDT() def load_dataset(self): data = pd.read_csv(r'gabor_feature_real_component.csv', dtype='a') self.label = np.array(data['label']) self.label = list(self.label) self.label = np.array([int(x) for x in self.label]) self.feature = [] feature = np.array(data['feature']) for i in range(feature.shape[0]): temp = np.fromstring(feature[i], dtype=float, sep=' ') self.feature.append(temp) self.feature = np.array(self.feature) # 降維 pca = PCA(210) self.feature = pca.fit_transform(self.feature) # print(sum(pca.explained_variance_ratio_[:210])) # print(self.feature) def classify_RF(self): self.train_feature, self.test_feature, self.train_label, self.test_label = \ train_test_split(self.feature, self.label, test_size=0.3, random_state=0) rfc = RandomForestClassifier(n_estimators=900, criterion='gini', max_depth=10, min_samples_split=2, min_samples_leaf=1, oob_score=False, n_jobs=-1, random_state=0) rfc.fit(self.train_feature, self.train_label) print(rfc.predict(self.test_feature)) print(self.test_label) score = rfc.score(self.test_feature,self.test_label) print(f"score:{score}") # print(len(rfc.feature_importances_)) predict_proba = rfc.predict_proba(self.test_feature) skplt.metrics.plot_roc(self.test_label, predict_proba) plt.show() skplt.metrics.plot_precision_recall_curve(self.test_label,predict_proba, cmap="nipy_spectral") plt.show() predictions = cross_val_predict(rfc, self.train_feature,self.train_label) plot = skplt.metrics.plot_confusion_matrix(self.train_label, predictions, normalize=True) plt.show() def classify_GBDT(self): self.train_feature, self.test_feature, self.train_label, self.test_label = \ train_test_split(self.feature, self.label, test_size=0.3, random_state=0) # gbdtc = GradientBoostingClassifier(n_estimators=2000, # max_depth=5, # min_samples_split=2, # learning_rate=0.01) # gbdtc.fit(self.train_feature, self.train_label) # y_predicted = gbdtc.predict(self.test_feature) # print(f"准確率:{metrics.accuracy_score(self.test_label, y_predicted)}") param_test1 = [{'n_estimators':[i for i in range(900,3050,50)]}] gsearch1 = GridSearchCV(estimator = GradientBoostingClassifier(learning_rate=0.1, min_samples_split=300, min_samples_leaf=20,max_depth=8,max_features='sqrt', subsample=0.8,random_state=10), param_grid = param_test1, scoring='roc_auc', iid=False, cv=5) # gsearch1.fit(self.train_feature, self.train_label) if __name__ == "__main__": c_test = classify()
【1】劉帥師,田彥濤,萬川.基於Gabor多方向特征融合與分塊直方圖的人臉表情識別方法[J].自動化學報,2011,37(12):1455-1463.
【2】https://blog.csdn.net/zdyueguanyun/article/details/8525739
【3】https://www.cnblogs.com/arxive/p/4990754.html