1. 从萤石云摄像头到 OpenClaw:边缘视觉看护的真实场景
家里有萤石云摄像头的朋友大概率都遇到过这个尴尬:摄像头装了,APP 也下了,但除了偶尔翻翻回放,它基本就是个摆设。真出事的时候,你不可能 24 小时盯着手机屏幕。我家里两台萤石云,一台 CP1 云台机放客厅看娃,一台 C2C 固定机位在阳台看猫,之前一直靠萤石云 APP 的移动侦测推送,结果窗帘一飘、电视画面一切,手机就震个不停,最后干脆把推送关了。
这套方案要解决的问题很具体:让摄像头自己"看懂"画面,只在真正出现人和猫的时候才通知你,其余时间完全静默。核心链路是 OpenClaw 作为调度中枢,调用萤石云开放平台的 RTSP 直播流,用 ffmpeg 抽帧,YOLO 做语义预筛,本地多模态模型做场景理解,最后通过飞书群回传告警。整套跑在一台 Mac mini 上,API 费用为零,硬件全是家里已有的。
适合谁跟做:有萤石云摄像头(或任意支持 RTSP 的 IPC)、有一台能 24 小时开机的设备(Mac mini / NUC / 旧笔记本都行)、想跑通边缘视觉闭环但不想买云服务的开发者。如果你只是想接个大模型 API 聊聊天,这篇可能不太对路;但如果你想真正把摄像头变成"会思考的眼睛",下面的每一步都可以直接复制。
需要提前说清楚能力边界:萤石云开放平台提供 Token 管理、设备列表、云台控制、抓拍、直播流地址获取这些标准 HTTPS 接口,不需要逆向私有协议。但不同型号能力差异很大,云台机支持预置点巡航,固定机位只有抓拍和直播流。代码里必须做设备路由,否则云台指令发给固定机位会直接报错。
2. TaoToken 统一 Key 接入:给 OpenClaw 配一个模型入口
OpenClaw 本身是个 Agent 调度框架,它需要调用大模型来做指令理解和结果汇总。如果你同时用多个模型(比如本地 Ollama 跑视觉、云端跑文本推理),每个模型一套 Key、一套 Base URL,管理起来很乱。TaoToken 的作用就是把这些入口统一成一个 Key,OpenClaw 里只配一次,后面切换模型只改 Model ID。
先拿 Key。打开 https://taotoken.net/console 注册后进入控制台,在 API Keys 页面创建一个新 Key,复制保存。这个 Key 后面要填到 OpenClaw 的配置里,格式通常是sk-开头的一串字符。
拿到 Key 之后,OpenClaw 的模型配置有两种写法。如果你用的是 OpenClaw 的 settings 文件(一般在~/.openclaw/settings.json或项目根目录的config/settings.json),配置片段长这样:
{ "models": { "default": { "provider": "openai-compatible", "base_url": "https://taotoken.net/api", "api_key": "sk-你的TaoToken密钥", "model_id": "claude-sonnet-4-5", "timeout": 60 }, "vision_local": { "provider": "ollama", "base_url": "http://127.0.0.1:11434", "model_id": "minicpm-v" } } }如果你用的是 TOML 格式的配置(部分 OpenClaw 版本支持config.toml),等价写法是:
[models.default] provider = "openai-compatible" base_url = "https://taotoken.net/api" api_key = "sk-你的TaoToken密钥" model_id = "claude-sonnet-4-5" timeout = 60 [models.vision_local] provider = "ollama" base_url = "http://127.0.0.1:11434" model_id = "minicpm-v"这里有个关键点:Base URL 填https://taotoken.net/api,不要带多余的路径后缀。Model ID 按你实际要用的模型填,TaoToken 支持的模型列表可以在 https://taotoken.net/doc 查到。API Key 建议不要硬编码在配置文件里,用环境变量注入更安全:
export TAOTOKEN_API_KEY="sk-你的TaoToken密钥"然后在配置里写"api_key": "${TAOTOKEN_API_KEY}"。OpenClaw 启动时会自动读取环境变量。
如果你用的是 Claude Code 或者 Cline 这类工具,配置逻辑一样,只是入口不同。Claude Code 的配置在~/.claude/settings.json,Cline 在 VS Code 的设置里找 MCP 配置。核心三件套永远是:Base URL + API Key + Model ID。三者缺一,请求就会报 401 或者 model not found。
配好之后先别急着跑看护脚本,用一条最简单的请求验证 Key 是否生效:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的TaoToken密钥" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "messages": [{"role": "user", "content": "回复 OK"}], "max_tokens": 10 }'预期返回一个 JSON,choices[0].message.content里是 "OK"。如果返回 401,检查 Key 有没有复制完整;如果返回 model not found,检查 Model ID 拼写。这一步过了,说明 TaoToken 入口通了,后面 OpenClaw 调用模型就不会卡在认证上。
3. 可复制配置:萤石云取流参数与 OpenClaw Skill 片段
这一节是整篇的核心,所有配置都可以直接复制。先解决萤石云取流。萤石云开放平台的直播流地址通过/api/lapp/live/address/get接口获取,需要传accessToken、deviceSerial、channelNo、protocol四个参数。protocol 填3拿 RTSP,填2拿 HLS。RTSP 延迟低,适合本地 ffmpeg 抽帧;HLS 兼容性好但延迟高 3-5 秒,看护场景不推荐。
取流之前先拿 AccessToken。萤石云的 Token 有效期 7 天,过期后所有接口静默失败,返回错误码 10002 或 10014。最稳的做法是在 API 调用最底层做自动续期,而不是靠上层定时刷新。下面这段 Python 是取流和自动续期的核心逻辑:
import requests, time, os EZVIZ_BASE = "https://open.ys7.com" APP_KEY = os.environ["EZVIZ_APP_KEY"] APP_SECRET = os.environ["EZVIZ_APP_SECRET"] _token = None _token_expire = 0 def get_token(): global _token, _token_expire if _token and time.time() < _token_expire - 300: return _token r = requests.post(f"{EZVIZ_BASE}/api/lapp/token/get", data={ "appKey": APP_KEY, "appSecret": APP_SECRET }, timeout=15).json() if r.get("code") == "200": _token = r["data"]["accessToken"] _token_expire = r["data"]["expireTime"] / 1000 return _token raise RuntimeError(f"get_token failed: {r}") def api(endpoint, params): params["accessToken"] = get_token() r = requests.post(f"{EZVIZ_BASE}{endpoint}", data=params, timeout=15).json() if r.get("code") == "200": return r.get("data", {}) if r.get("code") in ("10002", "10014"): global _token _token = None params["accessToken"] = get_token() r = requests.post(f"{EZVIZ_BASE}{endpoint}", data=params, timeout=15).json() if r.get("code") == "200": return r.get("data", {}) return None def get_rtsp_url(device_serial, channel=1): data = api("/api/lapp/live/address/get", { "deviceSerial": device_serial, "channelNo": channel, "protocol": 3, "quality": 1 }) return data["url"] if data else None拿到 RTSP URL 后,用 ffmpeg 抽帧。看护场景不需要高帧率,每 2 分钟抓一张就够。抽帧命令:
ffmpeg -rtsp_transport tcp -i "rtsp://..." -frames:v 1 -q:v 2 /tmp/frame_$(date +%s).jpg-rtsp_transport tcp强制走 TCP,避免 UDP 丢包导致花屏。-frames:v 1只抽一帧。-q:v 2控制 JPEG 质量,2 是高质量,文件大概 200KB 左右,喂给 YOLO 足够。
接下来是 OpenClaw Skill 的配置。在 OpenClaw 的 Skills 目录下建一个ezviz-monitor文件夹,里面放SKILL.md和monitor.py。SKILL.md定义 Skill 的能力和调用方式:
--- name: ezviz-monitor description: 萤石云摄像头智能看护,支持客厅看娃、阳台看猫、定时巡视、告警推送 --- # 萤石云智能看护 ## 能力 - 客厅巡视:检测画面中是否有 person,有则推送 GIF - 阳台巡视:检测画面中是否有 cat 或 dog,有则推送 GIF - 实时查询:抓拍当前画面并分析 ## 调用 - 客厅巡视:`python monitor.py --room living --action patrol` - 阳台巡视:`python monitor.py --room balcony --action patrol` - 实时查询:`python monitor.py --room living --action query` ## 路由规则 - 客厅摄像头(CP1)专门看宝宝,千万不能用这个摄像头去找猫 - 阳台摄像头(C2C)专门看猫,不要用来找宝宝monitor.py里封装完整链路:接收参数 → 取流 → 抽帧 → YOLO 预筛 → 命中则启动 ffmpeg 录制 → VLM 分析 → 生成 GIF → 输出结果。核心的 YOLO 预筛部分:
from ultralytics import YOLO model = YOLO("yolov8s.pt") def yolo_filter(image_path, targets=("person",)): results = model(image_path, verbose=False) for r in results: for box in r.boxes: cls_name = model.names[int(box.cls)] conf = float(box.conf) if cls_name in targets and conf > 0.4: return True, cls_name, conf return False, None, 0.0YOLOv8s 模型 21MB,在 Mac mini M4 上单帧推理约 36ms。实测下来,Nano 版(6MB)会把穿白色连体衣的宝宝误识别成 teddy bear,置信度只有 0.28;Small 版置信度 0.86,Medium 版 0.89,Large 版 0.90。从 Small 到 Large 推理时间翻 4 倍,置信度提升在实际系统里毫无意义,所以选 Small 是性价比最高的。
VLM 分析用本地 Ollama 跑 MiniCPM-V,Prompt 模板:
VLM_PROMPT = """你是一个家庭看护助手。请分析这张摄像头画面,用 JSON 返回: {"has_target": true/false, "target": "person/cat/none", "scene": "简短中文描述", "alert": true/false} 只返回 JSON,不要其他内容。"""调用 Ollama:
curl -X POST http://127.0.0.1:11434/api/generate \ -d '{"model":"minicpm-v","prompt":"...","images":["base64..."],"format":"json","stream":false}'MiniCPM-V 在 Mac mini M4 16GB 上冷启动约 21 秒,热推理约 5.5 秒。因为 Cron 每 2 分钟触发一次,相当于持续保活,模型常驻内存,实际延迟稳定在 5.5 秒。
4. 验证请求与预期输出:从抽帧到推理跑通闭环
配置写完,必须逐步验证。不要一次性跑完整链路,出错了不知道哪一环断。按下面的顺序来。
第一步,验证萤石云 Token 和取流。运行:
python -c " from monitor import get_token, get_rtsp_url print('token:', get_token()[:20], '...') url = get_rtsp_url('你的设备序列号') print('rtsp:', url[:60], '...') "预期输出:token 打印前 20 位,rtsp 打印一个rtsp://...开头的地址。如果 token 报错,检查 APP_KEY 和 APP_SECRET;如果 rtsp 返回 None,检查设备序列号是否正确、设备是否在线。
第二步,验证 ffmpeg 抽帧。用上一步拿到的 RTSP URL:
ffmpeg -rtsp_transport tcp -i "rtsp://..." -frames:v 1 -q:v 2 /tmp/test_frame.jpg ls -lh /tmp/test_frame.jpg预期输出:/tmp/test_frame.jpg文件存在,大小 150-300KB。如果 ffmpeg 报Connection refused或超时,检查 RTSP URL 是否过期(萤石云直播流地址有效期通常 1 小时),重新取流即可。
第三步,验证 YOLO 预筛。用抽出来的帧跑:
python -c " from monitor import yolo_filter hit, cls, conf = yolo_filter('/tmp/test_frame.jpg', targets=('person','cat')) print(f'hit={hit} class={cls} conf={conf:.2f}') "预期输出:如果画面里有人或猫,hit=True,class 是person或cat,conf 大于 0.4。如果画面是空房间,hit=False。第一次运行会下载 yolov8s.pt(约 21MB),耐心等几秒。
第四步,验证 VLM 推理。把帧转 base64 发给 Ollama:
python -c " import base64, requests, json img = base64.b64encode(open('/tmp/test_frame.jpg','rb').read()).decode() r = requests.post('http://127.0.0.1:11434/api/generate', json={ 'model': 'minicpm-v', 'prompt': '用JSON返回画面里有没有人:{\"has_person\": true/false}', 'images': [img], 'format': 'json', 'stream': False }, timeout=120).json() print(r['response']) "预期输出:一个 JSON 字符串,类似{"has_person": true}。第一次调用会加载模型,约 21 秒;第二次开始 5.5 秒左右。如果返回空字符串,检查 Ollama 是否在跑(ollama list看模型在不在),以及 Prompt 是否要求了format: json。
第五步,端到端跑一次完整巡视:
python monitor.py --room living --action patrol预期输出:脚本静默执行,如果 YOLO 和 VLM 都判定有人,会在/tmp生成一个 GIF,并打印[ALERT] living room person detected, gif=/tmp/xxx.gif。如果没人,打印[SILENT_SAFE_STATE],不生成任何文件。
第六步,验证 OpenClaw 调度。在 OpenClaw 里手动触发 Skill:
openclaw skill run ezviz-monitor --args "--room living --action patrol"预期输出:OpenClaw 返回 Skill 执行结果,如果检测到目标,飞书群会收到一条 GIF 消息。如果没检测到,群里静默。
整套验证下来,从取流到推理的闭环就通了。实测在 Mac mini M4 上,单次巡视(抽帧 + YOLO + VLM)耗时约 6 秒,其中 YOLO 36ms,VLM 5.5 秒,其余是网络和 IO。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
跑这套链路,报错基本集中在几个地方。下面按真实报错逐个拆。
401 Unauthorized。这个最常见,出现在 TaoToken 请求或萤石云请求。如果是 TaoToken 返回 401,检查 API Key 是否复制完整、有没有多余空格、Base URL 是不是https://taotoken.net/api(不要带/v1后缀,OpenClaw 会自动拼)。如果是萤石云返回 401,检查 AccessToken 是否过期,看返回码是不是 10002 或 10014,是的话走自动续期逻辑。
local proxy failed。这个报错通常出现在 OpenClaw 调用本地 Ollama 时。原因是 OpenClaw 默认走系统代理,而 Ollama 在127.0.0.1:11434,代理会把本地请求也拦掉。解法是在 OpenClaw 配置里给本地模型加no_proxy:
{ "models": { "vision_local": { "provider": "ollama", "base_url": "http://127.0.0.1:11434", "model_id": "minicpm-v", "no_proxy": "127.0.0.1,localhost" } } }或者在启动 OpenClaw 前export NO_PROXY=127.0.0.1,localhost。
reading choices 报错。这个出现在解析模型返回时,通常是response.choices为空或不存在。原因有两个:一是模型返回了错误信息而不是正常 completion,比如{"error": "model not found"};二是 Ollama 返回的格式和 OpenAI 兼容格式不一致。解法是先打印原始返回:
resp = requests.post(...).json() print(json.dumps(resp, ensure_ascii=False, indent=2))看choices字段在不在。如果不在,检查 Model ID 是否正确、TaoToken 是否支持这个模型。Ollama 的/api/generate返回的是response字段,不是choices,别混用。
OAuth 相关报错。如果你用 Claude Code 接入,可能会遇到 OAuth token 过期。Claude Code 的配置在~/.claude/settings.json,三件套是:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的TaoToken密钥", "ANTHROPIC_MODEL": "claude-sonnet-4-5" } }如果报 OAuth 失败,检查ANTHROPIC_BASE_URL有没有写错,以及 Key 是不是 TaoToken 的 Key 而不是 Anthropic 官方的。Cline 的 MCP 配置类似,在 VS Code 设置里找cline.apiProvider,选openai-compatible,填 Base URL 和 Key。
YOLO 返回空结果。如果yolo_filter一直返回 False,先确认图片路径对不对、图片能不能正常打开。然后降低置信度阈值到 0.25 试试。如果还是不行,可能是模型文件损坏,删掉yolov8s.pt重新下载。
VLM 返回空字符串。这个坑我踩过。Qwen3-VL 的 2B 版本在 Ollama 上请求format: json时,输出会写进内部 thinking 字段导致 API 返回为空。换 MiniCPM-V 就好了。如果非要用 Qwen 系列,去掉format: json,让它自由输出,然后自己用正则提取 JSON。
ffmpeg 录制延迟。如果你发现 GIF 里总是看不到目标,检查录制启动时机。正确做法是抓拍的同时就启动 ffmpeg 建连,而不是等 YOLO 或 VLM 出结果再录。萤石云 RTSP 建连握手要 2-3 秒,等判断完再录,目标早走了。
6. 语义一致 CTA:把闭环跑起来之后
整套方案跑通之后,日常体验是这样的:飞书群里绝大部分时间完全静默,宝宝进客厅了来一段 10 秒 GIF,然后沉默 1 小时;猫去阳台干饭了同样来一段,然后沉默。每晚 18:00 一份家庭日报,汇总一天的巡视记录。运行成本忽略不计,API 费用为零,硬件全是家里已有的。
如果你要复现,建议按这个顺序:先用 TaoToken 拿 Key 并验证模型请求通,再配萤石云取流和 ffmpeg 抽帧,然后单独跑 YOLO 和 VLM,最后用 OpenClaw Skill 串起来。每一步都验证通过再往下走,不要跳步。
TaoToken 的 API Key 在 https://taotoken.net/api-keys 创建,接入文档在 https://taotoken.net/doc,模型对话调试在 https://taotoken.net/chat。如果你打算长期跑编码类 Agent 或者多模型调度,可以看 https://taotoken.net/coding-plan;Claude Code 接入参考 https://taotoken.net/claudecode-anthropic。
最后分享一个实测有效的技巧:所有涉及物理设备的项目,先写一个终端调试脚本把硬件交互跑通,再做正式业务开发。我调萤石云云台预置点时写了个preset_wizard.py,用键盘方向键控制摄像头转动,按 P 抓拍预览,按 S 保存预置点,整个体验像终端遥控器。调试时不用反复掏手机开 APP,效率高很多。这个习惯在摄像头、机械臂、传感器这类项目里都适用。