ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

课堂专注度分析系统环境搭建全攻略:OpenCV+MediaPipe+PyTorch

课堂专注度分析系统环境搭建全攻略:OpenCV+MediaPipe+PyTorch

1. 为什么环境搭建这种“杂活”值得单独写一篇

《学生课堂专注度分析系统》这个项目,我在系列前面已经交代过整体思路:通过摄像头采集课堂中学生的面部画面,用计算机视觉技术提取人脸关键点、头部姿态和眼部状态,再结合时间维度估算每位学生的专注度。听起来不算复杂,真动手就会发现,绝大部分时间不是在写算法,而是在跟环境较劲。

这个项目最劝退人的,往往是第一关。要用到的库有OpenCV、MediaPipe、PyTorch、NumPy、Pandas,它们之间的版本搭配稍微出一点偏差,轻则import报错,重则摄像头就是不出画面。更麻烦的是,很多教程默认你在Windows上点几下就能装好,可实际场景里有人用Windows,有人用Ubuntu,还有人手里只有一台CPU笔记本,环境差异能直接决定项目能不能跑起来。

我写这篇的目的很明确,就是把这套系统环境搭建的全过程,包括我试过的版本、踩过的坑、验证环境是否可用的最小demo,一次性列清楚。如果你是一个有Python基础、想做视觉方向实战项目的人,或者正在复现类似的注意力分析系统,这篇可以直接当“扣作业指南”用。文中的所有命令我都按可复现的标准整理过,照着敲基本能过。

2. 先把系统的技术架构和依赖摸清楚

2.1 功能模块决定了我们到底要装什么

在动手装环境之前,需要先想清楚这个系统将来要跑哪些功能。环境搭建从来不是“把所有库装上”就完事,而是要按功能反推依赖。我的这套分析系统核心模块大概分成下面几块。

功能模块作用主要依赖
视频采集从摄像头或视频文件读取画面OpenCV
人脸检测与关键点定位定位人脸框和468个面部关键点MediaPipe Face Mesh
头部姿态估计通过关键点计算抬头、低头、左右转头角度MediaPipe + NumPy
眼部状态分析提取眼睛纵横比、眨眼频率等特征MediaPipe + NumPy
专注度评分模型根据特征序列判断专注状态PyTorch / scikit-learn
数据记录与可视化保存日志、绘制曲线、生成报告Pandas + Matplotlib

看清楚这张表,就知道哪些库是主线,哪些是辅助。主线是OpenCV和MediaPipe,它们负责所有图像处理工作;PyTorch负责后续的模型训练与推理;Pandas和Matplotlib用来处理数据和画图。装环境时以这些为核心,不要贪多乱装。

2.2 选型逻辑:为什么是MediaPipe而不是dlib

很多人一看“人脸关键点定位”,第一反应是用dlib。dlib确实经典,但它有个让人头疼的问题:安装困难。Windows上经常需要Visual Studio编译,Linux上要装一堆系统依赖,CPU版本跑起来还慢。我早期在一个实验室老机器上装dlib,折腾了整个下午,最后编译失败。后来换成MediaPipe,十分钟就能用,且自带468个关键点的Face Mesh模型,CPU上实时表现也不错,所以这个项目我直接放弃dlib。

MediaPipe也不是没有缺点。它对Python版本卡得比较严,不同版本支持范围不同。我测试下来,Python 3.9和3.10是最稳的,3.11部分版本可以,3.12基本装不上。所以这个项目我建议直接锁定Python 3.9或者3.10,别追新。

PyTorch的选择逻辑也很简单。专注度评分这部分,我会用PyTorch训练一个小型分类模型,输入是若干帧的面部关键点特征,输出是“专注/游离/低头/转头”之类的状态。相比TensorFlow,PyTorch的调试体验更友好,且环境依赖相对干净,venv里面一套conda就够了。

2.3 硬件预案:没有GPU也能跑起来

