Python 人臉辨識應用「戴不戴口罩」

Ping-Lun Liao
13 min readJul 6, 2021

若您覺得文章寫得不錯,請點選文章上的廣告,來支持小編,謝謝。

If you like this post, please click the ads on the blog or buy me a coffee. Thank you very much.

此文的程式為修改 Github 上的專案: R4j4n/Face-recognition-Using-Facenet-On-Tensorflow-2.X

人臉辨識需要經過人臉偵測、特徵擷取、人臉分類(如下圖)來取得某張人臉特徵是誰的人臉。

而常見人臉辨識流程為

  1. 使用 mtcnn 偵測 人臉特徵 如額頭、眼睛、耳朵、鼻子、嘴巴、下巴。
  2. 將這些特徵與某人做對應關係。
  3. 將多人的人臉特徵使用Facenet做分類訓練。

取得的整張人臉特徵如下圖橘色長方形

戴口罩人臉辨識流程為

  1. 使用 mtcnn 偵測 人臉的上半部 特徵如額頭、眼睛、耳朵、鼻子。
  2. 將這些特徵與某人做對應關係。
  3. 將多人的人臉特徵使用Facenet做分類訓練。

透過擷取人臉的上半臉如下圖綠色長方形

實作環境Environment

OS :Windows 10

IDE : anaconda Anaconda3–2021.05-Windows-x86_64

https://www.anaconda.com/products/individual#Downloads 下載最新版本。

函數庫(Libraries)安裝 :

安裝tensorflow 2.3.0

可參考 TensorFlow — Anaconda documentation

無GPU安裝

conda create — n tf tensorflow

conda activate tf

有GPU安裝

conda create — n tf — gpu tensorflow — gpu

conda activate tf — gpu

安裝 Python Package

pip install numpy

pip install opencv-python

pip install mtcnn

pip install scikit-learn

pip install scipy

在 Faces 資料夾下建立B某的資料夾,然後將B某的兩到三張照片放到B某資料夾。 注意:Faces 資料夾下的人名請用英文。

執行 train_v2.py 做訓練, 執行 detect.py 做人臉辨識

結果影片:

train_v2.py程式碼:

# architecture 模組,含有 FaceNet 網路架構 InceptionResNetV2 from architecture import * # 作業系統常用函式庫,如列出資料夾所有檔案 import os # 電腦視覺處理函式庫 import cv2 # Multi-task Cascaded Convolutional Neural Networks # 用來偵測人臉 import mtcnn # 讀取與儲存 Python 物件函式庫 import pickle # 多維度陣列運算的數學函式庫 import numpy as np # Normalizer 正規化函式庫 from sklearn.preprocessing import Normalizer # 讀取神經網路模型函式庫 from tensorflow.keras.models import load_model ###### 路徑與變數 ########## face_data = 'Faces/' # 將圖片中人臉,做正規化後的大小 required_shape = (160,160) # facenet 編碼器 face_encoder = InceptionResNetV2() # 設定 google facenet 建立好的模型參數檔案路徑 path = "facenet_keras_weights.h5" # 讀取 google facenet 的模型參數 face_encoder.load_weights(path) # 使用 mtcnn 人臉偵測器 face_detector = mtcnn.MTCNN() # 訓練後的人臉編碼串列 encodes = [] # 訓練後的人臉編碼表 encoding_dict = dict() # L2 正規化 l2_normalizer = Normalizer('l2') ############################### # 正規化 def normalize(img): mean, std = img.mean(), img.std() return (img - mean) / std # 對 Faces 資料夾下的每一人,擷取臉部特徵 for face_names in os.listdir(face_data): # 設定某甲的人臉資料夾路徑 person_dir = os.path.join(face_data,face_names) # 讀取某甲的每一張臉 for image_name in os.listdir(person_dir): # 設定某甲某張臉的檔案路徑 image_path = os.path.join(person_dir,image_name) # 讀取某甲某張臉,BGR的格式 img_BGR = cv2.imread(image_path) # 某甲某張臉 BGR 格式轉換成 RGB 格式,RGB格式才能給 mtcnn 人臉偵測器使用 img_RGB = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2RGB) # 找人臉特徵的位置 x = face_detector.detect_faces(img_RGB) # 框出來的人臉位置長方形 box:x1、y1、width寬、height高 x1, y1, width, height = x[0]['box'] #print(type(height)) #print(height) # 將人臉的高度除2,也就是取上半臉 height = int(height / 2) #print(height) # 框出來的人臉位置長方形 box的左上角位置 x1, y1 = abs(x1) , abs(y1) # 框出來的人臉位置長方形 box的右上角位置 x2, y2 = x1+width , y1+height # 從圖片取出人臉 face = img_RGB[y1:y2 , x1:x2] # 正規化,讓每一人臉照片的大小都一樣 face = normalize(face) face = cv2.resize(face, required_shape) # 改變 face 的維度 face_d = np.expand_dims(face, axis=0) # 做預測的編碼 encode = face_encoder.predict(face_d)[0] # 將此張人臉編碼,加到人臉編碼串列 encodes.append(encode) # 將新的人臉編碼加入人臉編碼表 if encodes: encode = np.sum(encodes, axis=0 ) encode = l2_normalizer.transform(np.expand_dims(encode, axis=0))[0] encoding_dict[face_names] = encode # 存檔 path = 'encodings/encodings.pkl' with open(path, 'wb') as file: pickle.dump(encoding_dict, file)

detect.py 程式碼:

# 電腦視覺處理函式庫 import cv2 # 多維度陣列運算的數學函式庫 import numpy as np # Multi-task Cascaded Convolutional Neural Networks # 用來偵測人臉 import mtcnn # architecture 模組,含有 FaceNet 網路架構 InceptionResNetV2 from architecture import * # 使用 train_v2 normalize 與 l2_normalizer from train_v2 import normalize,l2_normalizer # 使用 cosine 來計算某兩張臉的相似度 from scipy.spatial.distance import cosine # 讀取神經網路模型函式庫 from tensorflow.keras.models import load_model # 讀取與儲存 Python 物件函式庫 import pickle # 偵測到人臉的可信度最低值 confidence_t=0.99 # 相似程度 recognition_t=0.5 # 將圖片中人臉,做正規化後的大小 required_size = (160,160) # 取得圖片中的人臉 face,人臉位置左上角(x1,y1)與右下角(x2,y2) def get_face(img, box): x1, y1, width, height = box x1, y1 = abs(x1), abs(y1) # 取上半臉 height = int(height/2) x2, y2 = x1 + width, y1 + height face = img[y1:y2, x1:x2] return face, (x1, y1), (x2, y2) # 取得人臉特徵編碼 def get_encode(face_encoder, face, size): face = normalize(face) face = cv2.resize(face, size) encode = face_encoder.predict(np.expand_dims(face, axis=0))[0] return encode # 讀取人臉特徵編碼表 def load_pickle(path): with open(path, 'rb') as f: encoding_dict = pickle.load(f) return encoding_dict # 偵測人臉 def detect(img ,detector,encoder,encoding_dict): # 人臉 BGR 格式轉換成 RGB 格式,RGB格式才能給 mtcnn 人臉偵測器使用 img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 人臉偵測結果,可能會有好幾處偵測到人臉 results = detector.detect_faces(img_rgb) # 對影像中每一處可能為人臉的地方 for res in results: # 此人臉是否有超過 confidence_t ,若沒有超過 0.99,就跳過此人臉 if res['confidence'] < confidence_t: continue # 取得圖片中的人臉 face,人臉位置左上角 pt_1 與右下角 pt_2 face, pt_1, pt_2 = get_face(img_rgb, res['box']) # 取得人臉特徵編碼 encode = get_encode(encoder, face, required_size) # 人臉特徵編碼正規化 encode = l2_normalizer.transform(encode.reshape(1, -1))[0] # 是誰呢? name = 'unknown' # 與誰的臉最相似 distance = float("inf") # 找出與 Faces資料夾下的某人最相似 for db_name, db_encode in encoding_dict.items(): # 此人臉與某甲的相似度 dist = cosine(db_encode, encode) # 若有比 recognition_t 0.5 還小,此人臉就非常有可能是這位某甲 if dist < recognition_t and dist < distance: # 將人臉名稱為某甲的姓名 name = db_name # 更新相似度,繼續找更相似的某甲 distance = dist # 將人臉的名稱顯示在圖片上 if name == 'unknown': cv2.rectangle(img, pt_1, pt_2, (0, 0, 255), 2) cv2.putText(img, name, pt_1, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1) else: cv2.rectangle(img, pt_1, pt_2, (0, 255, 0), 2) cv2.putText(img, name + f'__{distance:.2f}', (pt_1[0], pt_1[1] - 5), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 200, 200), 2) # 回傳處理過的圖片,讓更新過的圖片顯示在視訊鏡頭視窗畫面上 return img if __name__ == "__main__": # 將圖片中人臉,做正規化後的大小 required_shape = (160,160) # facenet 編碼器 face_encoder = InceptionResNetV2() # 設定 google facenet 建立好的模型參數檔案路徑 path_m = "facenet_keras_weights.h5" # 讀取 google facenet 的模型參數 face_encoder.load_weights(path_m) # 訓練後的人臉編碼表檔案路徑 encodings_path = 'encodings/encodings.pkl' # 使用 mtcnn 人臉偵測器 face_detector = mtcnn.MTCNN() # 讀取訓練後的人臉編碼表 encoding_dict = load_pickle(encodings_path) # 設定鏡頭為作業系統編號0的視訊鏡頭 cap = cv2.VideoCapture(0) # 當成公開啟視訊鏡頭 while cap.isOpened(): # 讀取視訊鏡頭影像,ret 為讀取影像的結果狀態,frame為影像本身。 ret,frame = cap.read() # 將影像左右翻轉 frame = cv2.flip(frame,1) # 讀取影像沒有成功 if not ret: print("CAM NOT OPEND") break # 將視訊影像做人臉偵測,並將偵測結果更新到 frame 影像上 frame = detect(frame , face_detector , face_encoder , encoding_dict) # 將人臉偵測結果顯示在式窗上 cv2.imshow('camera', frame) # 若按下按鍵 q,退出程式 if cv2.waitKey(1) & 0xFF == ord('q'): # 釋放視訊鏡頭資源 cap.release() # 關閉程式所開啟的視窗 cv2.destroyAllWindows() break

Originally published at https://yunlinsong.blogspot.com.

--

--