多线程:
程序的两个功能之间可以独立运行,就需要采用多线程的方法,但当遇到临界资源的使用时,多个进程/线程之间就要互斥的访问以免出错,本程序中具体的设计方法: 本程序采用多线程的方法实现并行。 程序的三个按钮对应着三个功能,分别是录入人脸、人脸检测、退出程序。 由于程序中的用户界面是利用python中的tkinter库做的,其按钮的响应函数用command指出,所以这里在每个command跳转到的函数中设置多线程,每敲击一次就用threading.Thread创建一个新的线程,然后在新的线程的处理函数target中实现按钮原本对应的功能。
p = threading.Thread(target=f_scan_face_thread)
复制代码
在涉及到摄像头的访问时,线程之间需要互斥的访问,所以设置了一个全局的变量system_state_lock 来表示当前系统的状态,用以实现带有优先级的互斥锁的功能。 锁状态为0表示摄像头未被使用,1表示正在刷脸,2表示正在录入新面容。 程序在实际执行的过程中如果状态为0,则无论是刷脸还是录入都能顺利执行,如果状态为1表示正在刷脸,如果此时敲击刷脸按钮则,系统会提示正在刷脸并拒绝新的请求,如果此时敲击录入面容按钮,由于录入面容优先级比刷脸高,所以原刷脸线程会被阻塞,
global system_state_lock
while system_state_lock == 2: # 如果正在录入新面孔就阻塞
pass
复制代码
新的录入面容进程开始执行并修改系统状态为2,录入完成后状态变为原状态,被阻塞的刷脸进程继续执行,录入人脸线程刚执行完录入阶段现在正在训练,此时有两个线程并行,以此来保证训练数据的同时不影响系统的使用。
对于退出的功能,直接在函数内调用exit(),但是python的线程会默认等待子线程全部结束再退出,所以用p.setDaemon(True)将线程设置为守护线程,这样在主线程退出之后其它线程也都退出从而实现退出整个程序的功能。
GUI设计:
程序采用python中的tkinter库做可视化,优点是占用资源小、轻量化、方便。
- 首先创建一个窗口命名为window然后设置其大小和标题等属性。
- 然后在界面上设定一个绿底的标签,类似于一个提示窗口的作用
- 然后分别创建三个按钮,并设置响应函数和提示字符,放置在window内部。
- 然后设置一个label类型的控件用于动态的展示摄像头的内容(将摄像头显示嵌入到控件中)。具体方法:创建video_loop()函数,在函数内访问全局的变量img,img是从摄像头读取到的图像数据。然后把img显示在label内。 使用window.after方法,在给定时间后调用函数一次,实现固定时间刷新控件,从而达到实时显示摄像头画面在GUI中的效果。
window.after(1, video_loop)
# 这句的意思是一秒以后执行video_loop函数
# 因为这一句是写在video_loop函数中的所以每过一秒函数执行一次。
复制代码
运行测试
说明
测试环境:python 3.6 + opencv-python 3.4.14.51 需要的包:

图6:需要的包
录入人脸
从数据集录入

从摄像头录入


人脸识别