这是我特别想强调的一点。课堂专注度分析这个场景,真正部署时往往是在普通教室的PC上,大概率没有独立显卡。所以在环境搭建阶段,就要区分两套方案:

  • 有NVIDIA GPU:安装CUDA版PyTorch,后续训练模型会快很多。
  • 只有CPU:安装CPU版PyTorch,推理阶段反而更稳,不会因为驱动问题报错。

图像处理部分完全依赖CPU也能跑。MediaPipe的Face Mesh模型在CPU上单帧大约30到60毫秒,配合OpenCV降采样到640×480分辨率,15到20帧每秒是可以实现的。对课堂记录来说,不需要高帧率,15帧足够分析。硬件最低要求就是一台能装Python的电脑,内存8GB以上,摄像头分辨率720p即可。

3. 环境搭建实操记录

3.1 建立Python虚拟环境,把依赖隔离起来

我强烈建议用Anaconda或者Miniconda创建虚拟环境,不要直接在系统Python里pip install。项目里既有OpenCV又有PyTorch,还有各种依赖,装在一起极容易版本冲突。虚拟环境就像给每个项目一个独立的小房间,互不干扰。

打开终端,执行下面的命令:

conda create -n focus python=3.9.18 -y conda activate focus python --version

环境名我起的是focus,你也可以随意换。这里选择3.9.18,是因为它刚好满足MediaPipe 0.10.x的版本要求,同时PyTorch 2.x也完全支持。激活后终端前面会出现(focus)字样,后续所有安装命令都必须在这个状态下执行。

一个小建议:执行完conda create之后,顺手升级一下pip,避免老版本的pip在装包时解析出错。

python -m pip install --upgrade pip

3.2 安装PyTorch:先搞清楚你的显卡再选命令

这一步是最容易出问题的。PyTorch分CPU版和GPU版,命令完全不同。安装前先确认一下自己的显卡到底支持到什么程度。

先打开终端输入:

nvidia-smi

如果提示找不到命令,说明没有NVIDIA驱动,直接走CPU方案。如果能看到类似下面的输出:

NVIDIA-SMI 545.84 Driver Version: 545.84 CUDA Version: 12.3

记住右上角的CUDA Version,这只是说你的驱动最高支持到12.3,不代表你已经安装了CUDA runtime。后续安装PyTorch时,只要PyTorch自带的CUDA版本比这个低或者相等,基本就能用。

以驱动支持CUDA 12.1为例,安装命令用官方推荐的方式:

conda install pytorch=2.2.2 torchvision=0.17.2 torchaudio=2.2.2 pytorch-cuda=12.1 -c pytorch -c nvidia

如果你没有GPU,或者不想折腾CUDA,直接用CPU版:

pip install torch==2.2.2 torchvision==0.17.2 --index-url https://download.pytorch.org/whl/cpu

安装完成后务必验证一下,不要等到代码跑起来才发现白装:

python -c "import torch; print(torch.__version__, torch.cuda.is_available())"

如果输出类似2.2.2+cu121 True,说明GPU版生效了。如果是CPU版,最后会显示False,但不会报错。

3.3 安装OpenCV、MediaPipe和数据处理全家桶

PyTorch装完之后,剩下的包安装相对简单。为了复现稳定,我建议给关键库固定版本。以下是实测可用的组合:

pip install opencv-python==4.9.0.80 pip install mediapipe==0.10.14 pip install numpy==1.26.4 pandas==2.2.2 matplotlib==3.9.0 scikit-learn==1.4.2 imutils==0.5.4 tqdm==4.66.2

注意一点:不要先手动装numpy再装mediapipe,因为mediapipe会自动依赖特定版本的numpy,顺序反了可能会被覆盖。我建议最先装mediapipe,然后让它把numpy带上,再根据实际需要调整。上面列出的numpy版本是我测试过兼容的。

OpenCV版本我锁在4.9.0.80,这是一个比较稳的版本。不要看到一个更新的版本就忍不住手痒,某些新版本和MediaPipe的protobuf库会有兼容隐患。服务器场景可以使用opencv-python-headless,不过本地调试建议用opencv-python,这样可以直接弹窗显示画面。

安装完之后,快速执行一次导入测试:

