python: Face Recognition

文章目录

  • 一、项目结构
    • 系统整体架构图
    • MVC+DDD 六层分层总览
  • 二、MVC+DDD 分层架构
    • 程序入口 main.py 与全局初始化
    • 六层架构的职责边界
  • 三、各层职责与调用方向说明
    • interfaces(GUI 视图层)
    • application(服务层)
    • domain(领域模型与异常)
    • infrastructure(数据库与仓储)
    • config(配置)与 utils(工具)
    • 调用方向与依赖原则
  • 四、MVC 与 DDD 架构对比
    • 职责划分对比
    • 依赖方向对比
    • 代码位置对比
    • 优缺点对比
  • 五、总结

一、项目结构

下图展示了本系统基于 MVC+DDD 的分层架构,共分为 interfaces(GUI 视图层)、application(服务层)、domain(领域模型与异常)、infrastructure(数据库与仓储)、config(配置)、utils(工具)六层,各层职责清晰、依赖方向自上而下单向流动:

# encoding: utf-8 # 版权所有 2026 ©涂聚文有限公司™ ® # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述: # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2024.3.6 python 3.11 # os : windows 10 # database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j # Datetime : 2026/9/7 10:36 # User : geovindu # Product : PyCharm # Project : PyFaceRecognitionSystem # File : exceptions.py # -*- coding: utf-8 -*- """ 领域异常定义 封装业务层面的错误,避免在 UI 或数据层散落 try-except """ class FaceRecognitionError(Exception): """人脸识别系统基础异常类""" pass class FaceNotFoundError(FaceRecognitionError): """未检测到人脸异常""" pass class InvalidFaceNameError(FaceRecognitionError): """非法的人脸姓名异常""" pass encoding: utf-8 版权所有 2026 ©涂聚文有限公司™ ® 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 描述: Author : geovindu,Geovin Du 涂聚文. IDE : PyCharm 2024.3.6 python 3.11 os : windows 10 database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j Datetime : 2026/9/7 10:36 User : geovindu Product : PyCharm Project : PyFaceRecognitionSystem File : models.py -- coding: utf-8 -- """ DDD 领域模型 定义核心业务实体,包含属性验证逻辑,不依赖任何外部框架 """ from domain.exceptions import InvalidFaceNameError from utils.logger import logger class FaceEntity: """ 领域实体:人脸 封装人脸的核心属性和业务规则 """ def __init__(self, name: str, encoding: bytes): """ 初始化人脸实体 :param name: 人员姓名 :param encoding: 人脸特征向量(序列化后的字节流) """ if not name or not isinstance(name, str) or not name.strip(): logger.error(f"非法的人脸姓名: {name}") raise InvalidFaceNameError("姓名不能为空且必须是字符串") self.name = name.strip() self.encoding = encoding logger.debug(f"创建人脸实体成功: {self.name}") encoding: utf-8 版权所有 2026 ©涂聚文有限公司™ ® 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 描述: Author : geovindu,Geovin Du 涂聚文. IDE : PyCharm 2024.3.6 python 3.11 os : windows 10 database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j Datetime : 2026/9/7 10:33 User : geovindu Product : PyCharm Project : PyFaceRecognitionSystem File : settings.py -- coding: utf-8 -- """ 全局配置模块 集中管理数据库、日志、算法阈值等配置,方便不同环境(开发/生产)切换 """ 数据库配置 DB_URL = "sqlite:///faces.db" 连接池配置:支持高并发,防止连接耗尽 POOL_SIZE = 10 # 连接池常驻连接数 MAX_OVERFLOW = 20 # 允许超出 pool_size 的临时连接数 POOL_TIMEOUT = 30 # 获取连接超时时间(秒) POOL_RECYCLE = 3600 # 连接回收时间(秒),防止 SQLite 连接失效 算法配置 FACE_DISTANCE_THRESHOLD = 0.45 # 人脸比对距离阈值,越小越严格 日志配置 LOG_FILE = "face_system_{time:YYYY-MM-DD}.log" # 日志文件命名规则 LOG_RETENTION = "7 days" # 日志保留时间 encoding: utf-8 版权所有 2026 ©涂聚文有限公司™ ® 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 描述: Author : geovindu,Geovin Du 涂聚文. IDE : PyCharm 2024.3.6 python 3.11 os : windows 10 database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j Datetime : 2026/9/7 10:42 User : geovindu Product : PyCharm Project : PyFaceRecognitionSystem File : database.py -- coding: utf-8 -- """ 数据库基础设施模块 使用 SQLAlchemy 引擎、连接池和 scoped_session 实现严格的多线程安全 """ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, scoped_session, declarative_base from sqlalchemy.pool import QueuePool from config.settings import DB_URL, POOL_SIZE, MAX_OVERFLOW, POOL_TIMEOUT, POOL_RECYCLE from utils.logger import logger 声明式基类,所有 ORM 模型继承此类 Base = declarative_base() 创建数据库引擎,配置连接池以支持高并发 engine = create_engine( DB_URL, poolclass=QueuePool, pool_size=POOL_SIZE, max_overflow=MAX_OVERFLOW, pool_timeout=POOL_TIMEOUT, pool_recycle=POOL_RECYCLE, echo=False # 生产环境关闭 SQL 打印 ) 创建 Session 工厂 SessionFactory = sessionmaker(bind=engine, expire_on_commit=False) 使用 scoped_session 包装,基于 threading.local 为每个线程分配独立的 Session 彻底解决多线程共享 Session 导致的数据竞争和状态混乱问题 ScopedSession = scoped_session(SessionFactory) def init_db(): """初始化数据库表结构""" try: Base.metadata.create_all(engine) logger.info("数据库表结构初始化/检查完成") except Exception as e: logger.error(f"数据库初始化失败: {e}") raise encoding: utf-8 版权所有 2026 ©涂聚文有限公司™ ® 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 描述: Author : geovindu,Geovin Du 涂聚文. IDE : PyCharm 2024.3.6 python 3.11 os : windows 10 database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j Datetime : 2026/9/7 10:42 User : geovindu Product : PyCharm Project : PyFaceRecognitionSystem File : repositories.py -- coding: utf-8 -- """ 仓储模式实现 隔离 SQL 细节,提供纯 Python 的数据访问接口 """ import pickle from sqlalchemy import Column, Integer, String, LargeBinary from infrastructure.database import Base, ScopedSession from domain.models import FaceEntity from utils.logger import logger class FaceRecord(Base): """ORM 数据表定义""" tablename = 'faces' id = Column(Integer, primary_key=True, autoincrement=True, comment="主键ID") name = Column(String, nullable=False, comment="人员姓名") encoding = Column(LargeBinary, nullable=False, comment="人脸特征向量(BLOB)") class FaceRepository: """人脸数据仓储,封装所有数据库增删改查操作""" @staticmethod def add(face: FaceEntity) -&gt; None: """ 添加人脸记录 :param face: 领域实体对象 """ # 每次操作获取当前线程的 Session session = ScopedSession() try: record = FaceRecord(name=face.name, encoding=pickle.dumps(face.encoding)) session.add(record) session.commit() logger.info(f"人脸记录添加成功: {face.name}") except Exception as e: session.rollback() logger.error(f"添加人脸记录失败: {e}") raise finally: # 线程结束时必须清理 Session,防止内存泄漏 ScopedSession.remove() @staticmethod def delete(face_id: int) -&gt; None: """根据 ID 删除人脸记录""" session = ScopedSession() try: session.query(FaceRecord).filter_by(id=face_id).delete() session.commit() logger.info(f"人脸记录删除成功, ID: {face_id}") except Exception as e: session.rollback() logger.error(f"删除人脸记录失败, ID: {face_id}, 错误: {e}") raise finally: ScopedSession.remove() @staticmethod def update_name(face_id: int, new_name: str) -&gt; None: """更新人脸姓名""" session = ScopedSession() try: record = session.query(FaceRecord).filter_by(id=face_id).first() if record: record.name = new_name session.commit() logger.info(f"人脸姓名更新成功, ID: {face_id}, 新姓名: {new_name}") except Exception as e: session.rollback() logger.error(f"更新人脸姓名失败: {e}") raise finally: ScopedSession.remove() @staticmethod def get_all() -&gt; list: """获取所有人脸数据,返回元组列表 (id, name, encoding)""" session = ScopedSession() try: records = session.query(FaceRecord).all() # 将 ORM 对象转换为普通元组,避免 DetachedInstanceError return [(r.id, r.name, pickle.loads(r.encoding)) for r in records] except Exception as e: logger.error(f"获取人脸列表失败: {e}") return [] finally: ScopedSession.remove() encoding: utf-8 版权所有 2026 ©涂聚文有限公司™ ® 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 描述: Author : geovindu,Geovin Du 涂聚文. IDE : PyCharm 2024.3.6 python 3.11 os : windows 10 database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j Datetime : 2026/9/7 10:43 User : geovindu Product : PyCharm Project : PyFaceRecognitionSystem File : services.py -- coding: utf-8 -- """ 应用服务层 协调领域模型、仓储和外部库(face_recognition),处理核心业务逻辑 """ import cv2 import numpy as np import face_recognition from infrastructure.repositories import FaceRepository from domain.models import FaceEntity from domain.exceptions import FaceNotFoundError from utils.image_utils import add_chinese_text from utils.logger import logger from config.settings import FACE_DISTANCE_THRESHOLD class FaceRecognitionService: """人脸识别核心服务""" def init(self): """初始化服务,加载已知人脸到内存""" self.known_encodings = [] self.known_names = [] self.load_known_faces() def load_known_faces(self) -&gt; None: """从数据库重新加载所有已知人脸到内存列表""" logger.info("正在从数据库加载已知人脸数据...") data = FaceRepository.get_all() self.known_encodings = [item[2] for item in data] self.known_names = [item[1] for item in data] logger.info(f"成功加载 {len(self.known_names)} 个人脸数据") def capture_and_register(self, frame: np.ndarray, name: str) -&gt; bool: """ 从当前帧提取人脸并注册到数据库 :param frame: BGR 图像帧 :param name: 注册姓名 :return: 是否注册成功 """ rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) face_locations = face_recognition.face_locations(rgb_frame) face_encodings = face_recognition.face_encodings(rgb_frame, face_locations) if not face_encodings: logger.warning("当前画面未检测到人脸,无法注册") raise FaceNotFoundError("未检测到人脸,请正对摄像头") 取检测到的第一张人脸 face = FaceEntity(name, face_encodings[0]) FaceRepository.add(face) self.load_known_faces() # 注册后刷新内存 return True def process_frame(self, frame: np.ndarray) -&gt; np.ndarray: """ 处理单帧画面,执行人脸检测、比对并绘制结果 :param frame: BGR 原始图像帧 :return: 绘制了识别结果的图像帧 """ if not self.known_encodings: return frame rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) face_locations = face_recognition.face_locations(rgb_frame) face_encodings = face_recognition.face_encodings(rgb_frame, face_locations) for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings): matches = face_recognition.compare_faces(self.known_encodings, face_encoding) face_distances = face_recognition.face_distance(self.known_encodings, face_encoding) best_match_index = np.argmin(face_distances) name = "未知" if matches[best_match_index] and face_distances[best_match_index] &amp;amp;lt; FACE_DISTANCE_THRESHOLD: name = self.known_names[best_match_index] 绘制矩形框和中文名字 cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2) frame = add_chinese_text(frame, name, (left, top - 30), text_size=20) return frame def update_name(self, face_id: int, new_name: str) -&gt; None: """更新人员姓名""" FaceRepository.update_name(face_id, new_name) self.load_known_faces() def delete_face(self, face_id: int) -&gt; None: """删除人员记录""" FaceRepository.delete(face_id) self.load_known_faces() encoding: utf-8 版权所有 2026 ©涂聚文有限公司™ ® 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 描述: Author : geovindu,Geovin Du 涂聚文. IDE : PyCharm 2024.3.6 python 3.11 os : windows 10 database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j Datetime : 2026/9/7 10:44 User : geovindu Product : PyCharm Project : PyFaceRecognitionSystem File : gui.py -- coding: utf-8 -- """ MVC 视图层 (interfaces/gui.py) 纯粹负责 Tkinter 界面渲染、摄像头画面显示,不包含任何核心业务逻辑 使用 threading + queue 解决深度学习推理导致的 GUI 卡死问题 """ import cv2 import tkinter as tk from tkinter import ttk, messagebox, simpledialog from PIL import Image, ImageTk import threading import queue from application.services import FaceRecognitionService from infrastructure.repositories import FaceRepository from domain.exceptions import FaceRecognitionError from utils.logger import logger class FaceRecognitionView: """人脸识别系统 GUI 视图""" def init(self, root: tk.Tk): """ 初始化视图层 :param root: Tkinter 主窗口对象 """ self.root = root self.root.title("企业级人脸识别管理系统 (MVC+DDD)") self.root.geometry("900x500") 初始化应用服务层 self.service = FaceRecognitionService() 线程安全队列,用于子线程向主线程传递图像帧,避免跨线程操作 UI 报错 self.frame_queue = queue.Queue(maxsize=1) self.is_running = True # 控制程序运行的全局标志 self.is_recognizing = False # 控制是否开启实时识别的标志 构建 UI 布局 self._build_ui() 启动后台视频捕获与推理线程 (daemon=True 保证主程序退出时子线程自动销毁) self.video_thread = threading.Thread(target=self._video_worker, daemon=True) self.video_thread.start() 启动主线程的 UI 刷新定时器 self._update_ui() 绑定窗口关闭事件,确保资源安全释放 self.root.protocol("WM_DELETE_WINDOW", self._on_closing) def _build_ui(self) -&gt; None: """构建界面组件""" --- 左侧:视频显示区 --- left_frame = ttk.Frame(self.root, padding=10) left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.video_label = ttk.Label(left_frame) self.video_label.pack(pady=5) btn_frame = ttk.Frame(left_frame) btn_frame.pack(fill=tk.X, pady=5) ttk.Button(btn_frame, text="采集并录入", command=self._on_capture).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="开启/关闭识别", command=self._on_toggle).pack(side=tk.LEFT, padx=5) --- 右侧:人员列表区 --- right_frame = ttk.Frame(self.root, padding=10) right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) ttk.Label(right_frame, text="已录入人员列表", font=("Microsoft YaHei", 12, "bold")).pack(anchor=tk.W) self.tree = ttk.Treeview(right_frame, columns=("ID", "姓名"), show="headings", height=15) self.tree.heading("ID", text="ID") self.tree.heading("姓名", text="姓名") self.tree.pack(fill=tk.BOTH, expand=True, pady=5) op_frame = ttk.Frame(right_frame) op_frame.pack(fill=tk.X) ttk.Button(op_frame, text="修改姓名", command=self._on_update).pack(side=tk.LEFT, padx=5) ttk.Button(op_frame, text="删除记录", command=self._on_delete).pack(side=tk.LEFT, padx=5) 初始化列表数据 self._refresh_table() def _video_worker(self) -&gt; None: """ 后台工作线程:负责摄像头读取和深度学习推理 严禁在此线程中操作任何 Tkinter 控件 """ cap = cv2.VideoCapture(0) if not cap.isOpened(): logger.error("无法打开摄像头") return while self.is_running: ret, frame = cap.read() if not ret: break display_frame = frame.copy() 如果开启了识别,调用服务层处理当前帧 if self.is_recognizing: display_frame = self.service.process_frame(display_frame) 将处理后的帧放入队列,如果队列满了则丢弃旧帧,保证实时性 if not self.frame_queue.full(): self.frame_queue.put(display_frame) cap.release() logger.info("视频捕获线程已安全退出") def _update_ui(self) -&gt; None: """ 主线程定时器:负责从队列取帧并更新 Tkinter 界面 """ try: 非阻塞获取最新帧 frame = self.frame_queue.get_nowait() img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) imgtk = ImageTk.PhotoImage(image=img) self.video_label.imgtk = imgtk self.video_label.configure(image=imgtk) except queue.Empty: pass 每 10ms 检查一次队列 if self.is_running: self.root.after(10, self._update_ui) def _refresh_table(self) -&gt; None: """刷新右侧 Treeview 表格""" for item in self.tree.get_children(): self.tree.delete(item) 直接从仓储层获取数据刷新表格 for row in FaceRepository.get_all(): self.tree.insert("", tk.END, values=(row[0], row[1])) ==================== 事件处理(调用 Service 层) ==================== def _on_capture(self) -&gt; None: """处理采集录入事件""" name = simpledialog.askstring("录入人脸", "请输入该人员的姓名:") if name: try: 从队列获取当前最新帧用于注册 frame = self.frame_queue.queue[-1] if not self.frame_queue.empty() else None if frame is None: messagebox.showwarning("警告", "摄像头未就绪,无法采集") return self.service.capture_and_register(frame, name) self._refresh_table() messagebox.showinfo("成功", f"{name} 的人脸已成功录入!") except FaceRecognitionError as e: messagebox.showwarning("业务提示", str(e)) except Exception as e: logger.error(f"录入人脸时发生未知异常: {e}") messagebox.showerror("系统错误", "录入失败,请查看日志") def _on_toggle(self) -&gt; None: """处理开启/关闭识别事件""" self.is_recognizing = not self.is_recognizing status = "开启" if self.is_recognizing else "关闭" logger.info(f"实时识别已{status}") def _on_update(self) -&gt; None: """处理修改姓名事件""" selected = self.tree.selection() if not selected: messagebox.showinfo("提示", "请先在列表中选择一条记录") return item = self.tree.item(selected[0]) face_id, old_name = item['values'][0], item['values'][1] new_name = simpledialog.askstring("修改姓名", "请输入新姓名:", initialvalue=old_name) if new_name: try: self.service.update_name(face_id, new_name) self._refresh_table() except Exception as e: logger.error(f"更新姓名失败: {e}") messagebox.showerror("错误", "更新失败") def _on_delete(self) -&gt; None: """处理删除记录事件""" selected = self.tree.selection() if not selected: messagebox.showinfo("提示", "请先在列表中选择一条记录") return item = self.tree.item(selected[0]) face_id, name = item['values'][0], item['values'][1] if messagebox.askyesno("确认删除", f"确定要删除 {name} 的人脸记录吗?"): try: self.service.delete_face(face_id) self._refresh_table() except Exception as e: logger.error(f"删除记录失败: {e}") messagebox.showerror("错误", "删除失败") def _on_closing(self) -&gt; None: """ 窗口关闭事件处理 确保子线程、摄像头资源、数据库连接安全释放 """ self.is_running = False logger.info("正在关闭系统,释放资源...") self.root.destroy() encoding: utf-8 版权所有 2026 ©涂聚文有限公司™ ® 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 描述: Author : geovindu,Geovin Du 涂聚文. IDE : PyCharm 2024.3.6 python 3.11 os : windows 10 database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j Datetime : 2026/9/7 10:43 User : geovindu Product : PyCharm Project : PyFaceRecognitionSystem File : image_utils.py -- coding: utf-8 -- """ 图像处理工具模块 """ import cv2 import numpy as np from PIL import Image, ImageDraw, ImageFont def add_chinese_text(img: np.ndarray, text: str, position: tuple, text_color: tuple = (0, 255, 0), text_size: int = 20) -> np.ndarray: """ 在 OpenCV 图像上绘制中文文本 :param img: BGR 格式的 numpy 图像 :param text: 要绘制的中文文本 :param position: 文本左上角坐标 (x, y) :param text_color: 文本颜色 (B, G, R) :param text_size: 字体大小 :return: 绘制后的图像 """ if isinstance(img, np.ndarray): BGR 转 RGB img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) draw = ImageDraw.Draw(img_pil) try: 加载中文字体,Windows 下使用宋体,可根据实际路径修改 font = ImageFont.truetype("simsun.ttc", text_size, encoding="utf-8") except IOError: font = ImageFont.load_default() draw.text(position, text, text_color, font=font) RGB 转回 BGR return cv2.cvtColor(np.asarray(img_pil), cv2.COLOR_RGB2BGR) return img encoding: utf-8 版权所有 2026 ©涂聚文有限公司™ ® 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 描述: Author : geovindu,Geovin Du 涂聚文. IDE : PyCharm 2024.3.6 python 3.11 os : windows 10 database : mysql 9.0 sql server 2019, postgreSQL 17.0 Oracle 21c Neo4j Datetime : 2026/9/7 10:34 User : geovindu Product : PyCharm Project : PyFaceRecognitionSystem File : logger.py -- coding: utf-8 -- """ 统一日志配置模块 使用 loguru 实现控制台与文件同步输出,支持按天轮转,防止日志文件过大 """ import sys from loguru import logger from config.settings import LOG_FILE, LOG_RETENTION 移除默认的 logger,防止重复输出 logger.remove() 配置控制台输出格式(带颜色) logger.add( sys.stdout, format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " "<level>{level: <8}</level> | " "<cyan>{module}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>", level="DEBUG" ) 配置文件输出格式(自动轮转,保留7天) logger.add( sink=LOG_FILE, format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {module}:{line} - {message}", rotation="00:00", # 每天午夜轮转 retention=LOG_RETENTION, encoding="utf-8", level="INFO" ) 导出配置好的 logger 供全局使用 all = ["logger"]

