源码出处
http://www.cnblogs.com/AdaminXie
该项目笔记是基于下面博客的摘抄
Python 3 利用 Dlib 实现摄像头实时人脸识别 - coneypo - 博客园 (cnblogs.com)
Python 3 利用 Dlib 实现摄像头人脸检测特征点标定 - coneypo - 博客园 (cnblogs.com)
Python 3 利用 Dlib 实现人脸 68个 特征点的标定 - coneypo - 博客园 (cnblogs.com)
代码源文件
GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# 调用摄像头
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_detection_from_camera
# Created at 2018-02-26
# Updated at 2018-10-09
import cv2
cap = cv2.VideoCapture(0)
# cap.set(propId, value)
# 设置视频参数,propId设置的视频参数,value设置的参数值
cap.set(3, 480)
# cap.isOpened()
# 返回 true/false 检查初始化是否成功
print(cap.isOpened())
# cap.read()
""" 返回两个值
先返回一个布尔值,如果视频读取正确,则为 True,如果错误,则为 False,也可用来判断是否到视频末尾
再返回一个值,为每一帧的图像,该值是一个三维矩阵
通用接收方法为:
ret,frame = cap.read();
这样 ret 存储布尔值,frame 存储图像
若使用一个变量来接收两个值,如
frame = cap.read()
则 frame 为一个元组,原来使用 frame 处需更改为 frame[1]
返回值:R1:布尔值
R2:图像的三维矩阵
"""
while cap.isOpened():
ret_flag, img_rd = cap.read()
cv2.imshow("camera", img_rd)
# 每帧数据延时1ms,延时为0读取的是静态帧
k = cv2.waitKey(1)
# 保存
if k == ord('s'):
cv2.imwrite("test.jpg", img_rd)
# 退出
if k == ord('q'):
break
# 释放所有摄像头
cap.release()
# 删除建立的所有窗口
cv2.destroyAllWindows()
# 对静态人脸图像文件进行68个特征点的标定
# Real-time facial landmarks detection from local image
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_detection_from_camera
import dlib # 人脸处理的库 Dlib
import numpy as np # 数据处理的库 numpy
import cv2 # 图像处理的库 OpenCv
# Dlib 正向人脸检测器 / Use frontal face detector of Dlib
detector = dlib.get_frontal_face_detector()
# Dlib 人脸 landmark 特征点检测器 / Get face landmarks
# predictor = dlib.shape_predictor('data/dlib/shape_predictor_5_face_landmarks.dat')
# Dlib 人脸 landmark 特征点检测器 / Get face landmarks
predictor = dlib.shape_predictor('data/dlib/shape_predictor_68_face_landmarks.dat')
# 读取图像文件
img_rd = cv2.imread("data/samples/face_2.jpg")
img_gray = cv2.cvtColor(img_rd, cv2.COLOR_RGB2GRAY) #转化为灰度图
# 人脸数
#1,0代表将原始图像是否进行放大,1表示放大1倍再检查,提高小人脸的检测效果
faces = detector(img_gray, 0)
# 待会要写的字体
font = cv2.FONT_HERSHEY_SIMPLEX
# 标 68 个点
if len(faces) != 0:
# 检测到人脸
for i in range(len(faces)):
# 取特征点坐标
landmarks = np.matrix([[p.x, p.y] for p in predictor(img_rd, faces[i]).parts()])
for idx, point in enumerate(landmarks):
# 68 点的坐标
pos = (point[0, 0], point[0, 1])
# 利用 cv2.circle 给每个特征点画一个圈,共 68 个
cv2.circle(img_rd, pos, 2, color=(139, 0, 0))
# 利用 cv2.putText 写数字 1-68
cv2.putText(img_rd, str(idx + 1), pos, font, 0.2, (187, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "faces: " + str(len(faces)), (20, 50), font, 1, (0, 0, 0), 1, cv2.LINE_AA)#在对应坐标标记脸的个数
else:
# 没有检测到人脸
cv2.putText(img_rd, "no face", (20, 50), font, 1, (0, 0, 0), 1, cv2.LINE_AA)
# 窗口显示
# 参数取 0 可以拖动缩放窗口,为 1 不可以
# cv2.namedWindow("image", 0)
cv2.namedWindow("image", 1)
cv2.imshow("image", img_rd)#显示处理后的图片
cv2.waitKey(0)
和上一个静态人脸图类似,只是这个图片是一帧一帧会变化的。
多了一个截图,和删除截图的操作。
# 调用摄像头,进行人脸捕获,和68个特征点的追踪
# Real-time facial landmarks detection from camera
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_detection_from_camera
import dlib # 人脸识别的库 Dlib
import numpy as np # 数据处理的库 numpy
import cv2 # 图像处理的库 OpenCv
import time
import os
# 储存截图的目录
path_screenshots = "data/screenshots/"
# Dlib 正向人脸检测器 / Use frontal face detector of Dlib
detector = dlib.get_frontal_face_detector()
# Dlib 人脸 landmark 特征点检测器 / Get face landmarks
# predictor = dlib.shape_predictor('data/dlib/shape_predictor_5_face_landmarks.dat')
# Dlib 人脸 landmark 特征点检测器 / Get face landmarks
predictor = dlib.shape_predictor('data/dlib/shape_predictor_68_face_landmarks.dat')
# cap = cv2.VideoCapture("head-pose-face-detection-male.mp4") # 输入视频
cap = cv2.VideoCapture("1") # 输入摄像头
cap.set(3, 480)
screenshot_cnt = 0
# Delete all the screenshots 删除所有截图
# 遍历,一个一个删除
def clear_screenshots():
ss = os.listdir("data/screenshots/")
for image in ss:
print("Remove: ", "data/screenshots/"+image)
os.remove("data/screenshots/"+image)
# clear_screenshots()
while cap.isOpened():
flag, im_rd = cap.read()
k = cv2.waitKey(1)
img_gray = cv2.cvtColor(im_rd, cv2.COLOR_RGB2GRAY) #灰度图
faces = detector(img_gray, 0) #人脸数
# 待会要写的字体
font = cv2.FONT_HERSHEY_SIMPLEX
# 检测到人脸
if len(faces) != 0:
for i in range(len(faces)):
landmarks = np.matrix([[p.x, p.y] for p in predictor(im_rd, faces[i]).parts()])
# 标 68 个点
for idx, point in enumerate(landmarks):
# 68点的坐标
pos = (point[0, 0], point[0, 1])
# 特征点画圈
cv2.circle(im_rd, pos, 2, color=(255, 255, 255))
# 写1-68
cv2.putText(im_rd, str(idx + 1), pos, font, 0.2, (187, 255, 255), 1, cv2.LINE_AA)
cv2.putText(im_rd, "Faces: " + str(len(faces)), (20, 50), font, 1, (255, 255, 255), 1, cv2.LINE_AA)
else:
# 没有检测到人脸
cv2.putText(im_rd, "No face detected", (20, 50), font, 1, (0, 0, 0), 1, cv2.LINE_AA)
# 添加说明
im_rd = cv2.putText(im_rd, "'S': screen shot", (20, 400), font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
im_rd = cv2.putText(im_rd, "'Q': quit", (20, 450), font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
# 按下 's' 键保存
#截图数,然后设置输出的图片命名
if k == ord('s'):
screenshot_cnt += 1
print(path_screenshots + "ss_" + str(screenshot_cnt) + "_" +
time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) + ".jpg")
cv2.imwrite(path_screenshots + "ss_" + str(screenshot_cnt) + "_" +
time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) + ".jpg", im_rd)
# 按下 'q' 键退出
if k == ord('q'):
break
# 窗口显示
# 参数取 0 可以拖动缩放窗口,为 1 不可以
# cv2.namedWindow("camera", 0)
cv2.namedWindow("camera", 1)
cv2.imshow("camera", im_rd)
# 释放摄像头
cap.release()
# 删除建立的窗口
cv2.destroyAllWindows()
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# Mail: [email protected]
# 进行人脸录入 / Face register
import dlib
import numpy as np
import cv2
import os
import shutil
import time
import logging
# 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/" #选择路径
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
self.fps_show = 0
self.start_time = time.time()
# 新建保存人脸图像文件和数据 CSV 文件夹 / Mkdir for saving photos and csv
def pre_work_mkdir(self):
# 新建文件夹 / Create folders to save face images and csv
if os.path.isdir(self.path_photos_from_camera):
pass
else:
os.mkdir(self.path_photos_from_camera)
# 删除之前存的人脸数据文件夹 / Delete old face folders
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/"):
# 获取已录入的最后一个人脸序号 / 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]))
self.existing_faces_cnt = max(person_num_list)
# 如果第一次存储或者没有之前录入的人脸, 按照 person_1 开始录入 / Start from person_1
else:
self.existing_faces_cnt = 0
# 更新 FPS / Update FPS of Video stream
def update_fps(self):
now = time.time() #获取现在的时间戳
# 每秒刷新 fps / Refresh fps per second
if str(self.start_time).split(".")[0] != str(now).split(".")[0]:
self.fps_show = self.fps
self.start_time = now #记录开始的时间
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_show.__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
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)
logging.info("\n%-40s %s", "新建的人脸文件夹 / 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'):
logging.warning("请调整位置 / Please adjust your position")
else:
color_rectangle = (255, 255, 255)
save_flag = 1
#参数1:图片 参数2:起始坐标 参数3:结束坐标 参数4:颜色 参数5:矩形框的粗细像素
#坐标为元组格式 起始坐标:左上角 结束坐标:右下角
# -ww -hh +ww +hh 目的是把矩形框画大一些,把整张脸都包住
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
#np.unit8 是专门用来存储图片的格式,包含灰度图和RGB等
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)
logging.info("%-40s %s/img_face_%s.jpg", "写入本地 / Save into:", #在终端说明信息
str(current_face_dir), str(self.ss_cnt))
else:
logging.warning("请先按 '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("video.mp4") # Get video stream from video file
cap = cv2.VideoCapture(0) # Get video stream from camera
self.process(cap)
#释放视频流
cap.release()
#删除建立窗口
cv2.destroyAllWindows()
def main():
# logging 模块是 Python 内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等
logging.basicConfig(level=logging.INFO)
Face_Register_con = Face_Register()
Face_Register_con.run()
if __name__ == '__main__':
main()
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# Mail: [email protected]
# 人脸录入 Tkinter GUI / Face register GUI with tkinter
import dlib
import numpy as np
import cv2
import os
import shutil
import time
import logging
import tkinter as tk
from tkinter import font as tkFont
from PIL import Image, ImageTk
# Dlib 正向人脸检测器 / Use frontal face detector of Dlib
detector = dlib.get_frontal_face_detector()
class Face_Register:
def __init__(self):
self.current_frame_faces_cnt = 0 # 当前帧中人脸计数器 / cnt for counting faces in current frame
self.existing_faces_cnt = 0 # 已录入的人脸计数器 / cnt for counting saved faces
self.ss_cnt = 0 # 录入 person_n 人脸时图片计数器 / cnt for screen shots
# Tkinter GUI
self.win = tk.Tk() #实例化
self.win.title("Face Register @coneypo") #设置标题
# PLease modify window size here if needed
self.win.geometry("1300x550") #设置窗口大小
# GUI left part 左边部分
self.frame_left_camera = tk.Frame(self.win) #设置摄像头的显示框
self.label = tk.Label(self.win) #设置一个lable
self.label.pack(side=tk.LEFT) #设置label在左边
self.frame_left_camera.pack() #将摄像头显示框设置在左边
# GUI right part
self.frame_right_info = tk.Frame(self.win) #设置右边的框
self.label_cnt_face_in_database = tk.Label(self.frame_right_info, text=str(self.existing_faces_cnt)) #显示人脸个数
self.label_fps_info = tk.Label(self.frame_right_info, text="") #显示fps
self.input_name = tk.Entry(self.frame_right_info) #设置文本框
self.input_name_char = "" #设置名字
self.label_warning = tk.Label(self.frame_right_info) #设置警告
self.label_face_cnt = tk.Label(self.frame_right_info, text="Faces in current frame: ") #当前的人脸个数
self.log_all = tk.Label(self.frame_right_info) #显示日志信息
self.font_title = tkFont.Font(family='Helvetica', size=20, weight='bold') #设置标题大小
self.font_step_title = tkFont.Font(family='Helvetica', size=15, weight='bold') #step二级标题大小
self.font_warning = tkFont.Font(family='Helvetica', size=15, weight='bold') #设置警告字体大小
self.path_photos_from_camera = "data/data_faces_from_camera/" #存放图片的路径
self.current_face_dir = ""
self.font = cv2.FONT_ITALIC #设置字体
# Current frame and face ROI position 当前帧和面对ROI的位置
self.current_frame = np.ndarray #设置存放统一类型的多维数组
self.face_ROI_image = np.ndarray #提前定义好参数
self.face_ROI_width_start = 0
self.face_ROI_height_start = 0
self.face_ROI_width = 0
self.face_ROI_height = 0
self.ww = 0
self.hh = 0
self.out_of_range_flag = False #判断有没有超出范围
self.face_folder_created_flag = False #判断有没有创建文件夹
# FPS
self.frame_time = 0
self.frame_start_time = 0
self.fps = 0
self.fps_show = 0
self.start_time = time.time()
self.cap = cv2.VideoCapture(0) # Get video stream from camera 获得摄像头
# self.cap = cv2.VideoCapture("test.mp4") # Input local video
# 删除之前存的人脸数据文件夹 / Delete old face folders
def GUI_clear_data(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")
self.label_cnt_face_in_database['text'] = "0" #修改库中存放的人脸个数为0
self.existing_faces_cnt = 0 #更新记录人脸个数的数据
self.log_all["text"] = "Face images and `features_all.csv` removed!" #修改日志信息,提示已删除内容
def GUI_get_input_name(self): #创建指定名字的文件夹
self.input_name_char = self.input_name.get()
self.create_face_folder() #调用创建文件夹函数
self.label_cnt_face_in_database['text'] = str(self.existing_faces_cnt) #重新设置存在数据库的人脸数据
def GUI_info(self): #GUI初始化 grid 网格控件
tk.Label(self.frame_right_info, #设置标题
text="Face register",
font=self.font_title).grid(row=0, column=0, columnspan=3, sticky=tk.W, padx=2, pady=20)
tk.Label(self.frame_right_info, #设置fps
text="FPS: ").grid(row=1, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2)
self.label_fps_info.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2)
tk.Label(self.frame_right_info, #设置显示计入不同人脸个数
text="Faces in database: ").grid(row=2, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2)
self.label_cnt_face_in_database.grid(row=2, column=2, columnspan=3, sticky=tk.W, padx=5, pady=2)
tk.Label(self.frame_right_info, #设置显示当前摄像头识别的人脸个数
text="Faces in current frame: ").grid(row=3, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2)
self.label_face_cnt.grid(row=3, column=2, columnspan=3, sticky=tk.W, padx=5, pady=2)
self.label_warning.grid(row=4, column=0, columnspan=3, sticky=tk.W, padx=5, pady=2)
# Step 1: Clear old data 第一步的操作
tk.Label(self.frame_right_info, #设置label
font=self.font_step_title, #设置文字格式 下面是坐标
text="Step 1: Clear face photos").grid(row=5, column=0, columnspan=2, sticky=tk.W, padx=5, pady=20)
tk.Button(self.frame_right_info, #设置button
text='Clear', #调用clear函数
command=self.GUI_clear_data).grid(row=6, column=0, columnspan=3, sticky=tk.W, padx=5, pady=2)
# Step 2: Input name and create folders for face 第二步操作 输入名字和创建存放人脸的文件夹
tk.Label(self.frame_right_info, #添加文字
font=self.font_step_title,
text="Step 2: Input name").grid(row=7, column=0, columnspan=2, sticky=tk.W, padx=5, pady=20)
tk.Label(self.frame_right_info, text="Name: ").grid(row=8, column=0, sticky=tk.W, padx=5, pady=0)
self.input_name.grid(row=8, column=1, sticky=tk.W, padx=0, pady=2)
tk.Button(self.frame_right_info, #调用input_name函数
text='Input',
command=self.GUI_get_input_name).grid(row=8, column=2, padx=5)
# Step 3: Save current face in frame
tk.Label(self.frame_right_info, #设置文字
font=self.font_step_title,
text="Step 3: Save face image").grid(row=9, column=0, columnspan=2, sticky=tk.W, padx=5, pady=20)
tk.Button(self.frame_right_info, #调用保存图片的函数
text='Save current face',
command=self.save_current_face).grid(row=10, column=0, columnspan=3, sticky=tk.W)
# Show log in GUI #显示日志的GUI
self.log_all.grid(row=11, column=0, columnspan=20, sticky=tk.W, padx=5, pady=20)
self.frame_right_info.pack() #将控件放置在父控件内之前,规划此控件在区块内的位置
# 新建保存人脸图像文件和数据 CSV 文件夹 / Mkdir for saving photos and csv
def pre_work_mkdir(self):
# 新建文件夹 / Create folders to save face images and csv
if os.path.isdir(self.path_photos_from_camera):
pass #不做任何事,单纯一个空语句
else:
os.mkdir(self.path_photos_from_camera)
# 如果有之前录入的人脸, 在之前 person_x 的序号按照 person_x+1 开始录入 / Start from person_x+1
def check_existing_faces_cnt(self):
if os.listdir("data/data_faces_from_camera/"):
# 获取已录入的最后一个人脸序号 / Get the order of latest person
person_list = os.listdir("data/data_faces_from_camera/")
person_num_list = []
for person in person_list:
person_order = person.split('_')[1].split('_')[0] #截取中间的字符串
person_num_list.append(int(person_order)) #转化为int,添加到数组
self.existing_faces_cnt = max(person_num_list) #选择最大的数字
# 如果第一次存储或者没有之前录入的人脸, 按照 person_1 开始录入 / Start from person_1
else:
self.existing_faces_cnt = 0
# 更新 FPS / Update FPS of Video stream
def update_fps(self):
now = time.time()
# 每秒刷新 fps / Refresh fps per second
if str(self.start_time).split(".")[0] != str(now).split(".")[0]:
self.fps_show = self.fps
self.start_time = now
self.frame_time = now - self.frame_start_time
self.fps = 1.0 / self.frame_time
self.frame_start_time = now
self.label_fps_info["text"] = str(self.fps.__round__(2))
def create_face_folder(self):
# 新建存储人脸的文件夹 / Create the folders for saving faces
self.existing_faces_cnt += 1
if self.input_name_char: #设置一下命名格式(文本框内有输入内容)
self.current_face_dir = self.path_photos_from_camera + \
"person_" + str(self.existing_faces_cnt) + "_" + \
self.input_name_char
else: #设置一下命名格式(文本框内没有输入内容)
self.current_face_dir = self.path_photos_from_camera + \
"person_" + str(self.existing_faces_cnt)
os.makedirs(self.current_face_dir)
self.log_all["text"] = "\"" + self.current_face_dir + "/\" created!" #显示记录的信息
logging.info("\n%-40s %s", "新建的人脸文件夹 / Create folders:", self.current_face_dir)
self.ss_cnt = 0 # 将人脸计数器清零 / Clear the cnt of screen shots
self.face_folder_created_flag = True # Face folder already created 记录确定已经创建好文件夹
def save_current_face(self): #保存人脸图片主函数
if self.face_folder_created_flag: #确定文件夹已经创建
if self.current_frame_faces_cnt == 1: #检测识别的人脸是不是只有一位
if not self.out_of_range_flag: #如果检测框没有超出范围
self.ss_cnt += 1 #人脸计数器+1 下面截取框内数据,填充数据
# 根据人脸大小生成空的图像 / Create blank image according to the size of face detected
self.face_ROI_image = np.zeros((int(self.face_ROI_height * 2), self.face_ROI_width * 2, 3),
np.uint8)
for ii in range(self.face_ROI_height * 2): #这边就是遍历所有像素点进行赋值
for jj in range(self.face_ROI_width * 2):
self.face_ROI_image[ii][jj] = self.current_frame[self.face_ROI_height_start - self.hh + ii][
self.face_ROI_width_start - self.ww + jj]
self.log_all["text"] = "\"" + self.current_face_dir + "/img_face_" + str( #日志的说明,保存成功
self.ss_cnt) + ".jpg\"" + " saved!"
self.face_ROI_image = cv2.cvtColor(self.face_ROI_image, cv2.COLOR_BGR2RGB) #选用的图像格式是RGB
cv2.imwrite(self.current_face_dir + "/img_face_" + str(self.ss_cnt) + ".jpg", self.face_ROI_image)
logging.info("%-40s %s/img_face_%s.jpg", "写入本地 / Save into:", #日志说明写入在了哪里
str(self.current_face_dir), str(self.ss_cnt) + ".jpg")
else:
self.log_all["text"] = "Please do not out of range!" #提示不要超出范围
else:
self.log_all["text"] = "No face in current frame!" #提示没有检测到人脸
else:
self.log_all["text"] = "Please run step 2!" #提示首先要创建文件夹
def get_frame(self): #获取摄像头视频,也可以是video
try:
if self.cap.isOpened(): #如果检测到打开,就进行一个读取
ret, frame = self.cap.read()
return ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) #参数1:有没有读到数据,参数2:截取到的一帧图片
except:
print("Error: No video input!!!") #提示没有检测到视频
# 获取人脸 / Main process of face detection and saving 这里是项目的主要的函数
def process(self):
ret, self.current_frame = self.get_frame() #连接到摄像头
faces = detector(self.current_frame, 0) #运用模型,检测人脸个数
# Get frame
if ret: #如果为true
self.update_fps() #更新fps
self.label_face_cnt["text"] = str(len(faces)) #设置人脸个数
# 检测到人脸 / Face detected
if len(faces) != 0:
# 矩形框 / Show the ROI of faces #设置矩形框
for k, d in enumerate(faces):
self.face_ROI_width_start = d.left()
self.face_ROI_height_start = d.top()
# 计算矩形框大小 / Compute the size of rectangle box
self.face_ROI_height = (d.bottom() - d.top())
self.face_ROI_width = (d.right() - d.left())
self.hh = int(self.face_ROI_height / 2)
self.ww = int(self.face_ROI_width / 2)
# 判断人脸矩形框是否超出 480x640 / If the size of ROI > 480x640
if (d.right() + self.ww) > 640 or (d.bottom() + self.hh > 480) or (d.left() - self.ww < 0) or (
d.top() - self.hh < 0):
self.label_warning["text"] = "OUT OF RANGE" #要修改警告
self.label_warning['fg'] = 'red' #设置为红色字体颜色
self.out_of_range_flag = True #标记 超出范围
color_rectangle = (255, 0, 0)
else:
self.out_of_range_flag = False
self.label_warning["text"] = "" #否则,警告为空
color_rectangle = (255, 255, 255)
self.current_frame = cv2.rectangle(self.current_frame, #绘画矩形框
tuple([d.left() - self.ww, d.top() - self.hh]),
tuple([d.right() + self.ww, d.bottom() + self.hh]),
color_rectangle, 2)
self.current_frame_faces_cnt = len(faces) #记当前录人脸数
# Convert PIL.Image.Image to PIL.Image.PhotoImage #进行一个图片格式转换,否则会报错显示不出来
img_Image = Image.fromarray(self.current_frame) #实现array到image的转换
img_PhotoImage = ImageTk.PhotoImage(image=img_Image)
self.label.img_tk = img_PhotoImage
self.label.configure(image=img_PhotoImage)
# Refresh frame
self.win.after(20, self.process) #更新帧,以毫秒为单位
def run(self):
self.pre_work_mkdir() #新建一个文件夹
self.check_existing_faces_cnt() #检查文件夹内的人脸文件
self.GUI_info() #初始化GUI
self.process() #执行主函数,获取人脸
self.win.mainloop() #接收操作系统发来的事件,然后把事件分发给各个控件和窗体
def main():
logging.basicConfig(level=logging.INFO)
Face_Register_con = Face_Register()
Face_Register_con.run()
if __name__ == '__main__':
main()
features_all.csv 是一个 n 行 129 列的 CSV, n 是录入的人脸数,129 列是某人的名字加上 128D 特征(如果没有设置名字,那么就是 person_1
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# Mail: [email protected]
# 从人脸图像文件中提取人脸特征存入 "features_all.csv" / Extract features from images and save into "features_all.csv"
import os
import dlib
import csv
import numpy as np
import logging
import cv2
# 要读取人脸图像文件的路径 / 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
# Output: face_descriptor
def return_128d_features(path_img):
img_rd = cv2.imread(path_img) #获取图片
faces = detector(img_rd, 1) #进行模型识别处理,再确认一下
logging.info("%-40s %-20s", "检测到人脸的图像 / Image with faces detected:", path_img) #日志记录,检测到人脸图像
# 因为有可能截下来的人脸再去检测,检测不出来人脸了, 所以要确保是 检测到人脸的人脸图像拿去算特征
# 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]) #对人脸进行68点位特征检测
face_descriptor = face_reco_model.compute_face_descriptor(img_rd, shape) #对该图片进行128特征点位检测
else:
face_descriptor = 0 #检测不到人脸,提示信息
logging.warning("no face")
return face_descriptor #返回特征点信息
# 返回 personX 的 128D 特征均值 / Return the mean value of 128D face descriptor for person X
# Input: path_face_personX
# Output: features_mean_personX
def return_features_mean_personX(path_face_personX):
features_list_personX = []
photos_list = os.listdir(path_face_personX)
if photos_list:
for i in range(len(photos_list)):
# 调用 return_128d_features() 得到 128D 特征 / Get 128D features for single image of personX
logging.info("%-40s %-20s", "正在读的人脸图像 / Reading image:", path_face_personX + "/" + photos_list[i])
features_128d = return_128d_features(path_face_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:
logging.warning("文件夹内图像文件为空 / Warning: No images in%s/", path_face_personX)
# 计算 128D 特征的均值 / Compute the mean
# personX 的 N 张图像 x 128D -> 1 x 128D
if features_list_personX:
features_mean_personX = np.array(features_list_personX, dtype=object).mean(axis=0)
else:
features_mean_personX = np.zeros(128, dtype=object, order='C')
return features_mean_personX
def main():
logging.basicConfig(level=logging.INFO) #计入日志中
# 获取已录入的最后一个人脸序号 / Get the order of latest person
person_list = os.listdir("data/data_faces_from_camera/") #存放人脸图片的路径
person_list.sort() #对原列表进行排序
with open("data/features_all.csv", "w", newline="") as csvfile: #创建csv格式的文件
writer = csv.writer(csvfile) #向其内部写入文件
for person in person_list: #遍历所有人
# Get the mean/average features of face/personX, it will be a list with a length of 128D
logging.info("%sperson_%s", path_images_from_camera, person) #将该操作记入在日志中
features_mean_personX = return_features_mean_personX(path_images_from_camera + person) #调用 返回特征均值函数
if len(person.split('_', 2)) == 2: #split中的2 是分割2次的意思
# "person_x" #这样判断,文件人名是什么样的格式
person_name = person
else:
# "person_x_tom"
person_name = person.split('_', 2)[-1]
features_mean_personX = np.insert(features_mean_personX, 0, person_name, axis=0) #此时,将输入插入其中,垂直向下插入
# features_mean_personX will be 129D, person name + 128 features
writer.writerow(features_mean_personX) #写入在csv文件中
logging.info('\n') #记录日志
logging.info("所有录入人脸数据存入 / Save all the features of faces registered into: data/features_all.csv")
if __name__ == '__main__':
main()
伪代码如下
# 人脸检测器/预测器/识别模型
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')
facerec = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")
faces = detector(img_gray, 0)
# 1. 如果检测到人脸
if len(faces) != 0:
# 遍历所有检测到的人脸
for i in range(len(faces)):
# 2. 提取当前帧人脸的特征描述子
shape = predictor(img_rd, faces[i])
facerec.compute_face_descriptor(img_rd, shape)
# 3. 将当前帧人脸特征描述子和数据库的特征描述子进行对比
for i in range(len(self.features_known_list)):
e_distance_tmp = self.return_euclidean_distance(self.features_camera_list[k], self.features_known_list[i])
文件代码如下
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# Mail: [email protected]
# 摄像头实时人脸识别 / Real-time face detection and recognition
import dlib
import numpy as np
import cv2
import pandas as pd
import os
import time
import logging
from PIL import Image, ImageDraw, ImageFont
# 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")
class Face_Recognizer:
def __init__(self):
self.face_feature_known_list = [] # 用来存放所有录入人脸特征的数组 / Save the features of faces in database
self.face_name_known_list = [] # 存储录入人脸名字 / Save the name of faces in database
self.current_frame_face_cnt = 0 # 存储当前摄像头中捕获到的人脸数 / Counter for faces in current frame
self.current_frame_face_feature_list = [] # 存储当前摄像头中捕获到的人脸特征 / Features of faces in current frame
self.current_frame_face_name_list = [] # 存储当前摄像头中捕获到的所有人脸的名字 / Names of faces in current frame
self.current_frame_face_name_position_list = [] # 存储当前摄像头中捕获到的所有人脸的名字坐标 / Positions of faces in current frame
# Update FPS
self.fps = 0 # FPS of current frame
self.fps_show = 0 # FPS per second
self.frame_start_time = 0
self.frame_cnt = 0
self.start_time = time.time()
self.font = cv2.FONT_ITALIC #设置字体
self.font_chinese = ImageFont.truetype("simsun.ttc", 30)
# 从 "features_all.csv" 读取录入人脸特征 / Read known faces from "features_all.csv"
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 = []
self.face_name_known_list.append(csv_rd.iloc[i][0]) #存放对应人脸名字 iloc类似于数组,这一个是记坐标数,下loc是记坐标名
for j in range(1, 129):
if csv_rd.iloc[i][j] == '':
features_someone_arr.append('0')
else:
features_someone_arr.append(csv_rd.iloc[i][j])
self.face_feature_known_list.append(features_someone_arr)
logging.info("Faces in Database:%d", len(self.face_feature_known_list)) #日志:记录有多少个特征
return 1
else:
logging.warning("'features_all.csv' not found!") #如果不存在,提示错误,并给出提示
logging.warning("Please run 'get_faces_from_camera.py' "
"and 'features_extraction_to_csv.py' before 'face_reco_from_camera.py'")
return 0
# 计算两个128D向量间的欧式距离 / Compute the e-distance between two 128D features
# staticmethod用于修饰类中的方法,使其可以在不创建类实例的情况下调用方法,这样做的好处是执行效率比较高。
@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 / Update FPS of Video stream
def update_fps(self):
now = time.time()
# 每秒刷新 fps / Refresh fps per second
if str(self.start_time).split(".")[0] != str(now).split(".")[0]:
self.fps_show = self.fps
self.start_time = now
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):
cv2.putText(img_rd, "Face Recognizer", (20, 40), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "Frame: " + str(self.frame_cnt), (20, 100), self.font, 0.8, (0, 255, 0), 1, #处理的帧的数量
cv2.LINE_AA)
cv2.putText(img_rd, "FPS: " + str(self.fps_show.__round__(2)), (20, 130), self.font, 0.8, (0, 255, 0), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "Faces: " + str(self.current_frame_face_cnt), (20, 160), self.font, 0.8, (0, 255, 0), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "Q: Quit", (20, 450), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
def draw_name(self, img_rd):
# 在人脸框下面写人脸名字 / Write names under rectangle
img = Image.fromarray(cv2.cvtColor(img_rd, cv2.COLOR_BGR2RGB)) #实现array到image的转换
draw = ImageDraw.Draw(img) #绘图声明
for i in range(self.current_frame_face_cnt): #遍历摄像头识别的人脸
# cv2.putText(img_rd, self.current_frame_face_name_list[i], self.current_frame_face_name_position_list[i], self.font, 0.8, (0, 255, 255), 1, cv2.LINE_AA)
draw.text(xy=self.current_frame_face_name_position_list[i], text=self.current_frame_face_name_list[i], font=self.font_chinese,
fill=(255, 255, 0)) #参数1 坐标 参数2 人脸的名字 参数3 设置字体 参数4设置颜色
img_rd = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) #将处理好的图片,重新转换成新的图片
return img_rd
# 修改显示人名(支持中文) / Show names in chinese
def show_chinese_name(self):
# Default known name: person_1, person_2, person_3
if self.current_frame_face_cnt >= 1:
# 修改录入的人脸姓名 / Modify names in face_name_known_list to chinese name
self.face_name_known_list[0] = '张三'.encode('utf-8').decode()
# self.face_name_known_list[1] = '张四'.encode('utf-8').decode()
# 处理获取的视频流,进行人脸识别 / Face detection and recognition from input video stream
def process(self, stream):
# 1. 读取存放所有人脸特征的 csv / Read known faces from "features.all.csv"
if self.get_face_database():
while stream.isOpened():
self.frame_cnt += 1 #每处理一次,记一次数
logging.debug("Frame %d starts", self.frame_cnt) #记录在日志内
flag, img_rd = stream.read() #读取视频流
faces = detector(img_rd, 0) #获取人脸
kk = cv2.waitKey(1) #记录按了什么按键
# 按下 q 键退出 / Press 'q' to quit
if kk == ord('q'):
break
else:
self.draw_note(img_rd) #调用draw函数,生成文字
self.current_frame_face_feature_list = [] #存储当前摄像头中捕获到的人脸特征
self.current_frame_face_cnt = 0 #存储当前摄像头中捕获到的人脸数
self.current_frame_face_name_position_list = [] #存储当前摄像头中捕获到的所有人脸的名字坐标
self.current_frame_face_name_list = [] #
# 2. 检测到人脸 / Face detected in current frame
if len(faces) != 0:
# 3. 获取当前捕获到的图像的所有人脸的特征 / Compute the face descriptors for faces in current frame
for i in range(len(faces)):
shape = predictor(img_rd, faces[i])
self.current_frame_face_feature_list.append(face_reco_model.compute_face_descriptor(img_rd, shape))
# 4. 遍历捕获到的图像中所有的人脸 / Traversal all the faces in the database
for k in range(len(faces)):
logging.debug("For face %d in camera:", k+1)
# 先默认所有人不认识,是 unknown / Set the default names of faces with "unknown"
self.current_frame_face_name_list.append("unknown")
# 每个捕获人脸的名字坐标 / Positions of faces captured
self.current_frame_face_name_position_list.append(tuple(
[faces[k].left(), int(faces[k].bottom() + (faces[k].bottom() - faces[k].top()) / 4)])) #设置一下坐标
# 5. 对于某张人脸,遍历所有存储的人脸特征
# For every faces detected, compare the faces in the database
current_frame_e_distance_list = []
for i in range(len(self.face_feature_known_list)): #遍历存储的表格特征
# 如果 person_X 数据不为空
if str(self.face_feature_known_list[i][0]) != '0.0': #计算它们的欧氏距离
e_distance_tmp = self.return_euclidean_distance(self.current_frame_face_feature_list[k],
self.face_feature_known_list[i])
logging.debug(" With person %s, the e-distance is %f", str(i + 1), e_distance_tmp) #记录在日志内
current_frame_e_distance_list.append(e_distance_tmp) #添加数据
else:
# 空数据 person_X
current_frame_e_distance_list.append(999999999)
# 6. 寻找出最小的欧式距离匹配 / Find the one with minimum e-distance
similar_person_num = current_frame_e_distance_list.index(min(current_frame_e_distance_list))
logging.debug("Minimum e-distance with %s: %f", self.face_name_known_list[similar_person_num], min(current_frame_e_distance_list))
if min(current_frame_e_distance_list) < 0.4: #设置判断的阈值
self.current_frame_face_name_list[k] = self.face_name_known_list[similar_person_num]
logging.debug("Face recognition result: %s", self.face_name_known_list[similar_person_num])
else:
logging.debug("Face recognition result: Unknown person")
logging.debug("\n")
# 矩形框 / Draw rectangle
for kk, d in enumerate(faces):
# 绘制矩形框
cv2.rectangle(img_rd, tuple([d.left(), d.top()]), tuple([d.right(), d.bottom()]),
(255, 255, 255), 2)
self.current_frame_face_cnt = len(faces)
# 7. 在这里更改显示的人名 / Modify name if needed
# self.show_chinese_name()
# 8. 写名字 / Draw name
img_with_name = self.draw_name(img_rd)
else:
img_with_name = img_rd
logging.debug("Faces in camera now: %s", self.current_frame_face_name_list)
cv2.imshow("camera", img_with_name)
# 9. 更新 FPS / Update stream FPS
self.update_fps()
logging.debug("Frame ends\n\n")
# OpenCV 调用摄像头并进行 process
def run(self):
# cap = cv2.VideoCapture("video.mp4") # Get video stream from video file
cap = cv2.VideoCapture(0) # Get video stream from camera
cap.set(3, 480) # 640x480
self.process(cap)
cap.release() #释放视频流
cv2.destroyAllWindows() #关闭所有界面
def main():
# logging.basicConfig(level=logging.DEBUG) # Set log level to 'logging.DEBUG' to print debug info of every frame
logging.basicConfig(level=logging.INFO)
Face_Recognizer_con = Face_Recognizer()
Face_Recognizer_con.run()
if __name__ == '__main__':
main()
为解决上述代码,计算量过大,导致fps过低,摄像头识别卡顿问题。
所有源码分析是对下面博客的摘抄
利用目标跟踪来提高实时人脸识别处理速度 - coneypo - 博客园 (cnblogs.com)
目标追踪(Object Tracking)概念的简要介绍 - coneypo - 博客园 (cnblogs.com)
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# Mail: [email protected]
# 单张人脸实时识别 / Real-time face detection and recognition for single face
# 检测 -> 识别人脸, 新人脸出现 -> 再识别, 不会对于每一帧都进行识别 / Do detection -> recognize face, new face -> do re-recognition
# 其实对于单张人脸, 不需要 OT 进行跟踪, 对于新出现的人脸, 再识别一次就好了 / No OT here, OT will be used only for multi faces
import dlib
import numpy as np
import cv2
import os
import pandas as pd
import time
from PIL import Image, ImageDraw, ImageFont
import logging
# 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")
#人脸检测器(类)
class Face_Recognizer:
def __init__(self): #初始化
self.font = cv2.FONT_ITALIC #设置字体
self.font_chinese = ImageFont.truetype("simsun.ttc", 30)
# 统计 FPS / For FPS
self.frame_time = 0
self.frame_start_time = 0
self.fps = 0
self.fps_show = 0
self.start_time = time.time()
# 统计帧数 / cnt for frame
self.frame_cnt = 0
# 用来存储所有录入人脸特征的数组 / Save the features of faces in the database
self.features_known_list = []
# 用来存储录入人脸名字 / Save the name of faces in the database
self.face_name_known_list = []
# 用来存储上一帧和当前帧 ROI 的质心坐标 / List to save centroid positions of ROI in frame N-1 and N
self.last_frame_centroid_list = [] #ROI:感兴趣区域
self.current_frame_centroid_list = []
# 用来存储当前帧检测出目标的名字 / List to save names of objects in current frame
self.current_frame_name_list = []
# 上一帧和当前帧中人脸数的计数器 / cnt for faces in frame N-1 and N
self.last_frame_faces_cnt = 0
self.current_frame_face_cnt = 0
# 用来存放进行识别时候对比的欧氏距离 / Save the e-distance for faceX when recognizing
self.current_frame_face_X_e_distance_list = []
# 存储当前摄像头中捕获到的所有人脸的坐标名字 / Save the positions and names of current faces captured
self.current_frame_face_position_list = []
# 存储当前摄像头中捕获到的人脸特征 / Save the features of people in current frame
self.current_frame_face_feature_list = []
# 控制再识别的后续帧数 / Reclassify after 'reclassify_interval' frames
# 如果识别出 "unknown" 的脸, 将在 reclassify_interval_cnt 计数到 reclassify_interval 后, 对于人脸进行重新识别
self.reclassify_interval_cnt = 0
self.reclassify_interval = 10
# 从 "features_all.csv" 读取录入人脸特征 / Get known faces from "features_all.csv"
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) #读取csv文件
for i in range(csv_rd.shape[0]): #读取有几行
features_someone_arr = []
self.face_name_known_list.append(csv_rd.iloc[i][0]) #存入人脸的名字
for j in range(1, 129):
if csv_rd.iloc[i][j] == '':
features_someone_arr.append('0')
else:
features_someone_arr.append(csv_rd.iloc[i][j]) #将特征值存入零时创建的变量,二维数组
self.features_known_list.append(features_someone_arr) #将二维数组,存入在特征集中,每一行数据代表一个人的特征集
logging.info("Faces in Database: %d", len(self.features_known_list))
return 1
else: #文件不存在时的一个警告和提示
logging.warning("'features_all.csv' not found!")
logging.warning("Please run 'get_faces_from_camera.py' "
"and 'features_extraction_to_csv.py' before 'face_reco_from_camera.py'")
return 0
# 获取处理之后 stream 的帧数 / Update FPS of video stream
def update_fps(self):
now = time.time()
# 每秒刷新 fps / Refresh fps per second
if str(self.start_time).split(".")[0] != str(now).split(".")[0]:
self.fps_show = self.fps
self.start_time = now
self.frame_time = now - self.frame_start_time
self.fps = 1.0 / self.frame_time
self.frame_start_time = now
# 计算两个128D向量间的欧式距离 / Compute the e-distance between two 128D features
@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))) #计算两个128D向量间的欧氏距离
return dist #返回计算结果
# 生成的 cv2 window 上面添加说明文字 / putText on cv2 window
def draw_note(self, img_rd):
# 添加说明 (Add some statements
cv2.putText(img_rd, "Face Recognizer for single face", (20, 40), self.font, 1, (255, 255, 255), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "Frame: " + str(self.frame_cnt), (20, 100), self.font, 0.8, (0, 255, 0), 1, #记录帧数
cv2.LINE_AA)
cv2.putText(img_rd, "FPS: " + str(self.fps_show.__round__(2)), (20, 130), self.font, 0.8, (0, 255, 0), 1, #记录FPS
cv2.LINE_AA)
cv2.putText(img_rd, "Faces: " + str(self.current_frame_face_cnt), (20, 160), self.font, 0.8, (0, 255, 0), 1, #记录人脸数
cv2.LINE_AA)
cv2.putText(img_rd, "Q: Quit", (20, 450), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
def draw_name(self, img_rd): #调用函数,写上人名
# 在人脸框下面写人脸名字 / Write names under ROI
logging.debug(self.current_frame_name_list) #记录日志
img = Image.fromarray(cv2.cvtColor(img_rd, cv2.COLOR_BGR2RGB)) #图像转换
draw = ImageDraw.Draw(img) #进行名字的添加
draw.text(xy=self.current_frame_face_position_list[0], text=self.current_frame_name_list[0], font=self.font_chinese,
fill=(255, 255, 0))
img_rd = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) #再将其转换回来,返回转换后的图片
return img_rd
def show_chinese_name(self): #显示中文名
if self.current_frame_face_cnt >= 1: #如果当前人脸数>=1
logging.debug(self.face_name_known_list) #记录日志,用于调试
# 修改录入的人脸姓名 / Modify names in face_name_known_list to chinese name
self.face_name_known_list[0] = '张三'.encode('utf-8').decode() #修改第一个人名为张三
# self.face_name_known_list[1] = '张四'.encode('utf-8').decode()
# 处理获取的视频流, 进行人脸识别 / Face detection and recognition wit OT from input video stream
def process(self, stream):
# 1. 读取存放所有人脸特征的 csv / Get faces known from "features.all.csv"
if self.get_face_database(): #读取人脸特征
while stream.isOpened():
self.frame_cnt += 1 #记录帧数
logging.debug("Frame " + str(self.frame_cnt) + " starts") #记录在日志中,用于调试
flag, img_rd = stream.read() #读取图片
kk = cv2.waitKey(1)
# 2. 检测人脸 / Detect faces for frame X
faces = detector(img_rd, 0)
# 3. 更新帧中的人脸数 / Update cnt for faces in frames
self.last_frame_faces_cnt = self.current_frame_face_cnt #记录上一帧人脸数
self.current_frame_face_cnt = len(faces) #记录这一帧人脸数
# 4.1 当前帧和上一帧相比没有发生人脸数变化 / If cnt not changes, 1->1 or 0->0
if self.current_frame_face_cnt == self.last_frame_faces_cnt:
logging.debug("scene 1: 当前帧和上一帧相比没有发生人脸数变化 / No face cnt changes in this frame!!!") #记录日志
if "unknown" in self.current_frame_name_list: #如果有未知人脸在当前的人脸名字中
logging.debug(" >>> 有未知人脸, 开始进行 reclassify_interval_cnt 计数")
self.reclassify_interval_cnt += 1 #发现未知人脸,进行一个计数
# 4.1.1 当前帧一张人脸 / One face in this frame
if self.current_frame_face_cnt == 1: #如果当前人脸数为1
if self.reclassify_interval_cnt == self.reclassify_interval: #当发现未知人脸出现了10帧,重新对当前帧人脸识别
logging.debug(" scene 1.1 需要对于当前帧重新进行人脸识别 / Re-classify for current frame")
self.reclassify_interval_cnt = 0 #将计数清零
self.current_frame_face_feature_list = [] #当前捕获到的人脸特征
self.current_frame_face_X_e_distance_list = [] #存放进行识别时候对比的欧氏距离
self.current_frame_name_list = [] #用来存储当前帧检测出目标的名字
for i in range(len(faces)): #遍历人脸
shape = predictor(img_rd, faces[i])
self.current_frame_face_feature_list.append( #添加当前人脸特征
face_reco_model.compute_face_descriptor(img_rd, shape))
# a. 遍历捕获到的图像中所有的人脸 / Traversal all the faces in the database
for k in range(len(faces)):
self.current_frame_name_list.append("unknown") #将当前帧的所有人脸都改为Cjuicy
# b. 每个捕获人脸的名字坐标 / Positions of faces captured
self.current_frame_face_position_list.append(tuple( #记录当前帧每个人脸的坐标
[faces[k].left(),
int(faces[k].bottom() + (faces[k].bottom() - faces[k].top()) / 4)]))
# c. 对于某张人脸, 遍历所有存储的人脸特征 / For every face detected, compare it with all the faces in the database
for i in range(len(self.features_known_list)):
# 如果 person_X 数据不为空 / If the data of person_X is not empty
if str(self.features_known_list[i][0]) != '0.0':
e_distance_tmp = self.return_euclidean_distance( #返回计算出来的欧氏距离
self.current_frame_face_feature_list[k], #当前帧人脸的特征
self.features_known_list[i]) #数据库中对应人脸i的特征
logging.debug(" with person %d, the e-distance: %f", i + 1, e_distance_tmp)
self.current_frame_face_X_e_distance_list.append(e_distance_tmp)
else:
# 空数据 person_X / For empty data
self.current_frame_face_X_e_distance_list.append(999999999)
# d. 寻找出最小的欧式距离匹配 / Find the one with minimum e distance
similar_person_num = self.current_frame_face_X_e_distance_list.index(
min(self.current_frame_face_X_e_distance_list))
if min(self.current_frame_face_X_e_distance_list) < 0.4:
# 在这里更改显示的人名 / Modify name if needed
self.show_chinese_name()
self.current_frame_name_list[k] = self.face_name_known_list[similar_person_num] #将对应的人名赋上去
logging.debug(" recognition result for face %d: %s", k + 1,
self.face_name_known_list[similar_person_num])
else:
logging.debug(" recognition result for face %d: %s", k + 1, "unknown")
else:
logging.debug(
" scene 1.2 不需要对于当前帧重新进行人脸识别 / No re-classification needed for current frame")
# 获取特征框坐标 / Get ROI positions
for k, d in enumerate(faces):
cv2.rectangle(img_rd, #围着人脸,画出矩形
tuple([d.left(), d.top()]),
tuple([d.right(), d.bottom()]),
(255, 255, 255), 2)
self.current_frame_face_position_list[k] = tuple( #添加人名坐标
[faces[k].left(),
int(faces[k].bottom() + (faces[k].bottom() - faces[k].top()) / 4)])
img_rd = self.draw_name(img_rd) #进行画图
# 4.2 当前帧和上一帧相比发生人脸数变化 / If face cnt changes, 1->0 or 0->1
else:
logging.debug("scene 2: 当前帧和上一帧相比人脸数发生变化 / Faces cnt changes in this frame")
self.current_frame_face_position_list = [] #当前帧所有人脸坐标名字
self.current_frame_face_X_e_distance_list = [] #用来存放进行识别时候对比的欧氏距离
self.current_frame_face_feature_list = [] #存储当前摄像头中捕获到的人脸特征
# 4.2.1 人脸数从 0->1 / Face cnt 0->1
if self.current_frame_face_cnt == 1:
logging.debug(" scene 2.1 出现人脸, 进行人脸识别 / Get faces in this frame and do face recognition")
self.current_frame_name_list = [] #记录当前帧的人脸名字
for i in range(len(faces)):
shape = predictor(img_rd, faces[i]) #获取特征
self.current_frame_face_feature_list.append( #添加人脸当前特征
face_reco_model.compute_face_descriptor(img_rd, shape))
# a. 遍历捕获到的图像中所有的人脸 / Traversal all the faces in the database
for k in range(len(faces)):
self.current_frame_name_list.append("unknown") #将所有名字标记为unknow
# b. 每个捕获人脸的名字坐标 / Positions of faces captured
self.current_frame_face_position_list.append(tuple(
[faces[k].left(), int(faces[k].bottom() + (faces[k].bottom() - faces[k].top()) / 4)]))
# c. 对于某张人脸, 遍历所有存储的人脸特征 / For every face detected, compare it with all the faces in database
for i in range(len(self.features_known_list)):
# 如果 person_X 数据不为空 / If data of person_X is not empty
if str(self.features_known_list[i][0]) != '0.0':
e_distance_tmp = self.return_euclidean_distance( #返回计算的欧氏距离
self.current_frame_face_feature_list[k], #当前帧的人脸特征
self.features_known_list[i]) #表中第i个人脸特征(i从0开始计数)
logging.debug(" with person %d, the e-distance: %f", i + 1, e_distance_tmp)
self.current_frame_face_X_e_distance_list.append(e_distance_tmp) #添加到当前帧特征总表中
else:
# 空数据 person_X / Empty data for person_X
self.current_frame_face_X_e_distance_list.append(999999999)
# d. 寻找出最小的欧式距离匹配 / Find the one with minimum e distance
similar_person_num = self.current_frame_face_X_e_distance_list.index(
min(self.current_frame_face_X_e_distance_list))
if min(self.current_frame_face_X_e_distance_list) < 0.4:
# 在这里更改显示的人名 / Modify name if needed
self.show_chinese_name()
self.current_frame_name_list[k] = self.face_name_known_list[similar_person_num] #设置当前帧人名
logging.debug(" recognition result for face %d: %s", k + 1,
self.face_name_known_list[similar_person_num])
else:
logging.debug(" recognition result for face %d: %s", k + 1, "unknown")
if "unknown" in self.current_frame_name_list:
self.reclassify_interval_cnt += 1 #如果帧中还有未知人脸,计数
# 4.2.1 人脸数从 1->0 / Face cnt 1->0
elif self.current_frame_face_cnt == 0: #如果人脸消失
logging.debug(" scene 2.2 人脸消失, 当前帧中没有人脸 / No face in this frame!!!")
self.reclassify_interval_cnt = 0 #计数清零
self.current_frame_name_list = [] #名字为空
self.current_frame_face_feature_list = [] #当前帧中,记录人脸特征的表为空
# 5. 生成的窗口添加说明文字 / Add note on cv2 window
self.draw_note(img_rd)
if kk == ord('q'):
break
self.update_fps()
cv2.namedWindow("camera", 1)
cv2.imshow("camera", img_rd)
logging.debug("Frame ends\n\n")
def run(self):
# cap = cv2.VideoCapture("video.mp4") # Get video stream from video file
cap = cv2.VideoCapture(0) # Get video stream from camera
self.process(cap)
cap.release()
cv2.destroyAllWindows()
def main():
# logging.basicConfig(level=logging.DEBUG) # Set log level to 'logging.DEBUG' to print debug info of every frame
logging.basicConfig(level=logging.INFO)
Face_Recognizer_con = Face_Recognizer()
Face_Recognizer_con.run()
if __name__ == '__main__':
main()
因为人脸数不再单单只有1个,上面的方法就不足以支持了
所以引入质心追踪算法(核心算法)
def centroid_tracker(self):
for i in range(len(self.current_frame_centroid_list)):
e_distance_current_frame_face_x_list = []
# for face 1 in current_frame, compute e-distance with face 1/2/3/4/... in last frame
for j in range(len(self.last_frame_centroid_list)):
self.last_current_frame_centroid_e_distance = self.return_euclidean_distance(
self.current_frame_centroid_list[i], self.last_frame_centroid_list[j])
e_distance_current_frame_person_x_list.append(
self.last_current_frame_centroid_e_distance)
last_frame_num = e_distance_current_frame_person_x_list.index(
min(e_distance_current_frame_face_x_list))
self.current_frame_face_names_list[i] = self.last_frame_face_names_list[last_frame_num]
项目代码:
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# Mail: [email protected]
# 利用 OT 人脸追踪, 进行人脸实时识别 / Real-time face detection and recognition via OT for multi faces
# 检测 -> 识别人脸, 新人脸出现 -> 不需要识别, 而是利用质心追踪来判断识别结果 / Do detection -> recognize face, new face -> not do re-recognition
# 人脸进行再识别需要花费大量时间, 这里用 OT 做跟踪 / Do re-recognition for multi faces will cost much time, OT will be used to instead it
import dlib
import numpy as np
import cv2
import os
import pandas as pd
import time
import logging
# 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")
class Face_Recognizer:
def __init__(self):
self.font = cv2.FONT_ITALIC #这是一下font
# FPS
self.frame_time = 0 #这一部分是用来计算fps的变量,先初始化
self.frame_start_time = 0
self.fps = 0
self.fps_show = 0
self.start_time = time.time()
# cnt for frame 记录帧
self.frame_cnt = 0
# 用来存放所有录入人脸特征的数组 / Save the features of faces in the database
self.face_features_known_list = []
# 存储录入人脸名字 / Save the name of faces in the database
self.face_name_known_list = []
# 用来存储上一帧和当前帧 ROI 的质心坐标 / List to save centroid positions of ROI in frame N-1 and N
self.last_frame_face_centroid_list = [] #ROI 感兴趣区域
self.current_frame_face_centroid_list = []
# 用来存储上一帧和当前帧检测出目标的名字 / List to save names of objects in frame N-1 and N
self.last_frame_face_name_list = []
self.current_frame_face_name_list = []
# 上一帧和当前帧中人脸数的计数器 / cnt for faces in frame N-1 and N
self.last_frame_face_cnt = 0
self.current_frame_face_cnt = 0
# 用来存放进行识别时候对比的欧氏距离 / Save the e-distance for faceX when recognizing
self.current_frame_face_X_e_distance_list = []
# 存储当前摄像头中捕获到的所有人脸的坐标名字 / Save the positions and names of current faces captured
self.current_frame_face_position_list = []
# 存储当前摄像头中捕获到的人脸特征 / Save the features of people in current frame
self.current_frame_face_feature_list = []
# e distance between centroid of ROI in last and current frame
self.last_current_frame_centroid_e_distance = 0 #用于存放上一帧与现在帧质心的距离
# 控制再识别的后续帧数 / Reclassify after 'reclassify_interval' frames
# 如果识别出 "unknown" 的脸, 将在 reclassify_interval_cnt 计数到 reclassify_interval 后, 对于人脸进行重新识别
self.reclassify_interval_cnt = 0
self.reclassify_interval = 10
# 从 "features_all.csv" 读取录入人脸特征 / Get known faces from "features_all.csv"
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 = []
self.face_name_known_list.append(csv_rd.iloc[i][0])
for j in range(1, 129):
if csv_rd.iloc[i][j] == '':
features_someone_arr.append('0')
else:
features_someone_arr.append(csv_rd.iloc[i][j])
self.face_features_known_list.append(features_someone_arr) #将遍历的特征值添加到其中
logging.info("Faces in Database: %d", len(self.face_features_known_list))
return 1
else:
logging.warning("'features_all.csv' not found!")
logging.warning("Please run 'get_faces_from_camera.py' "
"and 'features_extraction_to_csv.py' before 'face_reco_from_camera.py'")
return 0
def update_fps(self): #更新fps
now = time.time()
# 每秒刷新 fps / Refresh fps per second
if str(self.start_time).split(".")[0] != str(now).split(".")[0]:
self.fps_show = self.fps
self.start_time = now
self.frame_time = now - self.frame_start_time
self.fps = 1.0 / self.frame_time
self.frame_start_time = now
@staticmethod
# 计算两个128D向量间的欧式距离 / Compute the e-distance between two 128D features
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
# 使用质心追踪来识别人脸 / Use centroid tracker to link face_x in current frame with person_x in last frame
def centroid_tracker(self):
for i in range(len(self.current_frame_face_centroid_list)): #遍历当前帧,人脸质心坐标表
e_distance_current_frame_person_x_list = [] #存放当前帧质心间的距离
# 对于当前帧中的人脸1, 和上一帧中的 人脸1/2/3/4/.. 进行欧氏距离计算 / For object 1 in current_frame, compute e-distance with object 1/2/3/4/... in last frame
for j in range(len(self.last_frame_face_centroid_list)): #遍历所有人脸坐标
self.last_current_frame_centroid_e_distance = self.return_euclidean_distance( #计算质心之间的欧氏距离
self.current_frame_face_centroid_list[i], self.last_frame_face_centroid_list[j])
e_distance_current_frame_person_x_list.append( #存放所有质心之间的欧氏距离
self.last_current_frame_centroid_e_distance)
last_frame_num = e_distance_current_frame_person_x_list.index( #寻找最小的欧氏距离的序号
min(e_distance_current_frame_person_x_list))
self.current_frame_face_name_list[i] = self.last_frame_face_name_list[last_frame_num] #存储最小欧氏距离的名字
# 生成的 cv2 window 上面添加说明文字 / putText on cv2 window
def draw_note(self, img_rd):
# 添加说明 / Add some info on windows
cv2.putText(img_rd, "Face Recognizer with OT", (20, 40), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "Frame: " + str(self.frame_cnt), (20, 100), self.font, 0.8, (0, 255, 0), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "FPS: " + str(self.fps.__round__(2)), (20, 130), self.font, 0.8, (0, 255, 0), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "Faces: " + str(self.current_frame_face_cnt), (20, 160), self.font, 0.8, (0, 255, 0), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "Q: Quit", (20, 450), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
for i in range(len(self.current_frame_face_name_list)): #在对应人脸质心处,添加文字,检验有没有出现错误
img_rd = cv2.putText(img_rd, "Face_" + str(i + 1), tuple(
[int(self.current_frame_face_centroid_list[i][0]), int(self.current_frame_face_centroid_list[i][1])]),
self.font,
0.8, (255, 190, 0),
1,
cv2.LINE_AA)
# 处理获取的视频流, 进行人脸识别 / Face detection and recognition wit OT from input video stream
def process(self, stream):
# 1. 读取存放所有人脸特征的 csv / Get faces known from "features.all.csv"
if self.get_face_database(): #加载数据库中的特征数据
while stream.isOpened():
self.frame_cnt += 1
logging.debug("Frame " + str(self.frame_cnt) + " starts")
flag, img_rd = stream.read() #读取视频流
kk = cv2.waitKey(1)
# 2. 检测人脸 / Detect faces for frame X
faces = detector(img_rd, 0)
# 3. 更新人脸计数器 / Update cnt for faces in frames
self.last_frame_face_cnt = self.current_frame_face_cnt
self.current_frame_face_cnt = len(faces)
# 4. 更新上一帧中的人脸列表 / Update the face name list in last frame
self.last_frame_face_name_list = self.current_frame_face_name_list[:]
# 5. 更新上一帧和当前帧的质心列表 / update frame centroid list
self.last_frame_face_centroid_list = self.current_frame_face_centroid_list
self.current_frame_face_centroid_list = []
# 6.1 如果当前帧和上一帧人脸数没有变化 / if cnt not changes
if (self.current_frame_face_cnt == self.last_frame_face_cnt) and (
self.reclassify_interval_cnt != self.reclassify_interval): #判断有未知人脸的计数有没有满10
logging.debug("scene 1: 当前帧和上一帧相比没有发生人脸数变化 / No face cnt changes in this frame!!!")
self.current_frame_face_position_list = [] #记录当前帧人脸位置坐标
if "unknown" in self.current_frame_face_name_list: #判断是否有未知人脸
logging.debug(" 有未知人脸, 开始进行 reclassify_interval_cnt 计数")
self.reclassify_interval_cnt += 1
if self.current_frame_face_cnt != 0: #如果当前人脸数不为0
for k, d in enumerate(faces): #返回其元素,以及对应的索引 k是索引,d是元素
self.current_frame_face_position_list.append(tuple( #获取当前帧 人名位置
[faces[k].left(), int(faces[k].bottom() + (faces[k].bottom() - faces[k].top()) / 4)]))
self.current_frame_face_centroid_list.append( #获取当前帧 质心的位置
[int(faces[k].left() + faces[k].right()) / 2,
int(faces[k].top() + faces[k].bottom()) / 2])
img_rd = cv2.rectangle(img_rd, #画出矩形框
tuple([d.left(), d.top()]),
tuple([d.right(), d.bottom()]),
(255, 255, 255), 2)
# 如果当前帧中有多个人脸, 使用质心追踪 / Multi-faces in current frame, use centroid-tracker to track
if self.current_frame_face_cnt != 1:
self.centroid_tracker() #调用质心追踪函数
for i in range(self.current_frame_face_cnt):
# 6.2 Write names under ROI
img_rd = cv2.putText(img_rd, self.current_frame_face_name_list[i], #在对应坐标写上名字
self.current_frame_face_position_list[i], self.font, 0.8, (0, 255, 255), 1,
cv2.LINE_AA)
self.draw_note(img_rd)
# 6.2 如果当前帧和上一帧人脸数发生变化 / If cnt of faces changes, 0->1 or 1->0 or ...
else:
logging.debug("scene 2: 当前帧和上一帧相比人脸数发生变化 / Faces cnt changes in this frame")
self.current_frame_face_position_list = [] #当前人脸坐标
self.current_frame_face_X_e_distance_list = [] #存放识别对比的欧氏距离
self.current_frame_face_feature_list = [] #存放当前帧人脸特征值
self.reclassify_interval_cnt = 0 #初始化人脸计数器
# 6.2.1 人脸数减少 / Face cnt decreases: 1->0, 2->1, ...
if self.current_frame_face_cnt == 0:
logging.debug(" scene 2.1 人脸消失, 当前帧中没有人脸 / No faces in this frame!!!")
# clear list of names and features
self.current_frame_face_name_list = [] #清空名字
# 6.2.2 人脸数增加 / Face cnt increase: 0->1, 0->2, ..., 1->2, ...
else:
logging.debug(" scene 2.2 出现人脸, 进行人脸识别 / Get faces in this frame and do face recognition")
self.current_frame_face_name_list = [] #初始化人名表
for i in range(len(faces)):
shape = predictor(img_rd, faces[i]) #遍历识别的人脸,进行特征提取
self.current_frame_face_feature_list.append(
face_reco_model.compute_face_descriptor(img_rd, shape)) #添加当前帧的人脸特征点
self.current_frame_face_name_list.append("unknown") #将人名表都先默认填入unknow
# 6.2.2.1 遍历捕获到的图像中所有的人脸 / Traversal all the faces in the database
for k in range(len(faces)):
logging.debug(" For face %d in current frame:", k + 1) #激励日志
self.current_frame_face_centroid_list.append( #添加当前帧质心坐标
[int(faces[k].left() + faces[k].right()) / 2,
int(faces[k].top() + faces[k].bottom()) / 2])
self.current_frame_face_X_e_distance_list = [] #定义欧氏距离
# 6.2.2.2 每个捕获人脸的名字坐标 / Positions of faces captured
self.current_frame_face_position_list.append(tuple(
[faces[k].left(), int(faces[k].bottom() + (faces[k].bottom() - faces[k].top()) / 4)]))
# 6.2.2.3 对于某张人脸, 遍历所有存储的人脸特征
# For every faces detected, compare the faces in the database
for i in range(len(self.face_features_known_list)):
# 如果 q 数据不为空
if str(self.face_features_known_list[i][0]) != '0.0':
e_distance_tmp = self.return_euclidean_distance( #调动计算欧氏距离函数
self.current_frame_face_feature_list[k],
self.face_features_known_list[i])
logging.debug(" with person %d, the e-distance: %f", i + 1, e_distance_tmp)
self.current_frame_face_X_e_distance_list.append(e_distance_tmp) #将欧氏距离填入表内
else:
# 空数据 person_X
self.current_frame_face_X_e_distance_list.append(999999999)
# 6.2.2.4 寻找出最小的欧式距离匹配 / Find the one with minimum e distance
similar_person_num = self.current_frame_face_X_e_distance_list.index(
min(self.current_frame_face_X_e_distance_list))
if min(self.current_frame_face_X_e_distance_list) < 0.4:
self.current_frame_face_name_list[k] = self.face_name_known_list[similar_person_num]
logging.debug(" Face recognition result: %s",
self.face_name_known_list[similar_person_num])
else:
logging.debug(" Face recognition result: Unknown person")
# 7. 生成的窗口添加说明文字 / Add note on cv2 window
self.draw_note(img_rd)
# cv2.imwrite("debug/debug_" + str(self.frame_cnt) + ".png", img_rd) # Dump current frame image if needed
# 8. 按下 'q' 键退出 / Press 'q' to exit
if kk == ord('q'):
break
self.update_fps()
cv2.namedWindow("camera", 1)
cv2.imshow("camera", img_rd)
logging.debug("Frame ends\n\n")
def run(self):
# cap = cv2.VideoCapture("video.mp4") # Get video stream from video file
cap = cv2.VideoCapture(0) # Get video stream from camera
self.process(cap)
cap.release()
cv2.destroyAllWindows()
def main():
# logging.basicConfig(level=logging.DEBUG) # Set log level to 'logging.DEBUG' to print debug info of every frame
logging.basicConfig(level=logging.INFO)
Face_Recognizer_con = Face_Recognizer()
Face_Recognizer_con.run()
if __name__ == '__main__':
main()
这一部分主要是看,调用数据库的操作,和识别的操作都需要花去多少时间
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
# 摄像头实时人脸特征描述子计算 / Real-time face descriptor computing
import dlib # 人脸识别的库 Dlib
import cv2 # 图像处理的库 OpenCV
import time
# 1. Dlib 正向人脸检测器
detector = dlib.get_frontal_face_detector()
# 2. Dlib 人脸 landmark 特征点检测器
predictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')
# 3. Dlib Resnet 人脸识别模型,提取 128D 的特征矢量
face_reco_model = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")
class Face_Descriptor:
def __init__(self):
self.frame_time = 0
self.frame_start_time = 0
self.fps = 0
self.frame_cnt = 0
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 run(self):
cap = cv2.VideoCapture(0)
cap.set(3, 480)
self.process(cap)
cap.release()
cv2.destroyAllWindows()
def process(self, stream):
while stream.isOpened():
flag, img_rd = stream.read()
self.frame_cnt+=1
k = cv2.waitKey(1)
print('- Frame ', self.frame_cnt, " starts:")
timestamp1 = time.time()
faces = detector(img_rd, 0)
timestamp2 = time.time()
print("--- Time used to `detector`: %s seconds ---" % (timestamp2 - timestamp1))
font = cv2.FONT_HERSHEY_SIMPLEX
# 检测到人脸
if len(faces) != 0:
for face in faces:
timestamp3 = time.time()
face_shape = predictor(img_rd, face)
timestamp4 = time.time()
print("--- Time used to `predictor`: %s seconds ---" % (timestamp4 - timestamp3))
timestamp5 = time.time()
face_desc = face_reco_model.compute_face_descriptor(img_rd, face_shape)
timestamp6 = time.time()
print("--- Time used to `compute_face_descriptor:` %s seconds ---" % (timestamp6 - timestamp5))
# 添加说明
cv2.putText(img_rd, "Face descriptor", (20, 40), font, 1, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "FPS: " + str(self.fps.__round__(2)), (20, 100), font, 0.8, (0, 255, 0), 1, cv2.LINE_AA)
cv2.putText(img_rd, "Faces: " + str(len(faces)), (20, 140), font, 0.8, (0, 255, 0), 1, cv2.LINE_AA)
cv2.putText(img_rd, "S: Save current face", (20, 400), font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "Q: Quit", (20, 450), font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
# 按下 'q' 键退出
if k == ord('q'):
break
self.update_fps()
cv2.namedWindow("camera", 1)
cv2.imshow("camera", img_rd)
print('\n')
def main():
Face_Descriptor_con = Face_Descriptor()
Face_Descriptor_con.run()
if __name__ == '__main__':
main()