python -c "import cv2, mediapipe, torch, numpy, pandas; print('all imports ok')"

如果没有任何报错,说明基础环境已经通了。

3.4 测试摄像头和视频文件是否可以正常打开

库装齐了,还要确认摄像头能正常打开。很多人的代码逻辑没问题,就是卡在cv2.VideoCapture返回False。我先用一个简单的脚本测试。

python - <<'EOF' import cv2 cap = cv2.VideoCapture(0) if not cap.isOpened(): print("cannot open camera") else: ret, frame = cap.read() if ret: print("frame shape:", frame.shape) cap.release() EOF

摄像头编号默认从0开始。笔记本自带摄像头绝大多数是0,外接USB摄像头在部分Windows电脑上可能是1。如果VideoCapture(0)打不开,试着改成1,或者写成cv2.VideoCapture(1, cv2.CAP_DSHOW)。

顺便也测试一下能否读取视频文件:

python -c "import cv2; cap=cv2.VideoCapture('test.mp4'); print(cap.isOpened(), cap.read()[0])"

只需要替换成你自己的视频路径。这一步通过,说明OpenCV基础通道没问题,后续开发就有了底气。

4. 环境搭建阶段踩过的坑(必看)

4.1 Python版本导致MediaPipe装不上

最常见的坑就是把环境建在Python 3.12上,然后pip install mediapipe报错,要么找不到匹配版本,要么下载后import报错。官方对3.12的支持一直滞后,我没必要在这儿硬碰硬。

解决办法很简单:重建环境,选3.9或3.10。如果你已经建了环境又不想重建,可以尝试mediapipe的预发布版,但稳定性没有保证。我的建议是干脆删了重来:

conda deactivate conda env remove -n focus conda create -n focus python=3.9.18 -y

环境问题越早解决越省心,不要在错误的版本上修补,那只会越陷越深。

4.2 CUDA版本不匹配,显卡直接罢工

GPU版PyTorch最常见的报错是:

AssertionError: Torch not compiled with CUDA enabled

这个说明你实际安装的是CPU版PyTorch。另一种情况是:

CUDA error: no kernel image available for execution on the device

这个则说明PyTorch的CUDA编译版本和你的显卡架构不匹配。比如老显卡遇到太新的PyTorch版本,可能因为没有对应的kernel而报错。

遇到这类问题,我的处理思路是:先看nvidia-smi的驱动版本,然后在PyTorch官网找到与驱动兼容的安装命令。不要凭记忆下载安装包,一定要用官方命令生成器。动手前多花两分钟,能省后面两小时。

4.3 Linux上OpenCV报libGL.so.1缺失

如果你在Ubuntu服务器上跑代码,import cv2时很可能会报:

ImportError: libGL.so.1: cannot open shared object file

这是因为OpenCV依赖系统图形库,但轻量版系统镜像里没有。解决方式是安装系统依赖:

sudo apt-get update sudo apt-get install -y libgl1 libglib2.0-0

如果实在不想装系统依赖,可以直接改装opencv-python-headless。但要注意,headless版不提供imshow弹窗,本地调试画面不方便,所以我更推荐装了来看效果,部署到服务器时再换headless。

4.4 摄像头占用或编号飘忽不定

有次我写好了代码,连接摄像头,结果VideoCapture(0)永远是False,换1也不行。排查半天发现是另一个后台进程已经占用了摄像头。还有的情况是笔记本的摄像头被Windows Hello占用,此时程序拿不到画面。

处理方式分两步:先关掉所有可能占用摄像头的软件,再重跑测试脚本。如果还是不行,就写个小脚本遍历一下所有可用的摄像头编号。

import cv2 for i in range(5): cap = cv2.VideoCapture(i, cv2.CAP_DSHOW if cv2.__version__.startswith("4.") else 0) if cap.isOpened(): print("camera index", i, "is available") cap.release()

把这个脚本跑一遍,就能知道哪个编号可用。在Windows上,还建议在cv2.VideoCapture中加上cv2.CAP_DSHOW参数,能避免一部分USB摄像头的兼容问题。

