CommonLibraries/README.md

144 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 目标测光曝光控制模块
独立 C++17 静态库,依赖 OpenCV `core`、`imgproc`。可独立计算原始图像的全局/ROI 亮度;也可通过控制实例接收采集时间及实际曝光,返回下一帧曝光建议。支持源码子目录引用和安装后的 CMake 包引用。
模块负责测光、时间窗口历史、3/5 点预测、比例/PI/死区控制及 CAPTURE/TRACK/LOST 状态管理。识别、采图、相机 SDK、机器人、三维处理、自动增益和业务节拍均由调用方负责。
## 接入方式
源码引用:
```cmake
add_subdirectory(path/to/TargetExposureControl)
target_link_libraries(your_app PRIVATE TargetExposureControl::Control)
```
安装包引用:
```cmake
find_package(TargetExposureControl 1 CONFIG REQUIRED)
target_link_libraries(your_app PRIVATE TargetExposureControl::Control)
```
配置 CMake 时设置 `OpenCV_DIR`,安装方式还需将安装前缀加入 `CMAKE_PREFIX_PATH`。安装包要求使用构建时相同版本的 OpenCV;使用其他版本时重新构建本库。编译器、架构、运行库和 Debug/Release 也应与调用工程一致。OpenCV 的运行时 DLL 由使用方部署。本库没有相机 SDK 或 Qt 依赖。
每路相机/图像流使用一个 `target_exposure::ExposureControl` 实例。不同实例状态隔离,同一实例由调用方串行调用。所有曝光单位为 **微秒 us**,所有时间戳也使用同一时钟域下的非负微秒;两者语义不同,不可相互替代。
## 独立离线测光(1.1.0)
只计算保存图像时,直接调用 `measureImage`,不创建控制实例、不设置曝光或时间,也不调用 `preCapture`。以下变量由调用方提供:
```cpp
target_exposure::Roi roi;
roi.rectangles = final_metering_rects; // 可换成旋转框或实际分割掩膜
const auto measurement = target_exposure::measureImage(original_image, roi, roi_is_usable);
// measurement.global.arithmetic_gray:全局普通平均灰度。
// measurement.target.arithmetic_gray:ROI 普通平均灰度。
// measurement.target.gray:ROI 截尾均值,保留既有控制算法的统计口径。
```
第四个参数为可选的 `MeteringConfig`,默认整图测光、目标区域两端各截尾 5%、至少 16 个原始像素和 8 个保留像素。只计算全局时传空 `Roi` 和 `false`。`roi_is_usable` 只表示存在可测光的区域;手绘 ROI 有效不代表工件已到位或已识别。
每个区域使用一次像素直方图,同时输出普通均值、截尾均值、原始/保留像素数和截尾前的近黑/饱和比例。目标样本不足时两个均值均为空,不能用零替代;全局不受目标区域无效影响。配置非法时两个区域均返回 `MeterStatus::InvalidConfiguration`;图像、ROI 错误由对应状态报告。无有效区域时不检查闲置 ROI 内容,全局照常计算。
这是无状态函数,同一输入可重复计算,结果不受曝光控制实例的历史影响;调用期间原图和 mask 均不被修改或保留。允许在输入数据不被并发修改的前提下并发测光。采集时间、处理耗时、模型来源及 CSV/JSON 存档由应用程序关联,不由测光函数生成。
`processFrame()` 使用同一测光实现,控制仍使用原 `gray` 字段。新增字段追加在原有数据字段之后,保留原源码的聚合初始化顺序;新增数据字段不保证旧二进制 ABI,应用和静态库应一起重新编译。
## 曝光控制调用顺序
1. 准备本工况的 `Config`。必须提供最小、最大和经验曝光;静止采图上限由业务节拍确定。
2. **拍照前**调用 `preCapture(now_us, config)`。检查 `accepted`;若 `initialization_required`,由调用方先成功设置 `suggested_exposure_us`,再采集。
3. 调用方采集原始图像并识别,读取这一帧真正使用的曝光值,构造 `FrameInput`。
4. 调用 `processFrame(input)`,检查输入接收状态、测光有效性及原因。仅当 `adjustment_suggested=true` 时,由调用方将建议应用于下一帧。
5. 每一帧都重新执行拍照前检查。建议未成功应用时,不把建议值当实际曝光。初始化设置失败时由业务重试设置或显式复位,不继续假装初始化成功。
可编译运行的完整示例见 [examples/minimal.cpp](examples/minimal.cpp),配置见 [examples/static_station_config.hpp](examples/static_station_config.hpp)。示例由外部循环模拟图像和曝光应用,模块本身不采图、不等待、不循环试曝光。
```cpp
target_exposure::ExposureControl exposure;
target_exposure::Config config;
config.min_exposure_us = 100;
config.max_exposure_us = 20000; // 仅作示例,替换为本工况上限
config.empirical_exposure_us = 1000; // 替换为本工况经验值
// 拍照前:
auto prepare = exposure.preCapture(now_us, config);
// accepted=false 时处理错误;initialization_required=true 时先由业务设置曝光。
// 业务完成采图、识别后:
target_exposure::FrameInput input;
input.image = original_image; // CV_8UC1 或 CV_8UC3(BGR)
input.roi.rectangles = final_metering_rects; // 也可叠加旋转矩形或 masks
input.has_usable_target = detector_has_target;
input.timestamp_us = captured_at_us;
input.actual_exposure_us = actual_frame_exposure_us;
auto result = exposure.processFrame(input);
// result.adjustment_suggested 为 true 时,由业务设置 result.suggested_exposure_us。
```
上面的变量来自调用工程,片段用于说明接口顺序;独立运行使用完整示例。
## 输入和测光契约
- 支持 Gray8 和 BGR8。BGR 使用 OpenCV 固定 `COLOR_BGR2GRAY` 转换;不做归一化、gamma 调整或自动增强。不接受画框图、增强图和未经解释的 Bayer/12/16 bit 数据。
- `Roi` 中矩形、旋转矩形及 mask 可以混用,按像素取并集,重叠只统计一次。mask 必须为与图像同尺寸的 `CV_8UC1`,非零即选中。
- 普通矩形使用 OpenCV 的左闭右开范围,先裁剪到图像;旋转矩形按像素中心是否位于旋转后的半开矩形内判断,不使用其包围框直接测光。
- 输入 ROI 被视为最终结果,不隐式内缩。`inset(rect, fraction_per_side)` 是独立、显式辅助函数;比例指每一侧内缩量,整数矩形向内取整。
- `arithmetic_gray` 为截尾前的普通平均;全局 `gray` 与其相同,`global_center_fraction<1` 时统计固定比例的居中矩形。目标 `gray` 在并集内上下各删除 `floor(N × trim_fraction_each_side)` 个样本后求平均。
- 无目标、ROI 空/格式错误、像素不足均有明确状态,`gray`、`arithmetic_gray` 为 `std::nullopt`。真实黑图的灰度 0 可以是有效测量。
- 非有限、非正或超出当前配置范围的实际曝光属于输入契约错误,返回 `InvalidActualExposure`,保留错误实参用于诊断,不伪造实际曝光或产生调整建议。调用方应修正配置/曝光状态后重走拍照前流程。
- 同一实例内帧时间必须严格递增。重复、乱序及缺少拍照前检查的帧不更新控制状态。相机时间重置时显式 `reset(ResetReason::ClockChanged)`。
- 图像只在调用期间读取,不改原图、不保存图像副本;调用期间调用方须保证数据有效且不被其他线程修改。
## 关键配置
所有控制参数都在公共头文件的强类型结构体中配置,无额外配置文件解析依赖。
| 配置 | 默认/示例 | 含义 |
|---|---|---|
| `min/max/empirical_exposure_us` | 默认未设置,必须由调用方填写 | 曝光范围及初始化曝光 |
| `profile_id`、`revision` | default、0 | 工况身份;任何参数或版本变化也会在拍照前初始化 |
| `history_window_us`、`idle_timeout_us` | 各 300000000 us | 历史窗口与停机阈值分别判断 |
| `history_capacity` | 512 | 帧诊断和有效目标各自保留最多此数量;不得小于预测点数 |
| `detect_confirm_frames/lost_confirm_frames/freeze_frames` | 3/3/2 | 三态连续计数和冻结拍数 |
| `metering.trim_fraction_each_side` | 0.05 | 每侧截尾比例 |
| `metering.min_target_pixels/min_retained_pixels` | 16/8 | 截尾前、后最少像素 |
| `metering.global_center_fraction` | 1.0 | 全图;可配置为 0~1 之间的中央区域比例 |
| `controller.target_gray` | 118 | 8 bit 标度下的目标灰度示例 |
| `controller.deadband_ratio/hysteresis_ratio/large_error_ratio` | 0.10/0.02/0.40 | 相对误差阈值,0.10 表示 10% |
| `controller.proportional_gain/kp/ki` | 0.70/0.40/0.10 | 附件比例分支及离散 PI 系数示例 |
| `controller.integral_abs_limit/max_step_ratio` | 1.0/0.25 | 积分幅度上限、大小误差统一步长上限 |
| `controller.same_direction_frames` | 2 | 小误差连续同方向确认;大误差按已确认规则立即动作 |
| `prediction.points` | 3,可选 5 | 只使用最近指定数量的有效目标记录 |
| `prediction.enabled/photometric_model_confirmed` | true/false | 必须确认光度模型前提后才采用预测;未确认仍正常反馈控制 |
| `prediction.black_level` | 0.0 | 已知黑电平偏置,不从场景中猜测 |
| `prediction.nominal_interval_us/max_horizon_us` | 0/60000000 us | 未传下一拍时刻时的备用间隔;0 表示缺失,无时距则降级 |
| `prediction.max_relative_fit_rmse` | 0.10 | 归一化响应拟合的相对残差上限 |
| `prediction.max_near_black_fraction/max_saturated_fraction` | 0.10/0.05 | 排除失去线性响应的记录;截尾前统计 |
| `prediction.min_predicted_gray/max_predicted_gray` | 1/254 | 预测适用范围 |
| `recovery.global_reference_gray` | 未设置 | 独立全局参考,不自动等于目标灰度 |
| `recovery.deadband_ratio/gain/max_step_ratio` | 0.30/0.30/0.10 | 单次恢复判据与保守比例修正 |
所有示例值只用于演示配置和逻辑,不能代表所有工件。调用方选择黑件/白件或不同工况配置。参数错误由 `validate(config)` 或 `preCapture().errors` 报告。
详细预测公式、三态和恢复预算语义见 [控制与状态说明](docs/控制与状态说明.md)。
## 构建与验证
Windows x64、VS 2022/MSVC 下,原控制版本使用 OpenCV 4.7 验证;当前独立测光版本使用与 MatchVerify 相同的 OpenCV 4.10 重新构建验证。源码构建不硬编码本机路径;以下命令使用本机实际依赖位置。
```powershell
pwsh -NoProfile -File tools/verify.ps1 `
-BuildRoot E:/05_Temp/TargetExposureControl/capture-metering `
-OpenCVDir E:/06_EnvConfig/opencv/build/x64/vc16/lib `
-OpenCVRuntimeDir E:/06_EnvConfig/opencv/build/x64/vc16/bin
```
脚本依次配置、构建 Release、运行全部分组 CTest 和最小示例、安装库,再构建并运行只引用安装包的独立消费工程。完整日志位于构建根目录的 `verification.log`。所有测试使用模拟时间和内存图像,不连接相机。
OpenCV 4.7 Windows 包的顶层配置不识别某些新 MSVC 版本。脚本发现已存在的 `x64/vc16/lib/OpenCVConfig.cmake` 时直接使用该包配置;没有修改 OpenCV。手动配置时可将 `OpenCV_DIR` 设到该目录。其他 OpenCV 布局可传具体包目录和 `-OpenCVRuntimeDir`。
源码作为子目录接入时默认不构建示例与测试;可通过 `TEC_BUILD_TESTS`、`TEC_BUILD_EXAMPLES` 控制。安装包内还包含中文说明与完整示例。
实际命令、结果及剩余验证边界见 [实施记录](实施记录.md)。构建、模拟控制测试和安装接入验证不能证明真实相机参数生效、实际成像改善或现场验收。