二、MVC+DDD 分层架构

python 复制代码
# encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : PyCharm 2024.3.6 python 3.11
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/9/7 10:33
# User      :  geovindu
# Product   : PyCharm
# Project   : PyFaceRecognitionSystem
# File      : main.py
# -*- coding: utf-8 -*-
"""
程序唯一入口模块 (main.py)
职责:
1. 负责系统的全局初始化(如数据库表结构创建)。
2. 负责启动图形用户界面 (GUI) 的主事件循环。
3. 保持代码极简,不包含任何具体的业务逻辑。
 $env:PYTHONUTF8="1"
pip install opencv-python
pip install face-recognition
pip install pillow
pip install dlib
pip install sqlalchemy
pip install loguru
pip install face_recognition_models
pip install git+https://github.com/ageitgey/face_recognition_models
pip uninstall face_recognition_models -y
pip install face_recognition_models --no-cache-dir
https://github.com/ageitgey/face_recognition_models
https://github.com/ageitgey/face_recognition
"""
import sys
import tkinter as tk
from infrastructure.database import init_db
from interfaces.gui import FaceRecognitionView
from utils.logger import logger
def main():
"""
应用程序主入口函数
集中处理系统初始化、异常捕获和 GUI 启动
"""
try:
# 1. 系统级初始化:检查并创建数据库表结构
logger.info("正在启动企业级人脸识别系统...")
init_db()
    # 2. 初始化 Tkinter 主窗口
    root = tk.Tk()
