1. 程式人生 > >採用face_recognition包做人臉檢測

採用face_recognition包做人臉檢測

face_recognitiaon安裝要基於dlib,安裝dlib一定上官網去查對應的python安裝的版本。通過cmd中pip install XXX。

本程式碼實現了功能:

考勤,即通過資料庫的照片訓練k近鄰分類器,然後測試未知樣本。

程式碼不足:關於編碼解碼的問題沒有解決,圖片中對應中文人名是亂碼。希望集思廣益,或者自己後期完善~(程式碼位置150~151處)

# 採用face_recognition包檢測人臉去識別
#################################
# 1-引用模組
#################################
import math
from sklearn import neighbors
import  os
import os.path
import pickle
from PIL import Image,ImageDraw
import face_recognition           #安裝見https://blog.csdn.net/wyx100/article/details/78556402
import face_recognition as fr
import sys #用於除錯時,使用。


from face_recognition.face_recognition_cli import image_files_in_folder #資料夾中的影象
from sklearn.neighbors import KNeighborsClassifier  #匯入k近鄰分類器
import sys

#################################
# 2-訓練模型
#################################
#1.建立一個數據集x:128維度加上y一個維度,總共是129維度。
# 2.對每一個照片操作
# 3.決定n
# 4.訓練出分類器
# 5.儲存分類器

def train(train_dir,model_save_path='trained_knn_model.clf',n_neighbors=3,knn_algo='ball_tree'):
    '''
    功能:訓練一個KNN分類器
    :param train_dir: 訓練目錄。其下對每個已知的人,分別以其名字,建立一個資料夾
    :param model_save_path:模型儲存的位置
    :param n_neighbors:鄰居數預設為3
    :param knn_algo: 支援KNN的資料結構;ball_tree是一種樹型結構。
    :return: KNN分類器

    '''
    #生成訓練集
    x=[] #注意x最終是18維。
    y=[]
    #遍歷訓練集中的每一個人
    for class_dir in os.listdir(train_dir):  #http://www.runoob.com/python/os-listdir.html
        if not os.path.isdir(os.path.join(train_dir,class_dir)): #判斷是否是目錄
            continue #結束當前迴圈,進入下一個迴圈。
        #遍歷這個人的每一張照片
        for image_path in image_files_in_folder(os.path.join(train_dir,class_dir)):
            image=fr.load_image_file(image_path) #傳入人臉的位置
            boxes=fr.face_locations(image)  #定出人臉位置
            #對於當前圖片,增加編碼到訓練集合
            x.append(fr.face_encodings(image,known_face_locations=boxes)[0])  #編碼返回時一個128維度的向量。
            y.append(class_dir)
    #決定n
    if n_neighbors is None:
        n_neighbors=3
    #訓練出分類器
    knn_clf=KNeighborsClassifier(n_neighbors=n_neighbors)
    knn_clf.fit(x,y)

    #儲存分類器
    if model_save_path is not None:
        with open(model_save_path,'wb') as f:#wb,b二進位制
            pickle.dump(knn_clf,f)   #模型儲存
    return knn_clf


# 預測
# prediction=predict(full_file_path,model_path='trained_knn_model.clf')
def predict(x_img_path,knn_clf=None,model_path=None,distance_threshold=0.35):
    '''
    利用KNN分類器識別給定的照片中的人臉
    :param x_imag_path:必須對應照片的地址而不是照片的資料夾
    :param knn_clf:
    :param distance_threshold:
    :return: [(人名1,邊界盒子1),...]
    '''
    if knn_clf is None and model_path is None:
        raise Exception('必須提供KNN分類器:可選方式為knn_clf或model_path')
    #載入訓練好的KNN模型(如果有)
    #rb表示要讀入的二進位制資料
    print('除錯斷點0')
    # sys.exit(0)
    if knn_clf is None:
        print(model_path) #trained_knn_model.clf
        with open(model_path,'rb') as f:          #https://www.cnblogs.com/tianyiliang/p/8192703.html

            knn_clf=pickle.load(f)
            print(f)
            print('除錯斷點1','*'*20)
            # sys.exit(1)
    #載入圖片,發現人臉的位置
    print(x_img_path)
    # sys.exit(2)
    x_img=fr.load_image_file(x_img_path)  #https://blog.csdn.net/MG_ApinG/article/details/82252954
    # x_img=fr.load_image_file(x_img_path)  #Permission denied沒有許可權,原因是load_image_file需要開啟檔案的地址而不是資料夾的地址。
    print('除錯斷點2','*'*20)
    # sys.exit(2)
    x_face_location=fr.face_locations(x_img)
    print(x_face_location)
    print('除錯斷點3','*'*20)
    # sys.exit(3)

    #對測試圖片中的人臉進行編碼
    encodings=fr.face_encodings(x_img) #http://www.360doc.com/content/18/0403/18/48868863_742603302.shtml
    print(encodings)
    print(len(encodings[0])) #128個數據組成的向量
    print('除錯斷點4','*'*20)
    # sys.exit(4)
    x_face_locations=fr.face_locations(x_img)
    print('除錯斷點5','*'*20)
    print(x_face_locations)
    # sys.exit(5)

    #利用KNN模型,找出與測試人員最匹配的人臉
    #encodings:128個人臉特徵的向量
    closest_distace=knn_clf.kneighbors(encodings,n_neighbors=3)
    print('除錯斷點6','*'*20)
    print(closest_distace) #(array([[0.34381298, 0.35287966, 0.35839984]]), array([[3, 2, 7]], dtype=int64))
    # sys.exit(6)

    are_matches=[closest_distace[0][i][0]<=distance_threshold  for i in range(len(x_face_locations))] #匿名函式, are_matches即是否匹配上
    print('除錯斷點7','*'*20)
    print(are_matches)
    # sys.exit(7)

    #預測類別,並remove classifications that aren't within the threshold即移除不在閥值內的分類
    print(knn_clf.predict(encodings))
    print(list(x_face_locations))
    print(list(are_matches))
    print(list(zip(knn_clf.predict(encodings),x_face_locations,are_matches)))
    #pred 預測值,loc頭像位置
    return [(pred,loc) if rec else ('unknown',loc) for pred,loc,rec in zip(knn_clf.predict(encodings),x_face_locations,are_matches)]  #zip 壓縮http://www.runoob.com/python/python-func-zip.html

