A: PoseAdapter.set_confidence_threshold is applied on start, so the
settings model-confidence field actually affects inference.
B: config source.mode ('stream'|'replay') is explicit; app no longer
guesses the source type from the URL prefix.
C: FallStateMachine takes a session_id and from_config generates a
unique one per run, so event ids never collide across restarts
(no screenshot overwrite or duplicate JSONL identity in a day).
51 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.7 KiB
5.7 KiB
本地模块与事件合约
Silver Pose 没有后端 API;本文定义 V1/V2 必须保持一致的本地配置、数据和事件合约。
配置合约
v1/config.example.json 的最小形状:
{
"source": {
"id": "lobby-camera-01",
"rtsp_url_env": "SILVER_POSE_RTSP_URL",
"mode": "stream",
"roi_normalized": [0.0, 0.0, 1.0, 1.0]
},
"model": {
"path": "models/best.pt",
"sha256": "0000000000000000000000000000000000000000000000000000000000000000",
"confidence_threshold": 0.25
},
"event": {
"keypoint_confidence_threshold": 0.4,
"suspect_window_seconds": 0.5,
"confirm_window_seconds": 1.0,
"recovery_window_seconds": 2.0,
"cooldown_seconds": 10.0
},
"artifacts": {
"event_dir": "../artifacts/events"
}
}
rtsp_url_env必填;应用从同名环境变量读取真实 URL。source.mode显式声明来源类型,取值stream(默认,实时 RTSP,允许有界重连)或replay(本地录像,EOF 不重放);由配置决定,不再按 URL 前缀猜测。model.confidence_threshold是模型检测置信度,可在设置草稿中调整,并在下次开始监控时经PoseAdapter.set_confidence_threshold真正生效。- 数值是待现场录像校准的默认值;每个值必须真正进入事件逻辑:
keypoint_confidence_threshold决定姿态质量门槛;suspect_window_seconds限制快速下移到水平姿态的最大间隔;confirm_window_seconds是水平倒地候选需持续的确认时间;recovery_window_seconds是恢复姿态需持续的时间;cooldown_seconds是确认事件后允许开始恢复判断前的最短等待时间。示例中的全零 SHA-256 只占位配置形状,T-103 必须以受控模型的真实哈希替换并验证后才能启动推理。 - 缺少环境变量、模型不存在或哈希不符时,应用显示配置错误,不启动监控。
核心数据
Keypoint = { x: float, y: float, confidence: float }
PersonPose = {
box_xyxy: [float, float, float, float],
box_confidence: float,
keypoints: Keypoint[17]
}
TrackedPersonPose = {
track_id: string,
detected_at_monotonic: float,
pose: PersonPose
}
PoseQuality = {
accepted: bool,
reason: string,
visible_joint_count: int
}
PoseEvidence = {
accepted: bool,
horizontal_pose: bool,
rapid_vertical_change: bool,
horizontal_angle_degrees: float | null,
hip_center_y: float | null,
torso_length: float | null,
reason: string
}
FallEvent = {
event_id: string,
track_id: string,
config_version: string,
suspected_at_monotonic: float,
confirmed_at_monotonic: float,
latency_seconds: float,
state: "CONFIRMED"
}
FallEvent 是状态机的纯内存确认事件,只在状态首次进入 CONFIRMED 时创建一次。连续帧更新 UI 状态,但不重复创建事件。config_version 是由运行配置快照计算的非敏感版本标识;T-202 的 alerts 会在不改变事件幂等语义的前提下,为截图/JSONL 记录补充来源、UTC 时间和证据。
event_id 带每次监控运行的会话前缀(FALL-<session>-NNNNNN),跨监控重启全局唯一;因此同一天目录内的截图不会被覆盖,events.jsonl 也不会出现同 id 不同内容的记录。
T-202 的 alerts 按 event_id 去重,对首次 CONFIRMED 只触发一次副作用(声音、弹窗、截图、JSONL 一行)。JSONL 记录如下,文件名与字段不含 RTSP 地址、凭证或客户姓名:
AlertRecord(JSONL) = {
event_id: string,
track_id: string,
config_version: string,
source_id: string,
state: "CONFIRMED",
confirmed_at_utc: string, # ISO-8601 UTC
suspected_at_monotonic: float,
confirmed_at_monotonic: float,
latency_seconds: float,
screenshot: string # 相对 event_dir 的 "YYYYMMDD/FALL-xxxxxx.png"
}
PersonPose 是 T-103 的纯模型输出,不带人员 ID;T-104 的跟踪模块产生 TrackedPersonPose 后,才允许事件证据按人员连续积累。
视频来源帧合约
FramePacket = {
image: ndarray | null,
timestamp_monotonic: float,
status: "connected" | "retrying" | "error" | "eof" | "closed",
error: string | null
}
SourceMode.REPLAY的录像优先使用容器时间戳;首帧时间戳无效或倒退时,回退为帧序号/FPS,保证回放时间单调;到达 EOF 后保持 EOF,不重放。SourceMode.STREAM的实时流以成功读帧时的单调时钟计时;读取失败进入有界指数退避重连,不使用CAP_PROP_POS_MSEC作为事件时间。retrying、error、eof和closed都没有图像,且绝不伪造人员、姿态或摔倒事件。- 可重连来源以有界指数退避重新打开;断流不推进状态机的证据时间。
事件合约
| 事件 | 触发者 | 负载 | 结果 |
|---|---|---|---|
source.connected |
视频源 | source_id、时间 |
UI 显示在线。 |
source.error |
视频源 | source_id、错误码、可重试标记 |
UI 显示异常;不报警。 |
person.updated |
跟踪与 Pose | TrackedPersonPose、PoseQuality、PoseEvidence |
UI 绘制骨架与 ID。 |
fall.suspected |
状态机 | track_id、开始时间 |
UI 显示黄色疑似状态。 |
fall.confirmed |
状态机 | FallEvent |
红色叠加、声音、弹窗、截图、JSONL。 |
fall.recovered |
状态机 | track_id、时间 |
UI 恢复绿色正常状态。 |
副作用边界
- 仅
alerts模块可写事件截图、播放声音、弹窗和追加 JSONL。 - 仅
video_source模块读 RTSP 或本地视频。 - 仅
pose模块加载模型;UI 不得直接调用模型。 - V2 可以改变实现语言,但不得改变配置含义、
FallEvent字段和事件幂等语义。