代码实现:
# 实验环境:python 3.6 + opencv-python 3.4.14.51
import cv2
import numpy as np
import os
import shutil
import threading
import tkinter as tk
from PIL import Image, ImageTk
# 首先读取config文件,第一行代表当前已经储存的人名个数,接下来每一行是(id,name)标签和对应的人名
id_dict = {} # 字典里存的是id——name键值对
Total_face_num = 999 # 已经被识别有用户名的人脸个数,
def init(): # 将config文件内的信息读入到字典中
f = open('config.txt')
global Total_face_num
Total_face_num = int(f.readline())
for i in range(int(Total_face_num)):
line = f.readline()
id_name = line.split(' ')
id_dict[int(id_name[0])] = id_name[1]
f.close()
init()
# 加载OpenCV人脸检测分类器Haar
face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
# 准备好识别方法LBPH方法
recognizer = cv2.face.LBPHFaceRecognizer_create()
# 打开标号为0的摄像头
camera = cv2.VideoCapture(0) # 摄像头
success, img = camera.read() # 从摄像头读取照片
W_size = 0.1 * camera.get(3)
H_size = 0.1 * camera.get(4)
system_state_lock = 0 # 标志系统状态的量 0表示无子线程在运行 1表示正在刷脸 2表示正在录入新面孔。
# 相当于mutex锁,用于线程同步
'''
============================================================================================
以上是初始化
============================================================================================
'''
def Get_new_face():
print("正在从摄像头录入新人脸信息
")
# 存在目录data就清空,不存在就创建,确保最后存在空的data目录
filepath = "data"
if not os.path.exists(filepath):
os.mkdir(filepath)
else:
shutil.rmtree(filepath)
os.mkdir(filepath)
sample_num = 0 # 已经获得的样本数
while True: # 从摄像头读取图片
global success
global img # 因为要显示在可视化的控件内,所以要用全局的
success, img = camera.read()
# 转为灰度图片
if success is True:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
else:
break
# 检测人脸,将每一帧摄像头记录的数据带入OpenCv中,让Classifier判断人脸
# 其中gray为要检测的灰度图像,1.3为每次图像尺寸减小的比例,5为minNeighbors
face_detector = face_cascade
faces = face_detector.detectMultiScale(gray, 1.3, 5)
# 框选人脸,for循环保证一个能检测的实时动态视频流
for (x, y, w, h) in faces:
# xy为左上角的坐标,w为宽,h为高,用rectangle为人脸标记画框
cv2.rectangle(img, (x, y), (x + w, y + w), (255, 0, 0))
# 样本数加1
sample_num += 1
# 保存图像,把灰度图片看成二维数组来检测人脸区域,这里是保存在data缓冲文件夹内
T = Total_face_num
cv2.imwrite("./data/User." + str(T) + '.' + str(sample_num) + '.jpg', gray[y:y + h, x:x + w])
pictur_num = 30 # 表示摄像头拍摄取样的数量,越多效果越好,但获取以及训练的越慢
cv2.waitKey(1)
if sample_num > pictur_num:
break
else: # 控制台内输出进度条
l = int(sample_num / pictur_num * 50)
r = int((pictur_num - sample_num) / pictur_num * 50)
print("r" + "%{:.1f}".format(sample_num / pictur_num * 100) + "=" * l + "->" + "_" * r, end="")
var.set("%{:.1f}".format(sample_num / pictur_num * 100)) # 控件可视化进度信息
# tk.Tk().update()
window.update() # 刷新控件以实时显示进度
def Train_new_face():
print("
正在训练")
# cv2.destroyAllWindows()
path = 'data'
# 初始化识别的方法
recog = cv2.face.LBPHFaceRecognizer_create()
# 调用函数并将数据喂给识别器训练
faces, ids = get_images_and_labels(path)
print('本次用于训练的识别码为:') # 调试信息
print(ids) # 输出识别码
# 训练模型 #将输入的所有图片转成四维数组
recog.train(faces, np.array(ids))
# 保存模型
yml = str(Total_face_num) + ".yml"
rec_f = open(yml, "w+")
rec_f.close()
recog.save(yml)
# recog.save('aaa.yml')
# 创建一个函数,用于从数据集文件夹中获取训练图片,并获取id
# 注意图片的命名格式为User.id.sampleNum
def get_images_and_labels(path):
image_paths = [os.path.join(path, f) for f in os.listdir(path)]
# 新建连个list用于存放
face_samples = []
ids = []
# 遍历图片路径,导入图片和id添加到list中
for image_path in image_paths:
# 通过图片路径将其转换为灰度图片
img = Image.open(image_path).convert('L')
# 将图片转化为数组
img_np = np.array(img, 'uint8')
if os.path.split(image_path)[-1].split(".")[-1] != 'jpg':
continue
# 为了获取id,将图片和路径分裂并获取
id = int(os.path.split(image_path)[-1].split(".")[1])
# 调用熟悉的人脸分类器
detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
faces = detector.detectMultiScale(img_np)
# 将获取的图片和id添加到list中
for (x, y, w, h) in faces:
face_samples.append(img_np[y:y + h, x:x + w])
ids.append(id)
return face_samples, ids
def write_config():
print("新人脸训练结束")
f = open('config.txt', "a")
T = Total_face_num
f.write(str(T) + " User" + str(T) + "
")
f.close()
id_dict[T] = "User" + str(T)
# 这里修改文件的方式是先读入内存,然后修改内存中的数据,最后写回文件
f = open('config.txt', 'r+')
flist = f.readlines()
flist[0] = str(int(flist[0]) + 1) + "
"
f.close()
f = open('config.txt', 'w+')
f.writelines(flist)
f.close()
'''
============================================================================================
以上是录入新人脸信息功能的实现
============================================================================================
'''
def scan_face():
# 使用之前训练好的模型
for i in range(Total_face_num): # 每个识别器都要用
i += 1
yml = str(i) + ".yml"
print("
本次:" + yml) # 调试信息
recognizer.read(yml)
ave_poss = 0
for times in range(10): # 每个识别器扫描十遍
times += 1
cur_poss = 0
global success
global img
global system_state_lock
while system_state_lock == 2: # 如果正在录入新面孔就阻塞
print("r刷脸被录入面容阻塞", end="")
pass
success, img = camera.read()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 识别人脸
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.2,
minNeighbors=5,
minSize=(int(W_size), int(H_size))
)
# 进行校验
for (x, y, w, h) in faces:
# global system_state_lock
while system_state_lock == 2: # 如果正在录入新面孔就阻塞
print("r刷脸被录入面容阻塞", end="")
pass
# 这里调用Cv2中的rectangle函数 在人脸周围画一个矩形
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
# 调用分类器的预测函数,接收返回值标签和置信度
idnum, confidence = recognizer.predict(gray[y:y + h, x:x + w])
conf = confidence
# 计算出一个检验结果
if confidence