# 3. 启动 MVC 视图层(内部会自动初始化 Controller 和 Service)
app = FaceRecognitionView(root)
4. 启动 Tkinter 主事件循环(程序将在此处阻塞,直到窗口关闭)
root.mainloop()
logger.info("系统已正常退出。")
except KeyboardInterrupt:
捕获用户在终端使用 Ctrl+C 强制退出的情况
logger.warning("用户通过键盘中断了程序运行。")
sys.exit(0)
except Exception as e:
捕获所有未预期的致命异常,防止程序直接崩溃无提示
logger.exception(f"系统发生致命错误,程序被迫退出: {e}")
sys.exit(1)
Python 标准入口保护:
确保当此文件被直接执行 (python main.py) 时运行主逻辑,
当被作为模块导入时不会自动触发,提升代码的可测试性和安全性。
if name == "main":
main()

三、各层职责与调用方向说明

本系统共分为六层,各层职责明确、边界清晰,调用方向自上而下单向流动,禁止反向依赖。下面逐一说明每一层的职责与调用方向。

  • interfaces(GUI 视图层):位于 interfaces/gui.py,负责 Tkinter 界面渲染、摄像头画面显示与用户事件响应。它只负责「展示与交互」,不包含任何核心业务逻辑;通过调用 application 层完成业务操作,是系统的唯一入口层。
  • application(服务层):位于 application/services.py,负责业务编排与用例协调。它接收视图层的调用请求,协调 domain 层完成业务规则校验,并调用 infrastructure 层完成数据持久化,是连接界面与领域模型的核心枢纽。
  • domain(领域模型与异常):位于 domain/models.py 与 domain/exceptions.py,承载核心业务实体 FaceEntity 与业务异常。它封装姓名校验等业务规则,不依赖任何外部框架,保持高度独立,是系统业务价值的核心所在。
  • infrastructure(数据库与仓储):位于 infrastructure/database.py 与 infrastructure/repositories.py,负责数据库连接、连接池管理与数据持久化。通过仓储模式隔离 SQL 细节,为 application 层提供纯 Python 的数据访问接口。
  • config(配置):位于 config/settings.py,集中管理数据库连接、日志、算法阈值等全局配置,方便不同环境(开发/生产)切换,被 infrastructure 与 utils 层依赖。
  • utils(工具):位于 utils/ 目录,提供日志、图像处理等通用工具函数,被 application、infrastructure 等层复用,不依赖任何业务层。