#結果視覺化
def show_names_on_image(img_path,predictions):
    '''
    人臉識別視覺化
    :param img_path: 待識別圖片的位置
    :param predictions: 預測的結果
    :return:
    '''
    pil_image=Image.open(img_path).convert('RGB') #將圖片轉換成格式  #https://blog.csdn.net/icamera0/article/details/50843172
    draw=ImageDraw.Draw(pil_image)                #ImageDraw類支援各種幾何圖形的繪製和文字的繪製https://blog.csdn.net/guduruyu/article/details/71213717
    for name,(top,right,bottom,left) in predictions:
        #用pillow模組畫圖人臉邊界盒子
        draw.rectangle( ((left,top),(right,bottom)),outline=(255,0,255))

        #pillow 裡可能生成UTF-8格式,所以這裡做如下轉換
        #這裡有draw不能解碼出name字型的問題。
        name=name.encode('utf-8')
        name=name.decode('ISO-8859-1')
        print('要列印的name是',type(name))
        #在人臉下寫下名字,作為標籤
        # sys.exit(1)
        text_width,text_height=draw.textsize(name)

        draw.rectangle(((left,bottom-text_height-10),(right,bottom)),fill=(0,0,0),outline=(0,0,0))
        draw.text((left,bottom-text_height-10),name,(255,0,255))
        #遍加名字到li_names
        li_names.append(name)

    #從記憶體刪除draw
    del draw
    #顯示結果圖
    pil_image.show()

#######################
#統計分析
#######################
#為了列印名字的集合
li_names=[]
#計算總人數
def count(train_dir):
    '''
    counts the total number of the set
    :param train_dir:
    :return:
    '''
    path=train_dir
    count=0
    for fn in os.listdir(path): #fn表示的是檔名
        count=count+1
    return count
#獲取所有名字的列表
def list_all(train_dir):
    '''
    determine the list of all names
    :param train_dir:
    :return:
    '''
    path=train_dir
    result=[]
    for fn in os.listdir(path):#fn表示的是資料夾名
        result.append(fn)
    return result
#輸出結果
def stat_output():
    s_list=set(li_names)
    s_list_all=set(list_all('examples/train'))
    if 'unknown'in s_list:
        s_list.remove('unknown')
    print('查閱',s_list)
    tot_num=count('examples/train')
    s_absent=set(s_list_all-s_list) #未到人數
    print('\n')
    print('***********************\n')
    print('全體名單',s_list_all)
    print('已到名單',s_list)
    print('應到人數',tot_num)
    print('已到人數',len(s_list))
    print('出勤率:{:.2f}'.format(float(len(s_list))/float(tot_num)))
    print('未到:',s_absent)
    
    
#######################
#執行
#######################
if __name__ == '__main__': #主函式
    # sys.exit()
    #1. 訓練KNN分類器(它可以儲存,以便再使用)

    #如果有模型時,就不要進行訓練了。

    # print('正在訓練KNN分類器')
    # classifier=train('examples/train',model_save_path='trained_knn_model.clf',n_neighbors=3)
    # print('完成費時間的KNN分類器訓練,訓練結束')

    # 2  利用訓練好的分類器,對新圖片進行預測
    for image_file in os.listdir('examples/test'): #對測試資料夾
        for picture_flie in os.listdir('examples/test/{}'.format(image_file)):
            full_file_path=os.path.join('examples/test/{}'.format(image_file),picture_flie)  #圖片對應的完整的位置,image_file對應圖片對應的資料夾
            print( 'full_file_path:',full_file_path)
            print(picture_flie)
            # sys.exit(0)
            print('正在在 {} 中尋找人臉ing'.format(image_file))
            # sys.exit(0)
            # 利用分類器,找出所有的人臉
            # 要麼傳遞一個classifier檔名,要麼一個classifer模型例項
            prediction=predict(full_file_path,model_path='trained_knn_model.clf')
            print(prediction)
            print('完成{}一次{}人臉預測'.format(image_file,picture_flie),'+'*50)
            # sys.exit(1)

        #列印結果

            for name,(top,right,bottom,left) in prediction:
                print('發現人臉 :{};   人臉位置:({},{},{},{})'.format(name,top,right,bottom,left))
            # sys.exit()

            # # 在圖片上顯示預測結果
            show_names_on_image(os.path.join('examples/test/{}'.format(image_file),picture_flie),prediction)
    # sys.exit()
    #3.輸出統計結果
    stat_output()

 執行結果:

 所提及的影象編碼亂碼:如下

英文的則不會出問題,如下: