本人参加比赛时用的人脸识别程序,功能包括人脸识别以及表情识别,表情识别可识别正常、开心、惊讶。能力有限,程序在检测到人脸时刷新率不是很高,摄像头画面有点卡,但在摄像头前没有人脸时画面还是很流畅的,这是由于人脸识别算法和表情识别需要花费太多的时间。
程序太大,无法上传,我就直接贴出来了。
get_face_from_camera.py/从摄像头获取人脸
- # 进行人脸录入 / Face register
- import dlib
- import numpy as np
- import cv2
- import os
- import shutil
- import time
- # Dlib 正向人脸检测器 / Use frontal face detector of Dlib
- detector = dlib.get_frontal_face_detector()
- class Face_Register:
- def __init__(self):
- self.path_photos_from_camera = "data/data_faces_from_camera_my/"
- self.font = cv2.FONT_ITALIC
- self.existing_faces_cnt = 0 # 已录入的人脸计数器 / cnt for counting saved faces
- self.ss_cnt = 0 # 录入 personX 人脸时图片计数器 / cnt for screen shots
- self.current_frame_faces_cnt = 0 # 录入人脸计数器 / cnt for counting faces in current frame
- self.save_flag = 1 # 之后用来控制是否保存图像的 flag / The flag to control if save
- self.press_n_flag = 0 # 之后用来检查是否先按 'n' 再按 's' / The flag to check if press 'n' before 's'
- # FPS
- self.frame_time = 0
- self.frame_start_time = 0
- self.fps = 0
- # 新建保存人脸图像文件和数据CSV文件夹 / Make dir for saving photos and csv
- def pre_work_mkdir(self):
- # 新建文件夹 / Create folders to save faces images and csv
- if os.path.isdir(self.path_photos_from_camera):
- pass
- else:
- os.mkdir(self.path_photos_from_camera)
- # 删除之前存的人脸数据文件夹 / Delete the old data of faces
- def pre_work_del_old_face_folders(self):
- # 删除之前存的人脸数据文件夹, 删除 "/data_faces_from_camera/person_x/"...
- folders_rd = os.listdir(self.path_photos_from_camera)
- for i in range(len(folders_rd)):
- shutil.rmtree(self.path_photos_from_camera+folders_rd[i])
- if os.path.isfile("data/features_all.csv"):
- os.remove("data/features_all.csv")
- # 如果有之前录入的人脸, 在之前 person_x 的序号按照 person_x+1 开始录入 / Start from person_x+1
- def check_existing_faces_cnt(self):
- if os.listdir("data/data_faces_from_camera_my/"):
- # 获取已录入的最后一个人脸序号 / Get the order of latest person
- person_list = os.listdir("data/data_faces_from_camera_my/")
- person_num_list = []
- for person in person_list:
- person_num_list.append(int(person.split('_')[-1]))
- self.existing_faces_cnt = max(person_num_list)
- # 如果第一次存储或者没有之前录入的人脸, 按照 person_1 开始录入 / Start from person_1
- else:
- self.existing_faces_cnt = 0
- # 获取处理之后 stream 的帧数 / Update FPS of video stream
- def update_fps(self):
- now = time.time()
- self.frame_time = now - self.frame_start_time
- self.fps = 1.0 / self.frame_time
- self.frame_start_time = now
- # 生成的 cv2 window 上面添加说明文字 / PutText on cv2 window
- def draw_note(self, img_rd):
- # 添加说明 / Add some notes
- cv2.putText(img_rd, "Face Register", (20, 40), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA)
- cv2.putText(img_rd, "FPS: " + str(self.fps.__round__(2)), (20, 100), self.font, 0.8, (0, 255, 0), 1,
- cv2.LINE_AA)
- cv2.putText(img_rd, "Faces: " + str(self.current_frame_faces_cnt), (20, 140), self.font, 0.8, (0, 255, 0), 1, cv2.LINE_AA)
- cv2.putText(img_rd, "N: Create face folder", (20, 350), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
- cv2.putText(img_rd, "S: Save current face", (20, 400), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
- cv2.putText(img_rd, "Q: Quit", (20, 450), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
- # 获取人脸 / Main process of face detection and saving
- def process(self, stream):
- # 1. 新建储存人脸图像文件目录 / Create folders to save photos
- global current_face_dir
- self.pre_work_mkdir()
- # 2. 删除 "/data/data_faces_from_camera" 中已有人脸图像文件 / Uncomment if want to delete the saved faces and start from person_1
- if os.path.isdir(self.path_photos_from_camera):
- self.pre_work_del_old_face_folders()
- # 3. 检查 "/data/data_faces_from_camera" 中已有人脸文件
- self.check_existing_faces_cnt()
- while stream.isOpened():
- flag, img_rd = stream.read() # Get camera video stream
- kk = cv2.waitKey(1)
- faces = detector(img_rd, 0) # Use Dlib face detector
- # 4. 按下 'n' 新建存储人脸的文件夹 / Press 'n' to create the folders for saving faces
- if kk == ord('n'):
- self.existing_faces_cnt += 1
- current_face_dir = self.path_photos_from_camera + "person_" + str(self.existing_faces_cnt)
- os.makedirs(current_face_dir)
- print('\n')
- print("新建的人脸文件夹 / Create folders: ", current_face_dir)
- self.ss_cnt = 0 # 将人脸计数器清零 / Clear the cnt of screen shots
- self.press_n_flag = 1 # 已经按下 'n' / Pressed 'n' already
- # 5. 检测到人脸 / Face detected
- if len(faces) != 0:
- # 矩形框 / Show the ROI of faces
- for k, d in enumerate(faces):
- # 计算矩形框大小 / Compute the size of rectangle box
- height = (d.bottom() - d.top())
- width = (d.right() - d.left())
- hh = int(height/2)
- ww = int(width/2)
- # 6. 判断人脸矩形框是否超出 480x640 / If the size of ROI > 480x640
- if (d.right()+ww) > 640 or (d.bottom()+hh > 480) or (d.left()-ww < 0) or (d.top()-hh < 0):
- cv2.putText(img_rd, "OUT OF RANGE", (20, 300), self.font, 0.8, (0, 0, 255), 1, cv2.LINE_AA)
- color_rectangle = (0, 0, 255)
- save_flag = 0
- if kk == ord('s'):
- print("请调整位置 / Please adjust your position")
- else:
- color_rectangle = (255, 255, 255)
- save_flag = 1
- cv2.rectangle(img_rd,
- tuple([d.left() - ww, d.top() - hh]),
- tuple([d.right() + ww, d.bottom() + hh]),
- color_rectangle, 2)
- # 7. 根据人脸大小生成空的图像 / Create blank image according to the size of face detected
- img_blank = np.zeros((int(height*2), width*2, 3), np.uint8)
- if save_flag:
- # 8. 按下 's' 保存摄像头中的人脸到本地 / Press 's' to save faces into local images
- if kk == ord('s'):
- # 检查有没有先按'n'新建文件夹 / Check if you have pressed 'n'
- if self.press_n_flag:
- self.ss_cnt += 1
- for ii in range(height*2):
- for jj in range(width*2):
- img_blank[ii][jj] = img_rd[d.top()-hh + ii][d.left()-ww + jj]
- cv2.imwrite(current_face_dir + "/img_face_" + str(self.ss_cnt) + ".jpg", img_blank)
- print("写入本地 / Save into:", str(current_face_dir) + "/img_face_" + str(self.ss_cnt) + ".jpg")
- else:
- print("请先按 'N' 来建文件夹, 按 'S' / Please press 'N' and press 'S'")
- self.current_frame_faces_cnt = len(faces)
- # 9. 生成的窗口添加说明文字 / Add note on cv2 window
- self.draw_note(img_rd)
- # 10. 按下 'q' 键退出 / Press 'q' to exit
- if kk == ord('q'):
- break
- # 11. Update FPS
- self.update_fps()
- cv2.namedWindow("camera", 1)
- cv2.imshow("camera", img_rd)
- def run(self):
- cap = cv2.VideoCapture(0)
- self.process(cap)
- cap.release()
- cv2.destroyAllWindows()
- def main():
- Face_Register_con = Face_Register()
- Face_Register_con.run()
- if __name__ == '__main__':
- main()
复制代码
feature.py/提取保存的照片的特征
- # 从人脸图像文件中提取人脸特征存入 "features_all.csv" / Extract features from images and save into "features_all.csv"
- import os
- import dlib
- from skimage import io
- import csv
- import numpy as np
- # 要读取人脸图像文件的路径 / Path of cropped faces
- path_images_from_camera = "data/data_faces_from_camera/"
- # Dlib 正向人脸检测器 / Use frontal face detector of Dlib
- detector = dlib.get_frontal_face_detector()
- # Dlib 人脸 landmark 特征点检测器 / Get face landmarks
- predictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')
- # Dlib Resnet 人脸识别模型,提取 128D 的特征矢量 / Use Dlib resnet50 model to get 128D face descriptor
- face_reco_model = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")
- # 返回单张图像的 128D 特征 / Return 128D features for single image
- # Input: path_img <class 'str'>
- # Output: face_descriptor <class 'dlib.vector'>
- def return_128d_features(path_img):
- img_rd = io.imread(path_img)
- faces = detector(img_rd, 1)
- print("%-40s %-20s" % ("检测到人脸的图像 / Image with faces detected:", path_img), '\n')
- # 因为有可能截下来的人脸再去检测,检测不出来人脸了, 所以要确保是 检测到人脸的人脸图像拿去算特征
- # For photos of faces saved, we need to make sure that we can detect faces from the cropped images
- if len(faces) != 0:
- shape = predictor(img_rd, faces[0])
- face_descriptor = face_reco_model.compute_face_descriptor(img_rd, shape)
- else:
- face_descriptor = 0
- print("no face")
- return face_descriptor
- # 返回 personX 的 128D 特征均值 / Return the mean value of 128D face descriptor for person X
- # Input: path_faces_personX <class 'str'>
- # Output: features_mean_personX <class 'numpy.ndarray'>
- def return_features_mean_personX(path_faces_personX):
- features_list_personX = []
- photos_list = os.listdir(path_faces_personX)
- if photos_list:
- for i in range(len(photos_list)):
- # 调用 return_128d_features() 得到 128D 特征 / Get 128D features for single image of personX
- print("%-40s %-20s" % ("正在读的人脸图像 / Reading image:", path_faces_personX + "/" + photos_list[i]))
- features_128d = return_128d_features(path_faces_personX + "/" + photos_list[i])
- # 遇到没有检测出人脸的图片跳过 / Jump if no face detected from image
- if features_128d == 0:
- i += 1
- else:
- features_list_personX.append(features_128d)
- else:
- print("文件夹内图像文件为空 / Warning: No images in " + path_faces_personX + '/', '\n')
- # 计算 128D 特征的均值 / Compute the mean
- # personX 的 N 张图像 x 128D -> 1 x 128D
- if features_list_personX:
- features_mean_personX = np.array(features_list_personX).mean(axis=0)
- else:
- features_mean_personX = np.zeros(128, dtype=int, order='C')
- print(type(features_mean_personX))
- return features_mean_personX
- # 获取已录入的最后一个人脸序号 / Get the order of latest person
- person_list = os.listdir("data/data_faces_from_camera/")
- person_num_list = []
- for person in person_list:
- person_num_list.append(int(person.split('_')[-1]))
- person_cnt = max(person_num_list)
- with open("data/features_all.csv", "w", newline="") as csvfile:
- writer = csv.writer(csvfile)
- for person in range(person_cnt):
- # Get the mean/average features of face/personX, it will be a list with a length of 128D
- print(path_images_from_camera + "person_" + str(person + 1))
- features_mean_personX = return_features_mean_personX(path_images_from_camera + "person_" + str(person + 1))
- writer.writerow(features_mean_personX)
- print("特征均值 / The mean of features:", list(features_mean_personX))
- print('\n')
- print("所有录入人脸数据存入 / Save all the features of faces registered into: data/features_all.csv")
复制代码
main.py/主程序
- import dlib
- import cv2
- import os
- import time
- import numpy as np
- import pandas as pd
- from PIL import Image
- # 正向人脸检测器
- detector = dlib.get_frontal_face_detector()
- # 特征点检测器
- predictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')
- # 人脸识别模型,提取 128D 的特征矢量
- face_reco_model = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")
- class Face_Recognizer:
- def __init__(self):
- self.feature_known_list = [] # 用来存放所有录入人脸特征的数组
- self.name_known_list = [] # 存储录入人脸名字
- self.current_frame_face_cnt = 0 # 存储当前摄像头中捕获到的人脸数
- self.current_frame_feature_list = [] # 存储当前摄像头中捕获到的人脸特征
- self.current_frame_name_position_list = [] # 存储当前摄像头中捕获到的所有人脸的名字坐标
- self.current_frame_name_list = [] # 存储当前摄像头中捕获到的所有人脸的名字
- # Update FPS
- self.fps = 0
- self.frame_start_time = 0
- # 读取录入人脸特征
- def get_face_database(self):
- if os.path.exists("data/features_all.csv"):
- path_features_known_csv = "data/features_all.csv"
- csv_rd = pd.read_csv(path_features_known_csv, header=None)
- for i in range(csv_rd.shape[0]):
- features_someone_arr = []
- for j in range(0, 128):
- if csv_rd.iloc[i][j] == '':
- features_someone_arr.append('0')
- else:
- features_someone_arr.append(csv_rd.iloc[i][j])
- self.feature_known_list.append(features_someone_arr)
- self.name_known_list.append("Person_"+str(i+1))
- print("人脸库中的人脸数:", len(self.feature_known_list))
- return 1
- else:
- print('##### Warning #####', '\n')
- print("'features_all.csv' not found!")
- print(
- "Please run 'get_faces_from_camera.py' and 'features.py' before 'main.py'",
- '\n')
- print('##### End Warning #####')
- return 0
- # 计算两个128D向量间的欧式距离
- @staticmethod
- def return_euclidean_distance(feature_1, feature_2):
- feature_1 = np.array(feature_1)
- feature_2 = np.array(feature_2)
- dist = np.sqrt(np.sum(np.square(feature_1 - feature_2)))
- return dist
- # 更新 FPS
- def update_fps(self):
- now = time.time()
- self.frame_time = now - self.frame_start_time
- self.fps = 1.0 / self.frame_time
- self.frame_start_time = now
- def draw_note(self, img_rd):
- cv2.putText(img_rd, "Face number: " + str(self.current_frame_face_cnt), (20, 40), cv2.FONT_HERSHEY_SIMPLEX,
- 1, (0, 0, 255), 2, 4)
- for i in range(self.current_frame_face_cnt):
- cv2.putText(img_rd, "person: "+str(self.current_frame_name_list[i]), (20, 120), cv2.FONT_HERSHEY_SIMPLEX,
- 1, (0, 0, 255), 2, 4)
- def draw_name(self, img_rd):
- # 在人脸框下面写人脸名字
- global img_with_name
- img = Image.fromarray(cv2.cvtColor(img_rd, cv2.COLOR_BGR2RGB))
- # draw = ImageDraw.Draw(img)
- for i in range(self.current_frame_face_cnt):
- img_with_name = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
- return img_with_name
- # 修改显示人名
- def show_chinese_name(self):
- # Default known name: person_1, person_2, person_3
- if self.current_frame_face_cnt >= 1:
- self.name_known_list[0] = 'xiaoyu'
- self.name_known_list[1] = 'xiaohui'
- self.name_known_list[2] = 'xiaochen'
- self.name_known_list[3] = 'xiaorui'
- self.name_known_list[4] = 'xiaomu'
- self.name_known_list[5] = 'xiaoman'
- # 处理获取的视频流,进行人脸识别
- def process(self, stream):
- a = 100
- m = 0
- # 1. 读取存放所有人脸特征的 csv
- if self.get_face_database():
- while stream.isOpened():
- flag, img_rd = stream.read()
- faces = detector(img_rd, 0)
- kk = cv2.waitKey(1)
- # 按下 q 键退出
- if kk == ord('q'):
- break
- else:
- self.draw_note(img_rd)
- self.current_frame_feature_list = []
- self.current_frame_face_cnt = 0
- self.current_frame_name_position_list = []
- self.current_frame_name_list = []
- # 2. 检测到人脸
- if len(faces) != 0:
- # 3. 获取当前捕获到的图像的所有人脸的特征
- for i in range(len(faces)):
- shape = predictor(img_rd, faces[i])
- self.current_frame_feature_list.append(face_reco_model.compute_face_descriptor(img_rd, shape))
- # 4. 遍历捕获到的图像中所有的人脸
- for k in range(len(faces)):
- # 先默认所有人不认识,是 unknown
- self.current_frame_name_list.append("unknown")
- # 每个捕获人脸的名字坐标
- self.current_frame_name_position_list.append(tuple(
- [faces[k].left(), int(faces[k].bottom() + (faces[k].bottom() - faces[k].top()) / 4)]))
- # 5. 对于某张人脸,遍历所有存储的人脸特征
- current_frame_e_distance_list = []
- for i in range(len(self.feature_known_list)):
- # 如果 person_X 数据不为空
- if str(self.feature_known_list[i][0]) != '0.0':
- e_distance_tmp = self.return_euclidean_distance(self.current_frame_feature_list[k],
- self.feature_known_list[i])
- # print(e_distance_tmp)
- current_frame_e_distance_list.append(e_distance_tmp)
- else:
- # 空数据 person_X
- current_frame_e_distance_list.append(999999999)
- # 6. 寻找出最小的欧式距离匹配
- similar_person_num = current_frame_e_distance_list.index(min(current_frame_e_distance_list))
- if min(current_frame_e_distance_list) < 0.4:
- self.current_frame_name_list[k] = self.name_known_list[similar_person_num]
- a = similar_person_num
- else:
- a = 7
- n = m
- m = self.face_emotion(stream)
- if a == 0:
- print("识别结果:小玉")
- elif a == 1:
- print("识别结果:小惠")
- elif a == 2:
- print("识别结果:小晨")
- elif a == 3:
- print("识别结果:小锐")
- elif a == 4:
- print("识别结果:小木")
- elif a == 5:
- print("识别结果:小满")
- elif a == 7:
- print("识别结果:陌生人")
- # print(min(current_frame_e_distance_list))
- if m == 1:
- print("表情:正常\n\n")
- cv2.putText(img_rd, "emotion: nature", (20, 80), cv2.FONT_HERSHEY_SIMPLEX,
- 1, (0, 0, 255), 2, 4)
- elif m == 2:
- print("表情:开心\n\n")
- cv2.putText(img_rd, "emotion: happy", (20, 80), cv2.FONT_HERSHEY_SIMPLEX,
- 1, (0, 0, 255), 2, 4)
- elif m == 3:
- print("表情:惊讶\n\n")
- cv2.putText(img_rd, "emotion: amazing", (20, 80), cv2.FONT_HERSHEY_SIMPLEX,
- 1, (0, 0, 255), 2, 4)
- # 矩形框
- for kk, d in enumerate(faces):
- # 绘制矩形框
- cv2.rectangle(img_rd, tuple([d.left(), d.top()]), tuple([d.right(), d.bottom()]),
- (0, 255, 255), 2)
- self.current_frame_face_cnt = len(faces)
- # 7. 在这里更改显示的人名
- self.show_chinese_name()
- # 8. 写名字
- img_with_name = self.draw_name(img_rd)
- else:
- print("无人脸\n")
- img_with_name = img_rd
- cv2.imshow("camera", img_with_name)
- # 9. 更新 FPS
- # self.update_fps()
- print("\n")
- # OpenCV 调用摄像头并进行 process
- def run(self):
- cap = cv2.VideoCapture(0)
- cap.set(3, 480) # 640x480
- self.process(cap)
- cap.release()
- cv2.destroyAllWindows()
- def face_emotion(self, cap):
- a = 0
- line_brow_x = []
- line_brow_y = []
- detector = dlib.get_frontal_face_detector()
- flag, im_rd = cap.read()
- # 每帧数据延时1ms,延时为0读取的是静态帧
- k = cv2.waitKey(1)
- # 取灰度
- img_gray = cv2.cvtColor(im_rd, cv2.COLOR_RGB2GRAY)
- # 使用人脸检测器检测每一帧图像中的人脸。并返回人脸数rects
- faces = detector(img_gray, 0)
- # 待会要显示在屏幕上的字体
- # 如果检测到人脸
- if (len(faces) != 0):
- # 对每个人脸都标出68个特征点
- for i in range(len(faces)):
- # enumerate方法同时返回数据对象的索引和数据,k为索引,d为faces中的对象
- for k, d in enumerate(faces):
- # 用红色矩形框出人脸
- cv2.rectangle(im_rd, (d.left(), d.top()), (d.right(), d.bottom()), (0, 0, 255))
- # 计算人脸热别框边长
- face_width = d.right() - d.left()
- # 使用预测器得到68点数据的坐标
- shape = predictor(im_rd, d)
- # 分析任意n点的位置关系来作为表情识别的依据
- mouth_width = (shape.part(54).x - shape.part(48).x) / face_width # 嘴巴咧开程度
- mouth_higth = (shape.part(66).y - shape.part(62).y) / face_width # 嘴巴张开程度
- # 通过两个眉毛上的10个特征点,分析挑眉程度和皱眉程度
- brow_sum = 0 # 高度之和
- frown_sum = 0 # 两边眉毛距离之和
- for j in range(17, 21):
- brow_sum += (shape.part(j).y - d.top()) + (shape.part(j + 5).y - d.top())
- frown_sum += shape.part(j + 5).x - shape.part(j).x
- line_brow_x.append(shape.part(j).x)
- line_brow_y.append(shape.part(j).y)
- # self.brow_k, self.brow_d = self.fit_slr(line_brow_x, line_brow_y) # 计算眉毛的倾斜程度
- tempx = np.array(line_brow_x)
- tempy = np.array(line_brow_y)
- z1 = np.polyfit(tempx, tempy, 1) # 拟合成一次直线
- # brow_k = -round(float(z1[0]), 4) # 拟合出曲线的斜率和实际眉毛的倾斜方向是相反的
- # print(self.brow_k)
- # brow_hight = (brow_sum / 10) / face_width # 眉毛高度占比
- # brow_width = (frown_sum / 5) / face_width # 眉毛距离占比
- # 眼睛睁开程度
- eye_sum = (shape.part(41).y - shape.part(37).y + shape.part(40).y - shape.part(38).y +
- shape.part(47).y - shape.part(43).y + shape.part(46).y - shape.part(44).y)
- eye_hight = (eye_sum / 4) / face_width
- # print("眼睛睁开距离与识别框高度之比:",round(eye_open/self.face_width,3))
- # 分情况讨论
- # 张嘴,可能是开心或者惊讶
- if round(mouth_higth >= 0.05):
- if eye_hight >= 0.038:
- cv2.putText(im_rd, "amazing", (d.left(), d.bottom() + 20), cv2.FONT_HERSHEY_SIMPLEX,
- 0.8, (0, 0, 255), 2, 4)
- flag_d = 3
- else:
- cv2.putText(im_rd, "happy", (d.left(), d.bottom() + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.8,
- (0, 0, 255), 2, 4)
- flag_d = 2
- # 没有张嘴,可能是正常和生气
- else:
- if mouth_width > 0.39:
- cv2.putText(im_rd, "happy", (d.left(), d.bottom() + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.8,
- (0, 0, 255), 2, 4)
- flag_d = 2
- else:
- cv2.putText(im_rd, "nature", (d.left(), d.bottom() + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.8,
- (0, 0, 255), 2, 4)
- flag_d = 1
- return flag_d
- def main():
- Face_Recognizer_con = Face_Recognizer()
- Face_Recognizer_con.run()
- if __name__ == '__main__':
- main()
复制代码
|