调用方向遵循「interfaces → application → domain / infrastructure」的单向依赖原则:视图层只调用服务层,服务层协调领域模型与仓储,领域层保持独立,基础设施层依赖配置与工具。这样的分层设计保证了各层职责单一、依赖清晰,便于测试与维护。

四、MVC 与 DDD 架构对比

下表从职责划分、依赖方向、代码位置、优缺点四个维度,对比 MVC 与 DDD 两种架构模式在本项目中的具体体现:

对比维度 MVC 在本项目中的体现 DDD 在本项目中的体现
职责划分 View(interfaces/gui.py)负责界面渲染与事件响应;Controller 职责由 View 内部事件处理方法承担,负责接收用户操作并调用 Service;Model 由 Service 与 Repository 共同承担数据流转。 domain 层承载核心业务实体 FaceEntity 与业务异常,封装姓名校验等业务规则;application 层负责业务编排;infrastructure 层通过仓储模式隔离数据访问细节。
依赖方向 View 依赖 Controller,Controller 依赖 Model,依赖方向为「视图 → 控制器 → 模型」的单向流动。 依赖方向为「interfaces → application → domain / infrastructure」,domain 保持独立不依赖外部框架,infrastructure 依赖 config 与 utils,形成清晰的分层边界。
代码位置 View 位于 interfaces/gui.py;Controller 逻辑内嵌于 FaceRecognitionView 的 _on_capture、_on_toggle、_on_update、_on_delete 等事件方法;Model 对应 application/services.py 与 infrastructure/repositories.py。 domain 位于 domain/models.py 与 domain/exceptions.py;application 位于 application/services.py;infrastructure 位于 infrastructure/database.py 与 infrastructure/repositories.py;config 位于 config/settings.py。
优缺点 优点:界面与业务分离清晰,适合 GUI 快速开发,事件驱动直观;缺点:Controller 与 View 耦合较紧,业务规则容易散落在事件方法中,复杂领域逻辑难以沉淀与复用。 优点:领域模型内聚、业务规则集中、可测试性与可维护性强,仓储模式隔离持久化细节;缺点:分层较多、学习成本较高,对简单 CRUD 场景略显繁重。

