kizumi_header_banner_img

欢迎来到 相互科技工作室 · 小站!

加载中

文章导读

守望 Sentinel:只在我不在的时候,才让摄像头替我看着


avatar
星澄 2026年11月11日 3

守望 Sentinel:只在我不在的时候,才让摄像头替我看着

一个跑在自己电脑上的小哨兵:手机还连着、屏幕还亮着,它就不打扰你;只有你真的离开之后,画面里出现人形,它才会连拍、挑出最清晰的那几张,作为一条 Telegram 相册发给你。

项目已 MIT 开源:fengye1003/watch-sentinel

守望的登录页
守望的登录页

一、为什么想做这个

我有一个很具体的场景:笔记本放在桌上,人离开房间,但不想为了"看一眼"去装一整套云摄像头。

市面上的方案大致三类:

  1. 云摄像头 / 家用监控:要买设备、要订阅云存储,画面还要经过别人的服务器。我要的只是"我不在的时候,有人进了这个房间,让我知道",不需要 7×24 录像。
  2. 本地 NVR(比如 Frigate):能力很强,但要 Docker、要模型、要一块常开的录像盘,对一台偶尔用电池的笔记本来说太重。
  3. 自己写一个:听起来最麻烦,其实需求很窄 —— 只需要回答两个问题:"我走了吗?" 和 "画面里是不是有人?"

真正让我下决心的是第二个问题的一个细节:手机连着电脑的时候,我人就在旁边。这时候摄像头一直录、一直告警,是最烦的。所以第一优先级不是"识别得多准",而是什么时候不该工作。

于是有了这个项目,我给它起名「守望」(Watch Sentinel)。


二、先设计"不工作"的条件

整个系统的门禁是一条链,只有全部通过,摄像头才真正开始判断:

手机蓝牙仍连接 ──▶ 待机(人在旁边)
屏幕亮度不是最低 且 未锁屏 ──▶ 待机(人还在电脑前)

两个条件都满足"人不在",才进入检测。

为什么用蓝牙,而不是 Wi-Fi / 定位

  • 手机连不连电脑,是本地、即时、不依赖网络的事实;
  • 蓝牙 API 在 Windows 上可以直接枚举"已配对 / 已连接",单次查询 1–3 ms;
  • 不需要手机装任何 App、不需要定位权限。

这里踩了一个很典型的坑:Get-PnpDevice -Class Bluetooth 里 Status=OK 根本不代表设备连着。我的三副耳机、两个手柄、手机,全都显示 OK,但实际一个都没连。真正的连接状态得走 Win32:

BluetoothGetDeviceInfo(radio, info)   # info.fConnected / info.fRemembered

fRemembered 是"配过对",fConnected 是"现在连着",两者是独立的事实。用 ctypes 直接调 bthprops.cpl,比每次起 PowerShell 快两个数量级。

设备名还做了归一化:去掉空格、.、·、-、_ 再 casefold,所以 Pixel.hello 和 Pixel·hello 都能命中同一个目标。

为什么加"亮度 + 锁屏"

蓝牙只能回答"手机在不在",回答不了"人是不是还坐在电脑前"。而屏幕亮度和锁屏状态是两个几乎零成本的代理信号:

  • 屏幕亮度不是最低 → 大概率还在用;
  • 已锁屏 → 大概率离开了。

亮度走 WMI 的 WmiMonitorBrightness(CurrentBrightness,0–100),锁屏走 WTSQuerySessionInformation 的 SessionFlags,再 OR 上 LogonUI.exe 是否存在做双保险。一次查询约 8 ms,2 秒刷新一次。

后来还发现一个有意思的边界:Windows 关显示器通常不改亮度值。所以"屏幕自动熄灭但没锁屏"这条规则按字面会认为人还在。真正稳定触发的是"手动拉到最低"或"Win+L"。要更严谨,可以再加显示器电源状态 —— 但那是另一个故事了。


三、检测:为什么不用 YOLO

需求是"识别类似人体的东西,尤其是人脸",同时明确要求低耗高效。

YOLOv8n 在纯 CPU 上一帧 60–120 ms,还要拖 torch / onnxruntime 几百 MB。对一台要长期挂着、偶尔用电池的笔记本来说,这是负分。

最后选了三层很便宜的组合:

判据 实现 单帧成本
画面变化 当前帧 vs N 秒前,灰度差 > 25 的像素占比 ~1 ms
人脸 OpenCV YuNet(FaceDetectorYN,模型 232 KB) 5–15 ms
人形轮廓 帧差最大连通块:面积 ≥ 8%、高宽比 ≥ 1.05 ~1 ms

640×480 下整条分析 8–20 ms。速度判据则用相邻两拍的变化比例除以时间,单位是 %/s。

这里还有一个隐藏的坑:OpenCV 的 DNN 读模型不吃中文路径。项目如果放在含中文的目录里,FaceDetectorYN.create(path) 会报 Can't read ONNX file,看起来像模型坏了、版本不兼容,其实只是路径编码。解法是把模型复制到纯 ASCII 的 %LOCALAPPDATA%\sentinel\models\ 再加载。

另外,OpenCV 5.0 已经移除了 CascadeClassifier 和 HOGDescriptor,所以老的 Haar 方案也不通 —— YuNet 反而是最稳的选择。


四、报警照片为什么总是糊的,以及连拍

第一版发出去的照片经常是这样:人在动,快门刚好抓在 Motion Blur 最严重的一帧,糊成一团。

解决思路很朴素:多拍几张,挑最清晰的。

  • 人形触发时,按固定间隔(默认 150 ms)连拍 N 张(默认 6 张);
  • 每张算一次 Laplacian 方差:cv2.Laplacian(gray, CV_64F).var();
  • 取分数最高的 m 张(默认 3 张),逐张重新做人脸检测并标注;
  • 通过 Telegram 的 sendMediaGroup 作为一条相册发出,第一张带 caption,其余不带。

为什么用 Laplacian 方差而不是别的:它是纯 CPU、单张 < 3 ms,对"运动模糊/失焦"这种最影响观感的退化最敏感。实测一张锐利棋盘 39931、高斯模糊后 1.8,差四个数量级,判别力足够。

Telegram 的细节:sendMediaGroup 最少 2 张、最多 10 张;caption 只能挂在第一张;只有 1 张时必须降级成 sendPhoto,否则 400。

连拍会短暂阻塞检测循环,所以代码里给总时长封了 8 秒上限。默认 0.9 秒,可以接受。


五、面板与登录

面板是 Material Design 3 风格,原生 HTML/CSS/JS,无 CDN、离线可用。所有阈值、连拍参数、摄像头参数、Telegram 配置都能改,改动即时保存。

考虑到它可能会被放到 tailnet / 局域网里,登录保护直接复用了我平时用的 TOTP:RFC 6238,SHA-1、6 位、30 秒、±1 步。密钥可以指向 DSH Web 面板的 state.json,手机里已有的验证器条目不用重扫。

会话是一个 HMAC 签名的 sentinel_auth cookie(HttpOnly、SameSite=Lax、默认 30 天)。同一 IP 60 秒内错 5 次返回 429。


六、把 AI Agent 也当成用户

这个项目从第一天就是"给自己用的工具",所以部署文档写成了给 agent 照着执行的 runbook,而不是给人看的安装说明:

仓库里的 docs/DEPLOY-FOR-AGENTS.md 包含:

  1. 环境自检(摄像头、蓝牙、Python 版本);
  2. 装依赖、下载模型;
  3. 配 Telegram(token 与 chat id);
  4. 启动与健康检查;
  5. 配置目标手机;
  6. 三步验收(演练 / 模拟告警 / 门禁状态);
  7. 6 个会伪装成"程序坏了"的坑;
  8. 回滚。

最短路径只有四行:

git clone https://github.com/fengye1003/watch-sentinel.git
cd watch-sentinel
python -m pip install -r requirements.txt
python scripts\fetch_model.py
python app.py

验收时 agent 应该确认:/api/health 返回 200、bt.found=true、/api/drill 能识别到人脸、/api/telegram/simulate 后用户在 TG 收到一条多图相册。


七、几个值得记下来的工程坑

  1. PnP 的 Status 不代表连接,蓝牙必须用 fConnected。
  2. OpenCV 模型路径不能有中文,否则报错会误导你怀疑模型。
  3. dxva2 的亮度在一些 HDR 屏上直接失败(ERROR_GEN_FAILURE),WMI 才稳。
  4. WMI 的 COM 对象有线程亲和,跨线程复用会报"已为另一线程整理的接口",要按线程隔离。
  5. 摄像头后端 MSMF 偶发"能打开但读不到帧",默认 DSHOW,失败自动回退。
  6. 任务计划程序 + powershell -WindowStyle Hidden 会闪黑窗:控制台在参数生效前就建好了。正确做法是用 wscript.exe 跑一个 VBS 壳,Run(cmd, 0, False),窗口完全不出现。

最后一条是给"开机自启"的,也是这个项目里唯一一个"用户一眼就能看见"的 bug。


八、可复现流程与验收清单

部署:

git clone https://github.com/fengye1003/watch-sentinel.git
cd watch-sentinel
python -m pip install -r requirements.txt
python scripts\fetch_model.py
.\start.ps1

验收:

  • [ ] GET /api/health → {"ok": true}
  • [ ] 面板能打开,蓝牙列表里能找到手机
  • [ ] /api/drill 10 秒内识别到人脸
  • [ ] /api/telegram/simulate 后 TG 收到一条多图相册
  • [ ] 手机连上电脑时,门禁显示「已连接,哨兵待机」
  • [ ] 锁屏/熄屏后,门禁显示「已锁屏,哨兵生效」
  • [ ] 开机自启的两个计划任务都是 Ready

回滚:

.\uninstall-autostart.ps1
.\stop.ps1

九、开源信息

它不是"更便宜的监控",而是一个很小的立场:只有当我不在的时候,它才有权看。 如果这个立场对你有用,欢迎拿去改成你自己的门禁条件 —— 蓝牙、亮度、锁屏,或者任何更能代表"你不在"的信号。



评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码