FEATURED · 精选文章

树莓派人脸识别实战:从环境搭建到实时视频流优化

发布时间 / 2026/9/11 18:28:37
来源 / 创域科博编辑部
栏目 / 资讯中心
树莓派人脸识别实战:从环境搭建到实时视频流优化 简介一套基于树莓派的人脸识别系统Python源码及配套文档面向毕业设计、课程项目或人脸识别入门学习。源码均经本地编译验证按文档配置好环境即可运行。包内共7个文件以5个Python脚本为主辅以Markdown说明和Word教程整体仅1.53MB结构清晰。功能上face.py为独立运行入口mysql_test.py用于验证树莓派数据库连接testopencv_camera.py检测OpenCV安装及摄像头状态faceDetection.py与uploadDetection.py需配合使用前者调用摄像头锁定人脸拍照并保存本地后者将照片上传至百度云并处理返回结果完整实现人脸检测、存储与云端识别链路。已有185人学习资源难度适中经助教审定适合需要完整可运行参考项目的同学下载使用。1. 树莓派人脸识别系统的真实边界先想清楚用什么算法再买摄像头树莓派 4B 跑人脸识别第一反应是上深度学习模型但实际做出来你会发现瓶颈从来不在算法精度而在摄像头采流方式、光照变化和树莓派的内存上限。这套“基于树莓派的人脸识别系统”看起来是毕业设计本质上是典型的边缘端人脸识别场景功耗受限、CPU 算力有限、摄像头接口固定却要把“检测、对齐、特征提取、比对”整条链路在本地跑完。很多人在树莓派上用 YOLO 或 MobileFaceNet 直接卡死换来的是 0.5 FPS 的教训。反而是 LBPH 或基于 dlib 的 128 维人脸编码在树莓派上能稳定跑到 10 FPS 以上。这篇博文把这条路拆开环境怎么搭、代码怎么写、数据集怎么采、摄像头怎么选全部按可复现的步骤来。2. 树莓派 4B 人脸识别环境搭建系统、换源与 Python 依赖安装2.1 系统选择与基础准备做树莓派人脸识别系统镜像一般只用两个分支Raspberry Pi OS Legacy基于 Buster和 Raspberry Pi OS基于 Bullseye/Bookworm。Legacy 的优势在于摄像头驱动还是老的raspistill体系OpenCV 读 CSI 摄像头很直接新版系统把相机栈换成了 libcameraOpenCV 的VideoCapture需要额外的libcamera-apps适配层很多源码从这里开始报错。如果只想让代码少踩坑我会直接用 Legacy 镜像python 版本 3.7 足够跑通 dlib 和 face_recognition后续若要上 YOLO 再换 64 位 Bookworm。准备好一张 32GB 以上的 TF 卡烧录完系统后先做两件事扩展根分区并设置固定 IP。扩展分区在raspi-config里完成但经验是开机后直接执行一次sudo raspi-config --expand-rootfs再重启否则后续装依赖时磁盘写满很难排查。2.2 换国内源与 Python 包管理器配置树莓派默认源在国外安装 opencv 和 dlib 会慢到怀疑人生。先替换 apt 源以 Buster 为例sudo sed -i s|/raspbian/|/raspbian/|g /etc/apt/sources.list sudo sed -i s|//deb.debian.org|//mirrors.tuna.tsinghua.edu.cn|g /etc/apt/sources.list sudo apt update sudo apt full-upgrade -y注意上面的第二个 sed 只替换了域名前缀实测中清华源对树莓派 armhf 架构支持完整。接下来安装 Python 依赖我的习惯是分两层装先用 apt 装系统级的 OpenCV 运行库再用 pip 装 face_recognition 相关包。sudo apt install -y python3-opencv python3-pip python3-numpy python3-scipy sudo pip3 install --upgrade pip sudo pip3 install face_recognition dlib这里有两个关键参数要说明python3-opencv是 apt 仓库里编译好的版本自带 ffmpeg 支持能省掉自行编译 OpenCV 时接近两个小时的等待时间face_recognition默认会拉取 dlib但树莓派是 arm 架构pip 找不到预编译 wheel必须走源码编译所以先手动安装 dlib确保编译期间你能看到进度而不是报错后静默退出。2.3 编译 dlib 的必调参数与常见失败点dlib 源码编译是整个环境搭建里最容易劝退的一步。官方默认的编译命令是pip install dlib但树莓派 4B 的 4GB 版本通常需要 40 到 60 分钟2GB 版本很可能因为内存不足直接 OOM。我一般会先启用 swap 再做编译sudo dphys-swapfile swapoff sudo sed -i s|CONF_SWAPSIZE100|CONF_SWAPSIZE2048|g /etc/dphys-swapfile sudo dphys-swapfile swapon然后把编译参数调成单线程避免多核并发导致内存峰值飙升。打开 dlib 的 setup.py 不需要改直接用环境变量控制sudo pip3 install --no-cache-dir dlib19.24.0编译之前务必确认已经安装 CMake 和 Boostsudo apt install -y cmake libboost-all-dev常见失败点有两个一是 Python 版本与 dlib 的 C 扩展不兼容二是编译过程中某个头文件缺失。看到error: nullptr was not declared之类的错误优先排查 gcc 版本是否太旧Buster 系统上的默认 gcc 8 是兼容的但如果升级过系统需要降级或切换至 g 8。2.4 验证人脸识别环境的最小命令环境是否可用的最直接判据不是 import 成功而是跑一次完整的检测推理。写个最小脚本验证python3 -c import cv2; import face_recognition; print(cv2.__version__); print(face_recognition.__version__)如果 face_recognition 的版本输出正常再用一张图片做检测python3 -c import face_recognition; imgface_recognition.load_image_file(test.jpg); locsface_recognition.face_locations(img); print(len(locs))执行时间超过 5 秒或内存占用超过 1GB说明 dlib 编译时没有启用 NEON 指令集优化。重新编译时设置export CXXFLAGS-O3 -mfpuneon-vfpv4 -mfloat-abihard这个参数能让树莓派上的 dlib 推理速度提升 30% 以上是很多网上教程不会写的一行。3. 人脸检测与识别核心代码从 Haar 级联到 128 维编码匹配3.1 传统 Haar 级联检测与 LBPH 识别完整实现在深入 dlib 之前先看传统方案因为它的代码最短、最容易跑通整套流程。OpenCV 自带的 Haar 级联分类器做人脸检测LBPH 做特征提取与识别组合起来就是最经典的轻量人脸识别方案。完整代码如下import cv2 import os # 创建 LBPH 识别器 recognizer cv2.face.LBPHFaceRecognizer_create() # 加载 Haar 级联检测器 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) def load_training_data(data_dir): images [] labels [] for label in os.listdir(data_dir): label_path os.path.join(data_dir, label) if not os.path.isdir(label_path): continue for img_name in os.listdir(label_path): img_path os.path.join(label_path, img_name) gray cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if gray is None: continue # 统一尺寸可以提升 LBPH 的识别稳定性 gray cv2.resize(gray, (200, 200)) images.append(gray) labels.append(int(label)) return images, np.array(labels) # 训练并保存模型 images, labels load_training_data(faces_dataset) recognizer.train(images, labels) recognizer.save(lbph_model.yml)LBPHFaceRecognizer_create有三个核心参数radius默认为 1表示 LBP 的邻域半径neighbors默认为 8指邻域采样点数grid_x和grid_y默认为 8它们决定特征直方图的分块粒度。识别阶段调用predictdef recognize_face(frame): gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(80, 80) ) for (x, y, w, h) in faces: roi cv2.resize(gray[y:yh, x:xw], (200, 200)) label, confidence recognizer.predict(roi) # LBPH 的置信度越低表示越接近 if confidence 80: cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.putText(frame, fUser {label}, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) return framedetectMultiScale的scaleFactor1.1表示每次缩放 10%值越小检测越精细但越慢minNeighbors5表示每个候选区域至少被 5 个邻近窗口确认为人脸才保留值太大会漏检侧面。LBPH 的置信度不是置信概率它的含义是直方图距离低于 80 才能视为同一个人。3.2 dlib 人脸检测与 face_recognition 的 128 维编码识别传统方案的硬伤在于光照变化后 LBPH 的识别率断崖式下降。想要更高的稳定性用 face_recognition 库它的底层是 dlib 的 HOG 人脸检测加上预训练的 ResNet 模型提取 128 维特征向量。识别逻辑不是训练分类器而是比对特征向量的欧氏距离import face_recognition def build_known_faces(known_dir): known_encodings [] known_names [] for name in os.listdir(known_dir): person_dir os.path.join(known_dir, name) for img_name in os.listdir(person_dir): img_path os.path.join(person_dir, img_name) img face_recognition.load_image_file(img_path) # 每张图可能检测到多张脸取第一张 encodings face_recognition.face_encodings(img) if encodings: known_encodings.append(encodings[0]) known_names.append(name) return known_encodings, known_names def match_faces(frame, known_encodings, known_names): # 转 RGB检测时用小尺寸提升帧率 rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) face_locations face_recognition.face_locations( rgb_frame, modelhog, number_of_times_to_upsample0 ) face_encodings face_recognition.face_encodings( rgb_frame, face_locations ) for face_encoding, face_location in zip(face_encodings, face_locations): matches face_recognition.compare_faces( known_encodings, face_encoding, tolerance0.45 ) name Unknown distances face_recognition.face_distance( known_encodings, face_encoding ) if len(distances) 0: best_match_idx np.argmin(distances) if matches[best_match_idx]: name known_names[best_match_idx] # face_location 格式是 (top, right, bottom, left) top, right, bottom, left face_location cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2) cv2.putText(frame, name, (left, top-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return frametolerance是匹配阈值默认 0.6值越小匹配越严格。实测中室内固定光照 0.4 最安全户外或光照复杂时调到 0.5 到 0.55 减少误拒。number_of_times_to_upsample是对输入图像上采样后再检测的次数每次上采样能让检测更准但耗时翻倍树莓派上务必设为 0否则实时画面直接卡死。3.3 跨设备部署技巧在 PC 上预生成特征树莓派上只做比对如果把所有图片都丢到树莓派上做编码每次启动都要重新跑一遍 ResNet 的前向推理既费时又费电。更合理的做法是在 PC 上离线处理完所有已知人脸把编码结果存成 numpy 文件import numpy as np # PC 端执行 known_encodings, known_names build_known_faces(known_people) np.save(known_encodings.npy, known_encodings) np.save(known_names.npy, np.array(known_names))树莓派启动时直接加载npy文件省掉全部编码计算known_encodings np.load(known_encodings.npy, allow_pickleTrue) known_names np.load(known_names.npy, allow_pickleTrue)这一点对毕业设计很关键因为它让树莓派的实时识别延迟从 3 秒以上降到了 300 毫秒左右给人“系统流畅”的直观感受。4. 训练自己的识别模型人脸数据采集、样本量与评估指标4.1 摄像头采集人脸样本脚本无论用哪种识别方案数据采集都是决定上限的一步。网上随便找的明星脸数据集和自采人脸混用识别率会非常不稳定。下面这个脚本用 OpenCV 从摄像头实时采集人脸区域自动保存成训练样本import cv2 import os output_dir faces_dataset/1 # 1 代表标签 os.makedirs(output_dir, exist_okTrue) cap cv2.VideoCapture(0) sample_count 0 total_samples 200 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) while sample_count total_samples: ret, frame cap.read() if not ret: continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.3, 5, minSize(100, 100)) for (x, y, w, h) in faces: # 扩大裁剪范围包含额头和下巴 margin int(w * 0.2) 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_roi cv2.resize(gray[y1:y2, x1:x2], (200, 200)) cv2.imwrite(f{output_dir}/sample_{sample_count}.jpg, face_roi) sample_count 1 cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.putText(frame, f{sample_count}/{total_samples}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(Collecting Faces, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()采集时最容易忽略的问题是姿势和光照单一。如果 200 张全是正脸直视角度的照片模型见过的人脸几乎都是一个平面的实际使用稍一低头就识别失败。正确的采集方式是坐在固定的位置分别向左、向右转头 15 度、30 度各拍若干张再配合正面自然表情。整个采集过程控制在 15 到 20 分钟内完成因为人脸在一天内的肤色和神态变化会影响特征稳定性。4.2 每个目标人物要采集多少数据数据量的判断标准很简单每人至少 100 张且至少覆盖 3 种光照条件。低于 50 张时LBPH 和 face_recognition 都会出现严重的过拟合表现为训练集识别 100%测试集跌到 60% 以下。这里采集的数据跟深度学习的“大数据”逻辑不同更强调覆盖度而不是数量。实际项目里我一般让每个目标人物分三个时间段采集上午、下午、晚上各一次每次 40 张左右间隔时间会让采集的人脸带上肤色和纹理的变化这才是模型泛化能力的来源。4.3 用分类报告验证模型好坏数据收集完验证不能只看一张图的识别结果需要统计测试集。用 face_recognition 方案做验证时给每个已知人物保留 20% 样本不参与特征库构建单独用来测试from sklearn.metrics import classification_report, confusion_matrix from sklearn.model_selection import train_test_split import numpy as np # 假设每个类别的图片已经编码成特征向量 # X 是所有样本的 128 维向量y 是标签 X np.load(all_encodings.npy) y np.load(all_labels.npy) # 用 80% 的数据构建特征库 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) predicted [] for test_vec in X_test: distances np.linalg.norm(X_train - test_vec, axis1) best_idx np.argmin(distances) predicted.append(y_train[best_idx]) print(classification_report(y_test, predicted)) print(confusion_matrix(y_test, predicted))这里用np.linalg.norm计算欧氏距离等价于 face_recognition 内部的face_distance逻辑。classification_report输出的每类 precision 和 recall 能直观看出哪些人容易混淆比如张三频繁被识别成李四说明两人的采集数据在姿态或光照分布上太接近重新采集其中一人的侧脸样本通常能解决。若对角线数字超过 95%说明数据质量和算法组合达标。5. 摄像头选型与实时视频流CSI ov5647 与 USB 摄像头的取舍5.1 两种摄像头接口的实测差异树莓派人脸识别系统里摄像头选错的后果是代码写再好也无法实时。CSI 接口的 ov5647 模块通过排线直连 GPU带宽高且延迟低实测在 640x480 分辨率下采集帧率能达到 30 FPSUSB 摄像头走 USB 2.0 总线帧率上限不高且 CPU 占用更高同时长排线在移动场景里容易松动。做门禁或固定点位识别选 CSI做临时演示或快速验证选 USB。树莓派 4B 上的 CSI 摄像头在 Legacy 系统上的启用命令很简单sudo raspi-config # 选择 Interfacing Options - Camera - Enable sudo reboot启用后先确认设备节点存在ls /dev/video*如果没有 video0说明摄像头没接好或没启用。再检查驱动模块dmesg | grep -i camera在老系统上能看到mmal或unicam相关日志即正常。如果是 Bookworm 新系统OpenCV 直接VideoCapture(0)读不到 CSI需要先跑libcamera-hello做初始化或者干脆用libcamera-vid的方式推流这又引入一层复杂度普通人很难一次搞定。5.2 OpenCV 读摄像头参数设置VideoCapture初始化之后有几个参数直接影响人脸识别的实时性。下面这段配置是基于树莓派 4B 的经验值import cv2 cap cv2.VideoCapture(0, cv2.CAP_V4L2) # 四个必调参数 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) cap.set(cv2.CAP_PROP_FPS, 30) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 关闭自动曝光固定曝光补偿 cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25) cap.set(cv2.CAP_PROP_EXPOSURE, 120)CAP_PROP_FRAME_WIDTH设为 640 而不是 1280是因为 dlib 的人脸检测在 640 宽下已经足够稳定分辨率翻倍只会让检测耗时长一倍。CAP_PROP_BUFFERSIZE1是关键OpenCV 默认内部缓冲区会积压几帧旧画面导致显示画面延迟超过 300 毫秒人脸都转头了画面才刚跟上。CAP_PROP_AUTO_EXPOSURE的值在 V4L2 驱动里 0.25 表示手动模式不同摄像头可能略有差异需要逐个试。5.3 多线程抓帧避免主循环卡顿cap.read()是阻塞式读取如果摄像头底层采集跟不上检测速度主循环就被拖住。常见做法是用一个独立线程持续抓帧主线程只拿最新一帧做识别import threading import queue frame_queue queue.Queue(maxsize1) def capture_thread(cap): while True: ret, frame cap.read() if ret: # 如果队列满了丢弃旧帧直接覆盖 if frame_queue.full(): try: frame_queue.get_nowait() except queue.Empty: pass frame_queue.put(frame) cap cv2.VideoCapture(0, cv2.CAP_V4L2) threading.Thread(targetcapture_thread, args(cap,), daemonTrue).start() while True: if not frame_queue.empty(): frame frame_queue.get() # 在这里执行 detect 和 recognize processed match_faces(frame, known_encodings, known_names) cv2.imshow(Face Recognition, processed) if cv2.waitKey(1) 0xFF ord(q): break队列大小设置为 1 而不是更大的值是为了保证处理的永远是最新帧宁丢勿积。get_nowait配合Queue.Empty异常处理是为了避免缓冲区满时主线程被阻塞。这种方式能充分利用树莓派 4B 的 4 核 CPU人脸检测线程占用一个核抓帧线程占用半个核剩余核留给系统调度整体帧率能比单线程方案提升 40%。6. 实战中提升识别率的三个硬核技巧投票机制、曝光固定与人脸质量筛选6.1 连续帧投票而不是单帧判决单帧识别结果直接作为最终判定的做法会把偶发误检放大成“识别成功”。门禁类应用的正确策略是连续 N 帧投票累计 5 帧中至少 3 帧判定为同一个人才算识别成功。在 match_faces 的主循环里维护一个计数器vote_counter {} def vote_recognize(name): for key in list(vote_counter.keys()): if key ! name: vote_counter[key] 0 if name ! Unknown: vote_counter[name] vote_counter.get(name, 0) 1 return vote_counter.get(name, 0) 3vote_counter按名字记录连续出现的次数一旦名字切换立刻清零只有同一个名字连续出现 3 次才返回 True。注意不是累计不清零那样会永久放大最初几帧的误判。这个技巧能过滤掉约 90% 的瞬间误识别。6.2 关闭自动白平衡与自动增益人脸识别最隐蔽的干扰源是摄像头的自动白平衡和自动增益。室内灯光下摄像头会随着环境光的微小波动不断调整白平衡导致同一个人在不同帧里肤色完全不同128 维编码的欧氏距离也随之抖动。用 V4L2 提供的参数把这两个自动调节关闭v4l2-ctl -d /dev/video0 -c auto_white_balance0 v4l2-ctl -d /dev/video0 -c white_balance_temperature4000 v4l2-ctl -d /dev/video0 -c exposure_auto1white_balance_temperature4000是适合室内荧光灯的色温值偏黄的暖光灯环境改为 3000户外日光改为 5500。固定色温后同一人脸的编码在一天内不会出现大幅漂移。6.3 用图像清晰度作为筛选条件模糊帧参与比对必然产生错误距离。用拉普拉斯算子的方差作为清晰度指标低于阈值直接跳过识别环节def is_blurry(frame, threshold50): gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) laplacian_var cv2.Laplacian(gray, cv2.CV_64F).var() return laplacian_var thresholdLaplacian的方差反映图像高频分量数值越大表示边缘越锐利。在树莓派摄像头上threshold50是比较合适的起步值如果镜头焦距偏松可能需要调到 30 避免把正常画面全部误判为模糊。这个函数只需要几毫秒放在人脸检测之前执行还能顺带省掉模糊帧的检测耗时。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