采集循环只认新帧,选区统计移到数值卡,状态栏改为会过期的通知行 - What: 1) 选区统计不再挤在曲线下方(中文长句会顶出面板右边界)——框选后三个数值卡的大号数字变成该窗口的峰值、下方小字为该窗口的均值·σ(chart.selection_detail),峰值语义放在卡片 tooltip;曲线下方只保留「选区 4.00s / 121 点」(完整一行留在它的 tooltip 里)。「全程总览」不算选区(那是默认适配),此时卡片回到"游标处样本";切换单位/换数据源/返回最新都会正确切回。2) 上方的 63.0 FPS 是采集循环自己的轮询速率,根因是同一帧被反复当成新样本:CaptureSource.read_latest 返回后台线程的最新帧,没有新帧时把同一帧再给一次,循环未做判断,于是 30 fps 的设备录出约 63 行/秒(frame_id 还各不相同)。新增 _capture_sequence():读 capture_source.status.last_seq,计数未变即"还没有新帧",睡 2 ms 重试、不进处理链、不发 packet;只有带后台读取线程的实时相机有该标记,视频文件是同步解码、每次都取下一帧,因此忽略(行为不变)。packet.fps 改为速率 (窗口帧数-1)/(首末到达差),窗口 1 s——按"1 秒内到达几帧"数数会系统性偏大。容器速率仍取传感器配置值,与此层无关。3) 底部状态栏只留一次性通知:删除右下角常驻「配置 sensor.json」(顶栏摘要按钮已写明),删除与顶栏重复的 status.run_started(运行中(0))与 status.connecting(正在连接…);QStatusBar 换成 _NoticeBar——Qt 的 showMessage(text) 默认 msecs=0 表示永不消失,这正是那句「运行中(0)」一直挂到底部的原因,子类统一补 6 秒超时;status.units_raw_fallback 之前每帧都发(等于永久占住通知行),改为每次运行只提示一次。附带修复:英文下表头模式名被截断(长句移入 tooltip,标签只留模式名 chart.mode_*_tip);数据源切换后曲线下方的选区文案会残留,改为在 _on_source_changed 里清零。 - Why: 用户评审 3 点——选区统计显示不全、顶部 fps 应受硬件限制最大 30、右下角配置文件名与左下角「运行中(0)」和顶栏信息重复。第 2 点查出根因是重复采样(数据缺陷,不只是显示问题),修掉后设备速率与记录行数才真正一致。 - Verification: ui_check.py 546 项 0 失败(新增:选区统计在卡片、卡片小字不裁切、状态栏无常驻标签且消息过期、顶栏 FPS 即 packet 交付速率、表头模式名中英不裁切);test_ui_smoke.py 71 项 0 失败(新增选区读数与通知行过期两例);drive_shared.py 148 项 0 失败(新增 shared/tests/test_worker_frame_rate.py:同一帧返回 3 次时 packet 数≈交付帧数,去掉判据为 11317:39,且 fps 与交付速率一致);locale_studio.py 267 键齐全、zh==en、prefs 对齐;reproduce.py 退出 0;ui_snapshot.py 新增 --select A,B,六张复核图(选区中英、实时、录制中、回放、标定 1280x720)。 - Not in this change: 录制文件格式与 CSV 列序不变;去重只影响"哪些帧算一帧",视频文件源行为不变;曲线下方不再显示三通道峰/均/σ(数值在卡片上,完整一行在 tooltip 里)。
This commit is contained in:
@@ -52,6 +52,25 @@ UI 线程 _on_run_tick ──> 曲线显示(仍可降帧)
|
||||
控件更新必须留在 UI 线程。
|
||||
- **sink 是普通 callable 列表**,某个 sink 抛异常会被记录并只影响该 packet,不会杀掉采集。
|
||||
|
||||
### 2.1 采集循环只认「新帧」,并据此定义帧率
|
||||
|
||||
`CaptureSource.read_latest` 返回的是**后台读取线程的最新一帧**:如果那一刻还没有新帧,
|
||||
它把**同一帧**再给一次。采集循环以前不做判断,于是同一帧被反复处理 —— 30 fps 的设备
|
||||
录出了 63 行/秒(且 `frame_id` 各不相同,看起来像真的),顶栏的 `packet.fps` 显示的是
|
||||
**我们自己的轮询速率**。
|
||||
|
||||
- `_capture_sequence(sensor)` 读 `capture_source.status.last_seq`:计数没变就是"还没有新
|
||||
帧",睡 `CAPTURE_REPEAT_POLL_S`(2 ms)后重试,**不进处理链、不发 packet**。
|
||||
只有带后台读取线程的源才有这个标记;视频文件是同步 `read_raw()`,每次调用都解码下一
|
||||
帧,因此它的计数与"重复"无关,直接忽略(返回 `None`,行为不变)。
|
||||
- `packet.fps` 改为**速率**:`(窗口内帧数 - 1) / (首末到达时刻之差)`,窗口 `FPS_WINDOW_S`
|
||||
= 1 s。按"1 秒内到达了多少帧"数数会系统性地偏大(窗口里正好装一秒,抖动就多算一帧)。
|
||||
- 这一层是"哪些帧算一帧"的唯一判据,记录、曲线、标定与 CSV 都跟着它走;它不改动
|
||||
`packet.fps` 的语义之外的东西 —— **容器速率**仍然是传感器配置值(见 §6.1)。
|
||||
|
||||
`shared/tests/test_worker_frame_rate.py` 用一个假 SDK 驱动真实循环验证:同一帧返回 3 次
|
||||
时,packet 数 ≈ 交付帧数(去掉判据后是 11317 : 39),且 `packet.fps` 与交付速率一致。
|
||||
|
||||
### 关键文件
|
||||
|
||||
| 文件 | 职责 |
|
||||
@@ -161,9 +180,9 @@ outputs/recordings/<UTC 时间戳>_session<NNNN>/
|
||||
|
||||
`session.json` 的 `video.fps` 是**容器速率**,必须等于传感器的采集速率,取自传感器配置
|
||||
(`config/sensors/<type>/sensor.json` 的 `fps`,box 为 30),**不是** packet 里那个
|
||||
`fps` 字段 —— 后者是"管线每秒完成多少帧"的计数器,开流瞬间的缓冲突发或视频文件源会让
|
||||
它读到 48,用它写 30 fps 的容器就等于把回放播成了快进。这是"传感器 30 fps、录制结果
|
||||
48 fps"的直接原因。
|
||||
`fps` 字段 —— 后者是"本机每秒处理了多少帧"的实时速率(见 §2.1),开流瞬间的缓冲突发会
|
||||
让它高于配置值,用它写 30 fps 的容器就等于把回放播成了快进。这是"传感器 30 fps、录制
|
||||
结果 48 fps"的直接原因。
|
||||
|
||||
- `StudioMainWindow._video_fps()`:实时相机 → 传感器配置的 `fps`;视频文件源 → 实测值;
|
||||
实时相机的实测值另存 `video.measured_fps`,两者都进 `session.json`,对不上时看得见。
|
||||
|
||||
Reference in New Issue
Block a user