一、项目简介
此项目一般适用于门禁系统、刷脸入园等场景。利用Python中的OpenCV库中的Haar级联分类器检测人脸“haarcascade_frontalface_default.xml”,来对人脸数据进行采集训练以及识别。
通过Python中的 tkinter 库来创建图形用户界面,实现交互功能,以及界面的跳转。
以模块化为主,用两个软件包来分别实现信息采集训练和识别功能,主模块对两个软件包进行调用。但后期的优化和调试为了方便,所以主文件也就是运行文件的新增功能并没有进行清晰的模块化处理,只是用 def 函数做了简单的区分。
OpenCV - Open Computer Vision Libraryhttps://opencv.org/
二、人脸信息采集提取训练模块
利用 cv2.face
模块中的Haar级联分类器检测人脸“haarcascade_frontalface_default.xml”,以及其中的 detectMultiScale() 函数来实现基于图像训练的人脸识别任务。
通过调用 PIL 库中的 “Image”、“ImageTk” 以及 numpy库 中numpy.array() 等来将图片先转化为灰度(“L”) 图片,然后转为NumPy数组(uint8)的格式将信息进行存储,以待下一步操作。
对于进行训练的图片中的人脸名称的对应,则需要在添加之前就进行命名,格式为:”XXX.png“,然后,调用 os 库,通过列表推导式生成完整图片路径列表,用 os.listdir(path) 获取目录下所有文件名,再利用os.path.join() 拼接路径与文件名,将所有文件的完整路径存储到 imagePath中。
之后就是本模块的核心部分,建立“id_mapping”字典,然后对 imagePath 进行循环,读取每一张照片,并设置id(从0开始),通过 id 与图片的名称建立起直接联系,从而变相的实现一一对应。将这些图像数组和id标签数组传入到主函数中,调用”cv2.face.LBPHFaceRecognizer_create“对传入的数据进行训练,得到基本的识别数据,供后续人脸识别时使用。同时将识别数据写入到“.yml”文件中,以供下次调用和读取以及更新。随后,以“utf-8”的格式将字典 “id_mapping”中包含的信息写入 json 文件中,确保识别过程中能通过id来获对应的姓名。
cv2.face.LBPHFaceRecognizer_create()
是 OpenCV 提供的用于创建 LBPH(局部二值模式直方图)人脸识别 的方法。它属于cv2.face
模块,适用于基于图像训练的人脸识别任务。
detectMultiScale()
是 OpenCV 中用于多物体检测的函数。
参数说明:cascade.detectMultiScale(image, scaleFactor, minNeighbors, flags, minSize, maxSize)
image:输入图像(必须是 灰度图)
scaleFactor:缩放比例,每次图像缩小的比例(如 1.1 表示每次缩小 10%)
minNeighbors:最小邻近矩形数(值越大,检测越严格,误检少;值小则检测更多)
flags :可选参数,通常使用 cv2.CASCADE_SCALE_IMAGE
minSize:最小检测对象尺寸(通常设定较小值,如 (30,30))
maxSize:最大检测对象尺寸(可选)
import os
import cv2
import sys
from PIL import Image
import numpy
import time
import json
# 路径处理
def Path(path):
path = path.replace("\\", "/")
return path
# 将 Windows 风格的反斜杠(\)路径转换为 Unix 风格的正斜杠(/),确保路径格式的一致性。
# 可以确保程序在不同操作系统(如 Windows 和 Linux)中能够正确处理文件路径。
# 加载图像并提取人脸
#函数从指定目录读取图片,提取每张图片中的人脸区域,并返回对应的面部样本和标签。
def getImageAndLabels(path):
id_mapping = {}
facesSamples = []
global current
ids = []
imagePaths = [os.path.join(path, f) for f in os.listdir(path)]
face_detector = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
for imagePath in imagePaths:
PIL_img = Image.open(imagePath).convert('L')
img_numpy = numpy.array(PIL_img, 'uint8')
faces = face_detector.detectMultiScale(img_numpy)
if len(faces) == 0:
print(f"没有检测到人脸: {imagePath}")
id_str = (os.path.split(imagePath)[1].split('.')[0])
id_mapping[id_str] = current
current += 1
id = id_mapping[id_str]
for x, y, w, h in faces:
facesSamples.append(img_numpy[y:y + h, x:x + w])
ids.append(id)
return facesSamples, ids, id_mapping
# 通过读取指定目录中的所有图像,使用 Haar Cascade 分类器检测每张图片中的人脸,并将这些人脸区域提取出来。
# 对于每张图片,提取出对应的 ID,并将 ID 与人脸样本一一对应。
# 训练人脸识别模型
#TrainingPreparation 函数负责用 LBPH(局部二值模式直方图)方法训练一个人脸识别器。
def TrainingPreparation(path):
path = Path(path)
faces, ids, id_mapping = getImageAndLabels(path)
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.train(faces, numpy.array(ids))
recognizer.write("check/trainer.yml")
with open("check/id_mapping.json", "w", encoding="utf-8") as f:
json.dump(id_mapping, f, ensure_ascii=False, indent=4)
# 调用 getImageAndLabels 函数获取面部样本和对应的 ID 标签。
# 使用 OpenCV 中的 LBPHFaceRecognizer_create() 方法训练一个人脸识别模型。
# 将训练好的模型保存为 trainer.yml 文件。
# 将 id_mapping 字典保存为 id_mapping.json 文件,方便后续使用和查看。
三、人脸识别模块
基于OpenCV 和 Tkinter,使用 LBPH (局部二值模式直方图) 算法 进行人脸识别。通过 cv2.face.LBPHFaceRecognizer_create()
加载训练好的 trainer.yml
模型,并结合上一模块训练后存储的数据信息进行人脸检测。然后,对之前的json文件进行逆向转化,获取到id对应的姓名,利用检测到的人脸与训练数据进行匹配,并在 Tkinter 界面中显示结果。
import cv2
import numpy
from PIL import Image, ImageTk
import os
import tkinter
import json
import time
from tkinter import messagebox
# 1、图像读取与预处理
def Path(a):
path = a
path = path.replace("\\", "/")
with open(path, 'rb') as f:
img_data = numpy.frombuffer(f.read(), numpy.uint8)
img = cv2.imdecode(img_data, cv2.IMREAD_COLOR)
# Path 函数接收图片路径,将其转化为 OpenCV 可以读取的格式。之后进行图像的灰度化处理与直方图均衡化。
# 2、加载训练模型与 ID 映射
recogizer = cv2.face.LBPHFaceRecognizer_create()
recogizer.read('check/trainer.yml')
with open("check/id_mapping.json", "r", encoding="utf-8") as f:
id_mapping = json.load(f)
reverse_dict = {v: k for k, v in id_mapping.items()}
# 使用 LBPHFaceRecognizer 加载预训练的人脸识别模型(trainer.yml)。
# 反向 ID 映射:读取 id_mapping.json,将识别结果的 ID 映射回姓名。
# 3、人脸检测与识别
face_detector = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
faces = face_detector.detectMultiScale(gray, scaleFactor=1.001, minNeighbors=14, minSize=(50, 50))
# 使用 Haar 级联分类器 检测图像中的人脸。通过调整 scaleFactor 和 minNeighbors 来优化人脸检测精度。
# 4、识别与结果处理
id, confidence = recogizer.predict(gray[y:y+h, x:x+w])
if confidence <= best_confidence:
best_confidence = confidence
best_id = id
best_face = (x, y, w, h)
# 对每一张检测到的人脸,使用 LBPH 识别器 来预测其身份,并保存置信度最低的结果。
# 当置信度大于设定的阈值(如 80),提示用户识别不清晰,要求调整距离。
# 5、结果显示与更新
img_rgb_pass = cv2.cvtColor(img_pass, cv2.COLOR_BGR2RGB)
img_pil_pass = Image.fromarray(img_rgb_pass)
img_tk_pass = ImageTk.PhotoImage(img_pil_pass)
root.image_label_pass.configure(image=img_tk_pass)
# 识别成功后,通过 Tkinter 更新界面,显示 “识别通过” 图片和识别结果。
# 使用 cv2.cvtColor 将 OpenCV 图像转换为 Tkinter 可以显示的格式,并更新显示区域。
# 6、异常处理与用户提示
# 未检测到人脸:如果图像中没有检测到人脸,提示用户重新上传。
messagebox.showinfo("Wrong", "未检测到人脸,请重新进行识别")
# 识别模糊:当识别的置信度过高(如 80 以上)时,提示用户调整距离或重新识别。
messagebox.showinfo("Wrong", "识别结果模糊,请调整距离重新进行识别")
四、运行模块
在 Tkinter 界面上展现当前的功能,支持通过摄像头进行现场人脸识别,为了尽可能少的受到文件路径传输的限制和扰乱,所以将部分临时存文件和训练数据文件等直接在python该目录下进行了存储,因此,如果如果有路径疑问,请不要忧虑。
1. 界面功能介绍
-
打开摄像头:通过点击“打开摄像头”按钮,可以启动摄像头,显示实时视频流,准备进行人脸识别。
-
进行人脸识别:点击“进行人脸识别”按钮时,程序会截取当前摄像头画面中的人脸,保存为图片,并进行人脸识别。
-
关闭摄像头:如果你想停止摄像头视频流,可以点击“关闭摄像头”按钮来关闭它。
-
新增信息:点击此按钮进入信息录入界面,用户需输入信息进行后续的处理和识别。
2. 核心功能实现
-
摄像头视频流:通过
cv2.VideoCapture(0, cv2.CAP_DSHOW)
初始化摄像头,实时读取视频流并展示在 Tkinter 窗口中。使用cv2.rectangle()
绘制绿色矩形框标出截图区域,cv2.cvtColor()
将 BGR 图像转换为 RGB 格式,适配 Tkinter 显示。 -
图像保存:每次人脸识别截图时,程序会自动将图片保存到当前工作目录下的文件夹中,文件名通过获取当前时间进行设置来避免冲突。
-
人脸识别和训练:拍照后,保存的图像会传递给人脸识别模块进行识别。如果是新的用户,系统会引导用户上传图片或现场拍照以进行训练。训练数据存储在文件中,程序会对图片进行处理,转存到适当的目录并触发训练过程。
-
信息录入与核对:用户输入姓名和身份证号后,程序会对身份证号码进行合法性检查。通过
dateutil.relativedelta.relativedelta()
检查年龄是否符合要求,如果符合,用户可以继续后续操作。 -
考勤表:成功进行人脸识别后,程序会自动更新考勤表,显示已经完成考勤的人员名单。
3. 临时文件与训练数据存储
-
check/
文件夹:用于存储临时拍摄的照片。每次拍摄后,照片会先存储在这个文件夹,然后移动到Training/
文件夹中进行人脸训练。 -
Training/
文件夹:存储训练数据,包含已注册的用户照片,用于后续的人脸识别训练。 -
路径说明:所有的文件都直接存储在当前工作目录下,避免路径问题带来的困扰。
4. 其他功能
-
关闭窗口:在用户关闭窗口时,程序会释放摄像头资源并关闭相关界面,确保摄像头不会继续占用系统资源。
-
动态界面更新:每次进行操作时,界面都会根据当前的状态动态更新,用户可以实时看到拍摄的图片,摄像头视频流和识别结果。
5. 代码注解
# 1. 界面初始化与管理
def hide_window(main):
main.withdraw() # 隐藏窗口
def show_window(main):
main.deiconify() # 显示窗口
# 这些函数用于在图形界面中切换显示与隐藏主窗口
# 2. 摄像头控制与视频流更新
def Camera(root):
global cap
global video_label
if cap != "":
return
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
video_label = tk.Label(root)
video_label.place(x=0, y=0)
def update_frame():
ret, frame = cap.read()
if not ret:
cap.release()
return
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = Image.fromarray(frame_rgb)
img_tk = ImageTk.PhotoImage(image=img)
video_label.configure(image=img_tk)
video_label.image = img_tk # 保持引用,避免被垃圾回收
video_label.after(10, update_frame)
update_frame()
# 该部分用于初始化摄像头并展示实时视频流。视频画面每 10 毫秒更新一次
# 3. 截图与保存
def Save(root):
global cap
global Img_Path
if cap == None:
messagebox.showinfo("ERROR", "没有打开摄像头")
return
ret, frame = cap.read()
if ret:
x, y, w, h = 180, 120, 330, 280
cropped_frame = frame[y:y + h, x:x + w]
save_dir = "check_face_image/"
if not os.path.exists(save_dir):
os.makedirs(save_dir)
screenshot_filename = os.path.join(save_dir, f"{work_time}.png")
Img_Path = screenshot_filename
cv2.imwrite(screenshot_filename, cropped_frame)
messagebox.showinfo("截图", f"截图已保存")
# 4. 身份信息验证与人脸图像处理
def Get_id_name(root, main):
global id_name
id_name = entry_name.get()
if id_name == "":
messagebox.showinfo("Warning", "请先输入姓名")
return
# 显示姓名并保存
messagebox.showinfo("Good", "姓名保存成功")
id_name_show = tk.Label(main, text=f"{id_name}", font=("楷体", 12), fg="Black")
id_name_show.place(x=200, y=148)
def Get_id_number(root, main):
global id_number
id_number = entry.get()
if len(id_number) != 18:
messagebox.showinfo("Wrong", "请输入正确的18位身份证号码")
return
# 身份证验证,检查年龄范围
age = check_age(birth_year, birth_month, birth_day)
if age < 18 or age > 60:
messagebox.showinfo("Sorry", "您的年龄不符合标准,请离开")
return
messagebox.showinfo("Welcome", "您的年龄符合标准,请继续下一步操作")
# 5. 信息上传与训练准备
def OpenFile(main, button1):
global id_name
img_new_path = askopenfilename()
if img_new_path is None or img_new_path == "":
return
img = Image.open(img_new_path)
width, height = img.size
new_width = 330
new_height = int((new_width / width) * height)
img_resized = img.resize((new_width, new_height))
# 保存并准备训练
file_extension = os.path.splitext(img_new_path)[1]
img_resized_name = f"{id_name}{file_extension}"
save_dir = "Training/"
temp_dir = "check_face_image/"
# 保存并移动文件
os.makedirs(save_dir, exist_ok=True)
os.makedirs(temp_dir, exist_ok=True)
#该函数允许用户从本地上传照片并进行大小调整,然后保存到 Training/ 文件夹进行后续的训练。
# 6.考勤表显示与管理
def Summary(main, a, l):
if a == 1:
list_box.place(x=860, y=436)
if main.winfo_exists():
return
if l == 1:
return
if name not in current_name:
current_name.append(name)
now_people += 1
else:
messagebox.showinfo("提醒", f"{name}已经完成考勤")
return
# 该部分显示考勤表,管理人员每次完成考勤时会更新显示的人数。
#代码主要包括:摄像头控制、图像处理与保存、身份信息录入、上传与训练准备、考勤记录等功能模块。
#每个功能模块的实现通过 Tkinter 的图形界面进行交互,
#OpenCV 用于摄像头控制和图像处理,PIL 用于图像的显示与调整大小。
本篇文章仅提供一个整体思想和思路以及部分关键的代码,激起大家对于学习python和OpenCV的兴趣和激情,等到六月之后,将会把整理完成的详细代码和存储文件附在新的一篇博客里面,希望各位读者能够从这篇文章里面得到帮助。
须知少日拏云志,曾许人间第一流。我们还年轻,我们还有无限可能!