From 8a99b388c4eb2a826d4f5a6b5163c88d95cacb4e Mon Sep 17 00:00:00 2001 From: QiuSW <105186638@qq.com> Date: Mon, 3 Aug 2026 17:22:22 +0800 Subject: [PATCH] docs: initialize YoVision requirements and architecture --- .gitattributes | 11 + .gitignore | 2 + Bell/.gitkeep | 1 + Brain/.gitkeep | 1 + Sense/.gitkeep | 1 + docs/raw/01-需求收集.md | 430 ++++++ docs/raw/02-需求分析.md | 792 +++++++++++ docs/raw/03-通用场景应用方案.md | 1255 +++++++++++++++++ docs/raw/04-目标客户-政府购买居家养老.md | 122 ++ docs/raw/05-目标客户-寄宿制学校.md | 130 ++ docs/raw/06-目标客户-工厂园区安全生产.md | 145 ++ docs/raw/07-事件契约比对-silver_pose.md | 259 ++++ docs/raw/08-三系统职责划分.md | 184 +++ docs/raw/contracts/README.md | 138 ++ .../contracts/event-v0.1.example-current.json | 60 + .../contracts/event-v0.1.example-radar.json | 66 + .../contracts/event-v0.1.example-target.json | 63 + docs/raw/contracts/event-v0.1.schema.json | 283 ++++ docs/raw/提案-寄宿制学校.html | 783 ++++++++++ docs/raw/流程图-系统怎么工作.html | 303 ++++ docs/raw/跌倒监护系统流程图.pdf | Bin 0 -> 90324 bytes docs/raw/跌倒监护系统流程图.png | Bin 0 -> 171833 bytes docs/raw/跌倒监护系统流程图.svg | 140 ++ ...人跌倒监护系统 — 方案流程说明(客户版).md | 135 ++ ...倒检测视频接入系统 — 演示与验证开发方案.md | 183 +++ ...跌倒检测视频接入系统 — 线上生产开发方案.md | 729 ++++++++++ 26 files changed, 6216 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Bell/.gitkeep create mode 100644 Brain/.gitkeep create mode 100644 Sense/.gitkeep create mode 100644 docs/raw/01-需求收集.md create mode 100644 docs/raw/02-需求分析.md create mode 100644 docs/raw/03-通用场景应用方案.md create mode 100644 docs/raw/04-目标客户-政府购买居家养老.md create mode 100644 docs/raw/05-目标客户-寄宿制学校.md create mode 100644 docs/raw/06-目标客户-工厂园区安全生产.md create mode 100644 docs/raw/07-事件契约比对-silver_pose.md create mode 100644 docs/raw/08-三系统职责划分.md create mode 100644 docs/raw/contracts/README.md create mode 100644 docs/raw/contracts/event-v0.1.example-current.json create mode 100644 docs/raw/contracts/event-v0.1.example-radar.json create mode 100644 docs/raw/contracts/event-v0.1.example-target.json create mode 100644 docs/raw/contracts/event-v0.1.schema.json create mode 100644 docs/raw/提案-寄宿制学校.html create mode 100644 docs/raw/流程图-系统怎么工作.html create mode 100644 docs/raw/跌倒监护系统流程图.pdf create mode 100644 docs/raw/跌倒监护系统流程图.png create mode 100644 docs/raw/跌倒监护系统流程图.svg create mode 100644 docs/其他项目的文档/居家老人跌倒监护系统 — 方案流程说明(客户版).md create mode 100644 docs/其他项目的文档/跌倒检测视频接入系统 — 演示与验证开发方案.md create mode 100644 docs/其他项目的文档/跌倒检测视频接入系统 — 线上生产开发方案.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3f925ef --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto + +*.md text eol=lf +*.json text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.html text eol=lf +*.svg text eol=lf + +*.png binary +*.pdf binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..80de9f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +_reference/ +agent_sessions.txt diff --git a/Bell/.gitkeep b/Bell/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Bell/.gitkeep @@ -0,0 +1 @@ + diff --git a/Brain/.gitkeep b/Brain/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Brain/.gitkeep @@ -0,0 +1 @@ + diff --git a/Sense/.gitkeep b/Sense/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Sense/.gitkeep @@ -0,0 +1 @@ + diff --git a/docs/raw/01-需求收集.md b/docs/raw/01-需求收集.md new file mode 100644 index 0000000..c3c136d --- /dev/null +++ b/docs/raw/01-需求收集.md @@ -0,0 +1,430 @@ +# 📋 YoVision 智能视频事件平台 — 需求收集文档 + +> 版本 v0.3 · 2026-08-03 +> 配套文档:《02-需求分析》《03-通用场景应用方案》 +> 参考输入:`其他项目的文档/` 下三份跌倒检测项目文档(居家养老单场景),本文档将其经验泛化到多场景 + +--- + +# 1. 项目定位 + +## 1.1 一句话说明 + +**用开源 NVR 统一接入各类场景的 IP 摄像头,用深度学习模型识别场景中的指定人群与指定行为,命中规则的画面组装成「事件实例」推送到管理系统,预警信息按升级链推送到 App 与其他终端。** + +## 1.2 与参考项目的关系 + +| 维度 | 参考项目(跌倒检测) | 本项目(YoVision) | +| --- | --- | --- | +| 场景 | 居家养老,单一 | 家庭 / 学校 / 社区 / 园区,多场景 | +| 检测目标 | 跌倒,单一行为 | 可配置的「人群 × 行为 × 区域 × 时段」组合 | +| 组织模型 | 家庭(扁平) | 租户 → 站点 → 区域 → 设备(多级、多租户) | +| 规模 | 200 户 / 250 路(历史参考项目) | **默认交付 16 路,单站点按 32 / 64 / 128 路横向扩展;本阶段上限 128 路** | +| 输出 | 跌倒告警 | 事件实例(结构化 + 证据)+ 分级预警 | +| 复用 | — | 接入层、对账器、推理流水线、预警状态机可整体复用 | + +**结论:参考项目是本项目的一个场景实例。本项目要做的是把它拆成「通用底座 + 场景包」。** 这个判断决定了后续所有需求的分层方式。 + +## 1.3 容量决策(已定,2026-08-03) + +| 项 | 决策 | +| --- | --- | +| 默认交付规格 | 单站点 **16 路视频设备** | +| 扩展档位 | 32 / 64 / 128 路 | +| 单站点本阶段上限 | **128 路**;超过 128 路需拆为多个逻辑站点或另行评估 | +| 扩容方式 | 增加媒体分片与推理 Worker,禁止靠单进程、单 GPU 硬扛 | +| 代码约束 | 16 只能是默认配额,数据库、规则、数组与循环中不得写死;配额必须可配置 | + +推荐配置项:`site.max_video_channels` 默认 16、最大 128;`media_shard.max_streams` 与 `inference_profile.max_sources` 按硬件压测确定。平台管理、批量导入、监控和前端列表从第一版起按 128 路设计。 + +--- + +# 2. 干系人与诉求 + +## 2.1 干系人矩阵 + +| 干系人 | 角色 | 核心诉求 | 最怕什么 | +| --- | --- | --- | --- | +| 最终使用者(家属 / 校方安保 / 社区物业) | 预警接收与处置 | 该报的必报、报了必须有人知道 | 误报太多导致不再看 | +| 被监护/被拍摄对象(老人、学生、居民) | 数据主体 | 不被过度监视 | 隐私泄露、被"全天候盯梢" | +| 管理方(养老机构 / 学校 / 街道) | 运营与考核 | 事件可追溯、可统计、可导出 | 出事后拿不出记录 | +| 平台运维 | 系统可用性 | 设备在线率、告警不淹没 | 业务预警被运维噪声淹没 | +| 实施/交付 | 部署上线 | 装得快、开通批量化 | 摄像头型号五花八门装不上 | +| 算法团队 | 模型迭代 | 真实场景数据回流 | 只有公开数据集,与现场脱节 | +| 法务/合规 | 合规落地 | 知情同意、留存期限、权限边界 | 人脸识别越界 | + +## 2.2 关键诉求冲突(需在需求分析阶段裁决) + +| 冲突 | A 方 | B 方 | 初步倾向 | +| --- | --- | --- | --- | +| 全量录像 vs 隐私 | 管理方要"什么都能查" | 数据主体要"平时别看" | 事件驱动采集,非事件不上云 | +| 灵敏度 | 使用者要"绝不漏报" | 使用者要"别老误报" | 两级判定 + 误报反馈闭环 | +| 人脸识别 | 管理方要"认出是谁" | 合规要"最小必要" | **已定**:ReID 为默认手段,人脸按租户授权开启(见 §5.1) | +| 边缘算力成本 | 交付要"盒子便宜" | 算法要"跑得动模型" | 触发式推理,非常态全量 | + +--- + +# 3. 需求来源与收集方法 + +| 来源 | 方法 | 状态 | +| --- | --- | --- | +| 参考项目文档 | 文档分析(三份 Notion 导出) | ✅ 已完成,见本文档全篇 | +| 场景走访 | 家庭 / 学校 / 社区各 2–3 个点位实地踏勘 | ⬜ 待执行 | +| 竞品与开源调研 | Frigate / ZoneMinder / Shinobi / MiBeeNvr / mediamtx / Savant | 🔶 NVR 选型已完成,见《03》§1.4;推理框架与商业竞品继续调研 | +| 硬件兼容性摸底 | 采购 3–5 款候选摄像头做 ONVIF 实测 | ⬜ 待执行(M0 出口) | +| 法务咨询 | 人脸识别、未成年人影像、留存期限 | ⬜ 待执行(阻塞 M3) | +| 现场误报采集 | 试点期真实误报案例库 | ⬜ 待执行(M3 首要产出) | + +> **踏勘清单**(走访时必须带回的东西):点位平面图与摄像头安装高度/俯角、现场网络出口带宽与是否有公网 IP、现有 NVR/平台型号、值班人员的实际工作流(现在出事怎么处理)、他们目前最痛的三件事。 + +--- + +# 4. 场景与需求清单 + +## 4.1 场景总览 + +| 编号 | 场景 | 典型部署点 | 主要目标人群 | 网络特征 | +| --- | --- | --- | --- | --- | +| S1 | 居家养老 | 客厅、走廊 | 老人(独居/失能) | 家庭 NAT 后,带宽小,易掉线 | +| S2 | 校园 | 教学楼、操场、宿舍、围墙、实验室 | 学生、教职工、外来人员 | 校园专网,带宽充足,有机房 | +| S3 | 社区/物业 | 出入口、电梯、周界、楼道、垃圾站 | 住户、访客、陌生人 | 小区专网,可能有本地机房 | +| S4 | 园区/厂区(扩展) | 车间、仓库、施工区 | 员工、承包商、访客 | 企业网,合规要求相对宽松 | + +## 4.2 各场景原始需求条目 + +需求编号规则:`RQ-<场景>-<序号>`。优先级:P0 必须 / P1 应该 / P2 可以。 + +### S1 居家养老 + +| 编号 | 需求 | 优先级 | 来源 | +| --- | --- | --- | --- | +| RQ-S1-01 | 检测老人跌倒并在 10s 内发出首次预警 | P0 | 参考项目 §10.10 | +| RQ-S1-02 | 长时间静止/无活动(如卫生间超时、多小时无位移)告警 | P0 | 参考项目 §10.4 `no_motion` | +| RQ-S1-03 | 夜间离床未归(起夜超时)告警 | P1 | 走访待确认 | +| RQ-S1-04 | 陌生人进入居所告警 | P1 | 走访待确认 | +| RQ-S1-05 | 卧室、卫生间**不得安装摄像头**,仅可用雷达等非成像传感器 | P0 | 参考项目 §3 隐私 | +| RQ-S1-06 | 平时不解码、不上传,仅事件片段上云 | P0 | 参考项目 §3 隐私 | +| RQ-S1-07 | 家属只能查看自己家、且与告警关联的片段 | P0 | 参考项目 §3 | +| RQ-S1-08 | 无人 ack 时逐级升级,最坏 3 分钟内有真人接手 | P0 | 参考项目 §10.3 | + +### S2 校园 + +| 编号 | 需求 | 优先级 | 备注 | +| --- | --- | --- | --- | +| RQ-S2-01 | 打架/推搡/围观聚集检测 | P0 | 校园安全核心诉求 | +| RQ-S2-02 | 翻越围墙、攀爬护栏检测 | P0 | 周界 | +| RQ-S2-03 | 危险区域(实验室、配电房、水池、天台)非授权人员闯入 | P0 | 需区域 + 身份/角色判定 | +| RQ-S2-04 | 外来人员尾随进入校门 | P1 | 需与门禁事件融合 | +| RQ-S2-05 | 上课时段学生在楼道/校外徘徊 | P1 | 需课表时段配置 | +| RQ-S2-06 | 宿舍晚归/夜间离寝 | P1 | 需时段策略 | +| RQ-S2-07 | 学生跌倒/晕倒 | P1 | 复用 S1 能力 | +| RQ-S2-08 | 未成年人影像的采集与留存需单独合规方案 | P0 | 法务阻塞项 | + +### S3 社区/物业 + +| 编号 | 需求 | 优先级 | 备注 | +| --- | --- | --- | --- | +| RQ-S3-01 | 周界入侵(翻墙、跨越警戒线) | P0 | | +| RQ-S3-02 | 电动车进入电梯/楼道 | P0 | 消防强诉求,目标非人 | +| RQ-S3-03 | 消防通道占用/堆物 | P0 | 静态变化检测,非人 | +| RQ-S3-04 | 高空抛物 | P1 | 需专用仰拍机位与轨迹算法,独立评估 | +| RQ-S3-05 | 陌生人在单元门口长时间徘徊 | P1 | 需 ReID + 停留时长 | +| RQ-S3-06 | 独居老人连续 N 天未出入 | P1 | 跨天统计,非实时 | +| RQ-S3-07 | 重点关注人员(黑名单)出现告警 | P1 | 强合规约束,需授权 | + +### S4 园区/厂区(扩展,本期不实现) + +未戴安全帽 / 未穿反光衣 / 区域入侵 / 离岗 / 吸烟明火。列出仅为验证架构可扩展性,M4 前不投入。 + +## 4.3 跨场景通用需求 + +### 4.3.1 接入与设备管理 + +| 编号 | 需求 | 优先级 | +| --- | --- | --- | +| RQ-C-01 | 支持 ONVIF/RTSP 标准 IP 摄像头接入,不绑定品牌 | P0 | +| RQ-C-02 | 支持摄像头位于 NAT 之后(家庭、无公网 IP 的小区)的接入 | P0 | +| RQ-C-03 | 设备身份用序列号而非 IP(DHCP/Wi-Fi 漫游会换 IP) | P0 | +| RQ-C-04 | 支持批量开通(脚本/CSV 导入);默认 16 路也不得依赖逐路手工配置,扩展到 128 路时操作量不得线性增加 | P0 | +| RQ-C-05 | 设备需有「待激活」中间态(批量开通时凭据待补) | P0 | +| RQ-C-06 | 设备探活与离线告警,离线超阈值主动通知 | P0 | +| RQ-C-07 | 开通时校时(ONVIF SetSystemDateAndTime),定期校准 | P0 | +| RQ-C-08 | 摄像头断线恢复后自动重连,不需人工干预 | P0 | +| RQ-C-09 | 架构需预留非视频传感器(毫米波雷达、门磁、紧急按钮)接入 | P1 | + +### 4.3.2 分析与规则 + +| 编号 | 需求 | 优先级 | +| --- | --- | --- | +| RQ-C-10 | 检测能力与场景解耦,同一模型可被多场景规则复用 | P0 | +| RQ-C-11 | 规则支持「目标类型 × 属性/身份 × 区域 × 时段 × 行为 × 持续时长」组合 | P0 | +| RQ-C-12 | 规则可按租户/站点/单设备三级覆盖配置 | P0 | +| RQ-C-13 | 支持画区域(多边形)、画警戒线(方向性越线) | P0 | +| RQ-C-14 | 支持时段/日历(工作日、课表、节假日)策略 | P1 | +| RQ-C-15 | 规则变更不需重启推理流水线 | P1 | +| RQ-C-16 | 人脸识别/身份比对为**可选能力**,默认关闭,需按租户显式授权开启 | P0 | + +### 4.3.3 事件实例 + +| 编号 | 需求 | 优先级 | +| --- | --- | --- | +| RQ-C-17 | 命中规则须生成事件实例,含:结构化元数据 + 抓拍图 + 视频片段 | P0 | +| RQ-C-18 | 视频片段须含事发前若干秒(pre-roll 回捞) | P0 | +| RQ-C-19 | 事件去重与防抖:同设备冷却、同站点聚合、已处置抑制 | P0 | +| RQ-C-20 | 事件推送到管理系统(Web),支持列表、筛选、详情、处置 | P0 | +| RQ-C-21 | 事件支持标记「误报」,误报样本自动进标注队列 | P0 | +| RQ-C-22 | 事件统计报表(按站点/类型/时段/处置结果) | P1 | + +### 4.3.4 预警投递 + +| 编号 | 需求 | 优先级 | +| --- | --- | --- | +| RQ-C-23 | 预警须有 ack(确认)环节,未 ack 自动升级 | P0 | +| RQ-C-24 | 升级链、超时值、联系人、时段策略可按站点/租户配置 | P0 | +| RQ-C-25 | 至少两条独立投递路径,且需有绕过互联网的通道(电话/短信) | P0 | +| RQ-C-26 | 区分「已发出 / 已送达 / 已被看到」三个事实,无回执按失败处理 | P0 | +| RQ-C-27 | 支持 App 推送、短信、语音呼叫、Webhook、大屏/声光设备 | P0 | +| RQ-C-28 | 支持限时静默(维护窗口),**必须强制限时并自动恢复** | P0 | +| RQ-C-29 | 业务预警与运维告警**不得共用**投递通道与值班配置 | P0 | +| RQ-C-30 | 告警先落库再投递,进程重启后未完成的升级链能续跑 | P0 | + +### 4.3.5 平台与运维 + +| 编号 | 需求 | 优先级 | +| --- | --- | --- | +| RQ-C-31 | 多租户隔离:数据、账号、配置、存储互不可见 | P0 | +| RQ-C-32 | RBAC:平台管理员 / 租户管理员 / 站点管理员 / 值班员 / 只读 | P0 | +| RQ-C-33 | 全链路审计日志,告警生命周期不可篡改留存 | P0 | +| RQ-C-34 | Prometheus 指标 + Grafana 看板(设备在线率、推理延迟、未收敛项) | P0 | +| RQ-C-35 | 支持边缘部署、中心部署、混合部署三种形态 | P1 | +| RQ-C-36 | 断网时边缘本地缓存事件,恢复后补传 | P0 | + +--- + +# 5. 目标人群与检测能力清单 + +「指定人群」在实现上分三类,**合规风险与技术难度依次上升**,需求分析阶段必须逐条确认是否真的需要: + +| 类别 | 判定依据 | 例子 | 是否需人脸 | 合规等级 | +| --- | --- | --- | --- | --- | +| A. 属性类 | 视觉外观属性 | 老人/儿童/成人、戴/未戴安全帽、穿工服 | 否 | 低 | +| B. 状态类 | 姿态与轨迹 | 跌倒、奔跑、聚集、徘徊、越线、滞留 | 否 | 低 | +| B+. 匿名同一性 | **ReID 跨镜再识别** | 同一人在此徘徊 10 分钟、跨机位跟随 | 否 | 中 | +| C. 身份类 | 人脸底库比对 | 住户/非住户、本校学生、黑白名单、具名人员 | **是** | **高** | + +## 5.1 决策(已定,2026-08-01) + +| 项 | 决策 | +| --- | --- | +| A、B 类 | P0,无争议 | +| **B+ ReID** | **默认手段**。所有"同一性"类需求(徘徊、尾随、跟随、跨镜追踪)一律优先用 ReID 实现 | +| **C 人脸识别** | **在方案范围内**,由客户/租户签署授权后启用;未授权租户系统层面不可开启 | + +> **原则:能用 ReID 解决的,不上人脸。** +> ReID 产生的是会话内的匿名 track/person 标识,能回答"是不是同一个人在这徘徊 10 分钟",但不回答"他是谁"。它满足 RQ-S3-05(徘徊)、RQ-S2-04(尾随)等大部分需求,且不涉及生物识别信息的采集与存储。 +> +> 人脸识别只用在**必须知道"是谁"**的需求上:白名单放行(本校学生/住户)、重点关注人员告警、访客登记核验。这类需求要逐条论证"为什么 ReID 不够"。 + +## 5.2 人脸识别启用的前置条件 + +授权不等于可以直接开。启用前必须齐备: + +| # | 前置条件 | 责任方 | +| --- | --- | --- | +| 1 | 租户签署书面授权(含用途、范围、期限、底库来源合法性承诺) | 客户 + 法务 | +| 2 | 被采集人(或未成年人监护人)的知情同意取得方式与留档流程 | 客户 | +| 3 | 采集场所的告示义务已履行 | 客户 | +| 4 | 底库数据的来源、录入流程、删除流程已定义 | 产品 + 客户 | +| 5 | 底库加密存储、独立访问审计、最小授权已实现 | 研发 | +| 6 | 未成年人场景的专项合规意见 | 法务 | + +> 校园场景(S2)涉及未成年人,即便有租户授权,第 6 项独立成阻塞条件。 + +## 5.3 派生需求(人脸识别) + +| 编号 | 需求 | 优先级 | +| --- | --- | --- | +| RQ-FR-01 | 人脸能力按**租户**开关,未授权租户在 API 与 UI 层均不可见 | P0 | +| RQ-FR-02 | 底库人员档案管理:录入、分组(白名单/关注名单/访客)、有效期、注销 | P0 | +| RQ-FR-03 | 底库支持到期自动失效(如学生毕业、住户搬离、访客当日有效) | P0 | +| RQ-FR-04 | 人脸特征值加密存储,**不存原始人脸图**(或原图与特征分库、单独授权) | P0 | +| RQ-FR-05 | 每一次比对命中与查询均记入独立审计日志,可追溯操作人 | P0 | +| RQ-FR-06 | 比对不可用/未命中时规则仍须工作(降级为"未知人员"),**不得因此漏报** | P0 | +| RQ-FR-07 | 支持置信度阈值配置与人工复核(高风险动作前需二次确认) | P1 | +| RQ-FR-08 | 支持底库整体导出与彻底删除(客户终止服务时) | P0 | +| RQ-FR-09 | 人脸相关数据的留存期限独立于视频证据,可单独配置 | P1 | + +--- + +# 6. 设备与环境约束调研 + +## 6.1 摄像头兼容性验证清单(沿用参考项目 M0 出口标准) + +采购 3–5 个候选型号做实测,**这是整个项目第一件要做的事**。 + +### 必过项 + +| 验证项 | 通过标准 | 不通过的后果 | +| --- | --- | --- | +| GetProfiles | 返回至少两个 profile(主码流 + 子码流) | 无法自动获取子码流,需人工配置 | +| GetStreamUri | 返回可用的 RTSP URL | 无法自动化开通 | +| SetSystemDateAndTime | 调用成功且生效 | 事件时间戳不可信 | +| 子码流规格 | 可设为 640×360 或 704×576 左右 | 带宽和算力估算失效 | +| 编码格式 | 支持 H.264 | H.265 浏览器无法原生预览 | +| WS-Security 认证 | digest 或 usernametoken 任一可用 | 认证失败 | +| RTSP over TCP | 长时间稳定不断流 | 家庭/无线网络下 UDP 丢包严重 | + +### 加分项 + +- 支持 ONVIF 事件订阅(移动侦测)—— 可作为触发式推理的一级信号 +- 支持修改 GOP 长度 —— 影响 pre-roll 回捞的关键帧间隔 +- 支持创建独立只读用户 —— 不必用 admin 凭据接入 +- 支持 ONVIF Profile T / 双向音频 —— 社区场景的远程喊话 + +> 任何一项必过项失败的型号直接从采购清单划掉。**后期写兼容代码的成本远高于换型号。** 验证结果整理成表格,作为采购白名单归档。 + +## 6.2 网络环境差异 + +| 场景 | 摄像头可达性 | 上行带宽 | 结论 | +| --- | --- | --- | --- | +| 家庭 | NAT 后,无公网 IP | 20–50 Mbps 上行,共享 | 必须边缘侧主动推流,中心不可主动拉 | +| 校园 | 专网,通常有机房 | 充足 | 可中心部署,也可校内边缘部署 | +| 社区 | 专网,有无机房不确定 | 视物业投入 | 混合,按项目定 | + +## 6.3 边缘硬件候选 + +| 平台 | 算力 | 适用 | 说明 | +| --- | --- | --- | --- | +| RK3588 | 6 TOPS NPU | 家庭单户、小型点位 | 无风扇、低噪、低功耗;模型需转 RKNN | +| Jetson Orin Nano/NX | 20–100 TOPS | 校园/社区中型边缘 | 与 Savant/DeepStream 原生契合 | +| x86 + 消费级 GPU | 高 | 中心机房 | ⚠️ GeForce 卡**同时硬件编码流数上限为 3**,做可视化回推时会卡死,需 L4/Quadro 类专业卡 | + +--- + +# 7. 已知约束与前提 + +## 7.1 技术约束(来自参考项目实测经验,直接继承) + +| 约束 | 后果 | 出处 | +| --- | --- | --- | +| mediamtx 通过 API 改的配置**不落盘**,重启即失效 | 设备台账必须在自有数据库,启动时 replay | 参考 §4.1 | +| 任何 path 配置变更会**踢掉当前发布者** | 改配置前必须比对哈希,没变绝不调 API | 参考 §4.1 | +| mediamtx 没有 enable/disable,只能删 path | 停用要靠鉴权回调返回 401 + kick | 参考 §4.1 | +| mediamtx 无 camera 实体,只有 path | 设备业务面必须自建 | 参考 §4.1 | +| 批量 API 调用无事务 | 只能幂等重试,不能回滚 | 参考 §6.2 | +| `sourceOnDemand` 下"挂上推理 source = 产生 reader = 开始拉流" | 暂停分析必须卸载 source,不能只在插件里 return | 参考 §6.4 | +| Savant 绑定 NVIDIA 硬件 + DeepStream 版本 | 硬件选型受限,需预留 Pipeless 备选 | 参考 §9.1 | + +## 7.2 许可约束 + +| 组件 | 许可 | 影响 | +| --- | --- | --- | +| mediamtx / gortsplib | MIT | ✅ 可商用 | +| Kerberos Agent | MIT | ✅ 可商用 | +| GoAlert | Apache-2.0 | ✅ 可商用 | +| Savant | 开源 | ✅ 需复核具体条款 | +| **Ultralytics YOLO**(v8/v11,含 -pose) | **AGPL-3.0** | ⚠️ 已决策沿用 YOLO,**须购买 Enterprise License**,见下 | +| YOLOX (Megvii) | Apache-2.0 | ✅ YOLO 家族内的宽松许可选项,作为不购授权时的替代 | +| RTMPose / RTMO (MMPose) | Apache-2.0 | ✅ 姿态备选,COCO 17 点定义与 YOLO-pose 相同,判定算法可直接复用 | +| 人脸识别模型(InsightFace 等) | 视具体模型而定 | ⚠️ 需逐个核对;部分模型权重仅限非商业用途 | + +## 7.2.1 模型许可决策(已定,2026-08-01) + +**决策:继续使用 YOLO。** + +由此产生的必办事项: + +| # | 事项 | 时限 | 说明 | +| --- | --- | --- | --- | +| 1 | 购买 **Ultralytics Enterprise License** | **M3 前必须完成** | 本项目是面向付费用户的网络服务,落在 AGPL-3.0 第 13 条(网络交互)覆盖范围。不购授权而严格执行 AGPL,需开源整个服务端 | +| 2 | 授权覆盖范围核对 | 采购时 | 确认覆盖:私有化交付的每个客户实例、边缘盒子内的模型、二次训练产出的权重 | +| 3 | 若采购未获批,启用替代路径 | M3 前 | 检测换 **YOLOX**(Apache-2.0,仍属 YOLO 家族),姿态换 **RTMPose**(Apache-2.0,COCO 17 点,判定算法不用改) | +| 4 | 人脸识别模型许可单独核对 | 启用人脸能力前 | 常见开源人脸模型的权重许可与代码许可可能不一致 | + +> 事项 3 是逃生通道,不是默认路径。把它写进文档是为了让"授权没批下来"不至于卡死 M3——迁移代价可控(关键点定义相同)。 + +## 7.3 合规约束(开工前需法务确认,**阻塞 M3**) + +- [ ] 视频/图像证据的最长保存期限 +- [ ] 各角色可查看范围的法定边界(家属能看什么、物业能看什么、校方能看什么) +- [ ] 被拍摄者(尤其被监护老人、未成年学生)的知情同意取得方式与留档 +- [ ] 公共区域摄像头的告示牌义务 +- [ ] 数据出境/第三方云服务使用的限制 + +**人脸识别专项**(客户已确认将授权使用,但授权 ≠ 合规完成,见 §5.2): + +- [ ] 租户授权书模板(用途、范围、期限、底库来源合法性承诺) +- [ ] 人脸信息的单独同意取得方式(生物识别信息通常需要单独同意,不能与一般隐私政策打包) +- [ ] 底库人员的知情权、查询权、删除权的响应流程与时限 +- [ ] **未成年人场景的专项意见**(S2 校园,独立阻塞项) +- [ ] 人脸数据的留存期限(独立于视频证据) +- [ ] 比对日志的留存与查阅权限 +- [ ] 客户终止服务时的底库彻底删除义务与证明方式 + +--- + +# 8. 待确认问题清单 + +需要与客户/产品明确的开放问题,**答案会实质改变架构**: + +| # | 问题 | 影响 | +| --- | --- | --- | +| Q1 | 首期落地哪一个场景?三个都做还是选一个打穿? | 决定 M1–M4 的取舍与工期 | +| Q2 | 是私有化交付(一个客户一套)还是 SaaS 多租户? | 决定多租户隔离深度、计费、升级方式 | +| ~~Q3~~ | ~~单个项目典型规模(路数)与总体目标规模?~~ **已答:默认 16 路,按 32 / 64 / 128 路横向扩展,单站点本阶段上限 128 路。见 §1.3** | ✅ 已闭环 | +| Q4 | 是否已有存量 NVR/平台需要对接(GB/T 28181 国标)? | 国标接入是独立工作量,需单列 | +| Q5 | 是否必须支持国产化信创环境(海光/鲲鹏 + 昇腾/寒武纪)? | 决定推理框架能否用 DeepStream/Savant | +| Q6 | 「管理系统」是本项目自研还是对接客户现有系统? | 决定前端工作量与 API 契约 | +| Q7 | 「App」是自研还是嵌入客户已有 App?推送用什么厂商通道? | 决定移动端工作量 | +| ~~Q8~~ | ~~是否需要人脸识别(C 类身份判定)?~~ **已答:需要,客户将授权。默认仍以 ReID 为主,人脸用于必须知道"是谁"的需求。见 §5.1–5.3** | ✅ 已闭环 | +| Q8-a | 人脸能力先在**哪个场景**落地?底库规模量级? | 决定底库存储与比对性能方案 | +| Q8-b | 底库数据由客户提供还是系统现场采集? | 决定录入模块工作量与合规责任划分 | +| Q9 | 家庭场景是否同步引入毫米波雷达做一级触发? | 决定 GPU 数量能否降一个数量级 | +| Q10 | 语音呼叫/短信是否有既定供应商? | 决定投递层对接工作量 | +| Q11 | 事件视频存哪(客户 NAS / 对象存储 / 公有云)?留多久? | 决定存储成本与合规 | +| Q12 | 验收标准如何定义?准确率/召回率的可接受阈值? | ⚠️ 必须在合同前定,见 §9 | + +--- + +# 9. 验收期望(需求收集阶段的初步共识) + +## 9.1 效果类指标的表述方式 + +> ⚠️ **不要在合同里承诺"跌倒检测准确率 ≥ 95%"这类数字。** 参考项目已记录的教训:公开数据集(Le2i / UR Fall)全是演员摆拍的表演性跌倒,真实老人多为缓慢滑落、部分遮挡,两者分布完全不同。在拿到现场基线之前,任何准确率承诺都无法验证。 + +建议表述方式: + +| 阶段 | 承诺形式 | +| --- | --- | +| 试点期(M3) | 交付**现场实测基线报告**:真实召回率、每路每天误报数、误报归因分类 | +| 运营期 | 承诺**误报收敛趋势**(如:运营 3 个月后每路每天误报 ≤ N 次)而非绝对准确率 | +| 全程 | 承诺**系统性 SLA**(延迟、可用性、投递成功率),这些是可测可控的 | + +## 9.2 系统性 SLA 目标(可承诺,待 M3 实测校准) + +| 指标 | 目标 | +| --- | --- | +| 事件发生 → 首次投递 | < 10s | +| 首次投递 → 送达回执 | < 5s(推送)/ < 30s(短信) | +| 全链路无人响应 → 人工介入 | < 3min(S1 场景)/ 按场景配置 | +| 告警丢失率 | 0(持久化后才确认触发) | +| 设备在线率 | ≥ 99%(排除用户侧断网断电) | +| 平台可用性 | ≥ 99.5% | + +--- + +# 10. 下一步 + +| 动作 | 责任方 | 阻塞什么 | +| --- | --- | --- | +| 回答 §8 剩余问题(Q1–Q7、Q8-a/b、Q9–Q12) | 产品 / 客户 | 需求分析定稿 | +| 采购 3–5 款摄像头做 §6.1 实测 | 硬件 / 交付 | M0 出口、所有接入开发 | +| 法务确认 §7.3 通用五项 | 法务 | M3 上线 | +| **法务确认 §7.3 人脸专项七项 + 出具授权书模板** | 法务 | **人脸能力启用(不阻塞 M3 主线)** | +| **采购 Ultralytics Enterprise License(§7.2.1)** | 采购 / 技术负责人 | **M3 前必须完成,否则走 YOLOX 替代路径** | +| 核对人脸识别模型的权重与代码许可 | 算法 | 人脸能力启用 | +| 三个场景各走访 2–3 个点位 | 产品 | 场景包定义 | + +--- + +> 本文档只负责**收集与澄清**,不做方案裁决。需求的分层、取舍与规格化见《02-需求分析》;技术实现路径见《03-通用场景应用方案》。 diff --git a/docs/raw/02-需求分析.md b/docs/raw/02-需求分析.md new file mode 100644 index 0000000..0209d0b --- /dev/null +++ b/docs/raw/02-需求分析.md @@ -0,0 +1,792 @@ +# 🔍 YoVision 智能视频事件平台 — 需求分析文档 + +> 版本 v0.3 · 2026-08-03 +> 上游:《01-需求收集》 · 下游:《03-通用场景应用方案》 +> 本文档负责把原始需求**分层、规格化、发现矛盾、推导出架构性约束**,不负责技术选型细节 + +--- + +# 1. 分析结论摘要 + +先给结论,后面是推导过程。 + +| # | 结论 | 依据 | +| --- | --- | --- | +| C1 | 系统必须切成「**通用底座 + 场景包**」两层,场景包是配置与插件,不是分支代码 | 三个场景 60% 需求重合,但检测能力与升级策略完全不同(§3) | +| C2 | 事件(Event)与预警(Alert)是**两个独立实体**,不可合并 | 一次事件可产生多次预警、多条投递;一次预警可聚合多个事件(§5.2) | +| C3 | 「指定人群」的判定分四类并在**能力层解耦**;**ReID 为默认手段,人脸识别为按租户授权开启的可插拔能力** | 合规风险与技术依赖不同;多数"同一性"需求不需要知道"是谁"(§4.3、§4.4) | +| C4 | 规则引擎必须放在**推理之后、告警之前**的独立环节,不能编进模型插件 | 规则变更频率远高于模型,且需支持热更新与多级覆盖(§6) | +| C5 | 接入层必须**控制面/数据面分离**,且数据面为边缘主动推流 | 家庭场景摄像头在 NAT 后,中心不可达(§7.1) | +| C6 | 系统状态一致性靠**水平触发的对账器**,不靠事件驱动 | 三个下游系统(媒体路由、推理、存储)无分布式事务(§7.3) | +| C7 | 默认 16 路可用单媒体/推理分片起步;扩展到 128 路必须**横向分片**,触发式推理用于降低平均算力 | GPU 数量不能由路数直接推导,须按真实模型、分辨率、FPS、batch 与硬件压测(§8.1–8.2) | +| C8 | 业务预警系统的核心是 **ack 与升级**,不是"发消息" | 没有 ack 环节的告警系统等于没有(§9.2) | +| C9 | **误报反馈闭环是产品飞轮,必须在 M3 第一天就有**,不是后期优化项 | 真实误报案例是最稀缺的训练数据(§10) | + +--- + +# 2. 需求分层 + +原始需求(《01》共 8 个场景需求组 + 36 条通用需求)按变化频率重新分层。**同一层内的东西一起变,不同层之间靠契约解耦。** + +```mermaid +flowchart TD + L5["L5 场景包
规则模板 · 升级策略 · 话术 · 报表口径"] + L4["L4 业务编排
事件生成 · 去重防抖 · 预警状态机 · 处置流"] + L3["L3 能力层
检测 · 姿态 · 跟踪 · ReID · 属性 · (人脸)"] + L2["L2 流水线层
解码 · 抽帧 · 调度 · 故障隔离 · 触发式唤醒"] + L1["L1 接入层
设备台账 · ONVIF · 媒体路由 · 隧道 · 对账"] + L0["L0 基础设施
多租户 · RBAC · 存储 · 可观测 · 审计"] + L5 --> L4 --> L3 --> L2 --> L1 --> L0 +``` + +| 层 | 变化频率 | 谁在改 | 改动方式 | +| --- | --- | --- | --- | +| L5 场景包 | 每周 | 实施/产品 | 配置,不发版 | +| L4 业务编排 | 每月 | 后端 | 发版 | +| L3 能力层 | 每季度 | 算法 | 模型热替换 | +| L2 流水线 | 半年 | 平台 | 发版 | +| L1 接入 | 半年 | 平台 | 发版 | +| L0 基础设施 | 年 | 平台 | 发版 | + +> **这张表是本文档最重要的产出。** 如果一个需求变更需要同时改 L5 和 L1,说明分层错了。典型反例:把"上课时段"这个校历概念写进 mediamtx 的 path 配置。 + +--- + +# 3. 场景共性与差异分析 + +## 3.1 共性度量 + +把《01》§4.2 的场景需求逐条拆成原子能力,统计复用度: + +| 原子能力 | S1 家庭 | S2 校园 | S3 社区 | 复用 | +| --- | :---: | :---: | :---: | :---: | +| 人体检测 + 跟踪 | ✅ | ✅ | ✅ | 3/3 | +| 姿态关键点 | ✅ | ✅ | ✅ | 3/3 | +| 跌倒判定 | ✅ | ✅ | ✅ | 3/3 | +| 区域入侵 / 越线 | ○ | ✅ | ✅ | 2/3 | +| 停留 / 徘徊时长 | ✅ | ✅ | ✅ | 3/3 | +| 静止 / 无活动 | ✅ | ○ | ✅ | 2/3 | +| 人群聚集 / 密度 | — | ✅ | ○ | 1.5/3 | +| 打架 / 剧烈动作 | — | ✅ | ○ | 1.5/3 | +| 攀爬 / 翻越 | — | ✅ | ✅ | 2/3 | +| 非人目标(电动车、堆物) | — | ○ | ✅ | 1.5/3 | +| ReID 跨镜跟踪(匿名) | ○ | ✅ | ✅ | 2.5/3 | +| 身份比对(人脸) | ○ | 🔐 | 🔐 | 授权后启用 | +| 高空抛物 | — | — | ✅ | 1/3 | + +✅ 核心 ○ 可选 — 不需要 🔐 需租户授权 + 前置条件齐备(§4.4) + +**结论**:前 6 项覆盖了三个场景的 P0 需求主体,构成**通用能力包**;后续按场景增量。高空抛物(RQ-S3-04)需要专用仰拍机位与独立轨迹算法,与其余能力无复用,**建议独立立项或后期外采,不进本期通用底座**。 + +## 3.2 差异分析(差异才是场景包要装的东西) + +| 维度 | S1 家庭 | S2 校园 | S3 社区 | +| --- | --- | --- | --- | +| 单点位路数 | 1–3 | 100–500 | 50–300 | +| 部署形态 | 边缘盒子(必须) | 校内边缘或中心 | 混合 | +| 摄像头可达性 | NAT 后 | 专网可达 | 专网可达 | +| 预警接收人 | 家属(非专业、随时可能没看手机) | 安保值班室(7×24 有人) | 物业值班(夜间可能无人) | +| 升级链 | 4 级,含语音电话与呼叫中心 | 2 级,值班室 → 校领导 | 3 级,值班 → 主管 → 业主群 | +| 超时容忍 | 30s / 60s / 90s | 2min / 10min | 1min / 5min | +| 误报容忍 | **极低**(家属会卸载 App) | 中(有人筛) | 中 | +| 隐私敏感度 | **极高** | 高(未成年人) | 中 | +| 常态录像 | ❌ 禁止上云 | ✅ 允许 | ✅ 允许 | + +> **关键洞察**:S1 的接收人是非专业个人,没有值班室兜底,所以升级链必须做到"穿透勿扰模式"级别;S2/S3 有值班室,升级链可以简化,但需要**批量事件的看板与工单**能力(S1 完全不需要)。**这两种形态的 UI 和交互是两个东西,不要试图用一套界面覆盖。** + +--- + +# 4. 领域模型 + +## 4.1 核心实体关系 + +```mermaid +erDiagram + TENANT ||--o{ SITE : "拥有" + SITE ||--o{ AREA : "划分" + SITE ||--o{ DEVICE : "部署" + DEVICE ||--o{ STREAM_BINDING : "产生" + AREA ||--o{ ZONE : "标注(多边形/警戒线)" + DEVICE ||--o{ ZONE : "画在画面上" + TENANT ||--o{ RULE : "定义" + RULE }o--|| SCENE_PACK : "继承自" + RULE ||--o{ EVENT : "命中生成" + EVENT ||--o{ EVIDENCE : "附带" + EVENT }o--o{ ALERT : "触发/聚合" + ALERT ||--o{ DELIVERY : "投递" + ALERT ||--o{ ALERT_LOG : "审计" + SITE ||--o{ CONTACT_CHAIN : "配置" + ALERT }o--|| CONTACT_CHAIN : "按级投递" +``` + +## 4.2 实体职责界定 + +| 实体 | 定义 | 关键约束 | +| --- | --- | --- | +| **Tenant 租户** | 计费与数据隔离的最小单位 | 所有查询必须带 tenant_id,无例外 | +| **Site 站点** | 一个物理场所(一户 / 一所学校 / 一个小区) | 升级链、时段策略挂在这一层 | +| **Area 区域** | 站点内的逻辑分区(教学楼 / 单元 / 客厅) | 用于权限范围与报表口径 | +| **Device 设备** | **不叫 Camera**,`device_type` 取 camera/radar/door/button | 为异构传感器预留(继承参考项目 §4.5) | +| **Zone 画面区域** | 画在某路画面上的多边形或警戒线 | 与 Area 是两回事:Area 是物理概念,Zone 是像素坐标 | +| **Rule 规则** | 「主体 × 条件 × 时空 × 动作」的可配置组合 | 三级覆盖:租户默认 → 站点 → 设备 | +| **Event 事件** | 一次被判定成立的客观发生 | **不可变**。误判只能被标记,不能被删改 | +| **Alert 预警** | 一次需要人响应的通知过程 | 有生命周期状态机,可被 ack | +| **Delivery 投递** | 一次向某人某通道的发送尝试 | **独立实体**,一次 Alert 有多条 | + +## 4.3 「指定人群」的建模 + +《01》§5 提出四类判定。分析后确定为**主体过滤器(Subject Filter)**,作用于跟踪目标而非规则整体: + +``` +Track(跟踪目标) + ├─ class: person | vehicle | e_bike | object + ├─ attributes: {age_group, posture, ppe, ...} ← A 类,模型直接产出 + ├─ states: {falling, running, loitering_sec, ...} ← B 类,时序分析产出 + ├─ anon_id: "pid_88213" | null ← B+ 类,ReID 匿名同一性,默认开 + └─ identity: {ref_id, group, confidence} | null ← C 类,人脸,按租户授权开 +``` + +| 判定类 | 实现位置 | 默认 | 开启条件 | 回答什么问题 | +| --- | --- | --- | --- | --- | +| A 属性 | 检测模型多头输出 / 独立分类器 | 开 | — | 他是什么样的人 | +| B 状态 | 时序窗口分析插件 | 开 | — | 他在做什么 | +| **B+ ReID** | ReID 模型,跨镜特征匹配 | **开(默认手段)** | — | **是不是同一个人** | +| C 身份 | 独立身份服务,异步比对后回填 | **关** | 租户授权 + §4.4 全部前置条件 | 他是谁 | + +**设计原则(已定)**: + +1. **能用 ReID 解决的不上人脸。** 徘徊、尾随、跟随、跨镜追踪这类"同一性"需求一律走 B+ 类。人脸只用于必须知道"是谁"的需求(白名单放行、关注名单告警、访客核验),且需逐条论证"为什么 ReID 不够"。 +2. **`identity` 为 null 时规则必须仍能工作**,降级为"未知人员"。身份服务不可用、比对超时、人脸角度不佳都会产生 null——**不得因此漏报**。这条决定了规则 DSL 必须显式声明 `on_unavailable` 语义(见《03》§2.5)。 +3. **`anon_id` 与 `identity` 是两个独立字段**,不可互相顶替。ReID 的 anon_id 只在会话/时间窗内有效,不是身份标识,也不应被持久化成长期人员画像。 + +## 4.4 身份识别(人脸)子系统的需求分析 + +客户已确认将授权使用人脸识别,因此该能力**进入方案范围**。但授权解决的是"能不能用",不解决"怎么用才安全"。分析出以下约束: + +### 4.4.1 授权是租户级开关,不是全局开关 + +| 层级 | 语义 | +| --- | --- | +| 平台级 | 能力是否编译/部署(私有化交付时可整体不部署) | +| **租户级** | **主开关**。未授权租户在 API 与 UI 层均不可见该能力,不是"能看到但点不动" | +| 站点级 | 在已授权租户内进一步限定生效范围(如只在校门口,不在教室) | +| 规则级 | 单条规则是否使用 identity 过滤 | + +> 未授权租户"看得到但用不了"是错误设计——它会诱导客户询问和绕过,也会让审计难以证明"该租户从未启用"。 + +### 4.4.2 底库是独立的高敏资产 + +| 需求 | 理由 | +| --- | --- | +| 底库与业务库**逻辑分离**,独立加密 | 泄露后果与一般业务数据不在一个量级 | +| **默认只存特征向量,不存原始人脸图** | 原图泄露可直接冒用;特征向量至少不可直接还原为可用照片。若业务必须留原图(如核验展示),原图单独分库 + 单独授权 | +| 人员档案必须有**有效期** | 学生毕业、住户搬离、访客当日有效——没有有效期的底库必然越积越脏 | +| 到期自动失效,不依赖人工清理 | 依赖运维记得清理是不可靠的 | +| 支持整体导出与彻底删除 | 客户终止服务时的义务 | +| 每次比对命中与每次底库查询均独立审计 | "谁查了谁"必须可追溯 | + +### 4.4.3 比对是异步旁路,不在主判定链路上 + +``` +主链路(必须实时):检测 → 跟踪 → 姿态/行为 → 规则 → 事件 + ↑ +旁路(可延迟、可失败):人脸检测 → 质量筛选 → 特征提取 → 底库比对 → 回填 identity +``` + +**推导**:若把比对放进主链路,一次底库慢查询会拖垮整条 pipeline 的实时性;而人身安全类事件(跌倒、打架)根本不需要知道是谁。 + +派生需求: + +| 编号 | 需求 | +| --- | --- | +| FR-ID-01 | 比对为异步旁路,超时/失败不阻塞事件生成 | +| FR-ID-02 | identity 回填有时间窗;超窗未回填则事件以 `identity: null` 定稿 | +| FR-ID-03 | 依赖 identity 的规则须声明 `on_unavailable`:`treat_as_unknown`(默认,倾向报警)或 `skip`(倾向不报) | +| FR-ID-04 | 人脸质量筛选(角度、清晰度、遮挡、尺寸)前置,低质量帧不送比对,避免误配 | +| FR-ID-05 | 置信度阈值可配;高风险动作(如触发告警、拒绝放行)前可要求人工复核 | + +> FR-ID-03 的两个取值对应两种业务倾向。**默认必须是 `treat_as_unknown`**:在监护与安防场景,"不确定是谁"应当倾向于报警而非放过。若某条规则选 `skip`,需在规则上显式写明并有审批记录。 + +### 4.4.4 误识别的后果分级 + +人脸比对有两种错误,后果完全不同,阈值不能用同一个: + +| 错误类型 | 场景 | 后果 | 阈值倾向 | +| --- | --- | --- | --- | +| 假阳性(认错人) | 白名单放行 | 陌生人被当成住户放行 → 安防失效 | **阈值调高**,宁可认不出 | +| 假阳性(认错人) | 关注名单告警 | 无辜者被当成关注对象告警 → **对个人的实质伤害** | **阈值调最高 + 强制人工复核** | +| 假阴性(认不出) | 白名单放行 | 住户被当成陌生人 → 体验差,但安全 | 可容忍 | +| 假阴性(认不出) | 关注名单告警 | 漏报 | 靠 ReID 与其他规则兜底 | + +> **关注名单类规则不得自动触发对人的处置动作**(如自动锁门、自动通报),只能生成待人工确认的事件。这是产品红线,写进 L4 编排层的硬约束。 + +--- + +# 5. 核心流程分析 + +## 5.1 主流程 + +```mermaid +flowchart TD + A["摄像头 / 传感器"] --> B["接入层
台账 · 隧道 · 媒体路由"] + B --> C{"触发判定
常态 / 运动 / 传感器"} + C -->|无触发| C + C -->|唤醒| D["推理流水线
检测 · 跟踪 · 姿态 · 属性"] + D --> E["规则引擎
主体过滤 × 时空 × 条件"] + E -->|未命中| F["丢弃
仅记指标"] + E -->|命中| G["事件生成器
回捞 pre-roll · 抓拍 · 元数据"] + G --> H{"去重 / 防抖 / 聚合"} + H -->|抑制| I["静默记录"] + H -->|通过| J["事件实例落库"] + J --> K["管理系统
看板 · 详情 · 处置"] + J --> L{"是否需要预警"} + L -->|是| M["预警状态机
投递 → ack → 升级"] + M --> N["App / 短信 / 语音 / Webhook / 大屏"] + N --> O["处置结果回填"] + K --> O + O --> P["误报标记 → 标注队列 → 模型迭代"] +``` + +## 5.2 为什么 Event 和 Alert 必须分开(C2 的推导) + +反例:把预警状态直接放在事件表上。会立刻遇到三个无法表达的情况: + +| 情况 | 合并模型下的困境 | +| --- | --- | +| 客厅两个机位拍到**同一次**跌倒 | 两条事件、两次预警,家属收到两遍 | +| 消防通道堆物**持续存在** | 每帧都命中规则,要么事件爆炸,要么无法表达"仍未整改" | +| 一次事件在**不同时间**升级到不同人 | 事件表上放不下多级投递记录 | +| 值班员**批量确认** 20 条事件 | ack 语义作用在哪个粒度说不清 | + +正确关系: + +``` +Event N ──┐ + ├──> Alert 1 ──> Delivery N +Event N ──┘ +``` + +- **Event**:一次客观发生,不可变,是证据与统计的单位 +- **Alert**:一次需要人响应的过程,可聚合多个事件,是 SLA 与考核的单位 + +## 5.3 预警生命周期状态机 + +``` +[触发] ─→ [投递中] ─→ [已送达] ─→ [已确认 ack] ─→ [处置中] ─→ [关闭] + │ │ │ │ + │ │ └─超时未 ack─→ [升级] ─→ 下一级投递 │ + │ └─投递失败─→ [升级] │ + └─防抖命中─→ [抑制] [标记误报] ←───┘ +``` + +**硬约束(继承参考项目 §10.10)**:告警**先落库再投递**。进程崩溃重启后,扫描所有非终态告警并接续升级链——与 §7.3 对账器的"水平触发"是同一个思路。 + +## 5.4 三个不同的事实,不可混淆 + +| 事实 | 含义 | 字段 | 拿不到时的处理 | +| --- | --- | --- | --- | +| 发出去了 | 调用了上游 API | `sent_at` | — | +| 送达了 | 上游回执确认到达终端 | `receipt_at` | **当失败处理并升级**,不做乐观假设 | +| 被看到了 | 用户主动 ack | `acked_at` | 超时升级 | + +--- + +# 6. 规则引擎需求分析 + +## 6.1 为什么必须独立(C4 的推导) + +| 如果把规则写进模型插件 | 后果 | +| --- | --- | +| 客户要改一个停留时长阈值 | 要改 Python 代码、重启 pipeline、影响同机所有路 | +| 不同租户要不同阈值 | 插件里写 if tenant == ... | +| 要审计"这条事件是哪条规则判的" | 无从追溯 | +| 要做规则的灰度/回滚 | 做不了 | + +**结论**:模型插件只输出**客观观测**(这里有个人、他的姿态是躺倒、他在多边形 A 内停留了 12 秒),规则引擎负责**主观判定**(这属于需要报警的情况)。这条边界要写进代码规范。 + +## 6.2 规则表达能力需求 + +一条规则必须能表达的五个维度: + +| 维度 | 内容 | 例 | +| --- | --- | --- | +| **主体** | class + 属性过滤 + 身份过滤 | 人 且 未识别为本校学生 | +| **时空** | 哪些设备/Zone + 哪些时段/日历 | 实验室 B 区 且 非上课时段 | +| **条件** | 观测量的逻辑组合 + 持续时长 | 进入区域 且 停留 > 3s 且 无教师同行 | +| **抑制** | 冷却期、聚合键、静默窗口 | 同区域 5 分钟内不重复 | +| **动作** | 事件等级 + 证据规格 + 通知策略 | 高危 + 前 10s 后 15s + 升级链 A | + +## 6.3 三级覆盖模型 + +``` +场景包默认规则 ──继承──> 租户规则 ──覆盖──> 站点规则 ──覆盖──> 设备规则 +``` + +覆盖语义必须明确定义(否则运维会被"为什么这条没生效"折磨死): + +| 字段类型 | 覆盖语义 | +| --- | --- | +| 标量(阈值、时长) | 就近覆盖 | +| 列表(联系人、通道) | **替换**,不合并(合并会导致意外扩大通知面) | +| 开关(enabled) | 就近覆盖,且允许下级关闭上级下发的规则 | +| Zone 坐标 | 只能在设备级定义,上级不可下发 | + +## 6.4 规则变更的非功能要求 + +| 要求 | 理由 | +| --- | --- | +| 热生效,不重启 pipeline | 一次重启影响同机全部路数 | +| 变更有版本与操作人记录 | 事故复盘时"谁把阈值改了" | +| 支持"试运行"模式:命中但不通知 | 新规则上线前评估误报量 | +| 支持按规则统计命中量与误报率 | 规则质量可度量,否则只能靠感觉调 | + +> **试运行模式是被低估的需求。** 没有它,每条新规则上线都是拿真实用户做实验。 + +--- + +# 7. 接入层需求分析 + +## 7.1 NAT 约束推导(C5) + +家庭场景摄像头在运营商 NAT + 家庭路由 NAT 之后,中心机房**无法主动发起连接**。校园/社区虽有专网,但不能假设每个项目都有公网 IP。 + +分析出的唯一可行结构:**控制面与数据面分离**(直接继承参考项目 §2.1) + +| 平面 | 方向 | 通道 | 流量 | +| --- | --- | --- | --- | +| 控制面 | 中心 → 设备 | 边缘侧 WireGuard 隧道 | 极小,仅配置与探活 | +| 数据面 | 边缘 → 中心 | 边缘主动推流(RTSP announce / SRT / RTMP) | 大 | + +派生需求: + +| 编号 | 需求 | +| --- | --- | +| FR-ACC-01 | 每个站点分配不冲突的内网段,设备地址确定化(如 `10.<站点高位>.<站点低位>.0/24`,摄像头从 `.10` 起) | +| FR-ACC-02 | 控制面失联**不得**中断视频推流(两者已解耦),仅告警 | +| FR-ACC-03 | 边缘侧断网时本地缓存事件与证据,恢复后补传 | +| FR-ACC-04 | 推流鉴权走 HTTP 回调到自有服务,注销/欠费/隐私模式在业务库判断,媒体层零配置变更 | + +## 7.2 设备身份与状态机 + +| 需求 | 理由 | +| --- | --- | +| 设备身份用**序列号**,不用 IP | DHCP 续租、Wi-Fi 漫游换 AP 都会换 IP | +| IP 变化时按序列号自动重定位并重连 | 家庭场景常态 | +| 设备状态含 `pending_activation` | 批量开通必然有凭据待补的中间态 | +| 探活用独立的 ONVIF 轻量调用,**不用流状态代替设备状态** | 「设备离线」与「无人订阅」在媒体层表现相同 | + +设备状态机: + +``` +[录入] → [pending_activation] ──补齐凭据──> [active] ⇄ [offline] + │ │ + └──────────────> [disabled] <──┘ +``` + +## 7.3 对账器:一致性方案(C6 的推导) + +一次设备开通需要在**多个独立系统**各做一次操作,且它们之间**没有分布式事务**: + +``` +数据库(唯一真相源) + ├──> 媒体路由:建 path + ├──> 推理流水线:挂 source + └──> 存储:建 bucket/前缀 +``` + +### 为什么必须是水平触发而非事件驱动 + +| 方案 | 部分失败时 | +| --- | --- | +| 事件驱动(边沿触发) | 事件丢了就永久不一致,需要补偿事务,补偿也会失败 | +| **水平触发(对账)** | 不处理"事件",只不断比对期望与实际并向期望收敛。失败自动在下一轮重试 | + +### 派生的硬性需求 + +| 编号 | 需求 | 理由 | +| --- | --- | --- | +| FR-REC-01 | 数据库是唯一真相源,下游均为被驱动的从属状态 | — | +| FR-REC-02 | 建 path 前先查是否存在,绝不盲目 add | 盲目 add 会踢掉正在推流的 publisher | +| FR-REC-03 | 改配置前比对 `desired_hash` / `applied_hash`,没变**绝不**调 API | 同上 | +| FR-REC-04 | 每个下游系统一行 sync_state,全部收敛才算开通成功 | 中间态必须可见 | +| FR-REC-05 | 开通顺序:媒体路由先 → 推理后;停用顺序:推理先 → 媒体路由后 | 建立与拆除逆序,写进代码注释 | +| FR-REC-06 | 部分失败**不回滚,只收敛**(退避重试) | 回滚本身会失败,导致回滚的回滚,无限递归 | +| FR-REC-07 | 每个下游系统独立限流预算(各 8 并发),不共享信号量 | 一个系统变慢会饿死另一个 | +| FR-REC-08 | 孤儿资源清理需有**安全闸**:单轮删除量 > 总量 10% 时停止并告警 | 防止"期望集合意外为空"把全部路数拆掉 | +| FR-REC-09 | 不设放弃阈值,持续重试 + 告警引入人工 | 对账器放弃 = 设备静默不工作,监护场景不可接受 | +| FR-REC-10 | 循环周期:正常 30s,有未收敛项降到 5s,另有 10min 全量对账(含孤儿扫描) | — | + +### 必须暴露的指标 + +| 指标 | 用途 | +| --- | --- | +| `reconciler_unconverged_bindings{system}` | 核心健康指标,持续 > 0 即异常 | +| `reconciler_orphans_detected{type}` | 有人手改了生产系统 | +| `reconciler_safety_brake_triggered` | 安全闸触发,**应当直接呼人** | +| `reconciler_loop_duration_seconds` | 循环超时 = 规模到顶,该分片了 | + +--- + +# 8. 非功能需求分析 + +## 8.1 容量模型 + +本项目以**单站点 16 路为默认交付规格**,支持 32 / 64 / 128 路扩展,单站点本阶段上限 128 路。16 是默认配额而非代码上限;128 路通过增加媒体分片与推理 Worker 实现,不把全部流压在一个进程或一块 GPU 上。 + +以下仅用于初步预算,最终以 M1–M5 的真实流水线压测为准: + +| 项 | 默认 16 路 | 上限 128 路 | 说明 | +| --- | ---: | ---: | --- | +| 子码流 640×360 @ 512 kbps | ≈ 8 Mbps | ≈ 64 Mbps | 推理优先使用子码流 | +| 主码流按 4–6 Mbps | ≈ 64–96 Mbps | ≈ 512–768 Mbps | 常态录像与证据回捞的预算口径 | +| 抽帧 5 fps | 80 帧/秒 | 640 帧/秒 | GPU、解码器和 batch 均需实测 | +| 媒体分片 | 1 个 | 建议 4 个起步 | `media_shard.max_streams` 初始建议 32;可按故障域降为 16 | +| 推理分片 | 1 个逻辑分片 | 最多 8 个 16 路逻辑分片 | 一个 Worker 可承载几个分片由压测决定 | + +容量必须按四个维度分别验收:接入/录像带宽、同时解码、AI 推理、证据存储。不能用“NVR 能录 128 路”推导“GPU 能分析 128 路”。连续录像时每 1 Mbps 约产生 10.8 GB/天;因此默认由客户已有 NVR 承担常态录像,YoVision 优先保存事件证据,避免重复存储全量视频。 + +> **实现约束**:`site.max_video_channels` 默认 16、最大 128;`media_shard.max_streams` 与 `inference_profile.max_sources` 为配置项。数据库、规则、数组、前端分页与批量操作中不得写死 16。 + +## 8.2 算力与触发式推理(C7 的推导) + +| 模式 | 默认 16 路 | 扩展到 128 路 | 适用 | +| --- | --- | --- | --- | +| 常态全量推理 | 单逻辑推理分片起步 | 按 16 路切成最多 8 个逻辑分片,分配到多个 Worker | 高价值点位 | +| **触发式推理** | 保持同一分片结构,空闲时不解码/不推理 | 通过降低各分片占空比减少平均 GPU 需求 | 家庭、社区大部分点位 | + +GPU 型号和数量不在需求阶段写死。M1 用 5 路打通,M3 以 16 路建立性能基线,再依次验证 64 路和 128 路;每档都以端到端 P95/P99 延迟、丢帧率和故障隔离为出口标准。 + +触发信号的三个来源(按成本与可靠性排序): + +| 触发源 | 成本 | 可靠性 | 适用 | +| --- | --- | --- | --- | +| 运动侦测(软件,参考 Frigate 思路) | 零 | 中(光照变化、树叶晃动会误触) | 通用兜底 | +| 摄像头 ONVIF 事件订阅 | 零 | 中 | 支持的型号 | +| **毫米波雷达等传感器** | 硬件成本 | 高 | 家庭卧室/卫生间(且解决隐私) | + +> **重要澄清**:流水线框架(Savant 等)主要解决多路调度、复用与故障隔离,不会自动降低单帧推理成本。触发式推理是降低平均算力的主要手段之一,但最终容量仍取决于模型、分辨率、FPS、batch、解码方式与硬件实测。 + +## 8.3 两级判定与误报(准确性需求的可实现形式) + +单一信号源的误报都会让使用者很快失去信任: + +| 信号源 | 典型误判 | +| --- | --- | +| 纯视觉 | 遮挡、逆光、坐下、弯腰、宠物、访客 | +| 纯雷达 | 快速坐下、弯腰 | + +**两级串联是行业标准做法**: +- 一级(低成本、常开):宁可多报,不能漏报 +- 二级(高成本、按需唤醒):审核一级的候选,剥离干扰 + +派生需求:架构必须支持"一级触发源"可插拔(运动侦测 / ONVIF 事件 / 雷达 / 门磁),且触发入口独立成 handler,接新传感器时不改动流控制逻辑。 + +## 8.4 分类粒度对误报的影响 + +参考实现的经验:`GajuuzZ/Human-Falling-Detect-Tracks` 对每个人轨迹每 30 帧预测动作,输出**站立/行走/坐下/躺下/起身/坐下动作/跌倒共 7 类**。 + +> **七分类而非二分类是降误报的关键。** 二分类模型面对"坐下"只能在"跌倒/非跌倒"之间硬选;七分类可以明确输出"这是坐下"。本项目所有行为判定模型均按此原则设计。 + +**负面经验**:`Y-B-Class-Projects/Human-Fall-Detection` 用约 500 张网络图片训 LSTM 完全学不会,最终退回 if-else 规则。→ **小数据量下时序模型学不动,先解决数据再上模型。** + +## 8.5 可用性与降级 + +| 故障 | 要求 | 降级行为 | +| --- | --- | --- | +| 单路摄像头掉线 | 不影响其他路 | 告警 + 自动重连 | +| 推理节点崩溃 | 不影响接入与其他节点 | 该分片流量转移或降级为仅录制 | +| 算力不足 | 不得积压至崩溃 | **实时模式:主动丢帧优于积压** | +| 突发流量(早晨集中活动) | 不丢事件 | 持久化磁盘缓冲,抗突发 | +| 中心平台不可达 | 不丢事件 | 边缘缓存,恢复后补传 | +| 通知供应商故障 | 不丢预警 | 多供应商冗余,`provider` 字段为切换预留 | + +## 8.6 安全需求 + +| 编号 | 需求 | +| --- | --- | +| NFR-SEC-01 | 设备凭据(ONVIF 用户名密码)加密存储,不落明文日志 | +| NFR-SEC-02 | 推流鉴权,未授权设备推不上来 | +| NFR-SEC-03 | 所有查询强制带 tenant_id,在数据访问层统一拦截,不依赖业务代码自觉 | +| NFR-SEC-04 | 证据文件访问用带时效的签名 URL,不可枚举 | +| NFR-SEC-05 | 管理界面不得对局域网默认无密码暴露(第三方面板的常见坑) | +| NFR-SEC-06 | 人脸底库(若启用)独立加密存储,独立审计,独立授权 | + +## 8.7 合规需求 + +| 编号 | 需求 | +| --- | --- | +| NFR-CMP-01 | `alerts` 与 `alert_deliveries` **只追加不修改**,状态变更写事件表,独立备份 | +| NFR-CMP-02 | 每条预警须能回答:何时触发、基于什么证据、向谁发过、走什么通道、是否送达、谁在何时确认、处置结果、升到第几级、每级耗时 | +| NFR-CMP-03 | 证据留存期限可配置,到期自动清理并留清理记录 | +| NFR-CMP-04 | 数据主体的知情同意记录可查 | +| NFR-CMP-05 | 隐私区域(卧室、卫生间)在设备录入时即标记,系统层面拒绝配置摄像头类设备 | + +> NFR-CMP-05 是**代码层面的硬约束**,不是流程约束。依赖实施人员"记得别装"是不可靠的。 + +--- + +# 9. 预警系统需求分析 + +## 9.1 两类告警必须彻底分开 + +| | 运维告警 | 业务预警 | +| --- | --- | --- | +| 触发源 | 设备离线、对账未收敛、GPU 过载、缓冲积压 | 规则命中 | +| 收件人 | 运维团队 | 家属 / 值班员 / 呼叫中心 | +| 延迟容忍 | 分钟级 | **秒级** | +| 漏报后果 | 服务降级 | 人身伤害 + 法律责任 | +| 方案 | Prometheus + Alertmanager(现成) | **自研状态机 + 复用投递通道** | + +> **两套系统不得共用投递通道和值班配置**,否则运维噪声会淡化业务预警。这是本节最重要的一条。 + +## 9.2 ack 是核心,不是附加功能(C8) + +「发出去」和「被人看到」是两回事。整个预警系统的设计围绕 **确认或升级** 这一对概念展开: + +- 无 ack → 无法知道是否有人在处理 → 无法决定是否升级 → 升级链形同虚设 +- 无 ack → 无法度量响应时长 → 无法做 SLA 与考核 +- 无 ack → 出事后无法举证"我们报了且某人确认了" + +## 9.3 升级链需求 + +| 需求 | 说明 | +| --- | --- | +| 层级数与超时值按站点可配 | 独居老人与有同住家属的策略应当不同 | +| 时段策略 | 白天找子女、夜间找同住者;校园白天找安保、夜间找宿管 | +| 通道冗余 | 至少两条独立路径,**且需有绕过互联网的通道** | +| 联系人可用性 | 支持轮值排班,而非固定人 | + +**通道独立性分析**:推送依赖 APNs/FCM,短信依赖运营商,任何单一通道都可能整体故障。语音电话是唯一能穿透勿扰模式且不依赖互联网的通道,**在 S1 场景是必需项**。 + +## 9.4 去重与防抖 + +| 规则 | 参数 | 理由 | +| --- | --- | --- | +| 同设备冷却期 | 默认 5min | 避免同一事件多次触发 | +| 同站点聚合 | 多设备同时触发合并为一条 | 两个机位拍到同一次跌倒 | +| 已处置抑制 | 已 ack 期间不再新建同类 | 人已到现场 | +| 维护窗口 | 用户主动静默 | **必须强制限时(建议最长 4h)并到期前提醒** | + +> **永久静默是这类系统最常见的事故成因。** 静默功能的限时是硬约束,不提供"永久"选项。 + +--- + +# 10. 数据闭环需求(C9) + +## 10.1 为什么这是 P0 而非优化项 + +| 事实 | 后果 | +| --- | --- | +| 公开数据集(Le2i / UR Fall 等)是演员摆拍的表演性动作 | 与真实场景分布不符,训出来的模型现场表现差 | +| 真实误报案例是最稀缺的训练数据 | 只能靠运营积累,无法采购 | +| 没有闭环,模型不会随运营时间变好 | 产品没有护城河 | + +## 10.2 闭环设计 + +``` +用户点「误报」→ 自动把 evidence(视频片段 + 结构化观测序列)推进标注队列 +用户确认「有效」→ 样本进正例库 + ↓ + 定期评估 → 规则阈值调整 / 模型重训 → 灰度上线(试运行模式) +``` + +**要求:从 M3 第一天就开始存,不要等到想训模型时才发现没数据。** `outcome = true_positive` 的样本同样是金矿。 + +## 10.3 派生的数据需求 + +| 编号 | 需求 | +| --- | --- | +| FR-DAT-01 | 事件证据须包含**结构化观测序列**(关键点、包围框、track_id 时序),不只是视频 | +| FR-DAT-02 | 误报标记须记录归因(遮挡/光照/宠物/坐下/其他),供分类统计 | +| FR-DAT-03 | 标注队列与训练数据的导出需符合 NFR-CMP-03 的留存与脱敏要求 | +| FR-DAT-04 | 每条规则的命中量、误报率可查询,作为规则质量指标 | + +--- + +# 11. 数据模型(分析层) + +在参考项目基础上扩展多租户与规则/事件。**字段级设计见《03》,此处只给结构与关键约束。** + +```sql +-- ── 组织(Bell 为真相源;Sense 只读配额投影)──────── +tenants(id, code, name, status, plan, created_at) +sites(id, tenant_id, code, name, scene_pack, subnet CIDR, timezone, status, + max_video_channels INT NOT NULL DEFAULT 16 CHECK (max_video_channels BETWEEN 1 AND 128)) +areas(id, site_id, name, parent_id) + +-- ── 设备(不叫 cameras,为异构传感器预留)──── +devices( + id, tenant_id, site_id, area_id, + device_type, -- camera | radar | door | button + serial UNIQUE, -- ⚠️ 身份用序列号,不用 IP + vendor, model, + onvif_addr, onvif_user, onvif_secret, -- secret 加密存储 + state, -- pending_activation | active | offline | disabled + privacy_flag, -- 隐私区域标记,为 true 时拒绝 camera 类型 + last_seen_at +) +stream_bindings(id, device_id, mtx_instance, inference_shard, path_name, rtsp_url, profile_token, enabled, + UNIQUE(mtx_instance, path_name)) + +-- ── 对账(每下游系统一行)────────────── +sync_state(binding_id, system, desired_hash, applied_hash, + last_error, attempt, retry_after, updated_at, + PRIMARY KEY(binding_id, system)) +-- system ∈ {'mediamtx','inference','storage'} +-- 全部行 desired_hash = applied_hash 才算真正开通 + +-- ── 规则 ────────────────────────────── +scene_packs(id, code, name, version, spec JSONB) -- 场景包模板 +zones(id, device_id, name, geom_type, coords JSONB) -- 画面区域,仅设备级 +rules(id, tenant_id, site_id, device_id, -- 三级覆盖,逐级可为 NULL + scene_pack_id, code, name, enabled, dry_run, -- dry_run = 试运行 + spec JSONB, version, updated_by, updated_at) +rule_versions(rule_id, version, spec, updated_by, updated_at) -- 变更审计 + +-- ── 事件(不可变)────────────────────── +events( + id, tenant_id, site_id, device_id, rule_id, rule_version, + kind, severity, confidence, + occurred_at, detected_at, -- 发生时刻 vs 判定时刻,两者都要 + subject JSONB, -- track_id / 属性 / anon_id(ReID) / identity + identity_status + observation JSONB, -- 触发时的客观观测量 + evidence_uri, snapshot_uris, + dedup_key, aggregated_into, -- 聚合关系 + outcome, -- true_positive | false_positive | unknown + outcome_reason, outcome_by, outcome_at +) + +-- ── 身份识别(独立库/独立 schema,独立加密与审计)──── +-- 仅在租户授权后创建;未授权租户无此 schema 的任何数据 +tenant_features(tenant_id, feature, enabled, authorized_by, + authorized_at, expires_at, doc_uri) -- feature='face_recognition' + +face_libraries(id, tenant_id, site_id, name, purpose, retention_days) +-- purpose ∈ {'whitelist','watchlist','visitor'};不同 purpose 的阈值与处置策略不同 + +persons(id, library_id, external_ref, display_name, + group_tags, valid_from, valid_until, -- ⚠️ 有效期必填,到期自动失效 + consent_ref, -- 知情同意留档指针 + created_by, created_at, revoked_at) + +face_vectors(id, person_id, embedding VECTOR, model_ver, + quality_score, source, created_at) -- 默认只存向量 +face_images(id, person_id, image_uri, created_at) -- 可选,单独授权才写入 + +face_match_logs(id, tenant_id, site_id, device_id, at, + person_id, score, threshold, decision, -- matched / below_threshold / no_candidate + event_id, reviewed_by, reviewed_at) -- 只追加,独立审计 + +-- ── 预警 ────────────────────────────── +alerts(id, tenant_id, site_id, kind, severity, state, + triggered_at, acked_by, acked_at, closed_at, + escalation_level, contact_chain_id) +alert_events(alert_id, event_id) -- 多对多 +contact_chains(id, site_id, level, contact_name, channels JSONB, + active_hours, timeout_sec, UNIQUE(site_id, level)) +alert_deliveries(id, alert_id, level, channel, provider, + sent_at, receipt_at, failed_reason) +alert_logs(id, alert_id, at, actor, from_state, to_state, detail JSONB) -- 只追加 + +CREATE INDEX ON alerts(state) WHERE state NOT IN ('closed','suppressed'); +``` + +**关键约束复述**: +1. `devices` 而非 `cameras`——为雷达/门磁/按钮预留 +2. 设备身份用 `serial`——IP 会变 +3. `sync_state` 每系统一行——无分布式事务,中间态必须可见 +4. `alert_deliveries` 是独立实体——一次预警多条投递,各自有回执 +5. `events` 与 `alert_logs` 只追加——合规要求 +6. `events.subject` 中 `anon_id`(ReID)与 `identity`(人脸)是两个独立字段,不可互相顶替 +7. 人脸相关表独立 schema、独立加密、独立审计;`persons.valid_until` 必填,无"永久有效"选项 +8. `sites.max_video_channels` 默认 16、最大 128;配额在写入设备时校验,分片容量由运行时配置与压测决定 +9. `sites.max_video_channels` 由 Bell 持有,Sense 通过版本化内部 API 或只读投影校验设备新增/启用;不得跨 schema 直接写入 + +--- + +# 12. 优先级与里程碑映射 + +## 12.1 MoSCoW + +| 级别 | 内容 | +| --- | --- | +| **Must** | 摄像头接入与台账、NAT 穿透、对账器、人体检测+跟踪+姿态、跌倒/区域入侵/停留三类规则、事件实例与证据、预警状态机与升级链、多租户与 RBAC、审计、误报反馈闭环 | +| **Should** | 触发式推理、**ReID(匿名同一性,默认手段)**、时段/日历策略、试运行模式、统计报表、边缘缓存补传、Grafana 看板 | +| **Could** | **人脸识别子系统**(租户授权开启:底库管理、异步比对旁路、审计)、人群聚集/打架检测、非人目标(电动车/堆物)、雷达接入、国标 GB/T 28181 对接、大屏与声光联动 | +| **Won't(本期)** | 高空抛物、S4 园区场景、车牌识别、**人脸在未成年人场景(S2)的启用**(待专项合规意见) | + +> **人脸识别的排期说明**:客户已确认授权,能力进入方案范围,但它**不在主线关键路径上**(§4.4.3 已论证比对是异步旁路)。因此定为 Could 并安排在 M5——这样 M3 的端到端主线不被合规流程与底库模块阻塞。ReID 提到 Should 并在 M4 交付,先把"同一性"类需求兜住。 + +## 12.2 里程碑与出口标准 + +| 阶段 | 内容 | 出口标准 | +| --- | --- | --- | +| **M0** | 摄像头兼容性验证 + 需求定稿 | 可直接运行 MiBeeNvr 作为隔离实验室测试台;3–5 款型号跑通 GetProfiles/GetStreamUri/SetSystemDateAndTime,采购白名单归档;M0 代码不得作为生产基线;《01》§8 的 Q1–Q12 有答案 | +| **M1** | 接入骨架 + 媒体路由 | **MediaMTX 正式进入且不得由完整 MiBeeNvr 替代**;5 路自动建 path、探活、断线重建;设备台账可用 | +| **M2** | 对账器 + 多租户 + 隧道 | 10 个站点试点,至少一个站点完成 16 路开通/停用全流程;`unconverged = 0` 稳定 | +| **M3** | 推理接入 + 规则引擎 + 事件与预警 | **默认 16 路**端到端稳定运行;跑通一个场景的三类规则;拿到现场误报基线,反馈闭环已收数据 | +| **M4** | 64 路分片 + 灰度扩容 + 管理系统 | 64 路稳定运行;单分片故障不影响其他分片;看板、批量操作与处置流可用 | +| **M5** | 128 路容量验收 + 第二/第三场景包 + 触发式推理 + **人脸识别子系统** | **128 路通过横向分片稳定运行,扩容不改业务代码**;场景包为纯配置交付;人脸能力在一个已授权租户上跑通 | +| **M6(阶段二)** | 异构传感器接入 + 两级判定 | 误报率显著下降,视频常态采集下降 | + +> **M5 的出口标准是本项目的架构验收点。** 如果新增场景需要改 L1–L4 的代码,说明 §2 的分层没做成,需要返工而不是继续叠加。 + +--- + +# 13. 风险清单 + +| 风险 | 影响 | 缓解 | +| --- | --- | --- | +| **Ultralytics YOLO 商业授权未按时到位** | 已决策沿用 YOLO;未购授权则落在 AGPL-3.0 第 13 条,需开源整个服务端 | **M3 前完成采购**;逃生通道:检测换 YOLOX(Apache-2.0,同属 YOLO 家族)、姿态换 RTMPose(COCO 17 点,判定算法不用改) | +| 授权范围未覆盖全部形态 | 私有化实例、边缘盒子内模型、二次训练权重可能不在授权内 | 采购时逐项书面确认 | +| **人脸误识别造成对个人的实质伤害** | 无辜者被当成关注对象告警 | §4.4.4 分级阈值;关注名单类规则**不得自动触发对人的处置动作**,只生成待人工确认事件 | +| **人脸底库泄露** | 生物识别信息泄露,后果与一般数据不在一个量级 | 独立 schema + 独立加密 + 默认只存特征向量不存原图 + 独立访问审计 | +| 底库越积越脏(毕业/搬离人员未清) | 误配率上升,合规风险累积 | `valid_until` 必填,到期自动失效,无"永久有效"选项 | +| 人脸能力拖累主线工期 | M3 被合规流程阻塞 | §4.4.3 比对为异步旁路;排期在 M5,不进 M3 关键路径 | +| 人脸模型权重许可与代码许可不一致 | 侵权 | 启用前逐个核对权重许可 | +| **公开数据集与真实场景脱节** | 模型上线后表现远低于论文指标 | M3 首要产出是**真实误报案例库**,不是模型指标;不承诺绝对准确率 | +| 摄像头 ONVIF 实现差异 | 部分型号无法接入 | 采购前真机验证(M0),白名单管控 | +| 摄像头时钟漂移 | 事件时间戳全乱,审计失效 | 开通时校时,定期校准 | +| NAT 隧道不稳 | 控制面失联 | 已与数据面解耦,告警但不阻断推流 | +| path reload 踢流 | 用户看到卡顿 | 变更前比对哈希,变更集中在维护窗口 | +| H.265 兼容性 | 浏览器无法原生预览 | 统一设为 H.264 | +| **隐私合规不过** | 项目无法落地 | 尽早引入触发式采集减少常态视频;隐私区域代码级硬约束;法务前置 | +| 推理框架绑定 NVIDIA | 硬件选型受限,信创环境不可用 | 推理代码与框架解耦,预留 ONNX Runtime / Pipeless 备选 | +| 场景包泛化失败,变成三套代码 | 维护成本三倍 | M5 出口标准强制验收 | +| 值班员被误报淹没后忽略全部告警 | 真实事件被漏 | 试运行模式 + 规则质量指标 + 误报率看板 | +| 客户要求"什么都能查"的全量录像 | 与隐私需求冲突且成本高 | 需求收集期即明确事件驱动采集的边界 | + +--- + +# 14. 需求追溯矩阵(节选) + +| 原始需求 | 分析结论 | 实现层 | 里程碑 | +| --- | --- | --- | --- | +| RQ-C-02 NAT 后接入 | C5 控制面/数据面分离 | L1 | M1/M2 | +| RQ-C-03 序列号身份 | §7.2 设备状态机 | L1 | M1 | +| RQ-C-11 规则组合表达 | C4 独立规则引擎,§6.2 五维度 | L4 | M3 | +| RQ-C-12 三级覆盖 | §6.3 覆盖语义表 | L4/L5 | M3 | +| RQ-C-16 人脸按租户授权 | C3 四类判定;§4.4 租户级主开关 | L3 | M5 | +| RQ-FR-01 未授权租户不可见 | §4.4.1 不是"看得到点不动" | L0/L3 | M5 | +| RQ-FR-03 底库有效期自动失效 | §4.4.2 无"永久有效" | L3 | M5 | +| RQ-FR-04 只存特征不存原图 | §4.4.2 | L3 | M5 | +| RQ-FR-06 不可用时不得漏报 | §4.3 原则 2 + FR-ID-03 `on_unavailable` | L3/L4 | M5 | +| RQ-S3-05 陌生人徘徊 | §4.3 原则 1:走 ReID,不上人脸 | L3 | M4 | +| RQ-S2-04 尾随进校 | 同上,ReID + 门禁事件融合 | L3/L4 | M4 | +| RQ-C-17 事件实例 | C2 Event/Alert 分离 | L4 | M3 | +| RQ-C-19 去重防抖 | §9.4 四条规则 | L4 | M3 | +| RQ-C-21 误报标记 | C9 数据闭环 | L4 | **M3(不可延后)** | +| RQ-C-23 ack 与升级 | C8 预警状态机 | L4 | M3 | +| RQ-C-25 通道独立 | §9.3 语音电话必需 | L4 | M3 | +| RQ-C-28 限时静默 | §9.4 硬约束,无永久选项 | L4 | M3 | +| RQ-C-29 两类告警分离 | §9.1 | L0/L4 | M2 | +| RQ-C-30 先落库再投递 | §5.3 硬约束 | L4 | M3 | +| RQ-C-31 多租户 | §11 强制 tenant_id 拦截 | L0 | M2 | +| RQ-S1-05 隐私区域禁摄像头 | NFR-CMP-05 代码级约束 | L0/L1 | M2 | +| RQ-S1-08 3 分钟人工介入 | §9.3 升级链,站点级配置 | L5 | M3 | +| RQ-S2-08 未成年人合规 | §7.3 法务阻塞项 | — | M0 | +| RQ-S3-04 高空抛物 | §3.1 无复用,建议独立立项 | — | 移出本期 | + +--- + +> 本文档的分析结论(§1 的 C1–C9)是《03-通用场景应用方案》的输入约束。任何技术选型若与这些结论冲突,应先回来修改本文档,而不是在方案里悄悄绕过。 diff --git a/docs/raw/03-通用场景应用方案.md b/docs/raw/03-通用场景应用方案.md new file mode 100644 index 0000000..d6ca725 --- /dev/null +++ b/docs/raw/03-通用场景应用方案.md @@ -0,0 +1,1255 @@ +# 🏗️ YoVision 智能视频事件平台 — 通用场景应用方案 + +> 版本 v0.4 · 2026-08-03 +> 上游约束:《02-需求分析》§1 的 C1–C9 +> 本文档描述**如何用一套底座支撑多个场景**,以及每个场景怎么落到配置上 + +--- + +# 1. 方案总纲 + +## 1.1 一句话 + +**一套通用底座(接入 → 流水线 → 能力 → 编排),场景差异全部收敛到「场景包」这一层配置里。** + +新增一个场景 = 写一个场景包(规则模板 + 升级策略 + 报表口径 + 话术),**不改核心代码**。这是《02》M5 的验收标准。 + +## 1.2 分层落地 + +```mermaid +flowchart TD + subgraph L5["L5 场景包(配置)"] + SP1["居家养老包"]:::pack + SP2["校园包"]:::pack + SP3["社区包"]:::pack + end + subgraph L4["L4 业务编排(Go)"] + RE["规则引擎"] --> EV["事件生成器
回捞 · 抓拍 · 去重"] + EV --> AL["预警状态机
ack · 升级 · 回执"] + AL --> DL["投递适配器
推送/短信/语音/Webhook"] + end + subgraph L3["L3 能力层(模型)"] + DET["检测+跟踪
YOLO"] --> POSE["姿态
YOLO-pose"] + DET --> ATTR["属性"] + DET --> REID["ReID 匿名同一性
默认手段"] + DET -.旁路.-> FACE["人脸比对
租户授权后"] + POSE --> TEMP["时序行为分析"] + end + subgraph L2["L2 流水线(Savant / 等价框架)"] + TRIG["触发器
运动/ONVIF事件/传感器"] --> PIPE["解码·抽帧·推理调度"] + BUF["磁盘缓冲"] --> PIPE + end + subgraph L1["L1 接入(Go + mediamtx)"] + DEV["设备台账"] --> RECON["对账器"] + RECON --> MTX["媒体路由"] + TUN["WireGuard 隧道"] --> DEV + end + subgraph L0["L0 基座"] + MT["多租户 · RBAC"] + ST["对象存储"] + OBS["Prometheus + Grafana + 审计"] + end + L5 -.驱动.-> L4 + L4 --> L3 --> L2 --> L1 --> L0 + classDef pack fill:#e8f0fe,stroke:#4285f4 +``` + +## 1.3 组件选型 + +| 层 | 组件 | 许可 | 角色 | 备注 | +| --- | --- | --- | --- | --- | +| L1 媒体路由 | **mediamtx** (bluenviron) | MIT | 协议互转、按需拉流、录制、鉴权回调、Prometheus | Go 单二进制 | +| L1 RTSP 库 | **gortsplib** | MIT | 自研 worker 拉流 + 帧级丢弃 | 同作者 | +| L1 ONVIF | **IOTechSystems/onvif** 或 use-go/onvif | Apache-2.0 | 设备控制 | 前者是 EdgeX device-onvif-camera 内部使用的增强版 | +| L1 API 客户端 | 自行用 **oapi-codegen** 从 mediamtx OpenAPI 生成 | — | 控制 mediamtx | ⚠️ 不依赖第三方 SDK 的更新节奏 | +| L1 边缘推流 | mediamtx 或 **go2rtc** | MIT | 边缘盒子 | 杂牌摄像头兼容性差时用 go2rtc 兜底 | +| L1 隧道 | **WireGuard** | GPL-2.0(内核态,不链接) | 控制面通道 | | +| L2 流水线 | **Savant** (insight-platform) | Apache-2.0 | 多路视频分析框架 | 基于 NVIDIA DeepStream;备选见 §6.2 | +| L3 检测 | **YOLO**(Ultralytics v8/v11) | AGPL-3.0 → **购 Enterprise License** | 目标检测(人、车、电动车、堆物多类) | 已决策沿用;授权采购见 §2.4 | +| L3 姿态 | **YOLO-pose**(COCO 17 点) | 同上 | 关键点 | 备选 RTMPose/RTMO(Apache-2.0,同为 COCO 17 点,可直接替换) | +| L3 跟踪 | ByteTrack / BoT-SORT | MIT | 多目标跟踪 | | +| L3 ReID | **ReID 模型(匿名同一性)** | 视模型,选 Apache/MIT 系 | 跨镜同一性,**默认手段** | 产出 `anon_id`,不涉及身份 | +| L3 人脸 | 人脸检测 + 特征提取(InsightFace 系等) | ⚠️ 权重许可需逐个核对 | 身份比对,**按租户授权开启** | 见 §2.8.3 | +| L3 向量检索 | pgvector 或 Milvus | 开源 | 底库比对 | 底库量小用 pgvector 即可 | +| L4 全部 | **自研 Go** | — | 规则、事件、预警、投递 | 业务定制度高,通用工具都要大改 | +| L4 投递 | Novu / Apprise / ntfy 或直连云厂商 | — | 只解决"怎么发" | 短信/语音**双供应商** | +| L0 数据库 | PostgreSQL | — | 唯一真相源 | | +| L0 存储 | MinIO / S3 兼容 | — | 证据文件 | | +| L0 可观测 | Prometheus + Grafana + Jaeger | — | 指标/看板/链路 | mediamtx 与 Savant 都出 Prometheus 指标,一个 Grafana 全收 | +| L0 运维告警 | Alertmanager | — | **仅运维**,不碰业务预警 | | + +### 明确不用的东西 + +| 组件 | 不用的理由 | +| --- | --- | +| **Grafana OnCall (OSS)** | 已于 2026-03-24 归档,仓库只读仅修 CVSS 7.0+;**移动推送、短信、电话在归档当日停止工作**(依赖 Grafana Cloud 中转)。送不出去的告警系统等于没有 | +| **完整 MiBeeNvr** | 可作实验室兼容性测试台与选择性源码参考,**不整仓进入生产**。它已支持 RTMP/SRT push-in、远程访问等跨网络能力,但其单体媒体、录像、SQLite、设备管理和 UI 与本项目的 MediaMTX/Sense/Bell 边界重叠;未见公开的 128 路多分片验收数据,也没有本项目需要的多租户真相源与配额模型 | +| **EdgeX Foundry(本期)** | 白送的能力约值一两周开发量,代价是长期多背 4 个服务;且它假设设备在同一局域网,与 NAT 场景冲突。接入雷达/门磁/Modbus 设备时再评估切换(切换成本低:ONVIF 部分两边几乎一样,对账器不用改) | + +## 1.4 NVR 开源项目选型结论(已定,2026-08-03) + +本项目不 fork 一个完整 NVR 作为生产系统。当前默认由客户已有 NVR 承担常态录像,YoVision 负责统一接入、AI 分析和事件证据,因此选型拆成三类:**MediaMTX 是生产媒体数据面,MiBeeNvr 是 Sense 的主源码参考,Frigate 是事件录像与回看交互参考**。 + +| 项目 | 许可 / 技术栈 | 与本项目的匹配 | 决策 | +| --- | --- | --- | --- | +| [MediaMTX](https://github.com/bluenviron/mediamtx) | MIT / Go | 协议路由、按需拉流、fMP4/MPEG-TS 录像、按时间回放、HTTP/JWT 鉴权、Control API、Prometheus;官方提供横向扩展示例 | **直接部署为生产依赖,不复制源码**;Sense 通过自行生成的 API 客户端驱动 | +| [MiBeeNvr](https://github.com/Mi-Bee-Studio/MiBeeNvr) | MIT / Go + Svelte | ONVIF、设备发现、按序列号重新定位 IP、健康监控、退避重连、REST API 与现代 UI 最接近 Sense;但完整系统与 MediaMTX/Bell 重叠,内部以单机 NVR 为中心 | **首选源码参考,按白名单选择性重写/移植,不整仓 fork,不加入 go.mod** | +| [Frigate](https://github.com/blakeblackshear/frigate) | MIT / Python + FFmpeg + go2rtc | 主/子码流角色、低成本运动过滤、pre/post-capture、事件保留策略和 Review/History 时间轴成熟;自带推理进程会与 Brain/Savant 重复 | **只借鉴事件录像语义和 UI 工作流,不引入其推理与进程架构** | +| [go2rtc](https://github.com/AlexxIT/go2rtc) | MIT / Go | 特殊流格式、WebRTC/MSE、双向语音与 RTSP restream 兼容性强 | **仅作为边缘兼容插件**;不成为中心真相源,管理页面默认不暴露 | +| [Moonfire NVR](https://github.com/scottlamb/moonfire-nvr) | GPL-3.0-or-later / Rust | 不解码、不转码的低开销录像索引和任意时间段 MP4 导出值得研究;仍为 pre-1.0 | **只看设计,不复制代码**,避免 GPL 传染和存储格式兼容风险 | +| [ZoneMinder](https://github.com/ZoneMinder/zoneminder) | GPL-2.0 / C++、Perl、PHP | 传统集成式 VMS,功能完整但技术栈、部署模型和系统边界与 Sense/Brain/Bell 差异大 | **不作为实现基线** | +| [Shinobi](https://gitlab.com/Shinobi-Systems/Shinobi) | 许可证按采用版本单独核验 / Node.js | 同属完整 VMS,主仓已迁至 GitLab;引入后仍需拆除其媒体、用户和事件体系 | **不作为实现基线** | + +核验资料:MediaMTX 官方[录像](https://mediamtx.org/docs/features/record)、[回放](https://mediamtx.org/docs/features/playback)、[横向扩展](https://mediamtx.org/docs/features/scalability)与 [Control API](https://mediamtx.org/docs/features/control-api);Frigate 官方[视频流水线](https://docs.frigate.video/frigate/video_pipeline/)与[录像保留策略](https://docs.frigate.video/configuration/record/)。MiBeeNvr 源码分析基线为本地 `_reference/mibeenvr` 的提交 `b8e7dd426d73d3b1910254c196d0937db9517a0f`(2026-08-01);实施前须重新核对上游版本、许可证与安全公告。 + +### MiBeeNvr 是否包含 MediaMTX 的功能 + +**部分包含,但不能等价替代。** MiBeeNvr 覆盖了单机 NVR 常用的摄像头接入、直播和录像功能;MediaMTX 的核心价值则是作为通用媒体路由器,为多个发布者/读者提供稳定 path、细粒度鉴权、会话控制和横向扩展。两者的功能交集很大,架构角色不同。 + +| 能力 | MiBeeNvr(当前分析基线) | MediaMTX | 对 YoVision 的影响 | +| --- | --- | --- | --- | +| ONVIF、设备发现、PTZ、序列号/IP 自愈 | **有,且更接近设备管理产品** | 不负责设备实体 | 借鉴 MiBeeNvr,落入 Sense | +| HLS/LL-HLS、WebRTC、浏览器直播 | 有,另含 HTTP-FLV/WebSocket | 有 HLS/WebRTC,侧重协议路由 | 初期界面演示可直接用 MiBeeNvr | +| RTMP/SRT 接入与 RTMP/RTSP 转推 | 有 | 有,且可在多种协议间发布、读取和代理 | 都能覆盖常见接入,但不能仅按协议名称判断可替换 | +| 通用 RTSP path 供 Brain、播放器、录像器共同读取 | 当前只确认摄像头 RTSP 输入与向外部目标转推,**未发现等价的通用 RTSP 读服务** | 核心能力:publish/read/proxy,多读者共享 | 含 Brain 的端到端版本应从 M1 引入 MediaMTX,否则 Brain 需要重复直连摄像头或再补一个媒体服务器 | +| 分段录像、保留、列表、时间轴与回放 | 有,MP4/SQLite/文件系统一体化 | 有 fMP4/MPEG-TS 录像与按时间回放,业务索引由上层负责 | 演示可用 MiBeeNvr;生产事件证据仍按 Sense/Bell 边界实现 | +| 鉴权 | 本地 BasicAuth、会话 token、API key | internal/HTTP/JWT,可按 path 与 publish/read/playback/api 等 action 控制 | MiBeeNvr 鉴权不能替代多租户 RBAC 与媒体动作授权 | +| 管理 API | 面向摄像头、录像、设置和 UI 的 NVR REST API | 面向 path、配置、发布者/读者和协议会话的 Control API,可查询/踢会话 | M1 对账器依赖 MediaMTX 的控制语义 | +| 横向分片与故障域 | 未见公开的多实例编排和 128 路分片验收 | 官方提供 origin/read-replica 与负载均衡方案 | 64/128 路不能以单体 MiBeeNvr 为扩展基线 | + +以上“未发现”是基于指定提交的源码与文档审查,不代表上游永久不支持;升级前必须重新核验。 + +### 前期能否只用 MiBeeNvr + +| 阶段/用途 | 是否允许只用 MiBeeNvr | 边界 | +| --- | --- | --- | +| **M0 实验室兼容性与 UI 演示**(3–5 款摄像头) | **可以,且优先直接运行而不是复制源码** | 只接自购测试设备;允许随 M0 结束丢弃,不形成生产数据 | +| 一次性内部 Alpha(最多 16 路) | 可以,但必须标记为 **disposable prototype** | 单站点、单用户、无生产 SLA;不承诺原地升级到 128 路 | +| **M1 首个可持续演进的生产骨架** | **不可以** | MediaMTX 必须进入;Sense 建立自有设备 ID、台账、API 客户端与对账器,Brain 以后只依赖稳定媒体地址 | +| M2/M3 默认 16 路真实试点 | 不可以 | MiBeeNvr 只保留为实验室测试台/源码参考;真实租户、配额、审计与事件契约不得落入其单机模型 | +| M4/M5 64/128 路 | 不可以 | 必须使用媒体/推理分片和故障隔离 | + +> **决策规则**:如果“初版”指可丢弃的验证版,可以只用 MiBeeNvr;如果“初版”指以后原地升级的第一个正式版本,MediaMTX 必须从 M1 进入。若确需修改 MiBeeNvr,应保留为独立 fork/目录及上游 Git 历史,不把整仓源码粘贴进 Sense。 + +### MiBeeNvr 验证版安全红线 + +当前分析提交在 `internal/api/handler.go` 的 `registerAnonymousRoutes` 中将录像 `download`、`merged` 回放端点注册为匿名且不受该文件中的公共限流组保护。这对隔离实验室演示可以接受,**对居家老人、学校和工厂真实视频不可接受**。 + +验证版必须满足: + +1. 仅监听隔离测试网或 localhost;若需远程访问,必须置于带认证的反向代理/VPN 后。 +2. 不接入真实住户、学生、员工视频;不得用匿名录像 URL 作为生产分享机制。 +3. 关闭不需要的 WebDAV、FTP、浏览器 AI、自动更新和公网管理端口,减少攻击面。 +4. 若进入任何真实试点,先把录像下载/回放纳入 Bell 鉴权与审计,或直接切换到 M1 的 MediaMTX + Sense 生产骨架。 + +### MiBeeNvr 借鉴白名单 + +| 可借鉴内容 | 落入本项目 | 处理方式 | +| --- | --- | --- | +| `internal/onvif/` 的发现、WS-Security、Profile 选择、设备时间处理 | `Sense/internal/onvif/` | 按本项目接口重写;只保留必要协议能力和测试向量 | +| `internal/camera/rediscovery*` 的序列号识别与 IP 自愈 | `Sense/internal/device/`、`probe/` | 身份仍以序列号为准;适配 Postgres 设备台账和对账状态机 | +| `internal/health/`、退避与冻结检测 | `Sense/internal/probe/` | 统一成指标和状态事件,不直接驱动录像/告警业务 | +| 摄像头表单、直播卡片、录像列表的交互模式 | `Bell/web/` | 只借鉴交互;按多租户 RBAC、分页和 128 路虚拟列表重做 | +| ONVIF、录像异常与存储故障测试用例的组织方式 | Sense 测试目录 | 复用测试思想和公开协议样例;不得携带真实设备密码/视频 | + +若逐行复制 MIT 源码,必须保留原版权和许可证声明,并在第三方清单记录文件、提交号和修改范围。优先采用“读懂后按 Sense 接口重写”,避免把 MiBeeNvr 的 `config/model/recorder/storage/event` 耦合链一起带入。 + +### 明确禁止复制的部分 + +- MiBeeNvr 的 `StreamHub`、HLS/WebRTC/RTMP/SRT 服务和录像主链:与 MediaMTX 重复。 +- MiBeeNvr 的 SQLite 真相源、单机 `CameraManager` 生命周期:与 Postgres、多租户、16→128 路分片冲突。 +- MiBeeNvr 的浏览器 AI 和 Frigate 的检测进程:AI 统一归 Brain/Savant。 +- 任一 NVR 的 BasicAuth/本地用户模型:身份、租户、RBAC 与审计统一归 Bell。 +- Frigate/Home Assistant/MQTT 的业务对象模型:只吸收事件录像与时间轴语义,不进入平台核心契约。 + +### 进入生产的验收门槛 + +1. 依赖固定版本与校验值,生成 SBOM 和第三方许可证清单;上游升级必须先过回归测试。 +2. M0 完成 3–5 款摄像头的 ONVIF/RTSP 兼容测试;M3 建立 16 路基线,M4/M5 分别完成 64/128 路分片验收。 +3. 分别测接入/录像带宽、同时解码、AI 推理和证据存储,不以“NVR 能录多少路”替代 AI 容量结论。 +4. MediaMTX、go2rtc 的管理 API/UI 默认只监听内网或 localhost,经 Bell/Sense 授权代理后才可访问。 + +--- + +# 2. 通用能力底座 + +## 2.1 接入层:控制面/数据面分离 + +这是解决 NAT 的关键,也是三个场景可以用同一套接入的原因。 + +```mermaid +flowchart LR + subgraph SITE["站点(家庭/学校/小区)"] + CAM["IP 摄像头"] + SENSOR["传感器
雷达/门磁/按钮"] + EDGE["边缘节点
推流 + 本地缓存 + WG"] + CAM --> EDGE + SENSOR --> EDGE + end + subgraph CENTER["中心平台"] + MTX["mediamtx 集群
(分片)"] + CTRL["控制面 Go 服务
台账 · 对账 · 鉴权回调"] + PIPE["推理流水线"] + BIZ["业务编排"] + DB[("PostgreSQL")] + OBJ[("对象存储")] + end + EDGE -->|"数据面:主动推流
RTSP announce / SRT"| MTX + CTRL -.->|"控制面:WireGuard 隧道
ONVIF 配置与探活"| EDGE + MTX -->|鉴权回调| CTRL + MTX --> PIPE --> BIZ + CTRL --> DB + BIZ --> DB + BIZ --> OBJ + BIZ --> APP["App / 短信 / 语音 / Webhook / 大屏"] +``` + +| 平面 | 方向 | 通道 | 特点 | +| --- | --- | --- | --- | +| 控制面 | 中心 → 设备 | WireGuard 隧道 | 流量极小,仅配置与探活时产生 | +| 数据面 | 边缘 → 中心 | 主动推流 | 不走隧道,避免隧道成为带宽瓶颈 | + +**控制面失联不影响视频推流**——这条解耦让隧道故障从 P0 降为 P2。 + +### IP 规划 + +每站点分配不冲突的内网段,设备地址确定化: + +``` +10.<站点高位>.<站点低位>.0/24 +摄像头固定从 .10 起分配,传感器从 .100 起 +``` + +设备地址可预测,不依赖自动发现,便于对账。**校园/社区场景若已有既定网段,在 `sites.subnet` 里登记实际值即可,逻辑不变。** + +### mediamtx 配置基线 + +```yaml +api: yes +apiAddress: :9997 +metrics: yes +metricsAddress: :9998 + +authMethod: http +authHTTPAddress: http://control-plane:8080/mtx-auth + +pathDefaults: + sourceOnDemand: yes # 有 reader 才拉流 + sourceOnDemandStartTimeout: 10s + sourceOnDemandCloseAfter: 30s + rtspTransport: tcp # 家庭/无线网络下 UDP 丢包严重 + record: no # 按场景包覆盖 + recordDeleteAfter: 7d + runOnOnline: curl -s -X POST http://control-plane:8080/hooks/online?path=$MTX_PATH + runOnOffline: curl -s -X POST http://control-plane:8080/hooks/offline?path=$MTX_PATH + +paths: + "~^t(\\d+)-s(\\d+)-d(\\d+)$": # tenant-site-device,一条正则兜底 + source: publisher +``` + +> 用一条正则 path 兜底,**不要写 N 条静态配置**。 +> +> ⚠️ v1.19.x 的钩子命名为 `runOnAvailable` / `runOnUnavailable` / `runOnOnline` / `runOnOffline`,与旧版 `runOnReady` / `runOnNotReady` 不同。部署前核对所用版本的配置参考。 + +### 必须遵守的接入约定(血泪清单) + +| 约定 | 原因 | +| --- | --- | +| 设备身份用序列号,不用 IP | DHCP / Wi-Fi 漫游会换 IP | +| 建 path 前先查是否存在 | 盲目 add 会踢掉正在推流的 publisher | +| 改配置前比对哈希,没变绝不调 API | 任何 path 配置变更都会触发 reload,断开正在推流的 source,**即使改动与它无关** | +| 重试用指数退避 | 无事务,只能靠幂等重试 | +| 并发限流(每系统信号量 8) | 避免打爆 API;每系统独立预算,不共享 | +| 设备状态含 `pending_activation` | 批量开通必然有凭据待补的中间态 | +| API 改的配置不落盘 | mediamtx 重启后 API 添加的 path 全部消失,服务启动时必须从 DB replay | +| 探活用独立 ONVIF 调用 | 「设备离线」与「无人订阅」在 mediamtx 侧表现相同 | +| 开通时 `SetSystemDateAndTime`,定期校时 | 时钟漂移会让事件时间戳全乱,审计失效 | + +### 停用的四种粒度(副作用从轻到重) + +| 需求 | 做法 | 效果 | +| --- | --- | --- | +| 暂停分析,流保留 | **卸载推理 source** | 配合 sourceOnDemand 自动停止拉流,随时恢复 | +| 立即断开,允许重连 | `POST /v3/rtspsessions/kick/{id}` | 断一次,客户端会重连 | +| 永久拒绝 | 鉴权回调返回 401 + kick | 推不上来,无需改 mediamtx 配置 | +| 彻底移除 | `DELETE /v3/config/paths/delete/{name}` | path 消失,重启后靠自有库恢复 | + +标准组合:**先 kick 断开、再让鉴权拒绝** = 立刻生效且不会重连。 + +> ⚠️ **关键耦合**:`sourceOnDemand: yes` 的语义是「有 reader 才拉流」,而**推理 source 挂上去就是一个 reader**。因此「暂停分析」必须通过**卸载 source** 实现,不能只在推理插件里 `return`——后者会让 mediamtx 持续拉流、带宽白烧,而监控上看起来一切正常。 + +## 2.2 对账器:多系统一致性 + +数据库是唯一真相源,mediamtx / 推理流水线 / 存储都是被驱动的从属状态。 + +**水平触发(level-triggered)而非边沿触发**:对账器不处理"事件",只不断比对期望状态与实际状态并向期望收敛。这是处理部分失败的唯一可行方案。 + +```go +func (r *Reconciler) Sync(ctx context.Context) error { + desired := r.db.EnabledBindings() // 真相源 + if len(desired) == 0 && r.lastDesiredCount > 0 { + return ErrSuspiciousEmptyDesired // 安全闸:期望集合意外为空 + } + + mtxActual, err1 := r.mtx.ListPaths(ctx) // 分片内所有实例 + infActual, err2 := r.inference.ListSources(ctx) + if err1 != nil || err2 != nil { + return errors.Join(err1, err2) // 拿不到实际状态就不动作 + } + + plan := buildPlan(desired, mtxActual, infActual) + // plan 内部已按顺序约束排序: + // setup: mtx 先 → inference 后 (流必须先存在) + // teardown: inference 先 → mtx 后 (先停消费再断流) + // ⚠️ 建立与拆除是逆序的,很容易被后来人改错 + + for _, step := range plan { + if !step.DueForRetry(now) { continue } // 退避窗口未到 + if step.DesiredHash == step.AppliedHash { continue } // 没变,绝不调 API + + sem[step.System].Acquire(ctx, 1) // 每系统独立限流 + go func() { + defer sem[step.System].Release(1) + if err := step.Apply(ctx); err != nil { + r.db.MarkFailed(step, err) // 退避,不回滚 + return + } + r.db.MarkApplied(step) // applied_hash = desired_hash + }() + } + return nil +} +``` + +### 部分失败:不回滚,只收敛 + +典型场景:mediamtx path 建好了,推理 source 挂载失败。 + +- ❌ **错误做法**:回滚删掉 path。回滚本身也会失败,那时你需要回滚的回滚,无限递归。 +- ✅ **正确做法**:保留已成功的部分,把失败那行 `sync_state` 标为未收敛 + 退避重试。 + +代价是会有一个无人消费的 path 短暂存在——但因为 `sourceOnDemand`,没有 reader 就不拉流,**成本接近零**。这是 sourceOnDemand 给多系统对账带来的额外好处:**中间态是安全的**。 + +### 重试预算与安全闸 + +| 参数 | 值 | +| --- | --- | +| 初始退避 | 5s | +| 退避倍率 | 2×,上限 5min | +| 告警阈值 | `attempt > 5` 且持续 > 15min → 告警 | +| 放弃阈值 | **不放弃**。持续重试,靠告警引入人工 | +| 循环周期 | 正常 30s;有未收敛项降到 5s;每 10min 全量对账(含孤儿扫描) | +| 孤儿清理安全闸 | 单轮删除量 > 总量 10% 时**停止并告警**,不执行 | + +> 不设放弃阈值是故意的:对账器放弃了,设备就静默地不工作了,这在监护场景是不可接受的失败模式。 +> +> 安全闸防的是数据库连接异常或查询 bug 导致的"期望集合意外为空"——那会把全部路数拆掉。 + +### 孤儿资源三类 + +| 类型 | 成因 | 处理 | +| --- | --- | --- | +| mediamtx 有 path,DB 无 | 人工手改、旧版本残留 | 删除(先记日志,不静默) | +| 推理有 source,DB 无 | 同上 | 卸载 | +| DB 有 binding,两边都无且长期未收敛 | 开通失败后无人处理 | 告警,**不自动删 DB** | + +## 2.3 流水线层:故障隔离与触发式推理 + +### 为什么用框架而非完全自研 + +开源社区没有成品的「批量摄像头行为检测」(算法是差异化,不会有人开源),但有成熟的**多路视频分析流水线框架**。**算法是自己的,工程化用现成的。** + +选定 **Savant**,三个决定性理由: + +1. **故障隔离** —— 原生 DeepStream 中数据流与 pipeline 紧耦合,任一源崩溃则整个 pipeline 崩溃,重启加载模型要数秒且期间丢数据。Savant 的 adapter 通过 ZeroMQ 与框架通信,**adapter 挂掉不影响 pipeline**。家庭网络掉线是常态,这是规模化的生死线。 +2. **可配置降级** —— 实时模式(算力不足时主动丢帧)或高吞吐模式(保证处理全部数据)。**本系统选实时模式,丢帧优于积压崩溃。** +3. **Replay(按需与非线性处理)** —— REST 控制的录制/回放/转推服务,直接对应触发式二级确认与 pre-roll 回捞。 + +同一套代码可跑在 Jetson 和数据中心 GPU 上,与边缘/中心混合部署契合。 + +**代价**:必须 NVIDIA 硬件,与 DeepStream 版本强绑定,学习曲线陡,无企业支持。 + +### Pipeline 架构 + +``` +边缘推流 → mediamtx(汇聚,按需拉流) + ↓ RTSP + RTSP source adapter(多路复用 + 故障隔离) + ↓ ZeroMQ / Protobuf + Buffer Adapter(磁盘持久化缓冲,抗突发) ← 必须开启 + ↓ + 推理 module + ├─ 检测 + 跟踪(YOLO + ByteTrack) + ├─ 姿态(YOLO-pose,COCO 17 点) + ├─ 属性 / ReID + ├─ 人脸旁路(授权租户,异步不阻塞) + └─ Python 插件:时序窗口 + 行为判定(**自研部分**) + ↓ ZeroMQ / Protobuf + Go 业务编排(规则引擎 → 事件 → 预警) +``` + +**需要自己写的只有中间那个 Python 插件,前后两段是现成的。** + +### 关键配置决策 + +| 决策 | 说明 | +| --- | --- | +| **必须开启 Buffer Adapter** | adapter 与 module 之间的持久化事务性磁盘缓冲,抗流量突发,条目数与大小导出到 Prometheus。本场景天然有突发(夜里安静、早上集中活动、上下学时段)。**一开始就加上,别等出问题再补** | +| **AO-RTSP Sink 仅用于调试** | ⚠️ GeForce 系列显卡**同时硬件编码流数上限为 3**。给多路挂可视化回推会直接卡在 3 路,需 Quadro/Tesla/L4 类专业卡。**不要做成常态功能** | +| **开发期开 Development Server** | 改代码动态重载不重启 pipeline,可从 IDE 远程开发跑在 Jetson/服务器上的代码。原生 DeepStream 改一行要重启等几十秒 | +| **实时模式** | 算力不足时主动丢帧,不积压 | + +### 与 Go 控制面的对接契约 + +| 需求 | 通道 | 语言无关 | +| --- | --- | --- | +| 接收检测结果 | 订阅 sink 的 ZeroMQ 端点,解 protobuf | ✅ | +| 动态增删流 | 动态挂载/卸载 source 与 sink,无需重载 pipeline | ✅ | +| 回放/回捞(pre-roll) | Replay 服务的 REST 接口 | ✅ | +| 程序化灌数据/调试 | Client SDK(**仅 Python**) | ❌ | + +> **设计约束:优先走 ZeroMQ 和 REST 两条语言无关通道,Go 服务不得依赖 Python 中间层。** Client SDK 仅用于算法调试和测试。 +> +> 因为两边都是 API 驱动,**对账器可以同时驱动 mediamtx 和推理流水线**:开通 = 建 path + 挂 source;停用 = kick + 卸 source。逻辑写在同一个 reconciler 里。 + +### 触发式推理(成本的数量级优化) + +常态全量推理不可持续。三种触发源,按场景选: + +```mermaid +flowchart LR + T1["运动侦测
(软件,零成本)"] --> GATE{"触发门"} + T2["ONVIF 事件订阅
(摄像头侧)"] --> GATE + T3["传感器
雷达/门磁/按钮"] --> GATE + GATE -->|唤醒| INF["推理流水线
+ Replay 回捞前 N 秒"] + GATE -->|静默| IDLE["不解码 不推理"] +``` + +| 触发源 | 成本 | 可靠性 | 推荐场景 | +| --- | --- | --- | --- | +| 运动侦测(参考 Frigate 思路) | 零 | 中(光照/树叶会误触) | 通用兜底、无雷达时的过渡方案 | +| ONVIF 事件订阅 | 零 | 中 | 支持的型号,节省中心解码 | +| 毫米波雷达 | 硬件成本 | 高 | 家庭卧室/卫生间(**同时解决隐私**) | +| 常态全量 | 高 | 最高 | 仅高价值点位(校园危险区域、社区出入口) | + +> **重要澄清**:Savant 这类框架主要解决多路调度、复用与故障隔离,不会自动降低单帧推理成本。触发式推理可以显著降低平均占空比,但实际容量必须按模型、分辨率、FPS、batch、解码方式和硬件实测。 + +### 架构预留:触发入口独立 + +```go +// 所有触发源汇入同一个入口,接新传感器时不改动流控制逻辑 +type TriggerSource interface { + Name() string + Subscribe(ctx context.Context) (<-chan TriggerEvent, error) +} + +type TriggerEvent struct { + DeviceID int64 + SiteID int64 + Kind string // motion | onvif_event | radar | door | button + At time.Time + PreRoll time.Duration // 需要回捞多久 + Payload json.RawMessage +} +``` + +## 2.4 能力层:检测什么 + +### 能力清单与场景映射 + +| 能力 | 模型/方法 | 输出 | S1 | S2 | S3 | +| --- | --- | --- | :-: | :-: | :-: | +| 人体检测 + 跟踪 | **YOLO** + ByteTrack | bbox, track_id | ✅ | ✅ | ✅ | +| 姿态关键点 | **YOLO-pose** (COCO 17) | 17 点坐标 + 置信度 | ✅ | ✅ | ✅ | +| 跌倒/倒地 | 姿态 + 时序窗口 | 7 分类动作 | ✅ | ✅ | ✅ | +| 区域入侵 / 越线 | 几何判定(Zone) | 进入/离开/越线方向 | ○ | ✅ | ✅ | +| 停留 / 徘徊 | track 时长统计 | 停留秒数 | ✅ | ✅ | ✅ | +| 静止 / 无活动 | 位移统计 | 无位移时长 | ✅ | ○ | ✅ | +| 人群聚集 / 密度 | 检测框计数 + 空间聚类 | 人数、密度 | — | ✅ | ○ | +| 剧烈动作 / 打架 | 姿态时序 + 光流 | 分类置信度 | — | ✅ | ○ | +| 攀爬 / 翻越 | 姿态 + 高度变化 + Zone | 分类 | — | ✅ | ✅ | +| 非人目标 | 检测模型多类 | e_bike / obstacle | — | ○ | ✅ | +| **ReID 跨镜(匿名)** | ReID 模型 | `anon_id` | ○ | ✅ | ✅ | +| 身份比对 | 人脸底库(§2.8.3) | `identity{ref_id, group, score}` | 🔐 | 🔐 | 🔐 | + +✅ 核心 ○ 可选 — 不需要 🔐 需租户授权后启用 + +> **能力选择原则**:「同一性」问题走 **ReID**,「是谁」问题才走**人脸**。徘徊、尾随、跟随、跨镜追踪一律用 ReID 实现;人脸只用于白名单放行、关注名单、访客核验这类必须知道身份的场景。 + +### 算法分层(模型只输出观测,不做主观判定) + +| 层 | 内容 | 位置 | +| --- | --- | --- | +| 姿态/检测提取 | YOLO / YOLO-pose | 推理单元(TensorRT) | +| 单帧启发式 | 包围框宽高比、重心高度、躯干角度 | Python 插件 | +| 时序判定 | 30 帧滑窗,静态位置 + 动态速度特征 | Python 插件 | +| **规则判定** | **主体过滤 × 时空 × 条件组合** | **Go 规则引擎(不在插件里)** | +| 事件后处理 | 去重、防抖、多源融合 | Go 业务编排 | + +> **这条边界是《02》C4 的落地**:插件输出「有个人、姿态是躺倒、在多边形 A 内停留 12 秒」这类**客观观测**;规则引擎决定「这是否需要报警」。阈值、时段、联系人**一律不进插件**。 + +### 参考实现与经验 + +| 项目 | 借鉴点 | 注意 | +| --- | --- | --- | +| `GajuuzZ/Human-Falling-Detect-Tracks` | ST-GCN 每 30 帧预测动作,**7 分类**(站立/行走/坐下/躺下/起身/坐下动作/跌倒) | **七分类而非二分类是降误报的关键** | +| `kelsonbatista/fall-detection-ai-project` | 逐帧抽 128 维特征(静态位置 + 动态速度),30 帧窗口喂 LSTM | ⚠️ GPL-3.0,只读思路不抄代码 | +| `Y-B-Class-Projects/Human-Fall-Detection` | **负面经验**:约 500 张网络图片训 LSTM 完全学不会,最终退回 if-else 规则 | **小数据量下时序模型学不动,先解决数据再上模型** | +| Frigate | 运动侦测过滤后才跑推理 | 无雷达时的触发式过渡方案 | + +### 模型许可(已决策:继续用 YOLO) + +**主路径:使用 Ultralytics YOLO + 购买 Enterprise License。** + +| # | 必办事项 | 时限 | +| --- | --- | --- | +| 1 | 采购 Ultralytics Enterprise License | **M3 前完成** | +| 2 | 书面确认授权覆盖:私有化交付的每个客户实例、边缘盒子内的模型文件、基于官方权重二次训练的产出 | 采购时 | +| 3 | 人脸/ReID 模型的**权重许可**单独核对(常与代码许可不一致,部分权重仅限非商业用途) | 启用前 | + +**逃生通道(授权未获批时启用)**: + +| 环节 | 替代 | 迁移代价 | +| --- | --- | --- | +| 检测 | **YOLOX**(Megvii,Apache-2.0,仍属 YOLO 家族) | 低,接口相近 | +| 姿态 | **RTMPose / RTMO**(MMPose,Apache-2.0) | **极低,关键点定义同为 COCO 17 点,判定算法不用改** | + +> 为让逃生通道随时可用,**推理封装必须与具体模型解耦**:定义统一的 `Detector` / `PoseEstimator` 接口,模型只作为可替换实现。这条约束成本很低,但决定了授权谈判失败时是"换个实现"还是"重写一遍"。 +> +> 附带好处:RTMPose 通过 MMDeploy 支持 ONNXRuntime、TensorRT、ncnn、OpenVINO、**RKNN** 多后端。家庭场景的 RK3588 盒子若跑不动 YOLO,可在边缘侧单独换成 RTMPose 而不影响中心侧。 + +## 2.5 编排层:规则引擎 + +### 规则 DSL + +```yaml +# 规则的完整形态。场景包提供模板,租户/站点/设备逐级覆盖。 +rule: + id: R-SCH-003 + code: danger_zone_intrusion + name: 危险区域非授权进入 + scene_pack: campus@1.2 + enabled: true + dry_run: false # 试运行:命中但不通知,仅记录 + + scope: # 时空:哪些设备、哪些时段 + devices: [tag:lab_area] # 支持标签选择,不写死设备 ID + zones: [danger_zone_1] # 画面区域,仅设备级可定义 + schedule: + calendar: school_term # 引用日历(学期/课表/节假日) + windows: ["!class_time"] # 非上课时段(! 为取反) + + subject: # 主体:检测什么人/物 + class: person + filters: + - attr: age_group + in: [child, teen] + - identity: # 身份类,identity 为 null 时按 unknown 处理 + not_in_group: authorized_staff + on_unavailable: treat_as_unknown # ⚠️ 身份服务不可用时不得漏报 + + condition: # 条件:客观观测量的组合 + all: + - type: zone_enter + zone: danger_zone_1 + - type: dwell + gte: 3s + - type: not_accompanied_by + role: teacher + radius_px: 200 + + suppression: # 抑制 + cooldown: 300s + aggregate_by: [site_id, zone] + respect_silence_window: true + + evidence: # 证据规格 + pre_roll: 10s + post_roll: 15s + snapshots: 3 + include_observations: true # 结构化观测序列,供后续训练 + + action: + severity: high + event_kind: danger_zone_intrusion + notify: + contact_chain: campus_security # 引用站点的升级链 + start_level: 0 +``` + +### 三级覆盖语义 + +``` +场景包默认 ──继承──> 租户 ──覆盖──> 站点 ──覆盖──> 设备 +``` + +| 字段类型 | 覆盖语义 | 理由 | +| --- | --- | --- | +| 标量(阈值、时长、severity) | 就近覆盖 | 直觉一致 | +| 列表(联系人、通道、devices) | **整体替换,不合并** | 合并会意外扩大通知面 | +| 开关 enabled | 就近覆盖,下级可关闭上级规则 | 单点位豁免是常见需求 | +| Zone 坐标 | **只能设备级定义**,上级不可下发 | 像素坐标与具体机位绑定 | + +> 覆盖语义必须在文档和 UI 上明示(显示"此值继承自站点/租户"),否则运维会被"为什么这条没生效"折磨死。 + +### 规则生命周期 + +| 能力 | 说明 | +| --- | --- | +| 热生效 | 规则变更不重启 pipeline(一次重启影响同机全部路数) | +| 版本化 | `rule_versions` 表,记录变更人与时间,事故复盘用 | +| **试运行(dry_run)** | 命中但不通知,仅落库。新规则上线前评估误报量 | +| 质量指标 | 每条规则的命中量、误报率可查询 | +| 灰度 | 按站点比例放量 | + +> **试运行模式是被低估的需求。** 没有它,每条新规则上线都是拿真实用户做实验。 + +## 2.6 事件实例 + +### 定义 + +「命中规则的画面组成事件实例」的具体产物: + +```json +{ + "id": "evt_01J8X...", + "tenant_id": 1001, + "site_id": 20301, + "device_id": 5012, + "rule": { "id": "R-SCH-003", "version": 7, "code": "danger_zone_intrusion" }, + "kind": "danger_zone_intrusion", + "severity": "high", + "confidence": 0.87, + + "occurred_at": "2026-08-01T14:23:07.412Z", + "detected_at": "2026-08-01T14:23:08.905Z", + + "subject": { + "class": "person", + "track_id": "trk_88213", + "attributes": { "age_group": "teen" }, + "anon_id": "pid_7f3a91", + "identity": null, + "identity_status": "not_enabled" + }, + + "observation": { + "zone": "danger_zone_1", + "dwell_sec": 3.4, + "bbox_seq_uri": "s3://.../evt_01J8X/obs.jsonl", + "keypoint_seq_uri": "s3://.../evt_01J8X/pose.jsonl" + }, + + "evidence": { + "clip_uri": "s3://.../evt_01J8X/clip.mp4", + "clip_range": ["2026-08-01T14:22:57Z", "2026-08-01T14:23:22Z"], + "snapshot_uris": ["s3://.../s0.jpg", "s3://.../s1.jpg", "s3://.../s2.jpg"] + }, + + "dedup_key": "20301:danger_zone_1:danger_zone_intrusion:2026-08-01T14:20", + "aggregated_into": null, + + "outcome": "unknown", + "outcome_reason": null +} +``` + +### 三条硬约束 + +| 约束 | 理由 | +| --- | --- | +| **事件不可变** | 误判只能通过 `outcome` 标记,不能删改。合规与举证要求 | +| **`occurred_at` 与 `detected_at` 都要存** | 前者是事发时刻(决定证据回捞窗口),后者是判定时刻(决定 SLA 计算) | +| **必须含结构化观测序列** | 只有视频无法直接用于训练;`bbox_seq` 与 `keypoint_seq` 是《02》C9 数据闭环的原料 | +| **`anon_id` 与 `identity` 分开存** | 前者是 ReID 匿名标识(会话内有效),后者是人脸身份;不可互相顶替 | +| **`identity_status` 必须显式** | 取 `not_enabled` / `pending` / `matched` / `below_threshold` / `no_candidate` / `timeout`。只写 `null` 无法区分"没开这功能"和"比对失败",后者是需要排查的故障 | + +### 证据回捞(pre-roll) + +事件是"事后才知道"的——判定成立时,事发画面已经过去了。三种回捞方式: + +| 方式 | 实现 | 适用 | +| --- | --- | --- | +| 边缘环形缓冲 | 边缘节点持续录最近 N 秒到本地环 | 家庭(不上云,隐私最优) | +| 中心持续录制 | mediamtx `record: yes` + `recordDeleteAfter` | 校园/社区(允许常态录像) | +| Replay 服务回捞 | 推理框架的 Replay REST 接口 | 通用 | + +> ⚠️ pre-roll 的实际起点受 **GOP 长度**限制——只能从关键帧切。这是《01》§6.1 把"支持修改 GOP 长度"列为加分项的原因。GOP 2s 时,请求 10s pre-roll 实际可能拿到 10–12s。 + +### 去重与聚合 + +| 规则 | 参数 | 场景 | +| --- | --- | --- | +| 同设备冷却期 | 默认 5min | 同一事件多次触发 | +| 同站点聚合 | `aggregate_by` 键相同则合并 | 两个机位拍到同一次跌倒 | +| 已处置抑制 | 已 ack 期间不再新建同类 | 人已到现场 | +| 静默窗口 | 用户主动静默 | **强制限时(≤4h),到期前提醒,无"永久"选项** | + +> **永久静默是这类系统最常见的事故成因。** + +## 2.7 预警投递 + +### 状态机 + +``` +[触发] ─→ [投递中] ─→ [已送达] ─→ [已确认 ack] ─→ [处置中] ─→ [关闭] + │ │ │ │ + │ │ └─超时未 ack─→ [升级] ─→ 下一级投递 │ + │ └─投递失败─→ [升级] │ + └─防抖命中─→ [抑制] [标记误报] ←───┘ +``` + +**硬约束:告警先落库再投递。** 进程崩溃重启后扫描所有非终态告警并接续升级链——与对账器的水平触发是同一思路。 + +### 升级链配置(场景差异的主要载体) + +```yaml +contact_chain: + id: home_elderly_default + levels: + - level: 0 + timeout_sec: 30 + contacts: [{ role: family_primary, channels: [push] }] + - level: 1 + timeout_sec: 60 + contacts: [{ role: family_primary, channels: [sms, voice] }] + - level: 2 + timeout_sec: 90 + contacts: [{ role: backup, channels: [sms, voice] }] # 邻居/物业/护理员 + - level: 3 + timeout_sec: null + contacts: [{ role: call_center, channels: [voice, ticket] }] + time_policy: + - window: "08:00-22:00" + override: { level0_role: family_primary } + - window: "22:00-08:00" + override: { level0_role: cohabitant } # 夜间找同住者 +``` + +### 通道选型与冗余 + +**必须至少两条独立路径,且电话通道要能绕过互联网。** 推送依赖 APNs/FCM,短信依赖运营商,任何单一通道都可能整体故障。 + +| 层 | 选型 | 说明 | +| --- | --- | --- | +| 状态机、升级链、ack、审计 | **自研 Go** | 业务定制度高,通用工具都要大改 | +| 多通道投递 | Novu / Apprise / ntfy 或直连云厂商 | 只解决"怎么发" | +| 短信 + 语音 | 云厂商,**双供应商** | `provider` 字段就是为切换准备的 | + +### 三个不同的事实 + +| 事实 | 含义 | 字段 | +| --- | --- | --- | +| 发出去了 | 调用了上游 API | `sent_at` | +| 送达了 | 上游回执确认到达终端 | `receipt_at` | +| 被看到了 | 用户主动 ack | `alerts.acked_at` | + +> **没有回执就当失败并升级**,不做乐观假设。推送可能被系统杀掉、短信可能延迟、电话可能占线。 + +### 业务预警 vs 运维告警 + +| | 运维告警 | 业务预警 | +| --- | --- | --- | +| 方案 | Prometheus + Alertmanager | 自研状态机 + 复用投递通道 | +| 收件人 | 运维团队 | 家属 / 值班员 / 呼叫中心 | +| 延迟容忍 | 分钟级 | 秒级 | + +> **两套系统不得共用投递通道和值班配置**,避免运维噪声淡化业务预警。 + +## 2.8 同一性与身份:ReID 与人脸识别 + +### 2.8.1 两条路径的分工 + +```mermaid +flowchart LR + TRK["跟踪目标 Track"] --> Q{"需要回答什么"} + Q -->|"是不是同一个人"| REID["ReID 匿名同一性
默认开启"] + Q -->|"他是谁"| FACE["人脸比对
租户授权后开启"] + REID --> AID["anon_id
会话内有效"] + FACE --> IDT["identity{ref_id, group, score}"] + AID --> RULE["规则引擎"] + IDT --> RULE +``` + +| 能力 | 产出 | 生命周期 | 合规负担 | 典型用途 | +| --- | --- | --- | --- | --- | +| **ReID** | `anon_id` | 会话/时间窗内有效,**不跨天持久化为人员画像** | 中 | 徘徊、尾随、跟随、跨镜追踪、同一人多次出现计数 | +| **人脸** | `identity` | 持久(底库) | **高** | 白名单放行、关注名单、访客核验 | + +> **默认走 ReID。** 一条规则若想用 `identity`,需在评审中回答"为什么 anon_id 不够"。 + +### 2.8.2 ReID 实现要点 + +| 要点 | 做法 | +| --- | --- | +| 特征时效 | `anon_id` 绑定滑动时间窗(默认 30min),超窗即失效重编号,避免变相形成长期追踪 | +| 跨镜匹配范围 | 限定在同一站点内,不跨租户、不跨站点 | +| 存储 | 特征向量仅驻内存/短期缓存,**不入长期库** | +| 事件中的表现 | 只写 `anon_id` 字符串,不写特征向量 | + +> 第 1 条和第 3 条是有意的自我约束:ReID 之所以合规负担低于人脸,正是因为它不建立可长期关联到自然人的档案。若把 ReID 特征长期入库并跨天关联,它在实质上就变成了无同意的身份追踪——合规优势荡然无存。 + +### 2.8.3 人脸识别子系统 + +#### 架构:异步旁路,不在主链路上 + +```mermaid +flowchart TD + subgraph MAIN["主链路(实时,不可阻塞)"] + D["检测+跟踪"] --> P["姿态/行为"] --> R["规则引擎"] --> E["事件生成"] + end + subgraph SIDE["旁路(可延迟、可失败)"] + FD["人脸检测"] --> QC["质量筛选
角度·清晰度·遮挡·尺寸"] + QC --> FE["特征提取"] --> M["底库比对
向量检索"] + end + D -.抽取人脸 ROI.-> FD + M -.时间窗内回填.-> E + M --> LOG[("face_match_logs
只追加审计")] + GATE{"租户授权开关"} -.控制.-> SIDE +``` + +**为什么必须旁路**:把比对放进主链路,一次底库慢查询会拖垮整条 pipeline 的实时性;而跌倒、打架这类人身安全事件根本不需要知道是谁。 + +| 参数 | 值 | +| --- | --- | +| 回填时间窗 | 默认 2s,超窗事件以 `identity: null` 定稿 | +| 质量筛选 | 人脸像素 ≥ 64×64、偏航角 ≤ 30°、遮挡比 < 30%、清晰度阈值 | +| 比对失败/超时 | 不阻塞事件生成,记 `decision = no_candidate / timeout` | + +#### 授权是租户级主开关 + +```yaml +tenant_features: + tenant_id: 1001 + feature: face_recognition + enabled: true + authorized_by: "客户法务签署 · 2026-08-01" + expires_at: "2027-08-01" # 授权有到期日,到期需续签 + doc_uri: "s3://compliance/auth/1001-face.pdf" + scope: + sites: [20301] # 可进一步限定站点 + purposes: [whitelist, visitor] # 未含 watchlist 则关注名单功能不可用 +``` + +| 层级 | 语义 | +| --- | --- | +| 平台级 | 能力是否部署(私有化交付可整体不装) | +| **租户级** | 主开关。未授权租户在 **API 与 UI 层均不可见**,不是"看得到点不动" | +| 站点级 | 已授权租户内的生效范围(如只在校门口,不在教室) | +| 规则级 | 单条规则是否使用 `identity` 过滤 | + +#### 底库管理 + +| 要求 | 实现 | +| --- | --- | +| 独立 schema,独立加密密钥 | 与业务库分离,泄露面隔离 | +| **默认只存特征向量,不存原图** | 需展示原图的场景(访客核验)单独授权,原图分库存储 | +| **人员必须有有效期** | `valid_until` 非空约束;学生按学年、住户按合同、访客按当日 | +| 到期自动失效 | 定时任务将过期人员移出检索集合,不依赖人工清理 | +| 三类库分开 | `whitelist` / `watchlist` / `visitor`,阈值与处置策略各不相同 | +| 整体导出与彻底删除 | 客户终止服务时的义务,需留删除证明 | +| 每次比对与每次查询均审计 | `face_match_logs` 只追加,含操作人/设备/分数/决策 | + +#### 阈值分级(误识别后果不对称) + +| 库类型 | 假阳性后果 | 阈值 | 附加控制 | +| --- | --- | --- | --- | +| `whitelist` 白名单放行 | 陌生人被当成住户放行 → 安防失效 | 高 | — | +| `watchlist` 关注名单 | **无辜者被当成关注对象 → 对个人的实质伤害** | **最高** | **强制人工复核** | +| `visitor` 访客核验 | 核验错误 | 中 | 人工在场 | + +> **产品红线(L4 编排层硬约束)**:`watchlist` 类规则**不得自动触发对人的处置动作**(自动锁门、自动通报、自动广播),只能生成待人工确认的事件。这条写进代码,不靠流程约束。 + +#### 规则中的降级语义 + +```yaml +subject: + class: person + filters: + - identity: + library: campus_students + not_in_group: authorized_staff + min_score: 0.75 + on_unavailable: treat_as_unknown # 默认;另一取值 skip +``` + +| 取值 | 语义 | 适用 | +| --- | --- | --- | +| **`treat_as_unknown`(默认)** | 比对不可用/未命中时按"未知人员"处理,**倾向报警** | 监护与安防场景 | +| `skip` | 比对不可用时不产生事件,**倾向不报** | 仅用于低风险规则,需显式写明并有审批记录 | + +> 默认必须是 `treat_as_unknown`:人脸角度不佳、服务抖动、底库超时都会产生 null,若默认跳过,系统会在最需要报警的时候安静下来。 + +--- + +# 3. 场景包 + +## 3.1 场景包是什么 + +一个可版本化的配置集合,**不含代码**: + +``` +scene_packs/campus@1.2/ +├── manifest.yaml # 名称、版本、依赖的能力与最低平台版本 +├── rules/ # 规则模板(DSL) +│ ├── fight.yaml +│ ├── climb_fence.yaml +│ ├── danger_zone.yaml +│ └── ... +├── zones/ # 区域模板(相对坐标 + 引导文案,实施时按机位调) +├── chains/ # 升级链模板 +├── calendars/ # 日历/时段模板(学期、课表、作息) +├── copy/ # 通知话术模板(多语言) +├── dashboards/ # Grafana 看板 JSON + 报表口径 +└── onboarding/ # 实施checklist:机位建议、安装高度、俯角、光照要求 +``` + +## 3.2 居家养老包(S1) + +### 规则集 + +| 规则 | 条件 | 严重度 | 备注 | +| --- | --- | --- | --- | +| 跌倒 | 姿态 7 分类命中 fall,持续 ≥ 2s | critical | 核心 | +| 长时间静止 | 无位移 ≥ 30min(白天)/ 卫生间 ≥ 20min | high | 阈值按户可调 | +| 夜间离床未归 | 22:00–06:00 离开卧室 ≥ 20min 未返回 | high | 需雷达或门磁 | +| 陌生人入室 | 非住户人员出现 | medium | 优先 ReID;家属若要"认出保姆/子女"可授权开人脸白名单库 | +| 活动量异常 | 日活动量较基线下降 > 50% | low | 跨天统计,非实时 | + +### 场景特有约束 + +| 约束 | 实现 | +| --- | --- | +| 卧室、卫生间**禁装摄像头** | `devices.privacy_flag = true` 时系统拒绝创建 `device_type = camera`(代码级硬约束) | +| 平时不解码不上传 | 触发式推理 + `record: no` + 边缘环形缓冲 | +| 家属只能看自己家的告警关联片段 | RBAC:`family` 角色的证据访问范围 = `site_id` ∩ `alert_events` | +| 3 分钟内必有真人接手 | 4 级升级链(30s/60s/90s → 呼叫中心) | + +### 时间线 + +| 时刻 | 发生什么 | +| --- | --- | +| T+0s | 事件发生,一级触发源捕获(雷达/运动) | +| T+3s | 唤醒推理,回捞前 30s 画面 | +| T+10s | 判定成立,事件落库,首次投递(App 推送) | +| T+40s | 未 ack → 短信 + 语音电话 | +| T+100s | 未 ack → 备用联系人(邻居/物业/护理员) | +| T+190s | 未 ack → 呼叫中心人工介入 | + +### 部署形态 + +```mermaid +flowchart LR + subgraph HOME["用户家中"] + CAM["客厅 IP 摄像头"] + RADAR["毫米波雷达
卧室 / 卫生间"] + BOX["边缘盒子 RK3588
录像环缓冲 + 推流 + WG"] + CAM --> BOX + RADAR --> BOX + end + subgraph CLOUD["云端平台"] + MTX["mediamtx"] --> INF["推理"] + INF --> BIZ["业务编排"] + BIZ --> DB[("事件与审计库")] + end + BOX -->|"仅上传事件片段"| MTX + BIZ --> FAM["家属 App / 短信 / 语音"] + BIZ --> CC["呼叫中心"] +``` + +**一户一盒子,互不影响。** 断网时本地缓存,恢复后补传;断网本身触发运维告警。 + +## 3.3 校园包(S2) + +### 规则集 + +| 规则 | 条件 | 严重度 | 时段 | +| --- | --- | --- | --- | +| 打架/推搡 | 剧烈动作分类 + 人数 ≥ 2 + 持续 ≥ 3s | critical | 全天 | +| 翻越围墙 | 攀爬姿态 + 周界 Zone + 高度变化 | critical | 全天 | +| 危险区域闯入 | Zone 进入 + 停留 ≥ 3s + 非授权角色 | high | 非开放时段 | +| 尾随进校 | 门禁事件 1 次 + 检测到人数 ≥ 2 | medium | 上下学时段 | +| 上课时段徘徊 | 楼道 Zone 停留 ≥ 5min | low | 课表内 | +| 宿舍晚归 | 22:30 后进入宿舍楼 | low | 夜间 | +| 学生跌倒/晕倒 | 复用 S1 跌倒能力 | high | 全天 | +| 人群聚集 | 单 Zone 人数 ≥ N 且持续 ≥ 30s | medium | 可配 | + +### 场景特有 + +| 特点 | 方案 | +| --- | --- | +| **未成年人合规** | 人脸能力即使租户已授权,**校园场景仍需专项合规意见方可开启**(独立阻塞项);证据留存期限从严;查看权限按角色最小化;采集告示牌 | +| 危险区域的"授权人员"判定 | 优先用**角色标签 + 工牌/工服属性**;确需人脸时用 `whitelist` 库(教职工),学生不入库 | +| 尾随进校 | **ReID + 门禁事件融合**,不依赖人脸 | +| 课表/校历驱动 | `calendars/school_term.yaml` + 课表导入,规则用 `!class_time` 这类语义引用 | +| 值班室 7×24 有人 | 升级链只需 2 级(值班室 → 校领导),超时 2min/10min | +| 批量事件处理 | **需要看板与工单**(S1 不需要):列表、批量 ack、按区域筛选、当班交接 | +| 大屏联动 | Webhook 推到值班室大屏 + 声光 | +| 带宽充足 | 可中心部署,可用常态推理覆盖高价值点位 | + +### 部署形态 + +校内边缘节点(Jetson Orin NX 或 x86+GPU)承担推理,事件与元数据上中心平台;视频不出校(若客户要求)。 + +## 3.4 社区/物业包(S3) + +### 规则集 + +| 规则 | 条件 | 严重度 | 备注 | +| --- | --- | --- | --- | +| 周界入侵 | 越线(方向性)或 Zone 进入 | high | 夜间加权 | +| 电动车进电梯 | 检测 class = e_bike + 电梯 Zone | high | **非人目标** | +| 消防通道占用 | 静态变化检测 + 持续 ≥ 10min | medium | 需持续态表达 | +| 陌生人徘徊 | ReID 会话在单元门 Zone 停留 ≥ 5min | medium | **用 ReID 不用人脸** | +| 独居老人异常 | 连续 N 天未在出入口出现 | medium | 跨天统计 | +| 高空抛物 | — | — | ⚠️ 需专用仰拍机位与独立算法,**不在本包,建议独立立项** | + +### 场景特有 + +| 特点 | 方案 | +| --- | --- | +| 持续态事件(消防通道堆物) | 事件带 `state: ongoing`,整改后关闭;**不是每帧生成新事件** | +| 夜间可能无人值班 | 升级链 3 级:值班 → 主管 → 业主群/物业总值班 | +| 非人目标检测 | 检测模型需含 e_bike / obstacle 类,与人体检测共用 backbone | +| 陌生人判定 | **优先 ReID(匿名)**,避免人脸底库的合规负担 | + +## 3.5 场景包对比速查 + +| 维度 | 居家养老 | 校园 | 社区 | +| --- | --- | --- | --- | +| 单站点路数 | 1–3 | 默认 16,单站点 ≤128 | 默认 16,单站点 ≤128 | +| 部署 | 边缘盒子(必须) | 校内边缘/中心 | 混合 | +| 推理模式 | 触发式 | 混合(重点常态) | 触发式为主 | +| 常态上云录像 | ❌ 禁止 | ✅ | ✅ | +| 升级链级数 | 4 | 2 | 3 | +| 首级超时 | 30s | 2min | 1min | +| 接收端 | App(个人) | 值班大屏 + 工单 | App + 值班 | +| 同一性/身份 | ReID 为主;人脸可授权开白名单库 | ReID 为主;**人脸需专项合规意见**(未成年人) | ReID 为主;人脸可用于访客核验 | +| 误报容忍 | 极低 | 中 | 中 | +| 主要成本 | 硬件(一户一盒) | GPU | GPU + 存储 | + +--- + +# 4. 部署形态 + +## 4.1 三种形态 + +| 形态 | 推理位置 | 适用 | 优点 | 代价 | +| --- | --- | --- | --- | --- | +| **边缘为主** | 站点内 | 家庭、隐私敏感、带宽受限 | 视频不出户,带宽省 | 硬件成本×站点数,运维难 | +| **中心为主** | 中心机房 | 校园、社区(有专网) | 算力集中利用率高,好运维 | 带宽压力,视频出站点 | +| **混合** | 一级边缘 + 二级中心 | 大多数实际项目 | 成本与隐私平衡 | 架构复杂度 | + +**推荐默认:混合。** 边缘做触发与初筛(运动侦测、轻量检测),中心做重推理与二次确认。 + +## 4.2 容量与分片 + +容量规格分为 16 / 32 / 64 / 128 四档。默认交付 16 路,单站点本阶段上限 128 路;16 是默认配额,不是数据库或代码硬限制。 + +| 项 | 默认 16 路 | 128 路上限 | 设计要求 | +| --- | ---: | ---: | --- | +| 子码流 640×360 @ 512 kbps | ≈ 8 Mbps | ≈ 64 Mbps | 推理优先使用子码流 | +| 主码流按 4–6 Mbps | ≈ 64–96 Mbps | ≈ 512–768 Mbps | 录像与证据回捞预算;不默认重复保存客户全量录像 | +| 抽帧 5 fps | 80 帧/秒 | 640 帧/秒 | 解码与推理均需实测 | +| mediamtx | 1 个分片 | 建议 4 个 32 路分片起步 | 若需缩小故障域,可改为 8 个 16 路分片 | +| 推理 | 1 个 16 路逻辑分片 | 最多 8 个 16 路逻辑分片 | 多个逻辑分片可落在同一 Worker,实际比例由压测决定 | + +推荐配置: + +```yaml +capacity: + site_default_video_channels: 16 + site_max_video_channels: 128 + media_shard_max_streams: 32 + inference_profile_max_sources: 16 +``` + +`stream_bindings` 同时记录 `mtx_instance` 与 `inference_shard`。默认按 `site_id + device_id` 稳定分配;扩容时允许迁移,但同一设备任一时刻只能归属一个有效媒体分片和一个推理分片。 + +`sites.max_video_channels` 的唯一真相源在 Bell;Sense 在设备新增/启用写路径通过版本化内部 API 或只读投影执行配额校验,不跨 schema 直接写入。配额依赖暂时不可用时只拒绝新的容量变更,已有视频链路继续运行。 + +> **不要单实例硬扛 128 路**:扩容必须通过增加分片与 Worker 完成。单分片故障只允许影响该分片,不能拖垮整个站点。GPU 型号与数量不在架构文档写死,M1 用 5 路打通,M3 建立 16 路基线,M4/M5 再验证 64/128 路。 + +## 4.3 硬件选型 + +| 平台 | 用途 | 注意 | +| --- | --- | --- | +| RK3588 | 家庭边缘盒 | 6 TOPS NPU,无风扇低噪;模型需 MMDeploy 转 RKNN | +| Jetson Orin Nano/NX/AGX | 校园/社区边缘 | 与 Savant/DeepStream 原生契合,同一套代码 | +| x86 + L4 / A2 / Quadro | 中心推理 | ⚠️ **GeForce 卡同时硬件编码流数上限 3**,做可视化回推必须用专业卡 | + +--- + +# 5. 可观测与运维 + +## 5.1 一个 Grafana 全收 + +mediamtx 与 Savant 都原生导出 Prometheus 指标,**前期不用写一行前端代码**就有运营看板。 + +| 类别 | 关键指标 | +| --- | --- | +| 接入 | 设备在线率、path 数、推流断连次数、探活失败率 | +| **对账** | `reconciler_unconverged_bindings{system}`(持续 > 0 即异常)、`reconciler_orphans_detected{type}`、`reconciler_safety_brake_triggered`(**应当直接呼人**)、`reconciler_loop_duration_seconds`(超时 = 该分片了) | +| 流水线 | 每路人体检出率、推理延迟 P50/P99、丢帧率、buffer adapter 条目数与大小 | +| 业务 | 事件数(按规则/站点/类型)、**误报率(按规则)**、预警投递成功率、ack 时长 P50/P95、升级到各级的比例 | +| SLA | 事件 → 首次投递延迟、投递 → 回执延迟、全链路无响应比例 | + +> **把每路的检出率、事件候选数、推理延迟声明成自定义指标** → Grafana 上就是现成的运营看板。 + +## 5.2 追踪 + +OpenTelemetry → Jaeger,排查单帧处理延迟与端到端事件链路。 + +## 5.3 管理界面 + +生产组合(mediamtx + 自研服务)**没有现成的管理界面**,三个选择: + +1. **前期不做完整 UI** —— REST API + 脚本撑过接入验证期;即使默认只有 16 路,也不应依赖逐路手工配置。正式管理端从第一版起按 128 路设计分页、虚拟列表、筛选与批量操作。 +2. 临时用 go2rtc 的 1984 页面看流状态 —— ⚠️ **默认无密码,局域网内任何人可访问**,必须在配置里关闭或用防火墙隔离。 +3. **正式做时长在自研服务上** —— 因为要展示的是「租户 → 站点 → 设备 → 流状态」的业务视图;第三方面板展示的是「path 列表」,不是一回事。 + +> 社区面板(mediamtx-dashboard、lgcshy/mediamtx-ui、nabaco/mediamtx-ui、bcanfield/mediamtx-connect)均为独立进程通过 API 对接,可按需临时使用。mediamtx 作者明确表示精力有限、优先保证核心稳定。 + +## 5.4 管理系统与 App 的功能边界 + +| 端 | 核心功能 | +| --- | --- | +| **管理系统(Web)** | 租户/站点/设备管理、批量开通、规则配置(含试运行)、Zone 画图、升级链配置、事件看板与筛选、事件详情(视频+观测回放)、处置与工单、误报标记、统计报表、审计查询、RBAC | +| **App(家属/值班员)** | 预警接收与 ack、事件详情与视频回看、一键呼叫、误报反馈、限时静默、设备状态、(家属)多老人切换 | +| **大屏/值班台** | 实时事件流、地图/平面图点位、声光联动、当班交接 | +| **Webhook/开放 API** | 对接客户既有平台、门禁、消防、工单系统 | + +--- + +# 6. 演进路径与备选 + +## 6.1 里程碑 + +| 阶段 | 内容 | 出口标准 | +| --- | --- | --- | +| **M0** | 摄像头兼容性验证 + 需求定稿 | 可直接运行 MiBeeNvr 作为隔离实验室测试台;3–5 款型号跑通 GetProfiles/GetStreamUri/SetSystemDateAndTime,采购白名单归档;M0 代码不得作为生产基线 | +| **M1** | 接入骨架 + mediamtx | **MediaMTX 正式进入且不得由完整 MiBeeNvr 替代**;5 路自动建 path、探活、断线重建 | +| **M2** | 对账器 + 多租户 + 隧道 | 10 站点试点,至少一个站点完成 16 路开通/停用全流程,`unconverged = 0` 稳定 | +| **M3** | 推理 + 规则引擎 + 事件预警 | **默认 16 路**端到端稳定;跑通首个场景三类规则;拿到现场误报基线,数据闭环已收数据 | +| **M4** | 64 路分片 + 灰度 + 管理系统 | 64 路稳定运行;单分片故障不扩散;看板、批量操作与处置流可用 | +| **M5** | 128 路容量验收 + 第二、三场景包 + 触发式推理 + **人脸识别子系统** | **128 路横向扩展不改业务代码**;场景包为纯配置交付;人脸在一个已授权租户跑通(底库有效期、异步回填、降级、审计) | +| **M6** | 异构传感器 + 两级判定 | 误报率显著下降,视频常态采集下降 | + +### 代码归属:三个系统 + +本方案的实现拆为三个可独立开发的系统,**完整职责与目录结构见《08-三系统职责划分》**: + +| 系统 | 中文 | 层 | 语言 | 状态 | 首次交付 | +| --- | --- | --- | --- | --- | --- | +| **Sense** | 感知系统 | L1 接入 | Go | 有(设备台账) | M1 | +| **Brain** | 推理系统 | L2 流水线 + L3 算法 | Python / CUDA | **无状态** | M3 | +| **Bell** | 管理系统 | L4 业务 + L0 基座 | Go + 前端 | 有(事件、告警) | M3 → M4 | + +三者之间只有一条契约——**事件实例 JSON**(§2.6,规范本体见 `contracts/event-v0.1.schema.json`)。Sense 向 Brain 供流与信号,Brain 产出事件,Bell 消费事件。这条边界让推理侧换模型、换框架(Savant → Pipeless、YOLO → YOLOX)时,另两个系统一行不改。 + +> mediamtx 是独立二进制,不属于任何一个系统。Sense 管它的配置与生命周期。 + +#### M1 骨架(约 200 行起步,全部是生产代码,**只动 Sense**) + +``` +Sense/ + cmd/sense-api/main.go + internal/ + onvif/ 连接、GetProfiles、GetStreamUri、SetSystemDateAndTime + store/ 先 SQLite,schema 与生产 Postgres 保持一致 + mtx/ oapi-codegen 生成的客户端 + 薄封装 + reconcile/ 最小对账循环(幂等 + 退避) + probe/ 探活 goroutine +``` + +跑通之后就已经站在生产架构上了,后面是往上加(Postgres、多租户、隧道、分片),**不是推倒重来**。Brain 与 Bell 到 M3 才起步——那也是事件契约第一次被真实使用的时刻。 + +### 演示与验证阶段的使用红线 + +| 允许 | 禁止 | +| --- | --- | +| 实验室接自购测试摄像头做兼容性验证 | 接入真实住户/学校的摄像头 | +| M0 直接运行现成 NVR(如 MiBeeNvr)给干系人做效果演示 | 把它作为可持续演进的生产系统第一版上线;将匿名录像端点暴露到真实网络 | +| 按 §1.4 白名单阅读/移植 ONVIF、IP 自愈、健康检测和 UI 交互 | 复制媒体内核、SQLite 真相源、AI 或本地用户体系;作为长期依赖引入 go.mod | + +> 理由:现成 NVR 的目标通常是单机一体化交付,而本项目是多租户控制面 + 可分片媒体面 + 独立推理面。许可证宽松不等于架构适配或已通过 128 路压测;生产采用范围必须由本项目自己的兼容性、容量、安全和故障隔离测试决定。 + +## 6.2 备选与逃生通道 + +| 主选 | 备选 | 触发切换的条件 | +| --- | --- | --- | +| Savant (DeepStream) | **Pipeless**(Rust,不绑 NVIDIA,支持 ONNX Runtime/OpenVINO/TensorRT,HTTP 动态增删流) | 硬件选型变更、规模缩小、需信创环境 | +| **YOLO + Ultralytics Enterprise License** | YOLOX(检测)+ RTMPose(姿态),均 Apache-2.0 | 授权采购未获批;或边缘 NPU 跑不动 YOLO | +| 人脸识别(授权租户) | 纯 ReID + 角色标签/工牌属性 | 合规意见未通过,或客户撤回授权 | +| 自研设备管理 | EdgeX Foundry 最小服务集 | 确定接入雷达/门磁/Modbus 等异构设备时 | +| 中心推理 | 边缘推理 | 隐私要求提升或带宽成本超预期 | + +> **推理代码必须与框架解耦**——模型推理、时序判定、特征工程写成独立模块,框架只负责调度。这是切换成本能否控制的关键。 + +### EdgeX 最小服务集(若切换) + +``` +core-metadata 设备注册表 +core-command 命令下发入口 +device-onvif-camera ONVIF <-> REST +redis 消息总线 +(consul 可用 -cp=none 省掉) +``` + +砍掉 core-data、app-service-*、security 全套。⚠️ discovery 在本场景基本不可用:netscan 对大量 /24 段是数万次探测,应使用预定义 deviceList 或 REST 批量录入,但保留 `EnableStatusCheck` / `CheckStatusInterval` 做探活。另注意 EdgeX 3.x 起 API 路径是 `/api/v3`,网上大量 2.x 文档写的是 `/api/v2`。 + +--- + +# 7. 落地检查清单 + +开工前逐项确认: + +## 硬件与网络 +- [ ] 3–5 款候选摄像头完成 §2.1 必过项实测,白名单归档 +- [ ] 各场景站点的网络出口带宽、是否有公网 IP 已摸底 +- [ ] 边缘硬件选型确定(RK3588 / Jetson / x86+GPU) +- [ ] 中心 GPU 型号确认(若需可视化回推,**必须专业卡**) + +## 许可与合规 +- [ ] **Ultralytics Enterprise License 已采购**,覆盖私有化实例 / 边缘模型 / 二次训练权重 +- [ ] 人脸与 ReID 模型的**权重许可**已逐个核对(可能与代码许可不一致) +- [ ] 视频证据留存期限已由法务确认 +- [ ] 各角色查看范围的法定边界已确认 +- [ ] 被拍摄者知情同意的取得方式与留档流程已确定 + +## 人脸能力启用前(逐租户执行) +- [ ] 租户授权书已签署并录入 `tenant_features`(含用途、范围、**到期日**) +- [ ] 人脸信息的**单独同意**取得方式已确定(通常不能与一般隐私政策打包) +- [ ] 底库来源合法性由客户书面承诺 +- [ ] 底库人员的知情/查询/删除权响应流程与时限已定 +- [ ] **未成年人场景:专项合规意见已出**(S2 独立阻塞项) +- [ ] 人脸数据留存期限已单独配置(独立于视频证据) +- [ ] 终止服务时的底库彻底删除义务与证明方式已约定 + +## 架构 +- [ ] `devices` 表而非 `cameras`,含 `device_type` +- [ ] 设备身份用序列号 +- [ ] 触发入口独立成 handler +- [ ] `sync_state` 每系统一行 +- [ ] 对账器安全闸已实现并有指标 +- [ ] 隐私区域拒绝 camera 类型是**代码级**约束 +- [ ] 所有查询强制带 tenant_id,在数据访问层统一拦截 +- [ ] 推理封装与具体模型解耦(`Detector` / `PoseEstimator` 接口),逃生通道随时可用 +- [ ] 人脸比对为**异步旁路**,超时不阻塞事件生成 +- [ ] 依赖 identity 的规则默认 `on_unavailable: treat_as_unknown` +- [ ] `watchlist` 类规则**不得自动触发对人的处置动作**(代码级) +- [ ] 未授权租户在 API 与 UI 层均不可见人脸能力 +- [ ] `persons.valid_until` 非空约束 + 到期自动失效任务 +- [ ] 告警先落库再投递,重启可续跑升级链 +- [ ] 静默功能无"永久"选项 + +## 数据闭环 +- [ ] 事件证据含结构化观测序列(bbox_seq / keypoint_seq) +- [ ] 误报标记含归因分类 +- [ ] 标注队列从 M3 第一天就在收数据 +- [ ] 每条规则的命中量与误报率可查 + +--- + +> 本方案的所有技术决策均可追溯到《02-需求分析》的 C1–C9。若实施中发现某条决策不成立,应先回到《02》修改分析结论,再同步修订本文档——**不要在实现里悄悄绕过**。 diff --git a/docs/raw/04-目标客户-政府购买居家养老.md b/docs/raw/04-目标客户-政府购买居家养老.md new file mode 100644 index 0000000..128914f --- /dev/null +++ b/docs/raw/04-目标客户-政府购买居家养老.md @@ -0,0 +1,122 @@ +# 🏛️ 目标客户群体 01 — 政府购买的居家养老监护 + +> 版本 v0.2 · 2026-08-03 +> 市场定位:中国南方四线城市 · 付费方:民政局 / 卫健委(2G) +> 对应场景包:**S1 居家养老包**(《03-通用场景应用方案》§3.2) + +--- + +# 1. 一句话定位 + +**卖给民政局的"独居老人跌倒无人知"问责解决方案,政府按户付费,老人和家属零付费。** + +# 2. 客户画像 + +## 2.1 付费方与决策链 + +| 角色 | 单位 | 在决策中的位置 | +| --- | --- | --- | +| **出钱** | 民政局(养老服务科)、卫健委(老龄健康科) | 项目预算的申请与执行方 | +| 拍板 | 分管副市长 / 民政局长 | 试点项目签字 | +| 使用 | 街道 / 社区居家养老服务中心、签约照护员 | 日常接收预警、上门处置 | +| 受益 | 独居 / 留守 / 特困老人及其外地子女 | 不付费,但满意度影响项目续约 | +| 验收 | 民政局 + 第三方评估机构 | 决定运营费能否逐年续 | + +**决策链特点**:2G 项目制。入口是"进入当年智慧养老/适老化改造的试点名单",出口是"验收报告 + 老人/家属满意度"。销售周期 6–12 个月,但一旦进入名单,单子完整、账期可预期。 + +## 2.2 为什么是四线城市的高付费群体 + +| 因素 | 说明 | +| --- | --- | +| **人口结构** | 四线城市是子女外流最严重的地方,独居/留守老人比例**高于一二线**,而照护人力更稀缺 | +| **问责刚性** | 特困供养、低保独居老人跌倒/离世多日无人发现,是当地民政系统的**问责事件**。付费动机是"不出事",不是改善型消费 | +| **资金来源明确** | 居家养老信息化专项、特殊困难老年人家庭适老化改造补贴(每户有定额)、长护险试点资金、福彩公益金 | +| **竞争洼地** | 头部安防厂商的重心在一二线智慧城市大单,四线城市的养老信息化项目往往由本地集成商拼凑交付,缺认真做算法闭环的供应商 | + +## 2.3 客户的真实痛点(按客户语言) + +1. "上面要求特困独居老人**每日探访**,我们人手根本跑不过来" → 系统替代日常巡访,异常才派人 +2. "去年隔壁县出了老人去世一周才发现的事,通报了" → 无活动检测 + 升级链兜底 +3. "以前装的摄像头就是个录像机,出了事才回头查" → 主动预警 vs 事后取证的差异,是方案讲解的核心 +4. "老人嫌摄像头对着自己,子女也嫌" → **隐私设计是成交点**:卧室卫生间只装雷达、平时不上传、只推事件片段(《03》S1 包已有) + +# 3. 卖什么 + +## 3.1 产品组合 + +| 层 | 内容 | 对应能力 | +| --- | --- | --- | +| 户内硬件 | 边缘盒子(RK3588) + 客厅摄像头 1–2 路 + (二期)卧室/卫生间毫米波雷达 | 《03》§3.2 部署形态,一户一盒互不影响 | +| 平台 | 事件管理系统(街道/社区端) + 处置工单 | L4 编排 + 管理系统 | +| 终端 | 家属 App(推送+ack) + 照护员端 + 民政驾驶舱大屏 | 《03》§5.4 | +| 服务 | 7×24 预警升级链、误报持续收敛、设备探活与上门维护 | 运营费的核心内容 | + +**驾驶舱大屏是 2G 项目的必配项**——验收委员会看的就是它。辖区地图、在保老人数、今日预警数、平均响应时长,这些指标《03》§5.1 的 Prometheus 体系天然就有,前端做一层皮即可。 + +## 3.2 核心规则集(S1 包直接复用) + +| 规则 | 预警对象 | 备注 | +| --- | --- | --- | +| 跌倒 | 照护员 → 家属 → 社区 → 呼叫中心 | 核心卖点,3 分钟必有人接手 | +| 长时间无活动 | 同上 | **对"去世多日无人发现"的直接回应,2G 场景权重高于跌倒** | +| 夜间离床未归 | 照护员 | 二期,需雷达 | +| 连续 N 天无活动量 | 社区网格员 | 跨天统计,替代每日探访打卡 | + +> 注意与商业养老院场景的区别:这里的第一响应人是**社区照护员/网格员**而非家属——很多服务对象的子女在外地,升级链的 level 0 要按项目实际配置,这正是《03》§2.7 升级链按站点可配的意义。 + +## 3.3 收费模型 + +| 项 | 模式 | 说明 | +| --- | --- | --- | +| 建设费 | 一次性,按户 | 设备 + 安装 + 平台部署。对齐当地适老化改造的每户补贴定额来定价 | +| 运营费 | 按户/按年 | 预警服务 + 维护 + 呼叫中心。**这是利润和续约的关键,合同里必须与建设费分开** | +| 扩展 | 按新增户数滚动 | 试点 100–200 户 → 全区特困老人 → 普通独居老人自愿加装 | + +> ⚠️ 只做一次性建设费的智慧养老项目,交付即死亡(设备一年后离线一半,验收后无人管)。**谈判底线:没有运营费的单子不接**,否则误报反馈闭环(《02》C9)收不到数据,产品也无从迭代。 + +# 4. 怎么打 + +## 4.1 销售路径 + +``` +本地民政系统关系/合作伙伴 + → 摸清当年智慧养老、适老化改造预算盘子 + → 免费给 10–20 户特困老人试装(对齐 M2 试点) + → 用 1–2 个真实处置案例(跌倒被及时发现)做汇报材料 + → 进入试点名单,拿 100–200 户示范项目 + → 验收 + 满意度调查 → 次年滚动扩面 + 邻县复制 +``` + +## 4.2 打单要点 + +| 要点 | 做法 | +| --- | --- | +| 真实案例 > 技术参数 | 汇报材料的第一页是"X 月 X 日 X 街道张奶奶跌倒,系统 8 秒预警,照护员 6 分钟到场",不是准确率数字 | +| 隐私主动讲 | "卧室卫生间不装摄像头、平时视频不出户"写进方案首页——这是打消老人和子女抵触的关键,也是与"装监控"类竞品的差异 | +| 不承诺准确率 | 承诺响应 SLA(《01》§9.2)与误报收敛趋势,合同附实测基线报告——四线城市评审专家往往来自本地,更看重"出了事有没有人管" | +| 绑定考核指标 | 把系统指标对齐民政的考核口径(探访覆盖率、异常发现时长),让局里能拿系统数据向上汇报 | + +## 4.3 风险 + +| 风险 | 缓解 | +| --- | --- | +| 财政支付能力弱、回款慢 | 首选有长护险试点或有专项转移支付的区县;合同分期与交付里程碑绑定 | +| 项目随主管领导调动而停摆 | 运营费合同签 3 年期;尽快做出跨街道的规模,提高替换成本 | +| 本地集成商低价抢单(纯摄像头+录像) | 标书里写入"主动预警响应时长""无活动检测"等功能性指标,拉开与录像方案的差距 | +| 老人拔电源/挡摄像头 | 设备探活告警(RQ-C-06)+ 照护员上门沟通流程;安装时的知情同意与讲解话术进 onboarding 清单 | +| 误报率高导致照护员疲于奔命 | 试运行模式先收基线再放量;误报反馈闭环从第一户就开始收数据 | + +# 5. 与平台规划的对齐 + +| 项 | 对齐点 | +| --- | --- | +| 里程碑 | M2 的 10 站点试点 = 本群体的免费试装户;M3 误报基线 = 汇报材料的数据来源 | +| 场景包 | S1 包零修改可用;新增"连续 N 天无活动量"规则(跨天统计,已在 S3 包有同类) | +| 人脸识别 | **本群体不需要**。陌生人入室用 ReID 即可;不要在 2G 养老项目里主动引入人脸,徒增合规评审环节 | +| 硬件 | RK3588 边缘盒方案直接适用;二期雷达对应 M6 异构传感器 | +| 容量 | 本场景一户对应一个 1–3 路逻辑站点,100–200 户是同一租户下的多站点规模,不受“单站点上限 128 路”误导;控制面、列表和批量运维仍须按多站点分页设计 | +| 合规 | 被监护老人的知情同意(《01》§7.3)在本群体尤其重要——由社区与家属共同签署,留档进平台 | + +--- + +> 姊妹文档:《05-目标客户-寄宿制学校》《06-目标客户-工厂园区安全生产》。三个群体的共性:**付费动机都是"不花钱就要担责"(政府问责/学校口碑/法定经费),而非自愿改善型消费**——这是四线城市安防 AI 唯一撑得起付费的逻辑。 diff --git a/docs/raw/05-目标客户-寄宿制学校.md b/docs/raw/05-目标客户-寄宿制学校.md new file mode 100644 index 0000000..f2f2650 --- /dev/null +++ b/docs/raw/05-目标客户-寄宿制学校.md @@ -0,0 +1,130 @@ +# 🏫 目标客户群体 02 — 寄宿制学校 + +> 版本 v0.2 · 2026-08-03 +> 市场定位:中国南方四线城市 · 付费方:民办学校自有资金 / 公办"平安校园"财政专项(2B + 2G) +> 对应场景包:**S2 校园包**(《03-通用场景应用方案》§3.3) + +--- + +# 1. 一句话定位 + +**卖给校长的"夜间宿舍与围墙不出事"系统:民办学校保招生口碑,公办学校花掉平安校园专项——两边都是必须花的钱。** + +# 2. 客户画像 + +## 2.1 为什么是寄宿制、为什么是四线城市 + +| 因素 | 说明 | +| --- | --- | +| **寄宿比例高** | 四线城市及下辖县乡的生源进城读书,寄宿制中学比例远高于一二线;学生 24 小时在校,学校承担全时段监护责任 | +| **夜间是事故高发段** | 晚归、翻墙外出(网吧)、宿舍打架、天台/危险区域——全部发生在值班力量最薄弱的时段 | +| **问责与口碑双重刚性** | 公办:出事校长负责,安全事故一票否决;民办:一次恶性事件直接摧毁下一年招生。**付费动机是保牌子,不是省人力** | +| **预算真实存在** | 公办有"平安校园"专项财政经费(必须花掉的预算);民办决策链短,校长一人可拍板,客单价能谈 | + +## 2.2 两类客户的打法差异 + +| | 民办寄宿学校 | 公办学校 | +| --- | --- | --- | +| 决策人 | 校长/董事长,**一人拍板** | 教育局统采 + 校长申报 | +| 销售周期 | 1–3 个月 | 6–12 个月(招投标) | +| 价格敏感度 | 中(对比的是出事的代价) | 低(预算内花完即可),但低价竞标者多 | +| 打法 | **直销,先打样板校** | 拿着民办样板校进教育局,争取列入区级统采目录 | +| 角色 | 现金流与打磨产品 | 规模与利润 | + +**顺序必须是先民办后公办**:民办给得起决策速度,让 M3–M4 阶段快速拿到真实校园误报数据;公办统采需要的案例和检测报告,从民办样板校来。 + +## 2.3 客户的真实痛点(按客户语言) + +1. "宿管晚上查完寝就睡了,学生翻墙出去出了事,学校全责" → 周界翻越检测 + 值班室声光告警 +2. "两个学生在厕所门口打起来,等老师知道已经进医院了" → 打架/剧烈动作检测,秒级推值班室 +3. "家长天天问孩子到校没有、出校没有" → (谨慎承接,见 §4.3 增值项) +4. "上面检查要看监控全覆盖,但摄像头装了 300 个,根本没人盯" → **这是最普遍的现状:有监控无监看**。方案首期默认选 16 路高风险点位验证价值,单逻辑站点本阶段可横向扩到 128 路;超过 128 路按校区/楼栋拆成多个逻辑站点分期覆盖 + +# 3. 卖什么 + +## 3.1 产品组合 + +| 层 | 内容 | 对应能力 | +| --- | --- | --- | +| 复用存量 | **接入学校已装的 IP 摄像头**(ONVIF/RTSP),不强制换设备 | L1 接入层;M0 白名单验证扩展到存量设备兼容性排查 | +| 边缘算力 | 首期按默认 16 路配置边缘推理节点(Jetson/x86+GPU),视频不出校;扩容时增加媒体/推理 worker | 《03》§4.1 边缘为主形态;硬件数量以现场码流与模型压测为准 | +| 平台 | 值班室看板 + 工单 + 批量 ack + 当班交接 | 《02》§3.2 已识别:值班室形态 UI 与家庭 App 是两个东西 | +| 终端 | 值班室大屏 + 声光联动 + 保卫科/校领导 App | 《03》§5.4 | +| 服务 | 规则按学期调整(课表/寒暑假)、误报收敛、检查迎检报表 | 年服务费内容 | + +**"复用存量摄像头"是四线学校的成交关键**——预算有限,重新布线换设备的方案直接出局。这正是平台不绑品牌(RQ-C-01)的商业价值。 + +## 3.2 核心规则集(S2 包,按夜间优先排序) + +| 优先 | 规则 | 时段 | 说明 | +| --- | --- | --- | --- | +| P0 | 翻越围墙/护栏 | 全天,夜间加权 | 校长最怕的事故源头 | +| P0 | 宿舍晚归/夜间离寝 | 22:30–06:00 | 楼栋出入口机位 + 时段策略 | +| P0 | 打架/推搡/围观聚集 | 全天 | 课间与就寝前高发 | +| P0 | 危险区域闯入(天台/配电房/水池/实验室) | 非开放时段 | Zone + 时段 | +| P1 | 尾随进校 | 上下学时段 | **ReID + 门禁事件融合,不用人脸** | +| P1 | 上课时段楼道徘徊 | 课表内 | 需课表导入(场景包 calendars) | +| P1 | 学生跌倒/晕倒 | 全天 | 复用 S1 能力,操场/楼梯间 | + +## 3.3 收费模型 + +| 项 | 模式 | 说明 | +| --- | --- | --- | +| 建设费 | 一次性 | 推理服务器 + 平台部署 + 存量摄像头接入调试 + Zone/课表配置 | +| 年服务费 | 按路数/按年 | 规则学期性调整、模型更新、误报收敛、迎检报表。**民办校谈 3 年锁定** | +| 教育局统采 | 按校打包 | 区级平台(多租户)+ 分校部署,L0 多租户能力的直接变现 | + +# 4. 怎么打 + +## 4.1 销售路径 + +``` +选 1–2 所本地有口碑焦虑的民办寄宿中学 + → 免费试运行一个月(只开翻墙+晚归+打架三条,dry_run 收基线) + → 用"上周拦下 N 起翻墙、平均响应 X 秒"的周报转正式合同 + → 民办样板校 2–3 所 + → 带案例进教育局 → 平安校园专项 / 区级统采 +``` + +试运行必须用 dry_run 模式(《02》§6.4):先收两周基线,把误报调到值班室可接受的量,再开真实告警。**第一周就用误报轰炸值班室,这个客户就永远丢了。** + +## 4.2 打单要点 + +| 要点 | 做法 | +| --- | --- | +| 卖"响应"不卖"识别" | 演示的高潮不是检测框,是值班室大屏弹窗 + 声光 + 值班员手机同时响。校长买的是"有人管",不是 AI | +| 夜间演示 | 安排在晚自习后实地演示翻墙检测——在客户最痛的时段展示 | +| **主动讲"我们不做人脸"** | 未成年人合规是文档写死的阻塞项(《02》§13)。在学校场景把"全程 ReID 匿名、不建人脸库、不追踪具体学生身份"作为**卖点**讲——校长同样怕家长投诉隐私 | +| 迎检价值 | 平安校园检查时,系统报表(覆盖点位、告警处置率、响应时长)就是现成迎检材料 | + +## 4.3 增值与边界 + +| 需求 | 态度 | 理由 | +| --- | --- | --- | +| 家长查看孩子到离校 | ⚠️ 谨慎 | 涉及逐个学生身份识别 = 人脸 + 未成年人双重合规。若做,走**校卡/门禁数据对接**(Webhook 开放 API),不走视觉识别 | +| 明厨亮灶(后厨监控) | ✅ 可接 | 有专项政策与预算,检测目标是着装/操作规范(属性类,无身份),S4 能力的低风险预演 | +| 课堂行为分析(学生专注度等) | ❌ 不接 | 争议大、合规风险高、与安全定位偏离 | + +## 4.4 风险 + +| 风险 | 缓解 | +| --- | --- | +| **未成年人合规**(即便不做人脸) | 视频留存期限从严、查看权限最小化、告示牌,专项合规意见先行(《01》§7.3)——这在本群体是售前成本,不是售后 | +| 存量摄像头质量差(夜间宿舍区无补光) | 售前勘测出具点位报告:哪些路能用、哪些需补光/换机。**不承诺坏机位的检测效果** | +| 寒暑假期间系统闲置引发续费质疑 | 假期切换到周界/入侵模式(场景包 calendars 天然支持),全年有价值 | +| 低价竞标者用"录像+关键词"方案搅局 | 标书写入功能性指标:越线方向判定、行为分类、响应时长、误报率报告 | +| 学生识别出摄像头盲区规避 | 定期用系统数据复盘翻墙热点,调整机位建议——写进年服务内容 | + +# 5. 与平台规划的对齐 + +| 项 | 对齐点 | +| --- | --- | +| 场景包 | S2 包直接复用;课表/校历(calendars)是本群体的核心配置工作量 | +| UI 形态 | 值班室看板 + 工单 + 批量 ack —— M4 管理系统需求的主要来源就是这个群体 | +| ReID | 尾随/徘徊全走 ReID(M4),**本群体人脸能力永不主动引入**,家长端需求走门禁数据对接 | +| 部署 | 边缘为主(视频不出校),对齐《03》§4.1;默认交付 16 路,单逻辑站点本阶段横向扩到 128 路。禁止承诺单服务器 100–300 路;超过 128 路按校区/楼栋拆分逻辑站点 | +| 多租户 | 教育局区级统采 = 多租户(L0)的第一个真实变现场景 | + +--- + +> 姊妹文档:《04-目标客户-政府购买居家养老》《06-目标客户-工厂园区安全生产》。 diff --git a/docs/raw/06-目标客户-工厂园区安全生产.md b/docs/raw/06-目标客户-工厂园区安全生产.md new file mode 100644 index 0000000..e74639b --- /dev/null +++ b/docs/raw/06-目标客户-工厂园区安全生产.md @@ -0,0 +1,145 @@ +# 🏭 目标客户群体 03 — 工厂与工业园区安全生产 + +> 版本 v0.2 · 2026-08-03 +> 市场定位:中国南方四线城市 · 付费方:企业法定安全生产费用(2B) +> 对应场景包:**S4 园区/厂区包** —— ⚠️ 当前在《02-需求分析》§12.1 被列为 Won't(本期),本文档同时给出优先级调整建议(§6) + +--- + +# 1. 一句话定位 + +**卖给厂长的"应急局检查过得去、安全经费花得掉"的 AI 监管员:三个客户群体中唯一有法定强制预算的。** + +# 2. 客户画像 + +## 2.1 为什么高付费 + +| 因素 | 说明 | +| --- | --- | +| **法定强制预算** | 企业按营收比例**强制提取安全生产费用**,专款专用,必须花在安全上——花不掉反而是审计问题。采购安全监控系统是最顺手的合规支出 | +| **双重倒逼** | 应急管理局执法检查(隐患整改通知书)+ 安全生产责任险费率与事故记录挂钩。上了 AI 监管系统既是整改材料,也可能谈保费 | +| **产业结构契合** | 四线城市恰恰是中小制造业聚集地:建材、五金、化工、食品加工、木材——高危工序多、安全员配置少 | +| **一票否决压力** | 重大事故对企业主意味着停产整顿甚至刑责。付费动机与前两个群体同构:**不花钱就要担责** | + +## 2.2 客户分层 + +| 层 | 对象 | 特点 | 打法 | +| --- | --- | --- | --- | +| A | 规上工厂(化工、建材、金属加工) | 有专职安全员、经费充足、检查频繁 | 直销,单厂 30–100 路 | +| B | 工业园区管委会 | 想做"智慧园区"政绩 + 统一安全监管 | 2G 打包,多租户平台覆盖园内企业 | +| C | 在建工地(住建口) | 智慧工地有政策要求(部分地区强制) | 跟本地智慧工地集成商合作出算法能力 | + +**A 是主攻**:决策人是老板/厂长,一人拍板,销售周期短;B 是规模化路径;C 竞争激烈(智慧工地已是红海),只做能力输出不做总集。 + +## 2.3 客户的真实痛点(按客户语言) + +1. "应急局上个月来查,开了整改单:动火区无人监管、行车下站人" → 区域闯入/滞留检测,整改材料直接引用系统 +2. "安全员就两个人,车间八个,巡不过来" → AI 替代巡场,违规即拍照留证 +3. "工人不戴安全帽,罚也罚了,没人盯就摘" → 未戴安全帽检测 + 车间大屏实时曝光,行为矫正 +4. "出了工伤,家属说设备没防护,我们拿不出监管证据" → 事件实例的证据留存(不可篡改,《02》NFR-CMP-01)在劳资纠纷中是企业自证材料 + +# 3. 卖什么 + +## 3.1 产品组合 + +| 层 | 内容 | 对应能力 | +| --- | --- | --- | +| 复用存量 | 接入工厂已有监控(ONVIF/RTSP) | L1 接入层,同校园打法 | +| 边缘算力 | 首期按默认 16 路配置厂内推理节点,数据不出厂;扩容时增加媒体/推理 worker | 边缘为主形态;硬件数量以现场码流、模型与目标帧率压测为准 | +| 平台 | 违规事件台账 + 整改闭环工单 + 迎检报表 | L4 + 管理系统 | +| 终端 | 安全员 App + 车间曝光大屏 + 班组长通知 | 投递通道复用 | +| 服务 | 规则随工序调整、月度安全分析报告 | 年服务费 | + +## 3.2 核心规则集(S4 包,全部为 A/B 属性-状态类,无身份) + +| 优先 | 规则 | 检测类型 | 说明 | +| --- | --- | --- | --- | +| P0 | 未戴安全帽 | 属性(A 类) | 需求最普遍,验收最直观 | +| P0 | 危险区域闯入/滞留(动火区、行车下方、配电房) | Zone + 状态 | 直接对应整改单 | +| P0 | 未穿反光衣/工服 | 属性(A 类) | 同安全帽,一个模型多头输出 | +| P1 | 离岗检测(关键岗位无人超时) | Zone 内无人 + 时长 | 中控室、锅炉房 | +| P1 | 吸烟/明火 | 目标检测 | 化工、木材、纺织客户的强需求 | +| P1 | 人数管控(受限空间作业人数) | 计数 | 有作业票制度的工厂 | + +**技术上是三个群体中最简单的**:安全帽/反光衣是成熟的检测任务,误报容忍度高(误报一次安全员看一眼即可,不像家庭场景误报会导致卸载 App),环境是固定机位良好光照——比跌倒检测低一个数量级的难度,交付快、验收标准清晰。 + +## 3.3 收费模型 + +| 项 | 模式 | 说明 | +| --- | --- | --- | +| 建设费 | 一次性 | 推理硬件 + 平台 + 存量接入 + Zone/规则配置 | +| 年服务费 | 按路/按年 | 规则调整 + 报表 + 模型更新。锚点:对比一个安全员的年薪,一套系统 7×24 盯全厂 | +| 园区版 | 管委会付平台费,企业付接入费 | 多租户,B 层打法 | + +**发票科目就写"安全生产监控系统"**,让客户可直接列支安全生产费用——这是本群体独有的成交润滑剂,方案报价单要为此专门适配。 + +# 4. 怎么打 + +## 4.1 销售路径 + +``` +本地应急局执法动态 / 安全生产协会 / 安责险保险公司 + → 锁定近期收到整改单的规上企业 + → 免费 2 周试运行(安全帽 + 危险区域两条规则,dry_run 收基线) + → 周报:本周违规 N 次、高发时段、高发班组 + → 转正式合同 → 同行业口碑复制(四线城市同业老板圈子极小) + → 园区管委会打包 +``` + +**安责险保险公司是被低估的渠道**:保险公司有动机降低承保企业出险率,部分保司为投保企业补贴安全技防投入——找到当地承保安责险的保司谈联合推广,一次触达一批企业。 + +## 4.2 打单要点 + +| 要点 | 做法 | +| --- | --- | +| 对着整改单卖 | 方案第一页引用客户收到的整改通知书条目,逐条对应系统功能 | +| 违规数据说话 | 试运行周报的"本周违规 N 次+抓拍图"比任何 PPT 有效——老板第一次直观看到自己厂里的真实违规量 | +| 算人力账 | 一个安全员年成本 vs 系统年费,7×24 无死角 vs 两条腿巡场 | +| 大屏曝光的行为矫正价值 | 车间大屏实时曝光违规抓拍,一个月内违规率肉眼可见下降——这是续费时最硬的数据 | + +## 4.3 边界与合规 + +| 项 | 态度 | 理由 | +| --- | --- | --- | +| 违规与具体工人身份绑定(考核扣款) | ⚠️ 谨慎 | 客户会强烈要求"认出是谁好罚款"。默认给**班组/区域级统计**;确要个人绑定,走工牌/定位卡对接,不走人脸。若客户坚持人脸,按《03》§2.8.3 完整授权流程(员工知情同意 + 工会程序),且 watchlist 红线同样适用:**系统不自动触发扣款,只生成待人工确认记录** | +| 工人隐私(休息区、更衣室) | 硬边界 | `privacy_flag` 代码级拒绝(NFR-CMP-05)同样适用于工厂 | +| 效率监控(工时、动作快慢) | ❌ 不接 | 偏离安全定位,劳资敏感,毁口碑 | + +## 4.4 风险 + +| 风险 | 缓解 | +| --- | --- | +| 小厂付款能力/意愿差 | 只做规上企业与园区打包,不碰 20 路以下小单 | +| 智慧工地/安全帽检测已是红海 | 不打价格战,差异化在:整改闭环工单 + 迎检报表 + 误报收敛服务(红海产品普遍装完即弃,误报没人管) | +| 车间环境恶劣(粉尘、蒸汽、频闪) | 售前勘测出具点位可行性报告,不承诺坏点位效果(同校园打法) | +| 客户拿系统罚款引发劳资对立,反噬口碑 | 合同与培训中引导"曝光教育为主";个人绑定功能默认不开 | + +# 5. 与平台规划的对齐 + +| 项 | 对齐点 | +| --- | --- | +| 能力复用 | 检测+跟踪+Zone+时段全部现成;新增的只是安全帽/反光衣/吸烟检测头(A 类属性,YOLO 多类直接支持)与离岗规则模板 | +| 数据闭环 | 误报容忍度高 + 客户配合标注(安全员每天在看),**是三个群体中数据飞轮转得最快的**——可反哺行为分析模型 | +| 人脸/ReID | 默认无身份;个人绑定走工牌对接;人脸仅在完整授权流程后按 watchlist 红线执行 | +| 部署 | 边缘为主,数据不出厂;默认交付 16 路,30–100 路项目按 32/64/128 档横向分片,禁止视为单实例容量;单逻辑站点本阶段上限 128 路 | +| 多租户 | 园区管委会版 = 多租户第二个变现场景(与教育局统采同构) | + +# 6. ⚠️ 对现有规划的调整建议(需拍板) + +《02-需求分析》§12.1 目前将 S4 列为 **Won't(本期)**,S3 社区列为场景之一。按本次四线城市付费能力分析: + +| 对比 | S3 社区/物业 | S4 工厂/园区 | +| --- | --- | --- | +| 付费方 | 物业公司(0.5–1.5 元/㎡物业费,微利) | 企业法定安全经费(强制提取) | +| 付费意愿 | 弱,决策分散 | 强,老板一人拍板 | +| 技术难度 | 中(电动车、堆物、抛物) | **低**(安全帽、区域、属性类) | +| 交付周期 | 中 | 短 | + +**建议**: +1. S4 与 S3 在 M5 的优先级**对调**:S4 场景包进 M5,S3 降为 Could(仅承接街道办"智慧社区"财政项目,不主动开拓物业直客) +2. S4 技术上最简单,恰好适合作为《02》M5 出口标准("场景包为纯配置交付,无需改核心代码")的**验收案例** +3. 若采纳,需同步修订:《01》§4.1 场景总览、《02》§12.1 MoSCoW 与 §12.2 里程碑、《03》§3 场景包章节 + +--- + +> 姊妹文档:《04-目标客户-政府购买居家养老》《05-目标客户-寄宿制学校》。三个群体共性:**付费动机都是"不花钱就要担责"**——政府问责、学校口碑、法定经费;四线城市安防 AI 只有问责链条撑得起付费。共同的销售模式:免费 dry_run 试运行收基线 → 用真实数据周报转化 → 年服务费锁定 → 同城口碑复制。 diff --git a/docs/raw/07-事件契约比对-silver_pose.md b/docs/raw/07-事件契约比对-silver_pose.md new file mode 100644 index 0000000..e90e99f --- /dev/null +++ b/docs/raw/07-事件契约比对-silver_pose.md @@ -0,0 +1,259 @@ +# 事件契约比对:silver_pose 实际输出 vs YoVision 设计 + +> 目的:确定 silver_pose(已验证的推理内核)与 YoVision(平台层)之间**唯一的那条契约**。 +> 比对基准:silver_pose `v1/alerts.py` + `v2/internal/alert/dispatcher.go` + `v2/internal/store/store.go`;YoVision《03-通用场景应用方案》§2.6。 +> 日期:2026-08-03 + +--- + +## 0. 结论摘要 + +| 项 | 结论 | +| --- | --- | +| v1 / v2 输出一致性 | ✅ Python 与 Go 的 JSONL 字段**完全相同**,契约已经稳定,可以直接当基线 | +| 与平台设计的关系 | silver_pose 的事件是平台事件的**真子集**,没有需要推翻的字段 | +| 真正的阻塞 | 不是字段缺失,是 **3 个语义冲突**(见 §3),必须先解决 | +| 接入点 | **已经存在**——`v1/alerts.py` 的 `AlertSink` 就是为注入外部副作用设计的,不需要改动已验收代码 | +| 反向吸收 | silver_pose 有 **5 处**设计文档没考虑到的东西,应该写回《03》§2.6 | + +--- + +## 1. silver_pose 当前实际输出 + +### 1.1 JSONL(每确认事件一行) + +`v1/alerts.py:197-208` 与 `v2/internal/alert/dispatcher.go:104-113`,字段一致: + +```json +{ + "event_id": "FALL--000001", + "track_id": "P-0001", + "config_version": "<配置版本串>", + "source_id": "<来源标识>", + "state": "CONFIRMED", + "confirmed_at_utc": "2026-07-20T10:31:22.417000+00:00", + "suspected_at_monotonic": 1043.21, + "confirmed_at_monotonic": 1045.68, + "latency_seconds": 2.47, + "screenshot": "20260720/FALL-abc-000001.png" +} +``` + +### 1.2 SQLite(仅 v2,`internal/store/store.go:35`) + +JSONL 之外多出的列: + +``` +source_kind (默认 'rtsp')、camera_host、camera_port、camera_channel +``` + +> 注释里写明:**记录 host/port/channel,但永不记录密码**。 + +### 1.3 状态机(`v1/fall_state.py`) + +`NORMAL → SUSPECT → CONFIRMED → RECOVERING`,只有进入 `CONFIRMED` 才产出 `FallEvent`。 +`RECOVERING` 表示「确认摔倒后又自己起来了」,当前**不产出任何事件**。 + +--- + +## 2. 字段级比对 + +| silver_pose | YoVision《03》§2.6 | 状态 | 说明 | +| --- | --- | --- | --- | +| `event_id` | `id` | ⚠️ **需改造** | 生成方式不同,见 §3.2 | +| `track_id` | `subject.track_id` | ✅ 直接对应 | 语义一致 | +| `source_id` | `device_id` | ⚠️ 弱对应 | 前者是字符串标识,后者是平台设备实体主键,需映射表 | +| `state` | — | ⚠️ 语义冲突 | 见 §3.3 | +| — | `kind` | ❌ 缺 | 事件类型隐含在 `event_id` 的 `FALL-` 前缀里,必须显式化为 `"fall"` | +| `confirmed_at_utc` | `detected_at` | ✅ 直接对应 | 判定时刻,用于 SLA | +| `suspected_at_monotonic` | `occurred_at` | ❌ **不可用** | 单调时钟,跨进程无意义。见 §3.1 | +| `confirmed_at_monotonic` | — | ➖ 可丢弃 | 有 `confirmed_at_utc` 即可 | +| `latency_seconds` | (派生) | ✅ 保留 | 见 §4.3,建议平台侧也存 | +| `screenshot` | `evidence.snapshot_uris[0]` | ✅ 直接对应 | 相对路径 → URI | +| `config_version` | `rule.version` | ⚠️ 粒度不同 | 见 §4.2,两者都要留 | +| `camera_host/port/channel` | (由 `device_id` 引用) | ✅ 平台侧更优 | 平台不该在事件里冗余连接信息 | +| — | `tenant_id` / `site_id` | ❌ 缺 | 单机演示不需要,平台必须 | +| — | `rule{id,version,code}` | ❌ 缺 | silver_pose 无规则实体,只有全局配置 | +| — | `severity` | ❌ 缺 | 摔倒恒定 high,可由平台侧按 `kind` 补 | +| — | `confidence` | ❌ 缺 | 见 §3.4,**不是简单补字段** | +| — | `subject.attributes` | ❌ 缺 | 无属性识别 | +| — | `subject.anon_id` | ❌ 缺 | 无 ReID | +| — | `identity` / `identity_status` | ❌ 缺 | 可先恒为 `null` / `"not_enabled"` | +| — | `observation.bbox_seq_uri` | ❌ **重要缺口** | 见 §5.1 | +| — | `observation.keypoint_seq_uri` | ❌ **重要缺口** | 见 §5.1 | +| — | `evidence.clip_uri` / `clip_range` | ❌ **重要缺口** | 只有一张截图,无 pre-roll 视频 | +| — | `dedup_key` / `aggregated_into` | ❌ 缺 | 见 §3.5 | +| — | `outcome` / `outcome_reason` | ❌ **重要缺口** | 误报反馈闭环无落点 | + +--- + +## 3. 五个语义冲突(不是补字段能解决的) + +### 3.1 单调时钟 vs 墙钟 —— P0 + +`suspected_at_monotonic` / `confirmed_at_monotonic` 用的是 `time.monotonic()`。这在状态机内部是**正确选择**(不受系统对时影响,`fall_state.py:78` 还强制了单调递增校验),但它: + +- 进程重启后归零,跨进程无意义 +- 无法和平台的 `occurred_at` 对齐 +- 无法用于证据回捞(回捞需要墙钟去定位录像位置) + +**解决**:在事件产出处补一个墙钟事发时刻。 + +``` +occurred_at = confirmed_at_utc - (confirmed_at_monotonic - suspected_at_monotonic) + = confirmed_at_utc - latency_seconds +``` + +这个换算精度足够(误差是单帧级别)。**保留** monotonic 字段做内部诊断,但契约上以 `occurred_at` 为准。 + +### 3.2 `event_id` 生成方式 —— P0 + +当前是 `FALL-{session_id}-{序号:06d}`(`fall_state.py:146-154`),会话内自增。 + +- 单机单路演示:没问题 +- 平台扩展到 128 路、多分片/多节点:**序号会撞**。`session_id` 若不是全局唯一,两台边缘节点会产出同一个 `event_id`,而 `AlertDispatcher` 的去重恰好是按 `event_id` 做的 → 平台侧会静默丢弃真事件 + +**解决**:平台契约用 ULID(`evt_01J8X...`)。silver_pose 侧保留原 `event_id` 作为 `source_event_id` 字段,便于回溯本地文件名(截图就是按它命名的)。 + +### 3.3 `state` 字段语义被占用 —— P0 + +silver_pose 的 `state` 是**状态机状态**(`NORMAL/SUSPECT/CONFIRMED/RECOVERING`);平台事件里没有这个概念——平台事件恒等于「已确认的客观发生」,其后续流转是**告警**的状态机(《03》§2.7),两者不能混。 + +**解决**:silver_pose 的 `state` 不进平台事件顶层。映射为: + +- `state == "CONFIRMED"` → 产出事件,`kind: "fall"` +- `state == "RECOVERING"` → **不是新事件**,是对已有事件的 `outcome` 输入(见 §4.1) + +### 3.4 `confidence` 在这条链路上没有天然来源 —— P1 + +平台设计里 `confidence: 0.87` 隐含假设「有一个模型给出置信度」。但 silver_pose 的判定是**几何证据 + 时间窗状态机**(`evidence.py` 算躯干角度与髋部下坠比,`fall_state.py` 要求连续 1–3s 不中断),最终是布尔判定,没有一个自然的 0–1 分值。 + +**不要硬凑一个假的置信度。** 三个选项: + +1. 置 `null`,并允许 schema 里 `confidence` 可空(推荐先这样) +2. 用可解释的替代量:`horizontal_angle_degrees` 距阈值的余量、`visible_joint_count`、`latency_seconds`(越接近确认窗口下限说明证据越"干脆") +3. 后续换成分类模型时再填 + +对应到《02》里「七类动作模型优于二分类」的结论——真正的 `confidence` 要等模型换代才有意义。 + +### 3.5 去重只在进程内存里 —— P1 + +`AlertDispatcher._seen_event_ids`(Python)/ `Dispatcher.seen`(Go,`dispatcher.go:34`)都是内存 `set`/`map`: + +- 进程重启即丢失 +- 两个机位拍到同一次跌倒 → 两条独立事件,无合并 +- 无冷却期概念 + +这与《03》§2.6「同设备冷却期 / 同站点聚合 / 已处置抑制」是四个不同层次的去重。 + +**解决**:进程内去重**保留**(它防的是同帧重复写盘,是正确的);跨机位/跨时间的去重归**平台侧**,由 `dedup_key` 承担。silver_pose 只需在事件里带上足以构造 dedup_key 的原料(site + 位置 + kind + 时间桶)。 + +--- + +## 4. 反向吸收:silver_pose 比设计文档考虑得更周到的 5 处 + +这些应该写回《03》§2.6,是实战沉淀,设计文档里没有。 + +### 4.1 `RECOVERING` 状态是免费的 outcome 信号 + +《03》§2.6 的 `outcome` 只设计了人工来源(值班员标记误报)。但 silver_pose 的状态机已经能识别「确认摔倒后又自己站起来了」——这是**自动的、及时的**处置结果输入,比等人工标记快得多,而且正是家属/值班员最想知道的一件事。 + +**建议**:平台事件增加 `outcome` 的自动来源: + +``` +subject_recovered —— 由推理侧在 RECOVERING 稳定后回传 +``` + +并在《03》§2.6 的 outcome 取值里显式列出「自动/人工」两种来源。 + +### 4.2 `config_version` 进事件 + +设计文档只有 `rule.version`(单条规则版本)。silver_pose 把**整个判定配置的版本**钉进每条事件(`fall_state.py` 构造时强制非空校验)。 + +价值:调完阈值后跑回归,能精确区分「哪些事件是旧配置产出的」。`rule.version` 粒度不够——阈值往往是全局的。 + +**建议**:《03》§2.6 事件结构中,`rule` 之外**增加顶层 `config_version`**。 + +### 4.3 `latency_seconds` 显式存储 + +可以由两个时间戳相减,但显式存有两个好处:一是 SLA 报表不用每次算,二是它是**判定质量的直接指标**(贴近确认窗口下限 = 证据干脆;贴近上限 = 勉强通过,是误报高发区)。 + +**建议**:保留为顶层字段,并作为误报排查的首选排序键。 + +### 4.4 证据文件命名的隐私纪律 + +`v1/alerts.py` 文件头注释: + +> Artifact names use only the event ID and a date folder — never an RTSP address, credential, or client name. + +这条纪律设计文档里**没有写**。截图/片段的文件名会出现在日志、URL、客服工单里,是最容易泄露客户身份和摄像头地址的地方。 + +**建议**:写入《03》§2.6「三条硬约束」表(升级为四条)。 + +### 4.5 存连接信息但绝不存密码 + +`store.go` 顶部注释明确:记录 camera_host / port / channel,**永不记录密码**。同样应写入约束表。 + +--- + +## 5. 缺口分档 + +### P0 —— 不补则平台无法工作(M1 前) + +| # | 缺口 | 动作 | +| --- | --- | --- | +| 1 | 墙钟 `occurred_at` | 由 `confirmed_at_utc - latency_seconds` 换算 | +| 2 | 全局唯一 `id` | 平台侧生成 ULID,原 id 降为 `source_event_id` | +| 3 | 显式 `kind` | 恒为 `"fall"`,从前缀提升为字段 | +| 4 | `tenant_id` / `site_id` / `device_id` | 由 `source_id` 经映射表补全,映射表在平台侧 | +| 5 | `state` 不进事件顶层 | 按 §3.3 映射 | + +### P1 —— 接入后很快会缺(M2–M3) + +| # | 缺口 | 说明 | +| --- | --- | --- | +| 6 | `evidence.clip_uri` + `clip_range` | 现在只有一张截图。**一张截图不足以让值班员判断真假**,这是误报反馈闭环的前置条件 | +| 7 | `observation.bbox_seq_uri` / `keypoint_seq_uri` | 《02》C9 数据闭环的**唯一原料**。只有截图无法训练。silver_pose 内部已经有逐帧 keypoints,只是没落盘——这是**最低成本的高价值补齐** | +| 8 | `outcome` 回写通道 | 平台 → 推理侧的反向通道,目前完全没有 | +| 9 | `dedup_key` 原料 | 见 §3.5 | + +### P2 —— 后续(M4+) + +`severity` / `confidence` / `subject.attributes` / `anon_id` / `identity` / `aggregated_into`。 +其中 `identity_status` 建议**立刻**恒填 `"not_enabled"`,成本为零,避免以后区分不了「没开」和「比对失败」。 + +--- + +## 6. 接入方案:不改已验收代码 + +**关键发现:接入点已经存在。** + +`v1/alerts.py` 的 `AlertSink` 本来就是为注入外部副作用设计的(`play_sound` / `show_popup` 都是可注入的桌面适配器): + +```python +class AlertSink: + """Injectable output for sound and popup. Default implementation is silent.""" + def play_sound(self, event: FallEvent) -> None: ... + def show_popup(self, record: AlertRecord) -> None: ... +``` + +所以平台接入 = **新增一个 sink 实现**,而不是改 `EventArtifactWriter`(它已通过单元测试与 T-000 验收): + +``` +FallEvent ──→ EventArtifactWriter ──→ 本地截图 + JSONL (现状,不动) + └─→ PlatformSink ──→ mapper ──→ YoVision event JSON ──→ 平台 +``` + +`PlatformSink` 的职责只有三件:字段映射(§2 的表)、P0 补全(§5)、投递(HTTP POST 起步,后续换 ZeroMQ)。**投递失败必须落本地队列重试,不能阻塞主链路**——这与《03》§2.7「告警先落库再投递」是同一原则。 + +v2 Go 侧结构相同,`Dispatcher.Dispatch` 返回 `Record` 后加一个 publisher 即可,同样不动 `write`。 + +--- + +## 7. 下一步(按顺序) + +1. **确认边界**:silver_pose 保持独立仓库,是「已验证的推理内核 + 单场景演示」;YoVision 是平台层。两者只通过本文档定义的事件 JSON 通信 +2. **冻结 v0.1 契约**:把 §2 的映射表 + §5 的 P0 五项,写成一份 JSON Schema,两个仓库各放一份(内容相同),改动必须双向同步 +3. **补 §4 的五处反向吸收**到《03》§2.6,特别是把隐私纪律(§4.4、§4.5)升格为硬约束 +4. **P1 第 7 项优先做**:keypoint 序列落盘。它在推理侧几乎零成本(数据已在内存),但决定了《02》C9 数据闭环能否启动 +5. `confidence` 保持 `null`,**不要造假值**(§3.4) diff --git a/docs/raw/08-三系统职责划分.md b/docs/raw/08-三系统职责划分.md new file mode 100644 index 0000000..31b7170 --- /dev/null +++ b/docs/raw/08-三系统职责划分.md @@ -0,0 +1,184 @@ +# 三系统职责划分:Sense / Brain / Bell + +> 本文档回答一个问题:**一段代码该写进哪个目录。** +> 划分依据来自《03-通用场景应用方案》的 L0–L5 分层与组件选型,本文档只是把它按三个可独立开发的系统重新切分。 +> 定稿:2026-08-03 + +--- + +## 1. 三系统总表 + +| | **Sense** | **Brain** | **Bell** | +| --- | --- | --- | --- | +| 中文 | 感知系统 | 推理系统 | 管理系统 | +| 小学生版 | 感觉到 | 想一想 | 打铃叫人 | +| 对应层 | L1 接入 | L2 流水线 + L3 算法 | L4 业务 + L0 基座 | +| 语言 | Go | Python / CUDA | Go + 前端 | +| 有无状态 | 有(设备台账) | **无状态** | 有(事件、告警) | +| DB schema | `sense` | 无 | `bell` | +| 契约角色 | 供流与信号 → Brain | **产出**事件 | **消费**事件 | +| 首次交付 | M1 | M3 | M3(最小)→ M4(完整) | + +### 1.1 各自装什么 + +**Sense —— 把现场的流和信号稳定地拿进来并管住** + +- 设备台账(`devices`,含 `modality`:video / radar / contact / button / wearable) +- ONVIF 客户端:连接、GetProfiles、GetStreamUri、SetSystemDateAndTime +- mediamtx API 客户端(**自行用 oapi-codegen 从其 OpenAPI 生成**,不依赖第三方 SDK) +- **对账器**:水平触发、`sync_state`、独立信号量、10% 孤儿删除安全闸、只收敛不回滚 +- 探活与断线重建 +- WireGuard 控制面隧道 +- mediamtx 鉴权回调(401 + 踢流是四种停用粒度之一) +- 容量配额执行:设备新增/启用时读取 Bell 的站点配额,只拒绝超额变更;配额服务暂时不可用时不影响已有流 +- 流绑定与媒体分片调度:维护 `mtx_instance`,默认 16 路交付;扩到 128 路时按 `media_shard.max_streams` 横向分片 +- 边缘节点 agent:推流、本地环形缓冲、断网续传 +- **设备型触发源**:雷达、门磁、按钮、ONVIF 事件订阅 +- 非视频传感器的信号接收(signal plane,不走 mediamtx) + +**Brain —— 看画面、出判定** + +- Savant 流水线与适配器(ZeroMQ 边界,适配器故障隔离,实时模式丢帧) +- 模型:检测 / 姿态 / 跟踪 / ReID(`anon_id`)/(M5)人脸 +- **`Detector` 与 `PoseEstimator` 接口解耦**——这是 AGPL 逃生通道的前提,换 YOLOX + RTMPose 时判定算法不动 +- 判定内核:几何证据 + 时间窗状态机(从 silver_pose 抽取,两边共用) +- 事件 mapper:判定结果 → 事件契约 v0.1 +- **像素级**运动侦测触发(要解码,所以在这里) +- 推理 worker 注册与分片:单逻辑推理分片默认最多 16 路;64/128 路由多个 worker 承担,路由变更不改判定代码 + +**Bell —— 记录、派发、追到人确认** + +- 事件接收与校验(schema 校验 + 契约 README §5 那六条代码级断言 + 生成 ULID) +- 事件存储:**不可变**,误判只改 `outcome` +- 规则引擎 + 场景包加载 +- 告警状态机:升级链、ack、抑制、静默(**≤4h,无永久选项**) +- 投递:push / 短信 / 语音,**双供应商** +- 误报反馈闭环:`outcome` 回写 Brain +- 多租户 RBAC、`tenant_features`(人脸授权开关:未授权时能力在 API 与 UI 中**不可见**,不是禁用) +- 审计日志 +- 站点容量配额的唯一真相源:`site.max_video_channels` 默认 16、上限 128;提供版本化内部读取接口/投影给 Sense 执行 +- 管理后台前端 + 大屏:按 128 路设计分页/虚拟列表、筛选与批量操作,不一次性加载全部视频 + +### 1.2 不属于任何一个目录的东西 + +| 组件 | 说明 | +| --- | --- | +| **mediamtx** | 独立二进制,外部依赖。Sense 管它的配置与生命周期,`Sense/deploy/` 放基线配置 | +| **PostgreSQL / MinIO / Prometheus** | 基础设施,独立部署清单 | +| **silver_pose** | 独立仓库,是 Brain 判定内核的来源与单机演示形态。**不改名、不合并**——它现在能卖,Brain 还不能 | +| **`_reference/`** | 只读参考源码(如 MiBeeNvr)。按《03》§1.4 白名单借鉴 ONVIF、IP 自愈、健康检测、测试组织与 UI 交互;禁止复制媒体内核、SQLite 真相源、AI/用户体系,禁止整仓进生产或加入 go.mod | + +--- + +## 2. 目录结构 + +### 2.1 Sense(Go) + +``` +Sense/ +├── cmd/ +│ ├── sense-api/ 控制面服务:设备台账、鉴权回调、对账器 +│ └── sense-agent/ 边缘节点:推流、环形缓冲、隧道 +├── internal/ +│ ├── device/ 设备台账(devices,含 modality) +│ ├── onvif/ 连接、GetProfiles、GetStreamUri、SetSystemDateAndTime +│ ├── mtx/ oapi-codegen 生成的 mediamtx 客户端 + 薄封装 +│ ├── reconcile/ 对账循环(幂等 + 退避 + 安全闸) +│ ├── probe/ 探活 +│ ├── trigger/ 设备型触发源(雷达/门磁/按钮/ONVIF 事件) +│ ├── tunnel/ WireGuard 控制面 +│ ├── authcb/ mediamtx 鉴权回调 +│ └── store/ M1 先 SQLite,schema 与生产 Postgres 保持一致 +├── deploy/ +│ └── mediamtx.yml 基线配置 +└── api/openapi.yaml +``` + +### 2.2 Brain(Python / CUDA) + +``` +Brain/ +├── pipeline/ Savant 流水线定义与适配器 +├── models/ +│ ├── detector/ Detector 接口 + YOLO / YOLOX 实现 +│ ├── pose/ PoseEstimator 接口 + YOLO-pose / RTMPose 实现 +│ ├── tracker/ ByteTrack / BoT-SORT +│ └── reid/ 匿名同一性,产出 anon_id +├── judge/ +│ ├── evidence.py 几何证据(躯干角、髋部下坠比) +│ └── state.py 时间窗状态机 NORMAL/SUSPECT/CONFIRMED/RECOVERING +├── emit/ +│ ├── mapper.py 判定结果 → 契约 v0.1 +│ └── publisher.py 投递(HTTP 起步 → ZeroMQ);**失败落本地队列重试,不阻塞主链路** +├── trigger/ 像素级运动侦测 +└── contracts/ ← 与 Bell/contracts/ 逐字节相同 +``` + +### 2.3 Bell(Go + 前端) + +``` +Bell/ +├── cmd/bell-api/ +├── internal/ +│ ├── ingest/ 接收事件、schema 校验、六条断言、生成 ULID +│ ├── event/ 事件存储(不可变,只允许追加 outcome) +│ ├── rule/ 规则引擎 + 场景包加载 +│ ├── alert/ 告警状态机、升级链、ack、静默 +│ ├── deliver/ push / 短信 / 语音,双供应商 +│ ├── feedback/ 误报回流,outcome 回写 Brain +│ ├── tenant/ 多租户 RBAC、tenant_features +│ ├── audit/ 审计日志 +│ └── store/ Postgres(schema: bell) +├── web/ 管理后台前端 +├── packs/ 场景包(**纯配置** —— M5 架构验收点) +└── contracts/ ← 与 Brain/contracts/ 逐字节相同 +``` + +> `contracts/` 只在 Brain 与 Bell 各存一份(生产者与消费者),**Sense 不需要**——它不碰事件契约。 + +--- + +## 3. 七条边界(不写死就会漂) + +| # | 边界 | 定论 | +| --- | --- | --- | +| 1 | mediamtx 二进制归谁 | 独立进程。**Sense** 管配置与生命周期,部署清单放 `Sense/deploy/` | +| 2 | 触发源归谁 | 按性质切:**设备型**(雷达/门磁/按钮/ONVIF 事件)→ Sense,它们本来就是设备;**像素级**运动侦测 → Brain,它要解码 | +| 3 | pre-roll 证据切片谁裁 | 录制在 Sense(mediamtx),事件在 Brain 产出。**Bell 发起回捞**(它拥有事件),Sense 提供切片接口 | +| 4 | ULID 谁生成 | **Bell**。Brain 只填 `source_event_id`。见契约 README §4 | +| 5 | 一个 PostgreSQL 还是三个 | **一个实例,schema 分离**(`sense` / `bell`),Brain 无 schema。"DB 是唯一真相源"是对账器成立的前提,拆库即失效 | +| 6 | 16 路与 128 路分别指什么 | **16 路是默认交付规格,128 路是本阶段单逻辑站点上限**,都不是“单进程/单服务器保证值”。媒体与推理必须横向分片;单机承载量由分辨率、码率、帧率、模型和硬件基准测试决定 | +| 7 | 站点配额谁拥有、谁执行 | **Bell 拥有 `sites.max_video_channels`,Sense 在设备新增/启用写路径执行**。通过版本化内部 API 或只读投影同步,不允许 Sense 直接写 Bell schema;依赖暂时不可用时拒绝新变更,但已有视频链路继续运行 | + +第 2、3、6、7 条是本文档新定的,若实施中发现更合适的切法,改这里并同步《02》《03》。 + +--- + +## 4. 标识符约定 + +``` +仓库/目录 Sense Brain Bell +镜像 yov/sense:x.y yov/brain:x.y yov/bell:x.y +日志/指标前缀 sense_ brain_ bell_ +三字母码 SEN BRN BEL +DB schema sense — bell +``` + +**命名规则:系统名不得是该系统领域模型里的实体名。** +`bell` 域内有 `alerts` 表 → 系统不能叫 Alert(`alert.alerts` 无法读,且撞 Prometheus 内置的 `ALERTS` 序列)。另两个同样干净:`sense` 域内是 `devices`/`streams`,`brain` 域内是 `models`/`detections`。 + +--- + +## 5. 里程碑与目录的对应 + +| 阶段 | 动哪个目录 | +| --- | --- | +| **M0** 摄像头兼容性验证 | 无生产代码;可直接运行 MiBeeNvr 等 `_reference/` 项目作为隔离实验室测试台,产物允许丢弃 | +| **M1** 5 路接入骨架 + mediamtx | **仅 Sense**;MediaMTX 正式进入,完整 MiBeeNvr 不得替代生产媒体数据面 | +| **M2** 对账器 + 多租户 + 隧道 + 16 路开通 | **仅 Sense**;至少一个站点验证默认 16 路配额与全流程 | +| **M3** 默认 16 路推理 + 规则引擎 + 事件预警 | **Brain + Bell 同时起步**,契约在此首次被真实使用 | +| **M4** 64 路分片 + 灰度 + 管理系统 | Sense 与 Brain 完成横向分片,Bell 完成 128 路规模下的列表与批量交互;验证单分片故障隔离 | +| **M5** 128 路容量验收 + 第二三场景包 + 触发式推理 + 人脸 | Sense/Brain 验证扩容不改业务代码;Bell 的 `packs/`(**纯配置**)+ Brain 的模型与触发 | +| **M6** 异构传感器 + 两级判定 | Sense 的 `trigger/` 与 `device/`(modality 扩展)+ Brain 两级判定 | + +> M3 是契约第一次被真实使用的时刻。**在此之前契约允许原地修订,之后版本递增规则绝对生效**(契约 README §1)。 diff --git a/docs/raw/contracts/README.md b/docs/raw/contracts/README.md new file mode 100644 index 0000000..fd2733f --- /dev/null +++ b/docs/raw/contracts/README.md @@ -0,0 +1,138 @@ +# 事件契约 v0.1(已冻结) + +> **冻结日期**:2026-08-03 +> **范围**:推理侧(silver_pose 及后续推理内核)→ YoVision 平台侧的唯一数据契约。 +> **依据**:《07-事件契约比对-silver_pose.md》 + +| 文件 | 用途 | +| --- | --- | +| `event-v0.1.schema.json` | JSON Schema(draft 2020-12),规范本体 | +| `event-v0.1.example-current.json` | **现状**基线:silver_pose 今天就能产出的形态,P1 缺口全为 null | +| `event-v0.1.example-target.json` | **目标**形态:P1 补齐后,含 keypoint 序列、clip、自动 outcome | +| `event-v0.1.example-radar.json` | **非成像模态**:卫生间纯雷达跌倒,`snapshot_uris` 为空数组,门磁佐证 | + +三个 example 都是**测试夹具**,两侧仓库的单元测试都应对它们做校验。 + +### 修订记录 + +| 日期 | 变更 | 理由 | +| --- | --- | --- | +| 2026-08-03 | 初次冻结 | — | +| 2026-08-03 | 新增 `sensors[]` 与 `observation.signal_seq_uri`;`evidence.snapshot_uris` 的 `minItems` 由 1 放宽为 0 | 感知系统后期接入毫米波雷达等非成像设备,原 schema 把「感知=摄像头」焊死在契约里,纯雷达事件无法表达。**本次为原地修订**,理由是当时零实现;首个生产者上线后,§1.2 的版本递增规则绝对生效,不再允许原地改 | + +--- + +## 1. 冻结意味着什么 + +1. **本目录内容在两个仓库各存一份,内容必须逐字节相同。** 任何一侧修改,同一次提交内同步另一侧 +2. `schema_version` 为 `"0.1"`。**破坏性变更必须递增版本并保留旧版本文件**,不得原地修改已冻结的 schema +3. 「破坏性」的定义:删除字段、收紧取值域、把可空改为必填、改变字段语义。新增可空字段与新增 `kind` 取值**不算**破坏性 +4. 实验性字段一律放 `ext`,不占用顶层命名空间,也不触发版本升级 + +## 2. 三条设计决定(及其代价) + +**① 所有顶层键必须存在,可为 null,但不得省略。** +「省略」与「显式 null」在下游无法区分,这是这类系统最常见的排查陷阱——`identity_status` 就是被这个问题逼出来的字段。代价是生产者要写一串 null,但生产者只有一个 mapper 函数。 + +**② 根对象 `additionalProperties: false`。** +未登记字段一律拒绝。防的是推理侧偷偷加字段、平台侧偷偷依赖,几个月后没人知道它是不是契约的一部分。逃生口是 `ext`。 + +**③ `confidence` 允许为 null,且现阶段必须为 null。** +几何证据 + 时间窗状态机的判定链路没有天然置信度。伪造一个常量会污染所有下游阈值调优。等换成分类模型再填。 + +## 3. kind 注册表 + +新增 `kind` 不需要升 schema 版本,但**必须在此表登记**。 + +| kind | 含义 | 默认 severity | 产出方 | 状态 | +| --- | --- | --- | --- | --- | +| `fall` | 人员摔倒确认 | `high` | silver_pose FSM 进入 CONFIRMED | ✅ 已上线 | + +## 4. silver_pose → 契约 映射表 + +mapper 的完整实现规格。左列出处见《07》§1。 + +| 契约字段 | 来源 | 变换 | +| --- | --- | --- | +| `schema_version` | — | 常量 `"0.1"` | +| `id` | — | **平台侧**生成 ULID,加 `evt_` 前缀 | +| `source_event_id` | `event_id` | 原样。截图文件即按它命名,是回溯本地证据的唯一钥匙 | +| `tenant_id` / `site_id` / `device_id` | `source_id` | 经平台设备映射表解析。**事件中不得出现 RTSP 地址或凭据** | +| `sensors` | `source_id` | 单摄像头事件为 `[{device_id, "video", "primary"}]`;多传感器融合由推理侧列全 | +| `kind` | `event_id` 的 `FALL-` 前缀 | 提升为显式字段 `"fall"` | +| `severity` | — | 按 kind 查注册表 → `"high"` | +| `confidence` | — | 恒 `null`(见 §2③) | +| `detected_at` | `confirmed_at_utc` | 原样 | +| `occurred_at` | 换算 | `confirmed_at_utc - latency_seconds`。误差为单帧级 | +| `latency_seconds` | `latency_seconds` | 原样 | +| `config_version` | `config_version` | 原样 | +| `rule` | — | `null`(推理侧无规则引擎),平台侧按 kind 反查补全 | +| `subject.class` | — | 常量 `"person"` | +| `subject.track_id` | `track_id` | 原样 | +| `subject.attributes` | — | `{}` | +| `subject.anon_id` / `identity` | — | `null` | +| `subject.identity_status` | — | 常量 `"not_enabled"` | +| `observation.*` | — | 全 `null`(P1 补 keypoint_seq_uri;非成像模态补 signal_seq_uri) | +| `evidence.snapshot_uris` | `screenshot` | 相对路径 → URI,单元素数组。**非成像模态为 `[]`** | +| `evidence.clip_uri` / `clip_range` | — | `null`(P1 补) | +| `dedup_key` / `aggregated_into` | — | `null`,由平台侧构造 | +| `outcome` | `state` | `CONFIRMED` → `"unknown"`;`RECOVERING` 稳定后回传 `"subject_recovered"` + `outcome_source: "auto"` | +| `diagnostics.fsm_state` | `state` | 原样,**仅供排查** | +| `diagnostics.*_monotonic` | 同名字段 | 原样。**单调时钟跨进程无意义,不得用于任何时间计算** | +| `ext` | — | `{}` | + +## 5. schema 拦不住的四件事(必须写代码校验) + +已实测(`REJECT` = schema 能拦,`ACCEPT` = 拦不住): + +schema **能**拦:省略必填键 / 非法 ULID / 顶层多余字段 / outcome 越界 / `config_version` 空串 / `snapshot_uris` 空数组 / 负 latency / kind 大写。 + +schema **拦不住**,需在 mapper 与平台入口各加一道断言: + +| # | 校验 | 理由 | +| --- | --- | --- | +| 1 | `detected_at >= occurred_at` | 跨字段约束,JSON Schema 表达不了。倒序时间会让 SLA 统计出负数 | +| 2 | `\|detected_at - occurred_at - latency_seconds\| < 0.1s` | 三者冗余,必须自洽。不一致说明 mapper 用错了时钟 | +| 3 | `confidence is null`(现阶段) | schema 只能约束 0–1 区间,拦不住伪造值 | +| 4 | 证据文件名不含 IP、端口、凭据、客户名 | 只允许 `事件ID + 日期目录`。文件名会出现在日志、URL 与工单里 —— 这是 silver_pose `alerts.py` 头注释的既有纪律,此处升格为强制校验 | +| 5 | `sensors` 中恰有一个 `role == "primary"`,且其 `device_id == 顶层 device_id` | 数组约束,schema 表达不了。两个 primary 会让归因和去重都失去基准 | +| 6 | 若任一 `sensors[].modality == "video"`,则该 `device_id` 所在区域 `privacy_flag` 必须为 false | 隐私区域硬约束的运行时兜底。设备录入时已拦一道,此处是第二道 | + +### 5.1 隐私区域约束的正确表述 + +原表述「隐私区域拒绝 `device_type = camera`」应改为**按模态判定**: + +``` +privacy_flag = true 的区域: + 允许 modality ∈ {radar, contact, button, wearable} + 拒绝 modality ∈ {video} +``` + +差别不是措辞。按设备类型判定,每接一种新设备就要重新问一次"它算不算摄像头";按模态判定,规则一次写死,新设备只需声明自己的 modality。《01》RQ-S1-05、《02》NFR-CMP-05、《03》§3.2 与落地清单四处的表述需同步。 + +## 6. 本地校验命令 + +```bash +pip install 'jsonschema>=4.18' +python3 - <<'PY' +import json +from jsonschema import Draft202012Validator, FormatChecker +s = json.load(open('event-v0.1.schema.json')) +Draft202012Validator.check_schema(s) +v = Draft202012Validator(s, format_checker=FormatChecker()) +for f in ('event-v0.1.example-current.json', 'event-v0.1.example-target.json'): + errs = list(v.iter_errors(json.load(open(f)))) + print(f, 'OK' if not errs else [e.message for e in errs]) +PY +``` + +已于 2026-08-03 执行通过:schema 合法,两个 example 均校验通过。 + +## 7. 未决项 + +| # | 事项 | 决策人 | +| --- | --- | --- | +| 1 | `source_id` → `device_id` 映射表放在哪(平台 DB 表 / 边缘配置文件) | 待定 | +| 2 | 投递通道:HTTP POST 起步还是直接上 ZeroMQ | 待定,建议先 HTTP | +| 3 | `outcome` 回写的反向通道协议(平台 → 推理侧) | 待定 | +| 4 | silver_pose 侧本文件的落地方式(需按其 T-xxx 任务流程走) | 待定 | diff --git a/docs/raw/contracts/event-v0.1.example-current.json b/docs/raw/contracts/event-v0.1.example-current.json new file mode 100644 index 0000000..136f286 --- /dev/null +++ b/docs/raw/contracts/event-v0.1.example-current.json @@ -0,0 +1,60 @@ +{ + "schema_version": "0.1", + "id": "evt_01J8XQ2K7M3P5R9T0V4W6Y8Z2B", + "source_event_id": "FALL-a3f9c1-000017", + "tenant_id": 1001, + "site_id": 20301, + "device_id": 5012, + "sensors": [{ "device_id": 5012, "modality": "video", "role": "primary" }], + + "kind": "fall", + "severity": "high", + "confidence": null, + + "occurred_at": "2026-08-03T10:31:19.947Z", + "detected_at": "2026-08-03T10:31:22.417Z", + "latency_seconds": 2.47, + "config_version": "sp-v1-2026.07.20", + + "rule": null, + + "subject": { + "class": "person", + "track_id": "P-0001", + "attributes": {}, + "anon_id": null, + "identity": null, + "identity_status": "not_enabled" + }, + + "observation": { + "zone": null, + "dwell_sec": null, + "bbox_seq_uri": null, + "keypoint_seq_uri": null, + "signal_seq_uri": null + }, + + "evidence": { + "snapshot_uris": ["file:///events/20260803/FALL-a3f9c1-000017.png"], + "clip_uri": null, + "clip_range": null + }, + + "dedup_key": null, + "aggregated_into": null, + + "outcome": "unknown", + "outcome_source": null, + "outcome_reason": null, + + "diagnostics": { + "fsm_state": "CONFIRMED", + "suspected_at_monotonic": 1043.21, + "confirmed_at_monotonic": 1045.68, + "horizontal_angle_degrees": 21.4, + "visible_joint_count": 15 + }, + + "ext": {} +} diff --git a/docs/raw/contracts/event-v0.1.example-radar.json b/docs/raw/contracts/event-v0.1.example-radar.json new file mode 100644 index 0000000..ea497fb --- /dev/null +++ b/docs/raw/contracts/event-v0.1.example-radar.json @@ -0,0 +1,66 @@ +{ + "schema_version": "0.1", + "id": "evt_01J8XT7Q3R5S8V2W4X6Y9Z1A3B", + "source_event_id": "FALL-b7d2e4-000003", + "tenant_id": 1001, + "site_id": 20301, + "device_id": 5044, + "sensors": [ + { "device_id": 5044, "modality": "radar", "role": "primary" }, + { "device_id": 5045, "modality": "contact", "role": "corroborating" } + ], + + "kind": "fall", + "severity": "high", + "confidence": null, + + "occurred_at": "2026-08-03T02:14:08.300Z", + "detected_at": "2026-08-03T02:14:11.140Z", + "latency_seconds": 2.84, + "config_version": "radar-v1-2026.08.01", + + "rule": { "id": "R-ELD-004", "version": 1, "code": "fall_confirmed_radar" }, + + "subject": { + "class": "person", + "track_id": "R-0002", + "attributes": {}, + "anon_id": null, + "identity": null, + "identity_status": "not_enabled" + }, + + "observation": { + "zone": "bathroom", + "dwell_sec": null, + "bbox_seq_uri": null, + "keypoint_seq_uri": null, + "signal_seq_uri": "s3://yov-evt/20260803/evt_01J8XT7Q.../radar_track.jsonl" + }, + + "evidence": { + "snapshot_uris": [], + "clip_uri": null, + "clip_range": null + }, + + "dedup_key": "20301:bathroom:fall:2026-08-03T02:10", + "aggregated_into": null, + + "outcome": "unknown", + "outcome_source": null, + "outcome_reason": null, + + "diagnostics": { + "fsm_state": "CONFIRMED", + "suspected_at_monotonic": 88214.02, + "confirmed_at_monotonic": 88216.86, + "horizontal_angle_degrees": null, + "visible_joint_count": null + }, + + "ext": { + "radar_doppler_peak_mps": -1.9, + "radar_target_height_m": 0.21 + } +} diff --git a/docs/raw/contracts/event-v0.1.example-target.json b/docs/raw/contracts/event-v0.1.example-target.json new file mode 100644 index 0000000..4302346 --- /dev/null +++ b/docs/raw/contracts/event-v0.1.example-target.json @@ -0,0 +1,63 @@ +{ + "schema_version": "0.1", + "id": "evt_01J8XR5N9P2Q4S7T1V3W5Y7Z9A", + "source_event_id": "FALL-a3f9c1-000018", + "tenant_id": 1001, + "site_id": 20301, + "device_id": 5012, + "sensors": [{ "device_id": 5012, "modality": "video", "role": "primary" }], + + "kind": "fall", + "severity": "high", + "confidence": null, + + "occurred_at": "2026-08-03T11:04:41.120Z", + "detected_at": "2026-08-03T11:04:42.980Z", + "latency_seconds": 1.86, + "config_version": "sp-v1-2026.07.20", + + "rule": { "id": "R-ELD-001", "version": 3, "code": "fall_confirmed" }, + + "subject": { + "class": "person", + "track_id": "P-0004", + "attributes": { "age_group": "senior" }, + "anon_id": "pid_7f3a91", + "identity": null, + "identity_status": "not_enabled" + }, + + "observation": { + "zone": "living_room", + "dwell_sec": null, + "bbox_seq_uri": "s3://yov-evt/20260803/evt_01J8XR5N.../obs.jsonl", + "keypoint_seq_uri": "s3://yov-evt/20260803/evt_01J8XR5N.../pose.jsonl", + "signal_seq_uri": null + }, + + "evidence": { + "snapshot_uris": [ + "s3://yov-evt/20260803/evt_01J8XR5N.../s0.jpg", + "s3://yov-evt/20260803/evt_01J8XR5N.../s1.jpg" + ], + "clip_uri": "s3://yov-evt/20260803/evt_01J8XR5N.../clip.mp4", + "clip_range": ["2026-08-03T11:04:31.000Z", "2026-08-03T11:04:56.000Z"] + }, + + "dedup_key": "20301:living_room:fall:2026-08-03T11:00", + "aggregated_into": null, + + "outcome": "subject_recovered", + "outcome_source": "auto", + "outcome_reason": "RECOVERING 状态持续 8s 后回落 NORMAL", + + "diagnostics": { + "fsm_state": "RECOVERING", + "suspected_at_monotonic": 3021.44, + "confirmed_at_monotonic": 3023.30, + "horizontal_angle_degrees": 17.9, + "visible_joint_count": 17 + }, + + "ext": {} +} diff --git a/docs/raw/contracts/event-v0.1.schema.json b/docs/raw/contracts/event-v0.1.schema.json new file mode 100644 index 0000000..ed77d14 --- /dev/null +++ b/docs/raw/contracts/event-v0.1.schema.json @@ -0,0 +1,283 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://yovision.local/contracts/event-v0.1.schema.json", + "title": "YoVision Event Instance v0.1", + "description": "推理侧 → 平台侧的唯一契约。冻结于 2026-08-03。所有顶层键必须存在(可为 null),不允许省略——省略与显式 null 无法区分,是这类系统最常见的排查陷阱。", + "type": "object", + "additionalProperties": false, + + "required": [ + "schema_version", + "id", + "source_event_id", + "tenant_id", + "site_id", + "device_id", + "sensors", + "kind", + "severity", + "confidence", + "occurred_at", + "detected_at", + "latency_seconds", + "config_version", + "rule", + "subject", + "observation", + "evidence", + "dedup_key", + "aggregated_into", + "outcome", + "outcome_source", + "outcome_reason", + "diagnostics", + "ext" + ], + + "properties": { + "schema_version": { + "description": "契约版本。破坏性变更必须递增主版本。", + "const": "0.1" + }, + + "id": { + "description": "平台侧生成的全局唯一事件 ID(ULID)。推理侧不得自行生成。", + "type": "string", + "pattern": "^evt_[0-9A-HJKMNP-TV-Z]{26}$" + }, + + "source_event_id": { + "description": "推理侧原始事件 ID,如 silver_pose 的 FALL--000001。用于回溯本地截图文件名(截图即按它命名)。会话内唯一,全局不保证唯一——不得用作主键。", + "type": "string", + "pattern": "^[A-Za-z0-9_-]{1,128}$" + }, + + "tenant_id": { "type": "integer", "minimum": 1 }, + "site_id": { "type": "integer", "minimum": 1 }, + "device_id": { + "description": "主传感器的平台设备实体主键。由推理侧的 source_id 经平台映射表解析得到。事件中不得冗余 RTSP 地址或任何凭据。多传感器融合事件的完整来源见 sensors。", + "type": "integer", + "minimum": 1 + }, + + "sensors": { + "description": "参与本次判定的全部传感器。单摄像头事件为单元素数组。恰好一个元素的 role 为 primary,且其 device_id 必须等于顶层 device_id。", + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["device_id", "modality", "role"], + "properties": { + "device_id": { "type": "integer", "minimum": 1 }, + "modality": { + "description": "设备模态。决定隐私区域准入:privacy_flag 为真的区域只允许非成像模态。", + "type": "string", + "enum": ["video", "radar", "contact", "button", "wearable", "other"] + }, + "role": { + "description": "primary=判定主依据;corroborating=佐证(如雷达判跌倒、门磁佐证无人离开)。", + "type": "string", + "enum": ["primary", "corroborating"] + } + } + } + }, + + "kind": { + "description": "事件类型。取值登记在 contracts/README.md 的类型注册表中,新增类型不需要升 schema 版本。v0.1 已登记:fall。", + "type": "string", + "pattern": "^[a-z][a-z0-9_]{2,63}$" + }, + + "severity": { + "type": "string", + "enum": ["low", "medium", "high", "critical"] + }, + + "confidence": { + "description": "模型置信度。几何+状态机判定链路没有天然来源,必须填 null——不得用任意常量或阈值余量伪造。", + "type": ["number", "null"], + "minimum": 0, + "maximum": 1 + }, + + "occurred_at": { + "description": "事发时刻(墙钟 UTC)。决定证据回捞窗口。推理侧若只有单调时钟,按 detected_at - latency_seconds 换算。", + "type": "string", + "format": "date-time" + }, + + "detected_at": { + "description": "判定成立时刻(墙钟 UTC)。决定 SLA 计算。必须 >= occurred_at。", + "type": "string", + "format": "date-time" + }, + + "latency_seconds": { + "description": "从可疑到确认的耗时。可由两时间戳相减,但显式存储:它是判定质量的直接指标——贴近确认窗口下限说明证据干脆,贴近上限是误报高发区,为误报排查的首选排序键。", + "type": "number", + "minimum": 0 + }, + + "config_version": { + "description": "产出本事件时整套判定配置的版本。粒度高于 rule.version(阈值往往是全局的),用于调参后的回归对比。不得为空串。", + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + + "rule": { + "description": "命中的规则实体。推理侧无规则引擎时为 null,由平台侧按 kind 反查补全。", + "type": ["object", "null"], + "additionalProperties": false, + "required": ["id", "version", "code"], + "properties": { + "id": { "type": "string" }, + "version": { "type": "integer", "minimum": 1 }, + "code": { "type": "string" } + } + }, + + "subject": { + "type": "object", + "additionalProperties": false, + "required": ["class", "track_id", "attributes", "anon_id", "identity", "identity_status"], + "properties": { + "class": { "type": "string", "enum": ["person", "vehicle", "object"] }, + "track_id": { + "description": "跟踪器内的短期标识,跨会话不保证稳定。", + "type": "string", + "minLength": 1 + }, + "attributes": { + "description": "A 类属性(年龄段、着装等)。未启用时为空对象,不是 null。", + "type": "object" + }, + "anon_id": { + "description": "B+ 类 ReID 匿名标识,站点内会话级有效(≤30min),不做跨日长期关联。未启用为 null。", + "type": ["string", "null"] + }, + "identity": { + "description": "C 类人脸身份。仅在租户已授权且比对命中时非 null。", + "type": ["object", "null"], + "additionalProperties": false, + "required": ["person_id", "library_id", "score"], + "properties": { + "person_id": { "type": "string" }, + "library_id": { "type": "string" }, + "score": { "type": "number", "minimum": 0, "maximum": 1 } + } + }, + "identity_status": { + "description": "必须显式。只写 null 无法区分「没开这功能」与「比对失败」,后者是需要排查的故障。", + "type": "string", + "enum": ["not_enabled", "pending", "matched", "below_threshold", "no_candidate", "timeout"] + } + } + }, + + "observation": { + "description": "结构化观测。bbox/keypoint 序列是数据闭环的唯一原料——只有视频与截图无法用于训练。", + "type": ["object", "null"], + "additionalProperties": false, + "required": ["zone", "dwell_sec", "bbox_seq_uri", "keypoint_seq_uri", "signal_seq_uri"], + "properties": { + "zone": { "type": ["string", "null"] }, + "dwell_sec": { "type": ["number", "null"], "minimum": 0 }, + "bbox_seq_uri": { + "description": "视觉模态专用。非视觉事件为 null。", + "type": ["string", "null"], + "format": "uri" + }, + "keypoint_seq_uri": { + "description": "COCO-17 关键点逐帧序列(JSONL)。视觉模态专用,P1 必补项。", + "type": ["string", "null"], + "format": "uri" + }, + "signal_seq_uri": { + "description": "非视觉模态的结构化序列(雷达点云轨迹与多普勒、门磁状态变迁等,JSONL)。与 keypoint_seq_uri 平级——两者是各自模态的数据闭环原料,缺任一模态的序列,该模态就无法参与模型迭代。", + "type": ["string", "null"], + "format": "uri" + } + } + }, + + "evidence": { + "type": "object", + "additionalProperties": false, + "required": ["snapshot_uris", "clip_uri", "clip_range"], + "properties": { + "snapshot_uris": { + "description": "证据截图。**允许为空数组**:非成像模态(雷达、门磁)产出的事件本就没有画面,隐私区域更是禁止成像。不得据此假设每个事件都有图可看——值班台 UI 必须能渲染无画面事件。文件命名只允许包含事件 ID 与日期目录,绝不得含 RTSP 地址、凭据或客户名称,文件名会出现在日志、URL 与工单中。", + "type": "array", + "minItems": 0, + "items": { "type": "string", "format": "uri" } + }, + "clip_uri": { + "description": "含 pre-roll 的证据片段。仅有截图不足以让值班员判断真假,是误报反馈闭环的前置条件。P1 必补项。", + "type": ["string", "null"], + "format": "uri" + }, + "clip_range": { + "type": ["array", "null"], + "minItems": 2, + "maxItems": 2, + "items": { "type": "string", "format": "date-time" } + } + } + }, + + "dedup_key": { + "description": "跨机位/跨时间去重键,由平台侧构造。推理侧进程内按 source_event_id 的去重仍保留——它防的是同帧重复写盘,属不同层次。", + "type": ["string", "null"] + }, + + "aggregated_into": { + "description": "被合并入的事件 ID。非 null 时本事件不独立触发告警。", + "type": ["string", "null"], + "pattern": "^evt_[0-9A-HJKMNP-TV-Z]{26}$" + }, + + "outcome": { + "description": "处置结果。事件不可变,误判只能通过本字段标记,不得删改。subject_recovered 由推理侧状态机自动回传(确认后自行起身),无需等人工。", + "type": "string", + "enum": [ + "unknown", + "true_positive", + "false_positive", + "subject_recovered", + "duplicate", + "test" + ] + }, + + "outcome_source": { + "type": ["string", "null"], + "enum": ["auto", "manual", null] + }, + + "outcome_reason": { "type": ["string", "null"] }, + + "diagnostics": { + "description": "推理侧内部诊断量,仅用于排查,平台不得依赖其语义。单调时钟跨进程无意义,不得用于任何时间计算。", + "type": ["object", "null"], + "additionalProperties": true, + "properties": { + "fsm_state": { + "type": "string", + "enum": ["NORMAL", "SUSPECT", "CONFIRMED", "RECOVERING"] + }, + "suspected_at_monotonic": { "type": "number" }, + "confirmed_at_monotonic": { "type": "number" }, + "horizontal_angle_degrees": { "type": ["number", "null"] }, + "visible_joint_count": { "type": ["integer", "null"], "minimum": 0, "maximum": 17 } + } + }, + + "ext": { + "description": "厂商/场景扩展位。根对象 additionalProperties=false,任何未登记字段一律放这里,避免为实验性字段升版本。", + "type": "object" + } + } +} diff --git a/docs/raw/提案-寄宿制学校.html b/docs/raw/提案-寄宿制学校.html new file mode 100644 index 0000000..e936528 --- /dev/null +++ b/docs/raw/提案-寄宿制学校.html @@ -0,0 +1,783 @@ +校园夜间安全预警系统 · 提案 + + + +
+ +
+ + +
+
+

YOVISION · 校园安全预警系统

+

夜里出事,
第一个知道的应该是学校

+

面向寄宿制学校的视频行为预警方案 · 方案提案

+
+ 22:3006:00 +
+

那道没有人在看的窗口

+
+
+ + +
+
+

现状

+

摄像头装了,晚上谁在看?

+

这不是设备问题。绝大多数寄宿制学校的监控覆盖已经够用了,缺的是夜里那几个小时里,有人替你盯着这些画面。

+
+
240
在用摄像头(1200 人规模典型配置)
+
2
夜间在岗人数 · 宿管 1 门卫 1
+
16
值班屏能同时显示的画面数
+
0
凌晨真正被人看过的画面
+
+

监控让你事后查得到,但它不会在半夜叫醒任何人。

+
+
+ + +
+
+

情景推演 · 现在的夜晚

+

一个普通的周三夜里

+
    +
  1. 熄灯查寝完毕,两栋宿舍楼安静

    正常
  2. +
  3. 两名学生从操场东侧围墙翻出

    无人知晓
  4. +
  5. 值班屏画面正常轮巡,无人在岗查看

    无人知晓
  6. +
  7. 早操点名,两人不在队列

    发现异常
  8. +
  9. 学校组织人手开始寻找,调取录像回溯

    被动响应
  10. +
+
+ 事情发生 → 学校知情 + 7 小时 45 分 +
+
+
+ + +
+
+

情景推演 · 同一个夜晚,装了系统

+

同样是这堵墙

+
    +
  1. 熄灯查寝完毕,系统进入夜间规则

    布防
  2. +
  3. 围墙东侧检测到翻越动作

    命中规则
  4. +
  5. 值班室大屏弹窗 + 声光,附现场画面

    已送达
  6. +
  7. 宿管、保卫科手机同时推送

    已送达
  8. +
  9. 值班员点击确认,前往东侧围墙

    已确认
  10. +
  11. 学生在校外一百米处被截回

    已处置
  12. +
+
+ 事情发生 → 有人到场 + 4 分钟 +
+
+
+ + +
+
+

我们做什么

+

把已经装好的摄像头,
变成一个只在出事时叫人的系统

+

不增加盯屏岗位,不改变现有值班安排。平时它一句话不说;一旦命中规则,它会一直找人,直到有人确认。

+

不加人 · 不换设备 · 不增加值班负担

+
+
+ + +
+
+

先开这四条

+

夜间最该被叫醒的四件事

+
+
+ 全天 · 夜间加权 +

翻越围墙护栏

+

识别攀爬姿态与越线方向,区分「翻出去」和「路过」,避免把走读生放学误报成翻墙。

+
+
+ 22:30 — 06:00 +

晚归与夜间离寝

+

宿舍楼出入口机位,就寝后进出即记录,可按年级与楼栋分别设定时段。

+
+
+ 全天 +

打架推搡与围观聚集

+

识别剧烈肢体动作与人群聚拢,课间与就寝前是高发时段,秒级推到值班室。

+
+
+ 非开放时段 +

危险区域闯入

+

天台、配电房、水池、实验室,在图上圈出范围与生效时段,进入并停留即告警。

+
+
+

操场跌倒、上课时段楼道徘徊、外来人员尾随进校,可在运行稳定后逐条加开。

+
+
+ + +
+
+

响应链路

+

报出去不算数,有人确认才算

+
+
01 检测命中规则
+
02 取证自动回捞事发前十秒画面
+
03 送达值班室大屏 + 声光 + 手机
+
04 确认值班员点击确认并处置
+
05 升级超时无人确认,自动往上找人
+
+

发出去和被人看到,是两回事。没有人确认,系统会自己升级到保卫科长、再到值班校领导——不会停在「已发送」。

+
+
+ + +
+
+

怎么落地

+

不重新布线,不换品牌

+
    +
  • 接入贵校现有的网络摄像头,支持标准协议即可,不限品牌型号。开工前先做半天勘测,出一份点位报告:哪些机位现在就能用、哪些需要补光或调角度。不承诺坏机位的效果。
  • +
  • 校内放一台推理服务器,视频与分析全部在校园网内完成。视频不出校。上传到平台的只有告警事件本身和那一小段证据片段。
  • +
  • 值班室加一块大屏和一套声光,其余沿用现有设备。
  • +
  • 规则、区域、时段在网页上配置,学期变化自己就能改。
  • +
+
+
+ + +
+
+

关于隐私

+

我们不做人脸识别

+
    +
  • 不建立学生人脸库,不采集学生面部特征。
  • +
  • 系统判断的是「有人翻墙」,不是「谁翻了墙」。告警推给值班员的是位置、时间和画面,由老师到现场自己看是哪位同学。
  • +
  • 跨摄像头跟踪使用匿名标识,仅当次有效,不留档、不关联到具体学生。
  • +
  • 未成年人影像的合规要求是硬线,也是家长最先问的问题。这一页的内容,可以直接写进给家长的说明里。
  • +
+
+
+ + +
+
+

怎么开始

+

先看两周数据,
再决定要不要开告警

+

第一周就用误报轰炸值班室,这套系统在贵校就再也没人信了。所以试运行期只统计、不告警——不打扰任何人的现有值班。

+
    +
  • 两周后交一份周报:本周检测到翻越多少次、集中在哪个时段和哪段围墙。
  • +
  • 晚归多少人次,集中在哪栋楼哪个门。
  • +
  • 误报多少次,分别是什么原因,已经怎么调整。
  • +
  • 你看完这份数据,再决定正式开哪几条规则。
  • +
+
+
+ + +
+
+

不止夜里有用

+

平安校园检查,报表直接导出

+
    +
  • 点位覆盖情况、告警处置率、平均响应时长,按月按学期一键导出。迎检材料不用再临时拼,系统里本来就在记。
  • +
  • 寒暑假切换成周界模式,全年不闲置。
  • +
  • 每月一次安全分析:高发时段、高发点位、机位调整建议。哪段围墙被翻得最多,数据会告诉你——这比加派人手更省。
  • +
  • 每一条告警的完整记录留存可查:什么时候报的、报给了谁、谁在几点确认、怎么处置的。
  • +
+
+
+ + +
+
+

投入结构

+

两笔钱,分开算

+
+
+

建设费

+

一次性

+
    +
  • 校内推理服务器
  • +
  • 平台部署与调试
  • +
  • 存量摄像头接入
  • +
  • 区域、时段与课表配置
  • +
  • 值班室大屏与声光
  • +
+
+
+

年服务费

+

按路数计,逐年

+
    +
  • 规则随学期与课表调整
  • +
  • 误报持续收敛
  • +
  • 模型更新
  • +
  • 迎检报表与月度分析
  • +
  • 远程运维与故障响应
  • +
+
+
+

具体报价需现场勘测后出具。哪些点位现在就能用、哪些需要补光或换机位,勘测报告里会逐条写清楚——我们不对达不到条件的机位承诺效果,也不把这部分算进合同。

+
+
+ + +
+
+

下一步

+

三步,不影响现有值班

+
    +
  1. +

    现场勘测

    +

    半天时间,走一遍围墙、宿舍楼出入口和危险区域,出具点位可行性报告。

    +
  2. +
  3. +

    试运行两周

    +

    只统计不告警,值班室照常运转,不增加任何人的工作。

    +
  4. +
  5. +

    看数据决定

    +

    拿到周报后,由学校决定正式开哪几条规则、覆盖哪些点位。

    +
  6. +
+

YoVision 校园安全预警系统 · 本方案中的夜间时间线为情景推演,用于说明响应流程,非真实事件记录。

+
+
+ +
+ +
01 / 13
+
↑ ↓ 翻页
+ + diff --git a/docs/raw/流程图-系统怎么工作.html b/docs/raw/流程图-系统怎么工作.html new file mode 100644 index 0000000..ce320d1 --- /dev/null +++ b/docs/raw/流程图-系统怎么工作.html @@ -0,0 +1,303 @@ +系统怎么工作 · 一张图看懂 + + + +
+ +
+

YOVISION · 系统工作原理

+

摄像头拍到 → 有人来管,中间发生了什么

+

整套系统只做一件事:把「装了但没人看」的摄像头,变成「出事时会主动叫人」的系统。下面是完整的五步。

+
+ +
+ +
+
+ + 看 +
+

摄像头

+

IP CAMERA

+

装在围墙、走廊、客厅这些地方,对着现场。用你已经装好的,不用换新的。

+
+ +
+ +
+
+ + 收 +
+

录像主机

+

NVR · 边缘盒子

+

把所有摄像头的画面汇到一起,存在本地。视频先留在现场,平时不往外传。

+
+ +
+ +
+
+ + 判断 +
+

AI 分析

+

推理服务

+

替你盯着这些画面。有人翻墙、有人摔倒、有人进了不该进的地方——它认得出来,也分得清什么是正常走动。

+
+ +
+ +
+
+ + 记录 +
+

管理后台

+

值班台账

+

每一次异常变成一条记录,带上那一小段视频。谁在几点确认的、怎么处理的,全都留着,随时能查。

+
+ +
+ +
+
+ + 叫人 +
+

手机 App

+

预警投递

+

推到值班员和负责人手机上。没有人点确认,它会一直往上找人,不会停在「已发送」。

+
+ +
+ +
+ ← 反馈回流 +

如果这次报错了,值班员点一下「误报」,这段画面会自动回到 AI 那里当作教材。用得越久,报得越准。

+
+ +
+
+

平时不打扰

+

没有异常的时候,系统一句话不说,也不往外传视频。只有出事的那一小段才会被留下来、发出去。

+
+
+

视频不出现场

+

录像和 AI 分析都在本地完成。上传到平台的只有告警记录和作为证据的那一小段画面。

+
+
+

不认人,只认事

+

系统判断的是「有人翻墙」,不是「谁翻了墙」。默认不做人脸识别,不建人脸库。

+
+
+ +
YoVision 智能视频事件平台 · 通用场景流程说明 · 2026
+ +
diff --git a/docs/raw/跌倒监护系统流程图.pdf b/docs/raw/跌倒监护系统流程图.pdf new file mode 100644 index 0000000000000000000000000000000000000000..824ab24cacca1e807ff7a98c8710b525f0ef42ff GIT binary patch literal 90324 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 + + + + + + + +居家跌倒监护系统 +从摔倒发生,到有人赶到 +摄像头平时不工作。只有雷达发现异常,系统才会看一眼。 + + + + + +1 +家中感知 · 24 小时 + + + + + + + +雷达日夜守护 +卧室、卫生间安装毫米波雷达, +只感知动作,不成像。 +摄像头平时保持休眠。 + + + + + + +2 +就地录像 · 3 秒内 + + + + + + +唤醒摄像头 +家中的小盒子立刻唤醒摄像头, +并自动回看事发前 30 秒。 +录像保存在家里,不全部上云。 + + + + + + +3 +智能判断 · 10 秒内 + + + + + + + +是摔倒,还是坐下 +AI 分析身体姿态,区分真正的 +摔倒与坐下、弯腰、宠物走动。 + +判断为误报 → 静默记录,不打扰家人 + + + + +确认摔倒 + + + + + +4 +管理后台 · 全程留痕 +所有事件,完整记录 +家属与养老机构随时可查 + + + +事件与视频证据 +谁、在何时、发生了什么 + + +设备状态监测 +摄像头、雷达离线自动提醒 + + +处置结果回填 +标记误报,系统越用越准 + + + + + + + +5 +逐级通知 +最坏情况:3 分钟内一定有真人接手 +直到有人接手 —— 任何一级被确认,升级立即停止 + + + + + + +预警 App 推送 +0 – 30 秒 + +短信 + 语音电话 +30 – 60 秒 · 可穿透静音 + +备用联系人 +60 – 90 秒 · 邻居、护理员 + +呼叫中心人工介入 +90 秒后 + + + + + + + + +隐私优先:卧室与卫生间只装雷达,不装摄像头;摄像头平时不解码、不上传,仅在确认异常时上传相关片段。 + diff --git a/docs/其他项目的文档/居家老人跌倒监护系统 — 方案流程说明(客户版).md b/docs/其他项目的文档/居家老人跌倒监护系统 — 方案流程说明(客户版).md new file mode 100644 index 0000000..5ee8a63 --- /dev/null +++ b/docs/其他项目的文档/居家老人跌倒监护系统 — 方案流程说明(客户版).md @@ -0,0 +1,135 @@ +# 📊 居家老人跌倒监护系统 — 方案流程说明(客户版) + +> 来源:Notion · https://app.notion.com/p/3af401dc38a681a186a4fa7a8f5930a5 +> 导出时间:2026-08-01 + +> 面向客户的方案说明材料 · 居家老人跌倒监护系统 + +# 一句话说明 + +**雷达 7×24 守护,AI 二次确认,确认跌倒后 3 分钟内必定有人响应。** + +平时不看、不录、不传——只有在真正发生异常时,系统才会看一眼。 + +# 一、业务流程 + +```mermaid +flowchart TD + A["老人在家中正常生活"] --> B{"雷达持续监测
姿态异常或长时间静止"} + B -->|一切正常| A + B -->|疑似跌倒| C["唤醒摄像头
回捞事发前 30 秒画面"] + C --> D{"AI 姿态分析
二次确认"} + D -->|判定为误报| E["静默记录
不打扰家属"] + E --> A + D -->|确认跌倒| F["生成预警事件
附带视频证据"] + F --> G["第一级:家属 App 推送"] + G --> H{"30 秒内确认?"} + H -->|已确认| M["家属查看视频
前往处理"] + H -->|未确认| I["第二级:短信 + 语音电话"] + I --> J{"60 秒内确认?"} + J -->|已确认| M + J -->|未确认| K["第三级:备用联系人
邻居 / 物业 / 护理员"] + K --> L{"90 秒内确认?"} + L -->|已确认| M + L -->|未确认| N["第四级:呼叫中心人工介入"] + M --> O["处置结果回填
误报反馈持续优化系统"] + N --> O +``` + +## 关键时间线 + +| 时间 | 发生什么 | +| --- | --- | +| T+0s | 跌倒发生,雷达捕获 | +| T+3s | 摄像头唤醒,AI 开始分析 | +| T+10s | 确认跌倒,首次通知发出 | +| T+40s | 未响应 → 短信与电话 | +| T+100s | 未响应 → 备用联系人 | +| T+190s | 未响应 → 呼叫中心人工介入 | + +**最坏情况下,三分钟内一定有真人接手。** + +# 二、系统架构 + +```mermaid +flowchart LR + subgraph HOME["用户家中"] + CAM["IP 摄像头"] + RADAR["毫米波雷达
卧室 / 卫生间"] + NVR["边缘盒子
录像 + 设备管理"] + CAM --> NVR + RADAR --> NVR + end + subgraph CLOUD["云端平台"] + SAVANT["AI 分析引擎
姿态识别 + 跌倒判定"] + CORE["业务核心服务
事件 / 证据 / 权限"] + ALERT["预警调度引擎
升级链 + 多通道"] + DB[("事件与审计库")] + SAVANT --> CORE + CORE --> ALERT + CORE --> DB + end + NVR -->|"仅上传事件片段"| SAVANT + ALERT --> FAM["家属 / 护理员 / 呼叫中心"] + CORE --> APP["家属 App"] +``` + +# 三、为什么这样设计 + +## 隐私:视频默认不出户 + +- 卧室、卫生间**只装雷达,不装摄像头**——雷达只感知人体轮廓与动作,不成像 +- 客厅摄像头平时**不解码、不上传**,仅在雷达报警时上传那一小段 +- 录像存在户内盒子,不全量上云 +- 家属只能看到**自己家、且与告警相关**的片段 + +## 可靠:不依赖单一环节 + +| 风险 | 应对 | +| --- | --- | +| 家属没看手机 | 短信 + 语音电话自动升级,可穿透勿扰模式 | +| 家属不在本地 | 升级至备用联系人与呼叫中心 | +| 家庭断网 | 边缘盒子本地缓存,恢复后补传;断网本身触发运维告警 | +| 设备故障 | 每日自动探活,离线超阀值主动联系用户 | +| 单户故障 | 一户一盒子,互不影响 | + +## 准确:两级判定降误报 + +单靠摄像头或单靠雷达,误报都会让家属很快失去信任。 + +- **一级(雷达)**:宁可多报,不能漏报 +- **二级(AI 视频)**:审核一级的候选,剥离坐下、弯腰、宠物、访客等干扰 +- **误报反馈**:家属标记的每一次误报都会回流系统,**越用越准** + +## 可追溯:每一条告警都有完整记录 + +什么时间触发、基于什么证据、向谁发过、走的什么通道、是否送达、谁在何时确认、处置结果如何——全部不可篡改地留存,可导出备查。 + +# 四、技术基座 + +核心组件均基于成熟开源项目,**无厂商锁定,数据完全可控**: + +| 环节 | 技术基座 | 说明 | +| --- | --- | --- | +| 边缘录像与设备管理 | Kerberos Agent | MIT 许可,可商用 | +| AI 分析流水线 | Savant | 开源,支持故障隔离与弹性扩容 | +| 预警调度 | GoAlert | Apache-2.0,由 Target 公司开源并在内部生产使用 | +| 边缘硬件 | RK3588 平台 | 无风扇设计,无噪音,低功耗 | +| 业务平台与家属 App | 自主开发 | 根据业务需求持续迭代 | + +# 五、实施路径 + +```mermaid +flowchart LR + P1["试点验证
10 户"] --> P2["小规模运营
50 户"] --> P3["全量部署
200 户"] +``` + +| 阶段 | 重点 | 产出 | +| --- | --- | --- | +| 试点 | 验证检测准确率与安装流程 | 真实误报基线、安装规范 | +| 小规模 | 验证告警响应链与运维能力 | SLA 实测数据、客服流程 | +| 全量 | 规模化与成本优化 | 正式运营 | + +--- + +> 本文档为方案概述。具体技术实现、容量规划与风险控制见《线上生产开发方案》。 diff --git a/docs/其他项目的文档/跌倒检测视频接入系统 — 演示与验证开发方案.md b/docs/其他项目的文档/跌倒检测视频接入系统 — 演示与验证开发方案.md new file mode 100644 index 0000000..1034dc8 --- /dev/null +++ b/docs/其他项目的文档/跌倒检测视频接入系统 — 演示与验证开发方案.md @@ -0,0 +1,183 @@ +# 🧪 跌倒检测视频接入系统 — 演示与验证开发方案 + +> 来源:Notion · https://app.notion.com/p/3af401dc38a6815297c0f1645631e26f +> 导出时间:2026-08-01 + +> 版本 v1 · 配套文档:《线上生产开发方案》。本文档描述演示与前期验证阶段的做法,与生产架构的关系必须清晰界定。 + +# 1. 本阶段的目的(先划清边界) + +演示阶段有两个完全不同的目标,容易被混为一谈,必须分开处理: + +| 目标 | 用什么做 | 产出 | +| --- | --- | --- | +| A. 摄像头兼容性验证 | MiBeeNvr(现成 NVR) | 采购白名单 | +| B. 效果演示给非技术干系人 | MiBeeNvr | 可看的界面 | +| C. 架构风险验证 | 200 行自研骨架 + mediamtx | 生产代码 | + +## 关键判断:MiBeeNvr 不是「v0 版本」 + +Mi-Bee-Studio/MiBeeNvr 在架构上替代的是 **mediamtx**(它自带 HLS/WebRTC/HTTP-FLV/RTMP/SRT 全套媒体服务),而不是生产方案里要自研的设备管理层。 + +``` +演示版: MiBeeNvr(一个 exe,全包) +生产版: mediamtx + 生成的 API 客户端 + 自研骨架 + 对账器 + NAT 隧道 + ↑ 没有一行代码从演示版继承 +``` + +它跑通了,对生产架构零验证。NAT 穿透、path 对账、按需拉流、多租户这些真正会翻车的地方,它一个都不涉及(它假设摄像头与自己在同一局域网)。 + +因此:不要把它包装成第一版系统然后计划后续替换,替换是推倒重来而非迭代。 + +# 2. 目标 A/B:MiBeeNvr 作为测试台 + +## 2.1 项目基本情况 + +| 项 | 值 | +| --- | --- | +| 仓库 | Mi-Bee-Studio/MiBeeNvr | +| 许可 | MIT | +| 技术栈 | Go + Svelte 5 + SQLite,CGO_ENABLED=0 单二进制 | +| Web UI | 端口 9090,内置登录、实时预览、录像、系统指标 | +| 规模定位 | 树莓派 3B / 1GB 内存可跑(homelab 场景) | + +## 2.2 为什么适合做兼容性测试台 + +- ONVIF 摄像头接入局域网后后台自动登记,无需手动扫描 +- 认证摄像头进入「待激活」状态等待补凭据;未认证的直接开始录制 +- IP 自愈:摄像头换 IP(Wi-Fi 漫游换 AP)时按序列号自动重定位并重连,使用 unicast 探测,可跨路由子网 +- 一条 [install.sh](http://install.sh),10 分钟起来,界面上直接看哪个型号能发现、能出流、能 PTZ + +比自己写测试代码快得多,失败信息本身就有价值:某型号它都认不出来,说明该型号 ONVIF 实现有问题,直接从采购清单划掉。 + +## 2.3 部署 + +```bash +# 方式一:一键安装 +curl -fsSL https://raw.githubusercontent.com/Mi-Bee-Studio/MiBeeNvr/main/install.sh | sudo bash + +# 方式二:预编译二进制 +wget https://github.com/Mi-Bee-Studio/MiBeeNvr/releases/latest/download/mibee-nvr-amd64 +chmod +x mibee-nvr-amd64 +./mibee-nvr-amd64 init --password +./mibee-nvr-amd64 -config mibee-nvr.yaml +# 打开 http://localhost:9090 +``` + +## 2.4 使用红线 + +| 允许 | 禁止 | +| --- | --- | +| 实验室环境接自购测试摄像头 | 接入真实住户家中的摄像头 | +| 给干系人做效果演示 | 作为生产系统的第一版上线 | +| 阅读源码借鉴设计 | 作为长期依赖引入 go.mod | + +理由:项目 84 star / 10 fork / 1 watcher,且功能面异常宽(SRT、WebRTC WHEP、小米 TUTK P2P、WebDAV、FTP、浏览器端 ONNX 推理、时移录制等),一个体量如此的项目在短时间做出这么宽的面,大概率有大量 AI 辅助生成,不能假设它被真实压测过。本项目是养老场景,摄像头对着卧室和卫生间,未经充分验证的组件出安全问题后果不是技术问题。 + +## 2.5 值得借鉴的源码 + +``` +internal/onvif/ ONVIF 封装(与 EdgeX device-onvif-camera 对比着读) +internal/camera/ 健康监控 + 自动修复状态机 +internal/store/ SQLite schema 设计 +``` + +重点看两处真实踩坑设计:IP 自愈(序列号而非 IP 作身份)和待激活状态机(批量开通的中间态)。这两点自己从零设计想不到。 + +前端 go:embed 打包成单二进制的做法,将来给自研服务加 Web UI 时可直接参考。 + +# 3. 目标 C:架构验证骨架(生产代码) + +这部分是真正要写的东西,约 200 行,全部为生产代码而非丢弃品。 + +## 3.1 范围 + +``` +mediamtx(配置文件里写死 5 路测试摄像头) + + +单个 Go 程序: + ONVIF 连接 -> GetStreamUri -> 调 mediamtx API 建 path -> 每 30s 探活 +``` + +跑通之后就已经站在生产架构上了,后面是往上加(SQLite、对账、隧道、多租户),不是推倒。 + +## 3.2 代码结构 + +``` +cmd/edge-agent/main.go +internal/ + onvif/ 连接、GetProfiles、GetStreamUri、SetSystemDateAndTime + store/ 先用 SQLite,schema 与生产 Postgres 保持一致 + mtx/ oapi-codegen 生成的客户端 + 薄封装 + reconcile/ 最小对账循环(幂等 + 退避) + probe/ 探活 goroutine +``` + +## 3.3 从第一天就要遵守的约定 + +| 约定 | 原因 | +| --- | --- | +| 设备身份用序列号,不用 IP | DHCP / Wi-Fi 漫游会换 IP | +| 建 path 前先查是否存在 | 盲目 add 会踢掉正在推流的 publisher | +| 改配置前比对哈希 | 同上;没变就不调 API | +| 重试用指数退避 | 无事务,只能靠幂等重试 | +| 并发限流(信号量 8) | 避免打爆 mediamtx API | +| 设备状态含 pending_activation | 批量开通必然有凭据待补的中间态 | + +## 3.4 mediamtx 侧最小配置 + +```yaml +api: yes +apiAddress: :9997 + +pathDefaults: + sourceOnDemand: yes + sourceOnDemandCloseAfter: 30s + rtspTransport: tcp +``` + +# 4. 摄像头兼容性验证清单(M0 出口标准) + +采购 3-5 个候选型号,这是整个项目第一件要做的事。 + +## 4.1 必过项 + +| 验证项 | 通过标准 | 不通过的后果 | +| --- | --- | --- | +| GetProfiles | 返回至少两个 profile(主码流 + 子码流) | 无法自动获取子码流,需人工配置 | +| GetStreamUri | 返回可用的 RTSP URL | 无法自动化开通 | +| SetSystemDateAndTime | 调用成功且生效 | 事件时间戳不可信 | +| 子码流规格 | 可设为 640x360 或 704x576 左右 | 带宽和算力估算失效 | +| 编码格式 | 支持 H.264 | H.265 浏览器无法原生预览 | +| WS-Security 认证 | digest 或 usernametoken 任一可用 | 认证失败 | +| RTSP over TCP | 长时间稳定不断流 | 家庭网络下 UDP 丢包严重 | + +## 4.2 加分项 + +- 支持 ONVIF 事件订阅(移动侦测) +- 支持修改 GOP 长度(影响回捞缓存的关键帧间隔) +- 支持创建独立的只读用户(不必用 admin 凭据接入) + +## 4.3 结论落地 + +任何一项必过项失败的型号,直接从采购清单划掉。后期写兼容代码的成本远高于换型号。验证结果整理成表格,作为采购白名单归档。 + +# 5. 阶段产出如何流入生产 + +| 演示阶段产出 | 流向 | +| --- | --- | +| 采购白名单 | 生产方案的硬件选型 | +| 骨架代码(onvif / store / mtx / reconcile) | 直接成为生产代码基础 | +| MiBeeNvr 的设计借鉴(IP 自愈、待激活态) | 写进生产的对账器与状态机 | +| MiBeeNvr 的界面截图 | 干系人沟通材料 | +| MiBeeNvr 本身 | 不进入生产 | + +# 6. 关于 Web 管理界面 + +生产组合(mediamtx + 自研服务)没有现成的管理界面。三个选择: + +1. 前期不做 UI — 用 REST API + 脚本撑过验证期。250 路的开通本来就是批量脚本,不该手点。 +2. 临时用 go2rtc 的 1984 页面看流状态 — 但它默认无密码,局域网内任何人可访问,必须在配置里关闭或用防火墙隔离。 +3. 正式做时长在自研服务上 — 因为要展示的是「家庭 -> 摄像头 -> 流状态」的业务视图,第三方面板展示的是「path 列表」,不是一回事。 + +> 补充:mediamtx 本身没有管理后台,作者明确表示精力有限、优先保证核心稳定,欢迎社区贡献。社区面板有 mediamtx-dashboard、lgcshy/mediamtx-ui、nabaco/mediamtx-ui、bcanfield/mediamtx-connect 等,均为独立进程通过 API 对接,可按需临时使用。 diff --git a/docs/其他项目的文档/跌倒检测视频接入系统 — 线上生产开发方案.md b/docs/其他项目的文档/跌倒检测视频接入系统 — 线上生产开发方案.md new file mode 100644 index 0000000..905d535 --- /dev/null +++ b/docs/其他项目的文档/跌倒检测视频接入系统 — 线上生产开发方案.md @@ -0,0 +1,729 @@ +# 🏗️ 跌倒检测视频接入系统 — 线上生产开发方案 + +> 来源:Notion · https://app.notion.com/p/3af401dc38a681c29985ffdbd0e9f3d1 +> 导出时间:2026-08-01 + +> 版本 v1 · 面向 200 户 / 250 路 IP 摄像头的跌倒检测视频接入系统。本文档描述**生产环境**架构与开发规范。演示与验证阶段见配套文档《演示与验证开发方案》。 + +# 1. 目标与约束 + +| 项 | 值 | +| --- | --- | +| 接入规模 | 约 200 个家庭,250 路 IP 摄像头 | +| 业务目标 | 视频流分析实现摔倒动作检测 | +| 关键约束 | 摄像头位于家庭路由器 NAT 之后,中心机房无法主动访问 | +| 未来扩展 | 毫米波雷达等异构传感器(架构必须预留) | +| 技术栈 | Go 为主,媒体层复用成熟开源组件 | + +# 2. 架构总览 + +系统分为三个平面,接缝要尽可能少。 + +| 平面 | 组件 | 职责 | +| --- | --- | --- | +| 流控制面 | mediamtx + 自研 Go 编排服务 | path 生命周期、按需拉流、录制、对账 | +| 设备业务面 | 自研 Go 服务(单 exe)+ Postgres | 家庭/摄像头台账、凭据、在线状态、开通与停用 | +| 感知面 | 自研推理服务 | 抽帧、姿态模型、跌倒判定 | + +## 2.1 控制面与数据面分离(核心决策) + +这是解决 NAT 问题的关键: + +- **控制面走隧道**:每户边缘盒子跑 WireGuard,中心服务通过隧道访问摄像头的 ONVIF 端口。ONVIF 控制流量极小,只在配置和探活时产生。 +- **数据面走推流**:视频不走隧道。边缘盒子主动把流推到中心 mediamtx(RTSP announce / SRT / RTMP)。 + +### IP 规划 + +每户分配一个不冲突的内网段,摄像头地址确定化: + +``` +10.<户号高位>.<户号低位>.0/24 +摄像头固定从 .10 起分配 +``` + +这样设备地址可预测,不依赖自动发现,也便于对账。 + +# 3. 组件选型 + +| 组件 | 仓库 | 角色 | 说明 | +| --- | --- | --- | --- | +| mediamtx | bluenviron/mediamtx | 媒体路由 | MIT,Go,单二进制。协议互转、按需拉流、录制、鉴权回调、Prometheus | +| gortsplib | bluenviron/gortsplib | RTSP 库 | mediamtx 作者同款。用于自研 worker 拉流并做帧级丢弃 | +| ONVIF 库 | IOTechSystems/onvif 或 use-go/onvif | 设备控制 | 前者是 EdgeX device-onvif-camera 内部使用的增强版 | +| API 客户端 | 自行用 oapi-codegen 从 mediamtx OpenAPI 生成 | mediamtx 控制 | 见 3.1 | +| 边缘推流 | mediamtx 或 go2rtc | 边缘盒子 | 杂牌摄像头兼容性差时用 go2rtc 兜底 | + +## 3.1 关于 mediamtx-sdk-go + +zbiljic/mediamtx-sdk-go 是个人项目,用 ogen 从官方 OpenAPI 规范生成。**建议自行生成客户端**,避免依赖第三方更新节奏: + +```bash +# 从 mediamtx 仓库取 apidocs/openapi.yaml +oapi-codegen -package mtxapi -generate types,client openapi.yaml > mtxapi/client.go +``` + +好处是版本跟随实际部署的 mediamtx,不会因上游停更而受阻。 + +# 4. 关键设计决策 + +## 4.1 mediamtx 只管流,不管设备 + +mediamtx 的数据模型里没有 camera 实体,只有 path(名字 + source URL + 配置)。它不知道 URL 背后是哪个品牌、是否同一台设备的两条码流、这户是否欠费。 + +必须自建设备业务面。三条硬约束: + +1. **API 改的配置不落盘** — mediamtx 重启后通过 API 添加的 path 全部消失。设备台账必须在自己的数据库,服务启动时 replay。 +2. **改配置会踢掉当前发布者** — 任何通过 API 的 path 配置变更都会触发 path reload,断开正在推流的 source,即使改动本身与它无关。因此改配置前必须先判断「真的变了吗」,没变绝对不调 API。 +3. **没有 enable/disable 开关** — 只能删除 path,不能停用。 + +## 4.2 按需拉流(sourceOnDemand) + +```yaml +pathDefaults: + sourceOnDemand: yes + sourceOnDemandStartTimeout: 10s + sourceOnDemandCloseAfter: 30s +``` + +语义:只有 reader 连上来时才去连摄像头,没人看就断开。对本系统等价于「AI worker 订阅 = 开始分析,取消订阅 = 自动停止拉流」,是最省带宽和算力的开关方式。 + +## 4.3 停用某路摄像头的四种粒度 + +按副作用从轻到重: + +| 需求 | 做法 | 效果 | +| --- | --- | --- | +| 暂停分析,流保留 | worker 取消订阅 | 配合 sourceOnDemand 自动停止拉流,随时恢复 | +| 立即断开,允许重连 | `POST /v3/rtspsessions/kick/{id}`(RTMP/SRT/WebRTC 各有对应接口) | 断一次,客户端会重连 | +| 永久拒绝 | 鉴权服务对该 path 返回 401 + kick | 推不上来,且无需改 mediamtx 配置 | +| 彻底移除 | `DELETE /v3/config/paths/delete/{name}` | path 消失,重启后靠自有库恢复 | + +标准组合:先 kick 断开、再让鉴权拒绝 = 立刻生效且不会重连。 + +## 4.4 鉴权用 HTTP 回调 + +```yaml +authMethod: http +authHTTPAddress: http://control-plane:8080/mtx-auth +paths: + "~^home-.*$": + source: publisher +``` + +每次边缘端推流,mediamtx POST 到自有鉴权服务,带 user/password/path/IP/action。返回 200 或 401 即控制了「哪些摄像头允许接入」。注销、欠费、隐私模式全部在自有业务库判断,mediamtx 侧零配置变更。 + +## 4.5 为雷达预留 + +未来接入毫米波雷达后,架构会发生一次反转:雷达 7x24 常开做一级触发,视频做二级确认。 + +- 算力:常态 GPU 占用接近零,只在事件时唤醒推理。250 路常态推理需 2-4 张卡,改为触发式后一张即可。 +- 隐私:卫生间、卧室只放雷达不放摄像头;客厅摄像头平时不解码不存储。这是养老场景能否落地的分水岭。 +- 误报:纯视觉对遮挡/逆光/坐下误报高,纯雷达对快速坐下和弯腰误判,两级串联是行业标准做法。 + +当前阶段的设计要求:设备表结构不要写死为「摄像头表」,预留 device_type;编排服务的触发入口要独立成一个 handler,将来接雷达告警时不用改动流控制逻辑。 + +# 5. 数据模型 + +```sql +-- 家庭 +CREATE TABLE households ( + id BIGSERIAL PRIMARY KEY, + code TEXT UNIQUE NOT NULL, + subnet CIDR NOT NULL, + status TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- 设备(不要命名为 cameras,为雷达预留) +CREATE TABLE devices ( + id BIGSERIAL PRIMARY KEY, + household_id BIGINT NOT NULL REFERENCES households(id), + device_type TEXT NOT NULL, + serial TEXT UNIQUE NOT NULL, + vendor TEXT, + model TEXT, + onvif_addr TEXT, + onvif_user TEXT, + onvif_secret TEXT, + state TEXT NOT NULL, + last_seen_at TIMESTAMPTZ +); + +-- 流绑定 +CREATE TABLE stream_bindings ( + id BIGSERIAL PRIMARY KEY, + device_id BIGINT NOT NULL REFERENCES devices(id), + mtx_instance TEXT NOT NULL, + path_name TEXT NOT NULL, + rtsp_url TEXT, + profile_token TEXT, + enabled BOOLEAN NOT NULL DEFAULT true, + UNIQUE (mtx_instance, path_name) +); + +-- 对账状态 +CREATE TABLE sync_state ( + binding_id BIGINT PRIMARY KEY REFERENCES stream_bindings(id), + desired_hash TEXT, + applied_hash TEXT, + last_error TEXT, + retry_after TIMESTAMPTZ +); +``` + +字段说明:device_type 取 camera / radar / button;state 取 pending_activation / active / disabled;onvif_secret 需加密存储;rtsp_url 存 ONVIF GetStreamUri 返回的子码流地址。 + +**设计要点**:以序列号而非 IP 作为设备身份。家庭 Wi-Fi 漫游或 DHCP 续租会导致 IP 变化,按序列号重定位是唯一可靠的做法。 + +# 6. 对账器(Reconciler) + +系统最容易出竞态的地方,也是自研工作量的核心。 + +## 6.1 职责 + +数据库是唯一真相源,**mediamtx 和 Savant 都是被驱动的从属状态**。对账器持续把三者拉齐。 + +关键原则:**水平触发(level-triggered)而非边沿触发(edge-triggered)**。对账器不处理「事件」,只不断比对期望状态与实际状态并向期望收敛。这是处理部分失败的唯一可行方案(见 6.5)。 + +```go +func (r *Reconciler) Sync(ctx context.Context) error { + desired := r.db.EnabledBindings() // 真相源 + actual := r.mtx.ListPaths(ctx) // 生成的 API 客户端 + + add, del, update := diff(desired, actual) + + sem := semaphore.NewWeighted(8) // 限流,别打爆 API + for _, b := range add { + // 幂等:已存在则跳过,绝不盲目 add(会踢掉 publisher) + // 重试:指数退避,失败写回 sync_state.retry_after + } + for _, b := range update { + // 关键:先比对 desired_hash 与 applied_hash + // 没变就绝对不调 API + } + // del 同理 +} +``` + +## 6.2 必须处理的边界情况 + +| 情况 | 处理 | +| --- | --- | +| mediamtx 重启 | path 数量骤降触发全量重建;也要有定时全量对账兜底 | +| 配置变更踢 publisher | 变更前比对哈希;确需变更时先标记为维护窗口 | +| API 批量失败 | 无事务。第 137 个失败时前 136 个已生效,必须幂等重试而非回滚 | +| 并发打满 | 信号量限流到 8 并发 | +| path 漂移 | 定时全量 diff,处理人工在 mediamtx 侧的手改 | +| 设备离线 vs 无人订阅 | 两者在 mediamtx 侧表现相同,靠自有探活区分 | + +## 6.3 探活 + +独立 goroutine,对每个 active 设备定期做 ONVIF 轻量调用(如 GetSystemDateAndTime),指数退避,更新 devices.last_seen_at。**不要用 path 在线状态代替设备在线状态**。 + +## 6.4 双系统对账:mediamtx + Savant + +引入 Savant(第 9 节)后,一个设备的完整开通需要在**两个独立系统**上各做一次操作,而两者之间**没有分布式事务**。 + +### 数据模型调整 + +第 5 节的 `sync_state` 拆成每系统一行: + +```sql +DROP TABLE sync_state; + +CREATE TABLE sync_state ( + binding_id BIGINT NOT NULL REFERENCES stream_bindings(id), + system TEXT NOT NULL, -- 'mediamtx' | 'savant' + desired_hash TEXT, + applied_hash TEXT, -- NULL = 尚未生效 + last_error TEXT, + attempt INT NOT NULL DEFAULT 0, + retry_after TIMESTAMPTZ, + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + PRIMARY KEY (binding_id, system) +); + +-- 便于运维发现中间态 +CREATE VIEW binding_health AS +SELECT b.id, b.path_name, + bool_and(s.desired_hash = s.applied_hash) AS converged, + max(s.attempt) AS max_attempt +FROM stream_bindings b JOIN sync_state s ON s.binding_id = b.id +WHERE b.enabled +GROUP BY b.id, b.path_name; +``` + +一个 binding 只有当**两行都收敛**时才算真正开通。`converged = false` 的就是中间态,必须在 Grafana 上可见。 + +### 操作顺序(不对称,必须遵守) + +| 动作 | 顺序 | 理由 | +| --- | --- | --- | +| **开通** | mediamtx 先 → Savant 后 | 流必须先存在,Savant 才能挂上去;反之 Savant 会反复重试连不存在的源 | +| **停用** | Savant 先 → mediamtx 后 | 先停消费再断流;反之 Savant 会看到源断开并报错告警 | + +建立和拆除是逆序的,这一点要写进代码注释,很容易被后来人改错。 + +### ⚠️ 关键耦合:Savant source = mediamtx reader + +`sourceOnDemand: yes` 的语义是「有 reader 才拉流」。**Savant 挂上 source 就是一个 reader**,也就是说: + +- 挂载 Savant source → mediamtx 立即开始拉流 → 产生带宽和连接开销 +- 卸载 Savant source → 30s 后(`sourceOnDemandCloseAfter`)mediamtx 自动断流 + +因此:**「暂停分析」必须通过卸载 Savant source 实现,不能只在 Python 插件里 `return`**。后者会让 mediamtx 持续拉流、带宽白烧,而监控上看起来一切正常。 + +这也意味着第 4.3 节「停用粒度」表格里的第一行(暂停分析)具体实现就是卸载 Savant source。 + +## 6.5 部分失败:不回滚,只收敛 + +典型场景:mediamtx path 建好了,Savant source 挂载失败。 + +**错误做法**:回滚删掉 mediamtx path。回滚本身也会失败,那时候你需要回滚的回滚,无限递归。 + +**正确做法**:保留已成功的部分,把失败的那行 `sync_state` 标为未收敛 + 退避重试。下一轮对账会看到 mediamtx 已收敛(跳过)、Savant 未收敛(重试)。 + +代价:**会有一个无人消费的 mediamtx path 短暂存在**。因为 `sourceOnDemand` 的存在,这个 path 没有 reader 就不会拉流,**成本接近零**。这是 `sourceOnDemand` 给双系统对账带来的额外好处——中间态是安全的。 + +### 重试预算 + +| 参数 | 值 | +| --- | --- | +| 初始退避 | 5s | +| 退避倍率 | 2x,上限 5min | +| 告警阈值 | `attempt > 5` 且持续 > 15min → 告警 | +| 放弃阈值 | 不放弃。持续重试,靠告警引入人工 | + +不设放弃阈值是故意的:对账器放弃了,设备就静默地不工作了,而这是养老监护场景不可接受的失败模式。 + +## 6.6 孤儿资源清理 + +对账不只是「把缺的补上」,还要「把多的删掉」。三类孤儿: + +| 类型 | 成因 | 处理 | +| --- | --- | --- | +| mediamtx 有 path,DB 无 | 人工手改、旧版本残留 | 删除(但先日志,不静默) | +| Savant 有 source,DB 无 | 同上 | 卸载 | +| DB 有 binding,两边都无且长期未收敛 | 开通失败后无人处理 | 告警,不自动删 DB | + +**清理动作必须有安全闸**:单轮对账删除量超过总量的 10%(如 25 路)时**停止并告警**,不执行。这道闸是防数据库连接异常或查询 bug 导致的「期望集合意外为空」——那会把 250 路全部拆掉。 + +## 6.7 完整循环 + +```go +func (r *Reconciler) Sync(ctx context.Context) error { + desired := r.db.EnabledBindings() // 真相源 + if len(desired) == 0 && r.lastDesiredCount > 0 { + return ErrSuspiciousEmptyDesired // 6.6 安全闸 + } + + mtxActual, err1 := r.mtx.ListPaths(ctx) // 分片内所有实例 + svActual, err2 := r.savant.ListSources(ctx) + if err1 != nil || err2 != nil { + return errors.Join(err1, err2) // 拿不到实际状态就不动作 + } + + plan := buildPlan(desired, mtxActual, svActual) + // plan 内部已按 6.4 的顺序约束排序: + // teardown: savant 先 → mtx 后 + // setup: mtx 先 → savant 后 + + for _, step := range plan { + if !step.DueForRetry(now) { continue } // 退避窗口未到 + if step.DesiredHash == step.AppliedHash { continue } // 没变,绝不调 API + + sem.Acquire(ctx, 1) // 每系统独立限流 + go func() { + defer sem.Release(1) + if err := step.Apply(ctx); err != nil { + r.db.MarkFailed(step, err) // 退避,不回滚 + return + } + r.db.MarkApplied(step) // applied_hash = desired_hash + }() + } +} +``` + +**两个独立的限流预算**:mediamtx API 和 Savant API 各自 8 并发,不共享信号量。否则一个系统变慢会饿死另一个。 + +**循环周期**:正常 30s;有未收敛项时降到 5s;另有每 10min 一次的全量对账(含孤儿扫描)。 + +## 6.8 必须暴露的指标 + +| 指标 | 用途 | +| --- | --- | +| `reconciler_unconverged_bindings{system}` | 核心健康指标,持续 > 0 即异常 | +| `reconciler_apply_duration_seconds{system,action}` | 发现 API 变慢 | +| `reconciler_orphans_detected{type}` | 有人手改了生产系统 | +| `reconciler_safety_brake_triggered` | 安全闸触发,**应该直接呼叫**人 | +| `reconciler_loop_duration_seconds` | 循环超时 = 规模到顶,该分片了 | + +# 7. mediamtx 配置要点 + +```yaml +api: yes +apiAddress: :9997 + +metrics: yes +metricsAddress: :9998 + +authMethod: http +authHTTPAddress: http://control-plane:8080/mtx-auth + +pathDefaults: + sourceOnDemand: yes + sourceOnDemandCloseAfter: 30s + rtspTransport: tcp # 家庭网络下 UDP 丢包严重 + record: no + recordDeleteAfter: 7d + runOnOnline: curl -s -X POST http://control-plane:8080/hooks/online?path=$MTX_PATH + runOnOffline: curl -s -X POST http://control-plane:8080/hooks/offline?path=$MTX_PATH + +paths: + "~^home-(\\d+)-cam(\\d+)$": + source: publisher +``` + +用一条正则 path 兜底,不要写 250 条静态配置。 + +> 注意:v1.19.x 的钩子命名为 runOnAvailable / runOnUnavailable / runOnOnline / runOnOffline,与旧版 runOnReady / runOnNotReady 不同。部署前核对所用版本的配置参考。 + +# 8. 容量与分片 + +## 8.1 带宽 + +| 项 | 估算 | +| --- | --- | +| 250 路子码流 640x360 @ 512 kbps | 约 125 Mbps 常态入向 | +| 250 路主码流 | 1-1.5 Gbps(不可行,必须用子码流做检测) | + +## 8.2 算力 + +- 抽帧到 5 fps:250 x 5 = 1250 帧/秒解码 +- CPU 软解不可行,需 NVDEC 或边缘解码 +- 用 gortsplib 在 Go 侧做帧级丢弃(只保留关键帧/按抽帧间隔),比起用 ffmpeg 子进程可省约 70% 解码开销 + +## 8.3 分片 + +- 单 mediamtx 实例建议承载 60-80 路,250 路分 3-4 个实例 +- 分片键用家庭 ID,映射存在 stream_bindings.mtx_instance +- 不要单实例硬扛:进程挂掉影响面过大,且默认 writeQueueSize: 512 下 250 路的内存与 GC 压力不小 + +# 9. 感知面:Savant 推理流水线 + +> 本节取代第 2 节表格中「感知面 = 自研推理服务」的占位描述。 + +## 9.1 为什么用框架而非完全自研 + +开源社区没有「批量摄像头摔倒检测」的成品(算法是差异化,不会有人开源),但有成熟的**多路视频分析流水线框架**。算法是自己的,工程化用现成的。 + +选定 **Savant**(insight-platform/Savant),基于 NVIDIA DeepStream 的高层框架,声明式 YAML + Python 插件函数。三个决定性理由: + +1. **故障隔离** — 原生 DeepStream 中数据流与 pipeline 紧耦合,任一源崩溃则整个 pipeline 崩溃,重启加载模型要数秒且期间丢数据。Savant 的 adapter 通过 ZeroMQ 与框架通信,adapter 挂掉不影响 pipeline。200 户家庭网络掉线是常态,**这是 250 路规模的生死线**。 +2. **可配置降级策略** — 可设为实时模式(算力不足时主动丢数据)或高吞吐模式(保证处理全部数据)。本系统选**实时模式**,丢帧优于积压崩溃。 +3. **Replay(按需与非线性处理)** — REST 控制的录制/回放/转推服务,直接对应阶段二的雷达触发式二级确认。 + +同一套代码可跑在 Jetson(Orin Nano/NX/AGX)和数据中心 GPU 上,与本方案的边缘/中心混合部署契合。 + +**代价**:必须 NVIDIA 硬件(Turing/Ampere/Ada/Hopper/Blackwell 或 Jetson),与 DeepStream 版本强绑定(需查对照表),学习曲线陡,无企业支持。 + +**备选**:Pipeless(Rust,不绑 NVIDIA,支持 ONNX Runtime/OpenVINO/TensorRT,HTTP 动态增删流)——若硬件选型变更或规模缩小时启用。 + +## 9.2 模型选型与许可警告 + +> ⚠️ **Ultralytics YOLO 是 AGPL-3.0**。YOLOv8/v11 系列仓库默认对所有用户采用 AGPL-3.0;要集成进商业产品和服务而不遵守其开源要求,必须购买 Enterprise License。本项目是面向付费用户的网络服务,落在 AGPL 第 13 条覆盖范围内,严格执行则需开源整个服务端。**必须在 M3 前做出决定:买授权,或换模型。** + +推荐替代:**RTMPose / RTMO**(MMPose 项目,Apache-2.0) + +- RTMPose-m:COCO 75.8% AP,GTX 1660 Ti + TensorRT 下 430+ FPS,Intel i7-11700 CPU + ONNXRuntime 下 90+ FPS +- 支持 ONNXRuntime、TensorRT、ncnn、OpenVINO、RKNN 多种部署后端(通过 MMDeploy) +- 关键点定义同为 COCO 17 点,**现有判定算法基本不用改** +- 430 FPS 的单卡吞吐意味着第 8 节估算的 1250 fps 需求可用 2-3 张中端卡满足 + +## 9.3 Pipeline 架构 + +``` +边缘推流 → mediamtx(250 路汇聚,按需拉流) + ↓ RTSP + Savant RTSP source adapter(多路复用 + 故障隔离) + ↓ ZeroMQ / Protobuf + Buffer Adapter(磁盘持久化缓冲,抗突发) + ↓ + Savant module + ├─ RTMPose / TensorRT(姿态关键点) + └─ Python 插件:时序窗口 + 跌倒判定(**自研部分**) + ↓ ZeroMQ / Protobuf + Go 控制面(告警、去重、防抖、通知) +``` + +需要自己写的只有中间那个 Python 插件,前后两段是现成的。 + +## 9.4 与 Go 控制面的对接契约 + +一条 pipeline 是自给自足的服务,通过高性能流式 API 与外界通信,视频和元数据统一使用 Protocol Buffers 协议。 + +| 需求 | 通道 | 语言无关 | +| --- | --- | --- | +| 接收检测结果 | 直接订阅 sink 的 ZeroMQ 端点,解 protobuf | ✅ | +| 动态增删流 | 动态挂载/卸载 source 与 sink,无需重载 pipeline | ✅ | +| 回放/回捞 | Replay 服务的 REST 接口 | ✅ | +| 程序化灌数据/调试 | Client SDK(**仅 Python**,集成 OpenTelemetry) | ❌ | + +**设计约束:优先走 ZeroMQ 和 REST 两条语言无关通道,Go 服务不得依赖 Python 中间层。** Client SDK 仅用于算法调试和测试。 + +因为两边都是 API 驱动,**第 6 节的对账器可以同时驱动 mediamtx 和 Savant**:开通 = 建 path + 挂 source;停用 = kick + 卸 source。逻辑写在同一个 reconciler 里。 + +## 9.5 关键配置决策 + +**必须开启 Buffer Adapter**。它实现了 adapter 与 module 之间的持久化事务性磁盘缓冲,用于支撑资源消耗不可预测的 pipeline、抗住流量突发,并把自身条目数和大小导出到 Prometheus。 + +> 本场景天然有突发:夜里安静无事,早上起床时段几十户同时有活动。**一开始就加上,别等出问题再补。** + +**AO-RTSP Sink 仅用于调试**。它把处理后、带元数据标注的视频通过 RTSP 和 LL-HLS 播出去,用来直观确认某一路的处理效果。 + +> ⚠️ **硬件限制**:GeForce 系列显卡同时硬件编码的流数上限为 **3**。若给多路挂 AO-RTSP 可视化,消费级卡直接卡在 3 路,需 Quadro/Tesla/L4 类专业卡。**不要做成常态功能。** + +**开发期开 Development Server**:支持改代码动态重载、不重启 pipeline,还能从 IDE 远程开发跑在 Jetson 或服务器上的代码。原生 DeepStream 改一行要重启加载模型等几十秒,这个工具能省大量时间。 + +## 9.6 可观测 + +Savant 没有管理界面(与 mediamtx 同构),但可观测完整: + +- **Prometheus + Grafana**:pipeline 和 buffer adapter 导出运行指标,开发者可声明自定义指标与系统指标一起导出。**把每路的人体检出率、跌倒候选数、推理延迟声明成自定义指标** → Grafana 上就是现成运营看板 +- **OpenTelemetry → Jaeger**:排查单帧处理延迟 +- mediamtx 也出 Prometheus 指标,**一个 Grafana 全收**,前期不用写一行前端代码 + +## 9.7 算法分层 + +| 层 | 内容 | 位置 | +| --- | --- | --- | +| 姿态提取 | RTMPose,COCO 17 关键点 | Savant 推理单元 | +| 单帧启发式 | 包围框宽高比、重心高度、躯干角度 | Python 插件(现有算法) | +| 时序判定 | 30 帧滑窗,静态位置 + 动态速度特征 | Python 插件(待建) | +| 事件后处理 | 去重、防抖、多源融合 | Go 控制面 | + +参考实现: + +- `GajuuzZ/Human-Falling-Detect-Tracks` — ST-GCN 对每个人轨迹的每 30 帧预测动作,支持站立/行走/坐下/躺下/起身/坐下动作/跌倒共 7 类。**七分类而非二分类是降误报的关键** +- `kelsonbatista/fall-detection-ai-project` — YOLO-pose 逐帧抽 128 维特征(静态位置 + 动态速度),30 帧窗口喂 LSTM。注意 GPL-3.0 + +**负面经验**:`Y-B-Class-Projects/Human-Fall-Detection` 用约 500 张网络图片训 LSTM 完全学不会,最后退回 if-else 规则。**小数据量下时序模型学不动,先解决数据再上模型。** + +## 9.8 本节风险 + +| 风险 | 缓解 | +| --- | --- | +| Ultralytics AGPL-3.0 污染商业代码 | M3 前决策:买授权或换 RTMPose | +| 公开数据集与真实场景脱节(Le2i/UR Fall 均为演员摆拍的表演性跌倒,真实老人常为缓慢滑落、部分遮挡) | M3 首要产出是**真实误报案例库**,不是模型指标 | +| Savant 学习曲线陡 | M1 阶段先用 5 路跑通一条 pipeline,测出单路真实吞吐再反推卡数 | +| 绑定 NVIDIA + DeepStream 版本 | 预留 Pipeless 作为备选;推理代码与框架解耦 | +| 常态全量推理成本 | **Savant 不降低算力需求**,只解决「怎么高效跑 250 路」。雷达一级触发仍是唯一的数量级优化,与 Replay 配套使用 | + +**无雷达时的过渡方案**:参考 Frigate 的「先用运动侦测过滤、有变化才跑推理」思路,与雷达触发是同一逻辑。 + +# 10. 预警信息管理系统 + +> 接住第 9 节的输出:检测出来之后怎么办。**本节描述的是产品本身,不是辅助设施。** + +## 10.1 两类告警必须彻底分开 + +| | 运维告警 | 业务预警 | +| --- | --- | --- | +| 触发源 | 设备离线、binding 未收敛、GPU 过载、缓冲积压 | 跌倒判定 | +| 收件人 | 运维团队 | 家属、护理员、呼叫中心 | +| 延迟容忍 | 分钟级 | **秒级** | +| 漏报后果 | 服务降级 | 人身伤害 + 法律责任 | +| 方案 | Prometheus + Alertmanager(现成) | **自研状态机 + 复用投递通道** | + +运维那侧接 Alertmanager 就完事,一两天工作量,本节不再展开。**两套系统不得共用投递通道和值班配置**,避免运维噪声淡化业务预警。 + +## 10.2 告警生命周期状态机 + +``` +[触发] ─→ [投递中] ─→ [已送达] ─→ [已确认 ack] ─→ [处置中] ─→ [关闭] + │ │ │ │ + │ │ └─超时未 ack─→ [升级] ─→ 下一级投递 │ + │ └─投递失败─→ [升级] │ + └─防抖命中─→ [抑制] [标记误报] ←─┘ +``` + +**没有 ack 环节的告警系统等于没有。** 发出去和被人看到是两回事,整个设计围绕「确认或升级」这一对概念展开。 + +## 10.3 分级升级链 + +``` +家属 App 推送 + │ 30s 无 ack + ▼ +家属短信 + 语音呼叫 + │ 60s 无 ack + ▼ +备用联系人(邻居/物业/护理员) + │ 90s 无 ack + ▼ +呼叫中心人工介入 +``` + +每户的联系人链、时段策略(白天找子女、夜间找同住者)、优先级都要可配。升级超时值需支持按户覆盖——独居老人和有同住家属的超时策略应该不同。 + +## 10.4 数据模型 + +```sql +CREATE TABLE alerts ( + id BIGSERIAL PRIMARY KEY, + household_id BIGINT NOT NULL REFERENCES households(id), + device_id BIGINT REFERENCES devices(id), + kind TEXT NOT NULL, -- 'fall' | 'no_motion' | 'device_offline_business' + confidence REAL, + triggered_at TIMESTAMPTZ NOT NULL, + state TEXT NOT NULL, -- triggered/delivering/delivered/acked/handling/closed/suppressed + acked_by BIGINT, + acked_at TIMESTAMPTZ, + closed_at TIMESTAMPTZ, + outcome TEXT, -- true_positive / false_positive / unknown + evidence_uri TEXT, -- 回捞的视频片段 + 姿态序列 + escalation_level INT NOT NULL DEFAULT 0 +); + +CREATE TABLE contact_chains ( + id BIGSERIAL PRIMARY KEY, + household_id BIGINT NOT NULL REFERENCES households(id), + level INT NOT NULL, -- 0,1,2,3 + contact_name TEXT NOT NULL, + channels JSONB NOT NULL, -- [{type:'push',token:...},{type:'sms',no:...}] + active_hours TEXT, -- '08:00-22:00' 或 NULL 表示全天 + timeout_sec INT NOT NULL DEFAULT 30, + UNIQUE (household_id, level) +); + +CREATE TABLE alert_deliveries ( + id BIGSERIAL PRIMARY KEY, + alert_id BIGINT NOT NULL REFERENCES alerts(id), + level INT NOT NULL, + channel TEXT NOT NULL, -- push / sms / voice / webhook + provider TEXT NOT NULL, -- 便于多供应商冗余 + sent_at TIMESTAMPTZ, + receipt_at TIMESTAMPTZ, -- 回执:NULL = 未确认送达 + failed_reason TEXT +); + +CREATE INDEX ON alerts (state) WHERE state NOT IN ('closed','suppressed'); +``` + +关键设计:**投递是独立实体而非 alerts 的字段**。一次告警会产生多条投递记录(多级×多通道),每条各自有回执状态。 + +## 10.5 投递回执:三个不同的事实 + +| 事实 | 含义 | 字段 | +| --- | --- | --- | +| 发出去了 | 调用了上游 API | `sent_at` | +| 送达了 | 上游回执确认到达终端 | `receipt_at` | +| 被看到了 | 用户主动 ack | `alerts.acked_at` | + +**没有回执就当失败并升级**,不要乐观假设。推送可能被系统杀掉、短信可能延迟、电话可能占线。 + +## 10.6 通道选型与冗余 + +**必须至少两条独立路径,且电话通道要能绕过互联网。** 推送依赖 APNs/FCM,短信依赖运营商,任何单一通道都可能整体故障。 + +| 层 | 选型 | 说明 | +| --- | --- | --- | +| 状态机、升级链、ack、审计 | **自研**(Go) | 业务定制度高,通用工具都要大改 | +| 多通道投递 | Novu / Apprise / ntfy,或直接对接云厂商 | 只解决「怎么发」 | +| 短信 + 语音呼叫 | 云厂商(双供应商) | `provider` 字段就是为切换准备的 | + +> ⚠️ **不要用 Grafana OnCall**。OSS 版已于 2026-03-24 归档,仓库只读,仅修 CVSS 7.0+ 漏洞。更关键的是移动推送、短信和电话在归档当日停止工作(那些通道依赖 Grafana Cloud 中转)。调度和升级链还能跑,但**送不出去的告警系统等于没有**。 + +若阶段二引入 ThingsBoard(接雷达时),其告警子系统 + 规则链可覆盖状态机的一大半,届时自研部分可收缩到只做升级链和电话对接。 + +## 10.7 去重与防抖 + +| 规则 | 参数 | 理由 | +| --- | --- | --- | +| 同设备冷却期 | 5min 内不重复告警 | 避免同一事件多次触发 | +| 同家庭聚合 | 多设备同时触发合并为一条 | 客厅两个机位拍到同一次跌倒 | +| 已处置抑制 | 已 ack 的告警期间不再新建 | 护理员已到现场 | +| 维护窗口 | 住户主动静默(如家庭聚会) | **需限时且到期自动恢复** | + +静默功能必须强制限时(建议最长 4h)并在到期前提醒。永久静默是这类系统最常见的事故成因。 + +## 10.8 误报反馈闭环(产品飞轮) + +家属点「误报」这个动作,要自动把 `evidence_uri` 对应的视频片段和姿态序列推进标注队列。 + +这条链路直接解决 9.8 节的数据集问题:公开数据集全是演员摆拍,而**真实误报案例是你最稀缺的训练数据**。没有这个闭环,模型不会随运营时间变好。 + +同理,`outcome = true_positive` 的样本是正例金矿。**从 M3 第一天就要存,不要等到想训模型时才发现没数据。** + +## 10.9 审计与合规 + +每条告警的完整生命周期需**不可篡改地**留存: + +- 什么时间触发、基于什么证据 +- 向谁发过、走的什么通道、是否送达 +- 谁在什么时间确认、处置结果 +- 升级到第几级、每级耗时 + +一旦出事,「系统有没有报、几点报的、谁接的、多久响应」是法律问题而非技术问题。建议 `alerts` 和 `alert_deliveries` **只追加不修改**(状态变更写事件表),并独立备份。 + +另需确认的合规项(开工前法务确认):视频证据的保存期限、家属查看权限范围、被监护者本人的知情同意。 + +## 10.10 SLA 目标(待 M3 实测校准) + +| 指标 | 目标 | +| --- | --- | +| 跌倒发生 → 首次投递 | < 10s | +| 首次投递 → 送达回执 | < 5s(推送)/ < 30s(短信) | +| 全链路无人响应 → 呼叫中心 | < 3min | +| 告警丢失率 | 0(持久化后才确认触发) | + +最后一行是硬约束:**告警先落库再投递**。进程崩溃后重启,未完成的升级链要能从数据库恢复继续——这跟第 6 节对账器的「水平触发」是同一个思路,启动时扫描所有未终态告警并接续处理。 + +# 11. EdgeX 升级路径(可选,阶段二) + +当前阶段不引入 EdgeX。理由:它白送的能力(设备注册表、探活、命令抽象)约值一两周开发量,但代价是长期多背 4 个服务、跨服务追日志,且它假设设备在同一局域网、与 NAT 场景冲突。 + +## 何时值得切换 + +当确定要接入毫米波雷达、门磁、紧急按钮等异构设备时。EdgeX 的 device-mqtt / device-modbus / device-snmp 能复用同一套设备模型,这是它唯一压倒性的优势。 + +## 切换成本低的原因 + +ONVIF 调用那部分代码两边几乎一样(EdgeX 的 device-onvif-camera 内部就是用 IOTechSystems/onvif),对账器也不用改。 + +## 若切换,最小服务集 + +``` +core-metadata 设备注册表 +core-command 命令下发入口 +device-onvif-camera ONVIF <-> REST +redis 消息总线 +(consul 可用 -cp=none 省掉) +``` + +砍掉 core-data、app-service-*、security 全套。注意 discovery 在本场景基本不可用:netscan 对 200 个 /24 段是 5 万次探测,不现实;应使用预定义 deviceList 或 REST 批量录入,但保留 EnableStatusCheck / CheckStatusInterval 做探活。 + +另注意:EdgeX 3.x 起 API 路径是 /api/v3,网上大量 2.x 文档写的是 /api/v2。 + +# 12. 风险清单 + +| 风险 | 影响 | 缓解 | +| --- | --- | --- | +| 摄像头 ONVIF 实现差异 | 部分型号无法接入 | 采购前用真机验证,见演示方案文档 | +| 摄像头时钟漂移 | 事件时间戳全乱 | 开通时调 SetSystemDateAndTime,定期校时 | +| NAT 隧道不稳 | 控制面失联 | 控制面失联不影响视频推流(已解耦);告警但不阻断 | +| path reload 踢流 | 用户看到卡顿 | 配置变更前比对哈希;变更集中在维护窗口 | +| H.265 兼容性 | 浏览器无法预览 | 摄像头统一设为 H.264 | +| 隐私合规 | 项目无法落地 | 尽早引入雷达做一级触发,减少视频常态采集 | + +# 13. 里程碑 + +| 阶段 | 内容 | 出口标准 | +| --- | --- | --- | +| M0 | 摄像头兼容性验证 | 3-5 个候选型号跑通 GetProfiles / GetStreamUri / SetSystemDateAndTime | +| M1 | 单 exe 骨架 + mediamtx | 5 路摄像头自动建 path、探活、断线重建 | +| M2 | 对账器 + Postgres + 隧道 | 10 户试点,含开通/停用全流程 | +| M3 | 抽帧 worker + 推理接入 | 端到端跌倒告警跑通,拿到误报率基线 | +| M4 | 分片 + 灰度扩容 | 250 路稳定运行 | +| M5(阶段二) | 雷达接入 + 两级判定 | 误报率显著下降,视频常态采集下降 |