4.5 conda环境内pip装错位置

明明已经激活了focus环境,pip install之后import却还是ModuleNotFoundError,这种情况大多是环境内外pip混用导致的。尤其是你曾经设置过PYTHONPATH环境变量,或者用了sudo pip,包就会被装到系统Python里。

我的建议是:激活环境后,全部统一使用python -m pip install,不要直接敲裸pip。因为python -m pip可以确保pip和当前Python解释器属于同一个环境,避免装到别处。也可以用conda list检查当前环境包里有没有这个库。

4.6 MediaPipe和OpenCV的protobuf冲突

MediaPipe依赖于protobuf,如果你单独安装了更高版本的protobuf,import mediapipe时可能会报:

TypeError: Descriptors cannot not be created directly.

这种情况发生在新旧protobuf混用的环境里。解法是安装兼容protobuf版本:

pip install protobuf==3.20.3

注意,这个版本要和mediapipe 0.10.x搭配。装完后再测试mediapipe能否正常导入。

4.7 视频帧率低,不是电脑不行而是设置不对

环境刚跑通时,可能发现画面特别卡,帧率只有个位数。先别急着怪电脑,看看是不是以下几个原因:

  • 摄像头输入分辨率太高,比如默认1920×1080,但实际处理只需要640×480;
  • 读取原始帧后没有resize直接扔给MediaPipe;
  • MediaPipe的detection confidence设置得太高,导致关键点检测耗时长;
  • 在读取帧和等待next帧之间没有控制节奏。

针对这些问题,我一般会在循环开始就统一resize到640宽,并限制处理帧率。后面最小demo里会演示具体写法。

5. 环境是否就绪:写一个最小“专注度分析”Demo

5.1 Demo要解决的核心问题

环境装完之后,不能光靠import不报错就断言“环境OK”。我们需要一个最小可运行的闭环demo,把摄像头采集、人脸关键点检测、画面可视化串起来,顺便观察实时性能。

这个demo就是整个专注度分析系统的最小雏形。它负责从摄像头拿到一帧图像,用MediaPipe检测人脸并绘制Face Mesh关键点,同时在画面左上角显示当前FPS。如果能在真实环境下稳定运行,说明图像采集和人脸特征提取链路全部打通,后续就可以在这个基础上加头部姿态计算、视线估计等逻辑。

5.2 完整代码与逐段说明

下面这段代码我在Windows 10、Ubuntu 20.04和Ubuntu 22.04上各跑过一遍,只要按照前面的步骤装环境,基本可以直接运行。

import cv2 import mediapipe as mp import time # 初始化MediaPipe Face Mesh mp_face_mesh = mp.solutions.face_mesh face_mesh = mp_face_mesh.FaceMesh( static_image_mode=False, # 视频流模式,非静态图 max_num_faces=5, # 同时检测5张脸,教室场景够用 refine_landmarks=True, # 是否生成瞳孔等额外关键点 min_detection_confidence=0.6, min_tracking_confidence=0.6 ) # 绘图工具 mp_drawing = mp.solutions.drawing_utils draw_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1) cap = cv2.VideoCapture(0) if not cap.isOpened(): print("无法打开摄像头,请检查编号或占用情况") exit() frame_count = 0 start_time = time.time() while True: ret, frame = cap.read() if not ret: break # 将BGR转RGB,MediaPipe要求RGB输入 rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) rgb_frame.flags.writeable = False results = face_mesh.process(rgb_frame) # 绘制关键点 if results.multi_face_landmarks: for landmarks in results.multi_face_landmarks: mp_drawing.draw_landmarks( frame, landmarks, mp_face_mesh.FACEMESH_TESSELATION, draw_spec, draw_spec ) # 计算FPS frame_count += 1 elapsed = time.time() - start_time fps = frame_count / elapsed if elapsed > 0 else 0 # 显示FPS和信息 cv2.putText(frame, f"FPS: {fps:.1f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.putText(frame, "Press q to quit", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) cv2.imshow("Focus Analysis - Env Check", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

