RuView WiFlow 浏览器训练器:在笔记本摄像头坐标系内,把 410 维 WiFi CSI 炼成 17 点人体骨架
【免费下载链接】RuViewπ RuView turns commodity WiFi signals into real-time spatial intelligence, vital sign monitoring, and presence detection — all without a single pixel of video.项目地址: https://gitcode.com/GitHub_Trending/wi/RuView
本文围绕 examples/through-wall/README.md 所描述的 WiFlow Browser Trainer(wiflow_browser.html)展开:它把"空房基线校准 → 相机监督采集 → 浏览器内训练 → 纯 WiFi CSI 推骨架"这条完整闭环压缩进一个自包含 HTML 页面,且全程运行在笔记本摄像头自身的坐标系里,使推断出的骨架与相机画面天然对齐。读完本文,你将掌握这个四阶段门控流程的每一步设计动机、410 维 CSI 输入向量的精确构成、WebGPU/WASM/WebGL 三级计算后端选择机制,以及从构建 sensing-server 到打开页面上手的完整运行步骤。
一、一个 HTML 页面的四阶段门控流程
WiFlow Browser Trainer 的核心是一个完全自包含的单 HTML 页面(仅依赖 CDN 库,无打包器),它在浏览器中、在笔记本摄像头的坐标系里完成相机监督的 WiFi 姿态学习闭环。页面顶部是一个进度步进条,四个阶段逐级解锁(上一阶段未完成,下一阶段保持锁定):
0 · CALIBRATE → 1 · CAPTURE → 2 · TRAIN → 3 · INFER从源码结构看,门控逻辑集中在 wiflow_browser.html 的stageUnlocked()中,四个阶段的解锁条件分别是:
| 阶段 | 解锁条件 | 源码依据 |
|---|---|---|
| 0 · CALIBRATE | 始终可用 | stageUnlocked('calibrate')恒为 true |
| 1 · CAPTURE | 基线校准完成 | return stageDone.calibrate |
| 2 · TRAIN | 基线完成且样本数 ≥ 200 | SAMPLES.length >= 200 |
| 3 · INFER | 已存在训练好的模型 | return !!model |
这种"无基线不可采集、样本不足不可训练、无模型不可推理"的硬门控,与 Python 侧管线(wiflow_capture.py → wiflow_train.py → wiflow_infer.py)的诚实设计一脉相承——README 明确指出"Can't capture without it / Can't train with <200 samples / Can't infer without a model"。
阶段 0 · CALIBRATE:空房基线(ADR-151)
校准阶段的直觉是:房间里静态的 WiFi 信道在绝大部分时间上是恒定的。因此你需要先走出房间,页面捕捉约 10 秒的"静息态"(quiescent)CSI,对这 410 维向量逐特征做在线Welford 均值 + 标准差累积。此后每一帧 CSI 都被表达为相对基线的偏差:
x_norm = (x − base_mean) / (base_std + ε)于是人体的扰动从静态信道中"浮"了出来。基线结果持久化到 IndexedDB,刷新页面后自动恢复(CALIBRATED (restored))。
实现细节可以从 wiflow_browser.html 看到:
- 累积器
cw使用Float64Array保存mean与m2(Welford 的平方和项),避免长序列浮点漂移; - 校准窗口常量
BASELINE_SECONDS = 10,期间只累积新鲜的 live 帧(freshLiveCSI()要求source === "esp32"且距上帧 < 400 ms),模拟源帧直接忽略,保证基线是"真"数据; - 点击calibrate baseline后先有一段可配置的get-ready 倒计时(默认 5 秒),画布上打出大字 "STEP OUT OF THE ROOM",倒计时结束才开始真正记录——这是给使用者留出离开感测区域的物理时间;
- 结束时的标准差取样本标准差
sqrt(m2/(n−1));若 20 秒(两倍窗口)内没有收集到任何 live 帧,则报NO LIVE CSI — check esp32并放行重试,而不是悄悄产出垃圾基线。
阶段 1 · CAPTURE:相机监督的配对准入
CAPTURE 阶段中,MediaPipe Pose 跑在笔记本摄像头上,输出 17 个 COCO 关键点作为标签(label);与之配对的是基线归一化后的 410 维 ESP32 CSI 向量作为输入(input)。页面内置一套引导式、均衡化的采集流程:大号屏幕提示轮播动作指令(stand / turn / walk / arms / crouch / sit / reach),每段动作前有 get-ready 倒计时、动作后有秒数倒计时,并配有逐姿态覆盖度仪表(coverage meter),防止你"采了 2000 帧全是站着不动"的偏态数据集。
从源码结构看,两个设计值得展开:
- 配对准入是"双条件与"。README 的表述是"只有当高置信姿态与新鲜的 live esp32 CSI 帧共存时才落盘一对"。wiflow_browser.html 的采集循环里,每帧先做三重检查:
latestVis > 0.5(MediaPipe 平均可见度)、freshLiveCSI()(source==esp32且 400 ms 内)、baseline存在;任何一项失败都会把跳过原因写进状态行(no confident pose/CSI not esp32 (sim)/no fresh CSI),让你实时知道数据为何没进库。落盘的是基线归一化后的 CSI(baselineNorm(latestCSI.vec))+ 34 维关键点坐标。 - 交错的多次 pass 解决时间切分 OOD 问题。wiflow_browser.html 定义了 9 个动作桶(
stand still / turn / walk left / walk right / arms up / arms down / crouch / sit / reach),但 README 面向读者概括为 stand / turn / walk / arms / crouch / sit / reach 七类引导提示。采集不是"把每个动作采完再换下一个",而是多轮交错 pass(pass1[全部动作] → pass2[全部动作] → …,默认 3 轮 × 每段 12 秒,均可在页面上调)。源码注释写明原因:这样每个动作被摊在整个时间轴上,时间序 80/20 留出切分中的验证段就与训练段具有相同的活动混合比例——这是针对此前"-6pp OOD 留出"问题的修复。
MediaPipe 推理做了限流:单帧在途(one-in-flight)+ 约 18 Hz 上限(MP_MIN_INTERVAL_MS = 55),保证 UI 不卡顿。数据集以wiflow-browser-dataset格式提供export/import JSON能力(wiflow_browser.html),导入时有维度守卫(csi.length !== CSI_DIM直接丢弃,"never fabricate"),支持多次会话累积数据。
阶段 2 · TRAIN:TensorFlow.js MLP 与必须击败的基线
TRAIN 阶段用一个 TensorFlow.js MLP 在浏览器里学习CSI → pose,并给出诚实的留出指标:PCK@0.10、PCK@0.05、MPJPE,外加一个均值姿态基线(mean-pose baseline)——模型必须击败它,否则页面会直说"无可用信号"。README 强调这正是本项目的一贯信条:"no baseline-beating signal, it says so"。
训练源码 中的关键工程决策:
- 时间序 80/20 切分:
cut = floor(n*0.8),验证段是会话最后的 20%,从未参与训练——指标不泄漏; - 双重归一化:输入先经过基线偏差归一化(阶段 0 的产物,采集时已做),训练时再在仅训练段上估计逐维 μ/σ 做标准化;
- 网络结构:
Dense(384, ReLU, L2 1e-4) → Dropout(0.35) → Dense(192, ReLU) → Dropout(0.35) → Dense(96, ReLU) → Dense(34, Sigmoid),输出 17×2 个 [0,1] 归一化坐标。源码注释解释了为什么是"较小容量":针对一次诚实负例运行中 train MSE 0.072 vs val MSE 0.161 的过拟合,把宽度从 512/256/128 降到 384/192/96 并把宽层 dropout 提到 0.35; - 优化与早停:Adam(lr 1e-3)+ MSE,batch 64,每 epoch 在验证段上评估 PCK/val-MSE,按 patience(默认 30)早停并恢复历史最佳权重(
model.setWeights(bestWeights))——报告的是最佳模型,不是最后一个; - 基线裁决:均值姿态基线 PCK@0.10 始终计算并显示;最终裁决按 ΔPCK 是否> 1 pp二值化,页面以绿色/红色横幅明文输出"model BEATS mean-pose baseline by +X.X pp → real CSI→pose signal"或"model does NOT beat baseline → no usable signal (honest)"。
模型与归一化参数(μ/σ、留出 PCK)一起存入 IndexedDB(indexeddb://wiflow-model),刷新页面后自动loadModel()恢复,INFER 阶段随之解锁。
阶段 3 · INFER:纯 WiFi CSI 驱动骨架,且与画面对齐
INFER 阶段中,已训练模型仅凭 WiFi CSI(基线归一化 → 标准化 → 前向)逐帧驱动骨架,画在训练时所用的同一个笔记本相机画面上——所以推断骨架与相机图像对齐。README 点明这正是整件事在浏览器里做、而不是另起一个 Python 相机进程做的全部意义:"That alignment is the entire point of doing this in-browser."
inferLoop 中还有两个细节:预测关键点经过一阶 EMA 平滑(α = 0.35)抑制抖动;骨架颜色由服务端classification.presence决定——在场为绿色,无人在场降为灰色。页面提供hide camera开关,可在纯黑背景上看"仅 CSI 骨架"。状态行持续显示实测的 held-out PCK@0.10,提醒读者推断姿态是粗粒度的。
二、为什么放进浏览器:Python 管线的坐标帧问题
README 的Why in-browser一节交代了浏览器版存在的理由。Python 侧管线(wiflow_capture.py → wiflow_train.py → wiflow_infer.py)已经证明信号是真的:文档记载其留出 PCK@0.10 ≈59.5%,对比 50% 的均值姿态基线,+9.4 个百分点。但它训练时用的是另一台相机的坐标系,推断出的骨架永远对不齐笔记本摄像头画面。
把采集、训练、推理全部放进浏览器、共用同一台相机,训练帧与推理帧的坐标系恒等,骨架自然对齐。两条管线共享同一份输入约定与诚实标准:
| 对比项 | Python 管线 | 浏览器训练器 |
|---|---|---|
| 采集 | wiflow_capture.py:cv2+ MediaPipe PoseLandmarker + WebSocket 订阅,双条件(置信姿态 ∧ live esp32 CSI)才写 JSONL | 同一双条件逻辑,样本进 IndexedDB |
| 配对时间窗 | --max-skew-ms默认 150 ms(源码) | 400 ms 新鲜度阈值(freshLiveCSI()) |
| 姿态可见度门槛 | --min-vis默认 0.5 | latestVis > 0.5 |
| 模型 | PyTorch MLP 512/256/128 → 34(wiflow_train.py) | TF.js MLP 384/192/96 → 34 |
| 切分与基线 | 时间序 80/20;均值姿态基线,Δ>1 pp 判 BEATS | 完全相同(wiflow_train.py 与浏览器版同源) |
| 推理 | wiflow_infer.py:纯 numpy 前向,在ws://:8770/pose广播给 HTML 客户端 | 页面内逐帧前向,直接画在相机画面上 |
两条管线的 410 维向量构造逐位一致,这是跨管线数据可互换的基础(见下节)。
三、410 维 CSI 输入向量的精确构成
README 要求浏览器版与 Python 管线完全匹配输入向量,其构成如下:
[ mean_rssi, variance, motion_band_power, breathing_band_power ] # 4 (features.*) + for node 9 then node 13: [ mean_rssi, variance, motion_band_power ] # 6 (node_features[].features.*) + signal_field.values, padded / truncated to 400 # 400 = 410-d即:4 个全局特征 + 每节点 3 个特征 × 2 个节点(9、13)+ 400 维信号场(20×20)= 410。
浏览器侧的csiVector()(wiflow_browser.html)与 Python 侧的csi_vector()(wiflow_capture.py)实现同一布局:全局features四元组 → 按节点 9 在前、节点 13 在后的node_features[].features三元组 →signal_field.values不足补零、超过截断到 400。README 说明该等价性已用真实 live 帧验证过:浏览器csiVector()产生与wiflow_capture.py csi_vector()完全相同的 410 向量(node 9 在前、node 13 在后,field 零填充)。
从源码结构看还有一个增强:浏览器版的节点集合不是写死的。NODE_IDS = [9, 13]只是检测运行前的回退值;detectSensors()会嗅探 live 流,把在 ≥40% 采样帧中出现(且满足最小帧数)的node_id锁定为有序集合(升序,保证模型输入布局跨采集/训练/推理稳定),并据此重算CSI_DIM = 4 + 3×|NODE_IDS| + 400。若检测到的集合与现有基线/数据不一致,页面会明确提示"切换将作废已有基线/N 条样本",确认后清空重来(wiflow_browser.html)——因为节点集合定义了模型的输入维度,静默变更就是数据污染。
四、计算后端:WebGPU → WASM-SIMD → WebGL
训练与推理都跑在 TensorFlow.js 上,页面在启动时自动选择可用后端,优先最快者(selectBackend):
- WebGPU(Chrome / Edge,安全上下文——
localhost满足条件)——GPU 计算,首选; - WASM-SIMD回退(
tfjs-backend-wasm,启用 SIMD,.wasm文件从 CDN 加载); - WebGL最后兜底(随 tfjs core 自带)。
选中的后端以徽章形式显示在页头(compute: WebGPU/WASM-SIMD/WebGL),如实标注实际在跑什么;模型代码本身后端无关,设备抽象由 tf.js 完成。这与 README 的Honesty (baked in)立场一致——不宣称性能,只陈述实际状态。
五、内建诚实性设计:基线裁决、来源横幅与那次被撤回的 92.9%
README 的Honesty (baked in)一节列出的四条设计约束,在源码中均可逐一对应:
- 颜色即身份:CAPTURE 骨架(蓝色)来自相机,是真值(ground truth),标注为 label;INFER 骨架(绿色)是纯 CSI 推断,标注为 coarse,旁边常驻显示实测留出 PCK,而非营销数字;
- 基线永远在场:TRAIN 阶段均值姿态基线始终计算并展示,裁决明文说明模型是beats(有真实信号)还是does not(无可用信号)。README 特别指出,这套检查正是为了防住该项目曾撤回的 92.9% 数字——那个数字正是在基线检查上失败的;
- 来源横幅严格互斥:LIVE(真实
source: "esp32")/SIMULATED — not real(任何其他 source)/NO-CSI-SERVER。connectCSI 中三态互斥更新,断线 1.5 秒自动重连,页面从不伪造帧; - 无基线不可采集:采集按钮在无基线时禁用,训练按钮在样本 < 200 时禁用,推理在无模型时阶段保持锁定。
六、运行步骤:两个进程、两个端口
1. 启动真实 sensing-server(提供 :8765 的 CSI WebSocket)
按 README 的步骤,在v2目录下构建并启动 Rust 侧感测服务:
cd v2 cargo build -p wifi-densepose-sensing-server ./target/debug/sensing-server.exe --ws-port 8765 --udp-port 5005页面期望的已验证 live 端点是ws://localhost:8765/ws/sensing,要求帧中source:"esp32"、节点[9, 13]、features.*、node_features[].features.*与signal_field.values(400 个浮点数)。要拿到source: "esp32"(即 LIVE 状态),必须有一台真实ESP32-S3完成配网并持续推流——固件的构建与配网步骤,README 指向仓库的CLAUDE.local.md(本目录 README 中的原始引用)。
2. 用 localhost 静态服务器托管页面(相机 + WebGPU 需要 localhost/安全源)
任何 localhost 静态服务器都可以,README 示例:
python -m http.server 8099 # 然后打开: http://localhost:8099/examples/through-wall/wiflow_browser.html注意端口分工:8099 只是静态文件服务器,8765 是另一个进程(CSI WebSocket)。浏览器提示时请允许相机访问。仓库还提供了一个带线程与 no-cache 头的专用静态服务器 serve.py(默认监听 8080,服务仓库根目录),其文件头注释解释了为什么不用单线程的python -m http.server:浏览器会并行拉取 HTML 与 CDN 资源,单线程 SimpleHTTPServer 会让首个连接占住唯一工作线程导致其余请求挂起。
指向其他主机上的 CSI 服务器,用?ws=查询参数覆盖:
http://localhost:8099/examples/through-wall/wiflow_browser.html?ws=ws://192.168.1.20:8765/ws/sensing对应源码逻辑:页面按https:与否自动选wss/ws并默认连接同主机 8765 端口,?ws=参数优先(wiflow_browser.html)。
3. 使用流程
- CAPTURE页签 →enable laptop camera→start guided recording。跟随引导流程(stand / turn / walk / arms / crouch / sit)。只有"高置信姿态 ∧ 新鲜 live esp32 CSI"共存时才落一对样本;目标量级是数千条。样本存 IndexedDB,刷新不丢。
- TRAIN页签 →train model。观察实时 loss 曲线(train 琥珀 / val 蓝)、留出 PCK 与基线裁决。模型存入 IndexedDB。
- INFER页签 → 绿色骨架改由 WiFi CSI 单独驱动,对齐叠在你自己的相机画面上。勾选hide camera可在黑底上只看 CSI 骨架。
七、依赖与持久化:CDN-only 库清单和 IndexedDB 布局
页面只依赖 CDN,无打包器,README 给出的库清单为:
| 库 | CDN |
|---|---|
| TensorFlow.js core | @tensorflow/tfjs@4.22.0/dist/tf.min.js |
| TF.js WebGPU backend | @tensorflow/tfjs-backend-webgpu@4.22.0/dist/tf-backend-webgpu.min.js |
| TF.js WASM backend | @tensorflow/tfjs-backend-wasm@4.22.0/dist/tf-backend-wasm.min.js |
| MediaPipe Pose 0.5 (legacy solutions) | @mediapipe/pose@0.5/pose.js |
与页面 HTML 头部 实际加载的四个<script>标签一一对应。
页面状态通过 IndexedDB 数据库wiflow-browser(对象存储kv)持久化(wiflow_browser.html),键位包括:samples(数据集)、baseline(基线 μ/σ/帧数/时间戳)、nodeIds(检测到的节点集合)、norm(输入标准化参数 + 留出 PCK);模型本身经 TF.js 存于indexeddb://wiflow-model。启动序列(boot)固定为:连 CSI → 选后端 → 恢复 nodeIds → 恢复基线 → 恢复样本 → 加载模型 → 刷新门控——即上次会话做到哪一步,刷新后从哪一步继续。相机选择存 localStorage,跨会话记忆。
八、适用范围与诚实的边界声明
最后必须完整继承 READMEScope / honesty caveats的适用前提,这也是使用本方案前必须理解的边界:
- 当前验证范围是同一人、同一房间、同一会话;未验证跨天、跨房间,也未验证真正的隔墙(through-wall)场景——目录名
through-wall指的是实验主题域,而非当前已交付的隔墙能力; - 推断姿态是粗粒度的,PCK@0.05 通常较弱;
- 如果模型没有击败均值姿态基线,页面会直说——这是特性,不是缺陷。这套"打不过基线就承认"的评估纪律(时间序留出、基线对照、来源三态横幅)贯穿浏览器与 Python 两条管线,是理解本项目姿态估计数字时必须先建立的方法论前提。
九、延伸阅读
- examples/through-wall/README.md:本文主体文档,四阶段流程与运行说明的权威来源
- examples/through-wall/wiflow_browser.html:1272 行自包含页面,含门控、Welford 基线、TF.js 训练与 CSI 推理全部实现
- examples/through-wall/wiflow_capture.py / wiflow_train.py / wiflow_infer.py:Python 侧采集、训练、推流三件套
- examples/through-wall/wiflow_ab.py、index.html:同目录的 A/B 实验与展示页面
- examples/through-wall/serve.py:线程化 no-cache 静态服务器
【免费下载链接】RuViewπ RuView turns commodity WiFi signals into real-time spatial intelligence, vital sign monitoring, and presence detection — all without a single pixel of video.项目地址: https://gitcode.com/GitHub_Trending/wi/RuView
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考