6 张表讲透 WiFi-DensePose 数据库设计:姿态数据存储完整指南
【免费下载链接】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
WiFi-DensePose 用普通路由器就能穿墙识别人体姿态,但信号变成姿态之后,这条实时数据流谁来接住?答案藏在它的数据库设计里:6 张表、按数据旅程分工。本文带你沿"信号→CSI→姿态"的完整流转拆解这套姿态数据存储与 CSI 数据管理的架构,看懂每张表为什么存在、关键设计为什么这么选。
数据从哪来:先走一遍完整旅程
打开模型清单之前,先跟着一条数据跑一圈——这比逐表罗列更能帮你建立直觉:
- 设备注册:路由器/传感器先入库建档,MAC 地址、坐标、状态写进
devices。 - 开启采集:一次采集行动开一个"值班记录本"——
sessions表,记下起止时间、设备外键、已收帧数,就像执勤台账。 - 原始信号落库:路由器吐出的 CSI 帧(各子载波的幅度+相位)高频写入
csi_data,先标pending,等模型去消费。 - 姿态结果落库:模型跑完,关键点、置信度写进
pose_detections,与原始帧通过会话和时间戳对齐。
你会发现:原始层(csi_data)和结果层(pose_detections)之间隔着一条"处理状态"的流水线,而不是混在一张大表里。
六张表各管一摊
| 表名 | 一句话角色 | 存什么 | 关键设计 |
|---|---|---|---|
devices | 设备档案柜 | MAC、IP、三维坐标、状态 | MAC 唯一约束 + 状态枚举校验 |
sessions | 采集值班记录本 | 起止时间、帧数统计、配置 | 外键指向设备,级联删除 |
csi_data | 原始信号仓 | 幅度/相位数组、频率、处理状态 | FloatArray 列 + 纳秒时间戳 |
pose_detections | 姿态结果库 | 关键点、边界框、置信度 | JSON 存变长结构 |
system_metrics | 系统体检表 | 指标名、数值、标签 | 按名称/来源/时间建索引 |
audit_logs | 审计黑匣子 | 事件类型、操作者、前后状态快照 | before/after 双 JSON 列 |
前三张是数据主干,后两张是"旁路":一个管系统健康,一个管操作留痕。
三个值得推敲的设计决策
姿态关键点为什么直接塞 JSON
每帧可能检出 1 个人,也可能 5 个;每个人的关键点数量还随模型版本变。如果拆成keypoints子表,外键和行数都会爆炸,而且查询时还要再 join 一次。于是keypoints和bounding_boxes直接存成 JSON 列——整帧结果一次读出,模型升级时结构自由伸缩。代价是没法对单个关节做 SQL 过滤,但这类细粒度分析本来就该交给下游,不是数据库的活。
class PoseDetection(Base, UUIDMixin, TimestampMixin): __tablename__ = "pose_detections" frame_number = Column(Integer, nullable=False) timestamp_ns = Column(Integer, nullable=False) session_id = Column(UUID(as_uuid=True), ForeignKey("sessions.id"), nullable=False) person_count = Column(Integer, default=0, nullable=False) keypoints = Column(JSON, nullable=True) # 每人一组关键点 bounding_boxes = Column(JSON, nullable=True) detection_confidence = Column(Float, nullable=True) processing_time_ms = Column(Float, nullable=False) # 处理耗时CSI 幅度相位为什么用数组列
一帧 CSI 有几十个乃至上百个子载波,每个都有幅度和相位。若"一个子载波一行",数据量瞬间膨胀几个数量级,还会把天然属于同一帧的数据打散。WiFi-DensePose 的选择是FloatArray:一帧一行,幅度相位各占一列,紧凑且能整帧读取。同时timestamp_ns精确到纳秒——无线信号的抖动是微秒级的,毫秒时间戳根本排不好序。
class CSIData(Base, UUIDMixin, TimestampMixin): __tablename__ = "csi_data" sequence_number = Column(Integer, nullable=False) timestamp_ns = Column(Integer, nullable=False) # 纳秒级时间戳 amplitude = Column(FloatArray, nullable=False) # 各子载波幅度 phase = Column(FloatArray, nullable=False) # 各子载波相位 frequency = Column(Float, nullable=False) # MHz num_subcarriers = Column(Integer, nullable=False) processing_status = Column(String(20), default="pending")审计日志为什么单独建表
把操作记录塞进业务表是最省事的偷懒,但业务表要删要改,痕迹就没了。audit_logs单独存在:每次关键操作记下谁(user_id)、动哪个资源(resource_type/id)、改了什么(before_state/after_state两份 JSON 快照)。只增不改的黑匣子,出了数据问题可以逐帧回放。
让数据既可靠又快
约束这样加,脏数据进不来
数据库层面用CheckConstraint把范围钉死:状态只能取枚举值、person_count >= 0、confidence必须在 0 到 1 之间、frequency > 0。再配一个唯一约束(device_id, sequence_number, timestamp_ns),同一设备的同一帧不可能重复入库——高频写入场景下,防重比事后去重便宜得多。
索引这样建,查询更快
索引完全对着查询模式建:按设备回溯、按会话拉数据、按时间窗切片、按处理状态扫队列,每个高频路径各有一个专属索引。
__table_args__ = ( Index("idx_csi_device_id", "device_id"), Index("idx_csi_session_id", "session_id"), Index("idx_csi_timestamp", "timestamp_ns"), Index("idx_csi_processing_status", "processing_status"), UniqueConstraint("device_id", "sequence_number", "timestamp_ns", name="uq_csi_device_seq_time"), )时间戳与分区:时间轴的双保险
所有表继承TimestampMixin,created_at/updated_at由数据库默认值自动维护,不用应用层操心。system_metrics还在created_at上加了索引——监控查询天然按时间走。对于csi_data这类只增、按时间检索的巨表,进一步的做法是按时间分区:查"今天上午"只扫一个分区,删旧数据直接 drop 分区,比逐行 DELETE 快几个量级。
回看这套设计的核心思想
让数据沿旅程分表、变长结构交给 JSON、可靠性下沉到约束、速度交给索引——每张表只干一件事,查询路径与索引一一对应。完整的 6 张表模型定义,可以直接读 archive/v1/src/database/models.py,逐字段验证本文说的每个决策。
【免费下载链接】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),仅供参考