五、总结

总体而言,本项目以 MVC 作为界面交互的组织方式,以 DDD 作为业务核心的分层骨架,两者互补:MVC 负责「界面如何响应操作」,DDD 负责「业务规则如何沉淀与复用」。

相关推荐
LEE1 小时前
别再堆 AGENTS.md 了:前端团队如何把 AI Coding 做成一套可执行的工程系统
前端·后端
工具派1 小时前
markdown在线编辑器怎么选?渲染管线的3个坑和md转PDF跑版记录
前端·后端
平头哥技术团队1 小时前
Day 13 | 调 line-height 和 margin:三处间距让名片页脱离模板感
开发语言·前端·javascript·学习·html5
2601_954811821 小时前
AI科学实验室MHS标准解读:AI智能体如何统一控制实验室设备接口
人工智能·python
一位正在转型AI全栈的前端工程师1 小时前
AI 全栈学习之旅 -Week 9:什么是AI Agent?从Function Calling到LangGraph
前端·python
用户0332126663671 小时前
使用 Python 为 PowerPoint 设置背景色和背景图【附代码示例】
python
ever_up9731 小时前
LangChain基础知识概述1
人工智能·python·langchain
黑马程序员毕设1 小时前
基于Java的仪器管理系统设计与实现
java·开发语言·spring boot·后端·微信小程序
长大19881 小时前
执行计划看不懂?一文理清 Oracle CBO 优化器工作原理
后端