逐段说明一下:

  • max_num_faces=5:表示最多检测5张脸。如果你在教室场景要一次捕捉多人,可以适当调高,但CPU负载会同步增加。
  • refine_landmarks=True:这个参数会额外生成瞳孔周围的6个关键点,对后续的视线估计很有用,所以提前打开。
  • rgb_frame.flags.writeable = False:这是MediaPipe官方推荐的性能优化技巧,可以避免处理时被隐式修改,略微提升速度。
  • FACEMESH_TESSELATION:用来绘制密密麻麻的三角网格,看起来最“炫酷”,也最直观地证明关键点检测成功。

运行后你应当能看到一个实时窗口,画面里的人脸被网格覆盖,左上角有FPS数字。

5.3 如何判断环境是否合格

单纯能跑不是目的,还要看性能是否在可接受范围。我用的判断标准是这样的:

指标合格线说明
人脸关键点检测单张脸能稳定输出468个点出现闪断说明置信度设置太高
FPS640×480下不低于12低于8说明需要降分辨率或关后台程序
连续运行时间10分钟无崩溃排除内存泄漏或线程问题
多人检测3人场景FPS下降不超过30%判断max_num_faces对性能的影响

如果FPS太低,第一个处理办法是降低输入分辨率。在读取摄像头时直接设置:

cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

多数摄像头支持这个分辨率,而且肉眼观感不会有多大差距。第二个办法是调低MediaPipe的confidence,比如把min_detection_confidence从0.6改成0.5,但不要太低,否则会出现误检。

还有一个容易被忽视的问题:保证摄像头前的人脸不要太小。如果人距离摄像头太远,比如超过3米,人脸像素太少,MediaPipe会检测不到。这就关系到摄像头安装位置和视角了,但属于部署问题,环境验证阶段先不管。

5.4 验证PyTorch与MediaPipe协同工作

有些人会担心两个框架装在一起会不会互相干扰。实际上,只要把环境隔离好,就不存在这个问题。为了放心,我还会额外跑一个很小的脚本,确认PyTorch能加载张量,且MediaPipe能正常处理图像。

import torch import mediapipe as mp import numpy as np x = torch.randn(2, 3, 224, 224) print("torch tensor shape:", x.shape) print("torch cuda available:", torch.cuda.is_available()) face_mesh = mp.solutions.face_mesh.FaceMesh( static_image_mode=True, max_num_faces=1 ) dummy_image = np.zeros((480, 640, 3), dtype=np.uint8) result = face_mesh.process(dummy_image) print("mediapipe process ok, faces detected:", len(result.multi_face_landmarks or [])) face_mesh.close()

如果这个脚本能正常输出,说明整个基础环境已经准备好,可以进入下一阶段的功能开发了。

6. 环境搭建阶段的一些个人体会

再次强调,环境搭建不是一个“一次性完成就再也不管”的环节。随着功能迭代,你可能会加新依赖、升级旧版本,每动一次都有潜在风险。我个人的习惯是:每完成一个稳定可跑的状态,就把当前环境导出成配置文件,放到项目里备份。

导出conda环境信息:

conda env export -n focus > environment.yml

同时把pip依赖也记录下来:

pip freeze > requirements.txt

之后等需要在新电脑上复现时,只需要一行命令就能重建环境:

conda env create -f environment.yml

这一步大概能帮你省掉至少半天时间。我已经不止一次体会到,项目发布或换电脑时,环境备份就是救命稻草。

这个项目后续的重头戏是特征提取和专注度模型训练。当前demo里已经获得了人脸关键点坐标,下一步需要把这些坐标转换成有意义的特征,比如头部欧拉角、眼睛纵横比、注视方向偏差等,再把这些特征整理成时间序列,输入到专注度分类模型里。这些都是环境搭建完成之后的自然延伸。

如果你也打算从零搭一套类似系统,我的最终建议是:不要追求最新版本,不要贪多,第一步先把最小闭环跑起来,再逐步叠加模块。环境是枯燥的,但也是唯一值得反复打磨的基础设施。跑通了这一步,后面的事会顺畅很多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进