硬核自律:我用 Python + YOLOv11 + MediaPipe 写了个“AI 监工”,专治写代码摸鱼
摘要:针对开发者容易分心摸鱼的问题,作者开发了一款基于计算机视觉的离线"AI监工"FocusGuard。系统采用Python+ONNX Runtime实现,核心技术包括:1) YOLOv11-Nano检测手机(COCO ID 67);2) MediaPipe手势识别(支持剪刀手切换休息模式);3) 多线程架构分离UI与推理任务。通过模型轻量化(2GB→40MB)、跳帧策略和状态
硬核自律:我用 Python + YOLOv11 + MediaPipe 写了个“AI 监工”,专治写代码摸鱼
摘要:作为一个自制力为负数的开发者,为了在 Deadline 前交出代码,我怒肝了一个周末,开发了一款基于计算机视觉的桌面端“防摸鱼”神器——FocusGuard。
核心技术栈:
Python 3.9+YOLOv11-Nano (ONNX)+MediaPipe+CustomTkinter。
项目亮点:纯离线运行(保护隐私)、CPU 推理优化(低占用)、手势隔空交互。
本文将详细拆解其技术架构、模型压缩方案及多线程调度逻辑,附核心源码。
🤯 为什么造这个轮子?
1. 痛点:面向搜索引擎编程 -> 面向短视频摸鱼
当你打开 IDE 准备写个 Hello World,突然手机亮了,你拿起来“只看一眼”。
等回过神来,两个小时过去了,代码没写几行,只有 CPU 风扇在空转。
市面上的番茄钟软件只能计时,不能**“感知”**你的状态。作为程序员,我决定用魔法打败魔法——让电脑长只眼睛,一旦发现我在玩手机或者发呆,就物理报警。
2. 需求分析 (MVP版本)
- 实时监测:必须在 100ms 内识别出“玩手机”的动作。
- 低资源占用:不能为了跑个监控把我的 3060 显卡占满了,影响我跑 Docker 和 PyCharm。
- 隐私绝对安全:禁止联网上传视频流(这是底线,我可不想直播写代码)。
- 交互:不想用鼠标点来点去,要支持“隔空手势”控制。
🏗️ 系统架构设计
为了实现“轻量级”和“离线”,我放弃了庞大的 PyTorch 依赖(打包后太大),全面转向 ONNX Runtime。
- 输入层:OpenCV 读取摄像头流。
- 计算层:独立守护线程,分离 UI 渲染与 AI 推理(避免界面卡死)。
- 模型层:
- YOLOv11s-Nano:用于检测
Cell Phone(COCO ID: 67)。 - MediaPipe Hands:用于识别手部关键点(21个点)。
- YOLOv11s-Nano:用于检测
💻 核心技术实现 (干货部分)
1. 模型轻量化:从 PyTorch 到 ONNX
原版 YOLO 模型依赖 ultralytics 和 torch,打包成 .exe 后体积高达 2GB+,且启动极慢。
解决方案:将模型导出为 .onnx 格式,并移除所有 PyTorch 依赖,仅使用 opencv-python 的 dnn 模块加载。
核心代码:加载 ONNX 模型
import cv2
import numpy as np
class ObjectDetector:
def __init__(self, model_path="yolo11s.onnx"):
# 使用 OpenCV DNN 模块加载,完全脱离 PyTorch 环境
self.net = cv2.dnn.readNetFromONNX(model_path)
# 优先使用 CPU,保证在轻薄本上也能跑
self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
def detect(self, frame):
# 预处理:归一化 (1/255.0) 并 Resize 到 640x640
blob = cv2.dnn.blobFromImage(frame, 1/255.0, (640, 640), swapRB=True, crop=False)
self.net.setInput(blob)
# 前向推理
outputs = self.net.forward()
# 后处理逻辑(简化版,只取置信度最高的)
predictions = np.transpose(outputs[0])
rows = predictions.shape[0]
for i in range(rows):
row = predictions[i]
# 获取类别得分 (YOLOv11 输出格式解析)
classes_scores = row[4:]
_, max_score, _, max_class_loc = cv2.minMaxLoc(classes_scores)
# COCO 数据集中,Cell Phone 的 ID 是 67
# 阈值设为 0.45,平衡召回率与误报率(防把计算器当手机)
if max_score > 0.45 and max_class_loc[1] == 67:
return True # 发现手机
return False
2. 手势识别:向量几何的妙用
为了实现“不碰鼠标申请休息”,我利用 MediaPipe 提供的 21 个手部关键点坐标,通过计算指尖与指根的相对位置来判断手势。
手势逻辑:✌️ (Victory)
- 食指 (Index):指尖 (8) 高于 指根 (6) -> 伸直
- 中指 (Middle):指尖 (12) 高于 指根 (10) -> 伸直
- 无名指 (Ring):指尖 (16) 低于 指根 (14) -> 弯曲
- 小指 (Pinky):指尖 (20) 低于 指根 (18) -> 弯曲
核心代码:
def check_victory_gesture(hand_landmarks):
"""
判断是否为剪刀手
注意:在屏幕坐标系中,y 越小,位置越高
"""
lm = hand_landmarks.landmark
# 简单的布尔逻辑门
index_up = lm[8].y < lm[6].y
middle_up = lm[12].y < lm[10].y
ring_down = lm[16].y > lm[14].y
pinky_down = lm[20].y > lm[18].y
return index_up and middle_up and ring_down and pinky_down
3. 性能优化:多线程与跳帧策略
在 Python 中,如果在主线程 (MainThread) 跑 AI 推理,UI 界面(Tkinter)会直接卡死无响应(ANR)。
优化架构:
- UI 线程:只负责绘制界面、响应按钮、显示视频流预览。
- Worker 线程:死循环读取摄像头 -> 推理 -> 更新全局状态变量。
- 跳帧检测:不需要每秒处理 60 帧,人眼的反应速度没那么快,也没必要占满 CPU。
- 每 3 帧跑一次手势识别(轻量)。
- 每 4 帧跑一次手机检测(较重)。
def _monitor_loop(self):
frame_count = 0
while self.is_running:
ret, frame = self.cap.read()
frame_count += 1
# 分时复用 CPU,避免过热
if frame_count % 3 == 0:
self.check_gestures(frame)
if frame_count % 4 == 0:
self.check_phone(frame)
# 线程休眠 10ms,释放 GIL,让 UI 线程有机会刷新界面
time.sleep(0.01)
🛠️ 遇到的“天坑”与解决方案
在开发过程中,最让我头秃的不是算法,而是环境打包。
坑 1:PyInstaller 打包后 MediaPipe 失效
现象:IDE 里运行正常,打包成 exe 后直接崩溃,报错 AttributeError: module 'mediapipe' has no attribute 'solutions'。
原因:MediaPipe 采用了复杂的动态加载机制,PyInstaller 的静态分析器找不到这些隐式调用的子模块(如 hands, drawing_utils)。
解法:在 .spec 文件中暴力收集(Collect All),并显式声明 Hidden Imports。
# FocusGuard.spec 配置片段
from PyInstaller.utils.hooks import collect_all
# 这一行救了我的命:强制收集 MediaPipe 的所有依赖
mp_datas, mp_binaries, mp_hiddenimports = collect_all('mediapipe')
a = Analysis(
...
datas = my_datas + mp_datas,
binaries = my_binaries + mp_binaries,
hiddenimports = my_hiddenimports + mp_hiddenimports + [
'mediapipe.python.solutions.hands', # 显式告诉打包机:我要这个!
],
...
)
坑 2:误报(把水杯当手机)
现象:喝水时被系统判定为玩手机,发出刺耳警报,心态崩了。
解法:
- 提高置信度:将 YOLO 的 Conf Threshold 从默认的 0.25 提升到 0.45。
- 状态防抖:不是检测到 1 帧就报警,而是引入
Counter。只有连续 3 帧检测到手机,才触发State = Distracted。
📦 最终成品:FocusGuard v1.0
经过两周的迭代,这个项目已经从一个简单的 .py 脚本变成了一个完整的桌面软件。
主要功能一览:
- 📱 手机检测:拿起来就报警,放下就停止。
- 🙌 手势交互:比个“耶”自动进入 8 分钟休息模式。
- 📊 数据报表:自动记录专注时长、走神次数,生成 timeline 图表。
- 🚀 云端更新:集成了 Gitee API,自动检查新版本。


写在最后:
技术不仅能用来卷大模型,也能用来解决我们生活中的小问题。希望这个小工具能帮各位同行找回消失的专注力!
如果你觉得这个思路有意思,欢迎点赞收藏!代码有问题评论区见! 👇
下载链接:
点击此处https://qcnxs6fhgged.feishu.cn/wiki/WTpGwF0FXizWCQkyRoqcsa4qn7g
更多推荐

所有评论(0)