CommonLibraries/docs/控制与状态说明.md

127 lines
13 KiB
Markdown
Raw Permalink Normal View History

# 控制与状态说明
实现依据为需求审查稿及用户批准的 A~E 建议。模块保留附件控制器,历史预测、时间初始化、恢复预算和异常输入处理是补充实现。
## 历史与预测
每张接收的新帧保存采集时间、可选目标灰度、全局灰度及其有效性,并绑定真实曝光、工况和配置版本。历史不保存图像。帧诊断记录与有效目标记录分别保留,避免大量无目标帧挤掉仍在窗口内的最近 3/5 条目标记录。两种队列都受时间窗及 `history_capacity` 限制。
`valid_history_count` 是当前缓存中位于有效窗口内的可用目标记录数,不是全天总计;`prediction.points_used` 是本次实际用于拟合的数量。窗口边界包含恰好等于窗口长度的记录,超过才剔除。3/5 点不足只降级预测,不初始化、不补拍。
仅当调用方确认增益、响应曲线稳定,灰度处于近似线性区且黑电平 B 已知时采用:
```text
q_i = (G_i - B) / E_i
b = Σ[(t_i-平均t)(q_i-平均q)] / Σ[(t_i-平均t)²]
q_next = q_current + b × (t_next-t_current)
G_predicted = B + E_actual_current × q_next
```
E 单位 us,时间差在拟合内部换算为秒。用实际采集时间拟合,不使用帧序号;先计算整数时间差,再转浮点,避免大纪元时间损失精度。外推锚定当前测量,历史只提供斜率。这里的 q 是目标光度响应代理量,不能将反射率变化与环境光变化解耦。
预测时刻优先取 `FrameInput.next_capture_timestamp_us`,缺失时用配置预计间隔。两者都缺失、跨度超限、目标无效、近黑/饱和比例超限、拟合残差过大或预测越界时退回当前目标反馈。灰度有效但不适用于线性预测,仍可用于原反馈控制。
预测可用且处于 TRACK 目标闭环时,选用 `G_control=G_predicted`;否则使用本帧 `G_current`。只生成这一个控制输入,不另外计算并叠加前馈曝光量。`prediction.adopted=true` 表示已选入控制链;即使最终被死区或连续确认保持,也会保留采用标志和具体保持原因。
## 控制器
```text
dG = G_target - G_control 灰度级
r = dG / G_target 无量纲,0.10 为 10%
大误差:dE = alpha × r × E_actual
小误差:dE = (Kp × r + Ki × I) × E_actual
```
误差绝对值小于死区阈值时保持,并锁存死区;随后达到“死区+滞回”的阈值才重新启动。小误差必须在新的有效帧中连续同方向超差达到指定次数;方向反转、无效测光或死区会清掉确认计数。确认后若一直同方向超差,可逐个新帧继续修正,不需要额外等待。
大误差按附件第 7 章伪代码立即修正。大、小误差均经过单拍步长限制,再经过曝光上下限。当前真实曝光不在配置范围时拒绝该帧控制,避免在满足绝对范围时又无法同时满足相对步长的矛盾,不擅自把真实曝光裁剪成一个假输入。
原接口及未启用时间模式时,I 是逐次有效 PI 调节累计的相对误差,不按秒积分。仅在新目标反馈有效且小误差连续确认满足时尝试累计;积分自身限幅。若候选累计导致步长或绝对曝光限幅阻挡同方向修正,则拒绝这次积分累计,使用此前积分计算建议。死区、迟滞保持和误差方向反转清零积分,防止先前暗场积分在当前过亮时继续增加曝光。等待确认不累计,无效目标测光保持积分但清除确认计数。目标丢失、进入大误差分支、复位和初始化也清除积分。
所有建议以输入的 `actual_exposure_us` 为基准。控制器没有“上次建议已经生效”的隐含状态,不会在重复帧上继续叠加修正。
## 状态转移
| 状态 | 条件 | 动作 |
|---|---|---|
| CAPTURE | 连续检出不足要求 | 保持并积累有效历史 |
| CAPTURE | 连续检出达到要求 | 进入 TRACK,本帧保持,下一有效帧闭环 |
| CAPTURE | 无目标 | 按下面的单次恢复规则处理 |
| TRACK | 目标标志为真且测光有效 | 目标控制;全局灰度不混入误差 |
| TRACK | 目标标志为真但测光无效 | 保持、报告 InvalidMetering,不伪装成检出失败 |
| TRACK | 未检出但连续数量不足 | 保持实际曝光、清积分,保留未过期历史 |
| TRACK | 连续丢失达到要求 | 进入 LOST,设置冻结拍数 |
| LOST | 新帧到来 | 减少冻结余量;期间保持 |
| LOST | 连续检出达到要求 | 进入 TRACK,本帧保持,不恢复经验曝光 |
| LOST | 冻结结束且本帧仍无目标 | 进入 CAPTURE,本帧仍保持,后续新帧才允许恢复建议 |
检出/丢失计数以调用方明确的 `has_usable_target` 为准。LOST 冻结计数包含间歇检出的新采集帧,重复帧和拍照前查询不计数。
## 连续反馈与原目标状态机的选择
本文件其余 CAPTURE/TRACK/LOST、预测与单次恢复说明适用于原 `ExposureControl`。版本 1.2.0 新增的 `ContinuousExposureControl` 是另一种明确调用入口,持续按成像亮度闭环,不把无目标当作曝光错误,也不需要外部“工件存在”信号。
连续控制只接收已计算的全局/区域测光结果。指定全局时使用全局普通均值;指定手动 ROI 或 YOLO 区域时,区域有效优先,失效后明确回退全局。手动 ROI 不参与任何真实目标存在计数。源切换后清积分和同向计数,避免 ROI 与全局不同亮度基准相互污染。
每次 `update` 先校验配置、严格递增时间、设备实际曝光及最终选用测光;有效后才调用同一个比例/PI 控制实现。黑图是有效的零灰度,会向增曝光方向调整;饱和图会向减曝光方向调整;正常死区保持。持续无目标不消耗任何恢复预算,每张新图都可继续反馈。饱和/近黑比例保留在诊断中,本批没有另创比例阈值保护策略。
当前参考灰度由实际来源选择 `global_controller` 或 `region_controller`;配置、来源或闲置超时变化清反馈状态,拒绝帧不消费时间水位。曝光建议仍是库输出,调用方必须落实“成功写入 → 读回 → 下一帧用实际值反馈”,才能构成设备闭环;库内验证不代表相机已生效。
### 稀疏采集与外部执行器保护
`ContinuousConfig::preserve_confirmation_on_idle` 默认 `false`,保留上述历史行为。定时采集调用方可设为 `true`:自动检测到 `IdleTimeout` 时保留同向误差确认和迟滞状态,清零旧 PI 积分,并禁止该帧累积积分。这样即使每帧间隔超过 300 秒,中等偏差仍能在第二次同向有效反馈后调整,而不会永久等待第一帧确认。若下一帧间隔正常,允许重新积累积分。配置切换、来源切换和任何显式 `reset()` 仍完整清空反馈状态,包括显式 `reset(IdleTimeout)`。时间倒退仍拒绝;切换时钟必须显式 `reset(ClockChanged)`,不隐式接纳乱序帧。
外部饱和保护或执行器策略预先确定要覆盖 PI 输出时,调用方应在 **本帧 `update()` 前** 设置 `ContinuousFrameInput::suppress_integral=true`。通过校验后,该帧清零已有积分并禁止新增积分,比例项、同向确认、迟滞和时间水位继续正常推进。下一帧未设置此标志即可恢复正常积分;这不是完整状态重置。拒绝帧不会因该标志而改变任何控制状态。
这两种抑制都在 `ControlTrace::integral_suppressed` 中明确记录;自动闲置仍另外报告 `reset_reason=IdleTimeout`。标志只表示接受的反馈执行了积分抑制,不等于实际相机参数已写入。原 `ExposureControl::processFrame()` 未启用这些行为,默认源码调用保持兼容;公共结构增加成员后消费工程需要与静态库一起重新编译。
### 按实际经过时间的连续反馈
`ContinuousConfig::time_aware=true` 启用可选时序控制,默认关闭以保留旧接口节拍。时间取调用方提供的单调采集时间戳,拒绝帧不会更新时钟、滤波或确认历史。
- 默认 `nominal_interval_us=5000000`:积分乘 `min(Δt / 5秒, 1)`;间隔再长也不把空白期间当成持续误差累积。闲置抑制帧的积分系数为0。
- 默认 `smoothing_tau_us=2000000`:对 `灰度/本帧实际曝光` 做 `α=1-exp(-Δt/τ)` 的指数平滑,再映射到本帧曝光。曝光本身改变不会被当成环境变化。该局部近似不假设增益单位或增益响应;**调用方检测实际增益变化后必须 reset**。大误差、外部积分抑制、闲置或其他重置使用当前原始灰度。滤波误差与原始误差反向时也改用当前原始值,原始值已经进入死区时立即保持。
- 默认 `confirmation_us=1000000`:小误差同时满足至少2次同方向观测及持续1秒才允许调整。大误差跳过确认,仍遵守限速。
- 默认 `max_step_ratio_per_second=0.05`:曝光单次变化上限是原每帧上限与 `每秒速率×Δt秒` 中较小者。首帧尚无时间差时使用5秒基准;长间隔不超过原每帧上限。该值限制库的普通曝光建议,不声称限速外部紧急保护或相机增益变化。
`raw_control_gray` 保留原始选中灰度,`control_gray` 是本次实际控制采用值;`ControlTrace` 记录 `elapsed_us`、`filtered_gray`、`filter_alpha`、`integral_time_scale`、`effective_max_step_ratio`、`same_direction_duration_us`、`confirmation_time_satisfied` 和 `time_rate_limited`。仅时间模式填入过滤灰度;该机制做平滑和节拍约束,不进行趋势外推,也不改变原 CAPTURE/TRACK/LOST 状态机。
## 单次无目标恢复
从首次未检出开始,到连续检出达到配置数量结束,视为同一丢失过程。恢复机会独立于三态及初始化记录。
恢复需要同时满足:处于 CAPTURE、本帧无目标、本次过程尚未使用恢复机会、调用方明确设置 `allow_no_target_recovery=true`、配置了独立全局参考灰度、全局测光有效且超出恢复死区。启动时尚未进入 TRACK,可在 CAPTURE 满足这些条件后尝试一次;由 LOST 返回的 CAPTURE 则须先完成冻结。
```text
r_global = (G_reference - G_global) / G_reference
dE = clamp(gain × r_global × E_actual, ±recovery_step × E_actual)
E_next = clamp(E_actual + dE, E_min, E_max)
```
发出一次恢复建议即消耗机会;限幅后无法改变曝光也消耗这次机会并报告 ExposureLimit。之后持续未恢复只保持并输出 NotRecovered。状态切换、超时、配置切换和普通复位均不重发机会。达到稳定检出条件或显式 `NewBusinessCycle` 复位才重新开放。
全局变化只触发一个已获许可的启发式尝试,不输出“已确认曝光故障”。模块无法只凭这些输入区分隔板、空场景与光照变化,因此许可由业务提供,默认 false;业务不确定时可以保持。
## 拍照前初始化与复位
`preCapture` 检查首次启动、显式复位、配置内容/版本变化、长期停机和有效历史过期。一次检查多个条件同时成立仍只初始化一次,优先级为显式待执行复位、配置变化、停机、历史过期。初始化清历史、积分和状态计数,返回 CAPTURE 和经验曝光建议。
历史不足不等于历史过期。只有曾取得有效目标记录且它已经过期,才会触发该事件。初始化会解除旧的历史/停机事件;新的有效目标或采集活动才建立新的时间基准,因此初始空历史和持续无目标不会逐周期重新初始化。超过停机阈值后真正再次停机,是新的停机事件。
每次 `processFrame` 需要一张尚未被成功帧消耗的拍照前检查凭据,并再次检查采集时刻是否已经越过超时条件。检查遗漏或过早时返回 PreCaptureRequired,拒绝使用旧状态,不宣称已保障这张照片曝光正确。配置只有通过 `preCapture` 才会生效。
| 复位原因 | 行为差别 |
|---|---|
| Manual | 清瞬态状态,下一拍初始化;保留未结束丢失过程及恢复预算 |
| NewBusinessCycle | 同时明确开始新业务周期,重置恢复预算 |
| ClockChanged | 同时解除旧帧时间水位,允许新时钟从较小值开始;恢复预算仍保留 |
| 其他原因 | 标注调用方指定的复位来源,保留恢复预算 |
## 输出诊断与边界
`accepted` 表示该帧通过元数据和调用顺序检查并已进入状态管理,**不等于**目标测光有效;还须检查 `metering.target.valid()`。无效亮度使用 `std::optional`,不是 0。
`reason` 描述最终调整或保持原因;测光、预测分别有独立原因。预测结果包含拟合数量、时刻、斜率、残差、预测灰度和采用标志。控制跟踪包含实际误差、选用误差、积分、确认计数、原始/最终修正量及限幅标志,便于判断预测是否真正影响建议。
采集或设置曝光失败、真实场景识别率和实际成像质量由外部验证。模块不会主动发起重拍,也不把无目标等同于曝光故障。