训练数据文件夹内按照姓名分文件夹存储照片,训练完成后需要保存模型文件。打开模型文件,打开照片就可以进行识别。
需要安装CMAKE和相关库(按照import),界面是Tkinter生成的简单界面。
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import os
import pickle
import face_recognition
from PIL import Image, ImageTk, ImageDraw
import numpy as np
class FaceRecognitionApp:
def __init__(self, root):
self.root = root
self.root.title("人脸识别人工界面应用")
self.root.geometry("1200x800")
# 设置中文字体
self.font = ('SimHei', 10)
# 存储已知人脸的编码和名称
self.known_face_encodings = []
self.known_face_names = []
# 当前处理的图像
self.current_image = None
self.current_image_path = None
self.recognized_image = None
# 创建界面
self.create_widgets()
def create_widgets(self):
# 创建顶部按钮框架
top_frame = tk.Frame(self.root)
top_frame.pack(fill=tk.X, padx=10, pady=10)
# 训练按钮
self.train_btn = tk.Button(
top_frame, text="训练模型", command=self.train_model, font=self.font)
self.train_btn.pack(side=tk.LEFT, padx=5)
# 加载编码文件按钮
self.load_encodings_btn = tk.Button(
top_frame, text="加载编码文件", command=self.load_encodings, font=self.font)
self.load_encodings_btn.pack(side=tk.LEFT, padx=5)
# 选择图片按钮
self.select_image_btn = tk.Button(
top_frame, text="选择图片", command=self.select_image, font=self.font, state=tk.DISABLED)
self.select_image_btn.pack(side=tk.LEFT, padx=5)
# 识别按钮
self.recognize_btn = tk.Button(
top_frame, text="开始识别", command=self.recognize_faces, font=self.font, state=tk.DISABLED)
self.recognize_btn.pack(side=tk.LEFT, padx=5)
# 创建图像显示框架
image_frame = tk.Frame(self.root)
image_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 原图显示区域
self.original_frame = tk.LabelFrame(image_frame, text="原图", font=self.font)
self.original_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
self.original_label = tk.Label(self.original_frame)
self.original_label.pack(fill=tk.BOTH, expand=True)
# 结果显示区域
self.result_frame = tk.LabelFrame(image_frame, text="识别结果", font=self.font)
self.result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5)
self.result_label = tk.Label(self.result_frame)
self.result_label.pack(fill=tk.BOTH, expand=True)
# 创建结果列表框架
results_frame = tk.LabelFrame(self.root, text="识别结果", font=self.font)
results_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 提取结果列表和滚动条的创建到单独方法
self._create_results_listbox(results_frame)
# 状态栏
self.status_var = tk.StringVar()
self.status_var.set("就绪")
self.status_bar = tk.Label(self.root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W, font=self.font)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
def _create_results_listbox(self, results_frame):
"""创建结果列表和滚动条"""
scrollbar = tk.Scrollbar(results_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.results_listbox = tk.Listbox(
results_frame, yscrollcommand=scrollbar.set, font=self.font)
self.results_listbox.pack(fill=tk.BOTH, expand=True)
scrollbar.config(command=self.results_listbox.yview)
def train_model(self):
"""训练人脸识别模型"""
train_dir = filedialog.askdirectory(title="选择训练图片目录")
if not train_dir:
return
try:
self.status_var.set("正在训练模型...")
total_images = self._count_total_images(train_dir)
progress_window, progress_label, progress_var, current_file_label = self._create_progress_window(total_images)
known_encodings, known_names, processed_images, failed_images = self._process_training_images(
train_dir, total_images, progress_window, progress_label, progress_var, current_file_label
)
progress_window.destroy()
self._handle_training_results(known_encodings, known_names, processed_images, failed_images)
except Exception as e:
self._handle_training_error(e)
def _count_total_images(self, train_dir):
total_images = 0
for person_name in os.listdir(train_dir):
person_dir = os.path.join(train_dir, person_name)
if not os.path.isdir(person_dir):
continue
total_images += len(os.listdir(person_dir))
return total_images
def _create_progress_window(self, total_images):
progress_window = tk.Toplevel(self.root)
progress_window.title("训练进度")
progress_window.geometry("400x200")
progress_window.transient(self.root)
progress_window.grab_set()
progress_label = tk.Label(progress_window, text="准备开始训练...", font=self.font)
progress_label.pack(pady=10)
progress_var = tk.DoubleVar()
progress_bar = ttk.Progressbar(progress_window, variable=progress_var, maximum=total_images)
progress_bar.pack(fill=tk.X, padx=20, pady=10)
current_file_label = tk.Label(progress_window, text="", font=self.font)
current_file_label.pack(pady=10)
return progress_window, progress_label, progress_var, current_file_label
def _process_training_images(self, train_dir, total_images, progress_window, progress_label, progress_var, current_file_label):
known_encodings = []
known_names = []
processed_images = 0
failed_images = []
valid_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']
for person_name in os.listdir(train_dir):
person_dir = os.path.join(train_dir, person_name)
if not os.path.isdir(person_dir):
continue
for filename in os.listdir(person_dir):
image_path = os.path.join(person_dir, filename)
current_file_label.config(text=f"正在处理: {person_name}/{filename}")
progress_window.update()
file_ext = os.path.splitext(filename)[1].lower()
if file_ext not in valid_extensions:
self.status_var.set(f"跳过非图片文件: {filename}")
continue
processed_images += 1
progress_var.set(processed_images)
progress_label.config(text=f"已处理 {processed_images}/{total_images} 张图片")
try:
image = face_recognition.load_image_file(image_path)
face_locations = face_recognition.face_locations(image, number_of_times_to_upsample=2)
if len(face_locations) > 0:
face_encoding = face_recognition.face_encodings(image, face_locations)[0]
known_encodings.append(face_encoding)
known_names.append(person_name)
self.status_var.set(f"已成功从 {filename} 中提取人脸")
else:
self.status_var.set(f"警告: 在 {filename} 中未找到人脸")
failed_images.append(image_path)
except Exception as e:
self.status_var.set(f"错误: 处理 {filename} 时出错: {str(e)}")
failed_images.append(image_path)
return known_encodings, known_names, processed_images, failed_images
def _handle_training_results(self, known_encodings, known_names, processed_images, failed_images):
if known_encodings:
save_path = filedialog.asksaveasfilename(
title="保存编码文件",
defaultextension=".pkl",
filetypes=[("Pickle Files", "*.pkl")]
)
if save_path:
self._save_encodings(save_path, known_encodings, known_names)
success_count = len(known_names)
success_rate = success_count / processed_images * 100 if processed_images > 0 else 0
summary = (f"模型训练完成!\n"
f"总处理图片数: {processed_images}\n"
f"成功提取人脸数: {success_count}\n"
f"成功率: {success_rate:.2f}%\n\n"
f"已保存 {len(known_names)} 个人脸编码")
self.status_var.set(summary)
messagebox.showinfo("成功", summary)
if failed_images:
failed_summary = "以下图片未能检测到人脸:\n" + "\n".join(failed_images)
messagebox.showwarning("注意", failed_summary)
else:
self.status_var.set("模型训练已取消")
else:
error_msg = (f"错误: 在训练图片中未找到人脸!\n"
f"总处理图片数: {processed_images}\n\n"
f"可能的原因:\n"
f"1. 图片中没有清晰的人脸\n"
f"2. 人脸太小或模糊\n"
f"3. 图片格式不支持\n"
f"4. 图片质量问题(如光照不足)\n\n"
f"请确保图片中包含清晰可见的人脸")
self.status_var.set("错误: 在训练图片中未找到人脸")
messagebox.showerror("错误", error_msg)
if failed_images:
failed_summary = "未能检测到人脸的图片:\n" + "\n".join(failed_images)
messagebox.showwarning("失败的图片", failed_summary)
def _save_encodings(self, save_path, known_encodings, known_names):
data = {
'encodings': known_encodings,
'names': known_names
}
with open(save_path, 'wb') as f:
pickle.dump(data, f)
def _handle_training_error(self, e):
self.status_var.set(f"训练过程中出错: {str(e)}")
messagebox.showerror("错误", f"训练过程中出错: {str(e)}")
def load_encodings(self):
"""加载人脸编码文件"""
file_path = filedialog.askopenfilename(
title="选择编码文件",
filetypes=[("Pickle Files", "*.pkl")]
)
if file_path:
try:
with open(file_path, 'rb') as f:
data = pickle.load(f)
self.known_face_encodings = data['encodings']
self.known_face_names = data['names']
self.status_var.set(f"已加载 {len(self.known_face_names)} 个人脸编码")
self.select_image_btn.config(state=tk.NORMAL)
messagebox.showinfo("成功", f"已成功加载 {len(self.known_face_names)} 个人脸编码")
except Exception as e:
self.status_var.set(f"加载编码文件失败: {str(e)}")
messagebox.showerror("错误", f"加载编码文件失败: {str(e)}")
def select_image(self):
"""选择要识别的图片"""
file_path = filedialog.askopenfilename(
title="选择图片",
filetypes=[("Image Files", "*.png;*.jpg;*.jpeg")]
)
if file_path:
self.current_image_path = file_path
self.display_image(self.original_label, file_path)
self.recognize_btn.config(state=tk.NORMAL)
self.status_var.set(f"已选择图片: {os.path.basename(file_path)}")
self.results_listbox.delete(0, tk.END) # 清除之前的结果
# 清除结果图像区域
self.result_label.config(image="")
self.result_label.config(text="识别结果将显示在这里")
def display_image(self, label, image_path, size=(500, 500)):
"""在标签中显示图片"""
try:
image = Image.open(image_path)
# 调整图片大小以适应显示区域,但保持比例
image.thumbnail(size, Image.LANCZOS)
photo = ImageTk.PhotoImage(image=image)
# 保存对图片的引用,防止被垃圾回收
label.image = photo
label.config(image=photo, text="")
# 保存当前显示的图像
self.current_image = image
except Exception as e:
self.status_var.set(f"显示图片失败: {str(e)}")
messagebox.showerror("错误", f"显示图片失败: {str(e)}")
def recognize_faces(self):
"""识别图片中的人脸"""
if not self.current_image_path or not self.known_face_encodings:
return
try:
self.status_var.set("正在识别...")
self.results_listbox.delete(0, tk.END)
# 加载图像
image = face_recognition.load_image_file(self.current_image_path)
# 找到图像中所有人脸的位置和编码
face_locations = face_recognition.face_locations(image)
face_encodings = face_recognition.face_encodings(image, face_locations)
# 转换为PIL图像以便绘制
pil_image = Image.fromarray(image)
draw = ImageDraw.Draw(pil_image)
# 成功识别的人脸计数
recognized_count = 0
# 遍历检测到的每个人脸
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
# 对比人脸编码,寻找匹配
matches = face_recognition.compare_faces(self.known_face_encodings, face_encoding)
name = "Unknown"
# 如果有匹配,使用距离最近的人脸
face_distances = face_recognition.face_distance(self.known_face_encodings, face_encoding)
best_match_index = np.argmin(face_distances)
if matches[best_match_index]:
name = self.known_face_names[best_match_index]
# 计算置信度 (距离越小,置信度越高)
confidence = 1 - face_distances[best_match_index]
# 只对置信度大于等于0.7的人脸进行标记
if confidence >= 0.7:
recognized_count += 1
# 在结果列表中添加识别结果
result_text = f"位置: ({left}, {top}), ({right}, {bottom}) - 识别为: {name} - 置信度: {confidence:.2%}"
self.results_listbox.insert(tk.END, result_text)
# 在人脸周围绘制矩形和标签
draw.rectangle(((left, top), (right, bottom)), outline=(0, 0, 255), width=2)
# 绘制标签背景和文本
text = f"{name} ({confidence:.2%})"
# 获取文本边界框
text_bbox = draw.textbbox((0, 0), text)
text_width = text_bbox[2] - text_bbox[0] # 宽度 = 右边界 - 左边界
text_height = text_bbox[3] - text_bbox[1] # 高度 = 下边界 - 上边界
draw.rectangle(((left, bottom - text_height - 10), (right, bottom)), fill=(0, 0, 255))
draw.text((left + 5, bottom - text_height - 5), text, fill=(255, 255, 255))
else:
# 对置信度不足的人脸,在结果中显示但不标记
result_text = f"位置: ({left}, {top}), ({right}, {bottom}) - 识别为: Unknown - 置信度不足: {confidence:.2%}"
self.results_listbox.insert(tk.END, result_text)
# 释放绘制对象
del draw
# 保存识别后的图像
self.recognized_image = pil_image
# 显示识别结果图像
result_image_path = os.path.splitext(self.current_image_path)[0] + "_result.jpg"
pil_image.save(result_image_path)
self.display_image(self.result_label, result_image_path)
self.status_var.set(f"识别完成,共检测到 {len(face_locations)} 张人脸,成功识别 {recognized_count} 张")
except Exception as e:
self.status_var.set(f"识别过程中出错: {str(e)}")
messagebox.showerror("错误", f"识别过程中出错: {str(e)}")
if __name__ == "__main__":
root = tk.Tk()
app = FaceRecognitionApp(root)
root.mainloop()