
简介基于OpenCV与TensorFlow 2.0的CNN人脸识别实战项目核心目标是实现Windows 10屏幕的自动锁定与解锁。项目面向计算机视觉初学者、毕业设计与课程设计人群完整覆盖了摄像头图像采集、人脸区域检测、图像预处理、卷积神经网络模型构建与训练以及识别结果触发系统屏幕锁定的全部环节。OpenCV为图像处理和摄像头调用提供了底层支持TensorFlow 2.0用于搭建轻量级CNN模型二者结合使整个项目兼具教学示范与实际可用性。压缩包共7个文件包括4个Python脚本分别负责数据采集、模型训练、实时识别与主流程串联1个VBS脚本和1个BAT批处理用于Windows后台调用和屏幕锁定动作另有1个说明文档辅助快速运行。资源包整体仅13KB小巧精炼便于阅读和二次修改。目前已有302人学习下载适合作为人脸识别类课程设计或毕业设计的参考。借助该资源可以掌握OpenCV图像处理与TensorFlow 2.0建模的关键写法理解从模型训练到系统集成的完整思路稍作调整即可部署到自己的Windows环境快速形成可演示的桌面安全控制原型。1. 基于OpenCV和TensorFlow 2.0做Win10人脸识别锁屏先想清楚它到底解决什么先说结论这个方向不是让你做一个能媲美Windows Hello的生物识别系统而是用最便宜、最透明的方式给「离开工位忘锁屏」这个高频毛病兜底。它的工作原理很简单——摄像头持续捕捉画面OpenCV负责快速找出画面里的人脸TensorFlow 2.0训练出的CNN模型负责判断这张脸是不是你不是就锁屏是你回来了就解锁。适合的人也非常明确手边有普通USB摄像头、不想为了一个锁屏功能换电脑、愿意自己折腾Python代码的开发者。我自己做这个项目时最大的感受是真正的难点根本不在CNN模型本身而在「如何让程序在Win10的锁屏状态下还能活下来」。2. 先过环境关Win10上装好OpenCV与TensorFlow 2.0并跑通验证2.1 版本组合怎么选Python 3.8、OpenCV 4.x、TensorFlow 2.10系做Windows本地人脸识别环境版本选错了比写代码还折磨人。我一般会让读者固定在 Python 3.8 或 3.10OpenCV 用 pip 直接装的 opencv-python 4.x 最新稳定版TensorFlow 则推荐 2.10.x 这一代。原因是 2.10 是 Windows 上最后一个能用 pip 无脑装完、不需要自己编译CUDA组件的版本对只做CPU推理的个人项目来说完全够用。有人会问用GPU版不是更快吗对实时摄像头推理来说CNN模型如果做得够小CPU跑一帧也就几十毫秒瓶颈反而在OpenCV的人脸检测上。与其折腾CUDA和cuDNN版本匹配不如先把CPU推理这条路走通。2.2 最小安装命令先装OpenCV再装TensorFlow顺序不能乱安装上有个很多人忽略的次序问题我建议先装OpenCV再装TensorFlow。因为如果你先装完TensorFlow再装OpenCV某些情况下opencv-python会把numpy升级到TensorFlow不支持的新版导致TensorFlow在import时报错。下面是完整的最小安装命令。# 建议新建一个虚拟环境避免动系统Python python -m venv faceenv faceenv\Scripts\activate # 先装OpenCV这里装的是带GUI模块的标准版 pip install opencv-python # 再装TensorFlowCPU版本就够不用装tensorflow-gpu pip install tensorflow2.10.0装完后跑一遍验证代码确保两个库都能正常importpython -c import cv2; print(OpenCV, cv2.__version__) python -c import tensorflow as tf; print(TensorFlow, tf.__version__)这里有个容易翻车的地方不要安装 opencv-python-headless。Headless版本不带GUI支持虽然能读摄像头但涉及imshow显示窗口时会直接报错。如果你装了headless版本请先卸载再装标准版。2.3 一个隐蔽坑tenserflow这个拼写真的能让你装个寂寞标题里写的tenserflow其实是拼错的正确拼法是tensorflow。这件事在实战里真的坑过人有人直接从标题复制关键词去pip install tenserflow结果PyPI上确实存在一些名字相近的包装完之后import tensorflow照样报ModuleNotFoundError。我身边同事就曾因为手滑多打一个s排查了半个下午。验签方式是看pip list里到底有没有tensorflowpip list | findstr tensorflow如果显示的是tenserflow之类乱七八糟的名字直接卸载重装正确包。顺带一提OpenCV这边的常见报错是ModuleNotFoundError: No module named cv2——出现这个先别急着重装确认你当前激活的是哪个虚拟环境八成是环境搞混了。3. 从零造数据用OpenCV采集人脸样本并完成预处理3.1 采集脚本Haar Cascade人脸检测加定时截帧CNN训练需要数据。你的训练集不需要像学术项目那样弄几万张我一般建议给每个要被识别的人采集150到200张人脸图。采集工具直接用OpenCV自带的Haar Cascade它在正脸检测上虽然不如深度学习检测器准但胜在轻量实时采集完全够用。import cv2 import os import time # 加载OpenCV自带的人脸检测模型 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) cap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(摄像头打不开先检查驱动或占用) save_dir dataset/me # 建议一个人一个文件夹 os.makedirs(save_dir, exist_okTrue) count 0 last_save time.time() while count 200: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(120, 120) ) for (x, y, w, h) in faces: # 稍微往外扩一点把额头和下巴都框进去 margin 20 x1 max(0, x - margin) y1 max(0, y - margin) x2 min(frame.shape[1], x w margin) y2 min(frame.shape[0], y h margin) face_img frame[y1:y2, x1:x2] # 控制保存频率避免连续帧过于相似 if time.time() - last_save 0.2: cv2.imwrite(f{save_dir}/{count:04d}.jpg, face_img) count 1 last_save time.time() print(f已保存 {count} 张, end\r) cap.release() cv2.destroyAllWindows()这里的两个关键参数是scaleFactor和minSize。scaleFactor1.1表示每层缩放步长是10%越小检测越慢但越准minSize设为(120,120)能有效过滤掉远处的小脸避免把背景里的海报脸也采进来。保存时做0.2秒限流是因为摄像头每秒30帧不限制的话相邻帧几乎一样训练出来容易过拟合。3.2 预处理灰度、尺寸统一、直方图均衡化采集到的人脸图尺寸不一致必须先统一再喂给模型。常用的输入尺寸是160x160或128x128太小丢失特征太大增加计算量。我习惯用160x160灰度图因为灰度图只有一个通道训练速度比三通道快很多而肤色信息对识别身份作用不大。预处理还包括直方图均衡化这一步对光照变化很有效。你工位靠窗上午和下午的光线能差好几个档位直方图均衡化能让模型看到的人脸明暗分布更稳定。处理代码如下def preprocess_face(img_path, target_size(160, 160)): img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if img is None: return None img cv2.resize(img, target_size) img cv2.equalizeHist(img) # 直方图均衡化 return img值得一提在实时推理阶段也要对这同一个函数处理后的数据做归一化即除以255.0。如果训练和推理的预处理流程不一致模型精度会明显下降这是很多项目部署后效果变差的隐藏原因。3.3 数据目录组织与数据集切分训练集和验证集要分开。我一般按这个目录结构组织用TensorFlow的image_dataset_from_directory可以直接加载不需要手写数据迭代器dataset/ train/ me/ # 你自己的照片160张 colleague/ # 同事的照片160张 stranger/ # 路人照片160张 val/ me/ # 每类留20张做验证 colleague/ stranger/切分比例建议每类照片按8:2分到train和val。有个实操技巧实际部署时陌生人这个类很重要。你不可能提前收集所有“非你”的人脸所以陌生人样本尽量多样化——找同事、朋友甚至公开数据集里不同人种的照片都可以。CNN分类器本质上是在学“你和别人有什么不同”陌生人样本越杂模型越不容易把某个路人误认成你。4. 训练CNN用TensorFlow 2.0的Keras搭一个能部署的人脸分类器4.1 模型结构三层卷积加全连接参数量控制在百万以内不要一上来就上ResNet50或者MobileNet这些预训练大模型。一是因为你的训练数据量小大模型会严重过拟合二是因为本地CPU推理模型太大延迟扛不住。我用的网络结构非常简单三层卷积提取特征两层全连接做分类参数量大约50万上下CPU上单次推理不到20毫秒。import tensorflow as tf from tensorflow.keras import layers, models model models.Sequential([ layers.Input(shape(160, 160, 1)), layers.Conv2D(32, (3, 3), activationrelu, paddingsame), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activationrelu, paddingsame), layers.MaxPooling2D((2, 2)), layers.Conv2D(128, (3, 3), activationrelu, paddingsame), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dropout(0.5), layers.Dense(64, activationrelu), layers.Dense(3, activationsoftmax) # 3类你/同事/陌生人 ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), losssparse_categorical_crossentropy, metrics[accuracy] ) model.summary()input_shape里的(160, 160, 1)对应160x160的单通道灰度图。最后一个Dense层的3对应你数据集里的类别数如果你的项目只识别你自己和陌生人两类这里改成2。Dropout(0.5)放在全连接前作用是随机丢弃一半神经元这是小数据集防过拟合性价比最高的一招。4.2 训练配置数据增强、EarlyStopping和模型保存训练集只有几百张图必须做数据增强。TensorFlow的ImageDataGenerator可以自动对图片做随机旋转、平移、翻转相当于把数据集凭空扩大了几倍。from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint train_datagen ImageDataGenerator( rescale1.0 / 255, # 归一化到0-1 rotation_range15, # 随机旋转15度 width_shift_range0.1, # 水平平移 height_shift_range0.1, horizontal_flipTrue # 水平翻转 ) # 验证集只做归一化不做增强 val_datagen ImageDataGenerator(rescale1.0 / 255) train_ds train_datagen.flow_from_directory( dataset/train, target_size(160, 160), color_modegrayscale, batch_size32, class_modesparse ) val_ds val_datagen.flow_from_directory( dataset/val, target_size(160, 160), color_modegrayscale, batch_size32, class_modesparse ) callbacks [ EarlyStopping(patience10, restore_best_weightsTrue), ModelCheckpoint(face_model.h5, save_best_onlyTrue) ] history model.fit( train_ds, validation_dataval_ds, epochs50, callbackscallbacks )EarlyStopping的patience我设为10意思是连续10个epoch验证集准确率不提升就停止训练同时restore_best_weights会把模型权重回滚到验证集表现最好的那个状态。这个小细节能避免你训练到第40个epoch时模型已经过拟合了还被保存下来。4.3 训练完成后从h5转到SavedModel格式Keras保存的h5文件可以直接用tf.keras.models.load_model加载但在实时推理项目里我更推荐保存为SavedModel格式。它更规范加载更快后续就算换到TensorFlow Serving也能直接用。# 先加载训练好的h5权重 best_model tf.keras.models.load_model(face_model.h5) # 转换并保存为SavedModel格式 best_model.save(face_saved_model, save_formattf) # 验证重新加载成功 loaded_model tf.keras.models.load_model(face_saved_model) print(loaded_model.predict( tf.zeros((1, 160, 160, 1)), verbose0 ).shape)这里有个容易踩的坑h5和SavedModel是两种不同格式加载方式不一样。h5用load_model(face_model.h5)SavedModel用load_model(face_saved_model)指文件夹路径。如果你路径写错TensorFlow不会给你友好提示而是直接抛出一大堆看不懂的栈信息。5. 实时推理与Win10锁定解锁双阶段识别加系统API集成5.1 双阶段推理OpenCV先找人脸CNN再判断身份把模型部署到实时摄像头流程里我强烈不建议让模型直接在整帧画面上做检测——CNN滑窗检测既慢又容易误判。正确做法是双阶段OpenCV的Haar Cascade快速框出人脸区域再把这块区域缩放后交给CNN判断是谁。前面说过Haar Cascade可能框不准但框出大概位置足够CNN对位置误差有一定容忍度。import cv2 import numpy as np import tensorflow as tf # 加载训练好的模型和OpenCV人脸检测器 model tf.keras.models.load_model(face_saved_model) face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) # 类别顺序要和训练时data目录里的文件夹顺序一致 class_names [me, colleague, stranger] cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(100, 100) ) for (x, y, w, h) in faces: face_region gray[y:yh, x:xw] face_region cv2.resize(face_region, (160, 160)) face_region cv2.equalizeHist(face_region) face_region face_region.astype(np.float32) / 255.0 face_region face_region.reshape(1, 160, 160, 1) pred model.predict(face_region, verbose0)[0] class_id int(np.argmax(pred)) confidence float(pred[class_id]) print(f{class_names[class_id]}: {confidence:.2f}) cap.release()注意推理时做了和在训练时完全相同的预处理步骤缩放、直方图均衡化、除以255、reshape成四维张量。很多人训练时效果不错部署到摄像头里就翻车八成是漏了直方图均衡化这一步。5.2 锁屏与解锁LockWorkStation锁定模拟密码解锁要绕开安全桌面现在进入这个项目里坑最深的部分。先讲锁定这部分很成熟调用Windows API即可import ctypes # 立即锁定Windows工作站 ctypes.windll.user32.LockWorkStation()真正让人头疼的是解锁。直接模拟键盘输入密码进锁屏界面在Win10上是不可靠的——因为LockWorkStation之后系统会切换到安全桌面普通程序的SendInput和keybd_event默认无法向安全桌面注入按键。网上教程里那些keybd_event输入密码解锁的代码在Win7上还能碰运气Win10上大概率无效。我经历过的最直观的翻车现场是程序判断是我回来了执行了解锁命令密码也输入了屏幕还是锁着的。所以工程上更常见的做法是不要追求真正的系统级解锁而是用关闭显示器来替代锁屏。原理是关闭显示器后系统会话并未锁定程序仍在正常工作应用层API可以随时把屏幕再打开。防偷窥效果接近实现难度却低一个量级。import ctypes import time # 关闭显示器相当于视觉上的锁屏 ctypes.windll.user32.SendMessageW( 0xFFFF, # HWND_BROADCAST 0x0112, # WM_SYSCOMMAND 0xF170, # SC_MONITORPOWER 2 # 2 关闭显示器 ) # 等待识别到本人后重新打开显示器 ctypes.windll.user32.SendMessageW( 0xFFFF, 0x0112, 0xF170, -1 # -1 打开显示器 ) # 再配合移动一下鼠标防止某些笔记本屏幕处于休眠唤醒状态 ctypes.windll.user32.mouse_event(0x0001, 1, 1, 0, 0)除此之外还要禁止系统在关屏后进入睡眠否则摄像头也跟着断电整个程序就瞎了。用SetThreadExecutionState告诉Windows程序正在工作不允许休眠# ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED ctypes.windll.user32.SetThreadExecutionState(0x80000000 | 0x00000001 | 0x00000002)把上面这些组合成一个完整的主循环检测到陌生人脸且当前屏幕亮着就关屏检测到本人脸且当前屏幕灭着就亮屏。这个逻辑实现简单在绝大多数Win10机器上都能稳定跑起来。5.3 排查清单锁屏后程序被杀、摄像头被占用、误锁与漏锁现象屏幕关闭后摄像头指示灯突然熄灭程序像是被挂起了。 原因系统在显示器关闭后进入了睡眠或现代待机导致USB摄像头断电。另外某些笔记本的摄像头有隐私开关屏幕关闭会联动切断摄像头电源。 解决调用SetThreadExecutionState防止睡眠检查笔记本是否有摄像头物理开关或BIOS里的Camera Power选项调到Always On。现象程序每次都报cv2.error: OpenCV(4.4.0)摄像头打不开。 原因摄像头被其他程序占用常见的有腾讯会议、钉钉、Windows相机应用。有些程序关闭后进程没退干净摄像头还被占着。 解决关掉所有可能占用摄像头的应用然后重启当前Python进程。排查时用任务管理器看是否有残留进程直接把进程结束再试。现象明明是同事路过程序没锁屏或者本人在屏幕前坐了半小时屏幕突然关了。 原因CNN置信度阈值设得太低。阈值低于0.7时误锁概率显著上升高于0.95时可能漏锁真人识别不出来。 解决在识别代码里加一个置信度判断低于阈值的画面一律当作“未知人”处理。阈值不是拍脑袋定的我一般会让程序后台跑一天记录所有预测结果的置信度分布取本人和陌生人分布之间最明显的分界点。6. 进阶玩法与验证置信度日志、开机自启和延迟优化6.1 用置信度日志摸清模型真实水平识别效果好不好不能靠感觉要记录数据。我会在推理循环里把每一帧的预测结果、置信度和时间写进一个CSV文件跑完一天之后用Excel或者pandas做统计分析。看两个关键指标一是本人视频里被识别成陌生人的比例二是陌生人视频里被识别成本人的比例。前者影响使用体验后者影响安全性。import csv from datetime import datetime with open(recognition_log.csv, a, newline) as f: writer csv.writer(f) writer.writerow([ datetime.now().isoformat(), class_names[class_id], round(confidence, 3) ])如果你的模型把同事错认成你自己不要急着调阈值先检查数据集里这两类样本是否足够多、是否多样。很多情况下是训练时把某个人的照片放在了一个类里但推理时换了光线或角度模型就张冠李戴了。补充更多样化的训练数据比调阈值有效得多。6.2 开机自启与摄像头预热顺序程序要开机就在后台跑常见做法是丢一个快捷方式到启动文件夹但在笔记本上有个容易忽略的点登录Windows之前摄像头可能还没被初始化程序启动太早会导致VideoCapture打不开。我一般用任务计划程序设置「用户登录时启动」并加上30秒延迟。# 创建开机启动任务延迟30秒执行 schtasks /Create /TN FaceLock /TR C:\path\to\faceenv\python.exe C:\path\to\main.py /SC ONLOGON /DELAY 0000:30 /RL LIMITED主程序里再写一个重试机制如果摄像头打开失败等5秒重试最多重试10次。这个逻辑简单但救命很多程序就是死在开机自启后第一次摄像头初始化失败上。6.3 把项目做成不过时的方向多用户识别与活体检测做完单机版之后你其实已经踩过了人脸识别落地的完整链条数据采集、模型训练、实时推理、系统集成。再往前走有三个方向多用户识别就是把分类器的输出类别扩到N个人活体检测是为了防止照片骗过系统可以用OpenCV的眨眼检测或红外深度相机子系统联动则是识别成功后可以顺便打开门禁或解锁软件。说点个人的血泪经验当初我就是没把安全桌面这个事研究透在模拟键盘解锁上耗了一周最后放弃真锁屏改用关屏方案整个项目一天内跑通。这个思路给了我一个教训——遇到系统级限制不要硬刚换个实现路径往往是更好的工程选择。如果你也打算在这个方向投入我的建议是先把关屏替代锁屏跑通再决定要不要往真正的系统解锁上深挖。希望帮到你。本文还有配套的精品资源点击获取