Python 3 利用 Dlib 和 sklearn 人臉笑臉檢測機器學習建模


0. 引言

  利用機器學習的方法訓練微笑檢測模型,輸入一張人臉照片,判斷是否微笑;

  精度在 95% 左右( 使用的數據集中 69 張沒笑臉,65 張有笑臉 );

    圖1 測試圖像與檢測結果

    
  項目實現的笑臉識別,並不是通過 計算嘴唇角度,滿足一定弧度認定為笑臉進行判定, 

  而是通過機器學習模型,讓模型去 學習人臉嘴唇的坐標和判定笑臉的關系:

    輸入: 人臉嘴唇的坐標

    輸出: 有沒笑臉 

  借助 Dlib 進行 人臉嘴部 20 個特征點坐標( 40 維特征)的提取,然后根據這 40 維輸入特征 作為 模型輸入, 1 維特征( 1 代表有微笑 / 0 代表沒微笑)作為 輸出,進行 Machine Learning 建模;

  利用幾種機器學習模型進行建模,達到一個二分類(分類 有/無 笑臉)的目的,然后分析模型識別精度和性能,並且可以識別給定圖片的人臉是否微笑;

 

  源碼: 

    GitHub: https://github.com/coneypo/Smile_Detector 

     1. get_features.py : 

      get_features(img_rd, pos_49to68)    # 輸入人臉圖像路徑,利用 Dlib 的 “shape_predictor_68_face_landmarks.dat” 提取嘴部20個特征點坐標的40個特征值;
      write_into_CSV()               # 將40維特征輸入和1維的輸出標記(1代表有微笑/0代表沒微笑)寫入 CSV 文件中;

     2. ML_ways_sklearn.py :

      pre_data()         # 讀取 CSV 中的數據,然后提取出訓練集 X_train 和測試集 X_test 

    3. show_lip.py :
      顯示某人嘴唇的位置

     4. check_smiles.py:

       輸入給定測試圖像,用 ML 模型檢測其 有/無笑臉

 

   用到的幾種機器學習分類模型:

  model_LR() ,    Logistic Regression,           (線性模型)中的邏輯斯特回歸

  model_Linear SVC() ,Support Vector Classification,      (支持向量機)中的線性支持向量分類 

  model_MLPC() ,   Multi-Layer Perceptron Classification,   (神經網絡)多層感知機分類

  model_SGDC() ,  Stochastic Gradient Descent Classification,(線性模型)隨機梯度法求解

  

1. 開發環境

  Python:  3.6.3

  Dlib:    19.7

  OpenCv, NumPy, sklearn, pandas, os, csv 等

 

  get_features.py 中調用的庫:

1 import dlib         # 人臉識別的庫 Dlib
2 import numpy as np  # 數據處理的庫 Numpy
3 import cv2          # 圖像處理的庫 OpenCv
4 import os           # 讀取文件
5 import csv          # csv操作

   

  ML_ways_sklearn.py 中調用的庫:

 1 # pd 讀取 CSV
 2 import pandas as pd  3 
 4 # 分割數據
 5 from sklearn.model_selection import train_test_split  6 
 7 # 用於數據預加工標准化
 8 from sklearn.preprocessing import StandardScaler  9 
