python人脸识别(转载)

源码出处

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

1. 源码分析

1.1 如何使用摄像头

# 调用摄像头

# 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()

1.2 对静态人脸图片进行特征点标记

# 对静态人脸图像文件进行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)

1.3 调用摄像头,进行人脸捕获,和特征点的追踪

和上一个静态人脸图类似,只是这个图片是一帧一帧会变化的。

多了一个截图,和删除截图的操作。

# 调用摄像头,进行人脸捕获,和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()

2.项目分析

2.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]

# 进行人脸录入 / 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()

2.2 人脸录入(包含了tkinter-GUI)

  • 首先检查存放图片的文件夹有没有创建
  • 检查文件夹里面有没有存放图片,记录数据
  • 初始化tkinter GUI
  • 执行最重要的函数,获取人脸数据
    • 检查摄像头没有有连接成功,同时记录摄像头获取的图片
    • 调用模型,识别出哪一部分是人脸
    • 调用更新fps函数,来记录每一秒处理几帧图片
    • 检测到人脸,用内置函数,对人脸用矩阵把它框出来
      • 同时进行一系列的判断
    • 进行一个图片格式的转换,让其能够显示在GUI界面上
    • 最后每20毫秒,再重新执行process,进行一个循环的处理
  • 接受操作系统发来的事件,然后把事件分发给各个空间和窗体
# 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()

2.3 从人脸图像文件中提取人脸特征

  • 记录日志
  • 获取存放人脸图像文件路径,并对其排序
  • 创建一个csv格式的文件,并对其录入数据
    • 遍历所有人
    • 对每一个人提取特征值,并获取其返回值(特征均值)
    • 同时将对应人的名称卸载csv文件内
    • 最后记录日志

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()

2.4 摄像头实时人脸识别

伪代码如下

# 人脸检测器/预测器/识别模型
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])

文件代码如下

  • 初始日志信息
  • 打开摄像头
  • 调用主函数process
    • 从csv中获取特征记录
    • 从视频流中获取人脸,并调用函数进行处理,返回我们需要的值
    • 调用draw函数,生成一些文字
    • 检测人脸
      • 捕获特征
      • 添加默认的名字
      • 获取人脸名字坐标
      • 遍历所有的存储人脸特征,进行比对
        • 进行欧氏距离计算,记录
        • 寻找出最小的欧氏距离匹配(如果csv中有多个人的人脸数据,就由多个欧氏距离)
        • 判断欧氏距离是不是小于这个值
          • 登记该人名字
        • 绘制识别人脸的矩形框
        • 调用函数,使其支持中文
        • 最后更新FPS,记录日志
# 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()

3.项目优化分析

为解决上述代码,计算量过大,导致fps过低,摄像头识别卡顿问题。

所有源码分析是对下面博客的摘抄

利用目标跟踪来提高实时人脸识别处理速度 - coneypo - 博客园 (cnblogs.com)

目标追踪(Object Tracking)概念的简要介绍 - coneypo - 博客园 (cnblogs.com)

3.1 摄像头中只出现0个或单张人脸

  • 初始日志
  • 加载人脸识别类
  • 开始人脸识别
    • 开启摄像头
    • 处理视频流,进行人脸识别
      • 读取记录在库中的人脸特征
      • 进行当前帧的正向人脸检测,获取特征。并记录人脸数。
      • 判断人脸数和上一帧相比,有没有发生变化(没发生变化)
        • 若发现未知人脸,未知人脸帧计数器+1
        • 若检测到有人脸
          • 确定是未知人脸,重新进行人脸比对,识别,画矩形框,添加对应人名
          • 是已知人脸,不需要重新识别,画出矩形框,添加名字即可
      • 判断人脸数和上一帧相比,有没有发生变化(发生变化)
        • 若人脸数从0到1,开始重新进行人脸识别,并画图
        • 若人脸是从0到1,所有计数都变为空
    • 开始将数据画在图像
    • 更新fps
    • 显示图片
# 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()

3.2 目标人脸数大于1

因为人脸数不再单单只有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]

项目代码:

  • 初始化日志
  • 运行项目
    • 连接摄像头
    • 开始人脸识别
      • 读取库中人脸特征数据
      • 读取视频流
      • 初始化一些变量,获取人脸数据
      • 判断当前帧和上一帧是否有变化(没有变化)
        • 判断是否有未知人脸,有未知人脸帧计数器加一
        • 判断当前人脸数不为0
          • 获取人脸的位置,获取质心的位置,画出识别人脸的矩形框
          • 判断是否有多张人脸,若有多张人脸,进行质心追踪
          • 质心追踪完之后,进行名字的登记写入
          • 生成文字在图片上
      • 如果发生变化
        • 初始一些参数
        • 判断人脸数是否为0,为0则将所有名字清空
        • 增加则对其进行人脸识别
          • 获取人脸特征,修改人名表
          • 添加质心坐标
          • 捕获人脸名字坐标
          • 计算欧氏距离,寻找最小欧氏距离匹配名字
        • 生成文字在图片上
      • 更新fps
# 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()

3.3 摄像头实时人脸特征描述子计算

这一部分主要是看,调用数据库的操作,和识别的操作都需要花去多少时间

# 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()

总结:

  • 一直在想,这么一个相对复杂的代码,是怎样还能知道上下文对应的。后来才意识到,我需要先做一个伪代码,对一整个流程进行一个大致的描述,然后再慢慢的对其进行完成,实现。
  • 同时我看到了日志的重要性,利用日志来代替print做调试
    • python3:用logging.debug()全面替换print()来调试脚本code_五力的博客-CSDN博客_logging.debug
  • 同时我也意识到,什么样的注释是真的一清二楚还不累赘,代码结构层次也很分明
  • 希望以后我也能像这位大老这样,写出很棒很棒的代码出来

你可能感兴趣的:(linux桌面小程序开发日记,python,opencv,计算机视觉)