七牛云 × XEngineer 暑期实训营「第四批次议题」选题一
以摄像头画面为输入,通过自然语言与 AI 对话——让 AI 帮你"看"身边的东西,问它任何问题。
作者已部署可用的线上版本,直接用 Chrome 浏览器访问即可:
授权摄像头后即可立刻开始视觉对话,无需任何配置。
环境要求:Go ≥ 1.23 · Chrome 浏览器
1. 配置环境变量
cp .env.example .env
# 编辑 .env,填入你的 Mimo API Key| 变量名 | 说明 | 示例值 |
|---|---|---|
MIMO_URL |
Mimo API 地址 | https://token-plan-cn.xiaomimimo.com/v1 |
MIMO_KEY |
你的 API Key | sk-xxxxxx |
MODEL_OK |
模型名称(支持逗号分隔多个,优先选 omni/vision/vl) | mimo-v2-omni |
2. 启动服务
cd backend && go run ./cmd/server3. 打开浏览器
访问 http://127.0.0.1:5173,授权摄像头后即可开始视觉对话。
项目采用前后端分离部署方案,详见 DEPLOYMENT.md。
| 部分 | 平台 | 说明 |
|---|---|---|
| 后端 | Railway | 根目录 Dockerfile 自动构建,配置 MIMO_URL / MIMO_KEY / MODEL_OK 环境变量 |
| 前端 | Cloudflare Pages | 发布目录 frontend/public,无需构建步骤,修改 config.js 中的后端地址 |
本项目完整经历了从 0 到 1 的产品开发周期,共五个阶段:
产品设计 ──► MVP 实现 ──► 产品上线 ──► 测试维护 ──► 文档编写
📐 💻 ☁️ 🔧 📝
梳理核心需求与技术可行性,产出以下设计文档(见 设计文档/ 目录):
- 需求文档:明确用户场景、功能边界与非功能性约束
- 产品流程图:端到端交互流程,覆盖摄像头授权 → 事件相机抽帧 → Mimo 问答 → 空间画像更新全链路
- 可行性分析:浏览器端帧差事件相机方案论证,避免持续视频流上传
- 空间画像格式设计:
Profile(scene + objects + confidence)数据结构定义
按前后端分离架构逐步实现:
| 模块 | 实现内容 |
|---|---|
| Go 后端 | HTTP 服务、Mimo 代理、帧缓存、对话历史、空间画像存储与生成 |
| 浏览器前端 | 摄像头采集、帧差事件相机、事件驱动采样、空间画像渲染 |
| 语音交互 | Web Speech API 中文识别、TTS 播报、音频频谱可视化 |
| 空间画像 | 12 帧触发生成、8 帧节流更新、双端维护(前端即时展示 + 后端持久化) |
采用前后端分离部署:
- 后端 → Railway 平台,基于根目录
Dockerfile自动构建 Go 服务 - 前端 → Cloudflare Pages,发布
frontend/public静态目录,零构建 - 线上地址 → https://ava.542000.xyz
- 单元测试:覆盖 Mimo 客户端、API Handler、空间画像服务(
go test ./...) - 端到端验证:
frontend/scripts/verify-browser.mjs无头 Chrome 自动化脚本,验证摄像头启动与完整对话流程 - 问题修复:定位并修复后期出现的帧缓存上传错误提示(静默处理非关键后台失败)
按照规范化文档结构完整撰写本 README,涵盖项目简介、快速启动、功能特性、技术架构、项目结构、空间画像机制与开发流程,方便后续贡献者快速上手。
| 功能 | 说明 |
|---|---|
| 📷 实时视觉问答 | 摄像头画面直接提问,AI 实时理解并回答 |
| 🎙️ 语音输入/播报 | Web Speech API 中文语音识别 + TTS 语音播报回答 |
| 🧠 空间画像记忆 | AI 自动记住画面中的物体及位置,跨轮对话共享上下文 |
| 📸 事件相机抽帧 | 浏览器端帧差算法,按画面变化程度智能选取关键帧 |
| 🔥 运动热力图 | Canvas 实时渲染帧差热力图与运动区域边框 |
| 💬 多对话管理 | 支持新建、切换、置顶多个独立对话,历史消息持久保存 |
| ☁️ 端云协同 | 浏览器端完成抽帧,云端 Mimo 大模型完成多模态理解 |
┌──────────────────────────────────────────────────────────┐
│ 浏览器端 (原生 HTML/CSS/JS) │
│ │
│ ┌────────────┐ ┌─────────────────┐ ┌───────────────┐ │
│ │ 摄像头采集 │ │ 事件相机 (帧差) │ │ 语音识别/合成 │ │
│ │ VideoCapture│ │ EventCamera │ │ SpeechController│ │
│ └─────┬──────┘ └───────┬─────────┘ └───────────────┘ │
│ │ │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 事件驱动采样器 (EventDrivenSampler) │ │
│ │ 高事件 420ms │ 低事件 900ms │ 静态 2200ms │ │
│ └──────────────────────┬───────────────────────────────┘ │
└─────────────────────────┼──────────────────────────────────┘
│ POST /api/frames (关键帧 + 事件元数据)
│ POST /api/vision (查询 + 事件数据)
┌─────────────────────────┼──────────────────────────────────┐
│ 后端 (Go 1.23 · 标准库 net/http) │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ API Handler │ │
│ │ /api/vision │ /api/frames │ /api/spatial │ /api/conversations │
│ └──────┬───────────────┬─────────────────┬─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌────────────┐ ┌──────────────────────┐ │
│ │ framecache │ │conversation│ │ spatialprofile │ │
│ │ (帧环形缓存) │ │ Store │ │ LocalClient + Store │ │
│ │ 最多12帧 │ │ (对话历史) │ │ (JSON 文件持久化) │ │
│ └─────────────┘ └────────────┘ └──────────┬───────────┘ │
│ │ │
└──────────────────────────────────────────────┼───────────────┘
│ OpenAI 兼容 API
┌──────────────────────────────────────────────┼───────────────┐
│ Mimo 云端大模型 │ │
│ ▼ │
│ mimo-v2-omni (多模态视觉理解 + 空间画像生成) │
└───────────────────────────────────────────────────────────────┘
| 层级 | 技术 | 说明 |
|---|---|---|
| 摄像头采集 | 浏览器 getUserMedia |
720p 视频流,Canvas 截帧为 320×240 JPEG |
| 事件相机 | 原生 JS 帧差算法 | 16×12 网格亮度差分,阈值 24,避免持续上传视频流 |
| 语音交互 | 浏览器 Web Speech API | 中文语音识别 + TTS 播报 + 音频频谱可视化 |
| 前端框架 | 原生 HTML/CSS/ES Module | 零构建工具依赖,模块化组织 |
| 后端框架 | Go 1.23 + 标准库 net/http |
零外部依赖,静态文件 + REST API 一体服务 |
| 帧缓存 | 内存环形缓冲(最多 12 帧) | 按事件分数动态选取 1/2/4 帧发送给模型 |
| 空间画像 | 内存 + JSON 文件持久化 | 每 12 帧首次生成,之后每 8 帧增量更新 |
| 多模态模型 | Mimo (OpenAI 兼容接口) | mimo-v2-omni,同时处理图像理解与空间画像生成 |
| 部署 | Railway (后端) + Cloudflare Pages (前端) | Docker 镜像部署,前端静态托管 |
XEngineer-04-01/
├── backend/
│ ├── cmd/
│ │ ├── server/main.go # 主服务入口,装配所有依赖并启动 HTTP 服务
│ │ └── spatial-profile/main.go # 空间画像独立服务入口(可选独立部署)
│ ├── internal/
│ │ ├── api/
│ │ │ ├── handler.go # HTTP 路由、请求校验、静态文件服务
│ │ │ └── handler_test.go # API 集成测试
│ │ ├── config/
│ │ │ └── config.go # .env 配置加载,自动选择视觉模型
│ │ ├── conversation/
│ │ │ └── store.go # 对话历史内存存储(增删查、置顶排序)
│ │ ├── eventmeta/
│ │ │ └── event.go # 事件相机元数据归一化(分数裁剪、运动框过滤)
│ │ ├── framecache/
│ │ │ └── store.go # 帧环形缓冲(最多 12 帧,按事件分数选帧)
│ │ ├── mimo/
│ │ │ ├── client.go # Mimo OpenAI 兼容多模态请求封装 + Prompt 构造
│ │ │ └── client_test.go # Mimo 客户端单元测试
│ │ ├── spatial/
│ │ │ └── graph.go # 空间画像知识图谱(实体 upsert、属性合并)
│ │ └── spatialprofile/
│ │ ├── client.go # 空间画像客户端(LocalClient / HTTPClient)
│ │ ├── model.go # Mimo 空间画像生成模型(12帧→场景+物体)
│ │ ├── server.go # 空间画像独立 HTTP 服务路由
│ │ ├── server_test.go # 空间画像服务测试
│ │ ├── store.go # 空间画像持久化存储(内存缓存 + JSON 文件)
│ │ └── types.go # Profile 数据结构、Normalize/ToContext 转换
│ └── go.mod
├── frontend/
│ ├── public/
│ │ ├── js/
│ │ │ ├── app.js # 主应用逻辑,组装各模块、管理对话与帧处理循环
│ │ │ ├── api-client.js # 后端 REST API 调用封装
│ │ │ ├── config.js # API 基地址配置(本地开发 / 生产部署切换)
│ │ │ ├── event-camera.js # 浏览器端帧差事件相机(16×12 网格、运动框检测)
│ │ │ ├── sampler.js # 事件驱动采样器(动态采样间隔)
│ │ │ ├── spatial-graph.js # 前端空间画像管理(Map 存储、upsert 合并)
│ │ │ ├── speech.js # Web Speech API 语音识别/合成 + 音频可视化
│ │ │ ├── ui.js # UI 渲染工具(消息气泡、热力图、实体卡片)
│ │ │ └── video-capture.js # 摄像头管理(getUserMedia、Canvas 截帧)
│ │ ├── index.html # 单页应用入口
│ │ └── styles.css # 全局样式(响应式三栏布局)
│ ├── scripts/
│ │ └── verify-browser.mjs # 无头 Chrome 端到端验证脚本
│ └── package.json
├── 设计文档/ # 需求、流程与可行性设计文档
├── .env.example # 环境变量模板
├── Dockerfile # Railway 后端 Docker 镜像构建
├── railway.json # Railway 部署配置
├── wrangler.toml # Cloudflare Pages 前端部署配置
└── package.json # 根目录开发编排命令(npm run dev)
空间画像是本项目的核心记忆系统,让 AI 能在多轮对话中持续"记住"它看到过的场景和物体。
{
"scene": "桌面上摆放着电脑、水杯和书本",
"objects": [
{ "name": "水杯", "description": "位于画面右侧,白色马克杯", "confidence": 0.92 },
{ "name": "笔记本电脑", "description": "位于画面中央,屏幕亮起", "confidence": 0.98 }
],
"updatedAt": "2026-06-14T10:30:00Z"
}摄像头帧缓存 (最多 12 帧)
│
│ 累计到 12 帧时首次触发
│ 之后每新增 8 帧触发一次
▼
spatialprofile.MimoModel.Generate()
│ 将 12 帧关键帧 + 已有画像发给 Mimo
│ Mimo 返回新的 scene + objects JSON
▼
spatialprofile.Store.Set() ← 内存缓存 + JSON 文件持久化
│
▼
下次视觉问答时注入 SpatialContext ← 作为历史记忆上下文传给 Mimo
| 设计 | 说明 |
|---|---|
| 异步后台更新 | 空间画像更新在 goroutine 中执行,不阻塞视觉问答响应 |
| 当前帧优先 | Mimo 被明确告知:当前图片永远优先于历史空间画像,二者冲突以图片为准 |
| 增量合并 | 有历史画像时执行更新模式,当前帧不可见的旧物体会被删除或改写 |
| 双端维护 | 前端 SpatialGraph 即时展示,后端 Store 按 session 持久化,多轮共享 |
| 置信度过滤 | 每个物体携带 confidence 字段(0-1),低置信度物体可被后续帧覆盖 |
| 触发节流 | 首次 12 帧触发,之后每 8 帧才再次触发,避免频繁调用 Mimo API |