10 from sklearn.linear_model import LogisticRegression     # 線性模型中的Logistic回歸模型
11 from sklearn.neural_network import MLPClassifier        # 神經網絡模型中的多層網絡模型
12 from sklearn.svm import LinearSVC                       # SVM模型中的線性SVC模型
13 from sklearn.linear_model import SGDClassifier          # 線性模型中的隨機梯度下降模型

  

  使用的人臉來自於 The MUCT Face Database(Link: http://www.milbo.org/muct/

  (The MUCT database was prepared by Stephen Milborrow, John Morkel, and Fred Nicolls in December 2008 at the University Of Cape Town. We would like to send out a thanks to the people who allowed their faces to be used.)

 

2. 設計流程

  工作內容主要以下兩大塊:提取人臉特征  建模

  整體的設計流程如下圖所示:

    圖 2 總體設計流程圖

 

2.1 提取人臉特征:

  該部分的設計流程圖:

 

   圖 3 人臉提取特征部分流程圖 

  先在項目目錄下建立兩個文件夾,分別存放

    有笑臉的人臉的路徑 : path_images_with_smiles = "data_imgs/database/smiles/"

    無笑臉的人臉的路徑:  path_images_no_smiles = "data_imgs/database/no_smiles/"

 

這樣之后讀取的時候就可以知道人臉的標記有/無人臉;

關於利用 Dlib 進行人臉 68個特征點的提取,在我之前另一篇博客里面介紹過 (link: http://www.cnblogs.com/AdaminXie/p/7905888.html);

本項目中只使用其中嘴部 20個特征點的坐標作為特征輸入,20個點的序號如下圖所示:  

    圖 4 Dlib 標定的嘴部特征點序號

 

  20 個特征點 40 個坐標值的提取,由 get_features() 函數實現;

  輸入是圖像文件所在路徑,返回的的是數組 pos_49to68(40個為特征點坐標值)

 1 # 輸入圖像文件所在路徑,返回一個41維數組(包含提取到的40維特征和1維輸出標記)
 2 def get_features(img_rd):  3 
 4     # 輸入: img_rd: 圖像文件
 5     # 輸出: pos_49to68: feature 49 to feature 68, 20 feature points in all, 40 points
 6 
 7     # read img file
 8     img = cv2.imread(img_rd)  9     # 取灰度
10     img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 11 
12     # 計算 68 點坐標
13     pos_68 = [] 14     rects = detector(img_gray, 0) 15     landmarks = np.matrix([[p.x, p.y] for p in predictor(img, rects[0]).parts()]) 16 
17     for idx, point in enumerate(landmarks): 18         # 68點的坐標
19         pos = (point[0, 0], point[0, 1]) 20  pos_68.append(pos) 21 
22     pos_49to68 = [] 23     # 將點 49-68 寫入 CSV
24     # 即 pos_68[48]-pos_68[67]
25     for i in range(48, 68): 26  pos_49to68.append(pos_68[i][0]) 27         pos_49to68.append(pos_68[i][1]) 28 
29     return pos_49to68

 

  然后就遍歷兩個存放有/無笑臉的文件夾,讀取圖像文件,然后利用 get_features() 函數得到特征值,寫入 CSV 中:

 1 def write_into_CSV():  2     with open(path_csv+"data.csv", "w", newline="") as csvfile:  3         writer = csv.writer(csvfile)  4 
 5         # 處理帶笑臉的圖像
 6         print("######## with smiles #########")  7         for i in range(len(imgs_smiles)):  8             print("img:", path_pic_smiles, imgs_smiles[i])  9 
10             # 用來存放41維特征
11             features_csv_smiles = [] 12 
13             # append "1" means "with smiles"
14             get_features(path_pic_smiles+imgs_smiles[i], features_csv_smiles) 15             features_csv_smiles.append(1) 16             print("features:", features_csv_smiles, "\n") 17 
18             # 寫入CSV
19  writer.writerow(features_csv_smiles) 20 
21         # 處理不帶笑臉的圖像
22         print("######## no smiles #########") 23         for i in range(len(imgs_no_smiles)): 24             print("img", path_pic_no_smiles, imgs_no_smiles[i]) 25 
26             # 用來存放41維特征
27             features_csv_no_smiles = [] 28 
29             # append "0" means "no smiles"
30             get_features(path_pic_no_smiles+imgs_no_smiles[i], features_csv_no_smiles) 31  features_csv_no_smiles.append(0) 32             print("features:", features_csv_no_smiles, "\n") 33 
34             # 寫入CSV
35             writer.writerow(features_csv_no_smiles)

 

  會得到一個 41 列的 CSV 文件,前 40 列為 40 維的輸入特征,第 41 列為笑臉標記。   

 

  show_lip.py 

 1 # Created on: 2018-01-27
 2 # Updated on: 2018-09-06
 3 
 4 # Author: coneypo
 5 # Blog: http://www.cnblogs.com/AdaminXie/
 6 # Github: https://github.com/coneypo/Smile_Detector
 7 
 8 # draw the positions of someone's lip
 9 
10 import dlib         # 人臉識別的庫 Dlib
11 import cv2          # 圖像處理的庫 OpenCv
12 from get_features import get_features   # return the positions of feature points
13 
14 path_test_img = "data_imgs/test_imgs/i064rc-mn.jpg"
15 
16 detector = dlib.get_frontal_face_detector() 17 predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat') 18 
19 pos_49to68 = get_features(path_test_img) 20 
21 img_rd = cv2.imread(path_test_img) 22 
23 # draw on the lip points
24 for i in range(0, len(pos_49to68), 2): 25     print(pos_49to68[i],pos_49to68[i+1]) 26     cv2.circle(img_rd, tuple([pos_49to68[i],pos_49to68[i+1]]), radius=1, color=(0,255,0)) 27 
28 cv2.namedWindow("img_read", 2) 29 cv2.imshow("img_read", img_rd) 30 cv2.waitKey(0)

 

2.2 ML 建模和測試

  這部分機器學習模型使用比較簡單,之前的特征提取已經完成,寫入了 CSV 文件中;接下來就是要從 CSV 中將想要的數據集提取出來,利用 sklearn 進行機器學習建模。

 

2.2.1 數據預加工

  利用 pands.read_csv 讀取 CSV 文件,然后利用 train_test_split 進行數據分割;

  得到 訓練集:X_train, y_train測試集:X_test, y_test

 1 # 從 csv 讀取數據
 2 def pre_data():  3     # 41維表頭
 4     column_names = []  5     for i in range(0, 40):  6         column_names.append("feature_" + str(i + 1))  7     column_names.append("output")  8 
 9     # read csv
10     rd_csv = pd.read_csv("data_csv/data.csv", names=column_names) 11 
12     # 輸出 csv 文件的維度
13     # print("shape:", rd_csv.shape)
14 
15     X_train, X_test, y_train, y_test = train_test_split( 16 
17         # input 0-40
18         # output 41
19         rd_csv[column_names[0:40]], 20         rd_csv[column_names[40]], 21 
22         # 25% for test, 75% for train
23         test_size=0.25, 24         random_state=33) 25 
26     return X_train, X_test, y_train, y_test

 

 2.2.2 機器學習建模

   幾種建模方法在 sklearn 中實現的代碼類似,所以在此只介紹 LR, logistic regression, 邏輯斯特回歸分類,它是屬於線性模型一種;

1 from sklearn.linear_model import LogisticRegression

 

   利用 LR.fit 訓練數據:LR.fit(X_train_LR, y_train_LR),利用 LR.predict 預測標記:y_predict_LR = LR_predict(X_test_LR);

  返回 ss_LR 和 LR,需要這兩個返回值,是因為之后要利用它們對給定圖像的進行檢測,之后 2.2.3 節會介紹;

 1 # LR, logistic regression, 邏輯斯特回歸分類(線性模型)
 2 def model_LR():  3     # get data
 4     X_train_LR, X_test_LR, y_train_LR, y_test_LR = pre_data()  5 
 6     # 數據預加工
 7     # 標准化數據,保證每個維度的特征數據方差為1,均值為0。使得預測結果不會被某些維度過大的特征值而主導
 8     ss_LR = StandardScaler()  9     X_train_LR = ss_LR.fit_transform(X_train_LR) 10     X_test_LR = ss_LR.transform(X_test_LR) 11 
12     # 初始化 LogisticRegression
13     LR = LogisticRegression() 14 
15     # 調用 LogisticRegression 中的 fit() 來訓練模型參數
16  LR.fit(X_train_LR, y_train_LR) 17 
18     # save LR model
19     joblib.dump(LR, path_models + "model_LR.m") 20 
21     # 評分函數
22     score_LR = LR.score(X_test_LR, y_test_LR) 23     # print("The accurary of LR:", score_LR)
24 
25     return (ss_LR)

 

   我的數據集里面是69張沒笑臉,65張有笑臉,測試精度如下,精度在95%附近:

The accurary of LR: 0.941176470588 The accurary of SGD: 0.882352941176 The accurary of SVM: 0.941176470588 The accurary of MLP: 0.970588235294

 

2.2.3 測試單張圖片

  現在我們已經建好機器學習模型,在 2.2.2 中可以利用 sklearn 機器學習模型的 score 函數得到模型精度;

  但是如果想檢測給定圖像的笑臉,需要進行該部分工作:path_test_pic 就是需要進行檢測的文件路徑,需要精確到圖像文件,比如 “F:/pic/test.pic”;

  然后調用 get_features.py 中的 get_features() 函數進行特征提取,得到給定圖像的40維特征數組 pos_49_68;

  

  check_smile.py:

 1 # Created on: 2018-01-27
 2 # Updated on: 2018-09-07
 3 # Author: coneypo
 4 # Blog: http://www.cnblogs.com/AdaminXie/
 5 # Github: https://github.com/coneypo/Smile_Detector
 6 
 7 # use the saved model
 8 from sklearn.externals import joblib  9 
10 from get_features import get_features 11 import ML_ways_sklearn 12 
13 import cv2 14 
15 # path of test img
16 path_test_img = "data_imgs/test_imgs/test1.jpg"
17 
18 # 提取單張40維度特征
19 pos_49to68_test = get_features(path_test_img) 20 
21 # path of models
22 path_models = "data_models/"
23 
24 print("The result of"+path_test_img+":") 25 print('\n') 26 
27 # ######### LR ###########
28 LR = joblib.load(path_models+"model_LR.m") 29 ss_LR = ML_ways_sklearn.model_LR() 30 X_test_LR = ss_LR.transform([pos_49to68_test]) 31 y_predict_LR = str(LR.predict(X_test_LR)[0]).replace('0', "no smile").replace('1', "with smile") 32 print("LR:", y_predict_LR) 33 
34 # ######### LSVC ###########
35 LSVC = joblib.load(path_models+"model_LSVC.m") 36 ss_LSVC = ML_ways_sklearn.model_LSVC() 37 X_test_LSVC = ss_LSVC.transform([pos_49to68_test]) 38 y_predict_LSVC = str(LSVC.predict(X_test_LSVC)[0]).replace('0', "no smile").replace('1', "with smile") 39 print("LSVC:", y_predict_LSVC) 40 
41 # ######### MLPC ###########
42 MLPC = joblib.load(path_models+"model_MLPC.m") 43 ss_MLPC = ML_ways_sklearn.model_MLPC() 44 X_test_MLPC = ss_MLPC.transform([pos_49to68_test]) 45 y_predict_MLPC = str(MLPC.predict(X_test_MLPC)[0]).replace('0', "no smile").replace('1', "with smile") 46 print("MLPC:", y_predict_MLPC) 47 
48 # ######### SGDC ###########
49 SGDC = joblib.load(path_models+"model_SGDC.m") 50 ss_SGDC = ML_ways_sklearn.model_SGDC() 51 X_test_SGDC = ss_SGDC.transform([pos_49to68_test]) 52 y_predict_SGDC = str(SGDC.predict(X_test_SGDC)[0]).replace('0', "no smile").replace('1', "with smile") 53 print("SGDC:", y_predict_SGDC) 54 
55 img_test = cv2.imread(path_test_img) 56 
57 img_height = int(img_test.shape[0]) 58 img_width = int(img_test.shape[1]) 59 
60 # show the results on the image
61 font = cv2.FONT_HERSHEY_SIMPLEX 62 cv2.putText(img_test, "LR: "+y_predict_LR,   (int(img_height/10), int(img_width/10)), font, 0.8, (84, 255, 159), 1, cv2.LINE_AA) 63 cv2.putText(img_test, "LSVC: "+y_predict_LSVC, (int(img_height/10), int(img_width/10*2)), font, 0.8, (84, 255, 159), 1, cv2.LINE_AA) 64 cv2.putText(img_test, "MLPC: "+y_predict_MLPC, (int(img_height/10), int(img_width/10)*3), font, 0.8, (84, 255, 159), 1, cv2.LINE_AA) 65 cv2.putText(img_test, "SGDC: "+y_predict_SGDC, (int(img_height/10), int(img_width/10)*4), font, 0.8, (84, 255, 159), 1, cv2.LINE_AA) 66 
67 cv2.namedWindow("img", 2) 68 cv2.imshow("img", img_test) 69 cv2.waitKey(0)

 

3. 實現效果

  圖 5 同一個人不同表情的笑臉檢測結果

 

 

  圖 6 檢測到沒微笑

 

  圖 7 檢測到有微笑

 

4. 總結

  數據集中有無笑臉是自己進行分類的,而且有寫的表情不太好界定,所以選取的是一些笑容比較明顯的照片作為有笑臉,所以可能出來模型在檢測一些微笑上有誤差;

  笑容檢測模型的數據集測試精度在 95% 左右,比較理想;

  其實人臉笑容檢測的話,光靠嘴部特征去判斷不太合適,要結合整張人臉特征點進行訓練,改進的話也比較簡單;

 

# 源碼上傳到了 GitHub,我也在不斷更新優化,如果對您有幫助或者感興趣歡迎 Star 支持我: https://github.com/coneypo/Smile_Detector

# 請尊重他人勞動成果,轉載或者使用源碼請注明出處:http://www.cnblogs.com/AdaminXie

# 交流學習可以聯系郵箱 coneypo@foxmail.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM